How can I pass my ReactiveSecurityContextHolder role to the mono webclient rest call? - mono

I am having Spring Cloud Gateway with a custom filter.
In this filter, I try to call my webclient with a parameter. The roles from the ReactiveSecurityContextHolder. The getAccessToken result a
com.xy.HeaderGatewayFilter$apply$1$1.apply(HeaderGatewayFilter.kt:37).
If I just return "getAccessToken(webClient, "fixString" ).flatMap(chain::filter)" without the actual roles its working and returns an accessToken.
#Component
class HeaderGatewayFilter(#Autowired private val webClient: WebClient) : AbstractGatewayFilterFactory<HeaderGatewayFilter.Config>() {
open class Config
override fun apply(config: Config?): GatewayFilter {
return OrderedGatewayFilter( { exchange, chain ->
return#OrderedGatewayFilter getCurrentUserRole()
.map{rolle -> getAccessToken(webClient, rolle )
}
.map { accessToken ->
exchange.request
.mutate()
.header(HttpHeaders.AUTHORIZATION, "Bearer ${accessToken}")
return#map exchange
}.flatMap(chain::filter)
},40)
fun getAccessToken(webClient: WebClient, rolle: String): Mono<String> {
val map: MultiValueMap<String, String> = LinkedMultiValueMap()
map.add("scope", "role:${rolle}")
val inserter = BodyInserters.fromFormData(map)
return webClient.post().body(inserter).retrieve().bodyToMono(Map::class.java).map {
it["access_token"] as String }
}
fun getCurrentUserRole() : Mono<String> {
return ReactiveSecurityContextHolder.getContext()
.switchIfEmpty(Mono.error(IllegalStateException("Context is empty")))
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.cast(Jwt::class.java)
.map {jwt ->
return#map jwt.claims["realm_access"] as JSONObject
}
.map { realmAccess ->
return#map realmAccess["roles"] as JSONArray}
.map { rollen -> rollen.first().toString() }
}

Ok, i got it. Everything in my cascade has to return a Mono.
I had a mapping, which returned a String, but now i changed it to Mono

Related

Get Retrofit annotation path for login purposes

I am using Retrofit with OkHttp. I like to add okhttp3.Interceptor to log the annotation path.
For example:
#GET("posts/{post_id}")
suspend fun getPost(#Path("post_id") postId: String): Response<Post>
Interceptor
class MyInterceptor : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Chain): Response {
// log the path here
}
}
I like to log posts/{post_id} each time getPost is called.
How can it be achieved?
found the answer in the tags of the request
fun extractPath(request: Request) = request.run {
javaClass.getDeclaredField("tags").let {
it.isAccessible = true
#Suppress("UNCHECKED_CAST")
it.get(this) as Map<Class<*>, Any>
}
}.run {
values.first().run {
javaClass.getDeclaredField("method").let {
it.isAccessible = true
(it.get(this) as Method).annotations[0]
}
}
}.run {
when (this) {
is DELETE -> value
is GET -> value
is HEAD -> value
is OPTIONS -> value
is PATCH -> value
is POST -> value
is PUT -> value
else -> null
}
}
You may want to check out HttpLoggingInterceptor, for example here.

ktor-client : how to serialize a post body as a specific type

With ktor client, I've got a non-serializable object derived from a serializable object like so:
#Serializable
#SerialName("login-request")
open class LoginRequest (
open val email : String = "",
open val password : String = ""
) : ServiceRequestPayload
impl class
class LoginRequestVo : LoginRequest("", ""), NodeComponent by NodeComponentImpl() {
override val email: String by subject("")
override val password: String by subject("")
}
Now when I manually use kotlinx serialization on this like so:
val request : LoginRequest = LoginRequestVo().apply {
email = "test#gmail.com"
password = "password"
}
val str = Json.encodeToString(request)
println(str)
It correctly serializes it, and when deserialized on the other side, it correctly deserializes to LoginRequest. However, when I use ktor-client to serialize my object, it complains that LoginRequestVo is not serializable. The example code below uses some other objects from my project and has more information that you need, but the gist is that the type of U in the invoke function and therefore the request.payload expression is type LoginRequest as specified by the LoginServiceImpl below.
suspend inline fun <T, U: ServiceRequestPayload> HttpClient.invoke(desc : EndpointDescriptor<T, U>, request : ServiceRequest<U>? ) : ServiceResponse {
val path = "/api${desc.path}"
return when(desc.method) {
HttpMethod.GET -> {
get(path)
}
HttpMethod.POST -> {
if (request == null) {
post(path)
} else {
post(path) {
contentType(ContentType.Application.Json)
body = request.payload
}
}
}
HttpMethod.DELETE -> delete(desc.path)
HttpMethod.PUT -> {
if (request == null) {
put(path)
} else {
post(path) {
contentType(ContentType.Application.Json)
body = request.payload
}
}
}
}
}
class LoginServiceImpl(
context : ApplicationContext
) : LoginService {
private val client by context.service<HttpClient>()
override suspend fun login(request: ServiceRequest<LoginRequest>) = client.invoke(LoginServiceDesc.login, request)
override suspend fun register(request: ServiceRequest<LoginRequest>) = client.invoke(LoginServiceDesc.register, request)
}
The error I get is:
My question is, Is there a way to specify to ktor-client the type or the serializer to use when it needs to serialize a body?

How can I access header in a service?

I'm trying to handle JWT-authentication in gRPC on my backend. I can extract the JWT in an interceptor but how do I access it in my service? I think it should be done with a CoroutineContextServerInterceptor but this doesn't work:
val jwtKey: Context.Key<String> = Context.key("jwtKey")
fun main() {
ServerBuilder.forPort(8980).intercept(UserInjector).addService(MyService).build().start().awaitTermination()
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return GrpcContextElement(Context.current().withValue(jwtKey, jwtString))
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${jwtKey.get()}")
return Test.MyResponse.getDefaultInstance()
}
}
Output:
coroutineContext: something
testingJWT: null
I think you'll need to propagate that in its own coroutine context element.
class JwtElement(val jwtString: String) : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<JwtElement>
override val key: CoroutineContext.Key<JwtElement>
get() = Key
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return JwtElement(jwtString)
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${coroutineContext[JwtElement]}")
return Test.MyResponse.getDefaultInstance()
}
}

Kotlin coroutines, how to async alist of calls and return the result as a map

var responseMap = mutableMapOf<VendorType, ChargeResponse>()
requests.forEach {
val response = when (it.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> {
throw NotImplementedError("${it.vendorType} does not support yet")
}
}
responseMap[it.vendorType] = response
}
responseMap
So I want all the service.charge function run in separate thread. Return the map when all is done
Hope to solve your problem:
Assume your service and request like this:
interface Service {
suspend fun chargeForType1(): ChargeResponse
suspend fun chargeForType2(): ChargeResponse
}
data class Request(val vendorType: VendorType)
suspend fun requestAll(requests: List<Request>): Map<VendorType, ChargeResponse> {
return coroutineScope {
requests
.map { request ->
async {
request.vendorType to when (request.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> throw NotImplementedError("${request.vendorType} does not support yet")
}
}
}
.awaitAll()
.toMap()
}
}

Add new key-value represented by a `Pair` to a `MutableMap`

I currently have this class with dsl like building ability
class GRLMessage {
var headerMap : MutableMap<String, String> = mutableMapOf()
lateinit var methodType : GRLMethod
lateinit var multipartObject : IGRLMultipart
fun message(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun method(closure: GRLMessage.() -> GRLMethod) : GRLMessage {
methodType = closure()
return this
}
fun headers(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
fun multipart(closure: GRLMessage.() -> IGRLMultipart) : GRLMessage {
multipartObject = closure()
return this
}
}
And I test it like this
class GRLMessageTest {
data class DummyMultipart(val field: String) : IGRLMultipart {
override fun getContent() {
this
}
}
#Test fun grlMessageBuilderTest() {
val grlMessage = GRLMessage().message {
method { GRLMethod.POST }
headers {
header { Pair("contentType", "object") }
header { Pair("objectType", "DummyMultipart") }
}
multipart { DummyMultipart("dummy") }
}
val multipart = DummyMultipart("dummy")
val headers = mapOf(
Pair("contentType", "object"),
Pair("objectType", "DummyMultipart")
)
val method = GRLMethod.POST
assertEquals(multipart, grlMessage.multipartObject)
assertEquals(method, grlMessage.methodType)
assertEquals(headers, grlMessage.headerMap)
}
}
But despite providing
header { Pair("contentType", "object") }
I still have to evaluate closure inside header method and directly put key and value into my MutableMap
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
Is there a better way adding entries to Map?
Does your headerMap need to be a var? If not, you can change it to a val and use headerMap += closure().
Adding an extension function makes your fluent methods more obviously fluent:
fun <T: Any> T.fluently(func: ()->Unit): T {
return this.apply { func() }
}
With that your fluent function is always clear about its return:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return fluently { headerMap += closure() }
}
Which is really the same as:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return this.apply { headerMap += closure() }
}
But the extension function adds a touch of readability.
Above I use the answer given in by #Ruckus for solving your specific question of adding a Pair to the headerMap. But you have other options that you might want to know about for other use cases of your DSL...
You can use let, apply or with which would allow any type of decomposition of the results of closure() call (maybe it is more complicated than Pair in the future). All of these are basically the same, minus their resulting value:
with(closure()) { headerMap.put(this.first, this.second) }
closure().apply { headerMap.put(this.first, this.second) }
closure().let { headerMap.put(it.first, it.second) }
Using let or apply is nice if you want to handle a case where closure() allows nullable return, in which case you might want to take action only if not null:
closure()?.apply { headerMap.put(this.first, this.second) }
closure()?.let { headerMap.put(it.first, it.second) }
Other notes about your code:
use val instead of var unless you have no other choice
lateinit (or the similar Delegates.notNull()) seem dangerous to use in an uncontrolled lifecycle where there is no guarantee it will be completed, because the error message will be confusing and happen at some unexpected time in the future. There are likely other ways to solve this with a DSL that chains calls to create more of a multi-step grammar
You can shorten code by only having types on one side of the assignment, for example:
val myMap = mutableMapOf<String, String>()
instead of
var myMap : MutableMap<String, String> = mutableMapOf()
Well for now as a solution I created extension for MutableMap
fun MutableMap<String, String>.put(pair : Pair<String, String>) {
this.put(pair.first, pair.second)
}
Which allowed me to write like this
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
headerMap.put(closure())
return this
}