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/10/28 19:39:43 UTC

[1/5] usergrid git commit: Add missing bad_confirmation_token.jsp

Repository: usergrid
Updated Branches:
  refs/heads/hotfix-20160819 a4cc63f8c -> 406a742d2


Add missing bad_confirmation_token.jsp


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

Branch: refs/heads/hotfix-20160819
Commit: 89218501a838d90b980cc0037ee879ac0bd80189
Parents: a4cc63f
Author: Dave Johnson <sn...@apache.org>
Authored: Thu Oct 20 16:43:30 2016 -0400
Committer: Michael Russo <mr...@apigee.com>
Committed: Fri Oct 28 12:20:24 2016 -0700

----------------------------------------------------------------------
 .../UserResource/bad_confirmation_token.jsp     | 33 ++++++++++++++++++++
 1 file changed, 33 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/89218501/stack/rest/src/main/webapp/WEB-INF/jsp/org/apache/usergrid/rest/management/users/UserResource/bad_confirmation_token.jsp
----------------------------------------------------------------------
diff --git a/stack/rest/src/main/webapp/WEB-INF/jsp/org/apache/usergrid/rest/management/users/UserResource/bad_confirmation_token.jsp b/stack/rest/src/main/webapp/WEB-INF/jsp/org/apache/usergrid/rest/management/users/UserResource/bad_confirmation_token.jsp
new file mode 100644
index 0000000..ecd74b8
--- /dev/null
+++ b/stack/rest/src/main/webapp/WEB-INF/jsp/org/apache/usergrid/rest/management/users/UserResource/bad_confirmation_token.jsp
@@ -0,0 +1,33 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+	pageEncoding="ISO-8859-1"%>
+<%@ page import="org.apache.usergrid.rest.AbstractContextResource"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+	<title>Error</title>
+	<link rel="stylesheet" type="text/css" href="/css/styles.css" />
+</head>
+<body>
+
+	<p>Confirmation token invalid or expired</p>
+
+</body>
+</html>


[3/5] usergrid git commit: If claims cannot be parsed, fetch new JWT token

Posted by mr...@apache.org.
If claims cannot be parsed, fetch new JWT token


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

Branch: refs/heads/hotfix-20160819
Commit: e0eb11c7dbae05d568ecc9ad9b524a472bfddb0c
Parents: 73de903
Author: Dave Johnson <sn...@apache.org>
Authored: Mon Oct 24 14:53:54 2016 -0400
Committer: Michael Russo <mr...@apigee.com>
Committed: Fri Oct 28 12:21:24 2016 -0700

----------------------------------------------------------------------
 .../security/sso/ApigeeSSO2Provider.java        | 55 +++++++++++---------
 1 file changed, 29 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/e0eb11c7/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
index 8ee8e03..27843b5 100644
--- a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
+++ b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
@@ -37,9 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
 import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.HashMap;
 import java.util.Map;
@@ -146,38 +144,43 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
         return properties.getProperty(USERGRID_EXTERNAL_PUBLICKEY_URL);
     }
 
-    public Jws<Claims> getClaimsForKeyUrl(String token, PublicKey ssoPublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, BadTokenException, ExpiredTokenException {
+    public Jws<Claims> getClaimsForKeyUrl(String token, PublicKey ssoPublicKey) throws BadTokenException {
+
         Jws<Claims> claims = null;
 
-        if(ssoPublicKey == null){
-            throw new IllegalArgumentException("Public key must be provided with Apigee " +
-                "token in order to verify signature.");
+        if (ssoPublicKey == null) {
+            throw new IllegalArgumentException( "Public key must be provided with Apigee JWT " +
+                "token in order to verify signature." );
         }
 
-        try {
-            claims = Jwts.parser().setSigningKey(ssoPublicKey).parseClaimsJws(token);
-        } catch (SignatureException se) {
-            if(logger.isDebugEnabled()) {
-                logger.debug("Signature was invalid for Apigee JWT: {} and key: {}", token, ssoPublicKey);
-            }
-            throw new BadTokenException("Invalid Apigee SSO token signature");
-        } catch (MalformedJwtException me){
-            if(logger.isDebugEnabled()) {
-                logger.debug("Beginning JSON object section of Apigee JWT invalid for token: {}", token);
+        int tries = 0;
+        int maxTries = 2;
+        while ( claims == null && tries++ < maxTries ) {
+            try {
+                claims = Jwts.parser().setSigningKey( ssoPublicKey ).parseClaimsJws( token );
+
+            } catch (SignatureException se) {
+                logger.warn( "Signature was invalid for Apigee JWT token: {} and key: {}", token, ssoPublicKey );
+
+            } catch (ExpiredJwtException e) {
+                final long expiry = Long.valueOf( e.getClaims().get( "exp" ).toString() );
+                final long expirationDelta = ((System.currentTimeMillis() / 1000) - expiry) * 1000;
+                logger.info(String.format("Apigee JWT Token expired %d milliseconds ago.", expirationDelta));
+
+            } catch (MalformedJwtException me) {
+                logger.error("Malformed JWT token", me);
+                throw new BadTokenException( "Malformed Apigee JWT token", me );
+
+            } catch (ArrayIndexOutOfBoundsException aio) {
+                logger.error("Error parsing JWT token", aio);
+                throw new BadTokenException( "Error parsing Apigee JWT token", aio );
             }
-            throw new BadTokenException("Malformed Apigee JWT");
-        } catch (ArrayIndexOutOfBoundsException aio){
-            if(logger.isDebugEnabled()) {
-                logger.debug("Signature section of Apigee JWT invalid for token: {}", token);
+
+            if ( claims == null ) {
+                this.publicKey =  getPublicKey( getExternalSSOUrl() );
             }
-            throw new BadTokenException("Malformed Apigee JWT");
-        } catch ( ExpiredJwtException e ){
-            final long expiry = Long.valueOf(e.getClaims().get("exp").toString());
-            final long expirationDelta = ((System.currentTimeMillis()/1000) - expiry)*1000;
-            throw new ExpiredTokenException(String.format("Token expired %d milliseconds ago.", expirationDelta ));
         }
 
-
         return claims;
     }
 


[5/5] usergrid git commit: Fix typo and unused reference

Posted by mr...@apache.org.
Fix typo and unused reference


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

Branch: refs/heads/hotfix-20160819
Commit: 406a742d2d9133fa5999ec983547c431cf05a56b
Parents: efa65ac
Author: Dave Johnson <sn...@apache.org>
Authored: Fri Oct 28 14:31:27 2016 -0400
Committer: Michael Russo <mr...@apigee.com>
Committed: Fri Oct 28 12:21:25 2016 -0700

----------------------------------------------------------------------
 .../apache/usergrid/rest/management/ExternalSSOEnabledIT.java  | 2 +-
 .../org/apache/usergrid/security/sso/ApigeeSSO2Provider.java   | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/406a742d/stack/rest/src/test/java/org/apache/usergrid/rest/management/ExternalSSOEnabledIT.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/management/ExternalSSOEnabledIT.java b/stack/rest/src/test/java/org/apache/usergrid/rest/management/ExternalSSOEnabledIT.java
index cae65df..7a2df1e 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/management/ExternalSSOEnabledIT.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/management/ExternalSSOEnabledIT.java
@@ -50,7 +50,7 @@ public class ExternalSSOEnabledIT extends AbstractRestIT {
     PrivateKey privateKey;
     String compactJws;
     String username = "SSOadminuser" + UUIDUtils.newTimeUUID();
-    ApigeeSSO2Provider apigeeSSO2ProviderTest;
+
     //SSO2 implementation
     public static final String USERGRID_EXTERNAL_SSO_ENABLED = "usergrid.external.sso.enabled";
     public static final String USERGRID_EXTERNAL_PROVIDER =    "usergrid.external.sso.provider";

http://git-wip-us.apache.org/repos/asf/usergrid/blob/406a742d/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
index 8e3c463..b1a05e6 100644
--- a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
+++ b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
@@ -61,7 +61,7 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
     public static final String USERGRID_EXTERNAL_PUBLICKEY_URL = "usergrid.external.sso.url";
 
-    public static final String USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS = "usergrid.external.sso.public-key-freshness";
+    public static final String USERGRID_EXTERNAL_PUBLICKEY_FRESHNESS = "usergrid.external.sso.public-key-freshness";
 
 
     public ApigeeSSO2Provider() {
@@ -241,11 +241,11 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
         lastPublicKeyFetch = System.currentTimeMillis();
 
-        String freshnessString = (String)properties.get( USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS );
+        String freshnessString = (String)properties.get( USERGRID_EXTERNAL_PUBLICKEY_FRESHNESS );
         try {
             freshnessTime = Long.parseLong( freshnessString );
         } catch ( Exception e ) {
-            logger.error("Ignoring invalid setting for " + USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS );
+            logger.error("Ignoring invalid setting for " + USERGRID_EXTERNAL_PUBLICKEY_FRESHNESS );
         }
     }
 }


[2/5] usergrid git commit: Ensure the admin invited to org email is sent even when external SSO is enabled.

Posted by mr...@apache.org.
Ensure the admin invited to org email is sent even when external SSO is enabled.


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

Branch: refs/heads/hotfix-20160819
Commit: 73de90309fe1593ec06e8b75095321af4c564f0c
Parents: 8921850
Author: Michael Russo <mr...@apigee.com>
Authored: Thu Oct 20 11:05:56 2016 -0700
Committer: Michael Russo <mr...@apigee.com>
Committed: Fri Oct 28 12:20:45 2016 -0700

----------------------------------------------------------------------
 .../usergrid/management/cassandra/ManagementServiceImpl.java     | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/73de9030/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 03377a3..876cd5b 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
@@ -1739,9 +1739,7 @@ public class ManagementServiceImpl implements ManagementService {
         invalidateManagementAppAuthCache();
 
         if ( email ) {
-            if(!tokens.isExternalSSOProviderEnabled()) {
-                sendAdminUserInvitedEmail(user, organization);
-            }
+            sendAdminUserInvitedEmail(user, organization);
         }
     }
 


[4/5] usergrid git commit: Add key freshness and full set of tests

Posted by mr...@apache.org.
Add key freshness and full set of tests


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

Branch: refs/heads/hotfix-20160819
Commit: efa65ac6ad86c00300ccf83797aae5d8d552cf70
Parents: e0eb11c
Author: Dave Johnson <sn...@apache.org>
Authored: Tue Oct 25 17:03:18 2016 -0400
Committer: Michael Russo <mr...@apigee.com>
Committed: Fri Oct 28 12:21:25 2016 -0700

----------------------------------------------------------------------
 .../security/sso/ApigeeSSO2Provider.java        |  86 ++++--
 .../usergrid/security/ApigeeSSO2ProviderIT.java | 297 +++++++++++++++++++
 2 files changed, 354 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/efa65ac6/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
index 27843b5..8e3c463 100644
--- a/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
+++ b/stack/services/src/main/java/org/apache/usergrid/security/sso/ApigeeSSO2Provider.java
@@ -45,9 +45,7 @@ import java.util.Properties;
 
 import static org.apache.commons.codec.binary.Base64.decodeBase64;
 
-/**
- * Created by ayeshadastagiri on 6/22/16.
- */
+
 public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
     private static final Logger logger = LoggerFactory.getLogger(ApigeeSSO2Provider.class);
@@ -56,9 +54,16 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
     protected ManagementService management;
     protected Client client;
     protected PublicKey publicKey;
+    protected long freshnessTime = 3000L;
+
+    public long lastPublicKeyFetch = 0L;
+
 
     public static final String USERGRID_EXTERNAL_PUBLICKEY_URL = "usergrid.external.sso.url";
 
+    public static final String USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS = "usergrid.external.sso.public-key-freshness";
+
+
     public ApigeeSSO2Provider() {
         ClientConfig clientConfig = new ClientConfig();
         clientConfig.register(new JacksonFeature());
@@ -67,17 +72,18 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
     public PublicKey getPublicKey(String keyUrl) {
 
-        if(keyUrl != null && !keyUrl.isEmpty()) {
+        if ( keyUrl != null && !keyUrl.isEmpty()) {
             try {
                 Map<String, Object> publicKey = client.target(keyUrl).request().get(Map.class);
-                String ssoPublicKey = publicKey.get(RESPONSE_PUBLICKEY_VALUE).toString().split("----\n")[1].split("\n---")[0];
+                String ssoPublicKey = publicKey.get(RESPONSE_PUBLICKEY_VALUE)
+                    .toString().split("----\n")[1].split("\n---")[0];
                 byte[] publicBytes = decodeBase64(ssoPublicKey);
                 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
                 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                 PublicKey pubKey = keyFactory.generatePublic(keySpec);
                 return pubKey;
             }
-            catch(Exception e){
+            catch (Exception e) {
                 throw new IllegalArgumentException("error getting public key");
             }
         }
@@ -91,14 +97,13 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
         UserInfo userInfo = validateAndReturnUserInfo(token, ttl);
 
-        if(userInfo == null){
-            throw new ExternalSSOProviderAdminUserNotFoundException("Unable to load user from token: "+token);
+        if (userInfo == null) {
+            throw new ExternalSSOProviderAdminUserNotFoundException("Unable to load user from token: " + token);
         }
 
         return new TokenInfo(UUIDUtils.newTimeUUID(), "access", 1, 1, 1, ttl,
                 new AuthPrincipalInfo(AuthPrincipalType.ADMIN_USER, userInfo.getUuid(),
                     CpNamingUtils.MANAGEMENT_APPLICATION_ID), null);
-
     }
 
     @Override
@@ -134,7 +139,7 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
     @Override
     public Map<String, Object> getAllTokenDetails(String token, String keyUrl) throws Exception {
-        Jws<Claims> claims = getClaimsForKeyUrl(token,getPublicKey(keyUrl));
+        Jws<Claims> claims = getClaimsForKeyUrl( token );
         return JsonUtils.toJsonMap(claims.getBody());
 
     }
@@ -144,50 +149,64 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
         return properties.getProperty(USERGRID_EXTERNAL_PUBLICKEY_URL);
     }
 
-    public Jws<Claims> getClaimsForKeyUrl(String token, PublicKey ssoPublicKey) throws BadTokenException {
+    public Jws<Claims> getClaimsForKeyUrl( String token ) throws BadTokenException {
 
         Jws<Claims> claims = null;
 
-        if (ssoPublicKey == null) {
-            throw new IllegalArgumentException( "Public key must be provided with Apigee JWT " +
-                "token in order to verify signature." );
-        }
+        Exception lastException = null;
 
         int tries = 0;
         int maxTries = 2;
         while ( claims == null && tries++ < maxTries ) {
             try {
-                claims = Jwts.parser().setSigningKey( ssoPublicKey ).parseClaimsJws( token );
+                claims = Jwts.parser().setSigningKey( publicKey ).parseClaimsJws( token );
 
             } catch (SignatureException se) {
-                logger.warn( "Signature was invalid for Apigee JWT token: {} and key: {}", token, ssoPublicKey );
+                // bad signature, need to get latest publicKey and try again
+                // logger.debug( "Signature was invalid for Apigee JWT token: {}", token );
+                lastException = se;
+
+            } catch (ArrayIndexOutOfBoundsException aio) {
+                // unknown error, need to get latest publicKey and try again
+                logger.debug("Error parsing JWT token", aio);
+                throw new BadTokenException( "Unknown error processing JWT", aio );
 
             } catch (ExpiredJwtException e) {
                 final long expiry = Long.valueOf( e.getClaims().get( "exp" ).toString() );
                 final long expirationDelta = ((System.currentTimeMillis() / 1000) - expiry) * 1000;
-                logger.info(String.format("Apigee JWT Token expired %d milliseconds ago.", expirationDelta));
+                logger.debug(String.format("Apigee JWT Token expired %d milliseconds ago.", expirationDelta));
+
+                // token is expired
+                throw new BadTokenException( "Expired JWT", e );
 
             } catch (MalformedJwtException me) {
-                logger.error("Malformed JWT token", me);
-                throw new BadTokenException( "Malformed Apigee JWT token", me );
+                logger.debug( "Malformed JWT", me );
 
-            } catch (ArrayIndexOutOfBoundsException aio) {
-                logger.error("Error parsing JWT token", aio);
-                throw new BadTokenException( "Error parsing Apigee JWT token", aio );
+                // token is malformed
+                throw new BadTokenException( "Malformed JWT", me );
             }
 
-            if ( claims == null ) {
-                this.publicKey =  getPublicKey( getExternalSSOUrl() );
+            long keyFreshness = System.currentTimeMillis() - lastPublicKeyFetch;
+            if ( claims == null && keyFreshness > this.freshnessTime ) {
+                logger.debug("Failed to get claims for token {}... fetching new public key", token);
+                publicKey =  getPublicKey( getExternalSSOUrl() );
+                lastPublicKeyFetch = System.currentTimeMillis();
+                logger.info("New public key is {}", publicKey);
             }
         }
 
+        if ( claims == null ) {
+            logger.error("Error getting Apigee JWT claims", lastException);
+            throw new BadTokenException( "Error getting Apigee JWT claims", lastException );
+        } else {
+            logger.debug( "Success! Got claims for token {} key {}", token, publicKey.toString() );
+        }
+
         return claims;
     }
 
     public Jws<Claims> getClaims(String token) throws Exception{
-
-        return getClaimsForKeyUrl(token,publicKey);
-
+        return getClaimsForKeyUrl(token);
     }
 
     private void validateClaims (final Jws<Claims> claims) throws ExpiredTokenException {
@@ -196,7 +215,7 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
 
         final long expiry = Long.valueOf(body.get("exp").toString());
 
-        if(expiry - (System.currentTimeMillis()/1000) < 0 ){
+        if (expiry - (System.currentTimeMillis()/1000) < 0 ){
 
             final long expirationDelta = ((System.currentTimeMillis()/1000) - expiry)*1000;
 
@@ -219,5 +238,14 @@ public class ApigeeSSO2Provider implements ExternalSSOProvider {
     public void setProperties(Properties properties) {
         this.properties = properties;
         this.publicKey =  getPublicKey(getExternalSSOUrl());
+
+        lastPublicKeyFetch = System.currentTimeMillis();
+
+        String freshnessString = (String)properties.get( USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS );
+        try {
+            freshnessTime = Long.parseLong( freshnessString );
+        } catch ( Exception e ) {
+            logger.error("Ignoring invalid setting for " + USERGRID_EXTERMAL_PUBLICKEY_FRESHNESS );
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/usergrid/blob/efa65ac6/stack/services/src/test/java/org/apache/usergrid/security/ApigeeSSO2ProviderIT.java
----------------------------------------------------------------------
diff --git a/stack/services/src/test/java/org/apache/usergrid/security/ApigeeSSO2ProviderIT.java b/stack/services/src/test/java/org/apache/usergrid/security/ApigeeSSO2ProviderIT.java
new file mode 100644
index 0000000..8dc13cf
--- /dev/null
+++ b/stack/services/src/test/java/org/apache/usergrid/security/ApigeeSSO2ProviderIT.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.usergrid.security;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.impl.crypto.RsaProvider;
+import org.apache.commons.collections4.map.HashedMap;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.usergrid.NewOrgAppAdminRule;
+import org.apache.usergrid.ServiceITSetup;
+import org.apache.usergrid.ServiceITSetupImpl;
+import org.apache.usergrid.cassandra.ClearShiroSubject;
+import org.apache.usergrid.management.ManagementService;
+import org.apache.usergrid.management.UserInfo;
+import org.apache.usergrid.persistence.Entity;
+import org.apache.usergrid.persistence.EntityManager;
+import org.apache.usergrid.persistence.SimpleEntityRef;
+import org.apache.usergrid.persistence.entities.User;
+import org.apache.usergrid.security.sso.ApigeeSSO2Provider;
+import org.apache.usergrid.security.tokens.TokenInfo;
+import org.apache.usergrid.security.tokens.exceptions.BadTokenException;
+import org.junit.*;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.*;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.*;
+
+import static org.apache.commons.codec.binary.Base64.decodeBase64;
+
+
+/**
+ * Created by Dave Johnson (snoopdave@apache.org) on 10/25/16.
+ */
+public class ApigeeSSO2ProviderIT {
+    private static final Logger logger = LoggerFactory.getLogger(ApigeeSSO2ProviderIT.class);
+
+    @ClassRule
+    public static final ServiceITSetup setup = new ServiceITSetupImpl();
+
+
+    @Test
+    public void testBasicOperation() throws Exception {
+
+        // create keypair
+        KeyPair kp = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKey = kp.getPublic();
+        PrivateKey privateKey = kp.getPrivate();
+
+        // create provider with private key
+        ApigeeSSO2Provider provider = new MockApigeeSSO2Provider();
+        provider.setManagement( setup.getMgmtSvc() );
+        provider.setPublicKey( publicKey );
+
+        // create user, claims and a token for those things
+        User user = createUser();
+        long exp = System.currentTimeMillis() + 10000;
+        Map<String, Object> claims = createClaims( user.getUsername(), user.getEmail(), exp );
+        String token = Jwts.builder().setClaims(claims).signWith( SignatureAlgorithm.RS256, privateKey).compact();
+
+        // test that provider can validate the token, get user, return token info
+        TokenInfo tokenInfo = provider.validateAndReturnTokenInfo( token, 86400L );
+        Assert.assertNotNull( tokenInfo );
+    }
+
+
+    @Test
+    public void testExpiredToken() throws Exception {
+
+        // create keypair
+        KeyPair kp = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKey = kp.getPublic();
+        PrivateKey privateKey = kp.getPrivate();
+
+        // create provider with private key
+        ApigeeSSO2Provider provider = new MockApigeeSSO2Provider();
+        provider.setManagement( setup.getMgmtSvc() );
+        provider.setPublicKey( publicKey );
+
+        // create user, claims and a token for those things
+        User user = createUser();
+        long exp = System.currentTimeMillis() - 1500;
+        Map<String, Object> claims = createClaims( user.getUsername(), user.getEmail(), exp );
+        String token = Jwts.builder()
+            .setClaims(claims)
+            .setExpiration( new Date() )
+            .signWith( SignatureAlgorithm.RS256, privateKey)
+            .compact();
+
+        Thread.sleep(500); // wait for claims to timeout
+
+        // test that token is expired
+        try {
+            provider.validateAndReturnTokenInfo( token, 86400L );
+            Assert.fail("Should have failed due to expired token");
+
+        } catch ( BadTokenException e ) {
+            Assert.assertTrue( e.getCause() instanceof ExpiredJwtException );
+        }
+    }
+
+
+    @Test
+    public void testMalformedToken() throws Exception {
+
+        // create keypair
+        KeyPair kp = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKey = kp.getPublic();
+
+        // create provider with private key
+        ApigeeSSO2Provider provider = new MockApigeeSSO2Provider();
+        provider.setManagement( setup.getMgmtSvc() );
+        provider.setPublicKey( publicKey );
+
+        // test that token is malformed
+        try {
+            provider.getClaims( "{;aklsjd;fkajsd;fkjasd;lfkj}" );
+            Assert.fail("Should have failed due to malformed token");
+
+        } catch ( BadTokenException e ) {
+            Assert.assertTrue( e.getCause() instanceof MalformedJwtException );
+        }
+    }
+
+    @Test
+    public void testNewPublicKeyFetch() throws Exception {
+
+        // create old keypair
+        KeyPair kp = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKey = kp.getPublic();
+        PrivateKey privateKey = kp.getPrivate();
+
+        // create new keypair
+        KeyPair kpNew = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKeyNew = kpNew.getPublic();
+        PrivateKey privateKeyNew = kpNew.getPrivate();
+
+        // create mock provider with old and old key
+        MockApigeeSSO2ProviderNewKey provider = new MockApigeeSSO2ProviderNewKey( publicKey, publicKeyNew );
+        provider.setManagement( setup.getMgmtSvc() );
+
+        // create user, claims and a token for those things. Sign with new public key
+        User user = createUser();
+        long exp = System.currentTimeMillis() + 10000;
+        Map<String, Object> claims = createClaims( user.getUsername(), user.getEmail(), exp );
+        String token = Jwts.builder().setClaims(claims).signWith( SignatureAlgorithm.RS256, privateKeyNew).compact();
+
+        // test that provider can validate the token, get user, return token info
+        TokenInfo tokenInfo = provider.validateAndReturnTokenInfo( token, 86400L );
+        Assert.assertNotNull( tokenInfo );
+
+        // assert that provider called for new key
+        Assert.assertTrue( provider.isGetPublicKeyCalled() );
+
+
+        // try it again, but this time it should fail due to freshness value
+
+        provider.setPublicKey( publicKey ); // set old key
+
+        // test that signature exception thrown
+        try {
+            provider.validateAndReturnTokenInfo( token, 86400L );
+            Assert.fail("Should have failed due to bad signature");
+
+        } catch ( BadTokenException e ) {
+            Assert.assertTrue( e.getCause() instanceof SignatureException );
+        }
+
+    }
+
+
+    @Test
+    public void testBadSignature() throws Exception {
+
+        // create old keypair
+        KeyPair kp = RsaProvider.generateKeyPair(1024);
+        PublicKey publicKey = kp.getPublic();
+        PrivateKey privateKey = kp.getPrivate();
+
+        // create new keypair
+        KeyPair kpNew = RsaProvider.generateKeyPair(1024);
+        PrivateKey privateKeyNew = kpNew.getPrivate();
+
+        // create mock provider with old public key
+        ApigeeSSO2Provider provider = new MockApigeeSSO2ProviderNewKey( publicKey, publicKey );
+        provider.setManagement( setup.getMgmtSvc() );
+
+        // create user, claims and a token for those things. Sign with new public key
+        User user = createUser();
+        long exp = System.currentTimeMillis() + 10000;
+        Map<String, Object> claims = createClaims( user.getUsername(), user.getEmail(), exp );
+        String token = Jwts.builder().setClaims(claims).signWith( SignatureAlgorithm.RS256, privateKeyNew).compact();
+
+        // test that signature exception thrown
+        try {
+            provider.validateAndReturnTokenInfo( token, 86400L );
+            Assert.fail("Should have failed due to bad signature");
+
+        } catch ( BadTokenException e ) {
+            Assert.assertTrue( e.getCause() instanceof SignatureException );
+        }
+
+    }
+
+    private User createUser() throws Exception {
+        String rando = RandomStringUtils.randomAlphanumeric( 10 );
+        String username = "user_" + rando;
+        String email = username + "@example.com";
+        Map<String, Object> properties = new HashMap<String, Object>() {{
+            put( "username", username );
+            put( "email", email );
+        }};
+        EntityManager em = setup.getEmf().getEntityManager( setup.getEmf().getManagementAppId() );
+        Entity entity = em.create( "user", properties );
+
+        return em.get( new SimpleEntityRef( User.ENTITY_TYPE, entity.getUuid() ), User.class );
+    }
+
+
+    private Map<String, Object> createClaims(final String username, final String email, long exp ) {
+        return new HashedMap<String, Object>() {{
+                put("jti","c7df0339-3847-450b-a925-628ef237953a");
+                put("sub","b6d62259-217b-4e96-8f49-e00c366e4fed");
+                put("scope","size = 5");
+                put("client_id", "dummy1");
+                put("azp","dummy2");
+                put("grant_type" ,"password");
+                put("user_id","b6d62259-217b-4e96-8f49-e00c366e4fed");
+                put("origin","usergrid");
+                put("user_name", username );
+                put("email", email);
+                put("rev_sig","dfe5d0d3");
+                put("exp", exp);
+                put("iat", System.currentTimeMillis());
+                put("iss", "https://jwt.example.com/token");
+                put("zid","uaa");
+                put("aud"," size = 6");
+            }};
+    }
+}
+
+class MockApigeeSSO2Provider extends ApigeeSSO2Provider {
+    private static final Logger logger = LoggerFactory.getLogger(MockApigeeSSO2Provider.class);
+
+    @Override
+    public PublicKey getPublicKey(String keyUrl ) {
+        return publicKey;
+    }
+
+    @Override
+    public void setPublicKey( PublicKey publicKey ) {
+        this.publicKey = publicKey;
+    }
+}
+
+
+class MockApigeeSSO2ProviderNewKey extends ApigeeSSO2Provider {
+    private static final Logger logger = LoggerFactory.getLogger(MockApigeeSSO2Provider.class);
+
+    private PublicKey newKey;
+    private boolean getPublicKeyCalled = false;
+
+    public MockApigeeSSO2ProviderNewKey( PublicKey oldKey, PublicKey newKey ) {
+        this.publicKey = oldKey;
+        this.newKey = newKey;
+        this.properties = new Properties();
+    }
+
+    @Override
+    public PublicKey getPublicKey( String keyUrl ) {
+        getPublicKeyCalled = true;
+        return newKey;
+    }
+
+    public boolean isGetPublicKeyCalled() {
+        return getPublicKeyCalled;
+    }
+}
+
+