/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.util; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.util.Set; import java.util.concurrent.ConcurrentMap; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; public class CachingPasswordEncoderTest { CachingPasswordEncoder cachingPasswordEncoder; private String password; @Before public void setUp() throws Exception { cachingPasswordEncoder = new CachingPasswordEncoder(); cachingPasswordEncoder.setPasswordEncoder(new BCryptPasswordEncoder()); password = new RandomValueStringGenerator().generate(); } @After public void tearDown() throws Exception { } @Test public void testSetPasswordEncoder() throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); cachingPasswordEncoder.setPasswordEncoder(encoder); assertSame(encoder, cachingPasswordEncoder.getPasswordEncoder()); } @Test public void testEncode() throws Exception { String encode1 = cachingPasswordEncoder.encode(password); String encode2 = cachingPasswordEncoder.getPasswordEncoder().encode(password); assertFalse(encode1.equals(encode2)); assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encode1)); assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encode2)); assertTrue(cachingPasswordEncoder.matches(password, encode1)); assertTrue(cachingPasswordEncoder.matches(password, encode2)); } @Test public void testMatches() throws Exception { cachingPasswordEncoder.encode(password); String encoded = cachingPasswordEncoder.encode(password); int iterations = 5; for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encoded)); assertTrue(cachingPasswordEncoder.matches(password, encoded)); } } @Test public void testMatches_But_Expires() throws Exception { cachingPasswordEncoder.setExpiryInSeconds(5); cachingPasswordEncoder.encode(password); String cacheKey = cachingPasswordEncoder.cacheEncode(password); String encoded = cachingPasswordEncoder.encode(password); int iterations = 5; for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encoded)); assertTrue(cachingPasswordEncoder.matches(password, encoded)); assertTrue(cachingPasswordEncoder.getOrCreateHashList(cacheKey).size()>0); } Thread.sleep(5500); assertTrue(cachingPasswordEncoder.getOrCreateHashList(cacheKey).size()==0); } @Test public void testNotMatches() throws Exception { cachingPasswordEncoder.encode(password); String encoded = cachingPasswordEncoder.encode(password); password = new RandomValueStringGenerator().generate(); int iterations = 5; for (int i=0; i<iterations; i++) { assertFalse(cachingPasswordEncoder.getPasswordEncoder().matches(password, encoded)); assertFalse(cachingPasswordEncoder.matches(password, encoded)); } } @Test public void testMatchesSpeedTest() throws Exception { int iterations = 15; String password = new RandomValueStringGenerator().generate(); String encodedBcrypt = cachingPasswordEncoder.encode(password); long nanoStart = System.nanoTime(); for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encodedBcrypt)); } long nanoStop = System.nanoTime(); long bcryptTime = nanoStop - nanoStart; nanoStart = System.nanoTime(); for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.matches(password, encodedBcrypt)); } nanoStop = System.nanoTime(); long cacheTime = nanoStop - nanoStart; //assert that the cache is at least 10 times faster assertTrue(bcryptTime > (10 * cacheTime)); System.out.println("CachingPasswordEncoder - Bcrypt Time:"+((double)bcryptTime / 1000000000.0) + " sec. Cache Time:"+((double)cacheTime / 1000000000.0)+" sec."); } @Test public void testEnsureNoMemoryLeak() { int maxkeys = 10; int maxpasswords = 4; cachingPasswordEncoder.setMaxKeys(maxkeys); assertEquals(maxkeys, cachingPasswordEncoder.getMaxKeys()); cachingPasswordEncoder.setMaxEncodedPasswords(4); assertEquals(maxpasswords, cachingPasswordEncoder.getMaxEncodedPasswords()); assertEquals(0, cachingPasswordEncoder.getNumberOfKeys()); for (int i=0; i<cachingPasswordEncoder.getMaxKeys(); i++) { String password = new RandomValueStringGenerator().generate(); for (int j=0; j<cachingPasswordEncoder.getMaxEncodedPasswords(); j++) { String encoded = cachingPasswordEncoder.encode(password); assertTrue(cachingPasswordEncoder.matches(password, encoded)); } } assertEquals(maxkeys, cachingPasswordEncoder.getNumberOfKeys()); String password = new RandomValueStringGenerator().generate(); String encoded = cachingPasswordEncoder.encode(password); assertTrue(cachingPasswordEncoder.matches(password, encoded)); //overflow happened assertEquals(1, cachingPasswordEncoder.getNumberOfKeys()); for (int j=1; j<cachingPasswordEncoder.getMaxEncodedPasswords(); j++) { encoded = cachingPasswordEncoder.encode(password); assertTrue(cachingPasswordEncoder.matches(password, encoded)); } ConcurrentMap<CharSequence, Set<String>> cache = cachingPasswordEncoder.asMap(); assertNotNull(cache); Set<String> passwords = cache.get(cachingPasswordEncoder.cacheEncode(password)); assertNotNull(passwords); assertEquals(maxpasswords, passwords.size()); cachingPasswordEncoder.matches(password, cachingPasswordEncoder.encode(password)); assertEquals(1, passwords.size()); } @Test public void testDisabledMatchesSpeedTest() throws Exception { int iterations = 15; cachingPasswordEncoder.setEnabled(false); assertFalse(cachingPasswordEncoder.isEnabled()); String password = new RandomValueStringGenerator().generate(); String encodedBcrypt = cachingPasswordEncoder.encode(password); long nanoStart = System.nanoTime(); for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.getPasswordEncoder().matches(password, encodedBcrypt)); } long nanoStop = System.nanoTime(); long bcryptTime = nanoStop - nanoStart; nanoStart = System.nanoTime(); for (int i=0; i<iterations; i++) { assertTrue(cachingPasswordEncoder.matches(password, encodedBcrypt)); } nanoStop = System.nanoTime(); long cacheTime = nanoStop - nanoStart; //assert that the cache is at least 10 times faster assertFalse(bcryptTime > (10 * cacheTime)); assertEquals(0, cachingPasswordEncoder.getNumberOfKeys()); System.out.println("CachingPasswordEncoder[disabled] - Bcrypt Time:"+((double)bcryptTime / 1000000000.0) + " sec. Cache Time:"+((double)cacheTime / 1000000000.0)+" sec."); } }