Vaadin: Disable HTTP PUT/DELETE requests - spring-boot

I'm running Vaadin on Spring-Boot.
I tried implementing WebMvcConfigurer & and HandlerInterceptor to disable PUT & DELETE requests, but it is not working. I can see WebMvcConfigurer is getting loaded, but the preHandle method in the custom HandlerInterceptor never gets called.
I noticed Vaadin is loading AtmosphereInterceptor, wondering if that is overriding my custom spring settings.
Any idea what can I do to disable PUT & DELETE on all paths (/**) by default in vaadin?
edit code:
#Component
class HTTPRequestInterceptor extends HandlerInterceptor {
override def preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean = {
if (HttpMethod.GET.matches(request.getMethod) || HttpMethod.POST.matches(request.getMethod)) {
true
} else {
response.sendError(HttpStatus.METHOD_NOT_ALLOWED.value())
false
}
}
}
#Configuration
class HTTPRequestInterceptorConfig (#Autowired interceptor: HTTPRequestInterceptor) extends WebMvcConfigurer {
private val log = LoggerFactory.getLogger(classOf[HTTPRequestInterceptorConfig])
override def addInterceptors(registry: InterceptorRegistry): Unit = {
log.info("adding interceptors")
registry.addInterceptor(interceptor).addPathPatterns("/**")
}
}
Note: I tried both with & without #Autowired parameter.

Related

#TestSecurity tests no longer succeeding with custom HttpAuthenticationMechanism

When adding a custom HttpAuthenticationMechanism, the #TestSecurity annotation does no longer work.
setup a project with SmallRye JWT authentication as described in https://quarkus.io/guides/security-jwt
create a #QuarkusTest test with test methods annotated with #TestSecurity(user = "user"), check for status code 200
run the test, they succeed, status code is 200
add a custom HttpAuthenticationMechanism without any custom logic, just forwarding the call (see below, documented in https://quarkus.io/guides/security-customization#dealing-with-more-than-one-http-auth-mechanisms)
tests no longer succeed, because returned result is 401
#Alternative
#Priority(1)
#ApplicationScoped
public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanism {
#Inject
JWTAuthMechanism jwt;
#Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
return jwt.authenticate(context, identityProviderManager);
}
#Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return jwt.getChallenge(context);
}
#Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return jwt.getCredentialTypes();
}
#Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return jwt.getCredentialTransport(context);
}
}
How can I make the tests suceed again?
Adding the following class under src/test/java seems to be working.
#Alternative
#Priority(1)
#ApplicationScoped
public class TestHttpAuthenticationMechanism extends io.quarkus.test.security.TestHttpAuthenticationMechanism {
}
I assume the io.quarkus.test.security.TestHttpAuthenticationMechanism is not used in tests due the #Alternative and #Priority attributes on MyHttpAuthenticationMechanism. So setting these attributes on a subclass of io.quarkus.test.security.TestHttpAuthenticationMechanism makes this test mechanism being used again.

Reactive #PreAuthroize on service not evaluated in test

I want to test that access is denied by a service, but the #PreAuthorize is not evaluated. I am probably missing some configuration, but I cannot figure out what.
This is the service
#Service
class FooServiceImpl : FooService {
#PreAuthorize("denyAll")
suspend fun bar() {
println("I am not accessible")
}
}
This is the test where I would expect an AccessDeniedException:
#ExtendWith(SpringExtension::class, MockKExtension::class)
#Import(TestSecurityConfig::class)
internal class FooServiceImplTest {
#InjectMockKs
lateinit var fooService: FooServiceImpl
#Test
fun shouldDeny() {
runBlocking {
assertThrows<Exception> {
fooService.bar()
}
}
}
}
This is my imported test config:
#TestConfiguration
#EnableReactiveMethodSecurity
#EnableWebFluxSecurity
class TestSecurityConfig {
#Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
return http {
csrf { disable() }
formLogin { disable() }
httpBasic { disable() }
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
}
The test fails:
Expected java.lang.Exception to be thrown, but nothing was thrown.
I also tried adding #EnableGlobalMethodSecurity(prePostEnabled = true) (but as far as I understood this is not required when using #EnableReactiveMethodSecurity?) and also tried adding the annotations directly on the test class.
I forgot to think about how the service under test is created. In the setup of the question the #InjectMockks of course creates the instance directly instead of in a spring contest. Without the context, no security is interwoven.
#ExtendWith(SpringExtension::class, MockKExtension::class)
#Import(TestSecurityConfig::class)
#ContextConfiguration(classes = [FooServiceImpl::class])
internal class FooServiceImplTest {
#Autowired
lateinit var fooService: FooService
// ...
}
Note 1: The FooServiceImpl now is selected via the #ContextConfiguration and then #Autowired to fooService by refering to it's interface.
Note 2: Previously I was using #MockK for dependencies (not shown in the question) of the FooService. They now must be #MockKBeans.

ConversationScoped in Quarkus

I am migrating an application from Thorntail to Quarkus. It uses the the conversation scope annotation in a bean that provides the token information during all the rest api request to any service interested in it. But in Quarkus documentation it says the conversation scope is not implemented. Is there a similar feature I can use?
Here is what I want to do:
#Path
#ApplicationScoped
public class FruitsResource {
#Inject FruitsService fruitsService;
#POST
public int post (Fruit fruit) {
return fruitsService.post(fruit);
}
}
#Provider
#ApplicationScoped
private class AuthorizationFilter implements ContainerRequestFilter {
#Inject AuthorizationHolder authorizationHolder;
#Override
public void filter (ContainerRequestContext request) {
String token = request.getHeaderString(HttpHeaders.AUTHORIZATION);
Authorization authorization = createAuthorizationFromToken(token);
authorizationHolder.setAuthorization(authorization);
}
}
#ConversationScoped
private class AuthorizationHolder {
private Authorization authorization;
#Produces
public Authorization getAuthorization () {
return authorization;
}
public void setAuthorization (Authorization authorization) {
this.authorization = authorization;
}
}
#ApplicationScoped
private class FruitsService {
#Inject Authorization authorization;
#Inject EntityManager entityManager;
#Transactional
public void post (Fruit fruit) {
// do some complex validation with the authorization object
...
// persist object
entityManager.persist(fruit);
entityManager.flush();
return fruit.getId();
}
}
Is the Authorization header present in each request? I suppose it is (or should be), in which case just using #RequestScoped instead of #ConversationScoped should work. This is probably the best thing to do, anyway.
In case the header is only present in "first" request and subsequent requests in the same session can reuse the token, then you can just replace #ConversationScoped with #SessionScoped. I think enforcing the header to be present in all requests would be better, though.
Finally, if you'd really like to emulate conversations, you can do something like this (not tested, not even written in an IDE, just from the top of my head):
#SessionScoped
private class AuthorizationHolder {
private ConcurrentMap<String, Authorization> authorizations = new ConcurrentHashMap<>();
public Authorization getAuthorization(ContainerRequestContext request) {
return authorizations.get(getConversationId(request));
}
public void setAuthorization(ContainerRequestContext request, Authorization authorization) {
this.authorizations.put(getConversationId(request), authorization);
}
private String getConversationId(ContainerRequestContext request) {
MultivaluedMap<String, String> query = request.getUriInfo().getQueryParameters();
return query.getFirst("cid");
}
}
However, as I said above, I really think you should make the bean #RequestScoped and force the clients to send the Authorization header with each request.

Stop Spring #Controller at runtime

I've found Can a spring boot #RestController be enabled/disabled using properties? which addresses not starting a #Controller at boot time, but I'm looking for a way to stop a #Controller at runtime.
I would actually used the #RefreshScope Bean and then when you want to stop the Rest Controller at runtime, you only need to change the property of said controller to false.
SO's link referencing to changing property at runtime.
Here are my snippets of working code:
#RefreshScope
#RestController
class MessageRestController(
#Value("\${message.get.enabled}") val getEnabled: Boolean,
#Value("\${message:Hello default}") val message: String
) {
#GetMapping("/message")
fun get(): String {
if (!getEnabled) {
throw NoHandlerFoundException("GET", "/message", null)
}
return message
}
}
And there are other alternatives of using Filter:
#Component
class EndpointsAvailabilityFilter #Autowired constructor(
private val env: Environment
): OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val requestURI = request.requestURI
val requestMethod = request.method
val property = "${requestURI.substring(1).replace("/", ".")}." +
"${requestMethod.toLowerCase()}.enabled"
val enabled = env.getProperty(property, "true")
if (!enabled.toBoolean()) {
throw NoHandlerFoundException(requestMethod, requestURI, ServletServerHttpRequest(request).headers)
}
filterChain.doFilter(request, response)
}
}
My Github explaining how to disable at runtime

Apache Camel Spring Javaconfig Unit Test No consumers available on endpoint

I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!

Resources