/*
* JOSSO: Java Open Single Sign-On
*
* Copyright 2004-2009, Atricore, Inc.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.josso.selfservices.password.lostpassword;
import org.josso.gateway.identity.SSOUser;
import org.josso.gateway.identity.exceptions.SSOIdentityException;
import org.josso.gateway.identity.exceptions.NoSuchUserException;
import org.josso.gateway.identity.service.SSOIdentityManager;
import org.josso.gateway.SSOException;
import org.josso.selfservices.password.*;
import org.josso.selfservices.annotations.Action;
import org.josso.selfservices.annotations.Extension;
import org.josso.selfservices.ChallengeResponseCredential;
import org.josso.selfservices.ProcessRequest;
import org.josso.selfservices.ProcessResponse;
import org.josso.selfservices.ProcessState;
import org.josso.auth.Credential;
import org.josso.auth.CredentialProvider;
import org.josso.auth.exceptions.AuthenticationFailureException;
import org.josso.util.id.IdGenerator;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import java.util.Set;
import java.util.HashSet;
/**
* Base service class with standard utils.
*
* Next JOSSO Architecture will use a BPM engine for this kind of stuf
*
* @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a>
* @version $Id: AbstractLostPasswordProcess.java 974 2009-01-14 00:39:45Z sgonzalez $
*/
public abstract class AbstractLostPasswordProcess extends BasePasswordManagementProcess implements Constants {
private static final Log log = LogFactory.getLog(AbstractLostPasswordProcess.class);
private CredentialProvider credentialProvider;
private PasswordDistributor distributor;
private PasswordGenerator generator;
private SSOIdentityManager identityManager;
private IdGenerator idGenerator;
@Extension( EXT_URL_PROVIDER)
private LostPasswordUrlProvider urlProvider;
// ---------------------------------------------------------------------------------------------------
@Override
public PasswordManagementProcess createNewProcess(String id) throws SSOException {
AbstractLostPasswordProcess newProcess = (AbstractLostPasswordProcess) super.createNewProcess(id);
// newProcess.setUserStore(extendedIdentityStore);
newProcess.setCredentialProvider(credentialProvider);
newProcess.setIdentityManager(identityManager);
newProcess.setPasswordDistributor(distributor);
newProcess.setPasswordGenerator(generator);
newProcess.setIdGenerator(idGenerator);
return newProcess;
}
@Override
protected ProcessState doMakeState(String id) {
return new LostPasswordProcessState(id);
}
// -----------------------------------------------------------< Process actions >
@Override
public ProcessResponse start() {
if (log.isDebugEnabled())
log.debug("Starting lost password process .. .");
super.start();
assert this.identityManager != null : "No Identity Manager Configured";
assert this.credentialProvider != null : "No Credential Provider Configured";
assert this.distributor != null : "No Password Distributor Configured";
assert this.generator != null : "No Password Generator Configured";
assert this.idGenerator != null : "No Password Assertion Generator Configured";
// Clear state
getLostPasswordState().setPasswordConfirmUrl(null);
getLostPasswordState().setAssertionId(null);
getLostPasswordState().setChallenges(new HashSet<ChallengeResponseCredential>());
// Start
ChallengeResponseCredential[] challenges = createInitilaChallenges();
// In this case, credentials are required!
ProcessResponse response = createResponse(STEP_REQUEST_CHALLENGES);
response.setAttribute(ATTR_CHALLENGES, challenges);
storeAllChallenges(challenges);
return response;
}
@Override
public void stop() {
if (log.isDebugEnabled())
log.debug("Stopping lost password process .. .");
super.stop();
}
@Action( fromSteps = {STEP_REQUEST_CHALLENGES, STEP_REQUEST_ADDITIONAL_CHALLENGES} )
public ProcessResponse processChallenges(ProcessRequest request) {
try {
// Store already received challenges
ChallengeResponseCredential[] c = (ChallengeResponseCredential[]) request.getAttribute(ATTR_CHALLENGES);
if (c == null) {
if (log.isDebugEnabled())
log.debug("No challenges received!");
return createFinalResponse(STEP_AUTH_ERROR);
}
storeAllChallenges(c);
Set<ChallengeResponseCredential> challenges = retrieveAllChallenges();
ChallengeResponseCredential[] additionalChallenges = createAdditionalChallenges(challenges);
if (additionalChallenges != null && additionalChallenges.length > 0 ) {
if (log.isDebugEnabled())
log.debug("Requesting additional challengis");
ProcessResponse response = createResponse(STEP_REQUEST_ADDITIONAL_CHALLENGES);
response.setAttribute(ATTR_CHALLENGES, additionalChallenges);
storeAllChallenges(additionalChallenges);
return response;
}
if (log.isDebugEnabled())
log.debug("Starting password reset");
// Authenticate user !
SSOUser user = authenticate (challenges);
if (log.isDebugEnabled())
log.debug("User " + user.getName() + " authenticated");
String clearPassword = createNewPassword(user, challenges);
if (log.isDebugEnabled())
log.debug("Password created for " + user.getName());
Credential password = credentialProvider.newEncodedCredential("password", clearPassword);
if (log.isDebugEnabled())
log.debug("Password encoded for " + user.getName());
this.getLostPasswordState().setUser(user);
this.getLostPasswordState().setNewPasswordCredential(password);
String passwordAssertionId = generateAssertionId(user);
if (log.isDebugEnabled())
log.debug("Password Assertion ID [" + passwordAssertionId + "] generated for " + user.getName());
getLostPasswordState().setAssertionId(passwordAssertionId);
getLostPasswordState().setPasswordConfirmUrl(urlProvider.provideResetUrl(passwordAssertionId));
distribute(user, clearPassword);
if (log.isDebugEnabled())
log.debug("Password distributed " + user.getName());
// Request confirmation challenges after password distribution
ProcessResponse response = createResponse(STEP_CONFIRM_PASSWORD);
ChallengeResponseCredential[] confirmationChallenges = createConfirmationChallenges();
if (confirmationChallenges != null && confirmationChallenges.length > 0 ) {
storeAllChallenges(confirmationChallenges);
response.setAttribute(ATTR_CHALLENGES, confirmationChallenges );
}
return response;
} catch (AuthenticationFailureException e) {
log.error("Authentication error " + e.getMessage(), e);
ProcessResponse response = createFinalResponse(STEP_AUTH_ERROR);
response.setAttribute("error", e);
return response;
} catch (Exception e) {
log.error("Fatal error error " + e.getMessage(), e);
ProcessResponse response = createFinalResponse(STEP_FATAL_ERROR);
response.setAttribute("error", e);
return response;
}
}
@Action (fromSteps = {STEP_CONFIRM_PASSWORD, STEP_REQUEST_ADDITIONAL_CONFIRMATION_CHALLENGES})
public ProcessResponse requestPasswordConfirmation(ProcessRequest request) throws PasswordManagementException {
ChallengeResponseCredential[] c = (ChallengeResponseCredential[]) request.getAttribute(ATTR_CHALLENGES);
storeAllChallenges(c);
ChallengeResponseCredential[] additionalChallenges = this.createAdditionalConfirmationChallenges(retrieveAllChallenges());
if (additionalChallenges != null && additionalChallenges.length > 0) {
ProcessResponse response = createFinalResponse(STEP_REQUEST_ADDITIONAL_CONFIRMATION_CHALLENGES);
response.setAttribute(ATTR_CHALLENGES, additionalChallenges);
storeAllChallenges(additionalChallenges);
return response;
}
Set<ChallengeResponseCredential> challenges = retrieveAllChallenges();
if (challenges != null && challenges.size() > 0) {
try {
authenticateConfirmation();
SSOUser user = this.getLostPasswordState().getUser();
if (log.isDebugEnabled())
log.debug("Password confirmed for " + user.getName());
Credential password = this.getLostPasswordState().getNewPasswordCredential();
updateAccount(user, password);
if (log.isDebugEnabled())
log.debug("Account updated : " + user.getName());
} catch (AuthenticationFailureException e) {
log.error(e.getMessage(), e);
return createFinalResponse(STEP_AUTH_ERROR);
}
} else {
// No challenges provided or requested!
log.error("No challenges provided or requested for password confirmation!");
return createFinalResponse(STEP_AUTH_ERROR);
}
return createFinalResponse(STEP_PASSWORD_RESETED);
}
@Action ( fromSteps = { STEP_PASSWORD_RESETED} )
public ProcessResponse passwordResetted(ProcessRequest request) {
this.stop();
return createFinalResponse(null);
}
@Action ( fromSteps = { STEP_FATAL_ERROR})
public ProcessResponse fatalError(ProcessRequest r) {
this.stop();
return createFinalResponse(null);
}
@Action ( fromSteps = { STEP_AUTH_ERROR})
public ProcessResponse authError(ProcessRequest r) {
this.stop();
return createFinalResponse(null);
}
// ----------------------------------------------------------< Process Primitive methods >
// Subclasses should override this.
/**
* This are the challenges presented to the user when pasword recovery is started.
*/
protected ChallengeResponseCredential[] createInitilaChallenges() {
return null;
}
/**
* This are additional challenges, created based on the initial challenges, this can be for example, specific
* secrect questions selected by the user on registration. If no additional challenges are required this method
* must return null.
*
* Before creating any challenge, make sure that the challeng was not previously created.
*
* @see #getChallenge(String)
*
*/
protected ChallengeResponseCredential[] createAdditionalChallenges(Set<ChallengeResponseCredential> challenges) {
return null;
}
/**
* This are challenges presented when the password change is confirmed. If no challenges are required on confirmation
* this method must return null. This implementation requests the password assertion challenge.
*
* Before creating any challenge, make sure that the challeng was not previously created.
*
* @see #getChallenge(String)
*/
protected ChallengeResponseCredential[] createConfirmationChallenges() {
if (getChallenge(CHALLENGE_PWD_ASSERTION_ID) != null) {
if (log.isDebugEnabled())
log.debug("Already created password assertion challenge, value is " + getChallenge(CHALLENGE_PWD_ASSERTION_ID).getValue());
return null;
}
log.debug("Creating password assertion challenge");
ChallengeResponseCredential c = new ChallengeResponseCredential (CHALLENGE_PWD_ASSERTION_ID, "Password Assertion");
return new ChallengeResponseCredential [] {c};
}
/**
* This are additional challenges, created based on the initial challenges, this can be for example, specific
* secrect questions selected by the user on registration. If no additional challenges are required this method
* must return null.
*
* Before creating any challenge, make sure that the challeng was not previously created.
*
* @see #getChallenge(String)
*
*/
protected ChallengeResponseCredential[] createAdditionalConfirmationChallenges(Set<ChallengeResponseCredential> challenges) {
return null;
}
/**
* Authenticates a user based on a set of challenges. This method will be invoked after initial and additional
* challenges are collected. All challenges are sent.
*/
protected abstract SSOUser authenticate(Set<ChallengeResponseCredential> challenges) throws AuthenticationFailureException;
/**
* This can authenticat confirmation credentials. This is invoked if confirmation credentials were requested.
*/
protected void authenticateConfirmation() throws AuthenticationFailureException {
ChallengeResponseCredential c = getChallenge(CHALLENGE_PWD_ASSERTION_ID);
if (c == null || c.getValue() == null)
throw new AuthenticationFailureException("No Password Assertion found");
String assertionId = (String) c.getValue();
if (!assertionId.equals(getPasswordAssertionId())) {
log.error("Invalid password assertion : " + assertionId);
throw new AuthenticationFailureException("Invalid password assertion : " + getPasswordAssertionId());
}
// all ok !
}
/**
* This method actually performs password reset.
*/
protected String createNewPassword(SSOUser user, Set<ChallengeResponseCredential> challenges) {
if (log.isDebugEnabled())
log.debug("Generating new password for " + user.getName());
return this.generator.generateClearPassword(user, retrieveAllChallenges());
}
/**
* This updates the user account with the new password. implementationc can also perform additional tasks like
* unlocking the user account.
*
* @param user
* @param password
*/
protected void updateAccount(SSOUser user, Credential password) throws PasswordManagementException {
if (log.isDebugEnabled())
log.debug("Updating user account for " + user.getName());
try {
identityManager.updateAccountPassword(user, password);
} catch (SSOIdentityException e) {
throw new PasswordManagementException(e.getMessage(), e);
}
}
/**
* Generate a password assertion ID.
* @param user
* @return
*/
protected String generateAssertionId(SSOUser user) throws PasswordManagementException {
if (log.isDebugEnabled())
log.debug("Generating assertion ID for " + user.getName());
return this.idGenerator.generateId();
}
/**
* After password is reseted. This method distributes the new value.
* @param user The user information.
* @param password The password in clear text!
*/
protected void distribute(SSOUser user, String password) throws PasswordManagementException {
if (log.isDebugEnabled())
log.debug("Distributing password for " + user.getName());
// Add all necessary properties for
this.distributor.distributePassword(user, password, this.getLostPasswordState());
}
protected SSOUser findUserByUsername(String username) {
if (log.isDebugEnabled())
log.debug("Looking for user " + username);
try {
return identityManager.findUser(username);
} catch (SSOIdentityException e) {
log.error(e.getMessage(), e);
return null;
}
}
// -------------------------------------------------------------------------< Some utils >
protected LostPasswordProcessState getLostPasswordState() {
return (LostPasswordProcessState) getState();
}
protected ChallengeResponseCredential getChallenge(String id) {
return getChallenge(id, retrieveAllChallenges());
}
protected ChallengeResponseCredential getChallenge(String id, Set<ChallengeResponseCredential> challenges) {
for (ChallengeResponseCredential challenge : challenges) {
if (challenge.getId().equals(id))
return challenge;
}
return null;
}
protected void storeAllChallenges(ChallengeResponseCredential[] challenges) {
if (challenges == null)
return;
for (int i = 0; i < challenges.length; i++) {
ChallengeResponseCredential challenge = challenges[i];
getLostPasswordState().getChallenges().add(challenge);
if (log.isDebugEnabled())
log.debug("Storing challenge : " + challenge.getId() + " ["+challenge.getResponse()+"]");
}
}
protected void clearChallenges() {
getLostPasswordState().getChallenges().clear();
}
protected Set<ChallengeResponseCredential> retrieveAllChallenges() {
return getLostPasswordState().getChallenges();
}
public String getPasswordAssertionId() {
return getLostPasswordState().getAssertionId();
}
/**
* @org.apache.xbean.Property alias="credential-provider"
*
* @return
*/
public CredentialProvider getCredentialProvider() {
return credentialProvider;
}
public void setCredentialProvider(CredentialProvider credentialProvider) {
this.credentialProvider = credentialProvider;
}
/**
* @org.apache.xbean.Property alias="password-distributor"
* @return
*/
public PasswordDistributor getPasswordDistributor() {
return distributor;
}
public void setPasswordDistributor(PasswordDistributor distributor) {
this.distributor = distributor;
}
/**
* @org.apache.xbean.Property alias="password-generator"
* @return
*/
public PasswordGenerator getPasswordGenerator() {
return generator;
}
public void setPasswordGenerator(PasswordGenerator generator) {
this.generator = generator;
}
/**
* @org.apache.xbean.Property alias="assertion-generator"
* @return
*/
public IdGenerator getIdGenerator() {
return idGenerator;
}
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
/**
* @org.apache.xbean.Property alias="identity-manager"
*
* @return
*/
public SSOIdentityManager getIdentityManager() {
return identityManager;
}
public void setIdentityManager(SSOIdentityManager identityManager) {
this.identityManager = identityManager;
}
}