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>
|
||||
<SelectionState runConfigName="app">
|
||||
<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 runConfigName="MainActivity">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
|
@ -1,27 +1,62 @@
|
||||
package com.shielddagger.auth.oidc_debugger
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
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.fillMaxWidth
|
||||
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.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.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.platform.LocalContext
|
||||
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 kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
OIDCDebuggerTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Greeting(
|
||||
name = "Android",
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(title = {
|
||||
Text("Issuer Configuration")
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
) { innerPadding ->
|
||||
IssuerForm(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
}
|
||||
@ -30,18 +65,87 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Hello $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
fun IssuerForm(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
var client: OIDCCore? = null;
|
||||
|
||||
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)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
OIDCDebuggerTheme {
|
||||
Greeting("Android")
|
||||
IssuerForm()
|
||||
}
|
||||
}
|
@ -1,19 +1,49 @@
|
||||
package com.shielddagger.auth.oidc_debugger
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
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.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.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 kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.json.JSONObject
|
||||
|
||||
class ValidateActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -23,10 +53,16 @@ class ValidateActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
OIDCDebuggerTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Greeting2(
|
||||
data = data.toString(),
|
||||
action = action.toString(),
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = {TopAppBar(
|
||||
title = {
|
||||
Text("Response Analysis")
|
||||
}
|
||||
)}
|
||||
) { innerPadding ->
|
||||
Analysis(
|
||||
data = data,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
}
|
||||
@ -35,18 +71,239 @@ class ValidateActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Composable
|
||||
fun Greeting2(data: String, action: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "$data\n$action",
|
||||
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 = "Authorization State",
|
||||
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)
|
||||
@Composable
|
||||
fun GreetingPreview2() {
|
||||
OIDCDebuggerTheme {
|
||||
Greeting2("data", "action")
|
||||
Analysis(null)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.shielddagger.auth.oidc_debugger.oidc
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.android.volley.Request
|
||||
import com.android.volley.Response
|
||||
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"),
|
||||
UNAUTHORIZED_CLIENT("unauthorized_client", "Unauthorized Client"),
|
||||
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 {
|
||||
return this.type
|
||||
@ -110,6 +112,8 @@ data class TokenResponse(
|
||||
val tokenType: OIDCTokenType? = null,
|
||||
val expiresIn: Int? = null,
|
||||
val refreshToken: String? = null,
|
||||
val refreshExpiresIn: Int? = null,
|
||||
val idToken: String? = null,
|
||||
val scope: List<String>? = null
|
||||
)
|
||||
|
||||
@ -218,16 +222,28 @@ class OIDCCore(
|
||||
)
|
||||
}
|
||||
|
||||
Log.d("oidccore", "validateTokenResponse: ${response.toString(4)}")
|
||||
|
||||
return TokenResponse(
|
||||
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,
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
fun getUserinfo(accessToken: String?): JsonObjectRequest? {
|
||||
return null
|
||||
fun getUserinfo(accessToken: String,
|
||||
responseHandler: Response.Listener<JSONObject> = Response.Listener {},
|
||||
errorHandler: Response.ErrorListener = Response.ErrorListener {}): JsonObjectRequest {
|
||||
return JsonAuthRequest(
|
||||
Request.Method.GET,
|
||||
userinfoUri,
|
||||
accessToken,
|
||||
responseHandler,
|
||||
errorHandler
|
||||
)
|
||||
}
|
||||
}
|
@ -47,4 +47,19 @@ class JsonFormRequest(
|
||||
}
|
||||
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="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="success">#FF009900</color>
|
||||
<color name="failure">#FF990000</color>
|
||||
</resources>
|
@ -20,4 +20,5 @@ kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# 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