Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package co.nilin.opex.admin.ports.auth.controller
import co.nilin.opex.admin.ports.auth.data.ImpersonateRequest
import co.nilin.opex.admin.ports.auth.data.KeycloakUser
import co.nilin.opex.admin.ports.auth.data.KycGroup
import co.nilin.opex.admin.ports.auth.data.QueryUserResponse
import co.nilin.opex.admin.ports.auth.service.AuthAdminService
import co.nilin.opex.admin.ports.auth.utils.asKeycloakUser
import org.springframework.http.MediaType
Expand All @@ -13,8 +14,13 @@ import org.springframework.web.bind.annotation.*
class AuthAdminController(private val service: AuthAdminService) {

@GetMapping("/user")
suspend fun getAllKeycloakUsers(): List<KeycloakUser> {
return service.findAllUsers().map { it.asKeycloakUser() }
suspend fun getAllKeycloakUsers(@RequestParam offset: Int, @RequestParam size: Int): QueryUserResponse {
return service.findAllUsers(offset, size)
}

@GetMapping("/user/{userId}")
suspend fun getUser(@PathVariable userId: String): KeycloakUser {
return service.getUser(userId).asKeycloakUser(true)
}

@PostMapping("/user/{userId}/join-kyc")
Expand All @@ -27,19 +33,32 @@ class AuthAdminController(private val service: AuthAdminService) {
service.switchKYCGroup(userId, KycGroup.ACCEPTED)
}

@PostMapping("/user/impersonate", produces = [MediaType.APPLICATION_JSON_VALUE])
suspend fun impersonate(@RequestBody body: ImpersonateRequest): String {
return service.impersonate(body.clientId, body.clientSecret, body.userId)
}

@GetMapping("/user/search")
suspend fun searchUsers(
@RequestParam search: String,
@RequestParam(required = false) by: String?,
@RequestParam offset: Int,
@RequestParam size: Int
): QueryUserResponse {
return if (by == "email") service.searchUserEmail(search)
else service.searchUser(search, offset, size)
}

@PostMapping("/user/{userId}/kyc/reject")
fun rejectKYC(@PathVariable userId: String) {
service.switchKYCGroup(userId, KycGroup.REJECTED)
}

@GetMapping("/group/{groupName}/members")
fun getMembersOfGroup(@PathVariable groupName: String): List<KeycloakUser> {
return service.findUsersInGroupByName(groupName).map { it.asKeycloakUser() }
}

@PostMapping("/user/impersonate", produces = [MediaType.APPLICATION_JSON_VALUE])
suspend fun impersonate(@RequestBody body: ImpersonateRequest): String {
return service.impersonate(body.clientId, body.clientSecret, body.userId)
fun getMembersOfGroup(
@PathVariable groupName: String, @RequestParam offset: Int, @RequestParam size: Int
): List<KeycloakUser> {
return service.findUsersInGroupByName(groupName, offset, size).map { it.asKeycloakUser() }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.nilin.opex.admin.ports.auth.data

data class QueryUserResponse(
val total: Int,
val users: List<KeycloakUser>
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class KeycloakProxy(private val webClient: WebClient) {
.with("requested_subject", userId)
.with("subject_token", token)
.with("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.with("agent", "opex-admin")

logger.info("Request token exchange for user $userId and client $clientId")
return webClient.post()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package co.nilin.opex.admin.ports.auth.service

import co.nilin.opex.admin.ports.auth.data.KycGroup
import co.nilin.opex.admin.ports.auth.data.QueryUserResponse
import co.nilin.opex.admin.ports.auth.proxy.KeycloakProxy
import co.nilin.opex.admin.ports.auth.utils.asKeycloakUser
import co.nilin.opex.utility.error.data.OpexError
import co.nilin.opex.utility.error.data.OpexException
import org.keycloak.admin.client.Keycloak
Expand All @@ -17,8 +19,15 @@ class AuthAdminService(
private val proxy: KeycloakProxy
) {

fun findAllUsers(): List<UserRepresentation> {
return opexRealm.users().list()
fun getUser(userId: String): UserRepresentation {
return opexRealm.users().get(userId).toRepresentation() ?: throw OpexException(OpexError.UserNotFoundAdmin)
}

fun findAllUsers(offset: Int, size: Int): QueryUserResponse {
return QueryUserResponse(
opexRealm.users().count(),
opexRealm.users().list(offset, size).map { it.asKeycloakUser() }
)
}

fun findGroupById(groupId: String): GroupResource {
Expand All @@ -39,9 +48,9 @@ class AuthAdminService(
return group.members()
}

fun findUsersInGroupByName(groupName: String): List<UserRepresentation> {
fun findUsersInGroupByName(groupName: String, offset: Int, size: Int): List<UserRepresentation> {
val group = findGroupByName(groupName)
return group.members()
return group.members(offset, size)
}

fun addUserToGroup(userId: String, groupId: String) {
Expand Down Expand Up @@ -69,4 +78,19 @@ class AuthAdminService(
return proxy.impersonate(token, clientId, clientSecret, userId)
}

fun searchUser(search: String, offset: Int, size: Int): QueryUserResponse {
return QueryUserResponse(
opexRealm.users().search(search).count(),
opexRealm.users().search(search, offset, size, false).map { it.asKeycloakUser() }
)
}

fun searchUserEmail(search: String): QueryUserResponse {
val users = opexRealm.users().search(search)
return QueryUserResponse(
users.count(),
users.map { it.asKeycloakUser() }
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package co.nilin.opex.admin.ports.auth.utils
import co.nilin.opex.admin.ports.auth.data.KeycloakUser
import org.keycloak.representations.idm.UserRepresentation

fun UserRepresentation.asKeycloakUser(): KeycloakUser = KeycloakUser(
fun UserRepresentation.asKeycloakUser(includeAttributes: Boolean = false): KeycloakUser = KeycloakUser(
id,
email,
username,
Expand All @@ -13,5 +13,5 @@ fun UserRepresentation.asKeycloakUser(): KeycloakUser = KeycloakUser(
isEmailVerified,
groups,
requiredActions,
attributes
if(includeAttributes) attributes else null
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package co.nilin.opex.auth.gateway.data

data class UserSessionResponse(
val id: String,
val ipAddress: String,
val id: String?,
val ipAddress: String?,
val started: Long,
val lastAccess: Long,
val state: String,
val state: String?,
val agent: String?,
val inUse: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -262,17 +262,19 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour
if (!auth.hasScopeAccess("trust")) return ErrorHandler.forbidden()

val user = session.users().getUserById(auth.getUserId(), opexRealm) ?: return ErrorHandler.userNotFound()
val sessions = session.sessions().getUserSessionsStream(opexRealm, user).map {
UserSessionResponse(
it.id,
it.ipAddress,
it.started.toLong(),
it.lastSessionRefresh.toLong(),
it.state.name,
tryOrElse(null) { it.notes["agent"] },
auth.token?.sessionState == it.id
)
}.toList()
val sessions = session.sessions().getUserSessionsStream(opexRealm, user)
.filter { tryOrElse(null) { it.notes["agent"] } != "opex-admin" }
.map {
UserSessionResponse(
it.id,
it.ipAddress,
it.started.toLong(),
it.lastSessionRefresh.toLong(),
it.state?.name,
tryOrElse(null) { it.notes["agent"] },
auth.token?.sessionState == it.id
)
}.toList()

return Response.ok(sessions).build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus
ChainNotFound(8003, "Chain not found", HttpStatus.NOT_FOUND),
CurrencyNotFoundBC(8004, "Currency not found", HttpStatus.NOT_FOUND),
TokenNotFound(8005, "Coin/Token not found", HttpStatus.NOT_FOUND),
InvalidAddressType(8006, "Address type is invalid", HttpStatus.NOT_FOUND);
InvalidAddressType(8006, "Address type is invalid", HttpStatus.NOT_FOUND),

// code 9000: admin
UserNotFoundAdmin(9001, "User not found", HttpStatus.NOT_FOUND),

;

companion object {
fun findByCode(code: Int?): OpexError? {
Expand Down