From cfc5fb27585c8d80dc70adc13d146be0f5377a1d Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sat, 21 Oct 2023 13:05:16 +0100 Subject: [PATCH] Add a custom OIDC identity provider test --- .../oidc/test/CustomIdentityProvider.java | 58 ++++++++++++ .../test/CustomIdentityProviderTestCase.java | 88 +++++++++++++++++++ ...lication-introspection-disabled.properties | 7 ++ 3 files changed, 153 insertions(+) create mode 100644 extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProvider.java create mode 100644 extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProviderTestCase.java create mode 100644 extensions/oidc/deployment/src/test/resources/application-introspection-disabled.properties diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProvider.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProvider.java new file mode 100644 index 0000000000000..f876ad8390d72 --- /dev/null +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProvider.java @@ -0,0 +1,58 @@ +package io.quarkus.oidc.test; + +import java.security.Principal; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.jwt.Claims; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; + +import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal; +import io.quarkus.security.AuthenticationCompletionException; +import io.quarkus.security.AuthenticationFailedException; +import io.quarkus.security.credential.TokenCredential; +import io.quarkus.security.identity.AuthenticationRequestContext; +import io.quarkus.security.identity.IdentityProvider; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.TokenAuthenticationRequest; +import io.quarkus.security.runtime.QuarkusSecurityIdentity; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +@Priority(1) +public class CustomIdentityProvider implements IdentityProvider { + + @Override + public Class getRequestType() { + return TokenAuthenticationRequest.class; + } + + @Override + public Uni authenticate(TokenAuthenticationRequest request, AuthenticationRequestContext context) { + QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(); + + TokenCredential credential = request.getToken(); + try { + JwtClaims jwtClaims = new JwtConsumerBuilder() + .setSkipSignatureVerification() + .setSkipAllValidators() + .build().processToClaims(credential.getToken()); + jwtClaims.setClaim(Claims.raw_token.name(), credential.getToken()); + + Principal principal = new OidcJwtCallerPrincipal(jwtClaims, credential); + if ("jdoe".equals(principal.getName())) { + throw new AuthenticationCompletionException(); + } + builder.setPrincipal(principal); + } catch (InvalidJwtException e) { + throw new AuthenticationFailedException(e); + } + + return Uni.createFrom().item(builder.build()); + + } + +} diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProviderTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProviderTestCase.java new file mode 100644 index 0000000000000..7ad82b090c541 --- /dev/null +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomIdentityProviderTestCase.java @@ -0,0 +1,88 @@ +package io.quarkus.oidc.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; + +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class CustomIdentityProviderTestCase { + + private static Class[] testClasses = { + ProtectedResource.class, + CustomIdentityProvider.class + }; + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(testClasses) + .addAsResource("application-introspection-disabled.properties", "application.properties")); + + @Test + public void testCustomIdentityProviderSuccess() throws IOException, InterruptedException { + + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8080/protected"); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + page = loginForm.getInputByName("login").click(); + + assertEquals("alice", page.getBody().asNormalizedText()); + + webClient.getCookieManager().clearCookies(); + } + + } + + @Test + public void testCustomIdentityProviderFailure() throws IOException, InterruptedException { + + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8080/protected"); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("jdoe"); + loginForm.getInputByName("password").setValueAttribute("jdoe"); + + try { + loginForm.getInputByName("login").click(); + fail("Exception is expected because `jdoe` is not allowed to access the service"); + } catch (FailingHttpStatusCodeException ex) { + assertEquals(401, ex.getStatusCode()); + } + + webClient.getCookieManager().clearCookies(); + } + + } + + private WebClient createWebClient() { + WebClient webClient = new WebClient(); + webClient.setCssErrorHandler(new SilentCssErrorHandler()); + return webClient; + } + +} diff --git a/extensions/oidc/deployment/src/test/resources/application-introspection-disabled.properties b/extensions/oidc/deployment/src/test/resources/application-introspection-disabled.properties new file mode 100644 index 0000000000000..dc153de2534fd --- /dev/null +++ b/extensions/oidc/deployment/src/test/resources/application-introspection-disabled.properties @@ -0,0 +1,7 @@ +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus +quarkus.oidc.application-type=web-app +quarkus.oidc.client-id=quarkus-web-app +quarkus.oidc.credentials.secret=secret + +#It blocks an attempt to fetch JWK set at the start up +quarkus.oidc.token.require-jwt-introspection-only=true \ No newline at end of file