/* * JBoss, Home of Professional Open Source * * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * 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 org.wildfly.security.auth; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.WildFlyElytronProvider; import org.wildfly.security.auth.principal.NamePrincipal; import org.wildfly.security.auth.realm.FileSystemSecurityRealm; import org.wildfly.security.auth.server.CloseableIterator; import org.wildfly.security.auth.server.ModifiableRealmIdentity; import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.AuthorizationIdentity; import org.wildfly.security.authz.MapAttributes; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; import org.wildfly.security.password.Password; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.BCryptPassword; import org.wildfly.security.password.interfaces.ClearPassword; import org.wildfly.security.password.interfaces.DigestPassword; import org.wildfly.security.password.interfaces.OneTimePassword; import org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword; import org.wildfly.security.password.interfaces.ScramDigestPassword; import org.wildfly.security.password.interfaces.SimpleDigestPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; import org.wildfly.security.password.spec.DigestPasswordAlgorithmSpec; import org.wildfly.security.password.spec.EncryptablePasswordSpec; import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec; import org.wildfly.security.password.spec.OneTimePasswordSpec; import org.wildfly.security.password.spec.SaltedPasswordAlgorithmSpec; import org.wildfly.security.password.util.PasswordUtil; import org.wildfly.security.util.CodePointIterator; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.Provider; import java.security.Security; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import static org.wildfly.security.password.interfaces.BCryptPassword.BCRYPT_SALT_SIZE; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ public class FileSystemSecurityRealmTest { private static final Provider provider = new WildFlyElytronProvider(); @BeforeClass public static void onBefore() throws Exception { Security.addProvider(provider); } @AfterClass public static void onAfter() throws Exception { Security.removeProvider(provider.getName()); } @Test public void testCreateIdentityWithNoLevels() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 0); ModifiableRealmIdentity identity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertFalse(identity.exists()); identity.create(); assertTrue(identity.exists()); } @Test public void testCreateIdentityWithLevels() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity identity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); identity.create(); assertTrue(identity.exists()); identity.dispose(); } @Test public void testCreateAndLoadIdentity() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 3); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); existingIdentity.dispose(); } @Test public void testShortUsername() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); newIdentity.create(); newIdentity.dispose(); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); assertTrue(existingIdentity.exists()); existingIdentity.dispose(); } @Test public void testSpecialCharacters() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); newIdentity.create(); newIdentity.dispose(); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); assertTrue(existingIdentity.exists()); existingIdentity.dispose(); } @Test public void testCaseSensitive() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); assertTrue(newIdentity.exists()); newIdentity.dispose(); ModifiableRealmIdentity differentIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("PLAINUSER")); assertFalse(differentIdentity.exists()); differentIdentity.dispose(); } @Test public void testCreateAndLoadAndDeleteIdentity() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 3); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); existingIdentity.delete(); assertFalse(existingIdentity.exists()); existingIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 3); existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertFalse(existingIdentity.exists()); existingIdentity.dispose(); } @Test public void testCreateIdentityWithAttributes() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); MapAttributes newAttributes = new MapAttributes(); newAttributes.addFirst("name", "plainUser"); newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); newIdentity.setAttributes(newAttributes); newIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); Attributes existingAttributes = authorizationIdentity.getAttributes(); existingIdentity.dispose(); assertEquals(newAttributes.size(), existingAttributes.size()); assertTrue(newAttributes.get("name").containsAll(existingAttributes.get("name"))); assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); } @Test public void testCreateIdentityWithClearPassword() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR); ClearPassword clearPassword = (ClearPassword) factory.generatePassword(new ClearPasswordSpec(actualPassword)); assertCreateIdentityWithPassword(actualPassword, clearPassword); } @Test public void testCreateIdentityWithBcryptCredential() throws Exception { PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT); char[] actualPassword = "secretPassword".toCharArray(); BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE))) ); assertCreateIdentityWithPassword(actualPassword, bCryptPassword); } @Test public void testCreateIdentityWithScramCredential() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); PasswordFactory factory = PasswordFactory.getInstance(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256); EncryptablePasswordSpec encSpec = new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(4096, salt)); ScramDigestPassword scramPassword = (ScramDigestPassword) factory.generatePassword(encSpec); assertCreateIdentityWithPassword(actualPassword, scramPassword); } @Test public void testCreateIdentityWithDigest() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); PasswordFactory factory = PasswordFactory.getInstance(DigestPassword.ALGORITHM_DIGEST_SHA_512); DigestPasswordAlgorithmSpec dpas = new DigestPasswordAlgorithmSpec("jsmith", "elytron"); EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(actualPassword, dpas); DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); assertCreateIdentityWithPassword(actualPassword, digestPassword); } @Test public void testCreateIdentityWithSimpleDigest() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); EncryptablePasswordSpec eps = new EncryptablePasswordSpec(actualPassword, null); PasswordFactory passwordFactory = PasswordFactory.getInstance(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512); SimpleDigestPassword tsdp = (SimpleDigestPassword) passwordFactory.generatePassword(eps); assertCreateIdentityWithPassword(actualPassword, tsdp); } @Test public void testCreateIdentityWithSimpleSaltedDigest() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); SaltedPasswordAlgorithmSpec spac = new SaltedPasswordAlgorithmSpec(salt); EncryptablePasswordSpec eps = new EncryptablePasswordSpec(actualPassword, spac); PasswordFactory passwordFactory = PasswordFactory.getInstance(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512); SaltedSimpleDigestPassword tsdp = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(eps); assertCreateIdentityWithPassword(actualPassword, tsdp); } @Test public void testCreateIdentityWithEverything() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); MapAttributes newAttributes = new MapAttributes(); newAttributes.addFirst("firstName", "John"); newAttributes.addFirst("lastName", "Smith"); newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); newIdentity.setAttributes(newAttributes); List<Credential> credentials = new ArrayList<>(); PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT); BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE))) ); credentials.add(new PasswordCredential(bCryptPassword)); byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); byte[] seed = "ke1234".getBytes(StandardCharsets.US_ASCII); PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1); OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( new OneTimePasswordSpec(hash, seed, 500) ); credentials.add(new PasswordCredential(otpPassword)); newIdentity.setCredentials(credentials); newIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); OneTimePassword otp = existingIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class); assertNotNull(otp); assertEquals(OneTimePassword.ALGORITHM_OTP_SHA1, otp.getAlgorithm()); assertArrayEquals(hash, otp.getHash()); assertArrayEquals(seed, otp.getSeed()); assertEquals(500, otp.getSequenceNumber()); AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); Attributes existingAttributes = authorizationIdentity.getAttributes(); existingIdentity.dispose(); assertEquals(newAttributes.size(), existingAttributes.size()); assertTrue(newAttributes.get("firstName").containsAll(existingAttributes.get("firstName"))); assertTrue(newAttributes.get("lastName").containsAll(existingAttributes.get("lastName"))); assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); } @Test public void testCredentialReplacing() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); identity1.create(); List<Credential> credentials = new ArrayList<>(); PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT); BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE))) ); credentials.add(new PasswordCredential(bCryptPassword)); byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); byte[] seed = "ke1234".getBytes(StandardCharsets.US_ASCII); PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1); OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( new OneTimePasswordSpec(hash, seed, 500) ); credentials.add(new PasswordCredential(otpPassword)); identity1.setCredentials(credentials); identity1.dispose(); // checking result securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1); ModifiableRealmIdentity identity3 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); assertTrue(identity3.exists()); assertTrue(identity3.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); identity3.dispose(); } private FileSystemSecurityRealm createRealmWithTwoIdentities() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); identity1.create(); identity1.dispose(); ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); identity2.create(); identity2.dispose(); return securityRealm; } @Test public void testIterating() throws Exception { FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(); Iterator<ModifiableRealmIdentity> iterator = securityRealm.getRealmIdentityIterator(); int count = 0; while(iterator.hasNext()){ Assert.assertTrue(iterator.next().exists()); count++; } Assert.assertEquals(2, count); getRootPath(); // will fail on windows if iterator not closed correctly } @Test public void testIteratingNeedlessClose() throws Exception { FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(); Iterator<ModifiableRealmIdentity> iterator = securityRealm.getRealmIdentityIterator(); int count = 0; while(iterator.hasNext()){ Assert.assertTrue(iterator.next().exists()); count++; } Assert.assertEquals(2, count); ((Closeable) iterator).close(); // needless, already closed getRootPath(); // will fail on windows if iterator not closed correctly } @Test public void testPartialIterating() throws Exception { FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(); Iterator<ModifiableRealmIdentity> iterator = securityRealm.getRealmIdentityIterator(); Assert.assertTrue(iterator.hasNext()); Assert.assertTrue(iterator.next().exists()); Assert.assertTrue(iterator instanceof Closeable); ((Closeable) iterator).close(); getRootPath(); // will fail on windows if iterator not closed correctly } @Test public void testPartialIteratingTryWithResource() throws Exception { FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(); try(CloseableIterator<ModifiableRealmIdentity> iterator = securityRealm.getRealmIdentityIterator()) { Assert.assertTrue(iterator.hasNext()); Assert.assertTrue(iterator.next().exists()); } // try should ensure iterator closing getRootPath(); // will fail on windows if iterator not closed correctly } private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential) throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.setCredentials(Collections.singleton(new PasswordCredential(credential))); newIdentity.dispose(); securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); existingIdentity.dispose(); } private Path getRootPath(boolean deleteIfExists) throws Exception { Path rootPath = Paths.get(getClass().getResource(File.separator).toURI()) .resolve("filesystem-realm"); if (rootPath.toFile().exists() && !deleteIfExists) { return rootPath; } return Files.walkFileTree(Files.createDirectories(rootPath), new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } private Path getRootPath() throws Exception { return getRootPath(true); } }