org.sakaiproject.accountvalidator.tool.producers.PasswordResetProducer.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.accountvalidator.tool.producers.PasswordResetProducer.java

Source

/**
 * $Id: MainProducer.java 105078 2012-02-24 23:00:38Z ottenhoff@longsight.com $
 * $URL: https://source.sakaiproject.org/svn/reset-pass/trunk/account-validator-tool/src/java/org/sakaiproject/accountvalidator/tool/producers/MainProducer.java $
 * DeveloperHelperService.java - entity-broker - Apr 13, 2008 5:42:38 PM - azeckoski
 **************************************************************************
 * Copyright (c) 2008, 2009 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sakaiproject.accountvalidator.tool.producers;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormat;
import org.joda.time.format.PeriodFormatter;
import org.sakaiproject.accountvalidator.model.ValidationAccount;
import org.sakaiproject.entitybroker.EntityReference;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserNotDefinedException;
import uk.org.ponder.localeutil.LocaleGetter;
import uk.org.ponder.messageutil.TargettedMessage;
import uk.org.ponder.rsf.components.*;
import uk.org.ponder.rsf.flow.ActionResultInterceptor;
import uk.org.ponder.rsf.view.ComponentChecker;
import uk.org.ponder.rsf.view.ViewComponentProducer;
import uk.org.ponder.rsf.viewstate.ViewParameters;

import java.util.Locale;

/**
 * Produces passwordReset.html - builds a form containing nothing more than the a password and confirm password field
 * @author bbailla2
 */
public class PasswordResetProducer extends BaseValidationProducer
        implements ViewComponentProducer, ActionResultInterceptor {

    private static Log log = LogFactory.getLog(PasswordResetProducer.class);
    public static final String VIEW_ID = "passwordReset";
    private static final String MAX_PASSWORD_RESET_MINUTES = "accountValidator.maxPasswordResetMinutes";
    private static LocaleGetter localeGetter;

    public void setLocaleGetter(LocaleGetter localeGetter) {
        this.localeGetter = localeGetter;
    }

    private Locale getLocale() {
        return localeGetter.get();
    }

    public String getViewID() {
        return VIEW_ID;
    }

    public void init() {

    }

    public void fillComponents(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker) {

        Object[] args = new Object[] { serverConfigurationService.getString("ui.service", "Sakai") };
        UIMessage.make(tofill, "welcome1", "validate.welcome1.reset", args);

        ValidationAccount va = getValidationAccount(viewparams, tml);
        if (va == null) {
            //handled by getValidationAccount
            return;
        } else if (!va.getAccountStatus().equals(ValidationAccount.ACCOUNT_STATUS_PASSWORD_RESET)) {
            //this is not a password reset
            args = new Object[] { va.getValidationToken() };
            //no such validation of the required account status
            tml.addMessage(new TargettedMessage("msg.noSuchValidation", args, TargettedMessage.SEVERITY_ERROR));
            return;
        } else if (ValidationAccount.STATUS_CONFIRMED.equals(va.getStatus())) {
            args = new Object[] { va.getValidationToken() };
            tml.addMessage(new TargettedMessage("msg.alreadyValidated", args, TargettedMessage.SEVERITY_ERROR));
            addResetPassLink(tofill, va);
            return;
        } else if (ValidationAccount.STATUS_EXPIRED.equals(va.getStatus())) {
            /*
             * If accountValidator.maxPasswordResetMinutes is configured, 
             * we give them an approrpiate message, otherwise we give them the default
             */
            TargettedMessage message = getExpirationMessage();
            if (message == null) {
                //give them the default
                args = new Object[] { va.getValidationToken() };
                tml.addMessage(
                        new TargettedMessage("msg.expiredValidation", args, TargettedMessage.SEVERITY_ERROR));
                addResetPassLink(tofill, va);
            } else {
                tml.addMessage(message);
            }
            return;
        } else if (sendLegacyLinksEnabled()) {
            redirectToLegacyLink(tofill, va);
            return;
        } else {
            /*
             * Password resets should go quickly. If it takes longer than accountValidator.maxPasswordResetMinutes, 
             * it could be an intruder who stumbled on an old validation token.
             */
            //Only do this check if accountValidator.maxPasswordResetMinutes is configured correctly
            String strMinutes = serverConfigurationService.getString(MAX_PASSWORD_RESET_MINUTES);
            if (strMinutes != null && !"".equals(strMinutes)) {
                if (va.getAccountStatus() != null) {
                    try {
                        //get the time limit and convert to millis
                        long maxMillis = Long.parseLong(strMinutes);
                        maxMillis *= 60 * 1000;

                        //the time when the validation token was sent to the email server
                        long sentTime = va.getValidationSent().getTime();

                        if (System.currentTimeMillis() - sentTime > maxMillis) {
                            //it's been too long, so invalide the token and stop the user
                            va.setStatus(ValidationAccount.STATUS_EXPIRED);

                            //get a nice expiration meesage
                            TargettedMessage expirationMessage = getExpirationMessage();
                            if (expirationMessage == null) {
                                //should never happen
                                args = new Object[] { va.getValidationToken() };
                                tml.addMessage(new TargettedMessage("msg.expiredValidation", args,
                                        TargettedMessage.SEVERITY_ERROR));
                                return;
                            }
                            tml.addMessage(expirationMessage);
                            addResetPassLink(tofill, va);
                            return;
                        }
                    } catch (NumberFormatException nfe) {
                        log.error("accountValidator.maxPasswordResetMinutes is not configured correctly");
                    }
                }
            }
        }

        User u = null;
        try {
            u = userDirectoryService.getUser(EntityReference.getIdFromRef(va.getUserId()));
        } catch (UserNotDefinedException e) {

        }

        if (u == null) {
            log.error("user ID does not exist for ValidationAccount with tokenId: " + va.getValidationToken());
            tml.addMessage(new TargettedMessage("validate.userNotDefined", new Object[] {},
                    TargettedMessage.SEVERITY_ERROR));
            return;
        }

        String resetMinutes = serverConfigurationService.getString(MAX_PASSWORD_RESET_MINUTES);
        if (resetMinutes != null && !"".equals(resetMinutes)) {
            try {
                int minutes = Integer.parseInt(resetMinutes);
                UIMessage.make(tofill, "welcome2", "validate.expirationtime",
                        new Object[] { getFormattedMinutes(minutes) });
            } catch (NumberFormatException nfe) {
                log.error("accountValidator.maxPasswordResetMinutes is not configured correctly");
            }
        }

        //the form
        UIForm detailsForm = UIForm.make(tofill, "setDetailsForm");

        UICommand.make(detailsForm, "addDetailsSub", UIMessage.make("submit.new.reset"),
                "accountValidationLocator.validateAccount");

        String otp = "accountValidationLocator." + va.getId();

        UIMessage.make(detailsForm, "username.new", "username.new.reset", args);
        UIOutput.make(detailsForm, "eid", u.getDisplayId());

        boolean passwordPolicyEnabled = (userDirectoryService.getPasswordPolicy() != null);
        String passwordPolicyEnabledJavaScript = "VALIDATOR.isPasswordPolicyEnabled = "
                + Boolean.toString(passwordPolicyEnabled) + ";";
        UIVerbatim.make(tofill, "passwordPolicyEnabled", passwordPolicyEnabledJavaScript);

        UIBranchContainer row1 = UIBranchContainer.make(detailsForm, "passrow1:");
        UIInput.make(row1, "password1", otp + ".password");

        UIBranchContainer row2 = UIBranchContainer.make(detailsForm, "passrow2:");
        UIInput.make(row2, "password2", otp + ".password2");

        detailsForm.parameters.add(new UIELBinding(otp + ".userId", va.getUserId()));
    }

    /**
     * Converts some number of minutes into a presentable String'
     * ie for English:
     * 122 minutes   ->   2 hours 2 minutes
     * 121 minutes   ->   2 hours 1 minute
     * 120 minutes   ->   2 hours
     * 62 minutes   ->   1 hour 2 minutes
     * 61 minutes   ->   1 hour 1 minute
     * 60 minutes   ->   1 hour
     * 2 minutes   ->   2 minutes
     * 1 minutes   ->   1 minute
     * 0 minutes   ->   0 minutes
     * Works with other languages too.
     * @param totalMinutes some number of minutes
     * @return a presentable String representation of totalMinutes
     */
    public String getFormattedMinutes(int totalMinutes) {
        // Create a joda time period (takes milliseconds)
        Period period = new Period(totalMinutes * 60 * 1000);
        // format the period for the locale
        /* 
         * Covers English, Danish, Dutch, French, German, Japanese, Portuguese, and Spanish. 
         * To translate into others, see http://joda-time.sourceforge.net/apidocs/org/joda/time/format/PeriodFormat.html#wordBased(java.util.Locale)
         * (ie. put the properties mentioned in http://joda-time.sourceforge.net/apidocs/src-html/org/joda/time/format/PeriodFormat.html#line.94 into the classpath resource bundle)
         */
        PeriodFormatter periodFormatter = PeriodFormat.wordBased(getLocale());
        return periodFormatter.print(period);
    }

    /**
     * Adds a link to the page for the user to request another validation token
     * @param tofill the parent of the link
     */
    private void addResetPassLink(UIContainer toFill, ValidationAccount va) {
        if (toFill == null || va == null) {
            // enforce method contract
            throw new IllegalArgumentException("null passed to addResetPassLink()");
        }

        //the url to reset-pass - assume it's on the gateway. Otherwise, we don't render a link and we log a warning
        String url = null;
        try {
            //get the link target
            url = developerHelperService.getToolViewURL("sakai.resetpass", null, null,
                    developerHelperService.getStartingLocationReference());
        } catch (IllegalArgumentException e) {
            log.warn("Couldn't create a link to reset-pass; no instance of reset-pass found on the gateway");
        }

        if (url != null) {
            //add the container
            UIBranchContainer requestAnotherContainer = UIBranchContainer.make(toFill, "requestAnotherContainer:");
            //add a label
            UIMessage.make(requestAnotherContainer, "request.another.label", "validate.requestanother.label");
            //add the link to reset-pass
            String requestAnother = null;
            if (ValidationAccount.ACCOUNT_STATUS_PASSWORD_RESET == va.getAccountStatus()) {
                requestAnother = messageLocator.getMessage("validate.requestanother.reset");
            } else {
                requestAnother = messageLocator.getMessage("validate.requestanother");
            }
            UILink.make(requestAnotherContainer, "request.another", requestAnother, url);
        }
        //else - there is no reset pass instance on the gateway, but the user sees an appropriate message regardless (handled by a targetted message)
    }

    /**
     * When a user's validation token expires (by accountValidator.maxPasswordResetMinutes elapsing)
     * this returns an appropriate TargettedMessage
     * @return an approrpiate TargettedMessage when a validaiton token has expired,
     * null if accountValidator.maxPasswordResetMinutes isn't configured correctly
     */
    private TargettedMessage getExpirationMessage() {
        //get the time limit (iff possible)
        String strMinutes = serverConfigurationService.getString(MAX_PASSWORD_RESET_MINUTES);
        if (strMinutes != null && !"".equals(strMinutes)) {
            try {
                int totalMinutes = Integer.parseInt(strMinutes);

                //get a formatted string representation of the time limit, and create the return value
                String formattedTime = getFormattedMinutes(totalMinutes);

                Object[] args = new Object[] { formattedTime };
                return new TargettedMessage("msg.expiredValidationRealTime", args, TargettedMessage.SEVERITY_ERROR);
            } catch (NumberFormatException e) {
                log.warn("accountValidator.maxPasswordResetMinutes is not configured properly");
            }
        }
        return null;
    }
}