You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by mr...@apache.org on 2016/03/02 22:34:24 UTC
[1/3] usergrid git commit: Cherry-pick the SSO simplifications from
1.x -> master (eliminates the external token validation end-point).
Repository: usergrid
Updated Branches:
refs/heads/release-2.1.1 5dee6c8d4 -> e15cb12a3
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/7f93739e
Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/7f93739e
Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/7f93739e
Branch: refs/heads/release-2.1.1
Commit: 7f93739ee5c584685867847801cd73ad93bd0efc
Parents: 5dee6c8
Author: Dave Johnson <sn...@apache.org>
Authored: Tue Feb 16 12:42:59 2016 -0500
Committer: Michael Russo <mr...@apigee.com>
Committed: Wed Mar 2 12:33:40 2016 -0800
----------------------------------------------------------------------
.../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/7f93739e/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/7f93739e/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/7f93739e/stack/services/pom.xml
----------------------------------------------------------------------
diff --git a/stack/services/pom.xml b/stack/services/pom.xml
index c791721..c80c58c 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/7f93739e/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/7f93739e/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/7f93739e/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;
+
+ }
+
+
}
[2/3] usergrid git commit: Minor cleanups, plus use standard TTL.
Posted by mr...@apache.org.
Minor cleanups, plus use standard TTL.
Conflicts:
stack/rest/src/main/java/org/apache/usergrid/rest/management/ManagementResource.java
stack/rest/src/test/java/org/apache/usergrid/rest/management/ManagementResourceIT.java
stack/services/src/main/java/org/apache/usergrid/security/tokens/cassandra/TokenServiceImpl.java
Project: http://git-wip-us.apache.org/repos/asf/usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/usergrid/commit/8f588b97
Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/8f588b97
Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/8f588b97
Branch: refs/heads/release-2.1.1
Commit: 8f588b97736532de1747bd763d404ea9d8a0e72e
Parents: 7f93739
Author: Dave Johnson <sn...@apache.org>
Authored: Tue Feb 16 17:19:07 2016 -0500
Committer: Michael Russo <mr...@apigee.com>
Committed: Wed Mar 2 13:30:49 2016 -0800
----------------------------------------------------------------------
.../rest/management/ManagementResourceIT.java | 49 ++++++++++++++++++++
.../tokens/cassandra/TokenServiceImpl.java | 8 +---
2 files changed, 51 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/usergrid/blob/8f588b97/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 f29edcf..f05d4b6 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,6 +609,55 @@ 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 ) );
+
+ Map<String, Object> loginInfo = new HashMap<String, Object>() {{
+ put("username", username );
+ put("password", "password" );
+ put("grant_type", "password");
+ }};
+
+ JsonNode accessInfoNode = management.token()
+ .post( JsonNode.class, loginInfo );
+ String accessToken = accessInfoNode.get( "access_token" ).asText();
+
+ // 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 );
+
+
+ // TODO: how do we unit test SSO now that we have no external token end-point?
+
+
+ JsonNode node = pathResource("/management/me").get( JsonNode.class, new QueryParameters()
+ .addParam( "access_token", accessToken) );
+
+
+ logger.info( "node: {}", node );
+ String token = node.get( "access_token" ).asText();
+
+ 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, "" );
+ 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/8f588b97/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 8bbb60f..8792fa8 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
@@ -38,12 +38,10 @@ 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;
@@ -324,16 +322,14 @@ public class TokenServiceImpl implements TokenService {
UUID uuid = getUUIDForToken( token );
- long ssoTtl = 1000000L; // TODO: property for this
-
if ( uuid == null ) {
- return isSSOEnabled() ? validateExternalToken( token, ssoTtl ) : null;
+ return null;
}
TokenInfo tokenInfo = getTokenInfo( uuid );
if ( tokenInfo == null ) {
- return isSSOEnabled() ? validateExternalToken( token, ssoTtl ) : null;
+ return isSSOEnabled() ? validateExternalToken( token, maxPersistenceTokenAge ) : null;
}
//update the token
[3/3] usergrid git commit: Add try/catch to handle case when local
verification fails.
Posted by mr...@apache.org.
Add try/catch to handle case when local verification fails.
Project: http://git-wip-us.apache.org/repos/asf/usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/usergrid/commit/e15cb12a
Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/e15cb12a
Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/e15cb12a
Branch: refs/heads/release-2.1.1
Commit: e15cb12a3187ce3f373b213f364c6327e9e56818
Parents: 8f588b9
Author: Michael Russo <mi...@gmail.com>
Authored: Tue Feb 16 16:00:59 2016 -0800
Committer: Michael Russo <mr...@apigee.com>
Committed: Wed Mar 2 13:31:09 2016 -0800
----------------------------------------------------------------------
.../security/tokens/cassandra/TokenServiceImpl.java | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/usergrid/blob/e15cb12a/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 8792fa8..3f046e2 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
@@ -326,10 +326,16 @@ public class TokenServiceImpl implements TokenService {
return null;
}
- TokenInfo tokenInfo = getTokenInfo( uuid );
-
- if ( tokenInfo == null ) {
- return isSSOEnabled() ? validateExternalToken( token, maxPersistenceTokenAge ) : null;
+ TokenInfo tokenInfo;
+ try {
+ tokenInfo = getTokenInfo( uuid );
+ } catch (InvalidTokenException e){
+ // now try from central sso
+ if ( isSSOEnabled() ){
+ return validateExternalToken( token, maxPersistenceTokenAge );
+ }else{
+ throw e; // re-throw the error
+ }
}
//update the token