Spring Integration DSL: lambda to return a Message<T> in handle method, e.g. with DelegatingSessionFactory? - spring-integration-dsl

Motivation: I need to set the threadKey for a DelegatingSessionFactory before I route to an Sftp outbound gateway and unset the threadKey afterwards.
Depending on a tenant I need to use a different Sftp user account. The user accounts are a matter of configuration in my application.yml, I do not want to write separate routes for each new tenant.
public IntegrationFlow aDynamicSftpFlow() {
f -> f
.handle(tenantSessionDefine()) // how can I use a lambda instead?
.handle(Sftp.outboundGateway(delegatingSessionFactory, ...))
.handle(...) // undefine sftp session
}
Setting the threadKey requires a Message<?>, not just payload and headers. So I use a bean because it takes a message:
public class TenantSessionDefine {
private DelegatingSessionFactory delegatingSessionFactory;
public TenantSessionDefine(DelegatingSessionFactory delegatingSessionFactory) {
this.delegatingSessionFactory = delegatingSessionFactory;
}
public Message<?> defineSession(Message<?> message) {
return delegatingSessionFactory.setThreadKey(message, message.getHeaders()
.get("tenantId", String.class));
// used by SessionFactoryLocator
}
}
I would like to write that as a lambda, as in
.handle(message -> delegatingSessionFactory.setThreadKey(message,
message.getPayload().getTenant())
but that is not so easy. The lambda that can be used with handle() which takes a Message<T> ends the flow because it is a void function (MessageHandler functional interface). The other lambda is a GenericHandler, which does not end the flow, but it takes payload and headers, not a message.
This is just an example, every now and then I wish I could use handle() with a message in a lambda without ending the flow. How can I do that?
Update
The DelegatingSessionFactory is not a particularly well suited example. Since setting and clearing the thread key should happen before and after the sftp invocation, an advice fits better than defining a handler before and after the call.

Got it. The javadoc for handle() says
Use handle(Class, GenericHandler) if you need to access the entire message.
The Class parameter must be Message.class:
.handle(Message.class,
(message, headers) -> sftpSessionFactory
.setThreadKey(message, headers.get("tenantId")))

Related

How can I configure 2 step filter chain for webflux security

I need to support 2 types of auth:
trust system where the tokens are injected by an autoconfigured filter.
postman like usecases where we get tokens from an inner app and test what our services return. This only works in our corp network (if that details is important).
My task is to validate the tokens on the server side in a way that if either of the methods is valid, I should allow the access.
My code for the initial (naive) implementation demonstrating the functionality is:
RemoteAddressSource remoteAddressSource = new WebFluxRemoteAddressSource(exchange);
return validateNotBlank(token)
.switchIfEmpty(Mono.defer(() -> iafTokenValidator.validateToken(remoteAddressSource)
.onErrorResume(AuthenticationFailedException.class, e -> tfTokenValidator.validateToken(remoteAddressSource))))
.onErrorResume(AuthenticationFailedException.class, e -> sendErrorResponse(e, exchange))
.switchIfEmpty(chain);
While working and denies and allows as expected, there are several problems with this approach:
This couples both forms of authentication instead of having 2 classes for them.
In case of not valid, the end user always "sees" the latter error message while it could stem from the first method.
I'm sure there is a proper way to do that with a properly configured filter-chain, though I haven't found any to this point.
I have come up with something that works, though I'm not fully happy about it.
I wanted a solution where the token runs through both options, and if either validates, the request is allowed, but I wanted them to be totally separated and oblivious to each other, which I have partially achieved through TokenAuthManager.
Still, when the none trust form is deprecated, we'll need to change the AuthManager, rather than just deleting this code.
Also, I don't like if statements and try/catch blocks for exception handling with reactive operators, so still not ideal.
As least it's in 1 class, so we're not looking at a lot of work:
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
ServiceAuthentication serviceAuthentication = (ServiceAuthentication) authentication;
String stripped = serviceAuthentication.getCredentials().toString().replaceAll("Bearer ", "").trim();
try {
ServicePrincipal principal = stv.validate(stripped, (String) serviceAuthentication.getDetails());
log.debug("client {} from ip {} allowed", principal.getName(), authentication.getDetails());
//if this is valid, return
authentication.setAuthenticated(true);
return Mono.just(authentication);
} catch (TokenException e) {
log.debug("token {} invalid for TF", authentication.getCredentials());
if (serviceAuthentication.isPureTf()) {
return Mono.error(new BadCredentialsException("The provided TF token is invalid", e));
}
return iafTokenValidator.authenticate(serviceAuthentication);
}
}

Extending a Future and/or preserving await functionality

I'd like to extend the Future class and give it more functionality while keeping the functionality of the await keyword. As I understand it, Futures can't be extended directly in Dart, but perhaps there is another way to achieve what I want?
The class I'm trying to create is effectively a mechanism for interacting with an API. The server is RPC-like in the sense that its' API can be bi-directional while the request is open (messages can go back and forth between server and client until the request is considered resolved).
To make the client library more usable, I'm trying to create a Request class that has all the goodness of a normal Future, but also the ability to use a when() or on() function which effectively listens for updates during the resolution of the future.
Some sudo code of usage:
Request(args)
.when('SomeEvent', (event) => print('An update: $event'))
.then((response) => print('The final response: $response'))
.onError((err) => print('Some error: $err'));
// This also needs to work:
final response = await Request(args);
So far I have something like this, but the await keyword doesn't work:
class Request {
final Completer _completer = Completer();
Request(args) {
/* Setup and make some request to an API and respond/update using response|error|update */
}
Future<dynamic> then(fn) async {
// Should this actually return a Request?
return _completer.future.then(fn);
}
Future<dynamic> onError(fn) async {
// Should this actually return a Request?
return _completer.future.onError(fn);
}
Request when(String eventName, Function fn) {
/* attach a listener/stream which fires fn on update */
return this;
}
void _response(res) {
_completer.complete(res);
}
void _error(err) {
_completer.completeError(err);
}
void _update(upd) {
/* Some update from the request is given */
}
}
Is what I'm attempting impossible in Dart?
I'd recommend not extending the Future interface, but instead let your Request class have a future instead of being a future.
Then you can do await request.result and request.when(...), without having to re-implement the entire Future API.
If you insist on making Request be a Future, all you need is to add implements Future<Object?> to the class ... and then actually implement the entire Future API. No need to do onError (that's an extension method which works on any Future, including your Request), but you need to implement then, catchError, whenComplete, asStream and timeout correctly and totally (support all the arguments and have the correct type).
Then you'll be able to use your class with await.
If you do that, you can make those functions return Request too, if you make Request generic (class Request<T> implements Future<T>), because .then<int>(...) needs to return a Future<int>. You'd need a strategy for forwarding the when/on events to the new futures then.
It's much easier not to do that, and just expose the internal future by itself, separately from the progress callbacks.
See also CancelableOperation.

Smallrye open api interceptor

I am developing a rest application.
Some endpoints require a custom header parameter, not related to authorisation. I created a custom annotation using jax-rs NameBinding. Here is an usage example:
#GET
#RequiresBankHeader
public int get(
#HeaderParam("bank")
#Parameter(ref = "#/components/parameters/banks")
String bank) {
return someService.getSomeInformation();
}
There is a provider that intercepts this call and do some routine using the information in the header parameter.
The problem is that I have to repeat '#HeaderParam("bank") #Parameter(ref = "#/components/parameters/banks") String bank' everywhere, just so it appears in Swagger, even though the service classes do not need it. I was able to at least reuse the parameter definition with ref = "#/components/parameters/banks", and declaring it in the OpenAPI.yml file, that Quarkus merges with generated code very nicely.
But I also want to create and interceptor to dynamically add this do the OpenApi definition whenever RequiresBankHeader annotation is present.
Is there a way to do it?
I dont think you can use interceptors to modify the generated Openapi schema output.
If all methods on a given endpoint require some parameter, you can specify it on class level like so:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("bank")
#Parameter(name = "bank")
String bank;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}
As mentioned by Roberto Cortez, the MP OpenAPI spec provides a programmatic way to contribute metadata to the openapi.yml file.
It is not possible to detect an annotation in the JAX-RS endpoint definition, but it was good enough to automate what I needed. Since all methods that had the RequiresBankHeader return the same Schema, I was able to hack it like this:
public class OpenApiConfigurator implements OASFilter {
#Override
public Operation filterOperation(Operation operation) {
operation.getResponses().getAPIResponses().values().stream().
map(APIResponse::getContent).
filter(Objects::nonNull).
map(Content::getMediaTypes).
flatMap(mediaTypes -> mediaTypes.values().stream()).
map(MediaType::getSchema).
filter(Objects::nonNull).
map(Schema::getRef).
filter(Objects::nonNull).
filter(ref -> ref.contains("the common response schema")).
findAny().
ifPresent(schema -> {
ParameterImpl parameter = new ParameterImpl();
parameter.setRef("#/components/parameters/banks");
operation.addParameter(parameter);
});
return operation;
}
OpenApiConfigurator should be configure in the application properties, using mp.openapi.filter=com.yourcompany.OpenApiConfigurator

How #PreAuthorize is working in an Reactive Application or how to live without ThreadLocal?

Can you explain where the advice handling #PreAuthorize("hasRole('ADMIN')") retrieves the SecurityContext in a Reactive application?
The following Spring Security example is a good illustration of this kind of usage: https://github.com/spring-projects/spring-security/tree/5.0.0.M4/samples/javaconfig/hellowebflux-method
After checking the Spring Security Webflux source code, I've found some implementations of SecurityContextRepository but the load method needs the ServerWebExchange as a parameter.
I'm trying to understand how to replace SecurityContextHolder.getContext().getAuthentication() call in a standard service (because ThreadLocal is no longer an option in a Reactive Application), but I don't understand how to replace this with a call to a SecurityContextRepository without a reference on the ServerWebExchange.
The ReactiveSecurityContextHolder provides the authentication in a reactive way, and is analogous to SecurityContextHolder.
Its getContext() method provides a Mono<SecurityContext>, just like SecurityContextHolder.getContext() provides a SecurityContext.
ReactiveSecurityContextHolder
.getContext()
.map(context ->
context.getAuthentication()
You're right, ThreadLocal is no longer an option because the processing of a request is not tied to a particular thread.
Currently, Spring Security is storing the authentication information as a ServerWebExchange attribute, so tied to the current request/response pair. But you still need that information when you don't have direct access to the current exchange, like #PreAuthorize.
The authentication information is stored in the Reactive pipeline itself (so accessible from your Mono or Flux), which is a very interesting Reactor feature - managing a context tied to a particular Subscriber (in a web application, the HTTP client is pulling data from the server and acts as such).
I'm not aware of an equivalent of SecurityContextHolder, or some shortcut method to get the Authentication information from the context.
See more about Reactor Context feature in the reference documentation.
You can also see an example of that being used in Spring Security here.
I implemented a JwtAuthenticationConverter (kotlin):
#Component
class JwtAuthenticationConverter : Function<ServerWebExchange,
Mono<Authentication>> {
#Autowired
lateinit var jwtTokenUtil: JwtTokenUtil
#Autowired
lateinit var userDetailsService: ReactiveUserDetailsService
private val log = LogFactory.getLog(this::class.java)
override fun apply(exchange: ServerWebExchange): Mono<Authentication> {
val request = exchange.request
val token = getJwtFromRequest(request)
if ( token != null )
try {
return userDetailsService.findByUsername(jwtTokenUtil.getUsernameFromToken(token))
.map { UsernamePasswordAuthenticationToken(it, null, it.authorities) }
} catch ( e: Exception ) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
exchange.response.headers["internal-message"] = e.message
log.error(e)
}
return Mono.empty()
}
private fun getJwtFromRequest(request: ServerHttpRequest): String? {
val bearerToken = request.headers[SecurityConstants.TOKEN_HEADER]?.first {
it.startsWith(SecurityConstants.TOKEN_PREFIX, true)}
return if (bearerToken.isNullOrBlank()) null else bearerToken?.substring(7, bearerToken.length)
}
And then I set a SecurityConfig like this:
val authFilter = AuthenticationWebFilter(ReactiveAuthenticationManager {
authentication: Authentication -> Mono.just(authentication)
})
authFilter.setAuthenticationConverter(jwtAuthenticationConverter)
http.addFilterAt( authFilter, SecurityWebFiltersOrder.AUTHENTICATION)
You can use this approach to customize your AuthenticationConverter as I did to jwt based authentication to set the desired authentication object.

Grails 2.5: how to propogate the IP address down to service layer?

The controller layer can get the IP using request.getRemoteAddr() and/or request.getHeader("Client-IP") etc.
However, down in the bowels of the service layer, we might want to log some detected or suspected fraudulent activity by the user, along with the IP address of the user. However, the IP is not available to the service layer, nor is the request.
Obviously, every call from every controller method to every single service method could also pass in the IP or the request, but as we have thousands of these calls and lots of chains of them, it is not really practical.
Can anyone think of a better way?
As we are not in charge of instantiation of the services (these just get magically injected), we can't even pass the IP in when each service is created for the current HTTP call.
UPDATE 1
As suggested, tried the MDC route. Unfortunately, this does not seem to work.
in filter:
import org.apache.log4j.MDC
class IpFilters {
def filters = {
all() {
before = {
MDC.put "IP", "1.1.1.1"
println "MDC.put:" + MDC.get("IP")
}
afterView = { Exception e ->
println "MDC.remove:" + MDC.get("IP")
MDC.remove 'IP'
}
}
in service:
import org.apache.log4j.MDC
:
def someMethod() {
String ip = MDC.get("IP")
println("someMethod: IP = $ip")
}
The result is always:
MDC.put:1.1.1.1
MDC.remove:1.1.1.1
someMethod: IP = null
So the service cant access MDC variables put on the thread in the filter, which is a real shame. Possibly the problem is that "someMethod" is actually called by springSecuirty.
Well, it is highly recommended that we should keep the business logic aware of the controller logic. But keeping your situation in mind, you have to do that and absolutely available. In your service method, write this to log the IP address of the current request:
import org.springframework.web.context.request.RequestContextHolder
// ... your code and class
def request = RequestContextHolder.currentRequestAttributes().getRequest()
println request.getRemoteAddr()
Just make sure, you handle the whatever exception thrown from that line when the same service method is invoked from outside a Grails request context like from a Job.
my two pence worth
basically been using above and it works perfectly fine when a request is directed through standard grails practices.
In this scenario, user triggers websockets connection this then is injected into websockets listener using Holders.applicationContext.
The issue arises around are your outside of the web request.
the fix was painful but may come in handy for anyone else in this situation:
private static String userIp
String getIp() {
String i
new Thread({
//to bypass :
// Are you referring to request attributes outside of an actual web request, or processing a
// request outside of the originally receiving thread? If you are actually operating within a web request
// and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet:
// In this case, use RequestContextListener or RequestContextFilter to expose the current request.
def webRequest = RequestContextHolder.getRequestAttributes()
if(!webRequest) {
def servletContext = ServletContextHolder.getServletContext()
def applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)
webRequest = grails.util.GrailsWebMockUtil.bindMockWebRequest(applicationContext)
}
//def request = RequestContextHolder.currentRequestAttributes().request
def request = WebUtils.retrieveGrailsWebRequest().currentRequest
i=request.getRemoteAddr()
if (!i ||i == '127.0.0.1') {
i=request.getHeader("X-Forwarded-For")
}
if (!i ||i == '127.0.0.1') {
i=request.getHeader("Client-IP")
}
if (!i) { i="127.0.0.1"}
this.userIp=i
} as Runnable ).start()
return i
}
Now when calling this some sleep time is required due to it running in as a runnable :
def aa = getIp()
sleep(300)
println "$aa is aa"
println "---- ip ${userIp}"
Also provided alternative way of calling request def request = WebUtils.retrieveGrailsWebRequest().currentRequest in grails 3 the commented out line .request comes up unrecognised in ide (even though it works)
the new Thread({ was still needed since even though it returned ip after getting ip it was attempting to save to a db and some other bizarre issue appeared around
java.lang.RuntimeException: org.springframework.mock.web.MockHttpServletRequest.getServletContext()Ljavax/servlet/ServletContext;
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerBase.handlePojoMethodException(PojoMessageHandlerBase.java:119)
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:82)
so the fix to getting hold of request attribute in this scenario is above
for the mock libraries you will require this in build.gradle:
compile 'org.springframework:spring-test:2.5'
So the saga continued - the above did not actually appear to work in my case since basically the request originated by user but when sent to websockets - the session attempting to retrieve Request (ip/session) was not actual real user.
This in the end had to be done a very different way so really steeply off the topic but when this method of attempting ip does not work the only way left is through SessionListeners:
in src/main/groovy/{packageName}
class SessionListener implements HttpSessionListener {
private static List activeUsers = Collections.synchronizedList(new ArrayList())
static Map sessions = [:].asSynchronized()
void sessionCreated (HttpSessionEvent se) {
sessions.put(se.session.id, se.session)
}
void sessionDestroyed (HttpSessionEvent se) {
sessions.remove(se.session.id)
}
}
in grails-app/init/Application.groovy
Closure doWithSpring() {
{ ->
websocketConfig WebSocketConfig
}
}
// this already exists
static void main(String[] args) {
GrailsApp.run(Application, args)
}
in that same init folder:
class WebSocketConfig {
#Bean
public ServletContextInitializer myInitializer() {
return new ServletContextInitializer() {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(SessionListener)
}
}
}
}
Now to get userIP, when the socket initially connects it sends the user's session to sockets. the socket registers that user's session within the websockets usersession details.
when attempting to get the user ip (i have registered the users ip to session.ip on the controller/page hitting the page opening sockets)
def aa = SessionListener.sessions.find{it.key==sessionId}?.value
println "aa $aa.ip"

Resources