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-org-apache-sling-auth-oauth-client.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cad956  feat: add support for all methods in the 
OAuthEnabledSlingServlet (#9)
3cad956 is described below

commit 3cad956b413142e8fa0c85c10c7c53f7a719c1e9
Author: Robert Munteanu <[email protected]>
AuthorDate: Thu Dec 12 17:21:26 2024 +0100

    feat: add support for all methods in the OAuthEnabledSlingServlet (#9)
---
 .../support/OAuthEnabledSlingServlet.java          | 122 ++++++++++--
 .../support/OAuthEnabledSlingServletTest.java      | 213 +++++++++++++++++++++
 2 files changed, 314 insertions(+), 21 deletions(-)

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 273551e..72d57a3 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
@@ -16,6 +16,11 @@
  */
 package org.apache.sling.auth.oauth_client.support;
 
+import static org.apache.sling.api.servlets.HttpConstants.METHOD_DELETE;
+import static org.apache.sling.api.servlets.HttpConstants.METHOD_GET;
+import static org.apache.sling.api.servlets.HttpConstants.METHOD_POST;
+import static org.apache.sling.api.servlets.HttpConstants.METHOD_PUT;
+
 import java.io.IOException;
 import java.util.Objects;
 
@@ -24,7 +29,7 @@ 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.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.auth.oauth_client.ClientConnection;
 import org.apache.sling.auth.oauth_client.OAuthTokenAccess;
 import org.apache.sling.auth.oauth_client.OAuthTokenResponse;
@@ -33,7 +38,18 @@ import org.apache.sling.auth.oauth_client.impl.TokenState;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-public abstract class OAuthEnabledSlingServlet extends SlingSafeMethodsServlet 
{
+/**
+ * Support class for implementing OAuth-enabled servlets
+ * 
+ * <p>Features:</p>
+ * 
+ * <ul> 
+ *  <li>Handles OAuth token retrieval and refresh</li>
+ *  <li>Starts the authentication flow if no token is available</li>
+ *  <li>Handles invalid access tokens ( {@link 
#isInvalidAccessTokenException(Exception)} )</li>
+ * </ul>
+ */
+public abstract class OAuthEnabledSlingServlet extends SlingAllMethodsServlet {
 
        private static final long serialVersionUID = 1L;
        
@@ -47,12 +63,41 @@ public abstract class OAuthEnabledSlingServlet extends 
SlingSafeMethodsServlet {
         this.connection = Objects.requireNonNull(connection, "connection may 
not null");
         this.tokenAccess = Objects.requireNonNull(tokenAccess, "tokenAccess 
may not null");
     }
+    
+    @Override
+    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull 
SlingHttpServletResponse response)
+            throws ServletException, IOException {
+        handleRequestWithToken(request, response, METHOD_GET);
+    }
 
-       @Override
-       protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull 
SlingHttpServletResponse response)
+    @Override
+    protected void doPost(@NotNull SlingHttpServletRequest request, @NotNull 
SlingHttpServletResponse response)
+            throws ServletException, IOException {
+        handleRequestWithToken(request, response, METHOD_POST);
+    }
+    
+    @Override
+    protected void doDelete(@NotNull SlingHttpServletRequest request, @NotNull 
SlingHttpServletResponse response)
+            throws ServletException, IOException {
+        handleRequestWithToken(request, response, METHOD_DELETE);
+    }
+    
+    @Override
+    protected void doPut(@NotNull SlingHttpServletRequest request, @NotNull 
SlingHttpServletResponse response)
+            throws ServletException, IOException {
+        handleRequestWithToken(request, response, METHOD_PUT);
+    }
+    
+    @Override
+    protected void doGeneric(@NotNull SlingHttpServletRequest request, 
@NotNull SlingHttpServletResponse response)
+            throws ServletException, IOException {
+        handleRequestWithToken(request, response, request.getMethod());
+    }
+    
+       private void handleRequestWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String method)
                        throws ServletException, IOException {
            
-           if ( request.getUserPrincipal() == null ) {
+           if ( request.getRemoteUser() == null ) {
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User is 
not authenticated");
             return;
            }
@@ -64,33 +109,68 @@ public abstract class OAuthEnabledSlingServlet extends 
SlingSafeMethodsServlet {
            
            OAuthTokenResponse 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());
-           }
-       }
-       
-       private void doGetWithPossiblyInvalidToken(@NotNull 
SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response, 
OAuthToken token, String redirectPath) throws ServletException, IOException {
-           try {
-            doGetWithToken(request, response, token.getValue());
-        } catch (ServletException | IOException e) {
+               OAuthToken token = new OAuthToken(TokenState.VALID, 
tokenResponse.getTokenValue());
+               try {
+               switch ( method ) {
+                case METHOD_GET:
+                    doGetWithToken(request, response, token.getValue());
+                    break;
+                case METHOD_POST:
+                    doPostWithToken(request, response, token.getValue());
+                    break;
+                case METHOD_PUT:
+                    doPutWithToken(request, response, token.getValue());
+                    break;
+                case METHOD_DELETE:
+                    doDeleteWithToken(request, response, token.getValue());
+                    break;
+                default:
+                    doGenericWithToken(request, response, token.getValue());
+                    break;
+               }
+        } catch (IOException | ServletException e) {
             if (isInvalidAccessTokenException(e)) {
                 logger.warn("Invalid access token, clearing exiting token and 
restarting OAuth flow", e);
-                OAuthTokenResponse tokenResponse = 
tokenAccess.clearAccessToken(connection, request, getRedirectPath(request));
-                
response.sendRedirect(tokenResponse.getRedirectUri().toString());
+                OAuthTokenResponse newTokenResponse = 
tokenAccess.clearAccessToken(connection, request, redirectPath);
+                
response.sendRedirect(newTokenResponse.getRedirectUri().toString());
             } else {
                 throw e;
             }
         }
-    }
-       
+           } else {
+               
response.sendRedirect(tokenResponse.getRedirectUri().toString());
+           }
+       }
+
        // TODO - do we need this as a protected method?
        protected @NotNull String getRedirectPath(@NotNull 
SlingHttpServletRequest request) {
            return request.getRequestURI();
        }
 
-       protected abstract void doGetWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String accessToken)
-               throws ServletException, IOException;
+       protected void doGetWithToken(@NotNull SlingHttpServletRequest request, 
@NotNull SlingHttpServletResponse response, String accessToken)
+               throws IOException, ServletException {
+           handleMethodNotImplemented(request, response);
+       }
+
+       protected void doPostWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String accessToken)
+               throws IOException, ServletException {
+           handleMethodNotImplemented(request, response);
+       }
+
+       protected void doPutWithToken(@NotNull SlingHttpServletRequest request, 
@NotNull SlingHttpServletResponse response, String accessToken)
+               throws IOException, ServletException {
+           handleMethodNotImplemented(request, response);
+       }
+
+       protected void doDeleteWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String accessToken)
+               throws IOException, ServletException {
+           handleMethodNotImplemented(request, response);
+       }
+       
+    protected void doGenericWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String accessToken)
+            throws IOException, ServletException {
+        handleMethodNotImplemented(request, response);
+    }
        
     protected boolean isInvalidAccessTokenException(Exception e) {
         return false;
diff --git 
a/src/test/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServletTest.java
 
b/src/test/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServletTest.java
new file mode 100644
index 0000000..69f8d07
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/oauth_client/support/OAuthEnabledSlingServletTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.auth.oauth_client.ClientConnection;
+import org.apache.sling.auth.oauth_client.InMemoryOAuthTokenStore;
+import org.apache.sling.auth.oauth_client.OAuthTokenAccess;
+import org.apache.sling.auth.oauth_client.impl.MockOidcConnection;
+import org.apache.sling.auth.oauth_client.impl.OAuthException;
+import org.apache.sling.auth.oauth_client.impl.OAuthTokenRefresher;
+import org.apache.sling.auth.oauth_client.impl.OAuthTokenStore;
+import org.apache.sling.auth.oauth_client.impl.OAuthTokens;
+import org.apache.sling.auth.oauth_client.impl.TokenAccessImpl;
+import org.apache.sling.testing.mock.sling.junit5.SlingContext;
+import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(SlingContextExtension.class)
+class OAuthEnabledSlingServletTest {
+
+    private SlingContext context = new SlingContext();
+    private TokenAccessImpl tokenAccess;
+    private InMemoryOAuthTokenStore tokenStore;
+    
+    @BeforeEach
+    void initServices() {
+        tokenStore = (InMemoryOAuthTokenStore) 
context.registerService(OAuthTokenStore.class, new InMemoryOAuthTokenStore());
+        context.registerService(OAuthTokenRefresher.class, new 
OAuthTokenRefresher() {
+            @Override
+            public OAuthTokens refreshTokens(ClientConnection connection, 
String refreshToken) throws OAuthException {
+                throw new UnsupportedOperationException("Not yet implemented");
+            }
+        });
+        
+        tokenAccess = 
context.registerInjectActivateService(TokenAccessImpl.class);
+    }
+    
+    @Test
+    void errorWhenUserIsNotLoggedIn() throws ServletException, IOException {
+        
+        OAuthEnabledSlingServletTestImpl servlet = new 
OAuthEnabledSlingServletTestImpl(MockOidcConnection.DEFAULT_CONNECTION, 
tokenAccess);
+        
+        servlet.service(context.request(), context.response());
+
+        assertThat(context.response().getStatus()).as("response status 
code").isEqualTo(401);
+    }
+
+    
+    @Test
+    void redirectWhenNoTokenIsFound() throws ServletException, IOException {
+        
+        context.request().setRemoteUser("user");
+
+        OAuthEnabledSlingServletTestImpl servlet = new 
OAuthEnabledSlingServletTestImpl(MockOidcConnection.DEFAULT_CONNECTION, 
tokenAccess);
+        
+        servlet.service(context.request(), context.response());
+
+        assertThat(context.response().getStatus()).as("response status 
code").isEqualTo(302);
+        assertThat(context.response().getHeader("location")).as("redirect 
location").startsWith("http://localhost/system/sling/oauth/entry-point";);
+    }
+    
+    @Test
+    void doGetInvokedWhenTokenIsFound() throws ServletException, IOException {
+        
+        doInvokeWithToken("GET", "Hello World. GET. TOKEN: ACCESS_TOKEN");
+    }
+    
+    @Test
+    void doPostInvokedWhenTokenIsFound() throws ServletException, IOException {
+        
+        doInvokeWithToken("POST", "Hello World. POST. TOKEN: ACCESS_TOKEN");
+    }
+
+    @Test
+    void doPutInvokedWhenTokenIsFound() throws ServletException, IOException {
+        
+        doInvokeWithToken("PUT", "Hello World. PUT. TOKEN: ACCESS_TOKEN");
+    }
+    
+    @Test
+    void doDeleteInvokedWhenTokenIsFound() throws ServletException, 
IOException {
+        
+        doInvokeWithToken("DELETE", "Hello World. DELETE. TOKEN: 
ACCESS_TOKEN");
+    }
+    
+    private void doInvokeWithToken(String method, String expectedBody) throws 
ServletException, IOException {
+        context.request().setRemoteUser("user");
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
context.resourceResolver(), new OAuthTokens("ACCESS_TOKEN", 0, null));
+
+        OAuthEnabledSlingServletTestImpl servlet = new 
OAuthEnabledSlingServletTestImpl(MockOidcConnection.DEFAULT_CONNECTION, 
tokenAccess);
+        
+        context.request().setMethod(method);
+        servlet.service(context.request(), context.response());
+
+        assertThat(context.response().getStatus()).as("response status 
code").isEqualTo(200);
+        assertThat(context.response().getOutputAsString()).as("response 
body").isEqualTo(expectedBody);
+    }
+    
+    @Test
+    void doGenericInvokedWhenTokenIsFound() throws ServletException, 
IOException {
+        
+        doInvokeWithToken("PATCH", "Hello World. PATCH. TOKEN: ACCESS_TOKEN");
+    }
+    
+    @Test
+    void exceptionClearedAndRedirectIssueWhenTokenIsInvalid() throws 
ServletException, IOException {
+        
+        context.request().setRemoteUser("user");
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
context.resourceResolver(), new OAuthTokens("ACCESS_TOKEN", 0, null));
+
+        OAuthEnabledSlingServletTestImpl servlet = new 
OAuthEnabledSlingServletTestImpl(MockOidcConnection.DEFAULT_CONNECTION, 
tokenAccess);
+        
+        context.request().setMethod("ERROR_TOKEN");
+        servlet.service(context.request(), context.response());
+
+        assertThat(context.response().getStatus()).as("response status 
code").isEqualTo(302);
+        assertThat(context.response().getHeader("location")).as("redirect 
location").startsWith("http://localhost/system/sling/oauth/entry-point";);
+        assertThat(tokenStore.allTokens()).as("all tokens").isEmpty();
+    }
+    
+    @Test
+    void exceptionPropagated() {
+        
+        context.request().setRemoteUser("user");
+        
+        tokenStore.persistTokens(MockOidcConnection.DEFAULT_CONNECTION, 
context.resourceResolver(), new OAuthTokens("ACCESS_TOKEN", 0, null));
+
+        OAuthEnabledSlingServletTestImpl servlet = new 
OAuthEnabledSlingServletTestImpl(MockOidcConnection.DEFAULT_CONNECTION, 
tokenAccess);
+        
+        context.request().setMethod("ERROR_GENERIC");
+        
+        assertThatThrownBy(() -> servlet.service(context.request(), 
context.response()))
+            .isInstanceOf(ServletException.class);
+    }
+    
+    static class OAuthEnabledSlingServletTestImpl extends 
OAuthEnabledSlingServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        public OAuthEnabledSlingServletTestImpl(ClientConnection connection, 
OAuthTokenAccess tokenAccess) {
+            super(connection, tokenAccess);
+        }
+        
+        @Override
+        protected void doGetWithToken(@NotNull SlingHttpServletRequest request,
+                @NotNull SlingHttpServletResponse response, String 
accessToken) throws ServletException, IOException {
+            response.getWriter().write("Hello World. GET. TOKEN: " + 
accessToken);
+        }
+        
+        @Override
+        protected void doPostWithToken(@NotNull SlingHttpServletRequest 
request, @NotNull SlingHttpServletResponse response, String accessToken)
+                throws ServletException, IOException {
+            response.getWriter().write("Hello World. POST. TOKEN: " + 
accessToken);
+        }
+        @Override
+        protected void doPutWithToken(@NotNull SlingHttpServletRequest request,
+                @NotNull SlingHttpServletResponse response, String 
accessToken) throws IOException, ServletException {
+            response.getWriter().write("Hello World. PUT. TOKEN: " + 
accessToken);
+        }
+        
+        @Override
+        protected void doDeleteWithToken(@NotNull SlingHttpServletRequest 
request,
+                @NotNull SlingHttpServletResponse response, String 
accessToken) throws IOException, ServletException {
+            response.getWriter().write("Hello World. DELETE. TOKEN: " + 
accessToken);
+        }
+        @Override
+        protected void doGenericWithToken(@NotNull SlingHttpServletRequest 
request,
+                @NotNull SlingHttpServletResponse response, String 
accessToken) throws IOException, ServletException {
+            
+            if (request.getMethod().equals("ERROR_TOKEN") )
+                throw new ServletException("CLEAR_TOKEN");
+            
+            if (request.getMethod().equals("ERROR_GENERIC"))
+                throw new ServletException();
+                
+            response.getWriter().write("Hello World. " + request.getMethod() + 
". TOKEN: " + accessToken);
+        }
+
+        @Override
+        protected boolean isInvalidAccessTokenException(Exception e) {
+            return "CLEAR_TOKEN".equals(e.getMessage());
+        }
+    }
+
+}

Reply via email to