You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2019/11/06 18:16:59 UTC

[cxf-fediz] branch master updated: Porting Token Revocation changes to Fediz

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

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf-fediz.git


The following commit(s) were added to refs/heads/master by this push:
     new a170985  Porting Token Revocation changes to Fediz
a170985 is described below

commit a17098506cc1bb70196b8ab92f5c42c2ffc38c91
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Wed Nov 6 18:16:29 2019 +0000

    Porting Token Revocation changes to Fediz
---
 .../fediz/service/oidc/OAuthDataProviderImpl.java  | 100 +++++++++++++
 .../cxf/fediz/systests/oidc/AbstractOIDCTest.java  | 160 ++++++++++++++++++++-
 2 files changed, 258 insertions(+), 2 deletions(-)

diff --git a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
index 67b6d5c..0cbc666 100644
--- a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
+++ b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
@@ -24,9 +24,12 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.cxf.rs.security.oauth2.common.Client;
+import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken;
 import org.apache.cxf.rs.security.oauth2.grants.code.JCacheCodeDataProvider;
 import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
+import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken;
 import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
+import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils;
 import org.apache.cxf.rs.security.oidc.utils.OidcUtils;
 
 public class OAuthDataProviderImpl extends JCacheCodeDataProvider {
@@ -42,4 +45,101 @@ public class OAuthDataProviderImpl extends JCacheCodeDataProvider {
             throw new OAuthServiceException("Required scopes are missing");
         }
     }
+
+    //
+    // BEGIN - TODO This can be removed once we pick up CXF 3.3.5
+    //
+
+    @Override
+    public ServerAccessToken refreshAccessToken(Client client, String refreshTokenKey,
+                                                List<String> restrictedScopes) throws OAuthServiceException {
+        RefreshToken currentRefreshToken = isRecycleRefreshTokens()
+            ? revokeRefreshToken(client, refreshTokenKey) : getRefreshToken(refreshTokenKey);
+        if (currentRefreshToken == null) {
+            throw new OAuthServiceException(OAuthConstants.ACCESS_DENIED);
+        }
+        if (OAuthUtils.isExpired(currentRefreshToken.getIssuedAt(), currentRefreshToken.getExpiresIn())) {
+            if (!isRecycleRefreshTokens()) {
+                revokeRefreshToken(client, refreshTokenKey);
+            }
+            throw new OAuthServiceException(OAuthConstants.ACCESS_DENIED);
+        }
+        if (isRecycleRefreshTokens()) {
+            revokeAccessTokens(client, currentRefreshToken);
+        }
+
+        ServerAccessToken at = doRefreshAccessToken(client, currentRefreshToken, restrictedScopes);
+        saveAccessToken(at);
+        if (isRecycleRefreshTokens()) {
+            createNewRefreshToken(at);
+        } else {
+            updateExistingRefreshToken(currentRefreshToken, at);
+        }
+        return at;
+    }
+
+    @Override
+    public void revokeToken(Client client, String tokenKey, String tokenTypeHint) throws OAuthServiceException {
+        ServerAccessToken accessToken = null;
+        if (!OAuthConstants.REFRESH_TOKEN.equals(tokenTypeHint)) {
+            accessToken = revokeAccessToken(client, tokenKey);
+        }
+        if (accessToken != null) {
+            handleLinkedRefreshToken(client, accessToken);
+        } else if (!OAuthConstants.ACCESS_TOKEN.equals(tokenTypeHint)) {
+            RefreshToken currentRefreshToken = revokeRefreshToken(client, tokenKey);
+            revokeAccessTokens(client, currentRefreshToken);
+        }
+    }
+
+    protected void handleLinkedRefreshToken(Client client, ServerAccessToken accessToken) {
+        if (accessToken != null && accessToken.getRefreshToken() != null) {
+            RefreshToken rt = getRefreshToken(accessToken.getRefreshToken());
+            if (rt == null) {
+                return;
+            }
+
+            unlinkRefreshAccessToken(rt, accessToken.getTokenKey());
+            if (rt.getAccessTokens().isEmpty()) {
+                revokeRefreshToken(client, rt.getTokenKey());
+            } else {
+                saveRefreshToken(rt);
+            }
+        }
+
+    }
+
+    protected void revokeAccessTokens(Client client, RefreshToken currentRefreshToken) {
+        if (currentRefreshToken != null) {
+            for (String accessTokenKey : currentRefreshToken.getAccessTokens()) {
+                revokeAccessToken(client, accessTokenKey);
+            }
+        }
+    }
+
+    protected ServerAccessToken revokeAccessToken(Client client, String accessTokenKey) {
+        ServerAccessToken at = getAccessToken(accessTokenKey);
+        if (at != null) {
+            if (!at.getClient().getClientId().equals(client.getClientId())) {
+                throw new OAuthServiceException(OAuthConstants.INVALID_GRANT);
+            }
+            doRevokeAccessToken(at);
+        }
+        return at;
+    }
+
+    protected RefreshToken revokeRefreshToken(Client client, String refreshTokenKey) {
+        RefreshToken refreshToken = getRefreshToken(refreshTokenKey);
+        if (refreshToken != null) {
+            if (!refreshToken.getClient().getClientId().equals(client.getClientId())) {
+                throw new OAuthServiceException(OAuthConstants.INVALID_GRANT);
+            }
+            doRevokeRefreshToken(refreshToken);
+        }
+        return refreshToken;
+    }
+
+    //
+    // END
+    //
 }
diff --git a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
index aa5283d..ffdd055 100644
--- a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
+++ b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/AbstractOIDCTest.java
@@ -962,6 +962,158 @@ abstract class AbstractOIDCTest {
         webClient2.close();
     }
 
+    @org.junit.Test
+    public void testAccessTokenRevocation() throws Exception {
+
+        String url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/idp/authorize"
+            + "?client_id=" + storedClientId
+            + "&response_type=code"
+            + "&scope=openid";
+        String user = "alice";
+        String password = "ecila";
+
+        // Login to the OIDC token endpoint + get the authorization code
+        WebClient webClient = setupWebClient(user, password);
+        String authorizationCode = loginAndGetAuthorizationCode(url, webClient);
+        assertNotNull(authorizationCode);
+
+        // Now use the code to get an IdToken
+
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/token";
+        WebRequest request = new WebRequest(new URL(url), HttpMethod.POST);
+
+        request.setRequestParameters(Arrays.asList(
+            new NameValuePair("client_id", storedClientId),
+            new NameValuePair("grant_type", "authorization_code"),
+            new NameValuePair("code", authorizationCode)));
+
+        WebClient webClient2 = setupWebClient("", "");
+        String data = storedClientId + ":" + storedClientPassword;
+        String authorizationHeader = "Basic "
+            + Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8));
+        webClient2.addRequestHeader("Authorization", authorizationHeader);
+        final UnexpectedPage responsePage = webClient2.getPage(request);
+        String response = responsePage.getWebResponse().getContentAsString();
+
+        // Check the IdToken
+        String idToken = getIdToken(response);
+        assertNotNull(idToken);
+        validateIdToken(idToken, storedClientId);
+
+        // Get the access token
+        String accessToken = parseToken(response, "access_token");
+        assertNotNull(accessToken);
+
+        // Introspect the token and check it's valid
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/introspect";
+        WebRequest introspectionRequest = new WebRequest(new URL(url), HttpMethod.POST);
+        introspectionRequest.setRequestParameters(Arrays.asList(
+            new NameValuePair("token", accessToken)));
+
+        UnexpectedPage introspectionResponsePage = webClient2.getPage(introspectionRequest);
+        String introspectionResponse = introspectionResponsePage.getWebResponse().getContentAsString();
+
+        assertTrue(introspectionResponse.contains("\"active\":true"));
+
+        // Now revoke the token
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/revoke";
+        WebRequest revocationRequest = new WebRequest(new URL(url), HttpMethod.POST);
+        revocationRequest.setRequestParameters(Arrays.asList(
+            new NameValuePair("token", accessToken)));
+
+        webClient2.getPage(revocationRequest);
+
+        // Now introspect the token again and check it's not valid
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/introspect";
+
+        introspectionResponsePage = webClient2.getPage(introspectionRequest);
+        introspectionResponse = introspectionResponsePage.getWebResponse().getContentAsString();
+
+        assertTrue(introspectionResponse.contains("\"active\":false"));
+
+        webClient.close();
+        webClient2.close();
+    }
+
+    @org.junit.Test
+    public void testAccessTokenRevocationWrongClient() throws Exception {
+
+        String url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/idp/authorize"
+            + "?client_id=" + storedClientId
+            + "&response_type=code"
+            + "&scope=openid";
+        String user = "alice";
+        String password = "ecila";
+
+        // Login to the OIDC token endpoint + get the authorization code
+        WebClient webClient = setupWebClient(user, password);
+        String authorizationCode = loginAndGetAuthorizationCode(url, webClient);
+        assertNotNull(authorizationCode);
+
+        // Now use the code to get an IdToken
+
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/token";
+        WebRequest request = new WebRequest(new URL(url), HttpMethod.POST);
+
+        request.setRequestParameters(Arrays.asList(
+            new NameValuePair("client_id", storedClientId),
+            new NameValuePair("grant_type", "authorization_code"),
+            new NameValuePair("code", authorizationCode)));
+
+        WebClient webClient2 = setupWebClient("", "");
+        String data = storedClientId + ":" + storedClientPassword;
+        String authorizationHeader = "Basic "
+            + Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8));
+        webClient2.addRequestHeader("Authorization", authorizationHeader);
+        final UnexpectedPage responsePage = webClient2.getPage(request);
+        String response = responsePage.getWebResponse().getContentAsString();
+
+        // Check the IdToken
+        String idToken = getIdToken(response);
+        assertNotNull(idToken);
+        validateIdToken(idToken, storedClientId);
+
+        // Get the access token
+        String accessToken = parseToken(response, "access_token");
+        assertNotNull(accessToken);
+
+        // Introspect the token and check it's valid
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/introspect";
+        WebRequest introspectionRequest = new WebRequest(new URL(url), HttpMethod.POST);
+        introspectionRequest.setRequestParameters(Arrays.asList(
+            new NameValuePair("token", accessToken)));
+
+        UnexpectedPage introspectionResponsePage = webClient2.getPage(introspectionRequest);
+        String introspectionResponse = introspectionResponsePage.getWebResponse().getContentAsString();
+
+        assertTrue(introspectionResponse.contains("\"active\":true"));
+
+        // Now try to revoke the token as the other client
+        WebClient webClient3 = setupWebClient("", "");
+        String data2 = storedClient2Id + ":" + storedClient2Password;
+        String authorizationHeader2 = "Basic "
+            + Base64.getEncoder().encodeToString(data2.getBytes(StandardCharsets.UTF_8));
+        webClient3.addRequestHeader("Authorization", authorizationHeader2);
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/revoke";
+        WebRequest revocationRequest = new WebRequest(new URL(url), HttpMethod.POST);
+        revocationRequest.setRequestParameters(Arrays.asList(
+            new NameValuePair("token", accessToken)));
+
+        webClient3.getPage(revocationRequest);
+        webClient3.close();
+
+        // Now introspect the token again and check it's still valid
+        url = "https://localhost:" + getRpHttpsPort() + "/" + getServletContextName() + "/oauth2/introspect";
+
+        introspectionResponsePage = webClient2.getPage(introspectionRequest);
+        introspectionResponse = introspectionResponsePage.getWebResponse().getContentAsString();
+
+        assertTrue(introspectionResponse.contains("\"active\":true"));
+
+        webClient.close();
+        webClient2.close();
+    }
+
     private static WebClient setupWebClient(String user, String password) {
         final WebClient webClient = new WebClient();
         webClient.getOptions().setUseInsecureSSL(true);
@@ -1025,9 +1177,13 @@ abstract class AbstractOIDCTest {
     }
 
     private static String getIdToken(String parentString) {
+        return parseToken(parentString, "id_token");
+    }
+
+    private static String parseToken(String parentString, String tag) {
         String foundString =
-            parentString.substring(parentString.indexOf("id_token")
-                                   + "id_token\":\"".length());
+            parentString.substring(parentString.indexOf(tag)
+                                   + (tag + "\":\"").length());
         int ampersandIndex = foundString.indexOf('"');
         if (ampersandIndex < 1) {
             ampersandIndex = foundString.length();