Repository: usergrid
Updated Branches:
  refs/heads/1.x 14d6cdf97 -> bbc5e1a05


Simplify Central SSO by eliminating the external token end-point 
https://issues.apache.org/jira/browse/USERGRID-1267


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

Branch: refs/heads/1.x
Commit: bd544367ad6f5aa397fa3ab50b560ebab9588726
Parents: 14d6cdf
Author: Dave Johnson <[email protected]>
Authored: Tue Feb 16 12:42:59 2016 -0500
Committer: Dave Johnson <[email protected]>
Committed: Tue Feb 16 12:42:59 2016 -0500

----------------------------------------------------------------------
 .../rest/management/ManagementResource.java     | 291 +----------------
 .../rest/management/ManagementResourceIT.java   |  33 +-
 stack/services/pom.xml                          |  21 +-
 .../usergrid/management/ManagementService.java  |   3 -
 .../cassandra/ManagementServiceImpl.java        |  36 +--
 .../tokens/cassandra/TokenServiceImpl.java      | 317 +++++++++++++++++--
 6 files changed, 333 insertions(+), 368 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/bd544367/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 46d6d6b..d71f45d 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
@@ -104,30 +104,15 @@ public class ManagementResource extends 
AbstractContextResource {
      *
      * /management/users/<user-name>/login
      * /management/users/<user-name>/password
-     * 
+     *
      */
 
     @Autowired
     private ApplicationCreator applicationCreator;
 
-    @Autowired
-    MetricsFactory metricsFactory;
-
-    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";
 
     public ManagementResource() {
         logger.info( "ManagementResource initialized" );
@@ -485,280 +470,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 = null;
-
-        Timer processingTimer = metricsFactory.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" ).getTextValue();
-
-            // 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" ).getTextValue();
-                String email = userNode.get( "email" ).getTextValue();
-                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 = 
metricsFactory.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.debug("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 = metricsFactory.getCounter(
-                ManagementResource.class, SSO_TOKENS_REJECTED );
-        Counter tokensValidatedCounter = metricsFactory.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.resource( me )
-                    .type( 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 );
-                }
-
-                MultiThreadedHttpConnectionManager cm = new 
MultiThreadedHttpConnectionManager();
-                HttpConnectionManagerParams cmParams = cm.getParams();
-                cmParams.setMaxTotalConnections( poolSize );
-                HttpClient httpClient = new HttpClient( cm );
-
-                // create Jersey Client using that HTTPClient and with 
configured timeouts
-
-                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 DefaultClientConfig();
-                clientConfig.getFeatures().put( 
JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE );
-                clientConfig.getProperties().put( 
ClientConfig.PROPERTY_CONNECT_TIMEOUT, timeout ); // ms
-                clientConfig.getProperties().put( 
ClientConfig.PROPERTY_READ_TIMEOUT, readTimeout ); // ms
-
-                ApacheHttpClientHandler handler = new ApacheHttpClientHandler( 
httpClient, clientConfig );
-                jerseyClient = new ApacheHttpClient( handler );
-
-            }
-        }
-
-        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/bd544367/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 cec172d..3a535c4 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
@@ -676,38 +676,20 @@ public class ManagementResourceIT extends AbstractRestIT {
                 .type( MediaType.APPLICATION_JSON_TYPE )
                 .post( props );
 
-        // attempt to validate the token, must be valid
+        // TODO: how do we unit test SSO now that we have no external token 
end-point?
 
-        JsonNode validatedNode = resource().path( "/management/externaltoken" )
-            .queryParam( "access_token", suToken ) // as superuser
-            .queryParam( "ext_access_token", accessToken )
-            .queryParam( "ttl", "1000" )
-            .get( JsonNode.class );
-        String validatedAccessToken = validatedNode.get( "access_token" 
).getTextValue();
-        assertEquals( accessToken, validatedAccessToken );
+        Map<String, String> payload = hashMap( "access_token", accessToken );
 
-        // attempt to validate an invalid token, must fail
-
-        try {
-            resource().path( "/management/externaltoken" )
-                .queryParam( "access_token", suToken ) // as superuser
-                .queryParam( "ext_access_token", "rubbish_token")
-                .queryParam( "ttl", "1000" )
-                .get( JsonNode.class );
-            fail("Validation should have failed");
-        } catch ( UniformInterfaceException actual ) {
-            assertEquals( 404, actual.getResponse().getStatus() );
-            String errorMsg = actual.getResponse().getEntity( JsonNode.class 
).get( "error_description" ).toString();
-            logger.error( "ERROR: " + errorMsg );
-            assertTrue( errorMsg.contains( "Cannot find Admin User" ) );
-        }
+        JsonNode node = resource().path( "/management/me" ).accept( 
MediaType.APPLICATION_JSON )
+                .type( MediaType.APPLICATION_JSON_TYPE ).post( JsonNode.class, 
payload );
 
+        logNode( node );
+        String token = node.get( "access_token" ).getTextValue();
 
+        assertNotNull( token );
 
         // TODO: how do we test the create new user and organization case?
 
-
-
         // unset the Usergrid Central SSO URL so it does not interfere with 
other tests
 
         props.put( USERGRID_CENTRAL_URL, "" );
@@ -716,7 +698,6 @@ public class ManagementResourceIT extends AbstractRestIT {
                 .accept( MediaType.APPLICATION_JSON )
                 .type( MediaType.APPLICATION_JSON_TYPE )
                 .post( props );
-
     }
 
 

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bd544367/stack/services/pom.xml
----------------------------------------------------------------------
diff --git a/stack/services/pom.xml b/stack/services/pom.xml
index 75fe4c3..f6edde5 100644
--- a/stack/services/pom.xml
+++ b/stack/services/pom.xml
@@ -463,7 +463,7 @@
       <!-- Testing and Logging Dependencies -->
     <dependency>
       <!--
-      Do not remove this slf4j-api dependency remove even though pulled 
+      Do not remove this slf4j-api dependency remove even though pulled
       in transitively. If not present IntelliJ IDEA wigs out.
       -->
       <groupId>org.slf4j</groupId>
@@ -565,6 +565,25 @@
       <scope>test</scope>
     </dependency>
 
+    <!-- needed for central ssso in TokenServiceImpl -->
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-client</artifactId>
+    </dependency>
+
+    <!-- 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>com.sun.jersey.contribs</groupId>
+      <artifactId>jersey-apache-client</artifactId>
+      <version>1.19</version>
+    </dependency>
 
   </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bd544367/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 0e0657c..4711a95 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
@@ -127,9 +127,6 @@ public interface ManagementService {
 
     public String getActivationTokenForOrganization( UUID organizationId, long 
ttl ) throws Exception;
 
-    /** Import an Admin User token generated by some other system */
-    public void importTokenForAdminUser( UUID userId, String token, long ttl ) 
throws Exception;
-
     public ServiceResults getAdminUserActivities( UserInfo user ) throws 
Exception;
 
     public ServiceResults getAdminUserActivity( UserInfo user ) throws 
Exception;

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bd544367/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 b5eaf25..5f95dea 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
@@ -1416,21 +1416,13 @@ public class ManagementServiceImpl implements 
ManagementService {
 
     @Override
     public String getAccessTokenForAdminUser( UUID userId, long duration ) 
throws Exception {
-
         return getTokenForPrincipal( ACCESS, null, MANAGEMENT_APPLICATION_ID, 
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, 
MANAGEMENT_APPLICATION_ID ), null, ttl );
-    }
-
-
-    /*
+  /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * 
org.apache.usergrid.management.ManagementService#revokeAccessTokensForAdminUser
    * (java.util.UUID)
@@ -1606,10 +1598,10 @@ public class ManagementServiceImpl implements 
ManagementService {
 
     @Override
     public void removeAdminUserFromOrganization( UUID userId, UUID 
organizationId ) throws Exception {
-        removeAdminUserFromOrganization( userId, organizationId, false );     
+        removeAdminUserFromOrganization( userId, organizationId, false );
     }
 
-    
+
     @Override
     public void removeAdminUserFromOrganization( UUID userId, UUID 
organizationId, boolean force ) throws Exception {
 
@@ -1620,10 +1612,10 @@ public class ManagementServiceImpl implements 
ManagementService {
         EntityManager em = emf.getEntityManager( MANAGEMENT_APPLICATION_ID );
 
         try {
-            int size = em.getCollection( new SimpleEntityRef( 
Group.ENTITY_TYPE, organizationId ), 
+            int size = em.getCollection( new SimpleEntityRef( 
Group.ENTITY_TYPE, organizationId ),
                     "users", null, 2, Level.IDS, false ).size();
-            
-            if ( !force && size <= 1 ) { 
+
+            if ( !force && size <= 1 ) {
                 throw new Exception();
             }
         }
@@ -1772,7 +1764,7 @@ public class ManagementServiceImpl implements 
ManagementService {
 
 
     /**
-     * Remove application from an organization. 
+     * Remove application from an organization.
      */
     @Override
     public void removeOrganizationApplication( UUID organizationId, UUID 
applicationId ) throws Exception {
@@ -1781,9 +1773,9 @@ public class ManagementServiceImpl implements 
ManagementService {
         }
 
         EntityManager em = emf.getEntityManager( MANAGEMENT_APPLICATION_ID );
-        em.deleteConnection( new ConnectionRefImpl(  
-            "group",          // String connectingEntityType 
-            organizationId,   // UUID connectingEntityId 
+        em.deleteConnection( new ConnectionRefImpl(
+            "group",          // String connectingEntityType
+            organizationId,   // UUID connectingEntityId
             "owns",           // String connectionType
             APPLICATION_INFO, // String connectedEntityType
             applicationId     // UUID connectedEntityId
@@ -2446,7 +2438,7 @@ public class ManagementServiceImpl implements 
ManagementService {
 
     /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * 
org.apache.usergrid.management.ManagementService#revokeAccessTokensForAappUser
    * (java.util.UUID, java.util.UUID)
@@ -2823,7 +2815,7 @@ public class ManagementServiceImpl implements 
ManagementService {
 
     /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * 
org.apache.usergrid.management.ManagementService#setOrganizationProps(java.util
    * .UUID, java.util.Map)
@@ -2846,7 +2838,7 @@ public class ManagementServiceImpl implements 
ManagementService {
 
     /*
    * (non-Javadoc)
-   * 
+   *
    * @see
    * 
org.apache.usergrid.management.ManagementService#getOrganizationProps(java.util
    * .UUID)

http://git-wip-us.apache.org/repos/asf/usergrid/blob/bd544367/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 7ea2af3..970e205 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,17 +17,29 @@
 package org.apache.usergrid.security.tokens.cassandra;
 
 
-import java.nio.ByteBuffer;
-import java.util.*;
-
-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 com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.api.json.JSONConfiguration;
+import com.sun.jersey.client.apache.ApacheHttpClient;
+import com.sun.jersey.client.apache.ApacheHttpClientHandler;
+import me.prettyprint.hector.api.Keyspace;
+import me.prettyprint.hector.api.beans.HColumn;
+import me.prettyprint.hector.api.mutation.Mutator;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.usergrid.exception.NotImplementedException;
+import org.apache.usergrid.management.*;
+import org.apache.usergrid.metrics.MetricsFactory;
 import org.apache.usergrid.persistence.EntityManagerFactory;
 import org.apache.usergrid.persistence.cassandra.CassandraService;
 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;
@@ -38,13 +50,18 @@ import 
org.apache.usergrid.security.tokens.exceptions.ExpiredTokenException;
 import org.apache.usergrid.security.tokens.exceptions.InvalidTokenException;
 import org.apache.usergrid.utils.JsonUtils;
 import org.apache.usergrid.utils.UUIDUtils;
+import org.codehaus.jackson.JsonNode;
+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.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;
@@ -53,20 +70,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 {
@@ -288,14 +298,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
@@ -491,7 +503,7 @@ public class TokenServiceImpl implements TokenService {
 
       /*
        * write to the PRINCIPAL+TOKEN The format is as follow
-       * 
+       *
        * appid+principalId+principalType :{ tokenuuid: 0x00}
        */
 
@@ -674,4 +686,257 @@ 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
+    MetricsFactory metricsFactory;
+
+    @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, 
CassandraService.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.resource( me )
+                    .type( 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 );
+                }
+
+                MultiThreadedHttpConnectionManager cm = new 
MultiThreadedHttpConnectionManager();
+                HttpConnectionManagerParams cmParams = cm.getParams();
+                cmParams.setMaxTotalConnections( poolSize );
+                HttpClient httpClient = new HttpClient( cm );
+
+                // create Jersey Client using that HTTPClient and with 
configured timeouts
+
+                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 DefaultClientConfig();
+                clientConfig.getFeatures().put( 
JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE );
+                clientConfig.getProperties().put( 
ClientConfig.PROPERTY_CONNECT_TIMEOUT, timeout ); // ms
+                clientConfig.getProperties().put( 
ClientConfig.PROPERTY_READ_TIMEOUT, readTimeout ); // ms
+
+                ApacheHttpClientHandler handler = new ApacheHttpClientHandler( 
httpClient, clientConfig );
+                jerseyClient = new ApacheHttpClient( handler );
+            }
+        }
+
+        return jerseyClient;
+    }
+
+
 }

Reply via email to