/*==========================================================================*\ | $Id: PasswordChangeRequest.java,v 1.2 2011/03/07 18:44:37 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT 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 General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import com.webobjects.foundation.*; import com.webobjects.eoaccess.*; import com.webobjects.eocontrol.*; import java.security.*; import java.util.*; import org.webcat.core.Application; import org.webcat.core.PasswordChangeRequest; import org.webcat.core.User; import org.webcat.core.WCProperties; import org.webcat.core._PasswordChangeRequest; import org.apache.log4j.*; // ------------------------------------------------------------------------- /** * Represents a user's request to change their password, because they forgot * the old one. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.2 $, $Date: 2011/03/07 18:44:37 $ */ public class PasswordChangeRequest extends _PasswordChangeRequest { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new PasswordChangeRequest object. */ public PasswordChangeRequest() { super(); } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Look up the password change request identified by the given code, * checking it for expiration first. * @param ec The editing context to use * @param code The code to look up * @return The password change request object, if found (and not expired), * or null otherwise */ public boolean hasExpired() { NSTimestamp myExpireTime = expireTime(); return myExpireTime != null && myExpireTime.before(new NSTimestamp()); } // ---------------------------------------------------------- /** * Look up the password change request identified by the given code, * checking it for expiration first. * @param ec The editing context to use * @param requestCode The code to look up * @return The password change request object, if found (and not expired), * or null otherwise */ public static PasswordChangeRequest requestForCode( EOEditingContext ec, String requestCode ) { PasswordChangeRequest request = null; NSArray<PasswordChangeRequest> results = requestsForCode(ec, requestCode); if ( results.count() > 0 ) { request = results.objectAtIndex( 0 ); if ( request.hasExpired() ) { // Expired timestamp, so delete it! request.delete(); ec.saveChanges(); request = null; } } return request; } // ---------------------------------------------------------- /** * Delete any pending password change requests for the given user. * @param ec The editing context to use * @param forUser The user requesting the password reset instructions * @return True if any pending requests were found and deleted, or * false if none were found */ public static boolean clearPendingUserRequests( EOEditingContext ec, User forUser ) { NSArray<PasswordChangeRequest> results = requestsForUser(ec, forUser); boolean result = results.count() > 0; for ( int i = 0; i < results.count(); i++ ) { PasswordChangeRequest pcr = results.objectAtIndex( i ); pcr.delete(); } if ( result ) { ec.saveChanges(); } return result; } // ---------------------------------------------------------- /** * Generate a new PasswordChangeRequest object for the given user, store * it in the database, and then e-mail the user instructions on how to * change their password. * @param ec The editing context to use * @param forUser The user requesting the password reset instructions */ public static void sendPasswordResetEmail( EOEditingContext ec, User forUser ) { log.info( "Creating password change ticket for: " + forUser.nameAndUid() ); PasswordChangeRequest pcr = (PasswordChangeRequest) EOUtilities.createAndInsertInstance( ec, ENTITY_NAME ); pcr.setUserRelationship( forUser ); NSTimestamp now = new NSTimestamp(); pcr.setExpireTime( now.timestampByAddingGregorianUnits( 0, 0, 1, 0, 0, 0 ) ); pcr.setCode(digestString(forUser.userName() + formatter.format(now) + forUser.email() + Integer.toString(random.nextInt()))); ec.saveChanges(); WCProperties properties = new WCProperties( Application.configurationProperties() ); forUser.addPropertiesTo( properties ); if ( properties.getProperty( "login.url" ) == null ) { String dest = Application.application().servletConnectURL(); properties.setProperty( "login.url", dest ); } properties.setProperty( "password.change.url", properties.getProperty( "base.url" ) + "/wa/passwordChangeRequest?code=" + pcr.code() ); Application.sendSimpleEmail( forUser.email() , properties.stringForKeyWithDefault( "PasswordChangeRequest.email.title", "How to change your Web-CAT password" ), properties.stringForKeyWithDefault( "PasswordChangeRequest.email.message", "Web-CAT has received a password change request for your " + "account with\nuser name ${user.userName}.\n\nTo change " + "your Web-CAT password, open the following link in your " + "Web\nbrowser to view your account properties and enter " + "your new password:\n\n${password.change.url}\n\n" + "This personalized password reset URL will only be active " + "for 24 hours\nand can only be used once, after which it " + "will no longer be valid.\n\nIf you did not make this " + "request, please visit the URL above and\nthen click the " + "logout link on the upper right of the resulting page\n" + "without changing your password. This will cancel the " + "reset request." ) ); } // ---------------------------------------------------------- /** * Produces a Base64-encoded SHA digest for <code>aString</code>. * * @param aString the text to digest * @return the corresponding SHA digest */ public static String digestString( String aString ) { String digestedString = null; try { // Make sure no one else is trying to use the static data // members, since the MessageDigest includes internal state // and is not synchronized. Lock on the encoder, however, since // it is possible that the digester has not yet been initialized. synchronized ( encoder ) { if ( digester == null ) { digester = MessageDigest.getInstance( "SHA" ); } // Convert to text by encoding it in Base64 digestedString = encoder.encode( digester.digest( aString.getBytes() ) ); // Trim any trailing equals sign(s) for URL param safety int pos = digestedString.indexOf( '=' ); if ( pos >= 0 ) { digestedString = digestedString.substring( 0, pos ); } // Substitute + by * and / by - for URL convenience digestedString = digestedString.replaceAll( "[+]", "*" ) .replaceAll( "/", "-" ); } } catch ( NoSuchAlgorithmException e ) { throw new NSForwardException( e ); } return digestedString; } //~ Instance/static variables ............................................. private static MessageDigest digester; private static sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); @SuppressWarnings("deprecation") private static NSTimestampFormatter formatter = new NSTimestampFormatter( "%Y%m%d%H%M%S%F" ); private static Random random = new Random(); static Logger log = Logger.getLogger( PasswordChangeRequest.class ); }