diff options
author | Martin Ritchie <ritchiem@apache.org> | 2008-12-05 17:11:08 +0000 |
---|---|---|
committer | Martin Ritchie <ritchiem@apache.org> | 2008-12-05 17:11:08 +0000 |
commit | 93d7a8b0e385c703a175a99bc7795c9cb541d355 (patch) | |
tree | a7ef68a86396ec31e403bf2acbcbbf9acc8bf727 | |
parent | 134b171215d8a8cb648d75d64d25b6f420e90a2f (diff) | |
download | qpid-python-93d7a8b0e385c703a175a99bc7795c9cb541d355.tar.gz |
QPID-1503 : Update to Base64 to correctly save password file. Pulled User out of Base64MD5 to allow better testing. Added unit test case for Base64 and User
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk@723792 13f79535-47bb-0310-9956-ffa450edef68
3 files changed, 466 insertions, 124 deletions
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java index 348bccb4e9..86a0b7d961 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java @@ -21,30 +21,26 @@ package org.apache.qpid.server.security.auth.database; import org.apache.log4j.Logger; +import org.apache.qpid.server.security.access.management.AMQUserManagementMBean; import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; -import org.apache.qpid.server.security.access.management.AMQUserManagementMBean; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.EncoderException; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.BufferedReader; import java.io.FileReader; -import java.io.UnsupportedEncodingException; +import java.io.IOException; import java.io.PrintStream; -import java.util.regex.Pattern; -import java.util.Map; +import java.security.Principal; import java.util.HashMap; -import java.util.List; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.concurrent.locks.ReentrantLock; -import java.security.Principal; -import java.security.NoSuchAlgorithmException; +import java.util.regex.Pattern; /** * Represents a user database where the account information is stored in a simple flat file. @@ -64,7 +60,7 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase private Map<String, AuthenticationProviderInitialiser> _saslServers; AMQUserManagementMBean _mbean; - private static final String DEFAULT_ENCODING = "utf-8"; + public static final String DEFAULT_ENCODING = "utf-8"; private Map<String, User> _users = new HashMap<String, User>(); private ReentrantLock _userUpdate = new ReentrantLock(); @@ -284,7 +280,6 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase return true; } - public Map<String, AuthenticationProviderInitialiser> getMechanisms() { return _saslServers; @@ -325,7 +320,6 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase } } - private void loadPasswordFile() throws IOException { try @@ -382,6 +376,7 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase { tmp.delete(); } + try { writer = new PrintStream(tmp); @@ -394,6 +389,7 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase if (result == null || result.length < 2 || result[0].startsWith("#")) { writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); continue; } @@ -485,114 +481,4 @@ public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase } } - private class User implements Principal - { - String _name; - char[] _password; - byte[] _encodedPassword = null; - private boolean _modified = false; - private boolean _deleted = false; - - User(String[] data) throws UnsupportedEncodingException - { - if (data.length != 2) - { - throw new IllegalArgumentException("User Data should be lenght 2, username, password"); - } - - _name = data[0]; - - byte[] encoded_password = data[1].getBytes(DEFAULT_ENCODING); - - Base64 b64 = new Base64(); - byte[] decoded = b64.decode(encoded_password); - - _encodedPassword = encoded_password; - - _password = new char[decoded.length]; - - int index = 0; - for (byte c : decoded) - { - _password[index++] = (char) c; - } - } - - public User(String name, char[] password) - { - _name = name; - setPassword(password); - } - - public String getName() - { - return _name; - } - - public String toString() - { - if (_logger.isDebugEnabled()) - { - return getName() + ((_encodedPassword == null) ? "" : ":" + new String(_encodedPassword)); - } - else - { - return _name; - } - } - - char[] getPassword() - { - return _password; - } - - void setPassword(char[] password) - { - _password = password; - _modified = true; - _encodedPassword = null; - } - - - byte[] getEncodePassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException - { - if (_encodedPassword == null) - { - encodePassword(); - } - return _encodedPassword; - } - - private void encodePassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException - { - byte[] byteArray = new byte[_password.length]; - int index = 0; - for (char c : _password) - { - byteArray[index++] = (byte) c; - } - _encodedPassword = (new Base64()).encode(byteArray); - } - - public boolean isModified() - { - return _modified; - } - - public boolean isDeleted() - { - return _deleted; - } - - public void delete() - { - _deleted = true; - } - - public void saved() - { - _modified = false; - } - - } } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..5a74160b95 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,325 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.regex.Pattern; + +public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase +{ + + Base64MD5PasswordFilePrincipalDatabase _database; + private String QPID_HOME; + private static final String TEST_COMMENT = "# Test Comment"; + private String USERNAME = "testUser"; + private static final String TEST_FILE_NAME = "B64Test.tmp"; + + public void setUp() + { + _database = new Base64MD5PasswordFilePrincipalDatabase(); + + QPID_HOME = System.getProperty("QPID_HOME"); + + assertNotNull("QPID_HOME not set", QPID_HOME); + } + + public void tearDown() + { + File testFile = new File(QPID_HOME + File.separator + TEST_FILE_NAME); + if (testFile.exists()) + { + testFile.delete(); + } + + testFile = new File(QPID_HOME + File.separator + TEST_FILE_NAME + ".old"); + if (testFile.exists()) + { + testFile.delete(); + } + } + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = new File(QPID_HOME + File.separator + TEST_FILE_NAME); + if (testFile.exists()) + { + testFile.delete(); + } + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(USERNAME + i + ":Password"); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.setPasswordFile(file.toString()); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + /** **** Test Methods ************** */ + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "createdPassword"; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + loadPasswordFile(testFile); + + assertNotNull("Created User was not saved", _database.getUser(CREATED_USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + testFile.delete(); + } + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(USERNAME + "0")); + + testFile.delete(); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List<Principal> users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + + testFile.delete(); + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "NewPassword"; + String NEW_PASSWORD_HASH = "TmV3UGFzc3dvcmQ="; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD_HASH, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testSetPasswordWithMissingFile() + { + try + { + _database.setPasswordFile("DoesntExist"); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.setPasswordFile(testFile.toString()); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + testFile.delete(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/UserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/UserTest.java new file mode 100644 index 0000000000..99902ef4c5 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/UserTest.java @@ -0,0 +1,131 @@ +/* + * + * 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.qpid.server.security.auth.database; + +import junit.framework.TestCase; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; + +/* + Note User is mainly tested by Base64MD5PFPDTest this is just to catch the extra methods + */ +public class UserTest extends TestCase +{ + + String USERNAME = "username"; + String PASSWORD = "password"; + String HASHED_PASSWORD = "cGFzc3dvcmQ="; + + public void testToLongArrayConstructor() + { + try + { + User user = new User(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } + + public void testArrayConstructor() + { + try + { + User user = new User(new String[]{USERNAME, HASHED_PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] hash = HASHED_PASSWORD.toCharArray(); + + try + { + for (byte c : user.getEncodePassword()) + { + assertEquals("Password incorrect", hash[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + hash = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", hash[index], c); + index++; + } + + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } + + public void testToString() + { + + User user = new User(USERNAME, PASSWORD.toCharArray()); + + // Test logger debug case + Logger.getLogger(User.class).setLevel(Level.DEBUG); + + assertEquals("User toString encoding not as expected", USERNAME, user.toString()); + + try + { + char[] hash = HASHED_PASSWORD.toCharArray(); + int index = 0; + for (byte c : user.getEncodePassword()) + { + + assertEquals("Hash not as expected", hash[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + assertEquals("User toString encoding not as expected", USERNAME + ":" + HASHED_PASSWORD, + user.toString()); + + Logger.getLogger(User.class).setLevel(Level.INFO); + + // Test normal case + assertEquals("User toString encoding not as expected", USERNAME, user.toString()); + } + +} + |