Skip to content

Commit

Permalink
Merge pull request #1462 from michalvavrik/backports/undertow-req-sco…
Browse files Browse the repository at this point in the history
…pe-activation-during-auth

[3.2] Verify Undertow allows activation of req scope during auth
  • Loading branch information
mjurc authored Oct 4, 2023
2 parents 9931b25 + e81933d commit 9ee5a8c
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 2 deletions.
4 changes: 4 additions & 0 deletions http/servlet-undertow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.ts.http.undertow.security;

import java.util.Set;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.control.ActivateRequestContext;

import io.quarkus.arc.Arc;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class ServletBasicAuthIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest> {
@Override
public Class<UsernamePasswordAuthenticationRequest> getRequestType() {
return UsernamePasswordAuthenticationRequest.class;
}

@Override
public Uni<SecurityIdentity> authenticate(UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationRequest,
AuthenticationRequestContext authenticationRequestContext) {
return authenticationRequestContext.runBlocking(() -> withIdentity(usernamePasswordAuthenticationRequest));
}

@ActivateRequestContext
SecurityIdentity withIdentity(UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationRequest) {
final SecurityIdentity identity;
var username = usernamePasswordAuthenticationRequest.getUsername();
var isPablo = "Pablo".equals(username);
if (isPablo || "Rocky".equals(username)) {

if (!Arc.container().requestContext().isActive()) {
throw new IllegalStateException("The request scope should be active");
}

final Set<String> roles;
if (isPablo) {
roles = Set.of("granados");
} else {
// unauthorized
roles = Set.of();
}
identity = QuarkusSecurityIdentity
.builder()
.setPrincipal(new QuarkusPrincipal(username))
.addRoles(roles)
.build();
} else {
// unauthenticated
identity = null;
}
return identity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.ts.http.undertow.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HttpConstraint;
import jakarta.servlet.annotation.ServletSecurity;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.jboss.logging.Logger;

import io.vertx.ext.web.RoutingContext;

@ServletSecurity(@HttpConstraint(rolesAllowed = { "pablo", "gonzalez", "granados" }))
@WebServlet(name = "SecuredWorldServlet", urlPatterns = "/secured", initParams = {
@WebInitParam(name = "message", value = "A secured message") })
@ApplicationScoped
public class SecuredWorld extends HttpServlet {

private static final Logger LOG = Logger.getLogger(SecuredWorld.class);

@Inject
RoutingContext routingContext;

@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
LOG.info(req.getSession().getId());
PrintWriter writer = resp.getWriter();
writer.write(routingContext.queryParam("secured-servlet-key").get(0));
writer.close();
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
quarkus.http.root-path=/app
quarkus.servlet.context-path=/servlet

quarkus.micrometer.export.json.enabled=true
quarkus.micrometer.export.json.enabled=true

quarkus.http.auth.basic=true
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@

import org.apache.http.HttpStatus;
import org.apache.http.util.Asserts;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

import io.quarkus.test.scenarios.QuarkusScenario;
import io.restassured.RestAssured;
import io.restassured.response.ValidatableResponse;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@QuarkusScenario
public class HttpServletWithSessionListenerIT {

static final Duration ACTIVE_SESSION_TIMEOUT = Duration.ofMinutes(2);
static final Duration REST_ASSURANCE_POLL_INTERVAL = Duration.ofSeconds(1);

@Order(1)
@Test
public void sessionEviction() {
int activeSessions = 20;
Expand All @@ -28,11 +36,39 @@ public void sessionEviction() {
thenWaitToEvictSessionsAndCheckActiveSessionsEqualTo(0);
}

@Tag("QUARKUS-2819")
@Order(2)
@Test
public void sessionSecured() {
// main objection is to test that CDI request scope can be activated during auth without
// having issue to use request scope later during processing
// session secured portion of this test is in order to stick to a test class theme
thenMakeSecuredWorldQuery("Rambo", 401);
thenCheckActiveSessionsEqualTo(0);
thenMakeSecuredWorldQuery("Rocky", 403);
thenCheckActiveSessionsEqualTo(0);
thenMakeSecuredWorldQuery("Pablo", 200).body(Matchers.is("secured-servlet-value"));
thenCheckActiveSessionsEqualTo(1);
}

private double getActiveSessions() {
return (Double) RestAssured.given().when()
var activeSessions = (Double) RestAssured.given().when()
.get("/app/q/metrics")
.then()
.statusCode(HttpStatus.SC_OK).extract().as(Map.class).get(GAUGE_ACTIVE_SESSION);
if (activeSessions == null) {
return 0;
}
return activeSessions;
}

private ValidatableResponse thenMakeSecuredWorldQuery(String user, int httpStatus) {
return RestAssured.given().when()
.auth().basic(user, user)
.queryParam("secured-servlet-key", "secured-servlet-value")
.get("/app/servlet/secured")
.then()
.statusCode(httpStatus);
}

private void thenMakeHelloWorldQuery(int requestAmount) {
Expand Down

0 comments on commit 9ee5a8c

Please sign in to comment.