Skip to content

Commit

Permalink
Merge pull request #282 from RADAR-base/feat/ory-auth
Browse files Browse the repository at this point in the history
Add support for Ory auth
  • Loading branch information
yatharthranjan authored Dec 3, 2024
2 parents dd456c7 + 4c4bf07 commit acfa382
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package org.radarbase.authorizer.config

data class AuthConfig(
val managementPortalUrl: String = "http://managementportal-app:8080/managementportal",
val authUrl: String = "http://hydra-public:4444/oauth2/token",
val clientId: String = "radar_rest_sources_auth_backend",
val clientSecret: String? = null,
val clientSecret: String? = "",
val jwtECPublicKeys: List<String>? = null,
val jwtRSAPublicKeys: List<String>? = null,
val jwtIssuer: String? = null,
val jwtResourceName: String = "res_restAuthorizer",
val jwksUrls: List<String> = listOf("http://hydra-public:4444/.well-known/jwks.json"),
)
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ interface RestSourceUserRepository {
suspend fun delete(user: RestSourceUser)
suspend fun reset(user: RestSourceUser, startDate: Instant, endDate: Instant?): RestSourceUser
suspend fun findByExternalId(externalId: String, sourceType: String): RestSourceUser?
suspend fun findByUserIdProjectIdSourceType(userId: String, projectId: String, sourceType: String): RestSourceUser?
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import org.radarbase.authorizer.api.Page
import org.radarbase.authorizer.api.RestOauth2AccessToken
import org.radarbase.authorizer.api.RestSourceUserDTO
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
Expand Down Expand Up @@ -52,19 +51,20 @@ class RestSourceUserRepositoryImpl(
.resultList.firstOrNull()

if (existingUser != null) {
throw HttpConflictException("user_exists", "User ${user.userId} already exists.")
}
RestSourceUser(
projectId = user.projectId,
userId = user.userId,
sourceId = user.sourceId ?: UUID.randomUUID().toString(),
sourceType = user.sourceType,
createdAt = Instant.now(),
version = Instant.now().toString(),
startDate = user.startDate,
endDate = user.endDate,
).also {
persist(it)
existingUser
} else {
RestSourceUser(
projectId = user.projectId,
userId = user.userId,
sourceId = user.sourceId ?: UUID.randomUUID().toString(),
sourceType = user.sourceType,
createdAt = Instant.now(),
version = Instant.now().toString(),
startDate = user.startDate,
endDate = user.endDate,
).also {
persist(it)
}
}
}

Expand Down Expand Up @@ -211,6 +211,27 @@ class RestSourceUserRepositoryImpl(
return if (result.isEmpty()) null else result[0]
}

override suspend fun findByUserIdProjectIdSourceType(
userId: String,
projectId: String,
sourceType: String,
): RestSourceUser? = transact {
createQuery(
"""
SELECT u
FROM RestSourceUser u
WHERE u.userId = :userId
AND u.projectId = :projectId
AND u.sourceType = :sourceType
""".trimIndent(),
RestSourceUser::class.java,
).apply {
setParameter("userId", userId)
setParameter("projectId", projectId)
setParameter("sourceType", sourceType)
}.resultList.firstOrNull()
}

override suspend fun delete(user: RestSourceUser) = transact {
remove(merge(user))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,38 @@ import org.radarbase.jersey.enhancer.JerseyResourceEnhancer
import org.radarbase.jersey.hibernate.config.HibernateResourceEnhancer

/** This binder needs to register all non-Jersey classes, otherwise initialization fails. */
class ManagementPortalEnhancerFactory(private val config: AuthorizerConfig) : EnhancerFactory {
class ManagementPortalEnhancerFactory(
private val config: AuthorizerConfig,
) : EnhancerFactory {
override fun createEnhancers(): List<JerseyResourceEnhancer> {
val authConfig = AuthConfig(
managementPortal = MPConfig(
url = config.auth.managementPortalUrl.trimEnd('/'),
clientId = config.auth.clientId,
clientSecret = config.auth.clientSecret,
syncProjectsIntervalMin = config.service.syncProjectsIntervalMin,
syncParticipantsIntervalMin = config.service.syncParticipantsIntervalMin,
),
jwtResourceName = config.auth.jwtResourceName,
)
val authConfig =
AuthConfig(
managementPortal =
MPConfig(
url = config.auth.managementPortalUrl.trimEnd('/'),
clientId = config.auth.clientId,
clientSecret = config.auth.clientSecret,
syncProjectsIntervalMin = config.service.syncProjectsIntervalMin,
syncParticipantsIntervalMin = config.service.syncParticipantsIntervalMin,
),
jwtResourceName = config.auth.jwtResourceName,
jwksUrls = config.auth.jwksUrls,
)

val dbConfig = config.database.copy(
managedClasses = listOf(
RestSourceUser::class.qualifiedName!!,
RegistrationState::class.qualifiedName!!,
),
)
val dbConfig =
config.database.copy(
managedClasses =
listOf(
RestSourceUser::class.qualifiedName!!,
RegistrationState::class.qualifiedName!!,
),
)
return listOf(
Enhancers.radar(authConfig),
Enhancers.health,
HibernateResourceEnhancer(dbConfig),
Enhancers.managementPortal(authConfig),
ManagementPortalResourceEnhancer(authConfig),
Enhancers.ecdsa,
JedisResourceEnhancer(),
Enhancers.exception,
AuthorizerResourceEnhancer(config),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.radarbase.authorizer.enhancer

import jakarta.inject.Singleton
import org.glassfish.jersey.internal.inject.AbstractBinder
import org.radarbase.auth.authentication.TokenValidator
import org.radarbase.auth.authorization.AuthorizationOracle
import org.radarbase.authorizer.service.MPClientFactory
import org.radarbase.jersey.auth.AuthConfig
import org.radarbase.jersey.auth.AuthValidator
import org.radarbase.jersey.auth.jwt.AuthorizationOracleFactory
import org.radarbase.jersey.auth.jwt.TokenValidatorFactory
import org.radarbase.jersey.auth.managementportal.ManagementPortalTokenValidator
import org.radarbase.jersey.enhancer.JerseyResourceEnhancer
import org.radarbase.jersey.service.ProjectService
import org.radarbase.jersey.service.managementportal.MPProjectService
import org.radarbase.jersey.service.managementportal.ProjectServiceWrapper
import org.radarbase.jersey.service.managementportal.RadarProjectService
import org.radarbase.management.client.MPClient

class ManagementPortalResourceEnhancer(private val config: AuthConfig) : JerseyResourceEnhancer {
override fun AbstractBinder.enhance() {
val config = config.withEnv()

bindFactory(TokenValidatorFactory::class.java)
.to(TokenValidator::class.java)
.`in`(Singleton::class.java)

bind(ManagementPortalTokenValidator::class.java)
.to(AuthValidator::class.java)
.`in`(Singleton::class.java)

bindFactory(AuthorizationOracleFactory::class.java)
.to(AuthorizationOracle::class.java)
.`in`(Singleton::class.java)

if (config.managementPortal.clientId != null) {
bindFactory(MPClientFactory::class.java)
.to(MPClient::class.java)
.`in`(Singleton::class.java)

bind(ProjectServiceWrapper::class.java)
.to(ProjectService::class.java)
.`in`(Singleton::class.java)

bind(MPProjectService::class.java)
.to(RadarProjectService::class.java)
.`in`(Singleton::class.java)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class RestSourceUserResource(
}

@POST
@NeedsPermission(Permission.SUBJECT_CREATE)
@NeedsPermission(Permission.SUBJECT_UPDATE)
fun create(
userDto: RestSourceUserDTO,
@Suspended asyncResponse: AsyncResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.radarbase.authorizer.service

import jakarta.ws.rs.core.Context
import org.radarbase.authorizer.config.AuthorizerConfig
import org.radarbase.ktor.auth.ClientCredentialsConfig
import org.radarbase.ktor.auth.clientCredentials
import org.radarbase.management.client.MPClient
import org.slf4j.LoggerFactory
import java.net.URI
import java.util.function.Supplier

class MPClientFactory(
@Context private val config: AuthorizerConfig,
) : Supplier<MPClient> {

override fun get(): MPClient {
val baseUrl = config.auth.managementPortalUrl
val clientId = config.auth.clientId
val clientSecret = config.auth.clientSecret ?: throw IllegalArgumentException("Client Secret is required")
val customTokenUrl = config.auth.authUrl

val mpClientConfig = MPClient.Config().apply {
url = baseUrl

auth {
val authConfig = ClientCredentialsConfig(
tokenUrl = customTokenUrl,
clientId = clientId,
clientSecret = clientSecret,
audience = "res_ManagementPortal",
).copyWithEnv()

return@auth clientCredentials(
authConfig = authConfig,
targetHost = URI.create(baseUrl).host,
)
}
}

return MPClient(mpClientConfig)
}

companion object {
private val logger = LoggerFactory.getLogger(MPClientFactory::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.radarbase.authorizer.service

import jakarta.ws.rs.WebApplicationException
import jakarta.ws.rs.core.Context
import jakarta.ws.rs.core.Response
import org.radarbase.auth.authorization.EntityDetails
Expand Down Expand Up @@ -39,6 +40,18 @@ class RestSourceUserService(

suspend fun create(userDto: RestSourceUserDTO): RestSourceUserDTO {
userDto.ensure()
val existingUser = userRepository.findByUserIdProjectIdSourceType(
userId = userDto.userId!!,
projectId = userDto.projectId!!,
sourceType = userDto.sourceType,
)
if (existingUser != null) {
val response = Response.status(Response.Status.CONFLICT)
.entity(mapOf("status" to 409, "message" to "User already exists.", "user" to userMapper.fromEntity(existingUser)))
.build()

throw WebApplicationException(response)
}
val user = userRepository.create(userDto)
return userMapper.fromEntity(user)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
}

redirectToAuthRequestLink() {
window.location.href = `${environment.authBaseUrl}/authorize?client_id=${
const scopes = "SOURCETYPE.READ%20PROJECT.READ%20SUBJECT.READ%20SUBJECT.UPDATE%20SUBJECT.CREATE"
window.location.href = `${environment.authBaseUrl}/auth?client_id=${
environment.appClientId
}&response_type=code&redirect_uri=${window.location.href.split('?')[0]}`;
}&response_type=code&state=${Date.now()}&audience=res_restAuthorizer&scope=${scopes}&redirect_uri=${window.location.href.split('?')[0]}`;
}
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ object Versions {

const val kotlin = "1.9.23"

const val radarCommons = "1.1.2"
const val radarCommons = "1.1.3"
const val radarJersey = "0.11.2"
const val postgresql = "42.6.1"
const val ktor = "2.3.11"
Expand Down

0 comments on commit acfa382

Please sign in to comment.