/*
* JBoss, Home of Professional Open Source.
* Copyright 2015 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.realm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.security.Provider;
import java.security.Security;
import org.junit.AfterClass;
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.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.interfaces.DigestPassword;
import org.wildfly.security.util.ByteIterator;
/**
* A test case for the {@link LegacyPropertiesSecurityRealm}.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class LegacyPropertiesSecurityRealmTest {
private static final String PROPERTIES_CLEAR_CREDENTIAL_NAME = "the-clear-one-it-is";
private static final String PROPERTIES_DIGEST_CREDENTIAL_NAME = "the-digested-one-it-is";
private static final String ELYTRON_PASSWORD_HASH = "c588863654f886d1caae4d8af47107b7";
private static final String ELYTRON_PASSWORD_CLEAR = "passwd12#$";
private static final String ELYTRON_SIMPLE_PASSWORD = "password";
private static final Provider provider = new WildFlyElytronProvider();
private static SecurityRealm specialCharsRealm;
@BeforeClass
public static void add() throws IOException {
Security.addProvider(provider);
specialCharsRealm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(LegacyPropertiesSecurityRealmTest.class.getResourceAsStream("specialchars.properties"))
.setPlainText(true)
.build();
}
@AfterClass
public static void remove() {
Security.removeProvider(provider.getName());
}
/**
* Test case to verify that the default properties file can be loaded.
*
* @throws IOException
*/
@Test
public void testDefaultFile() throws IOException {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream("empty.properties"))
.build();
assertNotNull("SecurityRealm", realm);
}
/**
* Test that the realm can handle the properties file where the passwords are stored in the clear.
*/
@Test
public void testPlainFile() throws Exception {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream("clear.properties"))
.setPlainText(true)
.build();
PasswordGuessEvidence goodGuess = new PasswordGuessEvidence(ELYTRON_PASSWORD_CLEAR.toCharArray());
PasswordGuessEvidence badGuess = new PasswordGuessEvidence("I will hack you".toCharArray());
PasswordGuessEvidence hashGuess = new PasswordGuessEvidence(ELYTRON_PASSWORD_HASH.toCharArray());
assertEquals("ClearPassword", SupportLevel.SUPPORTED, realm.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.SUPPORTED, realm.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.SUPPORTED, realm.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
RealmIdentity elytronIdentity = realm.getRealmIdentity(new NamePrincipal("elytron"));
assertEquals("ClearPassword", SupportLevel.SUPPORTED, elytronIdentity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.SUPPORTED, elytronIdentity.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.SUPPORTED, elytronIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
ClearPassword elytronClear = elytronIdentity.getCredential(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).getPassword(ClearPassword.class);
assertNotNull(elytronClear);
assertEquals(ELYTRON_PASSWORD_CLEAR, new String(elytronClear.getPassword()));
DigestPassword elytronDigest = elytronIdentity.getCredential(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5).getPassword(DigestPassword.class);
assertNotNull(elytronDigest);
String actualHex = ByteIterator.ofBytes(elytronDigest.getDigest()).hexEncode().drainToString();
assertEquals(ELYTRON_PASSWORD_HASH, actualHex);
assertTrue(elytronIdentity.verifyEvidence(goodGuess));
assertFalse(elytronIdentity.verifyEvidence(badGuess));
assertFalse(elytronIdentity.verifyEvidence(hashGuess));
elytronIdentity.dispose();
RealmIdentity badIdentity = realm.getRealmIdentity(new NamePrincipal("noone"));
assertEquals("ClearPassword", SupportLevel.UNSUPPORTED, badIdentity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.UNSUPPORTED, badIdentity.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.UNSUPPORTED, badIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
assertNull(badIdentity.getCredential(Credential.class));
assertFalse(badIdentity.verifyEvidence(goodGuess));
assertFalse(badIdentity.verifyEvidence(badGuess));
badIdentity.dispose();
}
/**
* Test that the realm can handle the properties file where the passwords are stored pre-hashed.
*/
@Test
public void testHashedFile() throws Exception {
performHashedFileTest("users.properties", null);
}
/**
* Test that the realm can handle the properties file where the passwords are stored pre-hashed but without the realm specified in the properties file.
*/
@Test
public void testHashedFile_NoRealm() throws Exception {
performHashedFileTest("users-no-realm.properties", "ManagementRealm");
}
private void performHashedFileTest(final String fileName, final String defaultRealm) throws Exception {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream(fileName))
.setDefaultRealm(defaultRealm)
.build();
PasswordGuessEvidence goodGuess = new PasswordGuessEvidence(ELYTRON_PASSWORD_CLEAR.toCharArray());
PasswordGuessEvidence badGuess = new PasswordGuessEvidence("I will hack you".toCharArray());
PasswordGuessEvidence hashGuess = new PasswordGuessEvidence(ELYTRON_PASSWORD_HASH.toCharArray());
assertEquals("ClearPassword", SupportLevel.UNSUPPORTED, realm.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.SUPPORTED, realm.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.SUPPORTED, realm.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
RealmIdentity elytronIdentity = realm.getRealmIdentity(new NamePrincipal("elytron"));
assertEquals("ClearPassword", SupportLevel.UNSUPPORTED, elytronIdentity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.SUPPORTED, elytronIdentity.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.SUPPORTED, elytronIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
assertNotNull(elytronIdentity.getCredential(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
DigestPassword elytronDigest = elytronIdentity.getCredential(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5).getPassword(DigestPassword.class);
assertNotNull(elytronDigest);
String actualHex = ByteIterator.ofBytes(elytronDigest.getDigest()).hexEncode().drainToString();
assertEquals(ELYTRON_PASSWORD_HASH, actualHex);
assertTrue(elytronIdentity.verifyEvidence(goodGuess));
assertFalse(elytronIdentity.verifyEvidence(badGuess));
assertFalse(elytronIdentity.verifyEvidence(hashGuess));
elytronIdentity.dispose();
RealmIdentity badIdentity = realm.getRealmIdentity(new NamePrincipal("noone"));
assertEquals("ClearPassword", SupportLevel.UNSUPPORTED, badIdentity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals("DigestPassword", SupportLevel.UNSUPPORTED, badIdentity.getCredentialAcquireSupport(PasswordCredential.class, DigestPassword.ALGORITHM_DIGEST_MD5));
assertEquals("Verify", SupportLevel.UNSUPPORTED, badIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
assertNull(badIdentity.getCredential(Credential.class));
assertNull(badIdentity.getCredential(Credential.class));
assertFalse(badIdentity.verifyEvidence(goodGuess));
assertFalse(badIdentity.verifyEvidence(badGuess));
badIdentity.dispose();
}
@Test
public void testGroups() throws Exception {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream("users.properties"))
.setGroupsStream(this.getClass().getResourceAsStream("groups.properties"))
.setGroupsAttribute("groups")
.build();
RealmIdentity elytronIdentity = realm.getRealmIdentity(new NamePrincipal("elytron"));
assertTrue(elytronIdentity.getAuthorizationIdentity().getAttributes().get("groups").contains("role1"));
assertTrue(elytronIdentity.getAuthorizationIdentity().getAttributes().get("groups").contains("role2"));
elytronIdentity.dispose();
RealmIdentity rolemanIdentity = realm.getRealmIdentity(new NamePrincipal("roleman"));
assertTrue(rolemanIdentity.getAuthorizationIdentity().getAttributes().get("groups").contains("role3"));
assertTrue(rolemanIdentity.getAuthorizationIdentity().getAttributes().get("groups").contains("role4"));
rolemanIdentity.dispose();
}
@Test
public void testPlainFileSpecialChars() throws Exception {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream("clear-special.properties"))
.setPlainText(true)
.build();
testClear(realm, "elytron", "password");
testClear(realm, "space man", "space password");
testClear(realm, "elytronumlautöäü", "password");
testClear(realm, "elytron用戶", "password");
testClear(realm, "backslash\\", "password");
testClear(realm, "backslash\\inthemiddle", "password");
testClear(realm, "dn=elytron,dc=wildfly,dc=org", "password");
testClear(realm, "elytron1", "pass=word");
testClear(realm, "elytron2", "password\\");
testClear(realm, "elytron3", "pass\\word");
testClear(realm, "elytron4", "passwordWithumlautöäü");
testClear(realm, "elytron5", "用戶");
}
private void testClear(SecurityRealm realm, String username, String password) throws Exception {
RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal(username));
assertTrue("Exists", identity.exists());
ClearPassword elytronClear = identity.getCredential(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).getPassword(ClearPassword.class);
assertEquals(password, new String(elytronClear.getPassword()));
identity.dispose();
}
/**
* Test that lines started with explanation mark in user property file are considered as comment.
*/
@Test
public void testSpecialChar_exclamationMarkAsComment() throws Exception {
checkVerifyIdentityFail(specialCharsRealm, "elytronWithExclamationMark", ELYTRON_SIMPLE_PASSWORD);
checkVerifyIdentityFail(specialCharsRealm, "!elytronWithExclamationMark", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain '@' character.
*/
@Test
public void testSpecialChar_atSignUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron@JBOSS.ORG", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain umlaut characters.
*/
@Test
public void testSpecialChar_umlautsUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytronumlautöäü", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain Chinese characters.
*/
@Test
public void testSpecialChar_chineseUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron用戶", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username is case sensitive.
*/
@Test
public void testSpecialChar_differentCasesUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "ElYtRoNuSeR", ELYTRON_SIMPLE_PASSWORD);
checkVerifyIdentityFail(specialCharsRealm, "elytronuser", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can finish with backslash.
*/
@Test
public void testSpecialChar_endBackslashUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "backslash\\", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain backslash.
*/
@Test
public void testSpecialChar_backslashInTheMiddleUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "backslash\\inthemiddle", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain '"' (double quote) character.
*/
@Test
public void testSpecialChar_quoteInUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "double\"qoute", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that username in property file can contain '=' character.
*/
@Test
public void testSpecialChar_equalsSignUsername() throws Exception {
checkVerifyIdentity(specialCharsRealm, "dn=elytron,dc=wildfly,dc=org", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that password in property file can contain '=' character.
*/
@Test
public void testSpecialChar_equalsSignPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron1", "pass=word");
}
/**
* Test that password in property file can contain escaped '=' character.
*/
@Test
public void testSpecialChar_escapedEqualsSignPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron2", "pass=word");
}
/**
* Test that password in property file can finish with backslash.
*/
@Test
public void testSpecialChar_endBackslashPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron3", "password\\");
}
/**
* Test that password in property file can contain backslash.
*/
@Test
public void testSpecialChar_backslashInTheMiddlePassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron4", "pass\\word");
}
/**
* Test that password in property file can contain '"' (double quote) character.
*/
@Test
public void testSpecialChar_quoteInPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron5", "pass\"word");
}
/**
* Test that password in property file can contain umlaut characters.
*/
@Test
public void testSpecialChar_umlautsPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron6", "passwordWithumlautöäü");
}
/**
* Test that password in property file can contain Chinese characters.
*/
@Test
public void testSpecialChar_chinesePassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron7", "用戶");
}
/**
* Test that password is case sensitive.
*/
@Test
public void testSpecialChar_differentCasesPassword() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron8", "PaSsWoRd", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that colon can be used as delimiter for username and password in plain text property file.
*/
@Test
public void testPlainFile_colonAsDelimiter() throws Exception {
checkVerifyIdentity(specialCharsRealm, "elytron", ELYTRON_SIMPLE_PASSWORD);
}
/**
* Test that colon can be used as delimiter for username and password in hashed property file.
*/
@Test
public void testHashedFile_colonAsDelimiter() throws Exception {
SecurityRealm realm = LegacyPropertiesSecurityRealm.builder()
.setUsersStream(this.getClass().getResourceAsStream("colondelimiter.properties"))
.build();
checkVerifyIdentity(realm, "elytron", ELYTRON_PASSWORD_CLEAR);
}
private void checkVerifyIdentity(SecurityRealm realm, String username, String goodPassword)
throws RealmUnavailableException {
checkVerifyIdentity(realm, username, goodPassword, "wrongPassword");
}
private void checkVerifyIdentity(SecurityRealm realm, String username, String goodPassword,
String wrongPassword) throws RealmUnavailableException {
RealmIdentity elytronIdentity = realm.getRealmIdentity(new NamePrincipal(username));
assertTrue(elytronIdentity.exists());
PasswordGuessEvidence goodPasswordEvidence = new PasswordGuessEvidence(goodPassword.toCharArray());
PasswordGuessEvidence wrongPasswordEvidence = new PasswordGuessEvidence(wrongPassword.toCharArray());
assertTrue(elytronIdentity.verifyEvidence(goodPasswordEvidence));
assertFalse(elytronIdentity.verifyEvidence(wrongPasswordEvidence));
elytronIdentity.dispose();
}
private void checkVerifyIdentityFail(SecurityRealm realm, String username, String password)
throws RealmUnavailableException {
RealmIdentity elytronIdentity = realm.getRealmIdentity(new NamePrincipal(username));
PasswordGuessEvidence passwordEvidence = new PasswordGuessEvidence(password.toCharArray());
assertFalse(elytronIdentity.verifyEvidence(passwordEvidence));
elytronIdentity.dispose();
}
}