This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch issue/high-level-api
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-oauth-client.git

commit 2f1f9efff04e1f43fc17a2b7d281601b359e3bd9
Author: Robert Munteanu <[email protected]>
AuthorDate: Tue Dec 10 11:32:21 2024 +0100

    feat: introduce a high-level API for retrieving access tokens
---
 README.md                                          |  98 ++++++++++++--
 .../sling/auth/oauth_client/TokenAccess.java       |  70 ++++++++++
 .../sling/auth/oauth_client/TokenAccessImpl.java   | 100 ++++++++++++++
 .../sling/auth/oauth_client/TokenResponse.java     |  84 ++++++++++++
 .../support/OAuthEnabledSlingServlet.java          |  46 ++-----
 .../auth/oauth_client/TokenAccessImplTest.java     | 149 +++++++++++++++++++++
 6 files changed, 500 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md
index d64fa0d..75687fe 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,48 @@ the authentication code flow based on OIDC and OAuth 2.0 .
 
 ## Usage
 
-### High-level APIs
+### Models and other Java APIs
+
+The `TokenAccess` OSGi service exposes methods to retrieve and clear access 
tokens. These methods encapsulate
+persistence concerns and handle refresh tokens transparently, if present.
+
+```java
+@Model(adaptables = SlingHttpServletRequest.class)
+public class MyModel {
+    
+    @SlingObject private SlingHttpServletRequest request;
+    
+    @OSGiService(filter = "(name=foo)") private ClientConnection connection;
+    
+    @OSGiService private TokenAccess tokenAccess;
+
+    private TokenResponse tokenResponse;
+    
+    @PostConstruct
+    public void initToken() {
+        tokenResponse = tokenAccess.getAccessToken(connection, request, 
request.getRequestURI());
+    }
+    
+    public MyView getResponse() {
+        if ( tokenResponse.hasValidToken() ) {
+            return doQuery(tokenResponse.getTokenValue());
+        }
+        
+        return null;
+    }
+    
+    public String getRedirectLink() {
+        if ( !tokenResponse.hasValidToken() ) {
+            return tokenResponse.getRedirectUri().toString();
+        }
+        
+        return null;
+    }
+}
+
+```
+
+### Servlets
 
 The bundle exposes an abstract `OAuthEnabledSlingServlet` that contains the 
boilerplate code needed
 to obtain a valid OAuth 2 access token.
@@ -36,10 +77,9 @@ public class MySlingServlet extends OAuthEnabledSlingServlet 
{
    
     @Activate
     public MySlingServlet(@Reference OidcConnection connection, 
-        @Reference OAuthTokenStore tokenStore,
-        @Reference OAuthTokenRefresher oidcClient,
+        @Reference TokenAccess tokenAccess,
         @Reference MyRemoteService svc) {
-        super(connection, tokenStore, oidcClient);
+        super(connection, tokenAccess);
         this.svc = svc;
     }
 
@@ -52,9 +92,6 @@ public class MySlingServlet extends OAuthEnabledSlingServlet {
 }
 ```
 
-### Low-level APIs
-
-TODO
 
 ### Clearing access tokens
 
@@ -65,24 +102,57 @@ invalidated out of band.
 The client will need to determine if the access token is invalid as this is a 
provider-specific
 check.
 
-To remove invalid access tokens there is a low-level API
+#### When the request and response are available
+
+This method is generally recommended as it permits the generation of a 
redirect URI that will kick
+off a new OAuth authorisation flow.
+
 
 ```java
-public class MyComponent {}
-    @Reference private OAuthTokenStore tokenStore;
+@Model(adaptables = SlingHttpServletRequest.class)
+public class MySlingModel {
+    @OSGiService private TokenAccess tokenAccess;
+    @SlingObject SlingHttpServletRequest request;
+    @OSGiService(filter = "(name=foo)") private ClientConnection connection;
+    
+    public String getLink() {
+        // code elided
+        if ( accessTokenIsInvalid() ) {
+            TokenResponse response = tokenAccess.clearAccessToken(connection, 
request, request.getRequestURI());
+            return response.getRedirectUri().toString();
+        }
+    }
+}
+```
+
+
+#### When the request and response are not available
+
+
+This approach should be used when invalidating access tokens without user 
interaction, as it does not
+provide a mechanism to generate a redirect URL for restarting the OAuth 
authorisation flow and obtaining
+a new access token.
+
+```java
+@Component
+public class MyComponent {
+    @Reference private TokenAccess tokenAccess;
     
     public void execute(@Reference OidcConnection connection, ResourceResolver 
resolver) {
         // code elided
         if ( accessTokenIsInvalid() ) {
-            tokenStore.clearAccessToken(connection, resolver);
-            // redirect to provider or present a message to the user
+            tokenAccess.clearAccessToken(connection, resolver);
         }
     }
 }
 ```
 
-For classes that extend from the `OAuthEnabledSlingServlet` the following 
method override can be
-applied
+
+
+#### When extending OAuthEnabledSlingServlet
+
+For classes that extend from the `OAuthEnabledSlingServlet` the 
`isInvalidAccessTokenException` method can be
+overriden. If this method returns true, the access token is cleared and a new 
OAuth flow is started.
 
 ```java
 @Component(service = { Servlet.class })
diff --git a/src/main/java/org/apache/sling/auth/oauth_client/TokenAccess.java 
b/src/main/java/org/apache/sling/auth/oauth_client/TokenAccess.java
new file mode 100644
index 0000000..3ae7aca
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/oauth_client/TokenAccess.java
@@ -0,0 +1,70 @@
+/*
+ * 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.auth.oauth_client;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Entry point for accessing and clearing access tokens
+ * 
+ * <p>The tokens are stored distinctly for each client connection and user. 
The client connection is identified by 
+ * {@link ClientConnection#name() name} and the user is identified by the 
{@link ResourceResolver#getUserID()}. </p>
+ * 
+ * <p>The storage strategy may vary and is controlled by the currently active 
implementation of the {@link OAuthTokenStore}.</p>
+ */
+@NotNull
+public interface TokenAccess {
+
+    /**
+     * Retrieves an existing access, valid, access token from storage.
+     * 
+     * <p>Refreshes expired access tokens if a refresh token is available but 
does not attempt to retrieve new access tokens.</p>
+     * 
+     * @param connection the client connection to retrieve token for
+     * @param request the request used to determine the current user for which 
to retrieve the token and to build the redirect URL
+     * @param redirectPath the path to redirect to after completing the OAuth 
flow
+     * @return the token response
+     */
+    TokenResponse getAccessToken(ClientConnection connection, 
SlingHttpServletRequest request, String redirectPath);
+
+    /**
+     * Clears the access token for the given connection and user, as 
identified by the request.
+     * 
+     * <p>Returns a response that does not have a valid token and contains a 
URI to redirect the user to.</p>
+     * 
+     * @param connection the client connection to clear the token for
+     * @param request the request used to determine the current user for which 
to retrieve the token and to build the redirect URL
+     * @param redirectPath the path to redirect to after completing the OAuth 
flow 
+     * @return the token response
+     */
+    TokenResponse clearAccessToken(ClientConnection connection, 
SlingHttpServletRequest request, String redirectPath);
+    
+
+    /**
+     * Clears the access token for the given connection and user, as 
identified by the resource resolver
+     * 
+     * <p>For scenarios where a redirect URI should be generated after 
clearing the access token {@link #clearAccessToken(ClientConnection, 
SlingHttpServletRequest, String)}
+     * should be used instead.</p>
+     * 
+     * @param connection the client connection to clear the token for
+     * @param resolver used to determine the current user for which to 
retrieve the token
+     */
+    void clearAccessToken(ClientConnection connection, ResourceResolver 
resolver);
+
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/auth/oauth_client/TokenAccessImpl.java 
b/src/main/java/org/apache/sling/auth/oauth_client/TokenAccessImpl.java
new file mode 100644
index 0000000..6eaf978
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/oauth_client/TokenAccessImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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.auth.oauth_client;
+
+import java.util.Optional;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+public class TokenAccessImpl implements TokenAccess {
+    
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    private OAuthTokenStore tokenStore;
+    private OAuthTokenRefresher tokenRefresher;
+    
+    @Activate
+    public TokenAccessImpl(@Reference OAuthTokenStore tokenStore, @Reference 
OAuthTokenRefresher tokenRefresher) {
+        this.tokenStore = tokenStore;
+        this.tokenRefresher = tokenRefresher;
+    }
+    
+    @Override
+    public TokenResponse getAccessToken(ClientConnection connection, 
SlingHttpServletRequest request, String redirectPath) {
+        
+        ResourceResolver resolver = request.getResourceResolver();
+        
+        OAuthToken token = tokenStore.getAccessToken(connection, resolver);
+        
+        if ( logger.isDebugEnabled() )
+            logger.debug("Accessing token for connection {} and user {}", 
connection.name(), request.getUserPrincipal());
+        
+        // valid access token present -> return token
+        if (token.getState() == TokenState.VALID) {
+            if (logger.isDebugEnabled())
+                logger.debug("Returning valid access token for connection {} 
and user {}", connection.name(), request.getUserPrincipal());
+            
+            return new TokenResponse(Optional.of(token.getValue()), 
connection, request, redirectPath);
+        }
+        
+        // expired token but refresh token present -> refresh and return
+        if (token.getState() == TokenState.EXPIRED) {
+            OAuthToken refreshToken = tokenStore.getRefreshToken(connection, 
resolver);
+            if (refreshToken.getState() == TokenState.VALID) {
+                if (logger.isDebugEnabled())
+                    logger.debug("Refreshing expired access token for 
connection {} and user {}", connection.name(), request.getUserPrincipal());
+
+                OAuthTokens newTokens = 
tokenRefresher.refreshTokens(connection, refreshToken.getValue());
+                tokenStore.persistTokens(connection, resolver, newTokens);
+                return new TokenResponse(Optional.of(newTokens.accessToken()), 
connection, request, redirectPath);
+            }
+        }
+
+        // all other scenarios -> redirect
+        if ( logger.isDebugEnabled() )
+            logger.debug("No valid access token found for connection {} and 
user {}", connection.name(), request.getUserPrincipal());
+        
+        return new TokenResponse(Optional.empty(), connection, request, 
redirectPath);
+    }
+    
+    @Override
+    public TokenResponse clearAccessToken(ClientConnection connection, 
SlingHttpServletRequest request, String redirectPath) {
+        
+        if ( logger.isDebugEnabled() )
+            logger.debug("Clearing access token for connection {} and user 
{}", connection.name(), request.getUserPrincipal());
+
+        tokenStore.clearAccessToken(connection, request.getResourceResolver());
+        
+        return new TokenResponse(Optional.empty(), connection, request, 
redirectPath);
+    }
+    
+    @Override
+    public void clearAccessToken(ClientConnection connection, ResourceResolver 
resolver) {
+
+        if ( logger.isDebugEnabled() )
+            logger.debug("Clearing access token for connection {} and user 
{}", connection.name(), resolver.getUserID());
+
+        tokenStore.clearAccessToken(connection, resolver);
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/auth/oauth_client/TokenResponse.java 
b/src/main/java/org/apache/sling/auth/oauth_client/TokenResponse.java
new file mode 100644
index 0000000..9464522
--- /dev/null
+++ b/src/main/java/org/apache/sling/auth/oauth_client/TokenResponse.java
@@ -0,0 +1,84 @@
+/*
+ * 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.auth.oauth_client;
+
+import java.net.URI;
+import java.util.Optional;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Encapsulates the response to a token request.
+ * 
+ * <p>This class has two top-level states:
+ * <ol>
+ *   <li>has a valid access token: {@link #hasValidToken()} returns {@code 
true}, and {@link #getTokenValue()} returns the token value.</li>
+ *   <li>does not have a valid access token: {@link #hasValidToken()} returns 
{@code false}, and {@link #getRedirectUri()} returns the URI to redirect the 
user to.</li>
+ * </ol>
+ * </p>
+ * 
+ * <p>Methods generally throw {@link IllegalStateException} if they are called 
in an unexpected state and do not return null values.</p>
+ */
+@NotNull
+public class TokenResponse {
+    
+    private final Optional<String> token;
+    private final ClientConnection connection;
+    private final SlingHttpServletRequest request;
+    private String redirectPath;
+    
+    public TokenResponse(Optional<String> token, ClientConnection connection, 
SlingHttpServletRequest request, String redirectPath) {
+        this.token = token;
+        this.connection = connection;
+        this.request = request;
+        this.redirectPath = redirectPath;
+    }
+
+    /**
+     * Returns true if a valid access token is present and false otherwise
+     * 
+     * @return true if a valid access token is present
+     */
+    public boolean hasValidToken() {
+        return token.isPresent();
+    }
+    
+    
+    /**
+     * Returns the a valid access token value and throws an {@link 
IllegalStateException} otherwise
+     * 
+     * @return a valid access token value
+     * @throws IllegalStateException if no access token is present
+     */
+    public String getTokenValue() {
+        return token.orElseThrow(() -> new IllegalStateException("No access 
token present."));
+    }
+    
+    /**
+     * Returns the URI to redirect the user to in order to start the OAuth flow
+     * 
+     * @return the URI to redirect the user to
+     * @throws IllegalStateException if an access token is present
+     */
+    public URI getRedirectUri() {
+        if ( token.isPresent() )
+            throw new IllegalStateException("Access token is present, will not 
generate a new redirect URI.");
+        
+        return OAuthUris.getOidcEntryPointUri(connection, request, 
redirectPath);
+    }
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServlet.java
 
b/src/main/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServlet.java
index d92f50d..6c6d838 100644
--- 
a/src/main/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServlet.java
+++ 
b/src/main/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServlet.java
@@ -27,10 +27,8 @@ import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
 import org.apache.sling.auth.oauth_client.ClientConnection;
 import org.apache.sling.auth.oauth_client.OAuthToken;
-import org.apache.sling.auth.oauth_client.OAuthTokenRefresher;
-import org.apache.sling.auth.oauth_client.OAuthTokenStore;
-import org.apache.sling.auth.oauth_client.OAuthTokens;
-import org.apache.sling.auth.oauth_client.OAuthUris;
+import org.apache.sling.auth.oauth_client.TokenAccess;
+import org.apache.sling.auth.oauth_client.TokenResponse;
 import org.apache.sling.auth.oauth_client.TokenState;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
@@ -43,14 +41,11 @@ public abstract class OAuthEnabledSlingServlet extends 
SlingSafeMethodsServlet {
 
     private final ClientConnection connection;
 
-    private final OAuthTokenStore tokenStore;
-
-    private final OAuthTokenRefresher oidcClient;
+    private final TokenAccess tokenAccess;
        
-    protected OAuthEnabledSlingServlet(ClientConnection connection, 
OAuthTokenStore tokenStore, OAuthTokenRefresher oidcClient) {
+    protected OAuthEnabledSlingServlet(ClientConnection connection, 
TokenAccess tokenAccess) {
         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");
+        this.tokenAccess = Objects.requireNonNull(tokenAccess, "tokenAccess 
may not null");
     }
 
        @Override
@@ -67,26 +62,11 @@ public abstract class OAuthEnabledSlingServlet extends 
SlingSafeMethodsServlet {
            if ( logger.isDebugEnabled() )
                logger.debug("Configured with connection (name={}) and 
redirectPath={}", connection.name(), redirectPath);
            
-           OAuthToken tokenResponse = tokenStore.getAccessToken(connection, 
request.getResourceResolver());
-           
-               switch ( tokenResponse.getState() ) {
-             case VALID:
-            doGetWithPossiblyInvalidToken(request, response, tokenResponse, 
redirectPath);
-               break;
-             case MISSING:
-               
response.sendRedirect(OAuthUris.getOidcEntryPointUri(connection, request, 
redirectPath).toString());
-               break;
-             case EXPIRED:
-               OAuthToken refreshToken = 
tokenStore.getRefreshToken(connection, request.getResourceResolver());
-               if ( refreshToken.getState() != TokenState.VALID ) {
-                 
response.sendRedirect(OAuthUris.getOidcEntryPointUri(connection, request, 
redirectPath).toString());
-                 return;
-               }
-               
-               OAuthTokens oidcTokens = oidcClient.refreshTokens(connection, 
refreshToken.getValue());
-               tokenStore.persistTokens(connection, 
request.getResourceResolver(), oidcTokens);
-               doGetWithPossiblyInvalidToken(request, response, new 
OAuthToken(TokenState.VALID, oidcTokens.accessToken()), redirectPath);
-               break;
+           TokenResponse tokenResponse = 
tokenAccess.getAccessToken(connection, request, redirectPath);
+           if (tokenResponse.hasValidToken() ) {
+               doGetWithPossiblyInvalidToken(request, response, new 
OAuthToken(TokenState.VALID, tokenResponse.getTokenValue()), redirectPath);
+           } else {
+               
response.sendRedirect(tokenResponse.getRedirectUri().toString());
            }
        }
        
@@ -95,9 +75,9 @@ public abstract class OAuthEnabledSlingServlet extends 
SlingSafeMethodsServlet {
             doGetWithToken(request, response, token);
         } catch (ServletException | IOException e) {
             if (isInvalidAccessTokenException(e)) {
-                logger.warn("Invalid access token, clearing and attempting to 
retrieve a fresh one", e);
-                tokenStore.clearAccessToken(connection, 
request.getResourceResolver());
-                
response.sendRedirect(OAuthUris.getOidcEntryPointUri(connection, request, 
redirectPath).toString());
+                logger.warn("Invalid access token, clearing restarting OAuth 
flow", e);
+                TokenResponse tokenResponse = 
tokenAccess.clearAccessToken(connection, request, getRedirectPath(request));
+                
response.sendRedirect(tokenResponse.getRedirectUri().toString());
             } else {
                 throw e;
             }
diff --git 
a/src/test/java/org/apache/sling/auth/oauth_client/TokenAccessImplTest.java 
b/src/test/java/org/apache/sling/auth/oauth_client/TokenAccessImplTest.java
new file mode 100644
index 0000000..24401c4
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/oauth_client/TokenAccessImplTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.auth.oauth_client;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.sling.auth.oauth_client.impl.MockOidcConnection;
+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 TokenAccessImplTest {
+    
+    private SlingContext slingContext = new SlingContext();
+
+    @Test
+    void missingAccessToken() {
+        
+        OAuthTokenStore tokenStore = new InMemoryOAuthTokenStore();
+        
+        TokenAccessImpl tokenAccess = new TokenAccessImpl(tokenStore, null);
+        
+        TokenResponse tokenResponse = 
tokenAccess.getAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        
+        assertThat(tokenResponse)
+            .as("tokenResponse")
+            .isNotNull()
+            .satisfies( tr -> {
+                assertThat(tr.hasValidToken()).as("hasValidToken").isFalse();
+                assertThrows(IllegalStateException.class, tr::getTokenValue, 
"getTokenValue");
+                assertThat(tr.getRedirectUri()).as("redirectUri")
+                    .isNotNull()
+                    .asString()
+                    .isNotBlank();
+            });
+    }
+    
+    @Test
+    void presentAccessToken() {
+        OAuthTokenStore tokenStore = new InMemoryOAuthTokenStore();
+
+        TokenAccessImpl tokenAccess = new TokenAccessImpl(tokenStore, null);
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.resourceResolver(), new OAuthTokens("access", 0, null));
+        
+        TokenResponse tokenResponse = 
tokenAccess.getAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        
+        assertThat(tokenResponse)
+            .as("tokenResponse")
+            .isNotNull()
+            .satisfies( tr -> {
+                assertThat(tr.hasValidToken()).as("hasValidToken").isTrue();
+                
assertThat(tr.getTokenValue()).as("tokenValue").isEqualTo("access");
+                assertThrows(IllegalStateException.class, tr::getRedirectUri, 
"getRedirectUri");
+            });
+    }
+    
+    @Test
+    void refreshTokenUsed() {
+        
+        OAuthTokens expiredTokens = new OAuthTokens("access", -1, "refresh");
+        OAuthTokens refreshedTokens = new OAuthTokens("access2", 0, null);
+        
+        OAuthTokenStore tokenStore = new InMemoryOAuthTokenStore();
+        
+        OAuthTokenRefresher tokenRefresher = new OAuthTokenRefresher() {
+            @Override
+            public OAuthTokens refreshTokens(ClientConnection connection, 
String refreshToken) {
+                if (!refreshToken.equals(expiredTokens.refreshToken()))
+                    throw new IllegalArgumentException("Invalid refresh 
token");
+                
+                return refreshedTokens;
+            }
+        };
+
+        TokenAccessImpl tokenAccess = new TokenAccessImpl(tokenStore, 
tokenRefresher);
+
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.resourceResolver(), expiredTokens);
+        
+        TokenResponse tokenResponse = 
tokenAccess.getAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        
+        assertThat(tokenResponse)
+            .as("tokenResponse")
+            .isNotNull()
+            .satisfies( tr -> {
+                assertThat(tr.hasValidToken()).as("hasValidToken").isTrue();
+                
assertThat(tr.getTokenValue()).as("tokenValue").isEqualTo(refreshedTokens.accessToken());
+                assertThrows(IllegalStateException.class, tr::getRedirectUri, 
"getRedirectUri");
+            });
+    }
+    
+    @Test
+    void clearAccessTokenWithResponse() {
+        
+        OAuthTokenStore tokenStore = new InMemoryOAuthTokenStore();
+
+        TokenAccessImpl tokenAccess = new TokenAccessImpl(tokenStore, null);
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.resourceResolver(), new OAuthTokens("access", 0, null));
+
+        TokenResponse okResponse = 
tokenAccess.getAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        assertThat(okResponse.hasValidToken()).isTrue();
+        
+        TokenResponse clearResponse = 
tokenAccess.clearAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        
+        assertThat(clearResponse)
+            .as("tokenResponse after clear")
+            .isNotNull()
+            .extracting(TokenResponse::hasValidToken)
+            .isEqualTo(false);
+    }
+    
+    @Test
+    void clearAccessTokenWithoutResponse() {
+        
+        InMemoryOAuthTokenStore tokenStore = new InMemoryOAuthTokenStore();
+
+        TokenAccessImpl tokenAccess = new TokenAccessImpl(tokenStore, null);
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.resourceResolver(), new OAuthTokens("access", 0, null));
+
+        TokenResponse okResponse = 
tokenAccess.getAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.request(), "/");
+        assertThat(okResponse.hasValidToken()).isTrue();
+        
+        tokenAccess.clearAccessToken(MockOidcConnection.DEFAULT_CONNECTION, 
slingContext.resourceResolver());
+        
+        assertThat(tokenStore.allTokens())
+            .as("all persisted tokens")
+            .isEmpty();
+    }
+
+}

Reply via email to