Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.application.djigzo.james.mailets; import java.security.SecureRandom; import java.util.Collection; import java.util.HashSet; import javax.mail.MessagingException; import mitm.application.djigzo.User; import mitm.application.djigzo.UserProperties; import mitm.application.djigzo.james.DjigzoMailAttributes; import mitm.application.djigzo.james.DjigzoMailAttributesImpl; import mitm.application.djigzo.james.MailAddressUtils; import mitm.application.djigzo.james.PasswordContainer; import mitm.application.djigzo.james.Passwords; import mitm.application.djigzo.james.UserPersister; import mitm.application.djigzo.service.SystemServices; import mitm.application.djigzo.workflow.UserWorkflow; import mitm.common.hibernate.DatabaseActionExecutor; import mitm.common.hibernate.DatabaseActionExecutorBuilder; import mitm.common.hibernate.DatabaseActionRetryEvent; import mitm.common.hibernate.SessionManager; import mitm.common.properties.HierarchicalPropertiesException; import mitm.common.security.SecurityFactoryFactory; import mitm.common.security.crypto.EncryptorException; import mitm.common.util.Check; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.mailet.Mail; /** * Mailet that generates User passwords for all recipients. The generated password will be of length passwordSize * and will be stored in the user properties and attached to the message as a Mail attribute * (see {@link DjigzoMailAttributes}). Each generated password will have an associated password ID. The password * ID is used to identify the generated password. The password ID is based on the current time in milliseconds. * To make the password ID smaller the current time is subtracted by the baseTime. uniqueID is used to make sure * that the password ID will be 100% unique when multiple * servers are used. * * Supported mailet parameters: * * baseTime : Date (format yyyy-MM-dd) that will be subtracted from the current time (default: 0) * uniqueID : If multiple servers are used this is used to make sure the password ID is unique * catchRuntimeExceptions : If true all RunTimeExceptions are caught * catchErrors : If true all Errors are caught * * @author Martijn Brinkers * */ public abstract class AbstractGeneratePassword extends AbstractDjigzoMailet { /* * The number of times a database action should be retried when a ConstraintViolation occurs */ private final static int ACTION_RETRIES = 3; /* * The activation context key */ protected final static String ACTIVATION_CONTEXT_KEY = "passwords"; /* * Used for generating the password IDs */ private SecureRandom randomGenerator; /* * manages database sessions */ private SessionManager sessionManager; /* * Used for adding and retrieving users */ private UserWorkflow userWorkflow; /* * Used to execute database actions in a transaction */ private DatabaseActionExecutor actionExecutor; /* * The the bound on the random number to be used for the password ID */ private int randomIDBound = Integer.MAX_VALUE; /* * Because there can be multiple servers (or multiple instances of this mailet) and the password ID is * based on a counter and some time in minutes there should be a way to distinguish between the different * instances because otherwise there is a chance that the same password ID is generated when a message * is sent to the same user. */ private String uniqueID; /* * Size in bytes of the passwords. */ private int defaultPasswordLength = 8; /* * Callback used to track user objects. */ protected static class ActivationContext { /* * The users with a password */ private final Collection<User> users = new HashSet<User>(); /* * The passwords */ private final Passwords passwords = new Passwords(); /* * The mail object which is currently handled */ private Mail mail; /* * General member which can be used by classes that extend this class * to store something in the context */ private Object tag; public Collection<User> getUsers() { return users; } public Passwords getPasswords() { return passwords; } public void clear() { users.clear(); passwords.clear(); } public Mail getMail() { return mail; } public void setMail(Mail mail) { this.mail = mail; } public Object getTag() { return tag; } public void setTag(Object tag) { this.tag = tag; } } /* * The mailet initialization parameters used by this mailet. */ private enum Parameter { RANDOM_ID_BOUND("randomIDBound"), UNIQUE_ID("uniqueID"), DEFAULT_PASSWORD_LENGTH("defaultPasswordLength"); private String name; private Parameter(String name) { this.name = name; } }; private void initRandomIDBound() throws MessagingException { String param = getInitParameter(Parameter.RANDOM_ID_BOUND.name); if (param != null) { randomIDBound = NumberUtils.toInt(param, randomIDBound); if (randomIDBound <= 0) { throw new IllegalArgumentException("randomIDBound must be > 0"); } } } private void initUniqueID() { uniqueID = getInitParameter(Parameter.UNIQUE_ID.name); } private void initDefaultPasswordLength() { String param = getInitParameter(Parameter.DEFAULT_PASSWORD_LENGTH.name); if (param != null) { defaultPasswordLength = NumberUtils.toInt(param, defaultPasswordLength); if (defaultPasswordLength <= 0) { throw new IllegalArgumentException("defaultPasswordLength must be > 0"); } } } @Override public void initMailet() throws MessagingException { super.initMailet(); initRandomIDBound(); initUniqueID(); initDefaultPasswordLength(); try { randomGenerator = SecurityFactoryFactory.getSecurityFactory().createSecureRandom(); } catch (Exception e) { throw new MessagingException("SecureRandom instance could not be created", e); } sessionManager = SystemServices.getSessionManager(); userWorkflow = SystemServices.getUserWorkflow(); actionExecutor = DatabaseActionExecutorBuilder.createDatabaseActionExecutor(sessionManager); assert (actionExecutor != null); StrBuilder sb = new StrBuilder(); sb.append("randomIDBound: "); sb.append(randomIDBound); sb.append("; "); sb.append("UniqueID: "); sb.append(uniqueID); sb.append("; "); sb.append("defaultPasswordLength: "); sb.append(defaultPasswordLength); getLogger().info(sb.toString()); } /* * Create a unique password id based on a random integer */ protected synchronized String generatePasswordID() { String passwordID = Integer.toString(randomGenerator.nextInt(randomIDBound)); if (uniqueID != null) { passwordID = uniqueID + passwordID; } return passwordID; } protected int getPasswordLength(UserProperties userProperties) throws HierarchicalPropertiesException { int passwordLength; passwordLength = userProperties.getPasswordLength(); if (passwordLength <= 0) { passwordLength = defaultPasswordLength; } return passwordLength; } protected abstract String generatePassword(User user, int passwordLength, String passwordID) throws MessagingException; /* * Gets called by MailAddressHandler#handleRecipients to handle the user */ protected void onHandleUserEvent(User user) throws MessagingException { ActivationContext passwordActivationContext = getActivationContext().get(ACTIVATION_CONTEXT_KEY, ActivationContext.class); Check.notNull(passwordActivationContext, "passwordActivationContext"); UserProperties userProperties = user.getUserPreferences().getProperties(); String passwordID = generatePasswordID(); String password; int passwordLength; try { passwordLength = getPasswordLength(userProperties); password = generatePassword(user, passwordLength, passwordID); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error generating password", e); } getLogger().debug("PasswordID for user '" + user.getEmail() + "': " + passwordID); try { userProperties.setPassword(password); userProperties.setPasswordID(passwordID); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Unable to set password for user " + user.getEmail(), e); } PasswordContainer passwordContainer; try { passwordContainer = new PasswordContainer(password, passwordID); passwordContainer.setPasswordLength(passwordLength); } catch (EncryptorException e) { throw new MessagingException("Unable to create PasswordContainer.", e); } passwordActivationContext.getPasswords().put(user.getEmail(), passwordContainer); passwordActivationContext.getUsers().add(user); } private void makeNonPersistentUsersPersistent(Collection<User> users) { UserPersister userPersister = new UserPersister(userWorkflow, sessionManager, actionExecutor); userPersister.tryToMakeNonPersistentUsersPersistent(users); } private void addPasswordsAttribute(Mail mail, Passwords passwords) { DjigzoMailAttributes attributes = new DjigzoMailAttributesImpl(mail); attributes.setPasswords(passwords); } private void handleRetry() { /* * When the database action is retried we should clear the activation context. */ ActivationContext passwordActivationContext = getActivationContext().get(ACTIVATION_CONTEXT_KEY, ActivationContext.class); passwordActivationContext.clear(); } @Override public void serviceMail(Mail mail) { try { ActivationContext passwordsActivationContext = new ActivationContext(); /* * The mail object will be store in the context for other mailets */ passwordsActivationContext.setMail(mail); /* * Place the passwordsActivationContext in the context so we can use it in onHandleUserEvent */ getActivationContext().set(ACTIVATION_CONTEXT_KEY, passwordsActivationContext); MailAddressHandler.HandleUserEventHandler eventHandler = new MailAddressHandler.HandleUserEventHandler() { @Override public void handleUser(User user) throws MessagingException { onHandleUserEvent(user); } }; /* * We must clear the generated passwords when the action is retried */ DatabaseActionRetryEvent retryHandler = new DatabaseActionRetryEvent() { @Override public void onRetry() { handleRetry(); } }; MailAddressHandler mailAddressHandler = new MailAddressHandler(sessionManager, userWorkflow, actionExecutor, eventHandler, ACTION_RETRIES, retryHandler); mailAddressHandler.handleMailAddresses(MailAddressUtils.getRecipients(mail)); addPasswordsAttribute(mail, passwordsActivationContext.getPasswords()); makeNonPersistentUsersPersistent(passwordsActivationContext.getUsers()); } catch (MessagingException e) { getLogger().error("Error generating passwords.", e); } } }