diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2012-03-10 19:22:10 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2012-03-10 19:22:10 +0000 |
commit | 4eaa4e42093e5524d9552d8fa312c214524b6bb4 (patch) | |
tree | a251d57ee92d9c779fe4455c583be0ed90e69a43 /qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java | |
parent | 92be7e8f3163c048a8642d2deeaa921bbb65dc9c (diff) | |
download | qpid-python-4eaa4e42093e5524d9552d8fa312c214524b6bb4.tar.gz |
NO-JIRA : AMQP-1-0 sandbox updates - merge from trunkrg-amqp-1-0-sandbox
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/rg-amqp-1-0-sandbox@1299257 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java')
-rw-r--r-- | qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..7088fae50c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java @@ -0,0 +1,484 @@ +/* + * + * 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 org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +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.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +public abstract class AbstractPasswordFilePrincipalDatabase<U extends PasswordPrincipal> implements PrincipalDatabase +{ + private final Pattern _regexp = Pattern.compile(":"); + + private final Map<String, AuthenticationProviderInitialiser> _saslServers = + new HashMap<String, AuthenticationProviderInitialiser>(); + + protected static final String DEFAULT_ENCODING = "utf-8"; + private final Map<String, U> _userMap = new HashMap<String, U>(); + private final ReentrantLock _userUpdate = new ReentrantLock(); + private final Random _random = new Random(); + private File _passwordFile; + + + protected AbstractPasswordFilePrincipalDatabase(UsernamePasswordInitialiser... initialisers) + { + for(UsernamePasswordInitialiser initialiser : initialisers) + { + initialiser.initialise(this); + _saslServers.put(initialiser.getMechanismName(), initialiser); + } + } + + public final void setPasswordFile(String passwordFile) throws IOException + { + File f = new File(passwordFile); + getLogger().info("PasswordFile using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws javax.security.auth.login.AccountNotFoundException If the Principal cannot be found in this Database + */ + public final void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + char[] pwd = lookupPassword(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + + /** + * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it + * creates strings of passwords. It should be modified to create only char arrays which get nulled out. + * + * @param name The principal name to lookup + * + * @return a char[] for use in SASL. + */ + protected final char[] lookupPassword(String name) + { + U user = _userMap.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + protected boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + U user = _userMap.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + char[] orig = user.getPassword(); + _userUpdate.lock(); + try + { + user.setPassword(password); + + savePasswordFile(); + + return true; + } + catch (IOException e) + { + getLogger().error("Unable to save password file due to '" + e.getMessage() + + "', password change for user '" + principal + "' discarded"); + //revert the password change + user.restorePassword(orig); + + return false; + } + finally + { + _userUpdate.unlock(); + } + } + + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + _userMap.clear(); + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + continue; + } + + U user = createUserFromFileData(result); + getLogger().info("Created user:" + user); + _userMap.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + finally + { + _userUpdate.unlock(); + } + } + + protected abstract U createUserFromFileData(String[] result); + + + protected abstract Logger getLogger(); + + protected File createTempFileOnSameFilesystem() + { + File liveFile = _passwordFile; + File tmp; + + do + { + tmp = new File(liveFile.getPath() + _random.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + return tmp; + } + + protected void swapTempFileToLive(final File temp) throws IOException + { + File live = _passwordFile; + // Remove any existing ".old" file + final File old = new File(live.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + // Create an new ".old" file + if(!live.renameTo(old)) + { + //unable to rename the existing file to the backup name + getLogger().error("Could not backup the existing password file"); + throw new IOException("Could not backup the existing password file"); + } + + // Move temp file to be the new "live" file + if(!temp.renameTo(live)) + { + //failed to rename the new file to the required filename + if(!old.renameTo(live)) + { + //unable to return the backup to required filename + getLogger().error( + "Could not rename the new password file into place, and unable to restore original file"); + throw new IOException("Could not rename the new password file into place, and unable to restore original file"); + } + + getLogger().error("Could not rename the new password file into place"); + throw new IOException("Could not rename the new password file into place"); + } + } + + protected void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + + File tmp = createTempFileOnSameFilesystem(); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + U user = _userMap.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + byte[] encodedPassword = user.getEncodedPassword(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + + user.saved(); + } + } + } + + for (U user : _userMap.values()) + { + if (user.isModified()) + { + byte[] encodedPassword; + encodedPassword = user.getEncodedPassword(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + user.saved(); + } + } + } + catch(IOException e) + { + getLogger().error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file",e); + } + finally + { + + try + { + if (reader != null) + { + reader.close(); + } + } + finally + { + if (writer != null) + { + writer.close(); + } + } + + } + + swapTempFileToLive(tmp); + } + finally + { + _userUpdate.unlock(); + } + } + + protected abstract U createUserFromPassword(Principal principal, char[] passwd); + + + public void reload() throws IOException + { + loadPasswordFile(); + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(_userMap.values()); + } + + public Principal getUser(String username) + { + if (_userMap.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + U user = _userMap.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + getLogger().error("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _userMap.remove(user.getName()); + } + finally + { + _userUpdate.unlock(); + } + + return true; + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_userMap.get(principal.getName()) != null) + { + return false; + } + + U user = createUserFromPassword(principal, password); + + + try + { + _userUpdate.lock(); + _userMap.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _userMap.remove(user.getName()); + return false; + } + } + finally + { + _userUpdate.unlock(); + } + } +} |