Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ reactor-test = "3.4.18"
selenium = "3.141.59"
serde = "1.0.1"
testcontainers = "1.17.2"
testcontainers-keycloak = "2.2.2"
unboundid-ldapsdk = "6.0.5"
velocity-engine-core = "2.3"
bouncycastle = "1.70"
Expand All @@ -30,6 +31,8 @@ selenium-firefox-driver = { module = "org.seleniumhq.selenium:selenium-firefox-d
selenium-remote-driver = { module = "org.seleniumhq.selenium:selenium-remote-driver", version.ref = "selenium" }
selenium-support = { module = "org.seleniumhq.selenium:selenium-support", version.ref = "selenium" }
testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers" }
testcontainers-spock = { module = "org.testcontainers:spock", version.ref = "testcontainers" }
testcontainers-keycloak = { module = "com.github.dasniko:testcontainers-keycloak", version.ref = "testcontainers-keycloak"}
testcontainers-selenium = { module = "org.testcontainers:selenium" }
unboundid-ldapsdk = { module = "com.unboundid:unboundid-ldapsdk", version.ref = "unboundid-ldapsdk" }
velocity-engine-core = { module = "org.apache.velocity:velocity-engine-core", version.ref = "velocity-engine-core" }
Expand Down
2 changes: 2 additions & 0 deletions security-oauth2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ dependencies {
testImplementation(mn.micronaut.http.server.netty)
testImplementation(libs.javax.activation) // Java 11
testImplementation(libs.testcontainers)
testImplementation(libs.testcontainers.keycloak)
testImplementation(libs.testcontainers.spock)
testImplementation(libs.groovy.json)
testImplementation project(":security-jwt")
testImplementation project(":test-suite-utils")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ public static class OpenIdClientConfigurationProperties implements OpenIdClientC
private static final String DEFAULT_CONFIG_PATH = "/.well-known/openid-configuration";
private final String name;

private String vendor;
private URL issuer;
private String configurationPath = DEFAULT_CONFIG_PATH;
private String jwksUri;
Expand All @@ -462,6 +463,20 @@ public String getName() {
return name;
}

@Override
public Optional<String> getVendor() {
return Optional.ofNullable(vendor);
}

/**
* A string that identifies the OpenId vendor.
*
* @param vendor The vendor
*/
public void setVendor(@Nullable String vendor) {
this.vendor = vendor;
}

@Override
public Optional<URL> getIssuer() {
return Optional.ofNullable(issuer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
*/
public interface OpenIdClientConfiguration extends Named {

/**
*
*
* @return a string that identifies the OpenId vendor.
*/
Optional<String> getVendor();

/**
* @return URL that the OpenID provider asserts as its issuer identifier.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ public enum AuthorizationServer {
OKTA,
COGNITO,
KEYCLOAK,
KEYCLOAK_17,
AUTH0;

private static final String ISSUER_PART_OKTA = "okta";
private static final String ISSUER_PART_COGNITO = "cognito";
private static final String ISSUER_PART_AUTH0 = "auth0";
private static final String ISSUER_PART_KEYCLOAK = "/auth/realms/";
private static final String ISSUER_PART_KEYCLOAK_17 = "/realms/";

/**
* @param issuer Issuer url
* @return An Authorization Server if it could be infered based on the contents of the issuer or empty if not
* @return An Authorization Server if it could be inferred based on the contents of the issuer or empty if not
*/
@NonNull
public static Optional<AuthorizationServer> infer(@NonNull String issuer) {
Expand All @@ -52,6 +54,9 @@ public static Optional<AuthorizationServer> infer(@NonNull String issuer) {
if (issuer.contains(ISSUER_PART_KEYCLOAK)) {
return Optional.of(AuthorizationServer.KEYCLOAK);
}
if (issuer.contains(ISSUER_PART_KEYCLOAK_17)) {
return Optional.of(AuthorizationServer.KEYCLOAK_17);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@ private Optional<EndSessionEndpoint> getEndSessionEndpoint(OauthClientConfigurat
}
switch (inferOptional.get()) {
case OKTA:
case KEYCLOAK_17:
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved the OktaEndSessionEndpoint for provider [{}]", providerName);
LOG.debug("Resolved the OpenIdEndSessionEndpoint for provider [{}]", providerName);
}
return oktaEndSessionEndpoint(oauthClientConfiguration, openIdProviderMetadata, endSessionCallbackUrlBuilder);
return openIdEndSessionEndpoint(oauthClientConfiguration, openIdProviderMetadata, endSessionCallbackUrlBuilder);
case COGNITO:
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved the AwsCognitoEndSessionEndpoint for provider [{}]", providerName);
Expand All @@ -154,12 +155,12 @@ private Optional<EndSessionEndpoint> getEndSessionEndpoint(OauthClientConfigurat
}

@NonNull
private Optional<EndSessionEndpoint> oktaEndSessionEndpoint(OauthClientConfiguration oauthClientConfiguration,
private Optional<EndSessionEndpoint> openIdEndSessionEndpoint(OauthClientConfiguration oauthClientConfiguration,
Supplier<OpenIdProviderMetadata> openIdProviderMetadata,
EndSessionCallbackUrlBuilder endSessionCallbackUrlBuilder) {
SecurityConfiguration securityConfiguration = beanContext.getBean(SecurityConfiguration.class);
TokenResolver tokenResolver = beanContext.getBean(TokenResolver.class);
return Optional.of(new OktaEndSessionEndpoint(endSessionCallbackUrlBuilder,
return Optional.of(new OpenIdEndSessionEndpoint(endSessionCallbackUrlBuilder,
oauthClientConfiguration,
openIdProviderMetadata,
securityConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.micronaut.security.oauth2.client.OpenIdProviderMetadata;
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration;
import io.micronaut.security.oauth2.endpoint.endsession.response.EndSessionCallbackUrlBuilder;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
Expand All @@ -44,7 +45,8 @@ public class KeycloakEndSessionEndpoint extends AbstractEndSessionRequest {
*/
public KeycloakEndSessionEndpoint(EndSessionCallbackUrlBuilder endSessionCallbackUrlBuilder,
OauthClientConfiguration clientConfiguration,
Supplier<OpenIdProviderMetadata> providerMetadata) {
Supplier<OpenIdProviderMetadata> providerMetadata
) {
super(endSessionCallbackUrlBuilder, clientConfiguration, providerMetadata);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@
import java.util.function.Supplier;

/**
* Provides specific configuration to logout from Okta.
* Provides specific configuration to logout from vendors supporting OpenID Connect RP-Initiated Logout .
*
* @see <a href="https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/java/logout.adoc">Keycloak Logout Endpoint</a>
* @see <a href="https://developer.okta.com/docs/api/resources/oidc/#logout">Okta Logout Endpont</a>
*
* @author Sergio del Amo
* @author Sergio del Amo, Dean Wette
* @since 1.2.0
*/
public class OktaEndSessionEndpoint extends AbstractEndSessionRequest {
public class OpenIdEndSessionEndpoint extends AbstractEndSessionRequest {

private static final String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri";
private static final String PARAM_ID_TOKEN_HINT = "id_token_hint";
Expand All @@ -52,11 +53,11 @@ public class OktaEndSessionEndpoint extends AbstractEndSessionRequest {
* @param securityConfiguration Security configuration
* @param tokenResolver Token Resolver
*/
public OktaEndSessionEndpoint(EndSessionCallbackUrlBuilder endSessionCallbackUrlBuilder,
OauthClientConfiguration clientConfiguration,
Supplier<OpenIdProviderMetadata> providerMetadata,
SecurityConfiguration securityConfiguration,
TokenResolver tokenResolver) {
public OpenIdEndSessionEndpoint(EndSessionCallbackUrlBuilder endSessionCallbackUrlBuilder,
OauthClientConfiguration clientConfiguration,
Supplier<OpenIdProviderMetadata> providerMetadata,
SecurityConfiguration securityConfiguration,
TokenResolver tokenResolver) {
super(endSessionCallbackUrlBuilder, clientConfiguration, providerMetadata);
this.securityConfiguration = securityConfiguration;
this.tokenResolver = tokenResolver;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import io.micronaut.security.token.validator.TokenValidator
import jakarta.inject.Named
import jakarta.inject.Singleton
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

import java.security.Principal

@IgnoreIf(value = { Jvm.getCurrent().isJava8() }, reason = "testcontainers-keycloak requires Java 11+")
class AuthenticationModeIdTokenSpec extends GebEmbeddedServerSpecification {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import io.micronaut.security.testutils.Keycloak
import jakarta.inject.Named
import jakarta.inject.Singleton
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

import java.security.Principal

@IgnoreIf(value = {Jvm.getCurrent().isJava8()}, reason = "testcontainers-keycloak requires Java 11+")
class OpenIdAuthorizationCodeSpec extends GebEmbeddedServerSpecification {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import reactor.core.publisher.FluxSink
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

import java.nio.charset.StandardCharsets

@IgnoreIf(value = { Jvm.getCurrent().isJava8() }, reason = "testcontainers-keycloak requires Java 11+")
class OpenIdAuthorizationRedirectOauthDisabledSpec extends EmbeddedServerSpecification {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import reactor.core.publisher.FluxSink
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

import java.nio.charset.StandardCharsets

@IgnoreIf(value = { Jvm.getCurrent().isJava8() }, reason = "testcontainers-keycloak requires Java 11+")
class OpenIdAuthorizationRedirectSpec extends EmbeddedServerSpecification {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import io.micronaut.security.oauth2.routes.OauthController
import io.micronaut.security.testutils.EmbeddedServerSpecification
import io.micronaut.security.testutils.Keycloak
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

import java.nio.charset.StandardCharsets

@IgnoreIf(value = { Jvm.getCurrent().isJava8() }, reason = "testcontainers-keycloak requires Java 11+")
class OpenIdAuthorizationRedirectWithJustOpenIdSpec extends EmbeddedServerSpecification {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class AuthorizationServerSpec extends Specification {
where:
issuer || authorizationServer
"http://localhost:8180/auth/realms/master" || AuthorizationServer.KEYCLOAK
"http://localhost:8180/realms/master" || AuthorizationServer.KEYCLOAK_17
"https://dev-XXXXX.oktapreview.com/oauth2/default" || AuthorizationServer.OKTA
"https://cognito-idp.us-east-1.amazonaws.com/12345}/" || AuthorizationServer.COGNITO
"https://micronautguides.eu.auth0.com" || AuthorizationServer.AUTH0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class EndSessionEndpointResolverSpec extends ApplicationContextSpecification {
Map<String, Object> getConfiguration() {
super.configuration + [
'micronaut.security.authentication': 'idtoken',
"micronaut.security.oauth2.clients.okta.openid.vendor" : "okta"
]
}

Expand Down Expand Up @@ -55,7 +56,7 @@ class EndSessionEndpointResolverSpec extends ApplicationContextSpecification {
EndSessionEndpoint endSessionEndpoint = endSessionEndpointOptional.get()

then:
endSessionEndpoint instanceof OktaEndSessionEndpoint
endSessionEndpoint instanceof OpenIdEndSessionEndpoint

when:
def cookies = Stub(Cookies) {
Expand Down Expand Up @@ -113,6 +114,11 @@ class EndSessionEndpointResolverSpec extends ApplicationContextSpecification {
@AutoImplement
static class CustomOpenIdClientConfiguration implements OpenIdClientConfiguration {

@Override
Optional<String> getVendor() {
return Optional.of("okta")
}

@Override
Optional<URL> getIssuer() {
return Optional.of(new URL("https://dev-33333.oktapreview.com/oauth2/default"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import io.micronaut.security.authentication.Authentication
import io.micronaut.security.oauth2.client.DefaultOpenIdClient
import io.micronaut.security.testutils.EmbeddedServerSpecification
import io.micronaut.security.testutils.Keycloak
import spock.lang.IgnoreIf
import spock.util.environment.Jvm

@IgnoreIf(value = { Jvm.getCurrent().isJava8() }, reason = "testcontainers-keycloak requires Java 11+")
class KeycloakEndSessionEndpointSpec extends EmbeddedServerSpecification {

@Override
Expand All @@ -24,9 +27,10 @@ class KeycloakEndSessionEndpointSpec extends EmbeddedServerSpecification {
"micronaut.security.endpoints.logout.get-allowed": true,
] as Map<String, Object>
if ((System.getProperty(Keycloak.SYS_TESTCONTAINERS) == null) || Boolean.valueOf(System.getProperty(Keycloak.SYS_TESTCONTAINERS))) {
m.putAll([ "micronaut.security.oauth2.clients.keycloak.openid.issuer" : Keycloak.issuer,
m.putAll([ "micronaut.security.oauth2.clients.keycloak.openid.vendor" : Keycloak.VENDOR,
"micronaut.security.oauth2.clients.keycloak.openid.issuer" : Keycloak.issuer,
"micronaut.security.oauth2.clients.keycloak.client-id" : Keycloak.CLIENT_ID,
"micronaut.security.oauth2.clients.keycloak.client-secret" : Keycloak.clientSecret,
"micronaut.security.oauth2.clients.keycloak.client-secret" : Keycloak.clientSecret
] as Map<String, Object>)
}
m
Expand All @@ -38,22 +42,23 @@ class KeycloakEndSessionEndpointSpec extends EmbeddedServerSpecification {

HttpRequest<?> request = new SimpleHttpRequest<>(HttpMethod.GET,
"http://localhost:" + embeddedServer.port + "/oauth/logout", null)
def defaultOpenIdClient = applicationContext.getBean(DefaultOpenIdClient, Qualifiers.byName(name))

expect:
applicationContext.containsBean(DefaultOpenIdClient, Qualifiers.byName(name))
applicationContext.getBean(DefaultOpenIdClient, Qualifiers.byName(name)).supportsEndSession()
applicationContext.getBean(DefaultOpenIdClient, Qualifiers.byName(name))
.endSessionRedirect(request, Authentication.build("sherlock")).isPresent()
defaultOpenIdClient
defaultOpenIdClient.supportsEndSession()

defaultOpenIdClient.endSessionRedirect(request, Authentication.build("sherlock")).isPresent()

when:
String redirect = applicationContext.getBean(DefaultOpenIdClient, Qualifiers.byName(name))
String redirect = defaultOpenIdClient
.endSessionRedirect(request, Authentication.build("sherlock"))
.get()
.getHeaders()
.get(HttpHeaders.LOCATION)
.toString()

then:
"http://localhost:" + Keycloak.port + "/auth/realms/master/protocol/openid-connect/logout?redirect_uri=http%3A%2F%2Flocalhost%3A" + embeddedServer.port + "%2Flogout" == redirect
redirect == "http://localhost:" + Keycloak.port + "/realms/master/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A" + embeddedServer.port + "%2Flogout"
}
}
1 change: 1 addition & 0 deletions test-suite-utils/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
implementation(mn.micronaut.http.server.netty)
implementation(mn.micronaut.http.client)
implementation(libs.testcontainers.selenium)
implementation(libs.testcontainers.keycloak)
implementation(libs.selenium.remote.driver)
implementation(libs.selenium.api)
implementation(libs.selenium.support)
Expand Down
Loading