Skip to content

Commit

Permalink
Working request with manual token
Browse files Browse the repository at this point in the history
  • Loading branch information
Iulia Stana committed Mar 7, 2023
1 parent be8807e commit 20ed429
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 20 deletions.
8 changes: 8 additions & 0 deletions app/src/main/kotlin/nl/eduid/di/AppQualifiers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nl.eduid.di

import javax.inject.Qualifier

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
internal annotation class EduIdScope
5 changes: 4 additions & 1 deletion app/src/main/kotlin/nl/eduid/di/api/EduIdApi.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nl.eduid.di.api

import nl.eduid.di.model.RequestNewIdRequest
import org.tiqr.data.api.TokenApi
import nl.eduid.di.model.UserDetails
import retrofit2.Response
import retrofit2.http.*

Expand All @@ -11,4 +11,7 @@ import retrofit2.http.*
interface EduIdApi {
@POST("myconext/api/idp/magic_link_request/")
suspend fun requestNewEduId(@Body request: RequestNewIdRequest): Response<String>

@GET("mobile/api/sp/me")
suspend fun getUserDetails(): Response<UserDetails>
}
42 changes: 42 additions & 0 deletions app/src/main/kotlin/nl/eduid/di/auth/TokenAuthenticator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package nl.eduid.di.auth

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import timber.log.Timber
import javax.inject.Inject

class TokenAuthenticator @Inject constructor(
private val tokenProvider: TokenProvider
) : Authenticator {

private var currentToken: String? =
"eyJraWQiOiJrZXlfMjAyM18wM18wN18wMF8wMF8wMF8wMTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiZGV2LmVnZW5pcS5ubCIsIm15Y29uZXh0LnJzIl0sInN1YiI6ImRldi5lZ2VuaXEubmwiLCJuYmYiOjE2NzgxNzI2MDYsInNjb3BlIjoiZWR1aWQubmxcL21vYmlsZSIsImlzcyI6Imh0dHBzOlwvXC9jb25uZWN0LnRlc3QyLnN1cmZjb25leHQubmwiLCJjbGFpbXMiOiJBUTkxeUt6RTRRTW5odTdkaXZuMGRlQ1wvMm5CaWY1M1Jpa1laVWFPWktiNkVaQWJnbzdZY1RKSFNiQTVWeVwvejg4Y3JOYmZSQmJscnNqNDk3eGc2cXhaNll2YStXNkdoOFpTXC8xakxRQ2QzV3E1b05hajlkZkFJQjVLWDRBXC9hSEhXUDN6M0wycmlUSitJTlF5Uk5BOTkrUDZ0UWFPSWd3aFwvYmRUcllRZEVNQU1GeXVwSkh1b0tDU0pZR2kwNDY3Nnp5aGNkeXVncU9WOG1nR3JvTVFWZExKdTBFRzJ3V0laSVhUUmlsbXJmdzhqYittaW5wS01mY0pRb1NQYWVLbVNJdWx1OUU3WW14YmI3Z09kMEpMN3BkY2lrZUFId1pvazBCdDg5XC8rcnNzNlFxbUxWQVRGa1Qyb0YzK0h6amowVGtmdFFIRW44OGZOS1BoUGQ4U1wvS25EQytpTDlpQ3BEZkdcL2xpWWw4SUJHYUNwRnpOWjROd2JxaEM3YnpiVkZiVEdSK3ZTa2JqRTlFZ00yXC9UZk9KckNRaDM0c1VKXC9nVUJOc1JnV1EyeUpYSkl5Rk9IZWtiR3gyV0o1WkUxZ1wvV0pzcENZYXRUTFhIQ2pvSU9YdkVRRWNmZlNYWlFWbThBb09hbG9OWEFuNDZ1UjBZRXdtNk9xekFkcjlVcEp1Tjg1VVlYZDcxMStCcFEyQW1FQ3BMMWJYXC8zaFlcL1lhZzNMQzZsV2xROERuS1VkXC9xUitJMmdmbzlaZmk2R1NqOVl3dWxhbHBrcjdSOWpnTWNYQTZlRWc0R0JUdEdGK1BGdlwvaFQxMXBLZ3RQWlZqeVA1dlI5KzRDOE1qY1NtU0s1bzg2eitqVkk3Y1RkSk1odjRRSFA2ZUF2aFRoc0dvRElLV2wwbjVIIiwiZXhwIjoxNjc4MTc2MjA2LCJjbGFpbV9rZXlfaWQiOiIyNTkzNzczMjQiLCJpYXQiOjE2NzgxNzI2MDYsImp0aSI6ImJkZWFmOTU2LWExZjUtNDFjZC1hMTVmLWY4MGZhMzM2ZDA0OSJ9.pXguHBZ8eor1CdNmWZKZaJW4jdeGQGrz6XIBZf13-ZUkr3HQu5KlH9RT4wIDF_3cnfmlqqE6tn5DOBG-4xdyVbfcbyEM4seckT55XcturQlrW-QXL9v6jq2jk5QAp0b8Foj7LYGc1pqPIaPQj_WaOBq8ddtrY8uqf4npapM_5nNe8w2z3Y6F3xQ4rkgfyJ1bKtupYJK7aGEBhbN75hkyV4TbkWh9Jwv2cBazxpLfUNbg05W32PFcrTyNIX5lslu-meCzSe6SKctLIjfFdQPpvZlPDXF_hoiyBW-r98SpQOf0CQBHyvYu4YubpQXXP5EvUkLZPM02zLJTUun2EIRAng"

override fun authenticate(route: Route?, response: Response): Request? {
synchronized(this) {
Timber.e("Authenticator intercept. Running on: ${Thread.currentThread().name}")
val previousToken = currentToken
return response.request.newBuilder().header(
"Authorization", "Bearer $currentToken"
).build()

// return runBlocking(Dispatchers.IO) {
// Timber.e("Blocking. Running on: ${Thread.currentThread().name}")
// val token = tokenProvider.refreshToken()
//
// if (token != previousToken) {
// currentToken = token
// response.request.newBuilder().header(
// "Authorization", "Bearer $token"
// ).build()
// } else {
// null
// }
// }
}
}
}
26 changes: 26 additions & 0 deletions app/src/main/kotlin/nl/eduid/di/auth/TokenInterceptor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nl.eduid.di.auth

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject

class TokenInterceptor @Inject constructor(private val tokenProvider: TokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.header("Authorization") == null) {
return runBlocking(Dispatchers.IO) {
val token = tokenProvider.getToken()
if (token != null) {
chain.proceed(
request.newBuilder().addHeader("Authorization", "Bearer $token").build()
)
} else {
chain.proceed(request)
}
}
}
return chain.proceed(request)
}
}
38 changes: 38 additions & 0 deletions app/src/main/kotlin/nl/eduid/di/auth/TokenProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nl.eduid.di.auth

import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import nl.eduid.di.repository.StorageRepository
import timber.log.Timber
import java.util.*
import kotlin.NoSuchElementException

class TokenProvider(private val repository: StorageRepository) {
private var token: String? =
"eyJraWQiOiJrZXlfMjAyM18wM18wMl8wMF8wMF8wMF8wMDAiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiZGV2LmVnZW5pcS5ubCIsIm15Y29uZXh0LnJzIl0sInN1YiI6ImRldi5lZ2VuaXEubmwiLCJuYmYiOjE2Nzc3NzMxNjMsInNjb3BlIjoiZWR1aWQubmxcL21vYmlsZSIsImlzcyI6Imh0dHBzOlwvXC9jb25uZWN0LnRlc3QyLnN1cmZjb25leHQubmwiLCJjbGFpbXMiOiJBUTkxeUt4S3UrOEFjcFlhd0lYdTVwK1lFK01RSFpocTJiUVBMYlZMTE9IWmt0dUJBMDFTNXZ4M2dtVmpzdllldFYyMFVtVGJFMDl6SjMzSENKNk1kVjE3SWxGSDdiK1ZpMUkyKzBFQThWVFdWdlwvQkpmcnkxcFBLVGJ4dWdMbFNaN0tHRGdzamp3VU9DWW8wS3JmTmYxK0VZVUMyeWIxdXV0a0lFWWx1WHZRT2RCY1JicjRCellpbkFSMUY3Y0FcL2swVUlWSFpSRU9wVjRCejNuODc0SWZxOFJZT2NsaldkUjF2XC9cL0V3Q00zUDRCM2s4dFRLVnBzaU13Y0tjVlZ6dWhIWkZaZHFONVhUMWZXSHhNMW1RekFoQVpJdnFSbXpYOGx5MFEzTXU4ZEdQSE9SS1h5djgzUkFBRDBXbmpFYWhUXC9zNUppWnowUEtZdm9xSmV0WTZieUF5cVdMQUwzMDVDOUpHaXRCZGxtdjdvcWFWczJDeFNnSGhydFNSQWZSSXAxQURyU2oxWktLaVBBS3NuMDNuZ3duK250d1wvRnBVVU1kQ1Y3SU95QWtxS2lHREJsVUJ4aDlNK0N6M3V5TUtiYmlnUVZHV1pzMUZONmt3b01oTm43MllQbzRCZjBLNnNqVll5VUVzMWlpUUJNckZxS01LQlZFa3V3MkZTV2RvMXVyYmdYKzFsQTZROFpkQlNORlE3WnVPOWU5Q1E5MHhDS2tDV1NjbyswcFdBbHFRWWhLaVEyOHBNOWphblwvcWxsVjJST2dmaE45a0JqejllaldKUUpJK0x1V3kzZUxSQmcyalwvcWlrZmt6elJvRVwvb21Ba1VSOUsxbHNzNTZ6Rm9pUUtSeUZySTBtU0xcL2tHQnhERmR5anBHa3J5Z0FLRUtLVUsyaiIsImV4cCI6MTY3Nzc3Njc2MywiY2xhaW1fa2V5X2lkIjoiMjU5Mzc3MzI0IiwiaWF0IjoxNjc3NzczMTYzLCJqdGkiOiI0YWJmOGRmNy02MzQxLTQ3NjUtYTQyNy1iOTU3MzY5YTRkN2MifQ.V3x94XrR_xBWejsJwlU0HTEO7AejEd770DJoH1iSfIk1ChVgdaZU1XX1BZzPji8pVEuB22aJCps5EAoMX5aNP3qkF7r7FDDqdhsRdT-2n855uOeT_6VGQ10caz5wO4HFOxSUZRn-9EJOfQNb-FYLI8ZlqdGy6JpjCBrHOKzRgFYjQjMmTM2zbx6ahyLk4bj_STU-jPnDgPvL4L05UcuR1WwBfGkcnVOpteZSnb4Hm64ibD9Zp7YdAVPSI39iHoq-z3ZieIbryZbu4FCUdkv0dgi0Oo3ueb0mTD14K2YCdOBw15SbfLertnS8L-aNg08cx-6xcp0S2VXE0K7dcJD_1g"

suspend fun getToken(): String? = token ?: refreshToken()

suspend fun refreshToken(): String? {
token = if (!repository.isAuthorized.first()) {
Timber.e("Not authorized. Token missing")
null
} else {
try {
//Todo: to an actual token refresh here
val authState = repository.authState.firstOrNull()
val expiresAt = Date(authState?.accessTokenExpirationTime ?: 0)
val accessToken = authState?.accessToken
Timber.e(
"Is authorized. token is available: $accessToken, expires at $expiresAt"
)
accessToken
} catch (ex: NoSuchElementException) {
Timber.w(ex, "Error refreshing token")
null
}
}
return token

}
}
54 changes: 53 additions & 1 deletion app/src/main/kotlin/nl/eduid/di/model/EduIdModels.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package nl.eduid.di.model

import android.os.Parcelable
import com.squareup.moshi.JsonClass
import kotlinx.parcelize.Parcelize


data class RequestNewIdRequest(
val user: User,
Expand All @@ -12,4 +16,52 @@ data class RequestNewIdRequest(
)
}

//{"user":{"email":"[email protected]","givenName":"Tester","familyName":"Testerson"},"authenticationRequestId":"48e0eb5f-62ae-429e-b103-444ad24f2cc0"}
//{"user":{"email":"[email protected]","givenName":"Tester","familyName":"Testerson"},"authenticationRequestId":"48e0eb5f-62ae-429e-b103-444ad24f2cc0"}


@Parcelize
@JsonClass(generateAdapter = true)
data class UserDetails(
val id: String,
val email: String,
val givenName: String,
val familyName: String,
val usePassword: Boolean,
val usePublicKey: Boolean,
val forgottenPassword: Boolean,
// val publicKeyCredentials: List<Any?>,
val linkedAccounts: List<LinkedAccount>,
val schacHomeOrganization: String,
val uid: String,
val rememberMe: Boolean,
val created: Long,

val eduIdPerServiceProvider: Map<String, EduIdPerServiceProvider>,

val loginOptions: List<String>,
// val registration: JsonObject
) : Parcelable

@Parcelize
@JsonClass(generateAdapter = true)
data class EduIdPerServiceProvider(
val serviceProviderEntityId: String,
val value: String,
val serviceName: String,
val serviceNameNl: String,
val serviceLogoUrl: String,
val createdAt: Long
) : Parcelable

@Parcelize
@JsonClass(generateAdapter = true)
data class LinkedAccount(
val institutionIdentifier: String,
val schacHomeOrganization: String,
val eduPersonPrincipalName: String,
val givenName: String,
val familyName: String,
val eduPersonAffiliations: List<String>,
val createdAt: Long,
val expiresAt: Long
) : Parcelable
47 changes: 42 additions & 5 deletions app/src/main/kotlin/nl/eduid/di/module/EduIdModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import nl.eduid.di.EduIdScope
import nl.eduid.di.api.EduIdApi
import nl.eduid.di.assist.AuthenticationAssistant
import nl.eduid.di.auth.TokenAuthenticator
import nl.eduid.di.auth.TokenInterceptor
import nl.eduid.di.auth.TokenProvider
import nl.eduid.di.repository.EduIdRepository
import nl.eduid.di.repository.StorageRepository
import nl.eduid.screens.personalinfo.PersonalInfoRepository
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.tiqr.data.BuildConfig
import org.tiqr.data.api.response.ApiResponseAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
Expand All @@ -29,7 +36,8 @@ internal object RepositoryModule {

@Provides
@Singleton
internal fun provideEduApi(retrofit: Retrofit): EduIdApi = retrofit.create(EduIdApi::class.java)
internal fun provideEduApi(@EduIdScope retrofit: Retrofit): EduIdApi =
retrofit.create(EduIdApi::class.java)


@Provides
Expand All @@ -38,33 +46,62 @@ internal object RepositoryModule {
api: EduIdApi,
) = EduIdRepository(api)

@Provides
@Singleton
internal fun providesPersonalInfoRepository(
api: EduIdApi,
) = PersonalInfoRepository(api)

@Provides
@Singleton
internal fun providesStorageRepository(
@ApplicationContext context: Context,
) = StorageRepository(context)

@Provides
@Singleton
internal fun providesTokenProvider(
repository: StorageRepository
) = TokenProvider(repository)

@Provides
@EduIdScope
fun providesTokenAuthOkHttp(
tokenAuthenticator: TokenAuthenticator,
tokenInterceptor: TokenInterceptor,
loggingInterceptor: HttpLoggingInterceptor,
okHttpClient: OkHttpClient
): OkHttpClient {
val builder = okHttpClient.newBuilder()
if (BuildConfig.DEBUG) {
builder.addInterceptor(loggingInterceptor)
}

return builder.addInterceptor(tokenInterceptor)
.authenticator(tokenAuthenticator).build()
}

@Provides
@Singleton
internal fun providesAuthenticationAssist(
) = AuthenticationAssistant()

@Provides
@Singleton
internal fun provideApiRetrofit(
client: Lazy<OkHttpClient>, moshi: Moshi
@EduIdScope
internal fun provideEduIdRetrofit(
@EduIdScope client: Lazy<OkHttpClient>, moshi: Moshi
): Retrofit {
return Retrofit.Builder().callFactory { client.get().newCall(it) }
.addCallAdapterFactory(ApiResponseAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl("https://login.eduid.nl/").build()
.baseUrl("https://login.test2.eduid.nl/").build()
}

@Provides
@Singleton
internal fun provideOkHttpClientBuilder(): OkHttpClient {
return OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class StorageRepository(private val context: Context) {
}
}

val isAuthorized: Flow<Boolean> = authState.map { it != null && it.isAuthorized }

val authRequest: Flow<AuthorizationRequest?> = context.dataStore.data.catch { exception ->
if (exception is IOException) {
Timber.e(exception, "Error reading preferences.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data class PersonalInfo(
val institutionStatus: InfoStatus = InfoStatus.Final,
) {
companion object {
fun demoData(): PersonalInfo {
fun demoData(): PersonalInfo {
return PersonalInfo(
name = "R. van Hamersdonksveer",
nameProvider = "Universiteit van Amsterdam",
Expand All @@ -31,10 +31,12 @@ data class PersonalInfo(
institutionStatus = InfoStatus.Final,
)
}
sealed class InfoStatus {
object Empty : InfoStatus()
object Editable : InfoStatus()
object Final : InfoStatus()
}
}

sealed class InfoStatus {
object Empty : InfoStatus()
object Editable : InfoStatus()
object Final : InfoStatus()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nl.eduid.screens.personalinfo

import nl.eduid.di.api.EduIdApi
import nl.eduid.di.model.UserDetails
import timber.log.Timber

class PersonalInfoRepository(private val eduIdApi: EduIdApi) {

suspend fun getUserDetails(): UserDetails? = try {
val response = eduIdApi.getUserDetails()
if (response.isSuccessful) {
response.body()
} else {
Timber.w("User details not available ${response.code()}: ${response.errorBody()}")
null
}
} catch (e: Exception) {
Timber.e(e, "Failed to retrieve user details")
null
}
}
Loading

0 comments on commit 20ed429

Please sign in to comment.