Implement basic OIDC debugging functionality
This commit is contained in:
parent
ade0f62568
commit
5427aa2032
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-01-29T21:02:35.084938400Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=38091JEHN10130" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="MainActivity">
|
<SelectionState runConfigName="MainActivity">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
@ -1,27 +1,62 @@
|
|||||||
package com.shielddagger.auth.oidc_debugger
|
package com.shielddagger.auth.oidc_debugger
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.ClientAuthType
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.OIDCCore
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.OIDCResponseType
|
||||||
import com.shielddagger.auth.oidc_debugger.ui.theme.OIDCDebuggerTheme
|
import com.shielddagger.auth.oidc_debugger.ui.theme.OIDCDebuggerTheme
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
OIDCDebuggerTheme {
|
OIDCDebuggerTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(
|
||||||
Greeting(
|
modifier = Modifier.fillMaxSize(),
|
||||||
name = "Android",
|
topBar = {
|
||||||
|
TopAppBar(title = {
|
||||||
|
Text("Issuer Configuration")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
) { innerPadding ->
|
||||||
|
IssuerForm(
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -30,18 +65,87 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
fun IssuerForm(modifier: Modifier = Modifier) {
|
||||||
Text(
|
val context = LocalContext.current
|
||||||
text = "Hello $name!",
|
var client: OIDCCore? = null;
|
||||||
modifier = modifier
|
|
||||||
|
if (context.fileList().contains("issuerConfig")) {
|
||||||
|
val ifo = context.openFileInput("issuerConfig")
|
||||||
|
try {
|
||||||
|
client = Json.decodeFromStream<OIDCCore>(ifo);
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
Log.e("persistence", "IssuerForm: Unable to parse issuerConfig, ignoring")
|
||||||
|
}
|
||||||
|
ifo.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var authorizeUrl by remember { mutableStateOf(client?.authorizeUri ?: "") }
|
||||||
|
var tokenUrl by remember { mutableStateOf(client?.tokenUri ?: "") }
|
||||||
|
var userinfoUrl by remember { mutableStateOf(client?.userinfoUri ?: "") }
|
||||||
|
var clientId by remember { mutableStateOf(client?.clientId ?: "") }
|
||||||
|
var clientSecret by remember { mutableStateOf(client?.clientSecret ?: "") }
|
||||||
|
var scopes by remember { mutableStateOf(client?.scope ?: listOf("")) }
|
||||||
|
|
||||||
|
Column (modifier = modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
TextField(authorizeUrl, { authorizeUrl = it },
|
||||||
|
label = { Text("Authorize URL")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
TextField(tokenUrl, { tokenUrl = it },
|
||||||
|
label = { Text("Token URL")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
TextField(userinfoUrl, { userinfoUrl = it },
|
||||||
|
label = { Text("Userinfo URL")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
TextField(clientId, { clientId = it },
|
||||||
|
label = { Text("Client ID")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
TextField(clientSecret, { clientSecret = it },
|
||||||
|
label = { Text("Client Secret")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
TextField(scopes.joinToString(" "), { scopes = it.split(" ")},
|
||||||
|
label = { Text("Scopes")},
|
||||||
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
Button({
|
||||||
|
val oidcClient = OIDCCore(
|
||||||
|
listOf(OIDCResponseType.CODE),
|
||||||
|
scopes,
|
||||||
|
clientId,
|
||||||
|
context.resources.getString(R.string.redirect_uri),
|
||||||
|
authorizeUrl,
|
||||||
|
tokenUrl,
|
||||||
|
userinfoUrl,
|
||||||
|
clientSecret,
|
||||||
|
ClientAuthType.POST
|
||||||
)
|
)
|
||||||
|
val authUri = oidcClient.beginAuth()
|
||||||
|
Log.d("oidcdebugger", "IssuerForm: authUri: $authUri")
|
||||||
|
|
||||||
|
val of = context.openFileOutput("issuerConfig", Context.MODE_PRIVATE)
|
||||||
|
val bv = of.bufferedWriter()
|
||||||
|
val data = Json.encodeToString(oidcClient)
|
||||||
|
bv.write(data)
|
||||||
|
bv.close()
|
||||||
|
of.close()
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, authUri)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}) {
|
||||||
|
Text("Authorize")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun GreetingPreview() {
|
fun GreetingPreview() {
|
||||||
OIDCDebuggerTheme {
|
OIDCDebuggerTheme {
|
||||||
Greeting("Android")
|
IssuerForm()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,19 +1,49 @@
|
|||||||
package com.shielddagger.auth.oidc_debugger
|
package com.shielddagger.auth.oidc_debugger
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ElevatedCard
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.android.volley.toolbox.Volley
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.OIDCCore
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.OIDCTokenErrorResponse
|
||||||
|
import com.shielddagger.auth.oidc_debugger.oidc.TokenResponse
|
||||||
import com.shielddagger.auth.oidc_debugger.ui.theme.OIDCDebuggerTheme
|
import com.shielddagger.auth.oidc_debugger.ui.theme.OIDCDebuggerTheme
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
class ValidateActivity : ComponentActivity() {
|
class ValidateActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@ -23,10 +53,16 @@ class ValidateActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
OIDCDebuggerTheme {
|
OIDCDebuggerTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(
|
||||||
Greeting2(
|
modifier = Modifier.fillMaxSize(),
|
||||||
data = data.toString(),
|
topBar = {TopAppBar(
|
||||||
action = action.toString(),
|
title = {
|
||||||
|
Text("Response Analysis")
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
) { innerPadding ->
|
||||||
|
Analysis(
|
||||||
|
data = data,
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -35,18 +71,239 @@ class ValidateActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Greeting2(data: String, action: String, modifier: Modifier = Modifier) {
|
fun Analysis(data: Uri?, modifier: Modifier = Modifier) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var client: OIDCCore? = null;
|
||||||
|
|
||||||
|
val tokenExchange = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
val tokenData = remember {
|
||||||
|
mutableStateOf(TokenResponse())
|
||||||
|
}
|
||||||
|
|
||||||
|
val userinfoExchange = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
val userinfoError = remember {
|
||||||
|
mutableStateOf<String?>(null)
|
||||||
|
}
|
||||||
|
val userinfoData = remember {
|
||||||
|
mutableStateOf(JSONObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.fileList().contains("issuerConfig")) {
|
||||||
|
val ifo = context.openFileInput("issuerConfig")
|
||||||
|
try {
|
||||||
|
client = Json.decodeFromStream<OIDCCore>(ifo);
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
Log.e("persistence", "IssuerForm: Unable to parse issuerConfig, ignoring")
|
||||||
|
}
|
||||||
|
ifo.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column (modifier = modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
ElevatedCard(
|
||||||
|
elevation = CardDefaults.cardElevation(
|
||||||
|
defaultElevation = 6.dp
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "$data\n$action",
|
text = "Authorization State",
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
if (client == null) {
|
||||||
|
Text(
|
||||||
|
"Client Missing",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
if (data == null) {
|
||||||
|
Text(
|
||||||
|
"Data Missing",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
|
||||||
|
client.validateAuthResponse(data).forEach {
|
||||||
|
Text(
|
||||||
|
it.message,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
color = if (it.success) Color(context.resources.getColor(R.color.success))
|
||||||
|
else Color(context.resources.getColor(R.color.failure))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
|
||||||
|
}
|
||||||
|
ElevatedCard(
|
||||||
|
elevation = CardDefaults.cardElevation(
|
||||||
|
defaultElevation = 6.dp
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Token Exchange",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (tokenExchange.value) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenData.value.error != null) {
|
||||||
|
Text(
|
||||||
|
text="Error: ${tokenData.value.error!!.message}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Type: ${tokenData.value.tokenType.toString()}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Scopes: ${tokenData.value.scope?.joinToString(", ")}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Expires In: ${tokenData.value.expiresIn?.toString() ?: Int.MAX_VALUE}s",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Refresh Token: ${tokenData.value.refreshToken != null}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "ID Token: ${tokenData.value.idToken != null}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
ElevatedCard(
|
||||||
|
elevation = CardDefaults.cardElevation(
|
||||||
|
defaultElevation = 6.dp
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "User Info",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userinfoExchange.value) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
if (userinfoError.value != null){
|
||||||
|
Text(
|
||||||
|
userinfoError.value!!,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp),
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
return@ElevatedCard
|
||||||
|
}
|
||||||
|
|
||||||
|
userinfoData.value.keys().forEach {
|
||||||
|
Text(
|
||||||
|
"$it: ${userinfoData.value[it]}",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(32.dp, 0.dp, 0.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null){
|
||||||
|
tokenExchange.value = false
|
||||||
|
tokenData.value = TokenResponse(OIDCTokenErrorResponse.INVALID_CLIENT)
|
||||||
|
userinfoExchange.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val queue = Volley.newRequestQueue(context)
|
||||||
|
|
||||||
|
val tokenRequest = client.getTokenFromCode(data!!, { tokenResponse ->
|
||||||
|
val tokenResponseData = client.validateTokenResponse(tokenResponse)
|
||||||
|
tokenData.value = tokenResponseData
|
||||||
|
tokenExchange.value = false
|
||||||
|
|
||||||
|
if (tokenResponseData.error != null) {
|
||||||
|
return@getTokenFromCode
|
||||||
|
}
|
||||||
|
|
||||||
|
val userinfoRequest = client.getUserinfo(tokenData.value.accessToken!!, { userdataResponse ->
|
||||||
|
userinfoData.value = userdataResponse
|
||||||
|
userinfoExchange.value = false
|
||||||
|
Log.d("oidcdebugger", "userinfo: ${userdataResponse.toString(4)}")
|
||||||
|
}, {
|
||||||
|
userinfoError.value = (it.message ?: "Unknown Error")
|
||||||
|
userinfoExchange.value = false
|
||||||
|
Log.d("oidcdebugger", it.message.toString())
|
||||||
|
})
|
||||||
|
queue.add(userinfoRequest)
|
||||||
|
}, {
|
||||||
|
tokenData.value = TokenResponse(OIDCTokenErrorResponse.INVALID_REQUEST)
|
||||||
|
tokenExchange.value = false
|
||||||
|
userinfoExchange.value = false
|
||||||
|
})
|
||||||
|
queue.add(tokenRequest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun GreetingPreview2() {
|
fun GreetingPreview2() {
|
||||||
OIDCDebuggerTheme {
|
OIDCDebuggerTheme {
|
||||||
Greeting2("data", "action")
|
Analysis(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.shielddagger.auth.oidc_debugger.oidc
|
package com.shielddagger.auth.oidc_debugger.oidc
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import com.android.volley.Request
|
import com.android.volley.Request
|
||||||
import com.android.volley.Response
|
import com.android.volley.Response
|
||||||
import com.android.volley.toolbox.JsonObjectRequest
|
import com.android.volley.toolbox.JsonObjectRequest
|
||||||
@ -74,7 +75,8 @@ enum class OIDCTokenErrorResponse(private val type: String, val message: String)
|
|||||||
INVALID_GRANT("invalid_grant", "Invalid Grant"),
|
INVALID_GRANT("invalid_grant", "Invalid Grant"),
|
||||||
UNAUTHORIZED_CLIENT("unauthorized_client", "Unauthorized Client"),
|
UNAUTHORIZED_CLIENT("unauthorized_client", "Unauthorized Client"),
|
||||||
UNAUTHORIZED_GRANT_TYPE("unsupported_grant_type", "Unauthorized Grant Type"),
|
UNAUTHORIZED_GRANT_TYPE("unsupported_grant_type", "Unauthorized Grant Type"),
|
||||||
INVALID_SCOPE("invalid_scope", "Invalid Scope");
|
INVALID_SCOPE("invalid_scope", "Invalid Scope"),
|
||||||
|
BAD_STATE("bad_state", "Incorrect Session State");
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return this.type
|
return this.type
|
||||||
@ -110,6 +112,8 @@ data class TokenResponse(
|
|||||||
val tokenType: OIDCTokenType? = null,
|
val tokenType: OIDCTokenType? = null,
|
||||||
val expiresIn: Int? = null,
|
val expiresIn: Int? = null,
|
||||||
val refreshToken: String? = null,
|
val refreshToken: String? = null,
|
||||||
|
val refreshExpiresIn: Int? = null,
|
||||||
|
val idToken: String? = null,
|
||||||
val scope: List<String>? = null
|
val scope: List<String>? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -218,16 +222,28 @@ class OIDCCore(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("oidccore", "validateTokenResponse: ${response.toString(4)}")
|
||||||
|
|
||||||
return TokenResponse(
|
return TokenResponse(
|
||||||
accessToken = response.getString("access_token"),
|
accessToken = response.getString("access_token"),
|
||||||
tokenType = OIDCTokenType.fromString(response.getString("token_type")),
|
tokenType = OIDCTokenType.fromString(response.getString("token_type").lowercase()),
|
||||||
|
idToken = response.getString("id_token"),
|
||||||
expiresIn = if (response.has("expires_in")) response.getInt("expires_in") else null,
|
expiresIn = if (response.has("expires_in")) response.getInt("expires_in") else null,
|
||||||
refreshToken = if (response.has("refresh_token")) response.getString("refresh_token") else null,
|
refreshToken = if (response.has("refresh_token")) response.getString("refresh_token") else null,
|
||||||
|
refreshExpiresIn = if (response.has("refresh_expires_in")) response.getInt("refresh_expires_in") else null,
|
||||||
scope = if (response.has("scope")) response.getString("scope").split(" ") else null
|
scope = if (response.has("scope")) response.getString("scope").split(" ") else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserinfo(accessToken: String?): JsonObjectRequest? {
|
fun getUserinfo(accessToken: String,
|
||||||
return null
|
responseHandler: Response.Listener<JSONObject> = Response.Listener {},
|
||||||
|
errorHandler: Response.ErrorListener = Response.ErrorListener {}): JsonObjectRequest {
|
||||||
|
return JsonAuthRequest(
|
||||||
|
Request.Method.GET,
|
||||||
|
userinfoUri,
|
||||||
|
accessToken,
|
||||||
|
responseHandler,
|
||||||
|
errorHandler
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,3 +48,18 @@ class JsonFormRequest(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class JsonAuthRequest(
|
||||||
|
method: Int,
|
||||||
|
url: String?,
|
||||||
|
private var token: String,
|
||||||
|
listener: Response.Listener<JSONObject>?,
|
||||||
|
errorListener: Response.ErrorListener?
|
||||||
|
) : JsonObjectRequest(method, url, null, listener, errorListener) {
|
||||||
|
override fun getHeaders(): MutableMap<String, String> {
|
||||||
|
val headers = mutableMapOf<String,String>()
|
||||||
|
headers.putAll(super.getHeaders())
|
||||||
|
headers["Authorization"] = "Bearer $token"
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,6 @@
|
|||||||
<color name="teal_700">#FF018786</color>
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
<color name="success">#FF009900</color>
|
||||||
|
<color name="failure">#FF990000</color>
|
||||||
</resources>
|
</resources>
|
@ -21,3 +21,4 @@ kotlin.code.style=official
|
|||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
org.gradle.configuration-cache=true
|
Loading…
x
Reference in New Issue
Block a user