Repository: usergrid
Updated Branches:
  refs/heads/apigee-sso-provider b21e47780 -> 7cf07b4ed


Reconfigure using factory pattern for external SSO providers.  Implement JWT 
token validation ( expiry for example).


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

Branch: refs/heads/apigee-sso-provider
Commit: 7cf07b4ede4ac97df9c7d993755a36e6082d1d08
Parents: b21e477
Author: Michael Russo <[email protected]>
Authored: Fri Jun 24 16:57:33 2016 -0700
Committer: Michael Russo <[email protected]>
Committed: Fri Jun 24 16:57:33 2016 -0700

----------------------------------------------------------------------
 .../main/resources/usergrid-default.properties  |   8 +-
 .../rest/management/ManagementResource.java     |  56 +++-
 .../security/sso/ApigeeSSO2Provider.java        | 167 ++++++++++++
 .../security/sso/ExternalSSOProvider.java       |  22 ++
 .../security/sso/SSOProviderFactory.java        |  57 ++++
 .../security/sso/UsergridExternalProvider.java  | 263 +++++++++++++++++++
 .../tokens/cassandra/TokenServiceImpl.java      |  74 ++++--
 .../externalProviders/ApigeeSSO2Provider.java   | 108 --------
 .../resources/usergrid-services-context.xml     |  13 +-
 9 files changed, 623 insertions(+), 145 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/config/src/main/resources/usergrid-default.properties
----------------------------------------------------------------------
diff --git a/stack/config/src/main/resources/usergrid-default.properties 
b/stack/config/src/main/resources/usergrid-default.properties
index 2783e0e..fe739b9 100644
--- a/stack/config/src/main/resources/usergrid-default.properties
+++ b/stack/config/src/main/resources/usergrid-default.properties
@@ -492,11 +492,11 @@ usergrid.central.read.timeout=10000
 
 
 
-###############################   Usergrid SSO2   #############################
-usergrid.external.sso.enabled=
-usergrid.external.sso.provider=apigee
+####################   Usergrid External SSO Configuration   
#####################
+usergrid.external.sso.enabled=false
+usergrid.external.sso.provider=
 usergrid.external.sso.url=
-usergrid.external.sso.publicKeyUrl=https://login.apigee.com/token_key
+usergrid.external.sso.publicKeyUrl=
 
 ###############################  Usergrid Assets  #############################
 #

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
----------------------------------------------------------------------
diff --git 
a/stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
 
b/stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
index d0f3efc..3d794d6 100644
--- 
a/stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
+++ 
b/stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
@@ -35,6 +35,8 @@ import 
org.apache.usergrid.rest.management.organizations.OrganizationsResource;
 import org.apache.usergrid.rest.management.users.UsersResource;
 import org.apache.usergrid.security.oauth.AccessInfo;
 import org.apache.usergrid.security.shiro.utils.SubjectUtils;
+import org.apache.usergrid.security.sso.ExternalSSOProvider;
+import org.apache.usergrid.security.sso.SSOProviderFactory;
 import org.glassfish.jersey.server.mvc.Viewable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -88,6 +90,9 @@ public class ManagementResource extends 
AbstractContextResource {
     @Autowired
     private ApplicationCreator applicationCreator;
 
+    @Autowired
+    private SSOProviderFactory ssoProviderFactory;
+
     // usergrid configuration property names needed
     public static final String USERGRID_SYSADMIN_LOGIN_NAME = 
"usergrid.sysadmin.login.name";
 
@@ -157,8 +162,47 @@ public class ManagementResource extends 
AbstractContextResource {
                                          @QueryParam( "access_token" ) String 
access_token,
                                          @QueryParam( "callback" ) 
@DefaultValue( "" ) String callback )
             throws Exception {
-        return getAccessTokenInternal( ui, authorization, grant_type, 
username, password, client_id, client_secret, ttl,
-                callback, false, true );
+
+
+        final UserInfo user = SubjectUtils.getUser();
+
+        // if user is null ( meaning no token was provided and previously 
validated in OAuth2AccessTokenSecurityFilter)
+        // then assume it's a token request
+        if( user == null) {
+            return getAccessTokenInternal(ui, authorization, grant_type, 
username, password, client_id, client_secret, ttl,
+                callback, false, true);
+        }
+
+
+
+        // if it's not a token request and we have a user, extract details 
from the token
+
+        final long passwordChanged = management.getLastAdminPasswordChange( 
user.getUuid() );
+        final boolean ssoEnabled = 
Boolean.parseBoolean(properties.getProperty(USERGRID_EXTERNAL_SSO_ENABLED));
+        long tokenTtl;
+
+        if(ssoEnabled){
+
+            ExternalSSOProvider provider = ssoProviderFactory.getProvider();
+
+            tokenTtl =
+                
Long.valueOf(provider.getDecodedTokenDetails(access_token).get("expiry")) - 
System.currentTimeMillis()/1000;
+
+        }else{
+
+            tokenTtl = tokens.getTokenInfo(access_token).getDuration();
+        }
+
+
+        final AccessInfo access_info = new AccessInfo().withExpiresIn( 
tokenTtl ).withAccessToken( access_token )
+            .withPasswordChanged( passwordChanged );
+
+        access_info.setProperty( "user", 
management.getAdminUserOrganizationData( user, true ) );
+
+        return Response.status( SC_OK ).type( jsonMediaType( callback ) )
+            .entity( wrapWithCallback( access_info, callback ) ).build();
+
+
     }
 
 
@@ -181,6 +225,8 @@ public class ManagementResource extends 
AbstractContextResource {
                                            String callback, boolean adminData, 
boolean me) throws Exception {
 
 
+
+
         UserInfo user = null;
 
         try {
@@ -196,12 +242,14 @@ public class ManagementResource extends 
AbstractContextResource {
 
             if ( user == null ) {
 
-                if ( !me ) { // if not lightweight-auth, i.e. /management/me 
then...
+
+
+                //if ( !me ) { // if not lightweight-auth, i.e. /management/me 
then...
 
                     // make sure authentication is allowed considering
                     // external token validation configuration (UG Central SSO)
                     ensureAuthenticationAllowed( username, grant_type );
-                }
+               // }
 
                 if ( authorization != null ) {
                     String type = stringOrSubstringBeforeFirst( authorization, 
' ' ).toUpperCase();

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
new file mode 100644
index 0000000..5400212
--- /dev/null
+++ 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
@@ -0,0 +1,167 @@
+package org.apache.usergrid.security.sso;
+
+import io.jsonwebtoken.*;
+import org.apache.usergrid.corepersistence.util.CpNamingUtils;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.management.UserInfo;
+import org.apache.usergrid.security.AuthPrincipalInfo;
+import org.apache.usergrid.security.AuthPrincipalType;
+import org.apache.usergrid.security.tokens.TokenInfo;
+import org.apache.usergrid.security.tokens.exceptions.BadTokenException;
+import org.apache.usergrid.security.tokens.exceptions.ExpiredTokenException;
+import org.apache.usergrid.utils.UUIDUtils;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.apache.commons.codec.binary.Base64.decodeBase64;
+
+/**
+ * Created by ayeshadastagiri on 6/22/16.
+ */
+public class ApigeeSSO2Provider implements ExternalSSOProvider {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ApigeeSSO2Provider.class);
+    private static final String RESPONSE_PUBLICKEY_VALUE = "value";
+    protected Properties properties;
+    protected ManagementService management;
+    protected Client client;
+    protected String publicKey;
+
+    public static final String USERGRID_EXTERNAL_PUBLICKEY_URL = 
"usergrid.external.sso.url";
+
+    public ApigeeSSO2Provider() {
+        ClientConfig clientConfig = new ClientConfig();
+        clientConfig.register(new JacksonFeature());
+        client = ClientBuilder.newClient(clientConfig);
+    }
+
+    private String getPublicKey() {
+        Map<String, Object> publicKey = 
client.target(properties.getProperty(USERGRID_EXTERNAL_PUBLICKEY_URL)).request().get(Map.class);
+        return 
publicKey.get(RESPONSE_PUBLICKEY_VALUE).toString().split("----\n")[1].split("\n---")[0];
+    }
+
+    @Override
+    public TokenInfo validateAndReturnTokenInfo(String token, long ttl) throws 
Exception {
+
+
+        UserInfo userInfo = validateAndReturnUserInfo(token, ttl);
+
+        if(userInfo == null){
+            throw new RuntimeException("Unable to load user from token: 
"+token);
+        }
+
+        return new TokenInfo(UUIDUtils.newTimeUUID(), "access", 1, 1, 1, ttl,
+                new AuthPrincipalInfo(AuthPrincipalType.ADMIN_USER, 
userInfo.getUuid(),
+                    CpNamingUtils.MANAGEMENT_APPLICATION_ID), null);
+
+    }
+
+    @Override
+    public UserInfo validateAndReturnUserInfo(String token, long ttl) throws 
Exception {
+
+        Jws<Claims> payload = getClaims(token);
+
+        // this step super important to ensure the token is a valid token
+        validateClaims(payload);
+
+        UserInfo userInfo = 
management.getAdminUserByEmail(payload.getBody().get("email").toString());
+
+        return userInfo;
+    }
+
+    @Override
+    public Map<String, String> getDecodedTokenDetails(String token) throws 
Exception {
+
+       Jws<Claims> jws = getClaims(token);
+
+        Claims claims = jws.getBody();
+        Map<String, String> tokenDetails = new HashMap<>();
+
+        tokenDetails.put("username", (String)claims.get("user_name"));
+        tokenDetails.put("email", (String)claims.get("email"));
+        tokenDetails.put("expiry", claims.get("exp").toString());
+
+
+        return tokenDetails;
+
+    }
+
+
+    private Jws<Claims> getClaims(String token) throws Exception{
+
+        Jws<Claims> claims = null;
+
+        if(publicKey == null){
+            throw new IllegalArgumentException("Public key must be provided 
with Apigee " +
+                "token in order to verify signature.");
+        }
+
+
+        byte[] publicBytes = decodeBase64(publicKey);
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey pubKey = keyFactory.generatePublic(keySpec);
+
+        try {
+            claims = Jwts.parser().setSigningKey(pubKey).parseClaimsJws(token);
+        } catch (SignatureException se) {
+            if(logger.isDebugEnabled()) {
+                logger.debug("Signature was invalid for Apigee JWT: {} and 
key: {}", token, publicKey);
+            }
+            throw new BadTokenException("Invalid Apigee SSO token signature");
+        } catch (MalformedJwtException me){
+            if(logger.isDebugEnabled()) {
+                logger.debug("Beginning JSON object section of Apigee JWT 
invalid for token: {}", token);
+            }
+            throw new BadTokenException("Malformed Apigee JWT");
+        } catch (ArrayIndexOutOfBoundsException aio){
+            if(logger.isDebugEnabled()) {
+                logger.debug("Signature section of Apigee JWT invalid for 
token: {}", token);
+            }
+            throw new BadTokenException("Malformed Apigee JWT");
+        }
+
+
+        return claims;
+    }
+
+    private void validateClaims (final Jws<Claims> claims) throws 
ExpiredTokenException {
+
+        final Claims body = claims.getBody();
+
+        final long expiry = Long.valueOf(body.get("exp").toString());
+
+        if(expiry - (System.currentTimeMillis()/1000) < 0 ){
+
+            final long expirationDelta = ((System.currentTimeMillis()/1000) - 
expiry)*1000;
+
+            throw new ExpiredTokenException(String.format("Token expired %d 
millisecons ago.", expirationDelta ));
+        }
+
+
+    }
+
+
+    @Autowired
+    public void setManagement(ManagementService management) {
+        this.management = management;
+    }
+
+    @Autowired
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+        this.publicKey = getPublicKey();
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/sso/ExternalSSOProvider.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/sso/ExternalSSOProvider.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/ExternalSSOProvider.java
new file mode 100644
index 0000000..eee46c6
--- /dev/null
+++ 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/ExternalSSOProvider.java
@@ -0,0 +1,22 @@
+package org.apache.usergrid.security.sso;
+
+import org.apache.usergrid.management.UserInfo;
+import org.apache.usergrid.security.tokens.TokenInfo;
+
+import java.util.Map;
+
+/**
+ * Created by ayeshadastagiri on 6/22/16.
+ */
+public interface ExternalSSOProvider {
+
+    /** Authenticate a userId and external token against this provider */
+    TokenInfo validateAndReturnTokenInfo(String token, long ttl) throws 
Exception;
+
+    /** Authenticate a userId and external token against this provider */
+    UserInfo validateAndReturnUserInfo(String token, long ttl) throws 
Exception;
+
+    /** Decode the token, if supported, and return any information encoded 
with the token */
+    Map<String, String> getDecodedTokenDetails(String token) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/sso/SSOProviderFactory.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/sso/SSOProviderFactory.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/SSOProviderFactory.java
new file mode 100644
index 0000000..fae0f7f
--- /dev/null
+++ 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/SSOProviderFactory.java
@@ -0,0 +1,57 @@
+package org.apache.usergrid.security.sso;
+
+import org.apache.usergrid.corepersistence.CpEntityManagerFactory;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.persistence.EntityManagerFactory;
+import org.apache.usergrid.security.tokens.cassandra.TokenServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Properties;
+
+/**
+ * Created by russo on 6/24/16.
+ */
+public class SSOProviderFactory {
+
+    enum Provider {
+        APIGEE, USERGRID
+    }
+
+    private EntityManagerFactory emf;
+    protected Properties properties;
+
+
+    public ExternalSSOProvider getProvider(){
+
+        final Provider configuredProvider;
+        try{
+            configuredProvider =
+                
Provider.valueOf(properties.getProperty(TokenServiceImpl.USERGRID_EXTERNAL_PROVIDER).toUpperCase());
+        }catch (IllegalArgumentException e){
+            throw new RuntimeException("Property 
usergrid.external.sso.provider must " +
+                "be configured when external SSO is enabled");
+        }
+
+        switch (configuredProvider){
+
+            case APIGEE:
+                return 
((CpEntityManagerFactory)emf).getApplicationContext().getBean( 
ApigeeSSO2Provider.class );
+            case USERGRID:
+                return 
((CpEntityManagerFactory)emf).getApplicationContext().getBean( 
UsergridExternalProvider.class );
+            default:
+                throw new RuntimeException("Unknown SSO provider");
+        }
+
+    }
+
+    @Autowired
+    public void setEntityManagerFactory( EntityManagerFactory emf ) {
+        this.emf = emf;
+    }
+
+
+    @Autowired
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/sso/UsergridExternalProvider.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/sso/UsergridExternalProvider.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/UsergridExternalProvider.java
new file mode 100644
index 0000000..a8d250d
--- /dev/null
+++ 
b/stack/services/src/main/java/org/apache/usergrid/security/sso/UsergridExternalProvider.java
@@ -0,0 +1,263 @@
+package org.apache.usergrid.security.sso;
+
+import com.codahale.metrics.Counter;
+import com.google.inject.Injector;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.usergrid.management.*;
+import org.apache.usergrid.persistence.core.metrics.MetricsFactory;
+import org.apache.usergrid.persistence.exceptions.EntityNotFoundException;
+import org.apache.usergrid.security.tokens.TokenInfo;
+import org.codehaus.jackson.JsonNode;
+import org.glassfish.jersey.apache.connector.ApacheClientProperties;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.MediaType;
+import java.util.*;
+
+/**
+ * Created by ayeshadastagiri on 6/23/16.
+ */
+public class UsergridExternalProvider implements ExternalSSOProvider {
+    private static final Logger logger = 
LoggerFactory.getLogger(ApigeeSSO2Provider.class);
+
+    private static final String SSO_PROCESSING_TIME = "sso.processing_time";
+    private static final String SSO_TOKENS_REJECTED = "sso.tokens_rejected";
+    private static final String SSO_TOKENS_VALIDATED = "sso.tokens_validated";
+    public static final String USERGRID_CENTRAL_URL = 
"usergrid.external.sso.publicKeyUrl";
+    public static final String CENTRAL_CONNECTION_POOL_SIZE = 
"usergrid.central.connection.pool.size";
+    public static final String CENTRAL_CONNECTION_TIMEOUT = 
"usergrid.central.connection.timeout";
+    public static final String CENTRAL_READ_TIMEOUT = 
"usergrid.central.read.timeout";
+    private static final String SSO_CREATED_LOCAL_ADMINS = 
"sso.created_local_admins";
+
+    protected ManagementService management;
+    protected MetricsFactory metricsFactory;
+    protected Properties properties;
+
+    private static Client jerseyClient = null;
+
+    @Autowired
+    private Injector injector;
+
+    @Autowired
+    private ApplicationCreator applicationCreator;
+
+    @Autowired
+    public void setManagement(ManagementService management) {
+        this.management = management;
+    }
+
+    @Autowired
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    @Autowired
+    public void setMetricFactory() {
+        this.metricsFactory = injector.getInstance(MetricsFactory.class);
+    }
+
+    MetricsFactory getMetricsFactory() {
+        return metricsFactory;
+    }
+
+    @Override
+    public TokenInfo validateAndReturnTokenInfo(String token, long ttl) throws 
Exception {
+        throw new UnsupportedOperationException("Returning user info not 
supported from external Usergrid SSO tokens");
+    }
+
+    @Override
+    public UserInfo validateAndReturnUserInfo(String token, long ttl) throws 
Exception {
+        if (token == null) {
+            throw new IllegalArgumentException("ext_access_token must be 
specified");
+        }
+        if (ttl == -1) {
+            throw new IllegalArgumentException("ttl must be specified");
+        }
+
+        com.codahale.metrics.Timer processingTimer = 
getMetricsFactory().getTimer(
+            UsergridExternalProvider.class, SSO_PROCESSING_TIME);
+
+        com.codahale.metrics.Timer.Context timerContext = 
processingTimer.time();
+
+        try {
+            // look up user via UG Central's /management/me endpoint.
+
+            JsonNode accessInfoNode = getMeFromUgCentral(token);
+
+            JsonNode userNode = accessInfoNode.get("user");
+
+            String username = userNode.get("username").asText();
+
+            // if user does not exist locally then we need to fix that
+
+            UserInfo userInfo = management.getAdminUserByUsername(username);
+            UUID userId = userInfo == null ? null : userInfo.getUuid();
+
+            if (userId == null) {
+
+                // create local user and and organizations they have on the 
central Usergrid instance
+                logger.info("User {} does not exist locally, creating", 
username);
+
+                String name = userNode.get("name").asText();
+                String email = userNode.get("email").asText();
+                String dummyPassword = 
RandomStringUtils.randomAlphanumeric(40);
+
+                JsonNode orgsNode = userNode.get("organizations");
+                Iterator<String> fieldNames = orgsNode.getFieldNames();
+
+                if (!fieldNames.hasNext()) {
+                    // no organizations for user exist in response from 
central Usergrid SSO
+                    // so create user's personal organization and use username 
as organization name
+                    fieldNames = 
Collections.singletonList(username).iterator();
+                }
+
+                // create user and any organizations that user is supposed to 
have
+
+                while (fieldNames.hasNext()) {
+
+                    String orgName = fieldNames.next();
+
+                    if (userId == null) {
+//
+                        // haven't created user yet so do that now
+                        OrganizationOwnerInfo ownerOrgInfo = 
management.createOwnerAndOrganization(
+                            orgName, username, name, email, dummyPassword, 
true, false);
+
+                        
applicationCreator.createSampleFor(ownerOrgInfo.getOrganization());
+
+                        userId = ownerOrgInfo.getOwner().getUuid();
+                        userInfo = ownerOrgInfo.getOwner();
+
+                        Counter createdAdminsCounter = 
getMetricsFactory().getCounter(
+                            UsergridExternalProvider.class, 
SSO_CREATED_LOCAL_ADMINS);
+                        createdAdminsCounter.inc();
+
+                        logger.info("Created user {} and org {}", username, 
orgName);
+
+                    } else {
+
+                        // already created user, so just create an org
+                        final OrganizationInfo organization =
+                            management.createOrganization(orgName, userInfo, 
true);
+
+                        applicationCreator.createSampleFor(organization);
+
+                        logger.info("Created user {}'s other org {}", 
username, orgName);
+                    }
+                }
+            }
+
+            return userInfo;
+        } catch (Exception e) {
+            timerContext.stop();
+            logger.debug("Error validating external token", e);
+            throw e;
+        }
+
+    }
+
+    @Override
+    public Map<String, String> getDecodedTokenDetails(String token) {
+
+        throw new UnsupportedOperationException("Not currently supported with 
Usergrid external tokens");
+
+    }
+
+    /**
+     * Look up Admin User via UG Central's /management/me endpoint.
+     *
+     * @param extAccessToken Access token issued by UG Central of Admin User
+     * @return JsonNode representation of AccessInfo object for Admin User
+     * @throws EntityNotFoundException if access_token is not valid.
+     */
+    private JsonNode getMeFromUgCentral(String extAccessToken) throws 
EntityNotFoundException {
+
+        // prepare to count tokens validated and rejected
+
+        Counter tokensRejectedCounter = getMetricsFactory().getCounter(
+            UsergridExternalProvider.class, SSO_TOKENS_REJECTED);
+        Counter tokensValidatedCounter = getMetricsFactory().getCounter(
+            UsergridExternalProvider.class, SSO_TOKENS_VALIDATED);
+
+        // create URL of central Usergrid's /management/me endpoint
+
+        String externalUrl = 
properties.getProperty(USERGRID_CENTRAL_URL).trim();
+
+        // be lenient about trailing slash
+        externalUrl = !externalUrl.endsWith("/") ? externalUrl + "/" : 
externalUrl;
+        String me = externalUrl + "management/me?access_token=" + 
extAccessToken;
+
+        // use our favorite HTTP client to GET /management/me
+
+        Client client = getJerseyClient();
+        final org.codehaus.jackson.JsonNode accessInfoNode;
+        try {
+            accessInfoNode = client.target(me).request()
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(org.codehaus.jackson.JsonNode.class);
+
+            tokensValidatedCounter.inc();
+
+        } catch (Exception e) {
+            // user not found 404
+            tokensRejectedCounter.inc();
+            String msg = "Cannot find Admin User associated with " + 
extAccessToken;
+            throw new EntityNotFoundException(msg, e);
+        }
+
+        return accessInfoNode;
+    }
+
+    private Client getJerseyClient() {
+
+        if (jerseyClient == null) {
+
+            synchronized (this) {
+
+                // create HTTPClient and with configured connection pool
+
+                int poolSize = 100; // connections
+                final String poolSizeStr = 
properties.getProperty(CENTRAL_CONNECTION_POOL_SIZE);
+                if (poolSizeStr != null) {
+                    poolSize = Integer.parseInt(poolSizeStr);
+                }
+
+                PoolingClientConnectionManager connectionManager = new 
PoolingClientConnectionManager();
+                connectionManager.setMaxTotal(poolSize);
+
+                int timeout = 20000; // ms
+                final String timeoutStr = 
properties.getProperty(CENTRAL_CONNECTION_TIMEOUT);
+                if (timeoutStr != null) {
+                    timeout = Integer.parseInt(timeoutStr);
+                }
+
+                int readTimeout = 20000; // ms
+                final String readTimeoutStr = 
properties.getProperty(CENTRAL_READ_TIMEOUT);
+                if (readTimeoutStr != null) {
+                    readTimeout = Integer.parseInt(readTimeoutStr);
+                }
+
+                ClientConfig clientConfig = new ClientConfig();
+                clientConfig.register(new JacksonFeature());
+                
clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, 
connectionManager);
+                clientConfig.connectorProvider(new ApacheConnectorProvider());
+
+                jerseyClient = ClientBuilder.newClient(clientConfig);
+                jerseyClient.property(ClientProperties.CONNECT_TIMEOUT, 
timeout);
+                jerseyClient.property(ClientProperties.READ_TIMEOUT, 
readTimeout);
+            }
+        }
+
+        return jerseyClient;
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
index eef7a1c..7beeb3b 100644
--- 
a/stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
+++ 
b/stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
@@ -32,14 +32,14 @@ import 
org.apache.usergrid.persistence.core.metrics.MetricsFactory;
 import org.apache.usergrid.persistence.entities.Application;
 import org.apache.usergrid.security.AuthPrincipalInfo;
 import org.apache.usergrid.security.AuthPrincipalType;
+import org.apache.usergrid.security.sso.SSOProviderFactory;
 import org.apache.usergrid.security.tokens.TokenCategory;
 import org.apache.usergrid.security.tokens.TokenInfo;
 import org.apache.usergrid.security.tokens.TokenService;
 import org.apache.usergrid.security.tokens.exceptions.BadTokenException;
 import org.apache.usergrid.security.tokens.exceptions.ExpiredTokenException;
 import org.apache.usergrid.security.tokens.exceptions.InvalidTokenException;
-import 
org.apache.usergrid.security.tokens.externalProviders.ApigeeSSO2Provider;
-import org.apache.usergrid.security.tokens.externalProviders.UsergridCentral;
+import org.apache.usergrid.security.sso.ExternalSSOProvider;
 import org.apache.usergrid.utils.ConversionUtils;
 import org.apache.usergrid.utils.JsonUtils;
 import org.apache.usergrid.utils.UUIDUtils;
@@ -316,29 +316,43 @@ public class TokenServiceImpl implements TokenService {
     @Override
     public TokenInfo getTokenInfo( String token, boolean updateAccessTime ) 
throws Exception {
 
-        UUID uuid = null;
+        UUID uuid;
+
+
+        /** Pre-validation of the token string based on Usergrid's encoding 
scheme.
+         *
+         * If the token does not parse out a UUID, then it's not a Usergrid 
token.  Check if External SSO provider
+         * is configured, which is not Usergrid and immediately try to 
validate the token based on this parsing
+         * information.
+         */
         try{
             uuid = getUUIDForToken( token );
         }
         catch(Exception e){
-            try{
-                return 
validateExternalToken(token,1,properties.getProperty(USERGRID_EXTERNAL_PROVIDER));
-            }
-            catch (Exception exception){
-                logger.debug("invalid request");
-                throw new IllegalArgumentException("Invalid token in the 
request.");
+
+            // If the token doesn't parse as a Usergrid token, see if an 
external provider other than Usergrid is
+            // enabled.  If so, just validate the external token.
+            if( isExternalSSOProviderEnabled() && 
!getExternalSSOProvider().equalsIgnoreCase("usergrid")) {
+                return validateExternalToken(token, 1, 
getExternalSSOProvider());
+            }else{
+                throw e; // re-throw the error
             }
+
         }
 
-        long ssoTtl = 1000000L; // TODO: property for this
+        final TokenInfo tokenInfo;
 
-        TokenInfo tokenInfo;
+        /**
+         * Now try actual Usergrid token validations.  First try locally.  If 
that fails and SSO is enabled with
+         * Usergrid being a provider, validate the external token.
+         */
         try {
             tokenInfo = getTokenInfo( uuid );
         } catch (InvalidTokenException e){
-            // now try from central sso
-            if ( isExternalSSOProviderEnabled() ){
-                return validateExternalToken( token, maxPersistenceTokenAge,"" 
);
+            // Try the request from Usergrid, conditions are specific so we 
don't incur perf hits for unncessary
+            // token validations that are known to not
+            if ( isExternalSSOProviderEnabled() && 
getExternalSSOProvider().equalsIgnoreCase("usergrid") ){
+                return validateExternalToken( token, maxPersistenceTokenAge, 
getExternalSSOProvider() );
             }else{
                 throw e; // re-throw the error
             }
@@ -767,6 +781,9 @@ public class TokenServiceImpl implements TokenService {
     @Autowired
     protected ManagementService management;
 
+    @Autowired
+    private SSOProviderFactory ssoProviderFactory;
+
     MetricsFactory getMetricsFactory() {
         return metricsFactory;
     }
@@ -776,6 +793,10 @@ public class TokenServiceImpl implements TokenService {
         return Boolean.valueOf(properties.getProperty( 
USERGRID_EXTERNAL_SSO_ENABLED ));
     }
 
+    private String getExternalSSOProvider(){
+        return properties.getProperty(USERGRID_EXTERNAL_PROVIDER);
+    }
+
     /**
      * <p>
      * Validates access token from other or "external" Usergrid system.
@@ -794,24 +815,25 @@ public class TokenServiceImpl implements TokenService {
      * @param ttl            Time to live for token.
      */
     public TokenInfo validateExternalToken(String extAccessToken, long ttl, 
String provider) throws Exception {
-        TokenInfo tokenInfo = null;
-        //todo: based on provider call the appropriate method.
-        if(provider.equalsIgnoreCase("apigee")){
-            ApigeeSSO2Provider apigeeProvider = 
((CpEntityManagerFactory)emf).getApplicationContext().getBean( 
ApigeeSSO2Provider.class );
-            return 
apigeeProvider.validateAndReturnTokenInfo(extAccessToken,ttl);
-        }
-        else if(provider.equalsIgnoreCase("usergridCentral")){
-            UsergridCentral ugCentralProvider = 
((CpEntityManagerFactory)emf).getApplicationContext().getBean( 
UsergridCentral.class );
-            UserInfo userinfo = 
ugCentralProvider.validateAndReturnUserInfo(extAccessToken,ttl);
 
-            // store the external access_token as if it were one of our own
+
+        ExternalSSOProvider ssoProvider = ssoProviderFactory.getProvider();
+
+        if(provider.equalsIgnoreCase("usergrid")){
+
+            UserInfo userinfo = 
ssoProvider.validateAndReturnUserInfo(extAccessToken,ttl);
+
+            // Store the external Usergrid access_token as if it were one of 
our own so we don't have to make the
+            // external HTTP validation call on subsequent requests
             importToken( extAccessToken, TokenCategory.ACCESS, null, new 
AuthPrincipalInfo(
                 ADMIN_USER, userinfo.getUuid(), 
CpNamingUtils.MANAGEMENT_APPLICATION_ID), null, ttl );
             return getTokenInfo( extAccessToken );
 
+        }else{
+
+            return ssoProvider.validateAndReturnTokenInfo(extAccessToken,ttl);
         }
-        //todo : what if the token info is null ?
-        return tokenInfo;
+
     }
 
 }

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/java/org/apache/usergrid/security/tokens/externalProviders/ApigeeSSO2Provider.java
----------------------------------------------------------------------
diff --git 
a/stack/services/src/main/java/org/apache/usergrid/security/tokens/externalProviders/ApigeeSSO2Provider.java
 
b/stack/services/src/main/java/org/apache/usergrid/security/tokens/externalProviders/ApigeeSSO2Provider.java
deleted file mode 100644
index 08ec781..0000000
--- 
a/stack/services/src/main/java/org/apache/usergrid/security/tokens/externalProviders/ApigeeSSO2Provider.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.apache.usergrid.security.tokens.externalProviders;
-
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jws;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureException;
-import org.apache.usergrid.corepersistence.util.CpNamingUtils;
-import org.apache.usergrid.management.ManagementService;
-import org.apache.usergrid.management.UserInfo;
-import org.apache.usergrid.security.AuthPrincipalInfo;
-import org.apache.usergrid.security.AuthPrincipalType;
-import org.apache.usergrid.security.tokens.TokenInfo;
-import org.apache.usergrid.utils.UUIDUtils;
-import org.glassfish.jersey.client.ClientConfig;
-import org.glassfish.jersey.jackson.JacksonFeature;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import java.security.KeyFactory;
-import java.security.PublicKey;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.apache.commons.codec.binary.Base64.decodeBase64;
-
-/**
- * Created by ayeshadastagiri on 6/22/16.
- */
-public class ApigeeSSO2Provider implements ExternalTokenProvider {
-
-    private static final Logger logger = 
LoggerFactory.getLogger(ApigeeSSO2Provider.class);
-    private static final String RESPONSE_PUBLICKEY_VALUE = "value";
-    protected Properties properties;
-    protected ManagementService management;
-    protected Client client;
-    protected String publicKey;
-
-    public static final String USERGRID_EXTERNAL_PUBLICKEY_URL = 
"usergrid.external.sso.publicKeyUrl";
-
-    public ApigeeSSO2Provider() {
-        ClientConfig clientConfig = new ClientConfig();
-        clientConfig.register(new JacksonFeature());
-        client = ClientBuilder.newClient(clientConfig);
-    }
-
-    private String getPublicKey() {
-        Map<String, Object> publicKey = 
client.target(properties.getProperty(USERGRID_EXTERNAL_PUBLICKEY_URL)).request().get(Map.class);
-        return 
publicKey.get(RESPONSE_PUBLICKEY_VALUE).toString().split("----\n")[1].split("\n---")[0];
-    }
-
-    @Override
-    public TokenInfo validateAndReturnTokenInfo(String token, long ttl) throws 
Exception {
-
-        try {
-            UserInfo userInfo = validateAndReturnUserInfo(token, ttl);
-            TokenInfo tokeninfo = new TokenInfo(UUIDUtils.newTimeUUID(), 
"access", 1, 1, 1, ttl,
-                new AuthPrincipalInfo(AuthPrincipalType.ADMIN_USER, 
userInfo.getUuid(),
-                    CpNamingUtils.MANAGEMENT_APPLICATION_ID), null);
-            return tokeninfo;
-        }
-        catch(Exception e){
-            logger.debug("Error construcing token info from userinfo");
-            e.printStackTrace();
-        }
-        return null;
-    }
-
-    @Override
-    public UserInfo validateAndReturnUserInfo(String token, long ttl) throws 
Exception {
-
-        byte[] publicBytes = decodeBase64(publicKey);
-        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        PublicKey pubKey = keyFactory.generatePublic(keySpec);
-        Jws<Claims> payload = null;
-        try {
-            payload = 
Jwts.parser().setSigningKey(pubKey).parseClaimsJws(token);
-            UserInfo userInfo = 
management.getAdminUserByEmail(payload.getBody().get("email").toString());
-            if (userInfo == null) {
-                throw new IllegalArgumentException("user " + 
payload.getBody().get("email").toString() + " doesnt exist");
-            }
-            return userInfo;
-        } catch (SignatureException se) {
-            logger.debug("Signature did not match.");
-            throw new IllegalArgumentException("Signature did not match for 
the token.");
-        } catch (Exception e) {
-            logger.debug("Error validating Apigee SSO2 token.");
-            e.printStackTrace();
-        }
-        return null;
-    }
-
-
-    @Autowired
-    public void setManagement(ManagementService management) {
-        this.management = management;
-    }
-
-    @Autowired
-    public void setProperties(Properties properties) {
-        this.properties = properties;
-        this.publicKey = getPublicKey();
-    }
-}

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7cf07b4e/stack/services/src/main/resources/usergrid-services-context.xml
----------------------------------------------------------------------
diff --git a/stack/services/src/main/resources/usergrid-services-context.xml 
b/stack/services/src/main/resources/usergrid-services-context.xml
index c80139c..a91524b 100644
--- a/stack/services/src/main/resources/usergrid-services-context.xml
+++ b/stack/services/src/main/resources/usergrid-services-context.xml
@@ -77,12 +77,12 @@
 
     <bean id="saltProvider" 
class="org.apache.usergrid.security.salt.NoOpSaltProvider"/>
 
-
-    <bean id="apigeeSSO2Provider" 
class="org.apache.usergrid.security.tokens.externalProviders.ApigeeSSO2Provider">
+    <!-- singletons for custom SSO providers if they are used -->
+    <bean id="apigeeSSO2Provider" 
class="org.apache.usergrid.security.sso.ApigeeSSO2Provider">
         <property name="management" ref="managementService" />
     </bean>
 
-    <bean id="usergridCentral" 
class="org.apache.usergrid.security.tokens.externalProviders.UsergridCentral">
+    <bean id="usergridExternalProvider" 
class="org.apache.usergrid.security.sso.UsergridExternalProvider">
         <property name="management" ref="managementService" />
     </bean>
 
@@ -100,11 +100,18 @@
         <constructor-arg ref="managementService"/>
     </bean>
 
+    <!-- sign in providers are for application users to have integration with 
FB,Ping,etc. -->
     <bean id="signInProviderFactory" 
class="org.apache.usergrid.security.providers.SignInProviderFactory">
         <property name="entityManagerFactory" ref="entityManagerFactory"/>
         <property name="managementService" ref="managementService"/>
     </bean>
 
+    <!-- sso providers are for admin user accounts integrated with SSO systems 
like Apigee or central UG -->
+    <bean id="ssoProviderFactory" 
class="org.apache.usergrid.security.sso.SSOProviderFactory">
+        <property name="entityManagerFactory" ref="entityManagerFactory"/>
+        <property name="properties" ref="properties"/>
+    </bean>
+
     <bean id="exportService" 
class="org.apache.usergrid.management.export.ExportServiceImpl">
         <property name="managementService" ref="managementService"/>
         <property name="emf" ref="entityManagerFactory"/>

Reply via email to