From 31bee2f900c013944f5c43a6ca2f48f6163a3907 Mon Sep 17 00:00:00 2001 From: y9vad9 Date: Wed, 13 Dec 2023 18:02:51 +0100 Subject: [PATCH] fix: todos --- .../api/rsocket/auth/AuthInterceptor.kt | 8 +++--- .../api/rsocket/auth/AuthorizationService.kt | 2 +- .../api/rsocket/internal/ApiFailure.kt | 9 +++++++ .../rsocket/internal/AuthorizationContext.kt | 26 +++++++++++++++++-- .../api/rsocket/internal/RSocketServiceExt.kt | 4 ++- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthInterceptor.kt b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthInterceptor.kt index 259de0d..7d26c84 100644 --- a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthInterceptor.kt +++ b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthInterceptor.kt @@ -1,7 +1,7 @@ package io.timemates.api.rsocket.auth import io.rsocket.kotlin.payload.Payload -import io.timemates.backend.authorization.usecases.GetUserIdByAccessTokenUseCase +import io.timemates.backend.authorization.usecases.GetAuthorizationUseCase import io.timemates.rsproto.metadata.ExtraMetadata import io.timemates.rsproto.server.annotations.ExperimentalInterceptorsApi import io.timemates.rsproto.server.interceptors.Interceptor @@ -14,9 +14,9 @@ import kotlin.coroutines.CoroutineContext */ @OptIn(ExperimentalInterceptorsApi::class) class AuthInterceptor( - private val getUserIdByAccessTokenUseCase: GetUserIdByAccessTokenUseCase, + private val getAuthorizationUseCase: GetAuthorizationUseCase, ) : Interceptor() { - data class Data(val accessHash: String?, val userIdProvider: GetUserIdByAccessTokenUseCase) : CoroutineContext.Element { + data class Data(val accessHash: String?, val authorizationProvider: GetAuthorizationUseCase) : CoroutineContext.Element { override val key get() = Key companion object Key : CoroutineContext.Key @@ -24,7 +24,7 @@ class AuthInterceptor( override fun intercept(coroutineContext: CoroutineContext, incoming: Payload): CoroutineContext { val accessHash = coroutineContext[ExtraMetadata]?.extra?.get("access_hash")?.decodeToString() - return coroutineContext + Data(accessHash, getUserIdByAccessTokenUseCase) + return coroutineContext + Data(accessHash, getAuthorizationUseCase) } } diff --git a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthorizationService.kt b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthorizationService.kt index aab390a..09f5ee6 100644 --- a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthorizationService.kt +++ b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/auth/AuthorizationService.kt @@ -115,7 +115,7 @@ class AuthorizationService( } override suspend fun terminateAuthorization(request: Empty): Empty { - removeAccessTokenUseCase.execute(AccessHash.createOrFail(Request.userAccessHash())) + removeAccessTokenUseCase.execute(AccessHash.createOrFail(Request.userAccessHash() ?: unauthorized())) return Empty.Default } } \ No newline at end of file diff --git a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/ApiFailure.kt b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/ApiFailure.kt index 238a154..38f5660 100644 --- a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/ApiFailure.kt +++ b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/ApiFailure.kt @@ -22,6 +22,9 @@ internal fun alreadyExists(): Nothing = throw ApiFailure.AlreadyExists context(RSocketService) internal fun noAccess(): Nothing = throw ApiFailure.NoAccess +context(RSocketService) +internal fun unauthorized(): Nothing = throw ApiFailure.Unauthorized + internal object ApiFailure { /** * Variable for custom RSocket error when the number of attempts exceeds a limit. @@ -68,4 +71,10 @@ internal object ApiFailure { */ val NoAccess: RSocketError.Custom get() = RSocketError.Custom(HttpStatusCode.Forbidden.value, "No access to given resource or operation.") + + /** + * Represents unauthorized failure in requests. + */ + val Unauthorized: RSocketError.Custom + get() = RSocketError.Custom(HttpStatusCode.Unauthorized.value, "Unauthorized.") } \ No newline at end of file diff --git a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/AuthorizationContext.kt b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/AuthorizationContext.kt index 01cd175..4fff141 100644 --- a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/AuthorizationContext.kt +++ b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/AuthorizationContext.kt @@ -1,8 +1,15 @@ package io.timemates.api.rsocket.internal +import io.timemates.api.rsocket.auth.AuthInterceptor +import io.timemates.backend.authorization.types.value.AccessHash +import io.timemates.backend.authorization.usecases.GetAuthorizationUseCase +import io.timemates.backend.features.authorization.Authorized import io.timemates.backend.features.authorization.AuthorizedContext import io.timemates.backend.features.authorization.Scope +import io.timemates.backend.features.authorization.authorizationProvider +import io.timemates.backend.features.authorization.types.AuthorizedId import io.timemates.rsproto.server.RSocketService +import kotlinx.coroutines.currentCoroutineContext /** * Executes the provided block of code within an authorized context. @@ -11,6 +18,21 @@ import io.timemates.rsproto.server.RSocketService * @return The result of executing the code block. */ context(RSocketService) -internal suspend inline fun authorized(block: AuthorizedContext.() -> R): R { - TODO() +internal suspend inline fun authorized( + constraint: (List) -> Boolean = { scopes -> scopes.any { it is T || it is Scope.All } }, + block: AuthorizedContext.() -> R, +): R { + val authInfo = currentCoroutineContext()[AuthInterceptor.Data] ?: unauthorized() + val accessHash = authInfo.accessHash ?: unauthorized() + + return authorizationProvider( + provider = { + authInfo.authorizationProvider.execute(AccessHash.createOrFail(accessHash)) + .let { (it as? GetAuthorizationUseCase.Result.Success)?.authorization ?: unauthorized() } + .takeIf { constraint(it.scopes) } + ?.let { Authorized(AuthorizedId(it.userId.long), scopes = it.scopes) } + }, + onFailure = { unauthorized() }, + block = block, + ) } \ No newline at end of file diff --git a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/RSocketServiceExt.kt b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/RSocketServiceExt.kt index 3471086..592b0db 100644 --- a/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/RSocketServiceExt.kt +++ b/infrastructure/rsocket-api/src/main/kotlin/io/timemates/api/rsocket/internal/RSocketServiceExt.kt @@ -1,9 +1,11 @@ package io.timemates.api.rsocket.internal import io.rsocket.kotlin.RSocketError +import io.timemates.api.rsocket.auth.AuthInterceptor import io.timemates.backend.validation.SafeConstructor import io.timemates.backend.validation.ValidationFailureHandler import io.timemates.rsproto.server.RSocketService +import kotlinx.coroutines.currentCoroutineContext /** * Used as a handler for validation inside RSocket requests. @@ -35,5 +37,5 @@ internal fun SafeConstructor.createOrFail(value: W): T { object Request { context(RSocketService) - internal fun userAccessHash(): String = TODO() + internal suspend fun userAccessHash(): String? = currentCoroutineContext()[AuthInterceptor.Data]?.accessHash } \ No newline at end of file