diff --git a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/controller/AuthAdminController.kt b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/controller/AuthAdminController.kt index 334340d86..62a8b710d 100644 --- a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/controller/AuthAdminController.kt +++ b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/controller/AuthAdminController.kt @@ -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 @@ -13,8 +14,13 @@ import org.springframework.web.bind.annotation.* class AuthAdminController(private val service: AuthAdminService) { @GetMapping("/user") - suspend fun getAllKeycloakUsers(): List { - 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") @@ -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 { - 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 { + return service.findUsersInGroupByName(groupName, offset, size).map { it.asKeycloakUser() } } } \ No newline at end of file diff --git a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/data/QueryUserResponse.kt b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/data/QueryUserResponse.kt new file mode 100644 index 000000000..676fe6499 --- /dev/null +++ b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/data/QueryUserResponse.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.admin.ports.auth.data + +data class QueryUserResponse( + val total: Int, + val users: List +) \ No newline at end of file diff --git a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/proxy/KeycloakProxy.kt b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/proxy/KeycloakProxy.kt index caaf0f29f..cdc9cb856 100644 --- a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/proxy/KeycloakProxy.kt +++ b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/proxy/KeycloakProxy.kt @@ -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() diff --git a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/service/AuthAdminService.kt b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/service/AuthAdminService.kt index e5e623ff7..3d481c4ae 100644 --- a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/service/AuthAdminService.kt +++ b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/service/AuthAdminService.kt @@ -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 @@ -17,8 +19,15 @@ class AuthAdminService( private val proxy: KeycloakProxy ) { - fun findAllUsers(): List { - 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 { @@ -39,9 +48,9 @@ class AuthAdminService( return group.members() } - fun findUsersInGroupByName(groupName: String): List { + fun findUsersInGroupByName(groupName: String, offset: Int, size: Int): List { val group = findGroupByName(groupName) - return group.members() + return group.members(offset, size) } fun addUserToGroup(userId: String, groupId: String) { @@ -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() } + ) + } + } \ No newline at end of file diff --git a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/utils/Extensions.kt b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/utils/Extensions.kt index 16f548eef..109d68c7b 100644 --- a/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/utils/Extensions.kt +++ b/admin/admin-ports/admin-service-auth/src/main/kotlin/co/nilin/opex/admin/ports/auth/utils/Extensions.kt @@ -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, @@ -13,5 +13,5 @@ fun UserRepresentation.asKeycloakUser(): KeycloakUser = KeycloakUser( isEmailVerified, groups, requiredActions, - attributes + if(includeAttributes) attributes else null ) \ No newline at end of file diff --git a/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/data/UserSessionResponse.kt b/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/data/UserSessionResponse.kt index 7153d8319..f31717a23 100644 --- a/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/data/UserSessionResponse.kt +++ b/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/data/UserSessionResponse.kt @@ -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 ) \ No newline at end of file diff --git a/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/UserManagementResource.kt b/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/UserManagementResource.kt index 60745dfa5..506be2cb8 100644 --- a/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/UserManagementResource.kt +++ b/user-management/keycloak-gateway/src/main/kotlin/co/nilin/opex/auth/gateway/extension/UserManagementResource.kt @@ -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() } diff --git a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt index fbe4543f6..ce8abca2d 100644 --- a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt +++ b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt @@ -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? {