001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com> and
003: * Steven Grimm <koreth[remove] at midwinter dot com>
004: * Distributed under the terms of either:
005: * - the common development and distribution license (CDDL), v1.0; or
006: * - the GNU Lesser General Public License, v2.1 or later
007: * $Id: Authenticated.java 3654 2007-02-06 06:50:16Z gbevin $
008: */
009: package com.uwyn.rife.authentication.elements;
010:
011: import com.uwyn.rife.authentication.Credentials;
012: import com.uwyn.rife.authentication.RememberManager;
013: import com.uwyn.rife.authentication.SessionAttributes;
014: import com.uwyn.rife.authentication.SessionManager;
015: import com.uwyn.rife.authentication.SessionValidator;
016: import com.uwyn.rife.authentication.credentials.RememberMe;
017: import com.uwyn.rife.authentication.elements.exceptions.UndefinedAuthenticationRememberManagerException;
018: import com.uwyn.rife.authentication.exceptions.CredentialsManagerException;
019: import com.uwyn.rife.authentication.exceptions.RememberManagerException;
020: import com.uwyn.rife.authentication.exceptions.SessionManagerException;
021: import com.uwyn.rife.authentication.exceptions.SessionValidatorException;
022: import com.uwyn.rife.engine.ElementDeployer;
023: import com.uwyn.rife.engine.ElementInfo;
024: import com.uwyn.rife.engine.exceptions.EngineException;
025: import com.uwyn.rife.engine.exceptions.PropertyRequiredException;
026: import com.uwyn.rife.engine.exceptions.UnsupportedTemplateTypeException;
027: import com.uwyn.rife.site.ValidationError;
028: import com.uwyn.rife.site.ValidationFormatter;
029: import com.uwyn.rife.template.Template;
030: import com.uwyn.rife.template.TemplateFactory;
031: import com.uwyn.rife.tools.Convert;
032: import com.uwyn.rife.tools.StringUtils;
033: import javax.servlet.http.Cookie;
034:
035: /**
036: * Requires that the user have a valid authentication session before access
037: * to a child element is allowed. This class contains the logic for restoring
038: * remembered sessions and displaying a template (typically a login form)
039: * if the user is not authenticated.
040: * <p>
041: * The following properties may be set:
042: * <p>
043: * <dl>
044: * <dt>enforce_authenticated (default = true)</dt>
045: * <dd>Controls whether access to child elements is allowed for users who
046: * don't have valid authentication sessions. If this property is false,
047: * a user with no authentication session is allowed to access the
048: * child element, but there is no user identity information available.
049: * <p>
050: * The child element implementation may distinguish an anonymous user
051: * from an authenticated one by calling
052: * {@code {@link #getRequestAttribute(String)
053: * getRequestAttribute(Identified.IDENTITY_ATTRIBUTE_NAME)}}.
054: * <p>
055: * This is similar to using an {@code {@link Identified}} element,
056: * but expired sessions will automatically be recreated if the user has
057: * the appropriate "remember me" cookie set and "remember me" is enabled.</dd>
058: * </dl>
059: * <p>
060: * To customize the behavior of the authentication, it's the easiest to override
061: * one of the hook methods.
062: *
063: * @author Steven Grimm (koreth[remove] at midwinter dot com)
064: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
065: * @version $Revision: 3654 $
066: * @since 1.6
067: */
068: public abstract class Authenticated extends Identified implements
069: SessionAttributes {
070: protected String mTemplateName = null;
071:
072: protected Authenticated() {
073: }
074:
075: /**
076: * Returns the ID of this authentication element.
077: *
078: * @return this authentication element's ID
079: * @since 1.0
080: */
081: public String getAuthenticatedElementId() {
082: return getElementInfo().getId();
083: }
084:
085: /**
086: * Returns the <code>ElementInfo</code> of this authentication element.
087: *
088: * @return this authentication element's <code>ElementInfo</code>
089: * @since 1.0
090: */
091: public ElementInfo getAuthElement() {
092: return getElementInfo();
093: }
094:
095: /**
096: * Returns the class that is used for handling the credentials.
097: *
098: * @return this credentials' class
099: * @since 1.0
100: */
101: public Class<? extends Credentials> getCredentialsClass() {
102: ElementDeployer deployer = getDeployer();
103:
104: if (deployer instanceof AuthenticatedDeployer) {
105: return ((AuthenticatedDeployer) deployer)
106: .getCredentialsClass();
107: }
108:
109: return null;
110: }
111:
112: /**
113: * Returns the class that is used for handling the credentials.
114: *
115: * @return the credentials' class
116: * @since 1.0
117: */
118: public SessionValidator getSessionValidator() {
119: ElementDeployer deployer = getDeployer();
120:
121: if (deployer instanceof AuthenticatedDeployer) {
122: return ((AuthenticatedDeployer) deployer)
123: .getSessionValidator();
124: }
125:
126: return null;
127: }
128:
129: /**
130: * Allows a custom template name to be set.
131: * <p>
132: * This method is typically called during the implementation of method hooks
133: * to change the template that will be used by this authentication element.
134: *
135: * @param name the name of the template
136: * @since 1.0
137: */
138: protected void setTemplateName(String name) {
139: mTemplateName = name;
140: }
141:
142: /**
143: * Hook method that is called at the start of the element's execution.
144: *
145: * @since 1.0
146: */
147: protected void initializeAuthentication() {
148: }
149:
150: /**
151: * Hook method that is called after the template instance has been instantiated.
152: *
153: * @param template the template instance that has been instantiated
154: * @since 1.0
155: */
156: protected void entrance(Template template) {
157: }
158:
159: /**
160: * Hook method that is called on login form submission when validation of the
161: * credentials produces validation errors.
162: *
163: * @param template this authentication element's template
164: * @param credentials the credentials object that was invalid
165: * @since 1.0
166: */
167: protected void unvalidatedCredentials(Template template,
168: Credentials credentials) {
169: }
170:
171: /**
172: * Hook method that is called on login form submission when the credentials
173: * are validated without errors
174: *
175: * @param credentials the credentials object that was valid
176: * @since 1.0
177: */
178: protected void validatedCredentials(Credentials credentials) {
179: }
180:
181: /**
182: * Hook method that is called when valid credentials have been accepted by the
183: * <code>CredentialsManager</code> that backs this authentication element.
184: *
185: * @param credentials the credentials object that was accepted
186: * @since 1.0
187: */
188: protected void acceptedCredentials(Credentials credentials) {
189: }
190:
191: /**
192: * Hook method that is called after a new authentication session has been
193: * successfully created.
194: *
195: * @param userId the user ID of the user that was successfully authenticated
196: * @since 1.0
197: */
198: protected void authenticated(long userId) {
199: }
200:
201: /**
202: * Hook method that is called when valid credentials have been rejected by the
203: * <code>CredentialsManager</code> that backs this authentication element.
204: * <p>
205: * This can for example happen when the password is not correct.
206: * <p>
207: * Note that there is already a default implementation of this hook method that
208: * simply adds a validation error to the credentials object. If you want to
209: * preserve this when you implement your own hook method, you need to call the
210: * super class's method in your implementation.
211: *
212: * @param template this authentication element's template
213: * @param credentials the credentials object that was rejected
214: * @since 1.0
215: */
216: @SuppressWarnings("deprecation")
217: protected void refusedCredentials(Template template,
218: Credentials credentials) {
219: if (template
220: .hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID)) {
221: // this is for backwards compatibility with the deprecated ValidationFormatter class
222: String message = null;
223: if (template.hasBlock("INVALID_CREDENTIALS")) {
224: message = template.getBlock("INVALID_CREDENTIALS");
225: } else {
226: message = "INVALID_CREDENTIALS";
227: }
228:
229: ValidationFormatter.setErrorArea(template, message);
230: } else {
231: // all new code should use this version of the validation errors
232: credentials.addValidationError(new ValidationError.INVALID(
233: "credentials"));
234: }
235: }
236:
237: /**
238: * Hook method that is called when the <code>SessionManager</code> couldn't
239: * create a new authentication session of valid and accepted credentials.
240: * <p>
241: * Note that there is already a default implementation of this hook method that
242: * simply adds a validation error to the credentials object. If you want to
243: * preserve this when you implement your own hook method, you need to call the
244: * super class's method in your implementation.
245: *
246: * @param template this authentication element's template
247: * @param credentials the credentials object that was used when creating the
248: * authentication session
249: * @since 1.0
250: */
251: @SuppressWarnings("deprecation")
252: protected void sessionCreationError(Template template,
253: Credentials credentials) {
254: if (template
255: .hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID)) {
256: // this is for backwards compatibility with the deprecated ValidationFormatter class
257: String message = null;
258: if (template.hasBlock("CANT_CREATE_SESSION")) {
259: message = template.getBlock("CANT_CREATE_SESSION");
260: } else {
261: message = "CANT_CREATE_SESSION";
262: }
263:
264: ValidationFormatter.setErrorArea(template, message);
265: } else {
266: // all new code should use this version of the validation errors
267: credentials
268: .addValidationError(new ValidationError.UNEXPECTED(
269: "sessioncreation"));
270: }
271: }
272:
273: /**
274: * Hook method that is called when the <code>SessionValidator</code> doesn't
275: * accept the authentication ID that a user provides after having been logged
276: * in.
277: * <p>
278: * This can happen for example happen when the maximum duration has expired,
279: * when the authentication ID has been tampered with, or when the
280: * authentication ID isn't known anymore by the backing store.
281: *
282: * @param childTriggerName the name of the child trigger that contains
283: * the authentication ID
284: * @param childTriggerValues the values of the child trigger with the
285: * authentication ID
286: * @param validityId a number that indicates the validation state of the
287: * session, as used by the <code>SessionValidator</code>, more information can
288: * be found here: {@link SessionValidator#validateSession}
289: * @since 1.0
290: */
291: protected void sessionNotValid(String childTriggerName,
292: String[] childTriggerValues, int validityId) {
293: }
294:
295: private Template getTemplateInstance(String type, String name,
296: String encoding) {
297: TemplateFactory template_factory = TemplateFactory
298: .getFactory(type);
299: if (null == template_factory) {
300: throw new UnsupportedTemplateTypeException(type);
301: }
302:
303: Template template = template_factory.get(name, encoding, null);
304: entrance(template);
305: return template;
306: }
307:
308: public void processElement() {
309: Class<? extends Credentials> credentials_class = getCredentialsClass();
310: SessionValidator session_validator = getSessionValidator();
311:
312: assert credentials_class != null;
313: assert session_validator != null;
314:
315: initializeAuthentication();
316:
317: if (!hasProperty("template_name") && null == mTemplateName) {
318: throw new PropertyRequiredException(getDeclarationName(),
319: "template_name");
320: }
321: if (!hasProperty("submission_name")) {
322: throw new PropertyRequiredException(getDeclarationName(),
323: "submission_name");
324: }
325: if (!hasProperty("authvar_name")) {
326: throw new PropertyRequiredException(getDeclarationName(),
327: "authvar_name");
328: }
329: if (!hasProperty("authvar_type")) {
330: throw new PropertyRequiredException(getDeclarationName(),
331: "authvar_type");
332: }
333: if (!hasProperty("remembervar_name")) {
334: throw new PropertyRequiredException(getDeclarationName(),
335: "remembervar_name");
336: }
337: if (!hasProperty("prohibit_remember")) {
338: throw new PropertyRequiredException(getDeclarationName(),
339: "prohibit_remember");
340: }
341:
342: // obtain the optional template_type property
343: String template_type = null;
344: if (hasProperty("template_type")) {
345: template_type = getPropertyString("template_type");
346: } else {
347: template_type = "enginehtml";
348: }
349:
350: // obtain the optional template_encoding property
351: String template_encoding = null;
352: if (hasProperty("template_encoding")) {
353: template_encoding = getPropertyString("template_encoding");
354: }
355:
356: // obtain the mandatory template_name property
357: String template_name = null;
358: if (mTemplateName != null) {
359: template_name = mTemplateName;
360: } else {
361: template_name = getPropertyString("template_name");
362: }
363:
364: // obtain the optional allow_anonymous property
365: boolean enforce_authenticated = true;
366: if (hasProperty("enforce_authenticated")) {
367: enforce_authenticated = StringUtils
368: .convertToBoolean(getPropertyString("enforce_authenticated"));
369: }
370:
371: Template template = null;
372:
373: // check if a remember id is provided
374: String rememberid = null;
375: String remembervar_name = getPropertyString("remembervar_name");
376: if (getElementInfo().containsIncookiePossibility(
377: remembervar_name)
378: && hasCookie(remembervar_name)) {
379: Cookie remembercookie = getCookie(remembervar_name);
380: rememberid = remembercookie.getValue();
381: }
382:
383: if (rememberid != null) {
384: long userid = -1;
385: try {
386: RememberManager remember_manager = session_validator
387: .getRememberManager();
388: if (null == remember_manager) {
389: throw new UndefinedAuthenticationRememberManagerException();
390: }
391:
392: userid = remember_manager
393: .getRememberedUserId(rememberid);
394: remember_manager.eraseRememberId(rememberid);
395: } catch (RememberManagerException e) {
396: throw new EngineException(e);
397: }
398:
399: // only start a new session if the userid could be retrieved
400: if (userid != -1) {
401: // try to start a new session, if it hasn't succeeded, the child trigger
402: // will not be activated and regular authentication will kick in
403: startNewSession(userid, true, true);
404: }
405: }
406:
407: // handle a credentials submission
408: if (hasSubmission(getPropertyString("submission_name"))) {
409: Credentials credentials = getSubmissionBean(
410: getPropertyString("submission_name"),
411: credentials_class);
412:
413: if (!credentials.validate()) {
414: template = getTemplateInstance(template_type,
415: template_name, template_encoding);
416:
417: unvalidatedCredentials(template, credentials);
418:
419: if (template
420: .hasValueId(ValidationFormatter.DEFAULT_ERROR_AREA_ID)) {
421: ValidationFormatter.setValidationErrors(template,
422: credentials.getValidationErrors());
423: }
424:
425: generateForm(template, credentials);
426: } else {
427: validatedCredentials(credentials);
428:
429: long userid = -1;
430:
431: try {
432: userid = session_validator.getCredentialsManager()
433: .verifyCredentials(credentials);
434: } catch (CredentialsManagerException e) {
435: throw new EngineException(e);
436: }
437:
438: // verify login attempt
439: if (userid >= 0) {
440: acceptedCredentials(credentials);
441:
442: // if the session has to be remembered, do so
443: boolean remember = false;
444: if (credentials instanceof RememberMe) {
445: remember = ((RememberMe) credentials)
446: .getRemember();
447: }
448:
449: // start a new session
450: if (!startNewSession(userid, remember, false)) {
451: template = getTemplateInstance(template_type,
452: template_name, template_encoding);
453:
454: // errors occurred, notify user
455: sessionCreationError(template, credentials);
456: } else {
457: template = getTemplateInstance(template_type,
458: template_name, template_encoding);
459: }
460: } else {
461: template = getTemplateInstance(template_type,
462: template_name, template_encoding);
463: }
464:
465: refusedCredentials(template, credentials);
466:
467: generateForm(template, credentials);
468: }
469: } else {
470: if (enforce_authenticated) {
471: template = getTemplateInstance(template_type,
472: template_name, template_encoding);
473: generateEmptyForm(template, credentials_class);
474: } else {
475: child();
476: }
477: }
478:
479: if (null != template) {
480: print(template);
481: }
482: }
483:
484: private boolean startNewSession(long userid, boolean remember,
485: boolean remembered) throws EngineException {
486: if (remember) {
487: String rememberid = null;
488: try {
489: RememberManager remember_manager = getSessionValidator()
490: .getRememberManager();
491: if (null == remember_manager) {
492: throw new UndefinedAuthenticationRememberManagerException();
493: }
494:
495: rememberid = remember_manager.createRememberId(userid,
496: getRemoteAddr());
497: } catch (RememberManagerException e) {
498: throw new EngineException(e);
499: }
500:
501: if (rememberid != null) {
502: Cookie remembercookie = new Cookie(
503: getPropertyString("remembervar_name"),
504: rememberid);
505: remembercookie.setPath("/");
506: remembercookie.setMaxAge(60 * 60 * 24 * 30 * 3); // three months
507: setCookie(remembercookie);
508: }
509: }
510:
511: String authid = null;
512: try {
513: authid = getSessionValidator().getSessionManager()
514: .startSession(userid, getRemoteAddr(), remembered);
515: } catch (SessionManagerException e) {
516: throw new EngineException(e);
517: }
518:
519: if (null == authid) {
520: return false;
521: } else {
522: authenticated(userid);
523:
524: // defer to child
525: if (getPropertyString("authvar_type").equals("input")) {
526: setOutput(getPropertyString("authvar_name"), authid);
527: } else if (getPropertyString("authvar_type").equals(
528: "cookie")) {
529: Cookie authcookie = new Cookie(
530: getPropertyString("authvar_name"), authid);
531: authcookie.setPath("/");
532: setCookie(authcookie);
533: }
534: }
535:
536: return true;
537: }
538:
539: public boolean childTriggered(String name, String[] values) {
540: boolean result = false;
541:
542: if (name.equals(getPropertyString("authvar_name"))) {
543: String authentication_request_attribute = createAuthenticationRequestAttributeName(
544: getElementInfo(), name, values[0]);
545:
546: if (hasRequestAttribute(authentication_request_attribute)) {
547: result = true;
548: } else {
549: SessionValidator session_validator = getSessionValidator();
550:
551: assert session_validator != null;
552:
553: // validate the session
554: String auth_id = values[0];
555: int session_validity_id = -1;
556: try {
557: session_validity_id = session_validator
558: .validateSession(auth_id, getRemoteAddr(),
559: this );
560: } catch (SessionValidatorException e) {
561: throw new EngineException(e);
562: }
563:
564: // check if the validation allows access
565: if (session_validator
566: .isAccessAuthorized(session_validity_id)) {
567: SessionManager session_manager = session_validator
568: .getSessionManager();
569:
570: try {
571: // prohibit access if the authentication session was
572: // started through rememberd credentials and that
573: // had been set to not allowed
574: if (Convert
575: .toBoolean(
576: getProperty("prohibit_remember"),
577: false)
578: && session_manager
579: .wasRemembered(auth_id)) {
580: sessionNotValid(name, values,
581: session_validity_id);
582: }
583: // continue the session
584: else {
585: result = session_manager
586: .continueSession(auth_id);
587: if (result) {
588: setRequestAttribute(
589: authentication_request_attribute,
590: true);
591: }
592: }
593: } catch (SessionManagerException e) {
594: throw new EngineException(e);
595: }
596: } else {
597: sessionNotValid(name, values, session_validity_id);
598: }
599: }
600: }
601:
602: if (!result) {
603: return false;
604: }
605:
606: super .childTriggered(name, values);
607:
608: return true;
609: }
610:
611: /**
612: * Creates a name for the current authentication context that can be used to
613: * cache the authentication process' result as a request attribute. This name
614: * is built from the authentication element's ID, the name of the
615: * authentication var and its value.
616: *
617: * @param elementInfo the authentication element information
618: * @param name the name of the authentication variable
619: * @param value the value of the authentication variable
620: *
621: * @return the created name
622: *
623: * @since 1.5
624: */
625: public static String createAuthenticationRequestAttributeName(
626: ElementInfo elementInfo, String name, String value)
627: throws EngineException {
628: return elementInfo.getId() + "\t" + name + "\t" + value;
629: }
630: }
|