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
4 changes: 4 additions & 0 deletions admin/admin-app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
<groupId>co.nilin.opex.admin</groupId>
<artifactId>admin-service-auth</artifactId>
</dependency>
<dependency>
<groupId>co.nilin.opex.admin</groupId>
<artifactId>admin-submitter-kafka</artifactId>
</dependency>
<dependency>
<groupId>co.nilin.opex.utility.error</groupId>
<artifactId>error-handler</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package co.nilin.opex.admin.app.config

import co.nilin.opex.admin.app.utils.hasRealmRole
import co.nilin.opex.admin.app.utils.hasRole
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
Expand All @@ -20,7 +20,8 @@ class SecurityConfig(private val webClient: WebClient) {
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
http.csrf().disable()
.authorizeExchange()
.pathMatchers("/auth/**").hasRealmRole("SCOPE_trust", "finance-admin")
.pathMatchers("/auth/**").hasRole("SCOPE_trust", "finance-admin")
.pathMatchers("/system/**").hasRole("SCOPE_trust", "system-admin")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package co.nilin.opex.admin.app.controller

import co.nilin.opex.admin.app.data.AddCurrencyRequest
import co.nilin.opex.admin.app.data.EditCurrencyRequest
import co.nilin.opex.admin.app.service.SystemConfigService
import co.nilin.opex.utility.error.data.OpexError
import co.nilin.opex.utility.error.data.OpexException
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/system/v1")
class SystemConfigController(private val service: SystemConfigService) {

@PostMapping("/currency")
suspend fun addCurrency(@RequestBody body: AddCurrencyRequest) {
if (!body.isValid())
throw OpexException(OpexError.BadRequest)
service.addCurrency(body)
}

@PutMapping("/currency/{name}")
suspend fun editCurrency(@RequestBody body: EditCurrencyRequest, @PathVariable name: String) {
if (!body.isValid())
throw OpexException(OpexError.BadRequest)
service.editCurrency(name, body)
}

@DeleteMapping("/currency/{name}")
suspend fun deleteCurrency(@PathVariable name: String) {
service.deleteCurrency(name)
}

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

data class AddCurrencyRequest(
val name: String?,
val symbol: String?,
val precision: Double
Comment thread
Marchosiax marked this conversation as resolved.
) {

fun isValid(): Boolean {
return !name.isNullOrEmpty() && !symbol.isNullOrEmpty() && precision > 0.0 && precision <= 1
}

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

data class EditCurrencyRequest(
val symbol: String?,
val precision: Double
){
fun isValid(): Boolean {
return !symbol.isNullOrEmpty() && precision > 0.0
Comment thread
ebrahimmfadae marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package co.nilin.opex.admin.app.service

import co.nilin.opex.admin.app.data.AddCurrencyRequest
import co.nilin.opex.admin.app.data.EditCurrencyRequest
import co.nilin.opex.admin.core.events.AddCurrencyEvent
import co.nilin.opex.admin.core.events.DeleteCurrencyEvent
import co.nilin.opex.admin.core.events.EditCurrencyEvent
import co.nilin.opex.admin.core.spi.AdminEventPublisher
import org.springframework.stereotype.Service

@Service
class SystemConfigService(private val publisher: AdminEventPublisher) {

suspend fun addCurrency(body: AddCurrencyRequest) {
with(body) {
publisher.publish(AddCurrencyEvent(name!!, symbol!!, precision))
}
}

suspend fun editCurrency(name: String, body: EditCurrencyRequest) {
publisher.publish(EditCurrencyEvent(name, body.symbol!!, body.precision))
}

suspend fun deleteCurrency(name: String) {
publisher.publish(DeleteCurrencyEvent(name))
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package co.nilin.opex.admin.app.utils

import com.nimbusds.jose.shaded.json.JSONArray
import com.nimbusds.jose.shaded.json.JSONObject
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.oauth2.jwt.Jwt

fun ServerHttpSecurity.AuthorizeExchangeSpec.Access.hasRealmRole(
fun ServerHttpSecurity.AuthorizeExchangeSpec.Access.hasRole(
authority: String,
role: String
): ServerHttpSecurity.AuthorizeExchangeSpec = access { mono, _ ->
mono.map { auth ->
auth.authorities.any { it.authority == authority }
&& (((auth.principal as Jwt).claims["realm_access"] as JSONObject?)?.get("roles") as JSONArray?)
?.contains(role) == true
}.map { granted ->
AuthorizationDecision(granted)
val hasAuthority = auth.authorities.any { it.authority == authority }
val hasRole = ((auth.principal as Jwt).claims["roles"] as JSONArray?)?.contains(role) == true
AuthorizationDecision(hasAuthority && hasRole)
}
}
5 changes: 5 additions & 0 deletions admin/admin-app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ spring:
name: opex-admin
main:
allow-bean-definition-overriding: true
kafka:
bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092}
consumer:
group-id: admin
cloud:
bootstrap:
enabled: true
Expand Down Expand Up @@ -34,6 +38,7 @@ spring:
app:
auth:
cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs
token-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/token
keycloak:
url: http://auth:8080/auth
realm: opex
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.nilin.opex.admin.core.events

data class AddCurrencyEvent(
val name: String,
val symbol: String,
val precision: Double
) : AdminEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.nilin.opex.admin.core.events

abstract class AdminEvent {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package co.nilin.opex.admin.core.events

data class DeleteCurrencyEvent(val name: String) : AdminEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package co.nilin.opex.admin.core.events

data class EditCurrencyEvent(
Comment thread
ebrahimmfadae marked this conversation as resolved.
val name: String,
val symbol: String,
val precision: Double
) : AdminEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.nilin.opex.admin.core.spi

import co.nilin.opex.admin.core.events.AdminEvent

interface AdminEventPublisher {

suspend fun publish(event: AdminEvent)

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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.service.AuthAdminService
import co.nilin.opex.admin.ports.auth.utils.asKeycloakUser
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*

@RestController
Expand Down Expand Up @@ -35,4 +37,9 @@ class AuthAdminController(private val service: AuthAdminService) {
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)
}

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

data class ImpersonateRequest(
val clientId: String,
val clientSecret: String,
val userId: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package co.nilin.opex.admin.ports.auth.proxy

import kotlinx.coroutines.reactor.awaitSingle
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.bodyToMono

@Component
class KeycloakProxy(private val webClient: WebClient) {

private val logger = LoggerFactory.getLogger(KeycloakProxy::class.java)

@Value("\${app.auth.token-url}")
private lateinit var tokenUrl: String

suspend fun impersonate(
token: String,
clientId: String,
clientSecret: String,
userId: String
): String {
val body = BodyInserters.fromFormData("client_id", clientId)
.with("client_secret", clientSecret)
.with("requested_subject", userId)
.with("subject_token", token)
.with("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")

logger.info("Request token exchange for user $userId and client $clientId")
return webClient.post()
.uri(tokenUrl)
.accept(MediaType.APPLICATION_JSON)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(body)
.retrieve()
.onStatus({ t -> t.isError }, { it.createException() })
.bodyToMono<String>()
.awaitSingle()
}

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

import co.nilin.opex.admin.ports.auth.data.KycGroup
import co.nilin.opex.admin.ports.auth.proxy.KeycloakProxy
import co.nilin.opex.utility.error.data.OpexError
import co.nilin.opex.utility.error.data.OpexException
import org.keycloak.admin.client.Keycloak
Expand All @@ -10,7 +11,11 @@ import org.keycloak.representations.idm.UserRepresentation
import org.springframework.stereotype.Service

@Service
class AuthAdminService(private val keycloak: Keycloak, private val opexRealm: RealmResource) {
class AuthAdminService(
private val keycloak: Keycloak,
private val opexRealm: RealmResource,
private val proxy: KeycloakProxy
) {

fun findAllUsers(): List<UserRepresentation> {
return opexRealm.users().list()
Expand Down Expand Up @@ -58,4 +63,10 @@ class AuthAdminService(private val keycloak: Keycloak, private val opexRealm: Re
}
}

suspend fun impersonate(clientId: String, clientSecret: String, userId: String): String {
opexRealm.users().get(userId) ?: throw OpexException(OpexError.NotFound, "User not found")
val token = keycloak.tokenManager().accessToken.token
return proxy.impersonate(token, clientId, clientSecret, userId)
}

}
33 changes: 33 additions & 0 deletions admin/admin-ports/admin-submitter-kafka/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
Loading