You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2017/07/05 10:20:18 UTC

[1/2] syncope git commit: [SYNCOPE-1149] Working support for third party JWT SSO providers, with test case and sample implementation

Repository: syncope
Updated Branches:
  refs/heads/2_0_X 647d03ad1 -> 48d917933
  refs/heads/master 0f5116c2f -> ffb78c087


[SYNCOPE-1149] Working support for third party JWT SSO providers, with test case and sample implementation


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/48d91793
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/48d91793
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/48d91793

Branch: refs/heads/2_0_X
Commit: 48d9179332f8c2d0c699661fc00b7ff83ea857cf
Parents: 647d03a
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Wed Jul 5 12:19:52 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Wed Jul 5 12:19:52 2017 +0200

----------------------------------------------------------------------
 .../core/spring/security/AuthDataAccessor.java  |  42 +++++---
 .../core/spring/security/JWTAccessToken.java    | 103 +++++++++++++++++++
 .../core/spring/security/JWTAuthentication.java |  10 +-
 .../security/JWTAuthenticationProvider.java     |   6 +-
 .../core/spring/security/JWTSSOProvider.java    |  12 ++-
 .../spring/security/SyncopeJWTSSOProvider.java  |  11 +-
 .../core/reference/CustomJWTSSOProvider.java    |  60 +++++++++--
 .../org/apache/syncope/fit/core/JWTITCase.java  |  21 ++--
 8 files changed, 224 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 402bfae..61b9aa7 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -389,37 +389,47 @@ public class AuthDataAccessor {
     }
 
     @Transactional
-    public Set<SyncopeGrantedAuthority> authenticate(final JWTAuthentication authentication) {
-        AccessToken accessToken = accessTokenDAO.find(authentication.getClaims().getTokenId());
-        if (accessToken == null) {
-            throw new AuthenticationCredentialsNotFoundException(
-                    "Could not find JWT " + authentication.getClaims().getTokenId());
-        }
-
+    public Pair<String, Set<SyncopeGrantedAuthority>> authenticate(final JWTAuthentication authentication) {
+        String username;
         Set<SyncopeGrantedAuthority> authorities;
 
-        if (adminUser.equals(accessToken.getOwner())) {
+        if (adminUser.equals(authentication.getClaims().getSubject())) {
+            AccessToken accessToken = accessTokenDAO.find(authentication.getClaims().getTokenId());
+            if (accessToken == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                        "Could not find an Access Token for JWT " + authentication.getClaims().getTokenId());
+            }
+
+            username = adminUser;
             authorities = getAdminAuthorities();
         } else {
             JWTSSOProvider jwtSSOProvider = getJWTSSOProvider(authentication.getClaims().getIssuer());
-            User user = jwtSSOProvider.resolve(accessToken.getOwner());
-            if (user == null) {
+            Pair<User, AccessToken> resolved = jwtSSOProvider.resolve(authentication.getClaims());
+            if (resolved == null || resolved.getLeft() == null) {
                 throw new AuthenticationCredentialsNotFoundException(
-                        "Could not find user " + accessToken.getOwner()
+                        "Could not find User " + authentication.getClaims().getSubject()
                         + " for JWT " + authentication.getClaims().getTokenId());
             }
-            LOG.debug("JWT {} issued by {} resolved to user {}",
+            if (resolved == null || resolved.getRight() == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                        "Could not find an Access Token for JWT " + authentication.getClaims().getTokenId());
+            }
+
+            User user = resolved.getLeft();
+            username = user.getUsername();
+            AccessToken accessToken = resolved.getRight();
+            LOG.debug("JWT {} issued by {} resolved to User {} and Access Token {}",
                     authentication.getClaims().getTokenId(),
                     authentication.getClaims().getIssuer(),
-                    user.getUsername());
+                    username, accessToken.getKey());
 
             if (BooleanUtils.isTrue(user.isSuspended())) {
-                throw new DisabledException("User " + user.getUsername() + " is suspended");
+                throw new DisabledException("User " + username + " is suspended");
             }
 
             CPlainAttr authStatuses = confDAO.find("authentication.statuses");
             if (authStatuses != null && !authStatuses.getValuesAsStrings().contains(user.getStatus())) {
-                throw new DisabledException("User " + user.getUsername() + " not allowed to authenticate");
+                throw new DisabledException("User " + username + " not allowed to authenticate");
             }
 
             if (BooleanUtils.isTrue(user.isMustChangePassword())) {
@@ -440,7 +450,7 @@ public class AuthDataAccessor {
             }
         }
 
-        return authorities;
+        return Pair.of(username, authorities);
     }
 
     @Transactional

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
new file mode 100644
index 0000000..2ddf002
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
@@ -0,0 +1,103 @@
+/*
+ * 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.syncope.core.spring.security;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.UUID;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+/**
+ * Convenience {@link AccessToken} implementation wrapping the received JWT, for usage with custom
+ * {@link JWTSSOProvider#resolve} implementations.
+ */
+public class JWTAccessToken implements AccessToken {
+
+    private static final long serialVersionUID = -3824671946137458487L;
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    private final String key = UUID.randomUUID().toString();
+
+    private final JwtClaims claims;
+
+    private byte[] authorities;
+
+    public JWTAccessToken(final JwtClaims claims) throws Exception {
+        this.claims = claims;
+        this.authorities = ENCRYPTOR.encode(
+                POJOHelper.serialize(Collections.emptySet()), CipherAlgorithm.AES).
+                getBytes();
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getBody() {
+        return null;
+    }
+
+    @Override
+    public Date getExpiryTime() {
+        return new Date(claims.getExpiryTime());
+    }
+
+    @Override
+    public String getOwner() {
+        return claims.getSubject();
+    }
+
+    @Override
+    public byte[] getAuthorities() {
+        return authorities;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        // nothing to do
+    }
+
+    @Override
+    public void setBody(final String body) {
+        // nothing to do
+    }
+
+    @Override
+    public void setExpiryTime(final Date expiryTime) {
+        // nothing to do
+    }
+
+    @Override
+    public void setOwner(final String owner) {
+        // nothing to do
+    }
+
+    @Override
+    public void setAuthorities(final byte[] authorities) {
+        this.authorities = ArrayUtils.clone(authorities);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
index 7348938..0b5ac61 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
@@ -38,6 +38,8 @@ public class JWTAuthentication implements Authentication {
 
     private final SyncopeAuthenticationDetails details;
 
+    private String username;
+
     private final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
 
     private boolean authenticated = false;
@@ -68,7 +70,11 @@ public class JWTAuthentication implements Authentication {
 
     @Override
     public Object getPrincipal() {
-        return claims.getSubject();
+        return username == null ? claims.getSubject() : username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
     }
 
     @Override
@@ -83,6 +89,6 @@ public class JWTAuthentication implements Authentication {
 
     @Override
     public String getName() {
-        return claims.getSubject();
+        return username == null ? claims.getSubject() : username;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
index b5a3353..36d718a 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.core.spring.security;
 
 import java.util.Date;
+import java.util.Set;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
@@ -44,7 +46,9 @@ public class JWTAuthenticationProvider implements AuthenticationProvider {
 
             @Override
             public Void exec() {
-                jwtAuthentication.getAuthorities().addAll(dataAccessor.authenticate(jwtAuthentication));
+                Pair<String, Set<SyncopeGrantedAuthority>> authenticated = dataAccessor.authenticate(jwtAuthentication);
+                jwtAuthentication.setUsername(authenticated.getLeft());
+                jwtAuthentication.getAuthorities().addAll(authenticated.getRight());
                 return null;
             }
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
index 63e7087..cb9ce0d 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
@@ -18,7 +18,10 @@
  */
 package org.apache.syncope.core.spring.security;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 
 /**
@@ -35,10 +38,11 @@ public interface JWTSSOProvider extends JwsSignatureVerifier {
     String getIssuer();
 
     /**
-     * Attempts to resolve the subject from a given JWT into an internal user.
+     * Attempts to resolve the given JWT claims into internal {@link User} and {@link AccessToken}.
+     * <strong>IMPORTANT</strong>: this is not invoked for the {@code}admin{@code} super-user.
      *
-     * @param jwtSubject subject from JWT claims
-     * @return internal user matching the provided subject if found, otherwise null
+     * @param jwtClaims JWT claims
+     * @return internal User and Access Token matching the provided JWT claims, if found; otherwise null
      */
-    User resolve(String jwtSubject);
+    Pair<User, AccessToken> resolve(JwtClaims jwtClaims);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
index d40a096..8d561a6 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
@@ -19,11 +19,15 @@
 package org.apache.syncope.core.spring.security;
 
 import javax.annotation.Resource;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -42,6 +46,9 @@ public class SyncopeJWTSSOProvider implements JWTSSOProvider {
     @Autowired
     private UserDAO userDAO;
 
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
     @Override
     public String getIssuer() {
         return jwtIssuer;
@@ -64,8 +71,8 @@ public class SyncopeJWTSSOProvider implements JWTSSOProvider {
 
     @Transactional(readOnly = true)
     @Override
-    public User resolve(final String jwtSubject) {
-        return userDAO.findByUsername(jwtSubject);
+    public Pair<User, AccessToken> resolve(final JwtClaims jwtClaims) {
+        return Pair.of(userDAO.findByUsername(jwtClaims.getSubject()), accessTokenDAO.find(jwtClaims.getTokenId()));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
index a980382..61ab632 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
@@ -18,14 +18,28 @@
  */
 package org.apache.syncope.fit.core.reference;
 
+import java.util.List;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthDataAccessor;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.apache.syncope.core.spring.security.JWTAccessToken;
 import org.apache.syncope.core.spring.security.JWTSSOProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -34,21 +48,29 @@ import org.springframework.transaction.annotation.Transactional;
  */
 public class CustomJWTSSOProvider implements JWTSSOProvider {
 
-    private String jwtIssuer = "custom-issuer";
+    private static final Logger LOG = LoggerFactory.getLogger(CustomJWTSSOProvider.class);
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    public static final String ISSUER = "custom-issuer";
+
+    public static final String CUSTOM_KEY = "12345678910987654321";
 
     private final JwsSignatureVerifier delegate;
 
     @Autowired
-    private UserDAO userDAO;
+    private AnySearchDAO searchDAO;
+
+    @Autowired
+    private AuthDataAccessor authDataAccessor;
 
     public CustomJWTSSOProvider() {
-        String customKey = "12345678910987654321";
-        delegate = new HmacJwsSignatureVerifier(customKey.getBytes(), SignatureAlgorithm.HS512);
+        delegate = new HmacJwsSignatureVerifier(CUSTOM_KEY.getBytes(), SignatureAlgorithm.HS512);
     }
 
     @Override
     public String getIssuer() {
-        return jwtIssuer;
+        return ISSUER;
     }
 
     @Override
@@ -68,8 +90,30 @@ public class CustomJWTSSOProvider implements JWTSSOProvider {
 
     @Transactional(readOnly = true)
     @Override
-    public User resolve(final String jwtSubject) {
-        return userDAO.findByUsername(jwtSubject);
+    public Pair<User, AccessToken> resolve(final JwtClaims jwtClaims) {
+        AttributeCond userIdCond = new AttributeCond();
+        userIdCond.setSchema("userId");
+        userIdCond.setType(AttributeCond.Type.EQ);
+        userIdCond.setExpression(jwtClaims.getSubject());
+
+        List<User> matching = searchDAO.search(SearchCond.getLeafCond(userIdCond), AnyTypeKind.USER);
+        if (matching.size() == 1) {
+            User user = matching.get(0);
+
+            AccessToken accessToken = null;
+            try {
+                accessToken = new JWTAccessToken(jwtClaims);
+                accessToken.setAuthorities(ENCRYPTOR.encode(
+                        POJOHelper.serialize(authDataAccessor.getAuthorities(user.getUsername())), CipherAlgorithm.AES).
+                        getBytes());
+            } catch (Exception e) {
+                LOG.error("Could not fetch or store authorities", e);
+            }
+
+            return Pair.of(user, accessToken);
+        }
+
+        return null;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/48d91793/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
index f5203912..ef122f6 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.fit.core;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -28,9 +29,12 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import javax.ws.rs.core.Response;
 import javax.xml.ws.WebServiceException;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.common.JoseType;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
@@ -44,10 +48,12 @@ import org.apache.cxf.rs.security.jose.jws.NoneJwsSignatureProvider;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.apache.cxf.rs.security.jose.jwt.JwtToken;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.AccessTokenService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.fit.AbstractITCase;
+import org.apache.syncope.fit.core.reference.CustomJWTSSOProvider;
 import org.junit.Test;
 
 /**
@@ -384,7 +390,6 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    @org.junit.Ignore
     public void thirdPartyToken() throws ParseException {
         // Create a new token
         Date now = new Date();
@@ -395,9 +400,9 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(UUID.randomUUID().toString());
-        jwtClaims.setSubject(ADMIN_UNAME);
+        jwtClaims.setSubject("puccini@apache.org");
         jwtClaims.setIssuedAt(now.getTime());
-        jwtClaims.setIssuer("custom-issuer");
+        jwtClaims.setIssuer(CustomJWTSSOProvider.ISSUER);
         jwtClaims.setExpiryTime(expiry.getTime().getTime());
         jwtClaims.setNotBefore(now.getTime());
 
@@ -405,14 +410,14 @@ public class JWTITCase extends AbstractITCase {
         JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims);
         JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken);
 
-        String customKey = "12345678910987654321";
-
         JwsSignatureProvider jwsSignatureProvider =
-                new HmacJwsSignatureProvider(customKey.getBytes(), SignatureAlgorithm.HS512);
+                new HmacJwsSignatureProvider(CustomJWTSSOProvider.CUSTOM_KEY.getBytes(), SignatureAlgorithm.HS512);
         String signed = producer.signWith(jwsSignatureProvider);
 
         SyncopeClient jwtClient = clientFactory.create(signed);
-        UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class);
-        jwtUserSelfService.read();
+
+        Pair<Map<String, Set<String>>, UserTO> self = jwtClient.self();
+        assertFalse(self.getLeft().isEmpty());
+        assertEquals("puccini", self.getRight().getUsername());
     }
 }


[2/2] syncope git commit: [SYNCOPE-1149] Working support for third party JWT SSO providers, with test case and sample implementation

Posted by il...@apache.org.
[SYNCOPE-1149] Working support for third party JWT SSO providers, with test case and sample implementation


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/ffb78c08
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/ffb78c08
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/ffb78c08

Branch: refs/heads/master
Commit: ffb78c0871ca6b99b39d67c6998438b3e9f832fb
Parents: 0f5116c
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Wed Jul 5 12:19:52 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Wed Jul 5 12:20:06 2017 +0200

----------------------------------------------------------------------
 .../core/spring/security/AuthDataAccessor.java  |  42 +++++---
 .../core/spring/security/JWTAccessToken.java    | 103 +++++++++++++++++++
 .../core/spring/security/JWTAuthentication.java |  10 +-
 .../security/JWTAuthenticationProvider.java     |   6 +-
 .../core/spring/security/JWTSSOProvider.java    |  12 ++-
 .../spring/security/SyncopeJWTSSOProvider.java  |  11 +-
 .../core/reference/CustomJWTSSOProvider.java    |  60 +++++++++--
 .../org/apache/syncope/fit/core/JWTITCase.java  |  21 ++--
 8 files changed, 224 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 402bfae..61b9aa7 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -389,37 +389,47 @@ public class AuthDataAccessor {
     }
 
     @Transactional
-    public Set<SyncopeGrantedAuthority> authenticate(final JWTAuthentication authentication) {
-        AccessToken accessToken = accessTokenDAO.find(authentication.getClaims().getTokenId());
-        if (accessToken == null) {
-            throw new AuthenticationCredentialsNotFoundException(
-                    "Could not find JWT " + authentication.getClaims().getTokenId());
-        }
-
+    public Pair<String, Set<SyncopeGrantedAuthority>> authenticate(final JWTAuthentication authentication) {
+        String username;
         Set<SyncopeGrantedAuthority> authorities;
 
-        if (adminUser.equals(accessToken.getOwner())) {
+        if (adminUser.equals(authentication.getClaims().getSubject())) {
+            AccessToken accessToken = accessTokenDAO.find(authentication.getClaims().getTokenId());
+            if (accessToken == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                        "Could not find an Access Token for JWT " + authentication.getClaims().getTokenId());
+            }
+
+            username = adminUser;
             authorities = getAdminAuthorities();
         } else {
             JWTSSOProvider jwtSSOProvider = getJWTSSOProvider(authentication.getClaims().getIssuer());
-            User user = jwtSSOProvider.resolve(accessToken.getOwner());
-            if (user == null) {
+            Pair<User, AccessToken> resolved = jwtSSOProvider.resolve(authentication.getClaims());
+            if (resolved == null || resolved.getLeft() == null) {
                 throw new AuthenticationCredentialsNotFoundException(
-                        "Could not find user " + accessToken.getOwner()
+                        "Could not find User " + authentication.getClaims().getSubject()
                         + " for JWT " + authentication.getClaims().getTokenId());
             }
-            LOG.debug("JWT {} issued by {} resolved to user {}",
+            if (resolved == null || resolved.getRight() == null) {
+                throw new AuthenticationCredentialsNotFoundException(
+                        "Could not find an Access Token for JWT " + authentication.getClaims().getTokenId());
+            }
+
+            User user = resolved.getLeft();
+            username = user.getUsername();
+            AccessToken accessToken = resolved.getRight();
+            LOG.debug("JWT {} issued by {} resolved to User {} and Access Token {}",
                     authentication.getClaims().getTokenId(),
                     authentication.getClaims().getIssuer(),
-                    user.getUsername());
+                    username, accessToken.getKey());
 
             if (BooleanUtils.isTrue(user.isSuspended())) {
-                throw new DisabledException("User " + user.getUsername() + " is suspended");
+                throw new DisabledException("User " + username + " is suspended");
             }
 
             CPlainAttr authStatuses = confDAO.find("authentication.statuses");
             if (authStatuses != null && !authStatuses.getValuesAsStrings().contains(user.getStatus())) {
-                throw new DisabledException("User " + user.getUsername() + " not allowed to authenticate");
+                throw new DisabledException("User " + username + " not allowed to authenticate");
             }
 
             if (BooleanUtils.isTrue(user.isMustChangePassword())) {
@@ -440,7 +450,7 @@ public class AuthDataAccessor {
             }
         }
 
-        return authorities;
+        return Pair.of(username, authorities);
     }
 
     @Transactional

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
new file mode 100644
index 0000000..2ddf002
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAccessToken.java
@@ -0,0 +1,103 @@
+/*
+ * 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.syncope.core.spring.security;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.UUID;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+/**
+ * Convenience {@link AccessToken} implementation wrapping the received JWT, for usage with custom
+ * {@link JWTSSOProvider#resolve} implementations.
+ */
+public class JWTAccessToken implements AccessToken {
+
+    private static final long serialVersionUID = -3824671946137458487L;
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    private final String key = UUID.randomUUID().toString();
+
+    private final JwtClaims claims;
+
+    private byte[] authorities;
+
+    public JWTAccessToken(final JwtClaims claims) throws Exception {
+        this.claims = claims;
+        this.authorities = ENCRYPTOR.encode(
+                POJOHelper.serialize(Collections.emptySet()), CipherAlgorithm.AES).
+                getBytes();
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getBody() {
+        return null;
+    }
+
+    @Override
+    public Date getExpiryTime() {
+        return new Date(claims.getExpiryTime());
+    }
+
+    @Override
+    public String getOwner() {
+        return claims.getSubject();
+    }
+
+    @Override
+    public byte[] getAuthorities() {
+        return authorities;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        // nothing to do
+    }
+
+    @Override
+    public void setBody(final String body) {
+        // nothing to do
+    }
+
+    @Override
+    public void setExpiryTime(final Date expiryTime) {
+        // nothing to do
+    }
+
+    @Override
+    public void setOwner(final String owner) {
+        // nothing to do
+    }
+
+    @Override
+    public void setAuthorities(final byte[] authorities) {
+        this.authorities = ArrayUtils.clone(authorities);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
index 7348938..0b5ac61 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java
@@ -38,6 +38,8 @@ public class JWTAuthentication implements Authentication {
 
     private final SyncopeAuthenticationDetails details;
 
+    private String username;
+
     private final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
 
     private boolean authenticated = false;
@@ -68,7 +70,11 @@ public class JWTAuthentication implements Authentication {
 
     @Override
     public Object getPrincipal() {
-        return claims.getSubject();
+        return username == null ? claims.getSubject() : username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
     }
 
     @Override
@@ -83,6 +89,6 @@ public class JWTAuthentication implements Authentication {
 
     @Override
     public String getName() {
-        return claims.getSubject();
+        return username == null ? claims.getSubject() : username;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
index b5a3353..36d718a 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.core.spring.security;
 
 import java.util.Date;
+import java.util.Set;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
@@ -44,7 +46,9 @@ public class JWTAuthenticationProvider implements AuthenticationProvider {
 
             @Override
             public Void exec() {
-                jwtAuthentication.getAuthorities().addAll(dataAccessor.authenticate(jwtAuthentication));
+                Pair<String, Set<SyncopeGrantedAuthority>> authenticated = dataAccessor.authenticate(jwtAuthentication);
+                jwtAuthentication.setUsername(authenticated.getLeft());
+                jwtAuthentication.getAuthorities().addAll(authenticated.getRight());
                 return null;
             }
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
index 63e7087..cb9ce0d 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTSSOProvider.java
@@ -18,7 +18,10 @@
  */
 package org.apache.syncope.core.spring.security;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 
 /**
@@ -35,10 +38,11 @@ public interface JWTSSOProvider extends JwsSignatureVerifier {
     String getIssuer();
 
     /**
-     * Attempts to resolve the subject from a given JWT into an internal user.
+     * Attempts to resolve the given JWT claims into internal {@link User} and {@link AccessToken}.
+     * <strong>IMPORTANT</strong>: this is not invoked for the {@code}admin{@code} super-user.
      *
-     * @param jwtSubject subject from JWT claims
-     * @return internal user matching the provided subject if found, otherwise null
+     * @param jwtClaims JWT claims
+     * @return internal User and Access Token matching the provided JWT claims, if found; otherwise null
      */
-    User resolve(String jwtSubject);
+    Pair<User, AccessToken> resolve(JwtClaims jwtClaims);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
index d40a096..8d561a6 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
@@ -19,11 +19,15 @@
 package org.apache.syncope.core.spring.security;
 
 import javax.annotation.Resource;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -42,6 +46,9 @@ public class SyncopeJWTSSOProvider implements JWTSSOProvider {
     @Autowired
     private UserDAO userDAO;
 
+    @Autowired
+    private AccessTokenDAO accessTokenDAO;
+
     @Override
     public String getIssuer() {
         return jwtIssuer;
@@ -64,8 +71,8 @@ public class SyncopeJWTSSOProvider implements JWTSSOProvider {
 
     @Transactional(readOnly = true)
     @Override
-    public User resolve(final String jwtSubject) {
-        return userDAO.findByUsername(jwtSubject);
+    public Pair<User, AccessToken> resolve(final JwtClaims jwtClaims) {
+        return Pair.of(userDAO.findByUsername(jwtClaims.getSubject()), accessTokenDAO.find(jwtClaims.getTokenId()));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
index a980382..61ab632 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
@@ -18,14 +18,28 @@
  */
 package org.apache.syncope.fit.core.reference;
 
+import java.util.List;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
 import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthDataAccessor;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.apache.syncope.core.spring.security.JWTAccessToken;
 import org.apache.syncope.core.spring.security.JWTSSOProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -34,21 +48,29 @@ import org.springframework.transaction.annotation.Transactional;
  */
 public class CustomJWTSSOProvider implements JWTSSOProvider {
 
-    private String jwtIssuer = "custom-issuer";
+    private static final Logger LOG = LoggerFactory.getLogger(CustomJWTSSOProvider.class);
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    public static final String ISSUER = "custom-issuer";
+
+    public static final String CUSTOM_KEY = "12345678910987654321";
 
     private final JwsSignatureVerifier delegate;
 
     @Autowired
-    private UserDAO userDAO;
+    private AnySearchDAO searchDAO;
+
+    @Autowired
+    private AuthDataAccessor authDataAccessor;
 
     public CustomJWTSSOProvider() {
-        String customKey = "12345678910987654321";
-        delegate = new HmacJwsSignatureVerifier(customKey.getBytes(), SignatureAlgorithm.HS512);
+        delegate = new HmacJwsSignatureVerifier(CUSTOM_KEY.getBytes(), SignatureAlgorithm.HS512);
     }
 
     @Override
     public String getIssuer() {
-        return jwtIssuer;
+        return ISSUER;
     }
 
     @Override
@@ -68,8 +90,30 @@ public class CustomJWTSSOProvider implements JWTSSOProvider {
 
     @Transactional(readOnly = true)
     @Override
-    public User resolve(final String jwtSubject) {
-        return userDAO.findByUsername(jwtSubject);
+    public Pair<User, AccessToken> resolve(final JwtClaims jwtClaims) {
+        AttributeCond userIdCond = new AttributeCond();
+        userIdCond.setSchema("userId");
+        userIdCond.setType(AttributeCond.Type.EQ);
+        userIdCond.setExpression(jwtClaims.getSubject());
+
+        List<User> matching = searchDAO.search(SearchCond.getLeafCond(userIdCond), AnyTypeKind.USER);
+        if (matching.size() == 1) {
+            User user = matching.get(0);
+
+            AccessToken accessToken = null;
+            try {
+                accessToken = new JWTAccessToken(jwtClaims);
+                accessToken.setAuthorities(ENCRYPTOR.encode(
+                        POJOHelper.serialize(authDataAccessor.getAuthorities(user.getUsername())), CipherAlgorithm.AES).
+                        getBytes());
+            } catch (Exception e) {
+                LOG.error("Could not fetch or store authorities", e);
+            }
+
+            return Pair.of(user, accessToken);
+        }
+
+        return null;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ffb78c08/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
index f5203912..ef122f6 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.fit.core;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -28,9 +29,12 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import javax.ws.rs.core.Response;
 import javax.xml.ws.WebServiceException;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.rs.security.jose.common.JoseType;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
@@ -44,10 +48,12 @@ import org.apache.cxf.rs.security.jose.jws.NoneJwsSignatureProvider;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.apache.cxf.rs.security.jose.jwt.JwtToken;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.AccessTokenService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.fit.AbstractITCase;
+import org.apache.syncope.fit.core.reference.CustomJWTSSOProvider;
 import org.junit.Test;
 
 /**
@@ -384,7 +390,6 @@ public class JWTITCase extends AbstractITCase {
     }
 
     @Test
-    @org.junit.Ignore
     public void thirdPartyToken() throws ParseException {
         // Create a new token
         Date now = new Date();
@@ -395,9 +400,9 @@ public class JWTITCase extends AbstractITCase {
 
         JwtClaims jwtClaims = new JwtClaims();
         jwtClaims.setTokenId(UUID.randomUUID().toString());
-        jwtClaims.setSubject(ADMIN_UNAME);
+        jwtClaims.setSubject("puccini@apache.org");
         jwtClaims.setIssuedAt(now.getTime());
-        jwtClaims.setIssuer("custom-issuer");
+        jwtClaims.setIssuer(CustomJWTSSOProvider.ISSUER);
         jwtClaims.setExpiryTime(expiry.getTime().getTime());
         jwtClaims.setNotBefore(now.getTime());
 
@@ -405,14 +410,14 @@ public class JWTITCase extends AbstractITCase {
         JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims);
         JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken);
 
-        String customKey = "12345678910987654321";
-
         JwsSignatureProvider jwsSignatureProvider =
-                new HmacJwsSignatureProvider(customKey.getBytes(), SignatureAlgorithm.HS512);
+                new HmacJwsSignatureProvider(CustomJWTSSOProvider.CUSTOM_KEY.getBytes(), SignatureAlgorithm.HS512);
         String signed = producer.signWith(jwsSignatureProvider);
 
         SyncopeClient jwtClient = clientFactory.create(signed);
-        UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class);
-        jwtUserSelfService.read();
+
+        Pair<Map<String, Set<String>>, UserTO> self = jwtClient.self();
+        assertFalse(self.getLeft().isEmpty());
+        assertEquals("puccini", self.getRight().getUsername());
     }
 }