usergrid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From snoopd...@apache.org
Subject usergrid git commit: Cherry-pick the SSO simplifications from 1.x -> master (eliminates the external token validation end-point).
Date Wed, 02 Mar 2016 19:34:01 GMT
Repository: usergrid
Updated Branches:
  refs/heads/master fafaa4e74 -> bf3d4263f


Cherry-pick the SSO simplifications from 1.x -> master (eliminates the external token validation
end-point).


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

Branch: refs/heads/master
Commit: bf3d4263fd94be0dd8f4fff67d85adfa8e4137be
Parents: fafaa4e
Author: Dave Johnson <snoopdave@apache.org>
Authored: Tue Feb 16 12:42:59 2016 -0500
Committer: Dave Johnson <snoopdave@apache.org>
Committed: Wed Mar 2 14:33:58 2016 -0500

----------------------------------------------------------------------
 .../rest/management/ManagementResource.java     | 308 ------------------
 .../rest/management/ManagementResourceIT.java   |  71 ----
 stack/services/pom.xml                          |  17 +
 .../usergrid/management/ManagementService.java  |   7 +-
 .../cassandra/ManagementServiceImpl.java        |  22 +-
 .../tokens/cassandra/TokenServiceImpl.java      | 322 +++++++++++++++++--
 6 files changed, 325 insertions(+), 422 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/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 035cf69..f6aa001 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
@@ -17,39 +17,24 @@
 package org.apache.usergrid.rest.management;
 
 
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Timer;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.google.inject.Injector;
 import org.apache.amber.oauth2.common.error.OAuthError;
 import org.apache.amber.oauth2.common.exception.OAuthProblemException;
 import org.apache.amber.oauth2.common.message.OAuthResponse;
 import org.apache.amber.oauth2.common.message.types.GrantType;
-import org.apache.commons.lang.RandomStringUtils;
 import org.apache.commons.lang.StringUtils;
-import org.apache.http.impl.conn.PoolingClientConnectionManager;
 import org.apache.shiro.codec.Base64;
-import org.apache.usergrid.exception.NotImplementedException;
 import org.apache.usergrid.management.ApplicationCreator;
-import org.apache.usergrid.management.OrganizationInfo;
-import org.apache.usergrid.management.OrganizationOwnerInfo;
 import org.apache.usergrid.management.UserInfo;
 import org.apache.usergrid.management.exceptions.DisabledAdminUserException;
 import org.apache.usergrid.management.exceptions.UnactivatedAdminUserException;
 import org.apache.usergrid.management.exceptions.UnconfirmedAdminUserException;
 import org.apache.usergrid.persistence.core.metrics.MetricsFactory;
-import org.apache.usergrid.persistence.exceptions.EntityNotFoundException;
 import org.apache.usergrid.rest.AbstractContextResource;
 import org.apache.usergrid.rest.exceptions.RedirectionException;
 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.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.glassfish.jersey.server.mvc.Viewable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -58,17 +43,12 @@ import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 
 import javax.ws.rs.*;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.UUID;
 
 import static javax.servlet.http.HttpServletResponse.*;
 import static javax.ws.rs.core.MediaType.*;
@@ -106,25 +86,9 @@ public class ManagementResource extends AbstractContextResource {
     @Autowired
     private ApplicationCreator applicationCreator;
 
-    @Autowired
-    Injector injector;
-
-
-    private static Client jerseyClient = null;
-
-
-    // names for metrics to be collected
-    private static final String SSO_TOKENS_REJECTED = "sso.tokens_rejected";
-    private static final String SSO_TOKENS_VALIDATED = "sso.tokens_validated";
-    private static final String SSO_CREATED_LOCAL_ADMINS = "sso.created_local_admins";
-    private static final String SSO_PROCESSING_TIME = "sso.processing_time";
-
     // usergrid configuration property names needed
     public static final String USERGRID_SYSADMIN_LOGIN_NAME = "usergrid.sysadmin.login.name";
     public static final String USERGRID_CENTRAL_URL =         "usergrid.central.url";
-    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";
 
     MetricsFactory metricsFactory = null;
 
@@ -509,278 +473,6 @@ public class ManagementResource extends AbstractContextResource {
 
 
     /**
-     * <p>
-     * Allows call to validateExternalToken() (see below) with a POST of a JSON object.
-     * </p>
-     *
-     * @param ui             Information about calling URI.
-     * @param json           JSON object with fields: ext_access_token, ttl
-     * @param callback       For JSONP support.
-     * @return               Returns JSON object with access_token field.
-     * @throws Exception     Returns 401 if access token cannot be validated
-     */
-    @POST
-    @Path( "/externaltoken" )
-    public Response validateExternalToken(
-            @Context UriInfo ui,
-            Map<String, Object> json,
-            @QueryParam( "callback" ) @DefaultValue( "" ) String callback )  throws Exception
{
-
-        if ( StringUtils.isEmpty( properties.getProperty( USERGRID_CENTRAL_URL ))) {
-            throw new NotImplementedException( "External Token Validation Service is not
configured" );
-        }
-
-        Object extAccessTokenObj = json.get( "ext_access_token" );
-        if ( extAccessTokenObj == null ) {
-            throw new IllegalArgumentException("ext_access_token must be specified");
-        }
-        String extAccessToken = json.get("ext_access_token").toString();
-
-        Object ttlObj = json.get( "ttl" );
-        if ( ttlObj == null ) {
-            throw new IllegalArgumentException("ttl must be specified");
-        }
-        long ttl;
-        try {
-            ttl = Long.parseLong(ttlObj.toString());
-        } catch ( NumberFormatException e ) {
-            throw new IllegalArgumentException("ttl must be specified as a long");
-        }
-
-        return validateExternalToken( ui, extAccessToken, ttl, callback );
-    }
-
-
-    /**
-     * <p>
-     * Validates access token from other or "external" Usergrid system.
-     * Calls other system's /management/me endpoint to get the User
-     * associated with the access token. If user does not exist locally,
-     * then user and organizations will be created. If no user is returned
-     * from the other cluster, then this endpoint will return 401.
-     * </p>
-     *
-     * <p> Part of Usergrid Central SSO feature.
-     * See <a href="https://issues.apache.org/jira/browse/USERGRID-567">USERGRID-567</a>
-     * for details about Usergrid Central SSO.
-     * </p>
-     *
-     * @param ui             Information about calling URI.
-     * @param extAccessToken Access token from external Usergrid system.
-     * @param ttl            Time to live for token.
-     * @param callback       For JSONP support.
-     * @return               Returns JSON object with access_token field.
-     * @throws Exception     Returns 401 if access token cannot be validated
-     */
-    @GET
-    @Path( "/externaltoken" )
-    public Response validateExternalToken(
-                                @Context UriInfo ui,
-                                @QueryParam( "ext_access_token" ) String extAccessToken,
-                                @QueryParam( "ttl" ) @DefaultValue("-1") long ttl,
-                                @QueryParam( "callback" ) @DefaultValue( "" ) String callback
)
-            throws Exception {
-
-
-        if ( StringUtils.isEmpty( properties.getProperty( USERGRID_CENTRAL_URL ))) {
-            throw new NotImplementedException( "External Token Validation Service is not
configured" );
-        }
-
-        if ( extAccessToken == null ) {
-            throw new IllegalArgumentException("ext_access_token must be specified");
-        }
-
-        if ( ttl == -1 ) {
-            throw new IllegalArgumentException("ttl must be specified");
-        }
-        AccessInfo accessInfo;
-
-        Timer processingTimer = getMetricsFactory().getTimer(
-            ManagementResource.class, SSO_PROCESSING_TIME );
-
-        Timer.Context timerContext = processingTimer.time();
-
-        try {
-            // look up user via UG Central's /management/me endpoint.
-
-            JsonNode accessInfoNode = getMeFromUgCentral( extAccessToken );
-
-            JsonNode userNode = accessInfoNode.get( "user" );
-            String username = userNode.get( "username" ).textValue();
-
-            // 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" ).textValue();
-                String email = userNode.get( "email" ).textValue();
-                String dummyPassword = RandomStringUtils.randomAlphanumeric( 40 );
-
-                JsonNode orgsNode = userNode.get( "organizations" );
-                Iterator<String> fieldNames = orgsNode.fieldNames();
-
-                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(
-                            ManagementResource.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
);
-                    }
-                }
-
-            }
-
-            // store the external access_token as if it were one of our own
-            management.importTokenForAdminUser( userId, extAccessToken, ttl );
-
-            // success! return JSON object with access_token field
-            accessInfo = new AccessInfo()
-                    .withExpiresIn( tokens.getMaxTokenAgeInSeconds( extAccessToken ) )
-                    .withAccessToken( extAccessToken );
-
-        } catch (Exception e) {
-            timerContext.stop();
-            logger.error("Error validating external token", e);
-            throw e;
-        }
-
-        final Response response = Response.status( SC_OK )
-            .type( jsonMediaType( callback ) ).entity( accessInfo ).build();
-
-        timerContext.stop();
-
-        return response;
-    }
-
-    /**
-     * 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(
-            ManagementResource.class, SSO_TOKENS_REJECTED );
-        Counter tokensValidatedCounter = getMetricsFactory().getCounter(
-            ManagementResource.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 JsonNode accessInfoNode;
-        try {
-            accessInfoNode = client.target( me ).request()
-                    .accept( MediaType.APPLICATION_JSON_TYPE )
-                    .get(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;
-    }
-
-
-    /**
      * Check that authentication is allowed. If external token validation is enabled (Central
Usergrid SSO)
      * then only superusers should be allowed to login directly to this Usergrid instance.
      */

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
b/stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
index c54fa0c..f29edcf 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
@@ -609,77 +609,6 @@ public class ManagementResourceIT extends AbstractRestIT {
 
 
     @Test
-    public void testValidateExternalToken() throws Exception {
-
-        // create a new admin user, get access token
-
-        String rand = RandomStringUtils.randomAlphanumeric(10);
-        final String username = "user_" + rand;
-        management().orgs().post(
-            new Organization( username, username, username+"@example.com", username, "password",
null ) );
-
-        refreshIndex();
-
-        refreshIndex();
-        QueryParameters queryParams = new QueryParameters()
-            .addParam( "username", username )
-            .addParam( "password", "password" )
-            .addParam( "grant_type", "password" );
-        Token accessInfoNode = management.token().get(queryParams);
-        String accessToken = accessInfoNode.getAccessToken();
-
-        // set the Usergrid Central SSO URL because Tomcat port is dynamically assigned
-
-        String suToken = clientSetup.getSuperuserToken().getAccessToken();
-        Map<String, String> props = new HashMap<String, String>();
-        props.put( USERGRID_CENTRAL_URL, getBaseURI().toURL().toExternalForm() );
-        pathResource( "testproperties" ).post( props );
-
-        try {
-
-            // attempt to validate the token, must be valid
-            queryParams = new QueryParameters()
-                .addParam( "ext_access_token", accessToken )
-                .addParam( "ttl", "1000" );
-
-            Entity validatedNode = management.externaltoken().get( Entity.class, queryParams
);
-            String validatedAccessToken = validatedNode.get( "access_token" ).toString();
-            assertEquals( accessToken, validatedAccessToken );
-
-            // attempt to validate an invalid token, must fail
-
-            try {
-                queryParams = new QueryParameters()
-                    .addParam( "access_token", suToken )
-                    .addParam( "ext_access_token", "rubbish_token" )
-                    .addParam( "ttl", "1000" );
-
-                validatedNode = management.externaltoken().get( Entity.class, queryParams
);
-
-                fail( "Validation should have failed" );
-
-            } catch (ClientErrorException actual) {
-                assertEquals( 404, actual.getResponse().getStatus() );
-                String errorMsg = actual.getResponse().readEntity( JsonNode.class )
-                    .get( "error_description" ).toString();
-                logger.error( "ERROR: " + errorMsg );
-                assertTrue( errorMsg.contains( "Cannot find Admin User" ) );
-            }
-
-            // TODO: how do we test the create new user and organization case?
-
-        } finally {
-
-            // unset the Usergrid Central SSO URL so it does not interfere with other tests
-
-            props.put( USERGRID_CENTRAL_URL, "" );
-            pathResource( "testproperties" ).post( props );
-        }
-
-    }
-
-
-    @Test
     public void testSuperuserOnlyWhenValidateExternalTokensEnabled() throws Exception {
 
         // create an org and an admin user

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/stack/services/pom.xml
----------------------------------------------------------------------
diff --git a/stack/services/pom.xml b/stack/services/pom.xml
index 522d326..06ae5d8 100644
--- a/stack/services/pom.xml
+++ b/stack/services/pom.xml
@@ -446,6 +446,23 @@
             <artifactId>java-wns</artifactId>
             <version>1.3</version>
         </dependency>
+
+        <!-- needed for central ssso in TokenServiceImpl -->
+
+        <!-- needed for central ssso in TokenServiceImpl -->
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.1</version>
+        </dependency>
+
+        <!-- needed for central ssso in TokenServiceImpl -->
+        <dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-apache-connector</artifactId>
+            <version>2.21</version>
+        </dependency>
+
     </dependencies>
 
     <!--

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
index 2f2d10f..1d74ec3 100644
--- a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
+++ b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
@@ -139,9 +139,6 @@ public interface ManagementService {
 
 	String getActivationTokenForOrganization( UUID organizationId, long ttl ) throws Exception;
 
-	/** Import an Admin User token generated by some other system */
-	void importTokenForAdminUser( UUID userId, String token, long ttl ) throws Exception;
-
 	ServiceResults getAdminUserActivities( UserInfo user ) throws Exception;
 
 	ServiceResults getAdminUserActivity( UserInfo user ) throws Exception;
@@ -174,7 +171,9 @@ public interface ManagementService {
 
 	ApplicationInfo getApplicationInfo( Identifier id ) throws Exception;
 
-	ApplicationInfo getApplicationInfoFromAccessToken( String token ) throws Exception;
+    void removeAdminUserFromOrganization( UUID userId, UUID organizationId, boolean force
) throws Exception;
+
+    ApplicationInfo getApplicationInfoFromAccessToken( String token ) throws Exception;
 
 	ServiceResults getApplicationMetadata( UUID applicationId ) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
index 622cecb..65f4744 100644
--- a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
+++ b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
@@ -1476,19 +1476,11 @@ public class ManagementServiceImpl implements ManagementService {
 
     @Override
     public String getAccessTokenForAdminUser( UUID userId, long duration ) throws Exception
{
-
         return getTokenForPrincipal( ACCESS, null, smf.getManagementAppId(), ADMIN_USER,
userId, duration );
     }
 
 
-    @Override
-    public void importTokenForAdminUser(UUID userId, String token, long ttl) throws Exception
{
-        tokens.importToken( token, TokenCategory.ACCESS, null,
-                new AuthPrincipalInfo( ADMIN_USER, userId, smf.getManagementAppId() ), null,
ttl );
-    }
-
-
-    /*
+  /*
    * (non-Javadoc)
    *
    * @see
@@ -1676,6 +1668,12 @@ public class ManagementServiceImpl implements ManagementService {
 
     @Override
     public void removeAdminUserFromOrganization( UUID userId, UUID organizationId ) throws
Exception {
+        removeAdminUserFromOrganization( userId, organizationId, false );
+    }
+
+
+    @Override
+    public void removeAdminUserFromOrganization( UUID userId, UUID organizationId, boolean
force ) throws Exception {
 
         if ( ( userId == null ) || ( organizationId == null ) ) {
             return;
@@ -1684,8 +1682,10 @@ public class ManagementServiceImpl implements ManagementService {
         EntityManager em = emf.getEntityManager( smf.getManagementAppId() );
 
         try {
-            if ( em.getCollection( new SimpleEntityRef( Group.ENTITY_TYPE, organizationId
), "users", null, 2,
-                    Level.IDS, false ).size() <= 1 ) {
+            int size = em.getCollection( new SimpleEntityRef( Group.ENTITY_TYPE, organizationId
),
+                    "users", null, 2, Level.IDS, false ).size();
+
+            if ( !force && size <= 1 ) {
                 throw new Exception();
             }
         }

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bf3d4263/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 c0729ff..8bbb60f 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
@@ -17,35 +17,55 @@
 package org.apache.usergrid.security.tokens.cassandra;
 
 
-import java.nio.ByteBuffer;
-import java.util.*;
-
-import org.apache.usergrid.utils.ConversionUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.util.Assert;
+import com.codahale.metrics.Counter;
+import com.google.inject.Injector;
+import me.prettyprint.hector.api.Keyspace;
+import me.prettyprint.hector.api.beans.HColumn;
+import me.prettyprint.hector.api.mutation.Mutator;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.usergrid.corepersistence.CpEntityManagerFactory;
+import org.apache.usergrid.corepersistence.util.CpNamingUtils;
+import org.apache.usergrid.exception.NotImplementedException;
+import org.apache.usergrid.management.*;
 import org.apache.usergrid.persistence.EntityManagerFactory;
 import org.apache.usergrid.persistence.cassandra.CassandraService;
+import org.apache.usergrid.persistence.core.metrics.MetricsFactory;
 import org.apache.usergrid.persistence.entities.Application;
+import org.apache.usergrid.persistence.exceptions.EntityNotFoundException;
 import org.apache.usergrid.security.AuthPrincipalInfo;
 import org.apache.usergrid.security.AuthPrincipalType;
 import org.apache.usergrid.security.tokens.TokenCategory;
 import org.apache.usergrid.security.tokens.TokenInfo;
+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.services.ServiceManager;
+import org.apache.usergrid.utils.ConversionUtils;
 import org.apache.usergrid.utils.JsonUtils;
 import org.apache.usergrid.utils.UUIDUtils;
+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 org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.util.Assert;
 
-import me.prettyprint.hector.api.Keyspace;
-import me.prettyprint.hector.api.beans.HColumn;
-import me.prettyprint.hector.api.mutation.Mutator;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.MediaType;
+import java.nio.ByteBuffer;
+import java.util.*;
 
 import static java.lang.System.currentTimeMillis;
-
 import static me.prettyprint.hector.api.factory.HFactory.createColumn;
 import static me.prettyprint.hector.api.factory.HFactory.createMutator;
 import static org.apache.commons.codec.binary.Base64.decodeBase64;
@@ -54,20 +74,13 @@ import static org.apache.commons.codec.digest.DigestUtils.sha;
 import static org.apache.usergrid.persistence.cassandra.CassandraPersistenceUtils.getColumnMap;
 import static org.apache.usergrid.persistence.cassandra.CassandraService.PRINCIPAL_TOKEN_CF;
 import static org.apache.usergrid.persistence.cassandra.CassandraService.TOKENS_CF;
-import static org.apache.usergrid.security.tokens.TokenCategory.ACCESS;
-import static org.apache.usergrid.security.tokens.TokenCategory.EMAIL;
-import static org.apache.usergrid.security.tokens.TokenCategory.OFFLINE;
-import static org.apache.usergrid.security.tokens.TokenCategory.REFRESH;
-import static org.apache.usergrid.utils.ConversionUtils.HOLDER;
-import static org.apache.usergrid.utils.ConversionUtils.bytebuffer;
-import static org.apache.usergrid.utils.ConversionUtils.bytes;
-import static org.apache.usergrid.utils.ConversionUtils.getLong;
-import static org.apache.usergrid.utils.ConversionUtils.string;
-import static org.apache.usergrid.utils.ConversionUtils.uuid;
+import static org.apache.usergrid.persistence.cassandra.Serializers.*;
+import static org.apache.usergrid.security.AuthPrincipalType.ADMIN_USER;
+import static org.apache.usergrid.security.tokens.TokenCategory.*;
+import static org.apache.usergrid.utils.ConversionUtils.*;
 import static org.apache.usergrid.utils.MapUtils.hasKeys;
 import static org.apache.usergrid.utils.MapUtils.hashMap;
 import static org.apache.usergrid.utils.UUIDUtils.getTimestampInMillis;
-import static org.apache.usergrid.persistence.cassandra.Serializers.*;
 
 
 public class TokenServiceImpl implements TokenService {
@@ -155,9 +168,10 @@ public class TokenServiceImpl implements TokenService {
 
     protected EntityManagerFactory emf;
 
+    protected MetricsFactory metricsFactory;
 
-    public TokenServiceImpl() {
 
+    public TokenServiceImpl() {
     }
 
 
@@ -310,14 +324,16 @@ public class TokenServiceImpl implements TokenService {
 
         UUID uuid = getUUIDForToken( token );
 
+        long ssoTtl = 1000000L; // TODO: property for this
+
         if ( uuid == null ) {
-            return null;
+            return isSSOEnabled() ? validateExternalToken( token, ssoTtl ) : null;
         }
 
         TokenInfo tokenInfo = getTokenInfo( uuid );
 
         if ( tokenInfo == null ) {
-            return null;
+            return isSSOEnabled() ? validateExternalToken( token, ssoTtl ) : null;
         }
 
         //update the token
@@ -573,7 +589,7 @@ public class TokenServiceImpl implements TokenService {
     }
 
 
-    private UUID getUUIDForToken( String token ) throws ExpiredTokenException, BadTokenException
{
+    private UUID getUUIDForToken(String token ) throws ExpiredTokenException, BadTokenException
{
         TokenCategory tokenCategory = TokenCategory.getFromBase64String( token );
         byte[] bytes = decodeBase64( token.substring( TokenCategory.BASE64_PREFIX_LENGTH
) );
         UUID uuid = uuid( bytes );
@@ -651,6 +667,8 @@ public class TokenServiceImpl implements TokenService {
     @Autowired
     public void setEntityManagerFactory( EntityManagerFactory emf ) {
         this.emf = emf;
+        final Injector injector = ((CpEntityManagerFactory)emf).getApplicationContext().getBean(
Injector.class );
+        metricsFactory = injector.getInstance(MetricsFactory.class);
     }
 
 
@@ -707,4 +725,252 @@ public class TokenServiceImpl implements TokenService {
 
 
     private static final int MAX_TTL = 20 * 365 * 24 * 60 * 60;
+
+
+    //-------------------------------------------------------------------------------------------------------
+    //
+    // Central SSO implementation
+
+    public static final String USERGRID_CENTRAL_URL =         "usergrid.central.url";
+    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";
+
+    // names for metrics to be collected
+    private static final String SSO_TOKENS_REJECTED =         "sso.tokens_rejected";
+    private static final String SSO_TOKENS_VALIDATED =        "sso.tokens_validated";
+    private static final String SSO_CREATED_LOCAL_ADMINS =    "sso.created_local_admins";
+    private static final String SSO_PROCESSING_TIME =         "sso.processing_time";
+
+    private static Client jerseyClient = null;
+
+    @Autowired
+    private ApplicationCreator applicationCreator;
+
+    @Autowired
+    protected ManagementService management;
+
+    MetricsFactory getMetricsFactory() {
+        return metricsFactory;
+    }
+
+    private boolean isSSOEnabled() {
+        return !StringUtils.isEmpty( properties.getProperty( USERGRID_CENTRAL_URL ));
+    }
+
+
+    /**
+     * <p>
+     * Validates access token from other or "external" Usergrid system.
+     * Calls other system's /management/me endpoint to get the User
+     * associated with the access token. If user does not exist locally,
+     * then user and organizations will be created. If no user is returned
+     * from the other cluster, then return null.
+     * </p>
+     * <p/>
+     * <p> Part of Usergrid Central SSO feature.
+     * See <a href="https://issues.apache.org/jira/browse/USERGRID-567">USERGRID-567</a>
+     * for details about Usergrid Central SSO.
+     * </p>
+     *
+     * @param extAccessToken Access token from external Usergrid system.
+     * @param ttl            Time to live for token.
+     */
+    public TokenInfo validateExternalToken(String extAccessToken, long ttl) throws Exception
{
+
+        TokenInfo tokenInfo = null;
+
+        if (!isSSOEnabled()) {
+            throw new NotImplementedException( "External Token Validation Service not enabled"
);
+        }
+
+        if (extAccessToken == 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(
+            TokenServiceImpl.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( extAccessToken );
+
+            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(
+                            TokenServiceImpl.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
);
+                    }
+                }
+            }
+
+            // store the external access_token as if it were one of our own
+            importToken( extAccessToken, TokenCategory.ACCESS, null, new AuthPrincipalInfo(
+                ADMIN_USER, userId, CpNamingUtils.MANAGEMENT_APPLICATION_ID), null, ttl );
+
+            tokenInfo = getTokenInfo( extAccessToken );
+
+        } catch (Exception e) {
+            timerContext.stop();
+            logger.debug( "Error validating external token", e );
+            throw e;
+        }
+
+        return tokenInfo;
+    }
+
+
+    /**
+     * 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(
+            TokenServiceImpl.class, SSO_TOKENS_REJECTED );
+        Counter tokensValidatedCounter = getMetricsFactory().getCounter(
+            TokenServiceImpl.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 JsonNode accessInfoNode;
+        try {
+            accessInfoNode = client.target( me ).request()
+                .accept( MediaType.APPLICATION_JSON_TYPE )
+                .get(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;
+
+    }
+
+
 }


Mime
View raw message