/*
* 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: Authenticated.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.authentication.elements;
import com.uwyn.rife.authentication.Credentials;
import com.uwyn.rife.authentication.RememberManager;
import com.uwyn.rife.authentication.SessionAttributes;
import com.uwyn.rife.authentication.SessionManager;
import com.uwyn.rife.authentication.SessionValidator;
import com.uwyn.rife.authentication.credentials.RememberMe;
import com.uwyn.rife.authentication.elements.exceptions.UndefinedAuthenticationRememberManagerException;
import com.uwyn.rife.authentication.exceptions.CredentialsManagerException;
import com.uwyn.rife.authentication.exceptions.RememberManagerException;
import com.uwyn.rife.authentication.exceptions.SessionManagerException;
import com.uwyn.rife.authentication.exceptions.SessionValidatorException;
import com.uwyn.rife.engine.ElementDeployer;
import com.uwyn.rife.engine.ElementInfo;
import com.uwyn.rife.engine.exceptions.EngineException;
import com.uwyn.rife.engine.exceptions.PropertyRequiredException;
import com.uwyn.rife.engine.exceptions.UnsupportedTemplateTypeException;
import com.uwyn.rife.site.ValidationError;
import com.uwyn.rife.site.ValidationFormatter;
import com.uwyn.rife.template.Template;
import com.uwyn.rife.template.TemplateFactory;
import com.uwyn.rife.tools.Convert;
import com.uwyn.rife.tools.StringUtils;
import javax.servlet.http.Cookie;
/**
* Requires that the user have a valid authentication session before access
* to a child element is allowed. This class contains the logic for restoring
* remembered sessions and displaying a template (typically a login form)
* if the user is not authenticated.
* <p>
* The following properties may be set:
* <p>
* <dl>
* <dt>enforce_authenticated (default = true)</dt>
* <dd>Controls whether access to child elements is allowed for users who
* don't have valid authentication sessions. If this property is false,
* a user with no authentication session is allowed to access the
* child element, but there is no user identity information available.
* <p>
* The child element implementation may distinguish an anonymous user
* from an authenticated one by calling
* {@code {@link #getRequestAttribute(String)
* getRequestAttribute(Identified.IDENTITY_ATTRIBUTE_NAME)}}.
* <p>
* This is similar to using an {@code {@link Identified}} element,
* but expired sessions will automatically be recreated if the user has
* the appropriate "remember me" cookie set and "remember me" is enabled.</dd>
* </dl>
* <p>
* To customize the behavior of the authentication, it's the easiest to override
* one of the hook methods.
*
* @author Steven Grimm (koreth[remove] at midwinter dot com)
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @version $Revision: 3918 $
* @since 1.6
*/
public abstract class Authenticated extends Identified implements SessionAttributes
{
protected String mTemplateName = null;
protected Authenticated()
{
}
/**
* Returns the ID of this authentication element.
*
* @return this authentication element's ID
* @since 1.0
*/
public String getAuthenticatedElementId()
{
return getElementInfo().getId();
}
/**
* Returns the <code>ElementInfo</code> of this authentication element.
*
* @return this authentication element's <code>ElementInfo</code>
* @since 1.0
*/
public ElementInfo getAuthElement()
{
return getElementInfo();
}
/**
* Returns the class that is used for handling the credentials.
*
* @return this credentials' class
* @since 1.0
*/
public Class<? extends Credentials> getCredentialsClass()
{
ElementDeployer deployer = getDeployer();
if (deployer instanceof AuthenticatedDeployer)
{
return ((AuthenticatedDeployer)deployer).getCredentialsClass();
}
return null;
}
/**
* Returns the class that is used for handling the credentials.
*
* @return the credentials' class
* @since 1.0
*/
public SessionValidator getSessionValidator()
{
ElementDeployer deployer = getDeployer();
if (deployer instanceof AuthenticatedDeployer)
{
return ((AuthenticatedDeployer)deployer).getSessionValidator();
}
return null;
}
/**
* Allows a custom template name to be set.
* <p>
* This method is typically called during the implementation of method hooks
* to change the template that will be used by this authentication element.
*
* @param name the name of the template
* @since 1.0
*/
protected void setTemplateName(String name)
{
mTemplateName = name;
}
/**
* Hook method that is called at the start of the element's execution.
*
* @since 1.0
*/
protected void initializeAuthentication()
{
}
/**
* Hook method that is called after the template instance has been instantiated.
*
* @param template the template instance that has been instantiated
* @since 1.0
*/
protected void entrance(Template template)
{
}
/**
* Hook method that is called on login form submission when validation of the
* credentials produces validation errors.
*
* @param template this authentication element's template
* @param credentials the credentials object that was invalid
* @since 1.0
*/
protected void unvalidatedCredentials(Template template, Credentials credentials)
{
}
/**
* Hook method that is called on login form submission when the credentials
* are validated without errors
*
* @param credentials the credentials object that was valid
* @since 1.0
*/
protected void validatedCredentials(Credentials credentials)
{
}
/**
* Hook method that is called when valid credentials have been accepted by the
* <code>CredentialsManager</code> that backs this authentication element.
*
* @param credentials the credentials object that was accepted
* @since 1.0
*/
protected void acceptedCredentials(Credentials credentials)
{
}
/**
* Hook method that is called after a new authentication session has been
* successfully created.
*
* @param userId the user ID of the user that was successfully authenticated
* @since 1.0
*/
protected void authenticated(long userId)
{
}
/**
* Hook method that is called when valid credentials have been rejected by the
* <code>CredentialsManager</code> that backs this authentication element.
* <p>
* This can for example happen when the password is not correct.
* <p>
* Note that there is already a default implementation of this hook method that
* simply adds a validation error to the credentials object. If you want to
* preserve this when you implement your own hook method, you need to call the
* super class's method in your implementation.
*
* @param template this authentication element's template
* @param credentials the credentials object that was rejected
* @since 1.0
*/
@SuppressWarnings("deprecation")
protected void refusedCredentials(Template template, Credentials credentials)
{
if (template.hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID))
{
// this is for backwards compatibility with the deprecated ValidationFormatter class
String message = null;
if (template.hasBlock("INVALID_CREDENTIALS"))
{
message = template.getBlock("INVALID_CREDENTIALS");
}
else
{
message = "INVALID_CREDENTIALS";
}
ValidationFormatter.setErrorArea(template, message);
}
else
{
// all new code should use this version of the validation errors
credentials.addValidationError(new ValidationError.INVALID("credentials"));
}
}
/**
* Hook method that is called when the <code>SessionManager</code> couldn't
* create a new authentication session of valid and accepted credentials.
* <p>
* Note that there is already a default implementation of this hook method that
* simply adds a validation error to the credentials object. If you want to
* preserve this when you implement your own hook method, you need to call the
* super class's method in your implementation.
*
* @param template this authentication element's template
* @param credentials the credentials object that was used when creating the
* authentication session
* @since 1.0
*/
@SuppressWarnings("deprecation")
protected void sessionCreationError(Template template, Credentials credentials)
{
if (template.hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID))
{
// this is for backwards compatibility with the deprecated ValidationFormatter class
String message = null;
if (template.hasBlock("CANT_CREATE_SESSION"))
{
message = template.getBlock("CANT_CREATE_SESSION");
}
else
{
message = "CANT_CREATE_SESSION";
}
ValidationFormatter.setErrorArea(template, message);
}
else
{
// all new code should use this version of the validation errors
credentials.addValidationError(new ValidationError.UNEXPECTED("sessioncreation"));
}
}
/**
* Hook method that is called when the <code>SessionValidator</code> doesn't
* accept the authentication ID that a user provides after having been logged
* in.
* <p>
* This can happen for example happen when the maximum duration has expired,
* when the authentication ID has been tampered with, or when the
* authentication ID isn't known anymore by the backing store.
*
* @param childTriggerName the name of the child trigger that contains
* the authentication ID
* @param childTriggerValues the values of the child trigger with the
* authentication ID
* @param validityId a number that indicates the validation state of the
* session, as used by the <code>SessionValidator</code>, more information can
* be found here: {@link SessionValidator#validateSession}
* @since 1.0
*/
protected void sessionNotValid(String childTriggerName, String[] childTriggerValues, int validityId)
{
}
private Template getTemplateInstance(String type, String name, String encoding)
{
TemplateFactory template_factory = TemplateFactory.getFactory(type);
if (null == template_factory)
{
throw new UnsupportedTemplateTypeException(type);
}
Template template = template_factory.get(name, encoding, null);
entrance(template);
return template;
}
public void processElement()
{
Class<? extends Credentials> credentials_class = getCredentialsClass();
SessionValidator session_validator = getSessionValidator();
assert credentials_class != null;
assert session_validator != null;
initializeAuthentication();
if (!hasProperty("template_name") &&
null == mTemplateName)
{
throw new PropertyRequiredException(getDeclarationName(), "template_name");
}
if (!hasProperty("submission_name"))
{
throw new PropertyRequiredException(getDeclarationName(), "submission_name");
}
if (!hasProperty("authvar_name"))
{
throw new PropertyRequiredException(getDeclarationName(), "authvar_name");
}
if (!hasProperty("authvar_type"))
{
throw new PropertyRequiredException(getDeclarationName(), "authvar_type");
}
if (!hasProperty("remembervar_name"))
{
throw new PropertyRequiredException(getDeclarationName(), "remembervar_name");
}
if (!hasProperty("prohibit_remember"))
{
throw new PropertyRequiredException(getDeclarationName(), "prohibit_remember");
}
// obtain the optional template_type property
String template_type = null;
if (hasProperty("template_type"))
{
template_type = getPropertyString("template_type");
}
else
{
template_type = "enginehtml";
}
// obtain the optional template_encoding property
String template_encoding = null;
if (hasProperty("template_encoding"))
{
template_encoding = getPropertyString("template_encoding");
}
// obtain the mandatory template_name property
String template_name = null;
if (mTemplateName != null)
{
template_name = mTemplateName;
}
else
{
template_name = getPropertyString("template_name");
}
// obtain the optional allow_anonymous property
boolean enforce_authenticated = true;
if (hasProperty("enforce_authenticated"))
{
enforce_authenticated = StringUtils.convertToBoolean(getPropertyString("enforce_authenticated"));
}
Template template = null;
// check if a remember id is provided
String rememberid = null;
String remembervar_name = getPropertyString("remembervar_name");
if (getElementInfo().containsIncookiePossibility(remembervar_name) &&
hasCookie(remembervar_name))
{
Cookie remembercookie = getCookie(remembervar_name);
rememberid = remembercookie.getValue();
}
if (rememberid != null)
{
long userid = -1;
try
{
RememberManager remember_manager = session_validator.getRememberManager();
if (null == remember_manager)
{
throw new UndefinedAuthenticationRememberManagerException();
}
userid = remember_manager.getRememberedUserId(rememberid);
remember_manager.eraseRememberId(rememberid);
}
catch (RememberManagerException e)
{
throw new EngineException(e);
}
// only start a new session if the userid could be retrieved
if (userid != -1)
{
// try to start a new session, if it hasn't succeeded, the child trigger
// will not be activated and regular authentication will kick in
startNewSession(userid, true, true);
}
}
// handle a credentials submission
if (hasSubmission(getPropertyString("submission_name")))
{
Credentials credentials = getSubmissionBean(getPropertyString("submission_name"), credentials_class);
if (!credentials.validate())
{
template = getTemplateInstance(template_type, template_name, template_encoding);
unvalidatedCredentials(template, credentials);
if (template.hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID))
{
ValidationFormatter.setValidationErrors(template, credentials.getValidationErrors());
}
generateForm(template, credentials);
}
else
{
validatedCredentials(credentials);
long userid = -1;
try
{
userid = session_validator.getCredentialsManager().verifyCredentials(credentials);
}
catch (CredentialsManagerException e)
{
throw new EngineException(e);
}
// verify login attempt
if (userid >= 0)
{
acceptedCredentials(credentials);
// if the session has to be remembered, do so
boolean remember = false;
if (credentials instanceof RememberMe)
{
remember = ((RememberMe)credentials).getRemember();
}
// start a new session
if (!startNewSession(userid, remember, false))
{
template = getTemplateInstance(template_type, template_name, template_encoding);
// errors occurred, notify user
sessionCreationError(template, credentials);
}
else
{
template = getTemplateInstance(template_type, template_name, template_encoding);
}
}
else
{
template = getTemplateInstance(template_type, template_name, template_encoding);
}
refusedCredentials(template, credentials);
generateForm(template, credentials);
}
}
else
{
if (enforce_authenticated)
{
template = getTemplateInstance(template_type, template_name, template_encoding);
generateEmptyForm(template, credentials_class);
}
else
{
child();
}
}
if (null != template)
{
print(template);
}
}
private boolean startNewSession(long userid, boolean remember, boolean remembered)
throws EngineException
{
if (remember)
{
String rememberid = null;
try
{
RememberManager remember_manager = getSessionValidator().getRememberManager();
if (null == remember_manager)
{
throw new UndefinedAuthenticationRememberManagerException();
}
rememberid = remember_manager.createRememberId(userid, getRemoteAddr());
}
catch (RememberManagerException e)
{
throw new EngineException(e);
}
if (rememberid != null)
{
Cookie remembercookie = new Cookie(getPropertyString("remembervar_name"), rememberid);
remembercookie.setPath("/");
remembercookie.setMaxAge(60*60*24*30*3); // three months
setCookie(remembercookie);
}
}
String authid = null;
try
{
authid = getSessionValidator().getSessionManager().startSession(userid, getRemoteAddr(), remembered);
}
catch (SessionManagerException e)
{
throw new EngineException(e);
}
if (null == authid)
{
return false;
}
else
{
authenticated(userid);
// defer to child
if (getPropertyString("authvar_type").equals("input"))
{
setOutput(getPropertyString("authvar_name"), authid);
}
else if (getPropertyString("authvar_type").equals("cookie"))
{
Cookie authcookie = new Cookie(getPropertyString("authvar_name"), authid);
authcookie.setPath("/");
setCookie(authcookie);
}
}
return true;
}
public boolean childTriggered(String name, String[] values)
{
boolean result = false;
if (name.equals(getPropertyString("authvar_name")))
{
String authentication_request_attribute = createAuthenticationRequestAttributeName(getElementInfo(), name, values[0]);
if (hasRequestAttribute(authentication_request_attribute))
{
result = true;
}
else
{
SessionValidator session_validator = getSessionValidator();
assert session_validator != null;
// validate the session
String auth_id = values[0];
int session_validity_id = -1;
try
{
session_validity_id = session_validator.validateSession(auth_id, getRemoteAddr(), this);
}
catch (SessionValidatorException e)
{
throw new EngineException(e);
}
// check if the validation allows access
if (session_validator.isAccessAuthorized(session_validity_id))
{
SessionManager session_manager = session_validator.getSessionManager();
try
{
// prohibit access if the authentication session was
// started through rememberd credentials and that
// had been set to not allowed
if (Convert.toBoolean(getProperty("prohibit_remember"), false) &&
session_manager.wasRemembered(auth_id))
{
sessionNotValid(name, values, session_validity_id);
}
// continue the session
else
{
result = session_manager.continueSession(auth_id);
if (result)
{
setRequestAttribute(authentication_request_attribute, true);
}
}
}
catch (SessionManagerException e)
{
throw new EngineException(e);
}
}
else
{
sessionNotValid(name, values, session_validity_id);
}
}
}
if (!result)
{
return false;
}
super.childTriggered(name, values);
return true;
}
/**
* Creates a name for the current authentication context that can be used to
* cache the authentication process' result as a request attribute. This name
* is built from the authentication element's ID, the name of the
* authentication var and its value.
*
* @param elementInfo the authentication element information
* @param name the name of the authentication variable
* @param value the value of the authentication variable
*
* @return the created name
*
* @since 1.5
*/
public static String createAuthenticationRequestAttributeName(ElementInfo elementInfo, String name, String value) throws EngineException
{
return elementInfo.getId() + "\t" + name + "\t" + value;
}
}