Skip to content

Commit

Permalink
Merge pull request quarkusio#27921 from michalvavrik/feature/custom-a…
Browse files Browse the repository at this point in the history
…uth-mechanism-should-challenge

Adjust 'challenge' selection so that custom auth mechanism is called
  • Loading branch information
sberyozkin authored Sep 14, 2022
2 parents c19be6d + 48d0c87 commit 4a2a5cb
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 12 deletions.
18 changes: 12 additions & 6 deletions docs/src/main/asciidoc/security-customization.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ In some cases such a default logic of selecting the challenge is exactly what is

[source,java]
----
@Alternative <1>
@Priority(1)
@ApplicationScoped
public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism {
Expand All @@ -102,18 +104,21 @@ public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism
@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return selectBetweenJwtAndOidcChallenge(context).getChallenge(context);
return selectBetweenJwtAndOidcChallenge(context).getChallenge(context);
}
@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return selectBetweenJwtAndOidc(context).getCredentialTypes();
Set<Class<? extends AuthenticationRequest>> credentialTypes = new HashSet<>();
credentialTypes.addAll(jwt.getCredentialTypes());
credentialTypes.addAll(oidc.getCredentialTypes());
return credentialTypes;
}
@Override
public HttpCredentialTransport getCredentialTransport(RoutingContext context) {
return selectBetweenJwtAndOidc(context).getCredentialTransport();
}
@Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return selectBetweenJwtAndOidc(context).getCredentialTransport(context);
}
private HttpAuthenticationMechanism selectBetweenJwtAndOidc(RoutingContext context) {
....
Expand All @@ -125,6 +130,7 @@ public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism
}
----
<1> Declaring the mechanism an alternative bean ensures this mechanism is used rather than `OidcAuthenticationMechanism` and `JWTAuthMechanism`.

[[security-identity-customization]]
== Security Identity Customization
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.quarkus.vertx.http.security;

import java.util.Set;
import java.util.function.Supplier;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import javax.inject.Inject;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.Priority;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.test.utils.TestIdentityController;
import io.quarkus.security.test.utils.TestIdentityProvider;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
import io.restassured.RestAssured;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class CustomFormAuthChallengeTest {

private static final int EXPECTED_STATUS = 203;
private static final String EXPECTED_HEADER_NAME = "ElizabethII";
private static final String EXPECTED_HEADER_VALUE = "CharlesIV";
private static final String ADMIN = "admin";
private static final String APP_PROPS = "" +
"quarkus.http.auth.form.enabled=true\n" +
"quarkus.http.auth.form.login-page=login\n" +
"quarkus.http.auth.form.error-page=error\n" +
"quarkus.http.auth.form.landing-page=landing\n" +
"quarkus.http.auth.policy.r1.roles-allowed=admin\n" +
"quarkus.http.auth.session.encryption-key=CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT\n";

@RegisterExtension
static QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(CustomFormAuthenticator.class, TestIdentityProvider.class, TestIdentityController.class,
TestTrustedIdentityProvider.class, PathHandler.class)
.addAsResource(new StringAsset(APP_PROPS), "application.properties");
}
});

@BeforeAll
public static void setup() {
TestIdentityController.resetRoles().add(ADMIN, ADMIN, ADMIN);
}

@Test
public void testCustomGetChallengeIsCalled() {
RestAssured
.given()
.when()
.formParam("j_username", ADMIN)
.formParam("j_password", "wrong_password")
.post("/j_security_check")
.then()
.assertThat()
.statusCode(EXPECTED_STATUS)
.header(EXPECTED_HEADER_NAME, Matchers.is(EXPECTED_HEADER_VALUE));
}

@Alternative
@Priority(1)
@ApplicationScoped
public static class CustomFormAuthenticator implements HttpAuthenticationMechanism {

@Inject
FormAuthenticationMechanism delegate;

@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
final var authenticate = delegate.authenticate(context, identityProviderManager);
context.put(HttpAuthenticationMechanism.class.getName(), this);
return authenticate;
}

@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return Uni.createFrom().item(new ChallengeData(EXPECTED_STATUS, EXPECTED_HEADER_NAME, EXPECTED_HEADER_VALUE));
}

@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return delegate.getCredentialTypes();
}

@Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return delegate.getCredentialTransport(context);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,14 @@ public Uni<Boolean> sendChallenge(RoutingContext routingContext) {
routingContext.request().resume();
Uni<Boolean> result = null;

HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName());
if (matchingMech != null) {
result = matchingMech.sendChallenge(routingContext);
// we only require auth mechanism to put itself into routing context when there is more than one mechanism registered
if (mechanisms.length > 1) {
HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName());
if (matchingMech != null) {
result = matchingMech.sendChallenge(routingContext);
}
}

if (result == null) {
result = mechanisms[0].sendChallenge(routingContext);
for (int i = 1; i < mechanisms.length; ++i) {
Expand Down Expand Up @@ -169,9 +173,12 @@ public Uni<? extends Boolean> apply(Boolean authDone) {
}

public Uni<ChallengeData> getChallenge(RoutingContext routingContext) {
HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName());
if (matchingMech != null) {
return matchingMech.getChallenge(routingContext);
// we only require auth mechanism to put itself into routing context when there is more than one mechanism registered
if (mechanisms.length > 1) {
HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName());
if (matchingMech != null) {
return matchingMech.getChallenge(routingContext);
}
}
Uni<ChallengeData> result = mechanisms[0].getChallenge(routingContext);
for (int i = 1; i < mechanisms.length; ++i) {
Expand Down

0 comments on commit 4a2a5cb

Please sign in to comment.