001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata groupware may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: PasswordAction.java,v $
031: * Revision 1.8 2005/10/14 14:01:52 colinmacleod
032: * Changed password checking routines to return boolean, rather than throwing
033: * an exception.
034: *
035: * Revision 1.7 2005/10/12 18:36:10 colinmacleod
036: * Standardized format of Logger declaration - to make it easier to find
037: * instances which are not both static and final.
038: *
039: * Revision 1.6 2005/10/03 10:21:13 colinmacleod
040: * Fixed some style and javadoc issues.
041: *
042: * Revision 1.5 2005/10/02 14:08:54 colinmacleod
043: * Added/improved log4j logging.
044: *
045: * Revision 1.4 2005/04/27 15:07:11 colinmacleod
046: * Fixed error handling when there is a script
047: * error.
048: *
049: * Revision 1.3 2005/04/10 18:47:18 colinmacleod
050: * Changed i tag to em and b tag to strong.
051: *
052: * Revision 1.2 2005/04/09 17:19:04 colinmacleod
053: * Changed copyright text to GPL v2 explicitly.
054: *
055: * Revision 1.1.1.1 2005/03/10 17:50:44 colinmacleod
056: * Restructured ivata op around Hibernate/PicoContainer.
057: * Renamed ivata groupware.
058: *
059: * Revision 1.6 2004/12/31 18:27:41 colinmacleod
060: * Added MaskFactory to constructor of MaskAction.
061: *
062: * Revision 1.5 2004/12/23 21:01:19 colinmacleod
063: * Updated Struts to v1.2.4.
064: * Changed base classes to use ivata masks.
065: *
066: * Revision 1.4 2004/11/12 18:19:12 colinmacleod
067: * Change action and form classes to extend MaskAction, MaskForm respectively.
068: *
069: * Revision 1.3 2004/11/12 15:56:45 colinmacleod
070: * Removed dependencies on SSLEXT.
071: * Moved Persistence classes to ivata masks.
072: *
073: * Revision 1.2 2004/11/03 15:28:43 colinmacleod
074: * Added logging.
075: * Removed some justClose flags on warnings - now user can change input and
076: * retry.
077: *
078: * Revision 1.1 2004/07/13 19:14:03 colinmacleod
079: * First version in CVS.
080: * -----------------------------------------------------------------------------
081: */
082: package com.ivata.groupware.admin.security.struts;
083:
084: import com.ivata.groupware.admin.security.Security;
085: import com.ivata.groupware.admin.security.server.SecuritySession;
086: import com.ivata.groupware.business.addressbook.struts.PersonForm;
087:
088: import com.ivata.mask.MaskFactory;
089: import com.ivata.mask.util.StringHandling;
090: import com.ivata.mask.util.SystemException;
091: import com.ivata.mask.util.ThrowableHandling;
092: import com.ivata.mask.validation.ValidationException;
093: import com.ivata.mask.web.struts.MaskAction;
094: import com.ivata.mask.web.struts.MaskAuthenticator;
095: import com.ivata.mask.web.struts.ValidationErrorsConvertor;
096:
097: import org.apache.commons.beanutils.PropertyUtils;
098:
099: import org.apache.log4j.Logger;
100:
101: import org.apache.struts.Globals;
102: import org.apache.struts.action.ActionErrors;
103: import org.apache.struts.action.ActionForm;
104: import org.apache.struts.action.ActionMapping;
105: import org.apache.struts.action.ActionMessage;
106: import org.apache.struts.action.ActionMessages;
107:
108: import org.sourceforge.clientsession.ClientSession;
109:
110: import java.lang.reflect.InvocationTargetException;
111:
112: import java.util.Iterator;
113: import java.util.Locale;
114:
115: import javax.servlet.http.HttpServletRequest;
116: import javax.servlet.http.HttpServletResponse;
117: import javax.servlet.http.HttpSession;
118:
119: /**
120: * <p>Invoked when the user changes a password for
121: * him/herself or any other user.</p>
122: *
123: * @since ivata groupware 0.9 (2003-01-26)
124: * @author Colin MacLeod
125: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
126: * @version $Revision: 1.8 $
127: */
128: public class PasswordAction extends MaskAction {
129: /**
130: * Logger for this class.
131: */
132: private static final Logger logger = Logger
133: .getLogger(PasswordAction.class);
134:
135: /**
136: * See {@link PasswordAction()}.
137: */
138: private Security security;
139:
140: /**
141: * <p>
142: * Constructor - invoked by <strong>PicoContainer</strong>.
143: * </p>
144: *
145: * @param securityParam The security implementation is used to check the
146: * password.
147: * @param maskFactory {@inheritDoc}
148: * @param authenticator {@inheritDoc}
149: */
150: public PasswordAction(final Security securityParam,
151: final MaskFactory maskFactory,
152: final MaskAuthenticator authenticator) {
153: super (maskFactory, authenticator);
154: this .security = securityParam;
155: }
156:
157: /**
158: * Called from <code>onConfirm</code>, this method actually checks the
159: * password, and then sets the new password.
160: *
161: * @param session Current session we are processing.
162: * @param securitySessionParam Used to confirm the current site user is
163: * allowed to change this password.
164: * @param personFormParam The person form for the person whose password
165: * we are changing.
166: * @param oldPassword The value of the password before the change (will
167: * be checked against the datastore.)
168: * @param newPassword The new password value requested.
169: * @param errors Will be used to store any (business) errors which occurr.
170: * @return <code>true</code> if the password could be successfully changed.
171: * @throws SystemException If there is any technical problem which prevents
172: * checking or setting the password.
173: */
174: private boolean setPassword(final HttpSession session,
175: final SecuritySession securitySessionParam,
176: final PersonForm personFormParam, final String oldPassword,
177: final String newPassword, final ActionMessages errors)
178: throws SystemException {
179: if (logger.isDebugEnabled()) {
180: logger.debug("setPassword(HttpSession session = " + session
181: + ", SecuritySession securitySessionParam = "
182: + securitySessionParam
183: + ", PersonForm personFormParam = "
184: + personFormParam + ", String oldPassword = "
185: + oldPassword + ", String newPassword = "
186: + newPassword + ", ActionMessages errors = "
187: + errors + ") - start");
188: }
189:
190: boolean success = false;
191: boolean passwordCheck = false;
192: try {
193: passwordCheck = security.checkPassword(
194: securitySessionParam, oldPassword);
195: } catch (SystemException e) {
196: logger.warn("SystemException on checkPassword", e);
197: }
198: if (!passwordCheck) {
199: if (personFormParam.getUserName().equals(
200: securitySessionParam.getUser().getName())) {
201: errors.add(ActionMessages.GLOBAL_MESSAGE,
202: new ActionMessage(
203: "password.error.badOldPassword"));
204: } else {
205: errors.add(ActionMessages.GLOBAL_MESSAGE,
206: new ActionMessage("password.error."
207: + "badYoursPassword"));
208: }
209: }
210:
211: if (errors.isEmpty()) {
212: // now change the password
213: if ((personFormParam == null)
214: || StringHandling.isNullOrEmpty(personFormParam
215: .getUserName())) {
216: errors.add(Globals.ERROR_KEY, new ActionMessage(
217: "errors.addressBook.password.noUserName"));
218: } else {
219: try {
220: security.setPassword(securitySessionParam,
221: personFormParam.getUserName(), newPassword);
222:
223: // if it is successful, just close the dialog with a
224: // happy message
225: success = true;
226:
227: // confirm this back to the user - ok, I've cheated
228: // - this is not _really_ an error
229: errors.add(Globals.ERROR_KEY, new ActionMessage(
230: "errors.addressBook.password."
231: + "passwordUpdated"));
232: } catch (Exception e) {
233: Throwable cause = ThrowableHandling.getCause(e);
234:
235: // if this is caused by a validation exception, try to
236: // get the real cause of the error out (if it is
237: // password-related)
238: if (cause instanceof ValidationException) {
239: handleValidationException(
240: errors,
241: (ValidationException) cause,
242: (Locale) session
243: .getAttribute(Globals.LOCALE_KEY));
244: } else {
245: logger.warn("Non validation exception.", e);
246: errors.add(ActionMessages.GLOBAL_MESSAGE,
247: new ActionMessage("password.error."
248: + "passwordCouldNotBeChanged"));
249: }
250: }
251: }
252: }
253:
254: if (logger.isDebugEnabled()) {
255: logger.debug("setPassword() - return value = " + success);
256: }
257: return success;
258: }
259:
260: /**
261: * <p>Overridden from the default intranet implementation to
262: * initialize the dialog.</p>
263: *
264: * @param mapping {@inheritDoc}
265: * @param form {@inheritDoc}
266: * @param request {@inheritDoc}
267: * @param response {@inheritDoc}
268: * @param session {@inheritDoc}
269: * @param clientSession {@inheritDoc}
270: * @exception SystemException if there is any problem which
271: * prevents processing. It will result in the webapp being forwarded
272: * to the standard error page.
273: * @return {@inheritDoc}
274: *
275: */
276: public String execute(final ActionMapping mapping,
277: final ActionForm form, final HttpServletRequest request,
278: final HttpServletResponse response,
279: final HttpSession session, final ClientSession clientSession)
280: throws SystemException {
281: if (logger.isDebugEnabled()) {
282: logger.debug("execute(ActionMapping mapping = " + mapping
283: + ", ActionForm form = " + form
284: + ", HttpServletRequest request = " + request
285: + ", HttpServletResponse response = " + response
286: + ", HttpSession session = " + session
287: + ", ClientSession clientSession = "
288: + clientSession + ") - start");
289: }
290:
291: SecuritySession securitySession = (SecuritySession) session
292: .getAttribute("securitySession");
293: ActionMessages errors = new ActionErrors();
294:
295: // by default, we don't want to just close the window!
296: boolean justClose = false;
297:
298: // first look out for null or empty people or user names!!
299: PersonForm personForm = (PersonForm) session
300: .getAttribute("addressBookPersonForm");
301:
302: if ((personForm == null)
303: || StringHandling.isNullOrEmpty(personForm
304: .getUserName())) {
305: errors.add(Globals.ERROR_KEY, new ActionMessage(
306: "errors.addressBook.password.noUserName"));
307: justClose = true;
308: } else if (!personForm.getUserName().equals(
309: personForm.getPerson().getUser().getName())) {
310: // you can't change the password for a new user until that user is
311: // added first
312: errors.add(Globals.ERROR_KEY, new ActionMessage(
313: "errors.addressBook.password.userNameApply"));
314: justClose = true;
315: } else if (!security.isUserEnabled(securitySession, personForm
316: .getPerson().getUser().getName())) {
317: // if user is disabled
318: errors.add(Globals.ERROR_KEY, new ActionMessage(
319: "errors.addressBook.password.userIsDisable"));
320: justClose = true;
321: }
322:
323: // save justClose in the form
324: try {
325: PropertyUtils.setSimpleProperty(form, "justClose",
326: new Boolean(justClose));
327: } catch (NoSuchMethodException e) {
328: logger.error("Setting property 'justClose', value '"
329: + justClose + "' on form '" + form + "'", e);
330:
331: throw new SystemException(e);
332: } catch (InvocationTargetException e) {
333: logger.error("Setting property 'justClose', value '"
334: + justClose + "' on form '" + form + "'", e);
335:
336: throw new SystemException(e);
337: } catch (IllegalAccessException e) {
338: logger.error("Setting property 'justClose', value '"
339: + justClose + "' on form '" + form + "'", e);
340:
341: throw new SystemException(e);
342: }
343:
344: if (!errors.isEmpty()) {
345: saveErrors(request, errors);
346: saveToken(request);
347: }
348:
349: if (logger.isDebugEnabled()) {
350: logger.debug("execute() - end - return value = " + null);
351: }
352: return null;
353: }
354:
355: /**
356: * Handle any validation exception, by extracting all the errors and
357: * convering them to Struts <code>ActionMessages</code>.
358: *
359: * @param errors All the error messages for the entire process.
360: * @param validationException The validation exception which occurred.
361: * @param locale Current locale from the session.
362: * @throws SystemException If there is a technical problem preventing the
363: * creation of <code>ActionMessages</code> from this validation exception.
364: */
365: private void handleValidationException(final ActionMessages errors,
366: final ValidationException validationException,
367: final Locale locale) throws SystemException {
368: if (logger.isDebugEnabled()) {
369: logger
370: .debug("handleValidationException(ActionMessages errors = "
371: + errors
372: + ", ValidationException validationException = "
373: + validationException
374: + ", Locale locale = "
375: + locale
376: + ") - start");
377: }
378:
379: ActionMessages allErrors = ValidationErrorsConvertor
380: .toActionErrors(validationException.getErrors(), locale);
381: Iterator allErrorsIterator = allErrors.get();
382:
383: while (allErrorsIterator.hasNext()) {
384: ActionMessage actionError = (ActionMessage) allErrorsIterator
385: .next();
386: String key = actionError.getKey();
387:
388: if (key.startsWith("password.error")) {
389: if (logger.isDebugEnabled()) {
390: logger.debug("Password error '" + key + "', "
391: + actionError);
392: }
393: errors.add(ActionMessages.GLOBAL_MESSAGE, actionError);
394: } else if ("errors.admin.script".equals(key)) {
395: if (logger.isDebugEnabled()) {
396: logger.debug("Admin script error '" + key + "', "
397: + actionError);
398: }
399: errors.add(ActionMessages.GLOBAL_MESSAGE, actionError);
400: } else {
401: logger.error("Invalid password action error key: "
402: + key);
403: errors
404: .add(
405: ActionMessages.GLOBAL_MESSAGE,
406: new ActionMessage(
407: "password.error.passwordCouldNotBeChanged"));
408: }
409: }
410:
411: if (logger.isDebugEnabled()) {
412: logger.debug("handleValidationException() - end");
413: }
414: }
415:
416: /**
417: * <p>This method is called if the ok or apply buttons are pressed.</p>
418: *
419: * @param mapping {@inheritDoc}
420: * @param form {@inheritDoc}
421: * @param request {@inheritDoc}
422: * @param response {@inheritDoc}
423: * @param session {@inheritDoc}
424: * @param clientSession {@inheritDoc}
425: * @param defaultForward {@inheritDoc}
426: * @exception SystemException if there is any problem which
427: * prevents processing. It will result in the webapp being forwarded
428: * to the standard error page.
429: * @return This method always returns "addressBookPassword"
430: */
431: public String onConfirm(final ActionMapping mapping,
432: final ActionForm form, final HttpServletRequest request,
433: final HttpServletResponse response,
434: final HttpSession session,
435: final ClientSession clientSession,
436: final String defaultForward) throws SystemException {
437: if (logger.isDebugEnabled()) {
438: logger.debug("onConfirm(ActionMapping mapping = " + mapping
439: + ", ActionForm form = " + form
440: + ", HttpServletRequest request = " + request
441: + ", HttpServletResponse response = " + response
442: + ", HttpSession session = " + session
443: + ", ClientSession clientSession = "
444: + clientSession + ", String defaultForward = "
445: + defaultForward + ") - start");
446: }
447:
448: String oldPassword;
449: String newPassword;
450: String confirmPassword;
451: boolean justClose = false;
452: ActionMessages errors = new ActionMessages();
453:
454: try {
455: oldPassword = (String) PropertyUtils.getSimpleProperty(
456: form, "oldPassword");
457: newPassword = (String) PropertyUtils.getSimpleProperty(
458: form, "newPassword");
459: confirmPassword = (String) PropertyUtils.getSimpleProperty(
460: form, "confirmPassword");
461: } catch (NoSuchMethodException e) {
462: logger.error("Error getting oldPassword, newPassword and "
463: + "confirmPassword properties in form '" + form
464: + "'", e);
465:
466: throw new SystemException(e);
467: } catch (InvocationTargetException e) {
468: logger.error("Error getting oldPassword, newPassword and "
469: + "confirmPassword properties in form '" + form
470: + "'", e);
471:
472: throw new SystemException(e);
473: } catch (IllegalAccessException e) {
474: logger.error("Error getting oldPassword, newPassword and "
475: + "confirmPassword properties in form '" + form
476: + "'", e);
477:
478: throw new SystemException(e);
479: }
480:
481: // check none of the passwords is null or empty
482: if (StringHandling.isNullOrEmpty(oldPassword)
483: || StringHandling.isNullOrEmpty(newPassword)
484: || StringHandling.isNullOrEmpty(confirmPassword)) {
485: errors.add(Globals.ERROR_KEY, new ActionMessage(
486: "errors.addressBook.password.notComplete"));
487: } else if (!newPassword.equals(confirmPassword)) {
488: errors.add(Globals.ERROR_KEY, new ActionMessage(
489: "errors.addressBook.password.notMatching"));
490: } else if (errors.isEmpty()) {
491: PersonForm personForm = (PersonForm) session
492: .getAttribute("addressBookPersonForm");
493: // check the old password matches this user
494: SecuritySession securitySession = (SecuritySession) session
495: .getAttribute("securitySession");
496: justClose = setPassword(session, securitySession,
497: personForm, oldPassword, newPassword, errors);
498: if (!errors.isEmpty()) {
499: saveErrors(request, errors);
500: saveToken(request);
501: }
502: }
503:
504: // save justClose in the form
505: try {
506: PropertyUtils.setSimpleProperty(form, "justClose",
507: new Boolean(justClose));
508: } catch (Exception e) {
509: logger.error("Error setting 'justClose' to '" + justClose
510: + "' on form '" + form + "'.", e);
511:
512: throw new SystemException(e);
513: }
514:
515: if (logger.isDebugEnabled()) {
516: logger.debug("onConfirm() - end -"
517: + " return value = addressBookPassword");
518: }
519: return "addressBookPassword";
520: }
521: }
|