/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) and * Steven Grimm <koreth[remove] at midwinter dot com> * Licensed under the Apache License, Version 2.0 (the "License") * $Id: MemoryUsers.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.authentication.credentialsmanagers; import com.uwyn.rife.authentication.credentialsmanagers.exceptions.*; import com.uwyn.rife.authentication.Credentials; import com.uwyn.rife.authentication.CredentialsManager; import com.uwyn.rife.authentication.PasswordEncrypting; import com.uwyn.rife.authentication.credentials.RoleUserCredentials; import com.uwyn.rife.authentication.credentialsmanagers.RoleUserAttributes; import com.uwyn.rife.authentication.exceptions.CredentialsManagerException; import com.uwyn.rife.rep.Participant; import com.uwyn.rife.rep.Rep; import com.uwyn.rife.resources.ResourceFinder; import com.uwyn.rife.tools.FileUtils; import com.uwyn.rife.tools.SortListComparables; import com.uwyn.rife.tools.StringEncryptor; import com.uwyn.rife.tools.StringUtils; import com.uwyn.rife.tools.exceptions.FileUtilsErrorException; import com.uwyn.rife.xml.exceptions.XmlErrorException; import java.io.File; import java.net.URL; import java.net.URLDecoder; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class MemoryUsers implements CredentialsManager, RoleUsersManager, PasswordEncrypting { public final static String DEFAULT_PARTICIPANT_NAME = "ParticipantMemoryUsers"; private Map<Long, String> mUserIdMapping = new HashMap<Long, String>(); private Map<String, RoleUserAttributes> mUsers = new TreeMap<String, RoleUserAttributes>(); private Map<String, ArrayList<String>> mRoles = new TreeMap<String, ArrayList<String>>(); private long mUserIdSequence = 0; private String mXmlPath = null; private ResourceFinder mResourceFinder = null; protected StringEncryptor mPasswordEncryptor = null; public MemoryUsers() { } public MemoryUsers(String xmlPath, ResourceFinder resourceFinder) throws CredentialsManagerException { if (null == xmlPath) throw new IllegalArgumentException("xmlPath can't be null."); if (0 == xmlPath.length()) throw new IllegalArgumentException("xmlPath can't be empty."); if (null == resourceFinder) throw new IllegalArgumentException("resourceFinder can't be null."); mXmlPath = xmlPath; mResourceFinder = resourceFinder; initialize(); } /** * Retrieves the path of the XML document that populated this * {@code MemoryUsers} instance. * * @return the path of the XML document that populated this * {@code MemoryUsers} instance * * @since 1.0 */ public String getXmlPath() { return mXmlPath; } public StringEncryptor getPasswordEncryptor() { return mPasswordEncryptor; } public void setPasswordEncryptor(StringEncryptor passwordEncryptor) { mPasswordEncryptor = passwordEncryptor; } public static boolean hasRepInstance() { return Rep.hasParticipant(DEFAULT_PARTICIPANT_NAME); } public static MemoryUsers getRepInstance() { Participant participant = Rep.getParticipant(DEFAULT_PARTICIPANT_NAME); if (null == participant) { return null; } return (MemoryUsers)participant.getObject(); } public long verifyCredentials(Credentials credentials) throws CredentialsManagerException { RoleUserCredentials role_user = null; if (credentials instanceof RoleUserCredentials) { role_user = (RoleUserCredentials)credentials; } else { throw new UnsupportedCredentialsTypeException(credentials); } synchronized (this) { if (null == role_user.getLogin()) { return -1; } RoleUserAttributes user_attributes = mUsers.get(role_user.getLogin()); if (null == user_attributes) { return -1; } // correctly handle encoded passwords String password = null; try { password = StringEncryptor.adaptiveEncrypt(role_user.getPassword(), user_attributes.getPassword()); } catch (NoSuchAlgorithmException e) { throw new VerifyCredentialsErrorException(credentials, e); } // handle roles if (role_user.getRole() != null) { if (user_attributes.isValid(password, role_user.getRole())) { return mUsers.get(role_user.getLogin()).getUserId(); } } else { if (user_attributes.isValid(password)) { return mUsers.get(role_user.getLogin()).getUserId(); } } } return -1; } public MemoryUsers addRole(String role) throws CredentialsManagerException { if (null == role || 0 == role.length()) { throw new AddRoleErrorException(role); } if (mRoles.containsKey(role)) { throw new DuplicateRoleException(role); } mRoles.put(role, new ArrayList<String>()); return this; } public long countRoles() { return mRoles.size(); } public boolean containsRole(String role) { if (null == role || 0 == role.length()) { return false; } return mRoles.containsKey(role); } public MemoryUsers addUser(String login, RoleUserAttributes attributes) throws CredentialsManagerException { if (null == login || 0 == login.length() || null == attributes) { throw new AddUserErrorException(login, attributes); } synchronized (this) { // throw an exception if the user already exists if (mUsers.containsKey(login)) { throw new DuplicateLoginException(login); } // correctly handle implicit and specific user ids if (-1 == attributes.getUserId()) { while (mUserIdMapping.containsKey(new Long(mUserIdSequence))) { // FIXME: check for long overflow mUserIdSequence++; } attributes.setUserId(mUserIdSequence); attributes.setAutomaticUserId(true); mUserIdMapping.put(new Long(mUserIdSequence), login); } else { if (mUserIdMapping.containsKey(new Long(attributes.getUserId()))) { throw new DuplicateUserIdException(attributes.getUserId()); } mUserIdMapping.put(new Long(attributes.getUserId()), login); } // correctly handle password encoding RoleUserAttributes attributes_clone = attributes.clone(); if (mPasswordEncryptor != null && !attributes_clone.getPassword().startsWith(mPasswordEncryptor.toString())) { try { attributes_clone.setPassword(mPasswordEncryptor.encrypt(attributes_clone.getPassword())); } catch (NoSuchAlgorithmException e) { throw new AddUserErrorException(login, attributes, e); } } mUsers.put(login, attributes_clone); // create reverse links from the roles to the logins createRoleLinks(login, attributes_clone); } return this; } private void createRoleLinks(String login, RoleUserAttributes attributes) throws CredentialsManagerException { assert login != null; assert login.length() > 0; if (attributes.getRoles() != null && attributes.getRoles().size() > 0) { ArrayList<String> logins = null; for (String role : attributes.getRoles()) { if (!mRoles.containsKey(role)) { throw new UnknownRoleErrorException(role, login, attributes); } else { logins = mRoles.get(role); if (!logins.contains(login)) { logins.add(login); } } } } } public RoleUserAttributes getAttributes(String login) { if (null == login || 0 == login.length()) { return null; } return mUsers.get(login); } public long countUsers() { return mUsers.size(); } public boolean listRoles(ListRoles processor) { if (null == processor) { return false; } if (0 == mRoles.size()) { return true; } boolean result = false; for (String role : mRoles.keySet()) { result = true; if (!processor.foundRole(role)) { break; } } return result; } public boolean listUsers(ListUsers processor) { if (null == processor) { return false; } if (0 == mUsers.size()) { return false; } boolean result = false; RoleUserAttributes attributes = null; for (String login : mUsers.keySet()) { result = true; attributes = mUsers.get(login); if (!processor.foundUser(attributes.getUserId(), login, attributes.getPassword())) { break; } } return result; } public boolean listUsers(ListUsers processor, int limit, int offset) { if (null == processor || limit <= 0 || 0 == mUsers.size()) { return false; } boolean result = false; RoleUserAttributes attributes = null; int count = 0; for (String login : mUsers.keySet()) { if (count < offset) { count++; continue; } if (count-offset >= limit) { break; } count++; result = true; attributes = mUsers.get(login); if (!processor.foundUser(attributes.getUserId(), login, attributes.getPassword())) { break; } } return result; } public boolean containsUser(String login) { if (null == login || 0 == login.length()) { return false; } synchronized (this) { return mUsers.containsKey(login); } } public boolean listUsersInRole(ListUsers processor, String role) throws CredentialsManagerException { if (null == processor) { return false; } if (null == role || 0 == role.length()) { return false; } if (0 == mUsers.size()) { return false; } boolean result = false; RoleUserAttributes attributes = null; for (String login : mUsers.keySet()) { attributes = mUsers.get(login); if (null == attributes.getRoles() || !attributes.getRoles().contains(role)) { continue; } result = true; if (!processor.foundUser(attributes.getUserId(), login, attributes.getPassword())) { break; } } return result; } public boolean isUserInRole(long userId, String role) { if (userId < 0 || null == role || 0 == role.length()) { return false; } synchronized (this) { String login = mUserIdMapping.get(new Long(userId)); if (null == login) { return false; } RoleUserAttributes user_attributes = mUsers.get(login); if (null == user_attributes) { return false; } return user_attributes.isInRole(role); } } public String getLogin(long userId) { if (userId < 0) { return null; } String login = null; synchronized (this) { login = mUserIdMapping.get(new Long(userId)); } return login; } public long getUserId(String login) { if (null == login || 0 == login.length()) { return -1; } long userid = -1; synchronized (this) { RoleUserAttributes attributes = mUsers.get(login); if (attributes != null) { userid = attributes.getUserId(); } } return userid; } public boolean updateUser(String login, RoleUserAttributes attributes) throws CredentialsManagerException { if (null == login || 0 == login.length() || null == attributes) { throw new UpdateUserErrorException(login, attributes); } synchronized (this) { if (!mUsers.containsKey(login)) { return false; } // get the current attributes RoleUserAttributes current_attributes = mUsers.get(login); // set the current password if it has not been provided RoleUserAttributes attributes_clone = attributes.clone(); if (null == attributes_clone.getPassword()) { attributes_clone.setPassword(current_attributes.getPassword()); } else { // correctly handle password encoding if (mPasswordEncryptor != null && !attributes_clone.getPassword().startsWith(mPasswordEncryptor.toString())) { try { attributes_clone.setPassword(mPasswordEncryptor.encrypt(attributes_clone.getPassword())); } catch (NoSuchAlgorithmException e) { throw new UpdateUserErrorException(login, attributes, e); } } } // ensure that the user id remains the same attributes_clone.setUserId(current_attributes.getUserId()); // update the reverse link from the roles collection removeRoleLinks(login); // store the new user attributes mUsers.put(login, attributes_clone); // create reverse links from the roles to the logins createRoleLinks(login, attributes_clone); } return true; } public boolean removeUser(String login) { if (null == login || 0 == login.length()) { return false; } synchronized (this) { // update the reverse link from the roles collection removeRoleLinks(login); // remove the user return null != mUsers.remove(login); } } public boolean removeUser(long userId) { if (userId < 0) { return false; } String login = null; synchronized (this) { if (null == mUserIdMapping.get(userId)) { return false; } else { login = mUserIdMapping.get(userId); // update the reverse link from the roles collection removeRoleLinks(login); // remove the user return null != mUsers.remove(login); } } } public boolean removeRole(String name) { if (null == name || 0 == name.length()) { return false; } synchronized (this) { if (mRoles.remove(name) == null) { return false; } for (String key : mUsers.keySet()) { Collection<String> roles = mUsers.get(key).getRoles(); if (roles != null && roles.contains(name)) { roles.remove(name); } } } return true; } private void removeRoleLinks(String login) { assert login != null; assert login.length() > 0; RoleUserAttributes attributes = mUsers.get(login); if (attributes != null && attributes.getRoles() != null && attributes.getRoles().size() > 0) { // remove the login from the roles it's registered for ArrayList<String> logins = null; ArrayList<String> roles_to_delete = null; for (String role : attributes.getRoles()) { logins = mRoles.get(role); logins.remove(login); if (0 == logins.size()) { if (null == roles_to_delete) { roles_to_delete = new ArrayList<String>(); } roles_to_delete.add(role); } } // remove the roles that now don't have any logins anymore if (roles_to_delete != null) { for (String role : roles_to_delete) { mRoles.remove(role); } } } } public void clearUsers() { synchronized (this) { mUsers = new TreeMap<String, RoleUserAttributes>(); mRoles = new TreeMap<String, ArrayList<String>>(); } } public boolean listUserRoles(String login, ListRoles processor) throws CredentialsManagerException { if (null == mUsers.get(login)) { return false; } if (null == processor) { return false; } if (0 == mRoles.size()) { return true; } boolean result = false; for (String role : mRoles.keySet()) { RoleUserAttributes attributes = null; synchronized (this) { attributes = mUsers.get(login); } if (attributes.isInRole(role)) { result = true; if (!processor.foundRole(role)) { break; } } } return result; } private void initialize() throws CredentialsManagerException { try { Xml2MemoryUsers xml_memoryusers = new Xml2MemoryUsers(); xml_memoryusers.processXml(mXmlPath, mResourceFinder); synchronized (this) { RoleUserAttributes attributes; for (Map.Entry<String, RoleUserAttributes> user_entry : xml_memoryusers.getUsers().entrySet()) { attributes = user_entry.getValue(); for (String role : attributes.getRoles()) { if (!containsRole(role)) { addRole(role); } } addUser(user_entry.getKey(), attributes); } } } catch (XmlErrorException e) { throw new InitializationErrorException(mXmlPath, e); } } public String toXml() { StringBuilder xml_output = new StringBuilder(); xml_output.append("<credentials>\n"); SortListComparables arraylist_sort = new SortListComparables(); ArrayList<String> logins_list = new ArrayList<String>(mUsers.keySet()); RoleUserAttributes user_attributes = null; arraylist_sort.sort(logins_list); for (String login : logins_list) { user_attributes = mUsers.get(login); xml_output.append("\t<user login=\"").append(StringUtils.encodeXml(login)).append("\""); if (!user_attributes.isAutomaticUserId()) { xml_output.append(" userid=\"").append(user_attributes.getUserId()).append("\""); } xml_output.append(">\n"); xml_output.append("\t\t<password>").append(StringUtils.encodeXml(user_attributes.getPassword())).append("</password>\n"); if (user_attributes.getRoles() != null && user_attributes.getRoles().size() > 0) { ArrayList<String> roles = new ArrayList<String>(user_attributes.getRoles()); arraylist_sort.sort(roles); for (String role : roles) { xml_output.append("\t\t<role>").append(StringUtils.encodeXml(role)).append("</role>\n"); } } xml_output.append("\t</user>\n"); } xml_output.append("</credentials>\n"); return xml_output.toString(); } public void storeToXml() throws CredentialsManagerException { String xmlpath = null; URL xmlpath_resource = null; xmlpath = getXmlPath(); if (null == xmlpath) { throw new MissingXmlPathException(); } xmlpath_resource = mResourceFinder.getResource(xmlpath); if (null == xmlpath_resource) { throw new CantFindXmlPathException(xmlpath); } storeToXml(new File(URLDecoder.decode(xmlpath_resource.getPath()))); } public synchronized void storeToXml(File destination) throws CredentialsManagerException { if (null == destination || destination.exists() && !destination.canWrite()) { throw new CantWriteToDestinationException(destination); } StringBuilder content = new StringBuilder("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"); content.append("<!DOCTYPE credentials SYSTEM \"/dtd/users.dtd\">\n"); content.append(toXml()); try { FileUtils.writeString(content.toString(), destination); } catch (FileUtilsErrorException e) { throw new StoreXmlErrorException(destination, e); } } }