This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit c55524077d1663528847f657140e242d9f8f8549 Author: Robert Munteanu <[email protected]> AuthorDate: Wed Sep 18 23:26:32 2024 +0200 oidc-rp: split up OidcClient into separate classes Clumping those concerns together did not make sense and the name of the interface gave the impression that this is an entry point to the API - which it was not. --- .../extensions/oidc_rp/OAuthTokenRefresher.java | 32 +++++++++ .../apache/sling/extensions/oidc_rp/OAuthUris.java | 58 ++++++++++++++++ .../sling/extensions/oidc_rp/OidcClient.java | 70 ------------------- ...lientImpl.java => OAuthTokenRefresherImpl.java} | 77 ++------------------ .../oidc_rp/impl/OidcEntryPointServlet.java | 60 ++++++++++++++-- .../oidc_rp/impl/OidcProviderMetadataRegistry.java | 12 +++- .../oidc_rp/support/OAuthEnabledSlingServlet.java | 11 +-- .../sling/servlets/oidc_rp/impl/OAuthUrisTest.java | 59 ++++++++++++++++ ...mplTest.java => OidcEntryPointServletTest.java} | 81 ++++++++++------------ 9 files changed, 262 insertions(+), 198 deletions(-) diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthTokenRefresher.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthTokenRefresher.java new file mode 100644 index 00000000..3b00215f --- /dev/null +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthTokenRefresher.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.extensions.oidc_rp; + +public interface OAuthTokenRefresher { + + /** + * Refreshes the OIDC tokens based on the supplied refresh token + * + * <p>It is the responsibility of the invoker to persist the returned tokens.</p> + * + * @param connection The connection to start the OIDC flow for + * @param refreshToken An existing refresh token + * @return OIDC tokens + * @throws OidcException in case anything goes wrong + */ + OidcTokens refreshTokens(OidcConnection connection, String refreshToken) throws OidcException; +} diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthUris.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthUris.java new file mode 100644 index 00000000..e3b7fbf4 --- /dev/null +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OAuthUris.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.extensions.oidc_rp; + +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.extensions.oidc_rp.impl.OidcEntryPointServlet; + +public abstract class OAuthUris { + + /** + * Generates a local URI to the OIDC entry point servlet + * + * <p>The URI can be used as-is to send a redirect to the user and start the OIDC flow.</p> + * + * @param connection The connection to start the OIDC flow for + * @param request The current request + * @param redirectPath The local redirect path to use after completing the OIDC flow + * @return a local URI + * @throws OidcException in case anything goes wrong + */ + public static URI getOidcEntryPointUri(OidcConnection connection, SlingHttpServletRequest request, String redirectPath) throws OidcException { + StringBuilder uri = new StringBuilder(); + uri.append(request.getScheme()).append("://").append(request.getServerName()); + boolean needsExplicitPort = ( "https".equals(request.getScheme()) && request.getServerPort() != 443 ) + || ( "http".equals(request.getScheme()) && request.getServerPort() != 80 ) ; + + if ( needsExplicitPort ) { + uri.append(':').append(request.getServerPort()); + } + uri.append(OidcEntryPointServlet.PATH).append("?c=").append(connection.name()); + if ( redirectPath != null ) + uri.append("&redirect=").append(URLEncoder.encode(redirectPath, StandardCharsets.UTF_8)); + + return URI.create(uri.toString()); + } + + private OAuthUris() { + // prevent instantiation + } +} diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OidcClient.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OidcClient.java deleted file mode 100644 index 12cbd33d..00000000 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/OidcClient.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sling.extensions.oidc_rp; - -import java.net.URI; - -import org.apache.sling.api.SlingHttpServletRequest; - -/** - * A client for dealing with over-the-network OIDC concerns - * - * <p>This client is able to generate URLs and make network calls related to OIDC.</p> - * - */ -public interface OidcClient { - - /** - * Generates a local URI to the OIDC entry point servlet - * - * <p>The URI can be used as-is to send a redirect to the user and start the OIDC flow.</p> - * - * @param connection The connection to start the OIDC flow for - * @param request The current request - * @param redirectPath The local redirect path to use after completing the OIDC flow - * @return a local URI - * @throws OidcException in case anything goes wrong - */ - URI getOidcEntryPointUri(OidcConnection connection, SlingHttpServletRequest request, String redirectPath) throws OidcException; - - /** - * Generates a URI to the OIDC provider's authorization endpoint - * - * <p>The URI can be used as-is to start the OIDC flow directly on the identity provider's side.</p> - * - * @param connection The connection to start the OIDC flow for - * @param request The current request - * @param redirectUri The redirect path to use after completing the OIDC flow - * @return a remote URI - * @throws OidcException in case anything goes wrong - */ - URI getAuthenticationRequestUri(OidcConnection connection, SlingHttpServletRequest request, URI redirectUri) throws OidcException; - - // OidcTokens getOidcTokens(OidcConnection connection, String authenticationCode) throws OidcException; - - /** - * Refreshes the OIDC tokens based on the supplied refresh token - * - * <p>It is the responsibility of the invoker to persist the returned tokens.</p> - * - * @param connection The connection to start the OIDC flow for - * @param refreshToken An existing refresh token - * @return OIDC tokens - * @throws OidcException in case anything goes wrong - */ - OidcTokens refreshTokens(OidcConnection connection, String refreshToken) throws OidcException; -} diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcClientImpl.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OAuthTokenRefresherImpl.java similarity index 51% rename from org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcClientImpl.java rename to org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OAuthTokenRefresherImpl.java index ee8cc394..47b25bbf 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcClientImpl.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OAuthTokenRefresherImpl.java @@ -16,17 +16,10 @@ */ package org.apache.sling.extensions.oidc_rp.impl; -import static org.apache.sling.extensions.oidc_rp.impl.OidcStateManager.PARAMETER_NAME_CONNECTION; -import static org.apache.sling.extensions.oidc_rp.impl.OidcStateManager.PARAMETER_NAME_REDIRECT; - import java.io.IOException; import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.extensions.oidc_rp.OidcClient; +import org.apache.sling.extensions.oidc_rp.OAuthTokenRefresher; import org.apache.sling.extensions.oidc_rp.OidcConnection; import org.apache.sling.extensions.oidc_rp.OidcException; import org.apache.sling.extensions.oidc_rp.OidcTokens; @@ -37,29 +30,23 @@ import org.osgi.service.component.annotations.Reference; import com.nimbusds.oauth2.sdk.AuthorizationGrant; import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.RefreshTokenGrant; -import com.nimbusds.oauth2.sdk.ResponseType; -import com.nimbusds.oauth2.sdk.Scope; import com.nimbusds.oauth2.sdk.TokenErrorResponse; import com.nimbusds.oauth2.sdk.TokenRequest; import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; import com.nimbusds.oauth2.sdk.auth.Secret; import com.nimbusds.oauth2.sdk.id.ClientID; -import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.oauth2.sdk.token.RefreshToken; -import com.nimbusds.openid.connect.sdk.AuthenticationRequest; -import com.nimbusds.openid.connect.sdk.Nonce; import com.nimbusds.openid.connect.sdk.OIDCTokenResponse; -import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import com.nimbusds.openid.connect.sdk.token.OIDCTokens; -@Component(service = { OidcClientImpl.class, OidcClient.class }) -public class OidcClientImpl implements OidcClient { +@Component +public class OAuthTokenRefresherImpl implements OAuthTokenRefresher { private final OidcProviderMetadataRegistry providerMetadataRegistry; @Activate - public OidcClientImpl(@Reference OidcProviderMetadataRegistry providerMetadataRegistry) { + public OAuthTokenRefresherImpl(@Reference OidcProviderMetadataRegistry providerMetadataRegistry) { this.providerMetadataRegistry = providerMetadataRegistry; } @@ -102,60 +89,4 @@ public class OidcClientImpl implements OidcClient { throw new OidcException(e); } } - - @Override - public URI getOidcEntryPointUri(OidcConnection connection, SlingHttpServletRequest request, String redirectPath) { - - StringBuilder uri = new StringBuilder(); - uri.append(request.getScheme()).append("://").append(request.getServerName()); - boolean needsExplicitPort = ( "https".equals(request.getScheme()) && request.getServerPort() != 443 ) - || ( "http".equals(request.getScheme()) && request.getServerPort() != 80 ) ; - - if ( needsExplicitPort ) { - uri.append(':').append(request.getServerPort()); - } - uri.append(OidcEntryPointServlet.PATH).append("?c=").append(connection.name()); - if ( redirectPath != null ) - uri.append("&redirect=").append(URLEncoder.encode(redirectPath, StandardCharsets.UTF_8)); - - return URI.create(uri.toString()); - } - - @Override - public URI getAuthenticationRequestUri(OidcConnection connection, SlingHttpServletRequest request, URI redirectUri) { - OIDCProviderMetadata providerMetadata = providerMetadataRegistry.getProviderMetadata(connection.baseUrl()); - - // The client ID provisioned by the OpenID provider when - // the client was registered - ClientID clientID = new ClientID(connection.clientId()); - - // Generate random state string to securely pair the callback to this request - State state = new State(); - OidcStateManager stateManager = OidcStateManager.stateFor(request); - stateManager.registerState(state); - stateManager.putAttribute(state, PARAMETER_NAME_CONNECTION, connection.name()); - if ( request.getParameter(PARAMETER_NAME_REDIRECT) != null ) - stateManager.putAttribute(state, PARAMETER_NAME_REDIRECT, request.getParameter(PARAMETER_NAME_REDIRECT)); - - // Generate nonce for the ID token - Nonce nonce = new Nonce(); - - // Compose the OpenID authentication request (for the code flow) - AuthenticationRequest.Builder authRequestBuilder = new AuthenticationRequest.Builder( - new ResponseType("code"), - new Scope(connection.scopes()), - clientID, URI.create(OidcCallbackServlet.getCallbackUri(request))) - .endpointURI(providerMetadata.getAuthorizationEndpointURI()) - .state(state) - .nonce(nonce); - - if ( connection.additionalAuthorizationParameters() != null ) { - Arrays.stream(connection.additionalAuthorizationParameters()) - .map( s -> s.split("=") ) - .filter( p -> p.length == 2 ) - .forEach( p -> authRequestBuilder.customParameter(p[0], p[1])); - } - - return authRequestBuilder.build().toURI(); - } } diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcEntryPointServlet.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcEntryPointServlet.java index 2daa0698..cdf856c0 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcEntryPointServlet.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcEntryPointServlet.java @@ -16,10 +16,13 @@ */ package org.apache.sling.extensions.oidc_rp.impl; +import static org.apache.sling.extensions.oidc_rp.impl.OidcStateManager.PARAMETER_NAME_CONNECTION; +import static org.apache.sling.extensions.oidc_rp.impl.OidcStateManager.PARAMETER_NAME_REDIRECT; import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -33,7 +36,6 @@ import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.auth.core.AuthConstants; -import org.apache.sling.extensions.oidc_rp.OidcClient; import org.apache.sling.extensions.oidc_rp.OidcConnection; import org.apache.sling.servlets.annotations.SlingServletPaths; import org.osgi.service.component.annotations.Activate; @@ -42,6 +44,14 @@ import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.nimbusds.oauth2.sdk.ResponseType; +import com.nimbusds.oauth2.sdk.Scope; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.State; +import com.nimbusds.openid.connect.sdk.AuthenticationRequest; +import com.nimbusds.openid.connect.sdk.Nonce; +import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; + @Component(service = { Servlet.class }, property = { AuthConstants.AUTH_REQUIREMENTS +"=" + OidcEntryPointServlet.PATH } ) @@ -49,21 +59,22 @@ import org.slf4j.LoggerFactory; public class OidcEntryPointServlet extends SlingAllMethodsServlet { private static final long serialVersionUID = 1L; - static final String PATH = "/system/sling/oidc/entry-point"; // NOSONAR + public static final String PATH = "/system/sling/oidc/entry-point"; // NOSONAR private final Logger logger = LoggerFactory.getLogger(getClass()); private final Map<String, OidcConnection> connections; - private final OidcClient oidcClient; + private final OidcProviderMetadataRegistry oidcRegistry; @Activate public OidcEntryPointServlet(@Reference(policyOption = GREEDY) List<OidcConnection> connections, - @Reference OidcClient oidcClient) { + @Reference OidcProviderMetadataRegistry oidcRegistry) { this.connections = connections.stream() .collect(Collectors.toMap( OidcConnection::name, Function.identity())); - this.oidcClient = oidcClient; + this.oidcRegistry = oidcRegistry; } + @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { @@ -85,6 +96,43 @@ public class OidcEntryPointServlet extends SlingAllMethodsServlet { if ( connection.baseUrl() == null ) throw new ServletException("Misconfigured baseUrl"); - response.sendRedirect(oidcClient.getAuthenticationRequestUri(connection, request, URI.create(OidcCallbackServlet.getCallbackUri(request))).toString()); + response.sendRedirect(getAuthenticationRequestUri(connection, request, URI.create(OidcCallbackServlet.getCallbackUri(request))).toString()); + } + + private URI getAuthenticationRequestUri(OidcConnection connection, SlingHttpServletRequest request, URI redirectUri) { + OIDCProviderMetadata providerMetadata = oidcRegistry.getProviderMetadata(connection.baseUrl()); + + // The client ID provisioned by the OpenID provider when + // the client was registered + ClientID clientID = new ClientID(connection.clientId()); + + // Generate random state string to securely pair the callback to this request + State state = new State(); + OidcStateManager stateManager = OidcStateManager.stateFor(request); + stateManager.registerState(state); + stateManager.putAttribute(state, PARAMETER_NAME_CONNECTION, connection.name()); + if ( request.getParameter(PARAMETER_NAME_REDIRECT) != null ) + stateManager.putAttribute(state, PARAMETER_NAME_REDIRECT, request.getParameter(PARAMETER_NAME_REDIRECT)); + + // Generate nonce for the ID token + Nonce nonce = new Nonce(); + + // Compose the OpenID authentication request (for the code flow) + AuthenticationRequest.Builder authRequestBuilder = new AuthenticationRequest.Builder( + new ResponseType("code"), + new Scope(connection.scopes()), + clientID, URI.create(OidcCallbackServlet.getCallbackUri(request))) + .endpointURI(providerMetadata.getAuthorizationEndpointURI()) + .state(state) + .nonce(nonce); + + if ( connection.additionalAuthorizationParameters() != null ) { + Arrays.stream(connection.additionalAuthorizationParameters()) + .map( s -> s.split("=") ) + .filter( p -> p.length == 2 ) + .forEach( p -> authRequestBuilder.customParameter(p[0], p[1])); + } + + return authRequestBuilder.build().toURI(); } } diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcProviderMetadataRegistry.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcProviderMetadataRegistry.java index 6a147a5d..66a2c4c3 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcProviderMetadataRegistry.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/impl/OidcProviderMetadataRegistry.java @@ -17,6 +17,7 @@ package org.apache.sling.extensions.oidc_rp.impl; import java.io.IOException; +import java.net.URI; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -40,7 +41,8 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; public class OidcProviderMetadataRegistry { private final ConcurrentMap<String, OIDCProviderMetadata> cache = new ConcurrentHashMap<>(); - public OIDCProviderMetadata getProviderMetadata(String base) { + // visible for testing + protected OIDCProviderMetadata getProviderMetadata(String base) { return cache.computeIfAbsent(base, s -> { try { return OIDCProviderMetadata.resolve(new Issuer(s)); @@ -49,4 +51,12 @@ public class OidcProviderMetadataRegistry { } }); } + + public URI getTokenEndpoint(String base) { + return getProviderMetadata(base).getTokenEndpointURI(); + } + + public URI getAuthorizationEndpoint(String base) { + return getProviderMetadata(base).getAuthorizationEndpointURI(); + } } \ No newline at end of file diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/support/OAuthEnabledSlingServlet.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/support/OAuthEnabledSlingServlet.java index a4d38506..9693e60a 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/support/OAuthEnabledSlingServlet.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/extensions/oidc_rp/support/OAuthEnabledSlingServlet.java @@ -25,7 +25,8 @@ import javax.servlet.http.HttpServletResponse; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; -import org.apache.sling.extensions.oidc_rp.OidcClient; +import org.apache.sling.extensions.oidc_rp.OAuthUris; +import org.apache.sling.extensions.oidc_rp.OAuthTokenRefresher; import org.apache.sling.extensions.oidc_rp.OidcConnection; import org.apache.sling.extensions.oidc_rp.OidcToken; import org.apache.sling.extensions.oidc_rp.OidcTokenState; @@ -45,9 +46,9 @@ public abstract class OAuthEnabledSlingServlet extends SlingSafeMethodsServlet { private final OidcTokenStore tokenStore; - private final OidcClient oidcClient; + private final OAuthTokenRefresher oidcClient; - protected OAuthEnabledSlingServlet(OidcConnection connection, OidcTokenStore tokenStore, OidcClient oidcClient) { + protected OAuthEnabledSlingServlet(OidcConnection connection, OidcTokenStore tokenStore, OAuthTokenRefresher oidcClient) { this.connection = Objects.requireNonNull(connection, "connection may not null"); this.tokenStore = Objects.requireNonNull(tokenStore, "tokenStore may not null"); this.oidcClient = Objects.requireNonNull(oidcClient, "oidcClient may not null"); @@ -74,12 +75,12 @@ public abstract class OAuthEnabledSlingServlet extends SlingSafeMethodsServlet { doGetWithToken(request, response, tokenResponse); break; case MISSING: - response.sendRedirect(oidcClient.getOidcEntryPointUri(connection, request, redirectPath).toString()); + response.sendRedirect(OAuthUris.getOidcEntryPointUri(connection, request, redirectPath).toString()); break; case EXPIRED: OidcToken refreshToken = tokenStore.getRefreshToken(connection, request.getResourceResolver()); if ( refreshToken.getState() != OidcTokenState.VALID ) { - response.sendRedirect(oidcClient.getOidcEntryPointUri(connection, request, redirectPath).toString()); + response.sendRedirect(OAuthUris.getOidcEntryPointUri(connection, request, redirectPath).toString()); return; } diff --git a/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OAuthUrisTest.java b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OAuthUrisTest.java new file mode 100644 index 00000000..27ba7325 --- /dev/null +++ b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OAuthUrisTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.servlets.oidc_rp.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + +import org.apache.sling.extensions.oidc_rp.OAuthUris; +import org.apache.sling.testing.mock.sling.junit5.SlingContext; +import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(SlingContextExtension.class) +class OAuthUrisTest { + + private final SlingContext context = new SlingContext(); + + @Test + void testRedirectUri() { + URI redirectUri = OAuthUris.getOidcEntryPointUri(MockOidcConnection.DEFAULT_CONNECTION, context.request(), "/foo"); + + assertThat(redirectUri).as("redirect uri") + .hasScheme("http") + .hasHost("localhost") + .hasNoPort() + .hasPath("/system/sling/oidc/entry-point") + .hasQuery("c=mock-oidc&redirect=/foo"); + } + + @Test + void testRedirectUri_customPort_noRedirect() { + context.request().setServerPort(8080); + URI redirectUri = OAuthUris.getOidcEntryPointUri(MockOidcConnection.DEFAULT_CONNECTION, context.request(), null); + + assertThat(redirectUri).as("redirect uri") + .hasScheme("http") + .hasHost("localhost") + .hasPort(8080) + .hasPath("/system/sling/oidc/entry-point") + .hasQuery("c=mock-oidc"); + } + +} diff --git a/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcClientImplTest.java b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServletTest.java similarity index 56% rename from org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcClientImplTest.java rename to org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServletTest.java index 23c01814..10c0f413 100644 --- a/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcClientImplTest.java +++ b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcEntryPointServletTest.java @@ -18,14 +18,21 @@ package org.apache.sling.servlets.oidc_rp.impl; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.servlet.ServletException; import org.apache.sling.extensions.oidc_rp.OidcConnection; -import org.apache.sling.extensions.oidc_rp.impl.OidcClientImpl; +import org.apache.sling.extensions.oidc_rp.impl.OidcEntryPointServlet; import org.apache.sling.extensions.oidc_rp.impl.OidcProviderMetadataRegistry; import org.apache.sling.testing.mock.sling.junit5.SlingContext; import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,17 +42,23 @@ import com.nimbusds.openid.connect.sdk.SubjectType; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; @ExtendWith(SlingContextExtension.class) -class OidcClientImplTest { +class OidcEntryPointServletTest { - private final SlingContext context = new SlingContext(); + private static final String MOCK_OIDC_PARAM = "mock-oidc-param"; - private OidcClientImpl clientImpl; + private final SlingContext context = new SlingContext(); + private List<OidcConnection> connections; + private OidcEntryPointServlet servlet; @BeforeEach - void initClient() { - clientImpl = new OidcClientImpl(new OidcProviderMetadataRegistry() { + void initServlet() { + connections = Arrays.asList( + MockOidcConnection.DEFAULT_CONNECTION, + new MockOidcConnection(new String[] {"openid"}, MOCK_OIDC_PARAM, "client-id", "client-secret", "http://example.com", new String[] { "access_type=offline" } ) + ); + servlet = new OidcEntryPointServlet(connections, new OidcProviderMetadataRegistry() { @Override - public OIDCProviderMetadata getProviderMetadata(String base) { + protected OIDCProviderMetadata getProviderMetadata(String base) { return new OIDCProviderMetadata(new Issuer(base), Collections.singletonList(SubjectType.PUBLIC), URI.create("https://foo.example/jwks")) { @Override public URI getAuthorizationEndpointURI() { @@ -57,36 +70,16 @@ class OidcClientImplTest { } @Test - void testRedirectUri() { - URI redirectUri = clientImpl.getOidcEntryPointUri(MockOidcConnection.DEFAULT_CONNECTION, context.request(), "/foo"); - - assertThat(redirectUri).as("redirect uri") - .hasScheme("http") - .hasHost("localhost") - .hasNoPort() - .hasPath("/system/sling/oidc/entry-point") - .hasQuery("c=mock-oidc&redirect=/foo"); - } + void testRedirectWithValidConnection() throws ServletException, IOException { - @Test - void testRedirectUri_customPort_noRedirect() { - context.request().setServerPort(8080); - URI redirectUri = clientImpl.getOidcEntryPointUri(MockOidcConnection.DEFAULT_CONNECTION, context.request(), null); + context.request().setQueryString("c=" + MockOidcConnection.DEFAULT_CONNECTION.name()); + MockSlingHttpServletResponse response = context.response(); - assertThat(redirectUri).as("redirect uri") - .hasScheme("http") - .hasHost("localhost") - .hasPort(8080) - .hasPath("/system/sling/oidc/entry-point") - .hasQuery("c=mock-oidc"); - } - - @Test - void testGetAuthenticationRequestUri() { - - URI requestUri = clientImpl.getAuthenticationRequestUri(MockOidcConnection.DEFAULT_CONNECTION, context.request(), URI.create("http://localhost/callback")); - - assertThat(requestUri).as("authentication request uri") + servlet.service(context.request(), response); + + URI location = URI.create(Objects.requireNonNull(response.getHeader("Location"), "location header")); + + assertThat(location).as("authentication request uri") .hasScheme("https") .hasHost("foo.example") .hasPath("/authz") @@ -95,18 +88,20 @@ class OidcClientImplTest { .hasParameter("client_id", "client-id") .hasParameter("redirect_uri", "http://localhost/system/sling/oidc/callback") .hasParameter("nonce") - .hasParameter("state"); + .hasParameter("state"); } @Test - void testGetAuthenticationRequestUri_customParam() { - - OidcConnection connection = new MockOidcConnection(new String[] {"openid"}, "mock-oidc", "client-id", "client-secret", "http://example.com", new String[] { "access_type=offline" } ); + void redirectWithValidConnectionAndCustomParameter() throws ServletException, IOException { + + context.request().setQueryString("c=" + MOCK_OIDC_PARAM); + MockSlingHttpServletResponse response = context.response(); - URI requestUri = clientImpl.getAuthenticationRequestUri(connection, context.request(), URI.create("http://localhost/callback")); - - assertThat(requestUri).as("authentication request uri") + servlet.service(context.request(), response); + + URI location = URI.create(Objects.requireNonNull(response.getHeader("Location"), "location header")); + + assertThat(location).as("authentication request uri") .hasParameter("access_type", "offline"); } - }
