Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update radar-auth #244

Merged
merged 9 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions authorizer-app-backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \

COPY ./buildSrc /code/buildSrc
COPY ./build.gradle.kts ./settings.gradle.kts ./gradle.properties /code/
COPY ./buildSrc /code/buildSrc
COPY authorizer-app-backend/build.gradle.kts /code/authorizer-app-backend/
RUN gradle downloadDependencies copyDependencies startScripts

Expand Down
25 changes: 15 additions & 10 deletions authorizer-app-backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
plugins {
application
id("org.jetbrains.kotlin.plugin.noarg")
id("org.jetbrains.kotlin.plugin.jpa")
id("org.jetbrains.kotlin.plugin.allopen")
kotlin("plugin.serialization")
kotlin("plugin.noarg")
kotlin("plugin.jpa")
kotlin("plugin.allopen")
}

application {
Expand All @@ -17,20 +18,24 @@ dependencies {
implementation("org.radarbase:radar-jersey-hibernate:${Versions.radarJersey}") {
runtimeOnly("org.postgresql:postgresql:${Versions.postgresql}")
}

implementation("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
implementation("org.radarbase:radar-commons-kotlin:${Versions.radarCommons}")

implementation("redis.clients:jedis:${Versions.jedis}")

testImplementation("org.hamcrest:hamcrest:${Versions.hamcrest}")
implementation(enforcedPlatform("io.ktor:ktor-bom:${Versions.ktor}"))
implementation("io.ktor:ktor-client-core")
implementation("io.ktor:ktor-client-auth")
implementation("io.ktor:ktor-client-cio")
implementation("io.ktor:ktor-client-content-negotiation")
implementation("io.ktor:ktor-serialization-kotlinx-json")

testImplementation("org.hamcrest:hamcrest:${Versions.hamcrest}")
testImplementation("org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlin}")

testImplementation("org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:${Versions.jersey}")
}

allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass")
annotation("jakarta.persistence.Embeddable")
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,38 @@

package org.radarbase.authorizer.api

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.Instant

@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class RestOauth2AccessToken(
@JsonProperty("access_token") val accessToken: String,
@JsonProperty("refresh_token") val refreshToken: String? = null,
@JsonProperty("expires_in") val expiresIn: Int = 0,
@JsonProperty("token_type") val tokenType: String? = null,
@JsonProperty("user_id") val externalUserId: String? = null,
@SerialName("access_token")
val accessToken: String,
@SerialName("refresh_token")
val refreshToken: String? = null,
@SerialName("expires_in")
val expiresIn: Int = 0,
@SerialName("token_type")
val tokenType: String? = null,
@SerialName("user_id")
val externalUserId: String? = null,
)

@Serializable
data class RestOauth1AccessToken(
@JsonProperty("oauth_token") val token: String,
@JsonProperty("oauth_token_secret") val tokenSecret: String? = null,
@JsonProperty("oauth_verifier") val tokenVerifier: String? = null,
@SerialName("oauth_token")
val token: String,
@SerialName("oauth_token_secret")
val tokenSecret: String? = null,
@SerialName("oauth_verifier")
val tokenVerifier: String? = null,
)

@Serializable
data class RestOauth1UserId(
@JsonProperty("userId") val userId: String,
@SerialName("userId")
val userId: String,
)

data class SignRequestParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ package org.radarbase.authorizer.api
import jakarta.ws.rs.core.Context
import org.radarbase.authorizer.doa.entity.RestSourceUser
import org.radarbase.jersey.service.managementportal.RadarProjectService
import org.radarbase.kotlin.coroutines.forkJoin

class RestSourceUserMapper(
@Context private val projectService: RadarProjectService,
) {
fun fromEntity(user: RestSourceUser): RestSourceUserDTO {
suspend fun fromEntity(user: RestSourceUser): RestSourceUserDTO {
val mpUser = user.projectId?.let { p ->
user.userId?.let { u -> projectService.subject(p, u) }
}
Expand All @@ -48,8 +49,8 @@ class RestSourceUserMapper(
)
}

fun fromRestSourceUsers(records: List<RestSourceUser>, page: Page?) = RestSourceUsers(
users = records.map(::fromEntity),
suspend fun fromRestSourceUsers(records: List<RestSourceUser>, page: Page?) = RestSourceUsers(
users = records.forkJoin { fromEntity(it) },
metadata = page,
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.radarbase.authorizer.config

import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import io.ktor.http.URLBuilder
import io.ktor.http.Url
import io.ktor.http.appendPathSegments
import io.ktor.http.takeFrom
import org.radarbase.authorizer.enhancer.ManagementPortalEnhancerFactory
import org.radarbase.jersey.enhancer.EnhancerFactory
import java.net.URI
Expand All @@ -18,26 +20,20 @@ data class AuthorizerServiceConfig(
val tokenExpiryTimeInMinutes: Long = 15,
val persistentTokenExpiryInMin: Long = 3.days.inWholeMinutes,
) {
val callbackUrl: HttpUrl by lazy {
val callbackUrl: Url by lazy {
val frontendBaseUrlBuilder = when {
frontendBaseUri != null -> frontendBaseUri.toHttpUrlOrNull()?.newBuilder()
advertisedBaseUri != null -> {
advertisedBaseUri.toHttpUrlOrNull()?.let { advertisedUrl ->
advertisedUrl.newBuilder().apply {
advertisedUrl.pathSegments.asReversed()
.forEachIndexed { idx, segment ->
if (segment.isEmpty() || segment == "backend") {
removePathSegment(advertisedUrl.pathSize - 1 - idx)
}
}
addPathSegment("authorizer")
}
frontendBaseUri != null -> URLBuilder().takeFrom(frontendBaseUri)
advertisedBaseUri != null -> URLBuilder().apply {
takeFrom(advertisedBaseUri)
pathSegments = buildList(pathSegments.size) {
addAll(pathSegments.dropLastWhile { it.isEmpty() || it == "backend" })
add("authorizer")
}
}
else -> null
else -> throw IllegalStateException("Frontend URL parameter is not a valid HTTP URL.")
}
checkNotNull(frontendBaseUrlBuilder) { "Frontend URL parameter $frontendBaseUri is not a valid HTTP URL." }
.addPathSegment("users:new")
frontendBaseUrlBuilder
.appendPathSegments("users:new")
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import org.radarbase.authorizer.config.AuthorizerConfig
import org.radarbase.authorizer.doa.entity.RegistrationState
import org.radarbase.authorizer.doa.entity.RestSourceUser
import org.radarbase.authorizer.util.Hmac256Secret
import org.radarbase.authorizer.util.Hmac256Secret.Companion.encodeToBase64
import org.radarbase.authorizer.util.Hmac256Secret.Companion.randomize
import org.radarbase.authorizer.util.encodeToBase64
import org.radarbase.authorizer.util.randomize
import org.radarbase.jersey.hibernate.HibernateRepository
import org.radarbase.jersey.service.AsyncCoroutineService
import java.time.Instant
import kotlin.time.Duration.Companion.minutes

class RegistrationRepository(
@Context private val config: AuthorizerConfig,
@Context em: Provider<EntityManager>,
) : HibernateRepository(em) {
@Context asyncService: AsyncCoroutineService,
) : HibernateRepository(em, asyncService) {

private val tokenExpiryTime = config.service.tokenExpiryTimeInMinutes.minutes
private val persistentTokenExpiryTime = config.service.persistentTokenExpiryInMin.minutes

fun generate(
suspend fun generate(
user: RestSourceUser,
secret: Hmac256Secret?,
persistent: Boolean,
Expand Down Expand Up @@ -53,11 +55,11 @@ class RegistrationRepository(
}
}

operator fun get(token: String): RegistrationState? = transact {
suspend fun get(token: String): RegistrationState? = transact {
find(RegistrationState::class.java, token)
}

fun cleanUp(): Int = transact {
suspend fun cleanUp(): Int = transact {
val cb = criteriaBuilder

// create delete
Expand All @@ -70,15 +72,11 @@ class RegistrationRepository(
createQuery(deleteQuery).executeUpdate()
}

operator fun minusAssign(token: String) = remove(token)

operator fun minusAssign(registrationState: RegistrationState) = remove(registrationState)

fun remove(registrationState: RegistrationState): Unit = transact {
suspend fun remove(registrationState: RegistrationState): Unit = transact {
remove(registrationState)
}

fun remove(token: String): Unit = transact {
suspend fun remove(token: String): Unit = transact {
val state = find(RegistrationState::class.java, token)
remove(state)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ import org.radarbase.authorizer.doa.entity.RestSourceUser
import java.time.Instant

interface RestSourceUserRepository {
fun create(user: RestSourceUserDTO): RestSourceUser
fun updateToken(token: RestOauth2AccessToken?, user: RestSourceUser): RestSourceUser
fun read(id: Long): RestSourceUser?
fun update(userId: Long, user: RestSourceUserDTO): RestSourceUser
fun query(
suspend fun create(user: RestSourceUserDTO): RestSourceUser
suspend fun updateToken(token: RestOauth2AccessToken?, user: RestSourceUser): RestSourceUser
suspend fun read(id: Long): RestSourceUser?
suspend fun update(userId: Long, user: RestSourceUserDTO): RestSourceUser
suspend fun query(
page: Page,
projectIds: List<String>,
sourceType: String? = null,
search: String?,
userIds: List<String>,
isAuthorized: Boolean?,
): Pair<List<RestSourceUser>, Page>
fun queryAllWithElapsedEndDate(sourceType: String? = null): List<RestSourceUser>
fun delete(user: RestSourceUser)
fun reset(user: RestSourceUser, startDate: Instant, endDate: Instant?): RestSourceUser
fun findByExternalId(externalId: String, sourceType: String): RestSourceUser?
suspend fun queryAllWithElapsedEndDate(sourceType: String? = null): List<RestSourceUser>
suspend fun delete(user: RestSourceUser)
suspend fun reset(user: RestSourceUser, startDate: Instant, endDate: Instant?): RestSourceUser
suspend fun findByExternalId(externalId: String, sourceType: String): RestSourceUser?
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ import org.radarbase.authorizer.doa.entity.RestSourceUser
import org.radarbase.jersey.exception.HttpConflictException
import org.radarbase.jersey.exception.HttpNotFoundException
import org.radarbase.jersey.hibernate.HibernateRepository
import org.radarbase.jersey.service.AsyncCoroutineService
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.UUID

class RestSourceUserRepositoryImpl(
@Context em: Provider<EntityManager>,
) : RestSourceUserRepository, HibernateRepository(em) {
@Context asyncService: AsyncCoroutineService,
) : RestSourceUserRepository, HibernateRepository(em, asyncService) {

override fun create(user: RestSourceUserDTO): RestSourceUser = transact {
override suspend fun create(user: RestSourceUserDTO): RestSourceUser = transact {
val existingUser = createQuery(
"""
SELECT u
Expand Down Expand Up @@ -86,16 +88,16 @@ class RestSourceUserRepositoryImpl(
}
}

override fun updateToken(token: RestOauth2AccessToken?, user: RestSourceUser): RestSourceUser = transact {
override suspend fun updateToken(token: RestOauth2AccessToken?, user: RestSourceUser): RestSourceUser = transact {
user.apply {
setToken(token)
merge(this)
}
}

override fun read(id: Long): RestSourceUser? = transact { find(RestSourceUser::class.java, id) }
override suspend fun read(id: Long): RestSourceUser? = transact { find(RestSourceUser::class.java, id) }

override fun update(userId: Long, user: RestSourceUserDTO): RestSourceUser = transact {
override suspend fun update(userId: Long, user: RestSourceUserDTO): RestSourceUser = transact {
val existingUser = find(RestSourceUser::class.java, userId)
?: throw HttpNotFoundException("user_not_found", "User with ID $userId not found")

Expand All @@ -108,7 +110,7 @@ class RestSourceUserRepositoryImpl(
}
}

override fun query(
override suspend fun query(
page: Page,
projectIds: List<String>,
sourceType: String?,
Expand Down Expand Up @@ -165,7 +167,7 @@ class RestSourceUserRepositoryImpl(
}
}

override fun queryAllWithElapsedEndDate(
override suspend fun queryAllWithElapsedEndDate(
sourceType: String?,
): List<RestSourceUser> {
var queryString = """
Expand All @@ -188,7 +190,7 @@ class RestSourceUserRepositoryImpl(
}
}

override fun findByExternalId(
override suspend fun findByExternalId(
externalId: String,
sourceType: String,
): RestSourceUser? {
Expand All @@ -209,11 +211,11 @@ class RestSourceUserRepositoryImpl(
return if (result.isEmpty()) null else result[0]
}

override fun delete(user: RestSourceUser) = transact {
override suspend fun delete(user: RestSourceUser) = transact {
remove(merge(user))
}

override fun reset(user: RestSourceUser, startDate: Instant, endDate: Instant?) = transact {
override suspend fun reset(user: RestSourceUser, startDate: Instant, endDate: Instant?) = transact {
user.apply {
this.version = Instant.now().toString()
this.timesReset += 1
Expand Down
Loading