/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed 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 com.cinchapi.concourse.security; import java.io.File; import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import com.cinchapi.concourse.security.AccessManager; import com.cinchapi.concourse.server.io.FileSystem; import com.cinchapi.concourse.test.ConcourseBaseTest; import com.cinchapi.concourse.test.Variables; import com.cinchapi.concourse.thrift.AccessToken; import com.cinchapi.concourse.time.Time; import com.cinchapi.concourse.util.ByteBuffers; import com.cinchapi.concourse.util.TestData; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * Unit tests for {@link AccessManager}. * * @author Jeff Nelson */ public class AccessManagerTest extends ConcourseBaseTest { private String current = null; private AccessManager manager = null; @Rule public TestRule watcher = new TestWatcher() { @Override protected void finished(Description desc) { FileSystem.deleteFile(current); } @Override protected void starting(Description desc) { current = TestData.DATA_DIR + File.separator + Time.now(); manager = AccessManager.create(current); } }; @Test public void testDefaultAdminLogin() { ByteBuffer username = ByteBuffer.wrap("admin".getBytes()); ByteBuffer password = ByteBuffer.wrap("admin".getBytes()); Assert.assertTrue(manager.isExistingUsernamePasswordCombo(username, password)); } @Test public void testChangeAdminPassword() { ByteBuffer username = ByteBuffer.wrap("admin".getBytes()); ByteBuffer password = ByteBuffer.wrap("admin".getBytes()); ByteBuffer newPassword = getSecurePassword(); manager.createUser(username, newPassword); Assert.assertFalse(manager.isExistingUsernamePasswordCombo(username, password)); Assert.assertTrue(manager.isExistingUsernamePasswordCombo(username, newPassword)); } @Test public void testAddUsers() { Map<ByteBuffer, ByteBuffer> users = Maps.newHashMap(); for (int i = 0; i < TestData.getScaleCount(); i++) { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); users.put(username, password); manager.createUser(username, password); } for (Entry<ByteBuffer, ByteBuffer> entry : users.entrySet()) { Assert.assertTrue(manager.isExistingUsernamePasswordCombo( entry.getKey(), entry.getValue())); } } @Test public void testAllUsersHaveUniqueUids() { Set<ByteBuffer> emptySet = Sets.newHashSet(); Set<ByteBuffer> users = (Set<ByteBuffer>) addMoreUsers(emptySet, manager); Set<Short> uniqueUids = Sets.newHashSet(); for (ByteBuffer username : users) { short uid = manager.getUidByUsername(username); Assert.assertFalse(uniqueUids.contains(uid)); // check uniqueness uniqueUids.add(uid); } AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager users = (Set<ByteBuffer>) addMoreUsers(users, manager2); uniqueUids = Sets.newHashSet(); for (ByteBuffer username : users) { short uid = manager2.getUidByUsername(username); Assert.assertFalse(uniqueUids.contains(uid)); // check uniqueness uniqueUids.add(uid); } } @Test public void testAllUsersHaveUniqueUidsAfterSomeUserDeletions() { List<ByteBuffer> emptyList = Lists.newArrayList(); List<ByteBuffer> users = (List<ByteBuffer>) addMoreUsers(emptyList, manager); users = deleteSomeUsers(users, manager); Set<Short> uniqueUids = Sets.newHashSet(); for (ByteBuffer username : users) { short uid = manager.getUidByUsername(username); Assert.assertFalse(uniqueUids.contains(uid)); // check uniqueness uniqueUids.add(uid); } AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager Variables.register("users", users); users = deleteSomeUsers(users, manager2); Variables.register("users_after_delete", Lists.newArrayList(users)); users = (List<ByteBuffer>) addMoreUsers(users, manager2); Variables.register("users_after_add", Lists.newArrayList(users)); uniqueUids = Sets.newHashSet(); Variables.register("uniqueUids", uniqueUids); for (ByteBuffer username : users) { short uid = manager2.getUidByUsername(username); Variables.register("uid", uid); Assert.assertFalse(uniqueUids.contains(uid)); // check uniqueness uniqueUids.add(uid); } } @Test public void testUsersHaveSameUidsAsBeforeSomeUserDeletions() { List<ByteBuffer> emptySet = Lists.newArrayList(); List<ByteBuffer> users = (List<ByteBuffer>) addMoreUsers(emptySet, manager); Map<ByteBuffer, Short> uids = Maps.newHashMap(); for (ByteBuffer username : users) { // retrieve short uid = manager.getUidByUsername(username); // valid uids uids.put(username, uid); // after add users } users = deleteSomeUsers(users, manager); uids = Maps.newHashMap(); for (ByteBuffer username : users) { // retrieve short uid = manager.getUidByUsername(username); // valid uids uids.put(username, uid); // after delete users } for (ByteBuffer username : users) { short uid = manager.getUidByUsername(username); Assert.assertEquals((short) uids.get(username), uid);// check // uniqueness } AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager users = (List<ByteBuffer>) addMoreUsers(users, manager2); for (ByteBuffer username : users) { // retrieve short uid = manager2.getUidByUsername(username); // valid uids uids.put(username, uid); // after add users } for (ByteBuffer username : users) { short uid = manager2.getUidByUsername(username); Assert.assertEquals((short) uids.get(username), uid);// check // uniqueness } } @Test public void testAllUsersHaveSameUidsAsBeforeServerRestarts() { Set<ByteBuffer> emptySet = Sets.newHashSet(); Set<ByteBuffer> users = (Set<ByteBuffer>) addMoreUsers(emptySet, manager); Map<ByteBuffer, Short> uids = Maps.newHashMap(); for (ByteBuffer username : users) { // retrieve valid short uid = manager.getUidByUsername(username); // uids after uids.put(username, uid); // add users } AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager for (ByteBuffer username : users) { short uid = manager2.getUidByUsername(username); Assert.assertEquals((short) uids.get(username), uid); } } @Test public void testAllUsersHaveSameUidsAsBeforePasswordChange() { Set<ByteBuffer> emptySet = Sets.newHashSet(); Set<ByteBuffer> users = (Set<ByteBuffer>) addMoreUsers(emptySet, manager); Map<ByteBuffer, Short> uids = Maps.newHashMap(); for (ByteBuffer username : users) { // retrieve valid short uid = manager.getUidByUsername(username); // uids after uids.put(username, uid); // add users } for (ByteBuffer username : users) { // change password manager.createUser(username, getSecurePassword()); } for (ByteBuffer username : users) { short uid = manager.getUidByUsername(username); Assert.assertEquals((short) uids.get(username), uid); } AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager for (ByteBuffer username : users) { manager2.createUser(username, getSecurePassword()); // change // password } for (ByteBuffer username : users) { short uid = manager2.getUidByUsername(username); Assert.assertEquals((short) uids.get(username), uid); } } @Test(expected = IllegalArgumentException.class) public void testCantRevokeAdmin() { manager.deleteUser(toByteBuffer("admin")); } @Test public void testRevokeUser() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); manager.deleteUser(username); Assert.assertFalse(manager.isExistingUsernamePasswordCombo(username, password)); } @Test public void testEnableUser() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); manager.disableUser(username); manager.enableUser(username); Assert.assertTrue(manager.isEnabledUsername(username)); } @Test public void testDisableUser() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); manager.disableUser(username); Assert.assertFalse(manager.isEnabledUsername(username)); } @Test public void testDisablingUserInvalidatesAllAccessTokens() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); List<AccessToken> tokens = Lists.newArrayList(); for (int i = 0; i < TestData.getScaleCount(); i++) { tokens.add(manager.getNewAccessToken(username)); } manager.disableUser(username); for (AccessToken token : tokens) { Assert.assertFalse(manager.isValidAccessToken(token)); } } @Test public void testNewlyCreatedUserIsEnabled() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); Assert.assertTrue(manager.isEnabledUsername(username)); } @Test public void testIsValidUsernameAndPassword() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); ByteBuffer badpassword = toByteBuffer(TestData.getString() + "bad"); manager.createUser(username, password); Assert.assertTrue(manager.isExistingUsernamePasswordCombo(username, password)); Assert.assertFalse(manager.isExistingUsernamePasswordCombo(username, badpassword)); } @Test public void testDiskSync() { Map<ByteBuffer, ByteBuffer> users = Maps.newHashMap(); for (int i = 0; i < TestData.getScaleCount(); i++) { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); users.put(username, password); manager.createUser(username, password); } AccessManager manager2 = AccessManager.create(current); for (Entry<ByteBuffer, ByteBuffer> entry : users.entrySet()) { Assert.assertTrue(manager2.isExistingUsernamePasswordCombo( entry.getKey(), entry.getValue())); } } @Test(expected = IllegalArgumentException.class) public void testCantCreateAccessTokenForInvalidUser() { manager.getNewAccessToken(toByteBuffer(TestData.getString() + "foo")); } @Test public void testCanCreateAccessTokenForValidUser() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); Assert.assertTrue(manager.isValidAccessToken(token)); } @Test public void testAccessTokenIsNotValidIfServerRestarts() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); AccessManager manager2 = AccessManager.create(current); // simulate // server // restart by // creating new // manager Assert.assertFalse(manager2.isValidAccessToken(token)); } @Test public void testAccessTokenIsNotValidIfPasswordChanges() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); ByteBuffer password2 = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); manager.createUser(username, password2); Assert.assertFalse(manager.isValidAccessToken(token)); } @Test public void testAccessTokenIsNotValidIfAccessIsRevoked() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); manager.deleteUser(username); Assert.assertFalse(manager.isValidAccessToken(token)); } @Test public void testAccessTokenAutoExpiration() throws InterruptedException { manager = AccessManager.createForTesting(current, 60, TimeUnit.MILLISECONDS); ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); TimeUnit.MILLISECONDS.sleep(60); Assert.assertFalse(manager.isValidAccessToken(token)); } @Test public void testInvalidateAccessToken() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token = manager.getNewAccessToken(username); manager.expireAccessToken(token); Assert.assertFalse(manager.isValidAccessToken(token)); } @Test public void testTwoAccessTokensForSameUser() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token1 = manager.getNewAccessToken(username); AccessToken token2 = manager.getNewAccessToken(username); Assert.assertNotEquals(token1, token2); Assert.assertTrue(manager.isValidAccessToken(token1)); Assert.assertTrue(manager.isValidAccessToken(token2)); } @Test public void testInvalidatingOneAccessTokenDoesNotAffectOther() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); AccessToken token1 = manager.getNewAccessToken(username); AccessToken token2 = manager.getNewAccessToken(username); manager.expireAccessToken(token2); Assert.assertTrue(manager.isValidAccessToken(token1)); } @Test public void testRevokingAccessInvalidatesAllAccessTokens() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); List<AccessToken> tokens = Lists.newArrayList(); for (int i = 0; i < TestData.getScaleCount(); i++) { tokens.add(manager.getNewAccessToken(username)); } manager.deleteUser(username); for (AccessToken token : tokens) { Assert.assertFalse(manager.isValidAccessToken(token)); } } @Test public void testChangingPasswordInvalidatesAllAccessTokens() { ByteBuffer username = getAcceptableUsername(); ByteBuffer password = getSecurePassword(); manager.createUser(username, password); List<AccessToken> tokens = Lists.newArrayList(); for (int i = 0; i < TestData.getScaleCount(); i++) { tokens.add(manager.getNewAccessToken(username)); } manager.createUser(username, getSecurePassword()); for (AccessToken token : tokens) { Assert.assertFalse(manager.isValidAccessToken(token)); } } @Test public void testEmptyPasswordNotSecure() { Assert.assertFalse(AccessManager.isSecurePassword(ByteBuffers .fromString(""))); } @Test public void testAllWhitespacePasswordNotSecure() { Assert.assertFalse(AccessManager.isSecurePassword(ByteBuffers .fromString(" "))); } @Test public void testUsernameWithWhitespaceNotAcceptable() { Assert.assertFalse(AccessManager.isAcceptableUsername(ByteBuffers .fromString(" f "))); } @Test public void testServiceTokenIsValid() { AccessToken token = manager.getNewServiceToken(); Assert.assertTrue(manager.isValidAccessToken(token)); } @Test public void testServiceTokenInvalidation() { AccessToken token = manager.getNewServiceToken(); manager.expireAccessToken(token); Assert.assertFalse(manager.isValidAccessToken(token)); } @Test(expected = IllegalArgumentException.class) public void testServiceTokenNotTiedToUser() { AccessToken token = manager.getNewServiceToken(); manager.getUidByAccessToken(token); } @Test public void testServiceTokenUsesInvalidUsername() { AccessToken token = manager.getNewServiceToken(); ByteBuffer username = manager.getUsernameByAccessToken(token); Assert.assertFalse(AccessManager.isAcceptableUsername(username)); } /** * Convert a string to a ByteBuffer. * * @param string * @return the bytebuffer */ protected static ByteBuffer toByteBuffer(String string) { return ByteBuffer.wrap(string.getBytes()); } /** * Return a username that will pass the acceptance test. * * @return username */ protected static ByteBuffer getAcceptableUsername() { ByteBuffer username = null; while (username == null || !AccessManager.isAcceptableUsername(username)) { username = toByteBuffer(TestData.getString()); } return username; } /** * Return a password that will pass the security test. * * @return password */ protected static ByteBuffer getSecurePassword() { ByteBuffer password = null; while (password == null || !AccessManager.isSecurePassword(password)) { password = toByteBuffer(TestData.getString()); } return password; } /** * Return a collection of unique binary usernames that is * added to the specified {@code manager}, which is also a * superset of the {@code existingUsers} and newly added * usernames. * * @param existingUsers * @param manager * @return the valid usernames */ private static Collection<ByteBuffer> addMoreUsers( Collection<ByteBuffer> existingUsers, AccessManager manager) { Set<ByteBuffer> usernames = Sets.newHashSet(); int count = TestData.getScaleCount(); while (usernames.size() < count) { ByteBuffer username = getAcceptableUsername(); if(!usernames.contains(username)) { ByteBuffer password = getSecurePassword(); manager.createUser(username, password); existingUsers.add(username); usernames.add(username); } } return existingUsers; } /** * Return a list of binary usernames that is still valid * after some usernames in {@code existingUsers} has been * randomly deleted from {@code manager}. * * @param existingUsers * @param manager * @return the valid usernames */ private static List<ByteBuffer> deleteSomeUsers( List<ByteBuffer> existingUsers, AccessManager manager) { java.util.Random rand = new java.util.Random(); Set<ByteBuffer> removedUsers = Sets.newHashSet(); int count = rand.nextInt(existingUsers.size()); for (int i = 0; i < count; i++) { ByteBuffer username = existingUsers.get(rand.nextInt(existingUsers .size())); removedUsers.add(username); } for (ByteBuffer username : removedUsers) { manager.deleteUser(username); existingUsers.remove(username); } return existingUsers; } }