/* * 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; } }