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
The following commit(s) were added to refs/heads/master by this push: new 0b5f3cbb oidc-rp: add some tests for the OidcConnectionFinderImpl 0b5f3cbb is described below commit 0b5f3cbb7a51f728b89731b8669e704b0fbbab83 Author: Robert Munteanu <romb...@apache.org> AuthorDate: Wed Jul 5 18:23:15 2023 +0300 oidc-rp: add some tests for the OidcConnectionFinderImpl --- org.apache.sling.servlets.oidc-rp/pom.xml | 31 ++++- .../servlets/oidc_rp/impl/OidcCallbackServlet.java | 1 - .../oidc_rp/impl/OidcConnectionFinderImpl.java | 8 +- .../oidc_rp/impl/OidcConnectionFinderImplTest.java | 155 +++++++++++++++++++++ 4 files changed, 184 insertions(+), 11 deletions(-) diff --git a/org.apache.sling.servlets.oidc-rp/pom.xml b/org.apache.sling.servlets.oidc-rp/pom.xml index ea542db1..7a7970d6 100644 --- a/org.apache.sling.servlets.oidc-rp/pom.xml +++ b/org.apache.sling.servlets.oidc-rp/pom.xml @@ -256,14 +256,14 @@ </dependency> <dependency> <groupId>org.apache.jackrabbit</groupId> - <artifactId>jackrabbit-api</artifactId> - <version>2.0.0</version> + <artifactId>oak-jackrabbit-api</artifactId> + <version>1.40.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.api</artifactId> - <version>2.0.8</version> + <version>2.22.0</version> <scope>provided</scope> </dependency> <dependency> @@ -327,5 +327,30 @@ <version>3.0.18</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.sling-mock.junit5</artifactId> + <version>3.4.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.sling-mock-oak</artifactId> + <version>3.1.2-1.40.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.jackrabbit.usermanager</artifactId> + <version>2.2.26</version> + <scope>test</scope> + </dependency> + <!-- pin version to override older oak transitively brought in by sling-mock --> + <dependency> + <groupId>org.apache.jackrabbit</groupId> + <artifactId>oak-jcr</artifactId> + <version>1.40.0</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java index 769d6414..b6fb06e8 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcCallbackServlet.java @@ -125,7 +125,6 @@ public class OidcCallbackServlet extends SlingAllMethodsServlet { if ( connection.baseUrl() == null ) throw new ServletException("Misconfigured baseUrl"); - // TODO - this code should be extracted and reused to refresh the access token with a refresh token, if present ClientID clientId = new ClientID(connection.clientId()); Secret clientSecret = new Secret(connection.clientSecret()); ClientSecretBasic clientCredentials = new ClientSecretBasic(clientId, clientSecret); diff --git a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java index 84d273c1..6d56b88a 100644 --- a/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java +++ b/org.apache.sling.servlets.oidc-rp/src/main/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImpl.java @@ -75,13 +75,7 @@ public class OidcConnectionFinderImpl implements OidcConnectionFinder, OidcConne if ( expiresAt != null && expiresAt.length == 1 && expiresAt[0].getType() == PropertyType.DATE ) { Calendar expiresCal = expiresAt[0].getDate(); if ( expiresCal.before(Calendar.getInstance())) { - logger.info("Token for {} expired at {}, not returning", connection.name(), expiresCal); - - Value[] tokenValue = user.getProperty(propertyPath(connection, PROPERTY_NAME_REFRESH_TOKEN)); - // no refresh token, missing - if ( tokenValue == null ) { - return new OidcToken(OidcTokenState.MISSING, null); - } + logger.info("Token for {} expired at {}, marking as expired", connection.name(), expiresCal); // refresh token is present, mark as expired return new OidcToken(OidcTokenState.EXPIRED, null); diff --git a/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImplTest.java b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImplTest.java new file mode 100644 index 00000000..5447673e --- /dev/null +++ b/org.apache.sling.servlets.oidc-rp/src/test/java/org/apache/sling/servlets/oidc_rp/impl/OidcConnectionFinderImplTest.java @@ -0,0 +1,155 @@ +/* + * 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.util.concurrent.TimeUnit; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.User; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.jackrabbit.usermanager.impl.AuthorizableAdapterFactory; +import org.apache.sling.servlets.oidc_rp.OidcConnection; +import org.apache.sling.servlets.oidc_rp.OidcToken; +import org.apache.sling.servlets.oidc_rp.OidcTokenState; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit5.SlingContext; +import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.PlainJWT; +import com.nimbusds.oauth2.sdk.token.BearerAccessToken; +import com.nimbusds.openid.connect.sdk.token.OIDCTokens; + +@ExtendWith(SlingContextExtension.class) +class OidcConnectionFinderImplTest { + + private final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + private final MockOidcConnection connection = new MockOidcConnection(new String[] {"openid"}, "mock-oidc", "client-id", "client-secret", "http://example.com"); + + @BeforeEach + public void registerAdapterFactories() { + context.registerInjectActivateService(new AuthorizableAdapterFactory()); + } + + @Test + void persistTokens_accessTokenOnly() throws LoginException, RepositoryException { + + OIDCTokens tokens = new OIDCTokens(new BearerAccessToken(12), null); + + OidcConnectionFinderImpl connectionFinder = new OidcConnectionFinderImpl(null); + connectionFinder.persistTokens(connection, context.resourceResolver(), tokens); + + Resource connectionResource = getConnectionResource(connection); + + ValueMap connectionProps = connectionResource.getValueMap(); + assertThat(connectionProps) + .as("stored tokens for connection") + .containsOnlyKeys("jcr:primaryType", "access_token") + .containsEntry("access_token", tokens.getAccessToken().getValue()); + } + + @Test + void persistTokens_accessAndIdToken() throws LoginException, RepositoryException { + + OIDCTokens tokens = new OIDCTokens(new PlainJWT(new JWTClaimsSet.Builder().issuer("example.com").build()), new BearerAccessToken(12), null); + + OidcConnectionFinderImpl connectionFinder = new OidcConnectionFinderImpl(null); + connectionFinder.persistTokens(connection, context.resourceResolver(), tokens); + + Resource connectionResource = getConnectionResource(connection); + + ValueMap connectionProps = connectionResource.getValueMap(); + assertThat(connectionProps) + .as("stored tokens for connection") + .containsOnlyKeys("jcr:primaryType", "access_token", "id_token") + .containsEntry("access_token", tokens.getAccessToken().getValue()); + } + + + @Test + void getAccessToken_missing() { + + OidcConnectionFinderImpl connectionFinder = new OidcConnectionFinderImpl(null); + + OidcToken accessToken = connectionFinder.getAccessToken(connection, context.resourceResolver()); + + assertThat(accessToken).as("access token") + .isNotNull() + .extracting( OidcToken::getState ) + .isEqualTo( OidcTokenState.MISSING ); + } + + @Test + void getAccessToken_valid() { + + OIDCTokens tokens = new OIDCTokens(new BearerAccessToken(12), null); + + OidcConnectionFinderImpl connectionFinder = new OidcConnectionFinderImpl(null); + connectionFinder.persistTokens(connection, context.resourceResolver(), tokens); + + OidcToken accessToken = connectionFinder.getAccessToken(connection, context.resourceResolver()); + assertThat(accessToken).as("access token") + .isNotNull() + .extracting( OidcToken::getState , OidcToken::getValue ) + .containsExactly( OidcTokenState.VALID, tokens.getAccessToken().getValue() ); + } + + @Test + void getAccessToken_expired() throws InterruptedException { + + int lifetimeSeconds = 1; + OIDCTokens tokens = new OIDCTokens(new BearerAccessToken(12, lifetimeSeconds, null), null); + + OidcConnectionFinderImpl connectionFinder = new OidcConnectionFinderImpl(null); + connectionFinder.persistTokens(connection, context.resourceResolver(), tokens); + + // wait for the token to expire + Thread.sleep( TimeUnit.SECONDS.toMillis( 2 * lifetimeSeconds ) ); + + OidcToken accessToken = connectionFinder.getAccessToken(connection, context.resourceResolver()); + assertThat(accessToken).as("access token") + .isNotNull() + .extracting( OidcToken::getState ) + .isEqualTo( OidcTokenState.EXPIRED ); + } + + private Resource getConnectionResource(OidcConnection connection) throws RepositoryException { + String userPath = context.resourceResolver().adaptTo(User.class).getPath(); + Resource userHomeResource = context.resourceResolver().getResource(userPath); + Resource oidcTokensResource = userHomeResource.getChild("oidc-tokens"); + + assertThat(oidcTokensResource) + .describedAs("oidc-tokens resource") + .isNotNull(); + + Resource connectionResource = oidcTokensResource.getChild(connection.name()); + assertThat(connectionResource) + .as("oidc-tokens/connection resource") + .isNotNull(); + return connectionResource; + } + + public record MockOidcConnection(String[] scopes, String name, String clientId, String clientSecret, String baseUrl) implements OidcConnection { } +}