I have an oauth2 server and client. In client I configured ClientRegistrationRepository:
#Bean
#Conditional(SsoCondition::class)
open fun clientRegistrationRepository(): ClientRegistrationRepository {
val test = ClientRegistration.withRegistrationId(registrationId)
.clientId(clientId)
.clientSecret(clientSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.authorizationUri(authorizeUri)
.tokenUri(tokenUri)
.userInfoUri(userInfoUri)
.scope("read", "write")
.userNameAttributeName(userNameAttribute)
.build()
return InMemoryClientRegistrationRepository(test)
}
This works fine and authorization is performed.
The problem is in userInfoUri. This uri is invoked and needed server method is performed. I see the user data and that method return this data.
The method for authorizeUri is:
#GetMapping("/api/user/me")
fun getUserInfo(response: HttpServletResponse, request: HttpServletRequest, principal: Principal): HashMap<String, Any?> {
val authentication = SecurityContextHolder.getContext().authentication
val userData = HashMap<String, Any?>()
userData[OUTER_ID] = principal.name
val ssoUser = authentication.userAuthentication.principal.attributes
// getting data from ssoUser to userData
...
return userData
}
And so the question is: where or how can I get this data in the client application?
I don't know how right this solution, but I got the user data like this:
Creating custom implementation of OAuth2AuthorizedClientService interface:
class CustomOAuth2AuthorizedClientService(private val clientRegistrationRepository: ClientRegistrationRepository) : OAuth2AuthorizedClientService {
private val principalData = ConcurrentHashMap<String, Authentication>()
...
override fun saveAuthorizedClient(authorizedClient: OAuth2AuthorizedClient, principal: Authentication) {
...
val key = ... // create some key
principalData[key] = principal
}
...
fun getPrincipal(key: String): Authentication? {
return authorizedClientsPrincipal[key]
}
}
Creating bean for CustomOAuth2AuthorizedClientService:
#Bean
open fun authorizedClientService(): OAuth2AuthorizedClientService {
return CustomOAuth2AuthorizedClientService(clientRegistrationRepository())
}
where clientRegistrationRepository() is a ClientRegistrationRepository bean.
In the code get user data from CustomOAuth2AuthorizedClientService bean:
#Autowired
private var oAuth2AuthorizedClientService: OAuth2AuthorizedClientService
...
fun test() {
val userData = (oAuth2AuthorizedClientService as CustomOAuth2AuthorizedClientService).getPrincipal(key)
}
Related
Test that when calling
underTest.subscribe()
that it verifies the function here is actually called.
subscription.subscribe("nats-msg-events", ::process)
Test that when calling
underTest.process(msg, connection)
that it verifies the functions here are actually called.
service.process(msg)
connection.publish(msg.headers.replyToOrThrow(), "ok".toByteArray(Charsets.UTF_8))
Class
class Processor(
private val subscription: Subscription,
private val service: Service,
) {
override fun subscribe() {
subscription.subscribe("n-m-events", ::process)
}
fun process(msg: Message, connection: Connection) {
service.process(msg)
connection.publish(msg.headers.replyToOrThrow(), "ok".toByteArray(Charsets.UTF_8))
}
}
Current test class
class ProcessorTest : BehaviorSpec({
val subscription = mockk<Subscription>()
val service = mockk<Service>()
val connection = mockk<Connection>()
val msg = mockk<DecodedMessage>()
val underTest = Processor(
subscription,
service
)
Given("processing of message"){
val randomReply = randomString()
every { service.process(msg) } just runs
When("Message is being processed"){
Then("service is called with the decoded message"){
underTest.process(msg, connection)
verify { service.process(msg) }
}
}
}
})
I'm super frustrated with a Kotlin/Mockito problem
What I want to accomplish is very simple, I've an AuthorizationFilter on my springboot application and for test purposes I want to mock its behavior to let the test requests pass by
My AuthorizationFilter indeed calls an API which will then provide the user auth status. so I thought that the simplest way to mock this is just have the AuthApi mocked into the filter and return whatever status I want... BUT IT WORKS RANDOMLY
#Component
class AuthorizationFilter(
val authApi: authApi
) : OncePerRequestFilter() {
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
if (request.method.equals("OPTIONS")) {
filterChain.doFilter(request, response)
return
}
val token = request.getHeader("authorization")
if (token == null) {
response.sendError(401)
return
}
runCatching {
authApi.authorize(token.replace("Bearer ", ""))
}.onSuccess {
AuthorizationContext.set(it)
filterChain.doFilter(request, response)
}.onFailure {
it.printStackTrace()
response.sendError(401)
}
}
}
the authApi authorize method is irrelevant to this question, but just let it be clear it will NEVER return null... it might throw an exception but wont return null
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
class SocketIOServerTest {
#MockBean
lateinit var mockedApiComponent: AuthApi
#Autowired
lateinit var boardRepository: BoardRepository
#Autowired
private lateinit var servletRegistrationBean: ServletRegistrationBean<SocketIOServer>
private lateinit var socketIOServer: SocketIOServer
#LocalServerPort
private val serverPort: String? = null
lateinit var clientSocket: Socket
private val userId = 1
private val groupId = 123
private val admin = false
private val auth = Authorization("token", userId, groupId, admin)
private val objectMapper = ObjectMapper()
#BeforeAll
fun connect() {
AuthorizationContext.set(auth)
Mockito.`when`(mockedApiComponent.authorize(anyOrNull())).thenReturn(auth)
socketIOServer = servletRegistrationBean.servlet
clientSocket = IO.socket("http://localhost:${serverPort}", IO.Options.builder().setExtraHeaders(mutableMapOf(Pair("Authorization", listOf("Bearer token")))).build())
clientSocket.on(Socket.EVENT_CONNECT) {
println("client connected")
}
clientSocket.on(Socket.EVENT_DISCONNECT) {
println("client disconnected")
}
clientSocket.connect()
}
#Test
fun testPingPong() {
var finished = false
clientSocket.on("pong") {
println("event: ${it[0]}")
val pongTime = (it[0] as String).substring(18, 31).toLong()
assertTrue(System.currentTimeMillis() - pongTime < 1000)
finished = true
}
clientSocket.emit("ping")
while (!finished) Thread.yield()
}
#Test
fun testBasicNotification(){
clientSocket.on("basic_notification"){
println(Arrays.toString(it))
}
socketIOServer.send(SocketIOEvent("${groupId}","basic_notification","data"))
Thread.sleep(1000)
}
#Test
fun testBoardNotification() {
clientSocket.on("entity_create") {
val event = it[0] as String
println("event: $event")
val eventValue = objectMapper.readValue(event, Map::class.java)
val entityValue = eventValue["entity"] as Map<*, *>
assertEquals("BOARD", eventValue["entity_type"])
assertEquals("board name", entityValue["name"])
assertEquals(groupId, entityValue["groupId"])
assertEquals(userId, entityValue["created_by"])
assertEquals(userId, entityValue["last_modified_by"])
}
val board = boardRepository.save(Board(groupId, "board name"))
//boardRepository.delete(board)
}}
Just to be clear, THE TEST WORKS, the assertions are correct and although it has some random behavior at the end it works.... BUT IT PRINTS A BIG STACK TRACE DUE SOME CRAZY BEHAVIOR
As you can see I'm using a SocketIO client which sends several requests out of my code... some of them get authenticated and some of them throw nullpointerexception on this line
.onSuccess {
AuthorizationContext.set(it) //this line
filterChain.doFilter(request, response)
}.
because it is null, because somehow the mockedApiComponent.authorize() returned null... again which would be impossible on the real component and which shouldn't be happening because the mock clearly states which object to return
I've exhaustively debbuged this code, thinking that somehow junit got two beans of the AuthApi
but the whole execution shows the same object id which matches the mock... and even weirder that the token parameter used on authorize is always the same
can anyone help me?
I've exhaustively debbuged this code, thinking that somehow junit got two beans of the AuthApi but the whole execution shows the same object id which matches the mock... and even weirder that the token parameter used on authorize is always the same
This looks to me disturbing, like some problem with async code at runtime. I would try to do a couple of things:
Check for when the context is null in: AuthorizationContext.set(it) and put some more debug code to know what is happening. Or just debug from there
Use a recover{} block to deal with the NullPointerException and debug from there to see original problem in stack trace
What happens when instead runCatching{} you use mapCatching{}?
i was wondering if it is possible to send messages from the backend (for example a running task that receives information from an external system) to the UI. In my case it needs to be a specific session (no broadcast) and only on a specific screen
plan B would be polling the backend frequently but i was hoping to get something more "realtime"
I was trying to work something out like this, but i keep getting a NotSerializableException.
#Push
class StorageAccess : Screen(), MessageListener {
#Inject
private lateinit var stationWSService: StationWebSocketService
#Inject
private lateinit var notifications: Notifications
#Subscribe
private fun onInit(event: InitEvent) {
}
#Subscribe("stationPicker")
private fun onStationPickerValueChange(event: HasValue.ValueChangeEvent<StorageUnit>) {
val current = AppUI.getCurrent()
current.userSession.id ?: return
val prevValue = event.prevValue
if (prevValue != null) {
stationWSService.remove(current.userSession.id)
}
val value = event.value ?: return
stationWSService.listen(current.userSession.id, value, this)
}
override fun messageReceived(message: String) {
val current = AppUI.getCurrent()
current.access {
notifications.create().withCaption(message).show()
}
}
#Subscribe
private fun onAfterDetach(event: AfterDetachEvent) {
val current = AppUI.getCurrent()
current.userSession.id ?: return
stationWSService.remove(current.userSession.id)
}
}
-- The callback interface
interface MessageListener : Serializable {
fun messageReceived(message: String);
}
-- The listen method of my backend service
private val listeners: MutableMap<String, MutableMap<UUID, MessageListener>> = HashMap()
override fun listen(id: UUID, storageUnit: StorageUnit, callback: MessageListener) {
val unitStationIP: String = storageUnit.unitStationIP ?: return
if (!listeners.containsKey(unitStationIP))
listeners[unitStationIP] = HashMap()
listeners[unitStationIP]?.set(id, callback)
}
The Exception i get is NotSerializableException: com.haulmont.cuba.web.sys.WebNotifications which happens during adding the listener to the backend: stationWSService.listen(current.userSession.id, value, this)
as far as i understand this is the place where the UI sends the information to the backend - and with it the entire status of the class StorageAccess, including all its members.
is there an elegant solution to this?
regards
There is an add-on that solves exactly this problem: https://github.com/cuba-platform/global-events-addon
I need some params from the headers inside my coroutine in my controller, and log them as a corelation id for my requests.
Is it possible to use webflux / kotlin coroutines in controller AND to do contextual logging with the params in the header ?
I know Webflux can use WebFilter to intercept headers and log them or modify them, but can it be sent to the coroutine it will trigger ?
#RestController
class ItemController(private val itemRepository: ItemRepository) {
#GetMapping("/")
suspend fun findAllItems(): List<Item> =
// do stuff
logger.log("Corelation id is : " + myCorelationIdHeaderParam) // that's the param i need
return itemService.findAll()
}
Any context you set in the webfilter can be accessed by using subscriberContext down the line in your controllers/services.
Below is an example using Java. You can use similar logic in your Kotlin code:
Your filter: (Here you are setting the header value "someHeaderval" in your "myContext" )
public class MyFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String someHeaderval = request.getHeaders().get("someHeader").get(0);
return chain.filter(exchange).subscriberContext(context -> {
return context.put("myContext",someHeaderval);
});;
}
}
Now you can use this context anywhere:
#GetMapping(value = "/myGetApi")
public Mono<String> sampleGet() {
return Mono.subscriberContext()
.flatMap(context -> {
String myHeaderVal = (String)context.get("myContext");
//do logging with this header value
return someService.doSomething(myHeaderVal);
});
}
It turns out you can access the ReactorContext from the CoroutineContext with
coroutineContext[ReactorContext]
Here is my code :
#Component
class MyWebFilter : WebFilter {
val headerKey = "correlation-token-key"
val contextKey = "correlationId"
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val headers: HttpHeaders = exchange.request.headers
return chain.filter(exchange)
.subscriberContext(Context.of(contextKey, headers[headerKey] ?: "unidentified"))
}
}
and the controller part (most important for Kotlin users) :
#RestController
class ItemController(private val itemRepository: ItemRepository) {
#GetMapping("/")
suspend fun findAllItems(): List<Item> =
// do stuff
logger.log("Correlation id of request is : " + coroutineContext[ReactorContext]?.context?.get<List<String>>("correlationId")?.firstOrNull())
return itemService.findAll()
}
Say I have this situation:
interface Repository {
fun getMovies(success: (List<String>) -> Unit, failure: (Int) -> Unit)
}
and I want to mock the implementation of this interface. Basically in this case, there are two lambdas as input parameters to the getmovie method, and for the test case, I only want to produce success (success.invoke(theMoviesList) should be called).
Below is something similar to what I would like to do:
class MovieViewModel constructor(val repository: AppRepository) {
var movieNames = listOf<String>() // Not private, or live data, for simplicity
fun fetchMovies() {
repository.fetchMovies(
success = {
movies ->
this.movieNames = movies
}}, failure: {
statusCode ->
})
}
}
class MoviePageTests {
private var movieViewModel: MovieViewModel? = null
#Mock
lateinit var mockRepository: AppRepository
#Before
#Throws(Exception::class)
fun before() {
MockitoAnnotations.initMocks(this)
movieViewModel = MovieViewModel(repository = mockRepository)
}
#Test
fun checkFetchMoviesUpdatesMoviesData() {
var testMovies = listof("Dracula", "Superman")
//Set up mockito so that the repository generates success with testMovies above
?????
//
movieViewModel.fetchMovies()
assertEquals(movieViewModel.movies, testMovies)
}
}
I know how to do this by way of a RepositoryImpl, but not in Mockito, despite looking at many examples online.
Any ideas?