Author: snoopdave Date: Tue Apr 3 15:03:02 2007 New Revision: 525301 URL: http://svn.apache.org/viewvc?view=rev&rev=525301 Log: Implemented Proposal: Account activation via email. A proposal and patch submitted by Sedat Ciftci
http://cwiki.apache.org/confluence/display/ROLLER/Proposal+Account+Activation+via+Email Modified: incubator/roller/trunk/metadata/database/tmpls/3xx-to-400-migration.vm incubator/roller/trunk/metadata/database/tmpls/createdb.vm incubator/roller/trunk/metadata/xdoclet/global-forwards.xml incubator/roller/trunk/src/org/apache/roller/business/UserManager.java incubator/roller/trunk/src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java incubator/roller/trunk/src/org/apache/roller/pojos/UserData.java incubator/roller/trunk/src/org/apache/roller/ui/core/struts/actions/UserNewAction.java incubator/roller/trunk/web/WEB-INF/classes/ApplicationResources.properties incubator/roller/trunk/web/WEB-INF/classes/rollerRuntimeConfigDefs.xml incubator/roller/trunk/web/WEB-INF/jsps/core/welcome.jsp Modified: incubator/roller/trunk/metadata/database/tmpls/3xx-to-400-migration.vm URL: http://svn.apache.org/viewvc/incubator/roller/trunk/metadata/database/tmpls/3xx-to-400-migration.vm?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/metadata/database/tmpls/3xx-to-400-migration.vm (original) +++ incubator/roller/trunk/metadata/database/tmpls/3xx-to-400-migration.vm Tue Apr 3 15:03:02 2007 @@ -4,6 +4,8 @@ DON'T RUN THIS, IT'S NOT A DATABASE CREATION SCRIPT!!! **# +#addColumnNull("rolleruser" "activationcode" "varchar(48)") + #addColumnNull("webpage" "outputtype" "varchar(48)") -- add new action column to webpage table, default value is custom Modified: incubator/roller/trunk/metadata/database/tmpls/createdb.vm URL: http://svn.apache.org/viewvc/incubator/roller/trunk/metadata/database/tmpls/createdb.vm?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/metadata/database/tmpls/createdb.vm (original) +++ incubator/roller/trunk/metadata/database/tmpls/createdb.vm Tue Apr 3 15:03:02 2007 @@ -15,6 +15,7 @@ passphrase varchar(255) not null, fullname varchar(255) not null, emailaddress varchar(255) not null, + activationcode varchar(48), datecreated $db.TIMESTAMP_SQL_TYPE not null, locale varchar(20), timezone varchar(50), Modified: incubator/roller/trunk/metadata/xdoclet/global-forwards.xml URL: http://svn.apache.org/viewvc/incubator/roller/trunk/metadata/xdoclet/global-forwards.xml?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/metadata/xdoclet/global-forwards.xml (original) +++ incubator/roller/trunk/metadata/xdoclet/global-forwards.xml Tue Apr 3 15:03:02 2007 @@ -19,6 +19,7 @@ <forward name="yourProfile" path="/roller-ui/yourProfile.do?method=edit" /> <forward name="createWebsite" path="/roller-ui/createWebsite.do?method=create" /> <forward name="yourWebsites" path="/roller-ui/yourWebsites.do?method=edit" /> +<forward name="activateUser" path="/rollerui/user.do?method=activateUser"/> <!-- LIMITED/AUTHOR weblog UI actions --> Modified: incubator/roller/trunk/src/org/apache/roller/business/UserManager.java URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/business/UserManager.java?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/src/org/apache/roller/business/UserManager.java (original) +++ incubator/roller/trunk/src/org/apache/roller/business/UserManager.java Tue Apr 3 15:03:02 2007 @@ -394,4 +394,13 @@ */ public void release(); + + /** + * get a user by activation code + * @param activationCode + * @return + * @throws RollerException + */ + public UserData getUserByActivationCode(String activationCode) throws RollerException; + } Modified: incubator/roller/trunk/src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java (original) +++ incubator/roller/trunk/src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java Tue Apr 3 15:03:02 2007 @@ -264,6 +264,11 @@ newUser.grantRole("editor"); if(adminUser) { newUser.grantRole("admin"); + + //if user was disabled (because of activation user account with e-mail property), + //enable it for admin user + newUser.setEnabled(Boolean.TRUE); + newUser.setActivationCode(null); } this.strategy.store(newUser); @@ -1024,6 +1029,27 @@ super(property, value, "=", true); } } + + + public UserData getUserByActivationCode(String activationCode) throws RollerException { + + if (activationCode == null) + throw new RollerException("activationcode is null"); + + try { + Session session = ((HibernatePersistenceStrategy) this.strategy) + .getSession(); + Criteria criteria = session.createCriteria(UserData.class); + + criteria.add(Expression.eq("activationCode", activationCode)); + + UserData user = (UserData) criteria.uniqueResult(); + + return user; + } catch (HibernateException e) { + throw new RollerException(e); + } + } } Modified: incubator/roller/trunk/src/org/apache/roller/pojos/UserData.java URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/pojos/UserData.java?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/src/org/apache/roller/pojos/UserData.java (original) +++ incubator/roller/trunk/src/org/apache/roller/pojos/UserData.java Tue Apr 3 15:03:02 2007 @@ -64,10 +64,12 @@ private String locale; private String timeZone; private Boolean enabled = Boolean.TRUE; + private String activationCode; private Set roles = new HashSet(); private List permissions = new ArrayList(); + public UserData() { } @@ -275,6 +277,7 @@ this.locale = other.getLocale(); this.timeZone = other.getTimeZone(); this.dateCreated = other.getDateCreated()!=null ? (Date)other.getDateCreated().clone() : null; + this.activationCode = other.getActivationCode(); } @@ -341,6 +344,22 @@ role.setUser(this); } } + + /** activation code + * @ejb:persistent-field + * @hibernate.property column="activationcode" non-null="false" + * @roller.wrapPojoMethod type="simple" + */ + public String getActivationCode() { + return activationCode; + } + + /** @ejb:persistent-field */ + public void setActivationCode(String activationCode) { + this.activationCode = activationCode; + } + + //------------------------------------------------------- Good citizenship Modified: incubator/roller/trunk/src/org/apache/roller/ui/core/struts/actions/UserNewAction.java URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/core/struts/actions/UserNewAction.java?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/src/org/apache/roller/ui/core/struts/actions/UserNewAction.java (original) +++ incubator/roller/trunk/src/org/apache/roller/ui/core/struts/actions/UserNewAction.java Tue Apr 3 15:03:02 2007 @@ -19,10 +19,17 @@ package org.apache.roller.ui.core.struts.actions; import java.io.IOException; -import java.util.HashMap; +import java.net.MalformedURLException; +import java.text.MessageFormat; import java.util.Locale; +import java.util.ResourceBundle; import java.util.TimeZone; +import java.util.UUID; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.naming.InitialContext; +import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -37,6 +44,7 @@ import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.action.ActionMessages; +import org.apache.struts.util.RequestUtils; import org.apache.roller.RollerException; import org.apache.roller.config.RollerConfig; import org.apache.roller.config.RollerRuntimeConfig; @@ -47,6 +55,7 @@ import org.apache.roller.ui.core.RollerRequest; import org.apache.roller.ui.core.security.CustomUserRegistry; import org.apache.roller.ui.authoring.struts.formbeans.UserFormEx; +import org.apache.roller.util.MailUtil; import org.apache.commons.lang.StringUtils; @@ -107,12 +116,12 @@ // Let's see if there's any user-authentication available from Acegi // and retrieve custom user data to pre-populate form. boolean usingSSO = RollerConfig.getBooleanProperty("users.sso.enabled"); - if(usingSSO) { - UserData fromSSO = CustomUserRegistry.getUserDetailsFromAuthentication(); - if(fromSSO != null) { - userForm.copyFrom(fromSSO, request.getLocale()); - userForm.setDataFromSSO(true); - } + if(usingSSO) { + UserData fromSSO = CustomUserRegistry.getUserDetailsFromAuthentication(); + if(fromSSO != null) { + userForm.copyFrom(fromSSO, request.getLocale()); + userForm.setDataFromSSO(true); + } } userForm.setPasswordText(null); @@ -161,35 +170,81 @@ ud.setEnabled(Boolean.TRUE); // If user set both password and passwordConfirm then reset password - if ( !StringUtils.isEmpty(form.getPasswordText()) - && !StringUtils.isEmpty(form.getPasswordConfirm())) { + if ( !StringUtils.isEmpty(form.getPasswordText()) + && !StringUtils.isEmpty(form.getPasswordConfirm())) { ud.resetPassword(RollerFactory.getRoller(), form.getPasswordText(), form.getPasswordConfirm()); } - // save new user - mgr.addUser(ud); - RollerFactory.getRoller().flush(); - - if (form.getAdminCreated()) { - // User created for admin, so return to new user page with empty form - msgs.add(ActionMessages.GLOBAL_MESSAGE, - new ActionMessage("newUser.created")); - saveMessages(request, msgs); - form.reset(mapping, request); - return createUser(mapping, actionForm, request, response); - } else { - // User registered, so go to welcome page - request.setAttribute("contextURL", - RollerRuntimeConfig.getAbsoluteContextURL()); + boolean activationEnabled = RollerConfig.getBooleanProperty( + "user.account.activation.enabled"); + if (activationEnabled) { + // User account will be enabled after the activation process + ud.setEnabled(Boolean.FALSE); + + // Create & save the activation data + String activationCode = UUID.randomUUID().toString(); - // Invalidate session, otherwise new user who was originally authenticated - // via LDAP/SSO will remain logged in with a but without a valid Roller role. - request.getSession().invalidate(); + if (mgr.getUserByActivationCode(activationCode) != null) { + // In the *extremely* unlikely event that we generate an + // activation code that is already use, we'll retry 3 times. + int numOfRetries = 3; + if (numOfRetries < 1) numOfRetries = 1; + for (int i = 0; i < numOfRetries; i++) { + activationCode = UUID.randomUUID().toString(); + if (mgr.getUserByActivationCode(activationCode) == null) { + break; + } else { + activationCode = null; + } + } + // In more unlikely event that three retries isn't enough + if (activationCode == null){ + throw new RollerException("error.add.user.activationCodeInUse"); + } + } + ud.setActivationCode(activationCode); + } + + if (activationEnabled && ud.getActivationCode() != null) { + // send activation mail to the user + sendActivationMail(request, ud, errors); - return mapping.findForward("welcome.page"); + // activationStatus = 1:activated, 0:has to be activated, -1:error + request.setAttribute("activationStatus", "0"); } - + + if (errors.size() > 0) { + // Error occured so save it and bail out + saveErrors(request, errors); + + } else { + // save new user + mgr.addUser(ud); + RollerFactory.getRoller().flush(); + + if (form.getAdminCreated()) { + // User created for admin, so return to new user page with empty form + msgs.add(ActionMessages.GLOBAL_MESSAGE, + new ActionMessage("newUser.created")); + saveMessages(request, msgs); + form.reset(mapping, request); + return createUser(mapping, actionForm, request, response); + + } else { + // User registered, so go to welcome page + request.setAttribute("contextURL", + RollerRuntimeConfig.getAbsoluteContextURL()); + + // Invalidate session, otherwise new user who was originally + // authenticated via LDAP/SSO will remain logged in with + // a but without a valid Roller role. + request.getSession().invalidate(); + + return mapping.findForward("welcome.page"); + } + } + } catch (RollerException e) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(e.getMessage())); saveErrors(request,errors); @@ -208,25 +263,25 @@ /** Validate user form. TODO: replace with Struts validation. */ protected ActionMessages validate( UserFormEx form, ActionMessages errors ) { - + // if usingSSO, we don't want to error on empty password/username from HTML form. form.setDataFromSSO(false); boolean usingSSO = RollerConfig.getBooleanProperty("users.sso.enabled"); if(usingSSO) { - boolean storePassword = RollerConfig.getBooleanProperty("users.sso.passwords.saveInRollerDb"); - UserData fromSSO = CustomUserRegistry.getUserDetailsFromAuthentication(); - if(fromSSO != null) { - String password = RollerConfig.getProperty("users.sso.passwords.defaultValue", "<unknown>"); - if(storePassword) { - password = fromSSO.getPassword(); - } - form.setPasswordText(password); - form.setPasswordConfirm(password); - form.setUserName(fromSSO.getUserName()); - form.setDataFromSSO(true); - } + boolean storePassword = RollerConfig.getBooleanProperty("users.sso.passwords.saveInRollerDb"); + UserData fromSSO = CustomUserRegistry.getUserDetailsFromAuthentication(); + if(fromSSO != null) { + String password = RollerConfig.getProperty("users.sso.passwords.defaultValue", "<unknown>"); + if(storePassword) { + password = fromSSO.getPassword(); + } + form.setPasswordText(password); + form.setPasswordConfirm(password); + form.setUserName(fromSSO.getUserName()); + form.setDataFromSSO(true); + } } - + super.validate(form, errors); if ( StringUtils.isEmpty(form.getPasswordText()) && StringUtils.isEmpty(form.getPasswordConfirm())) { @@ -234,6 +289,153 @@ new ActionError("error.add.user.missingPassword")); } return errors; + } + + /** + * Send activation mail + */ + private void sendActivationMail( + HttpServletRequest request, UserData user, ActionMessages errors) { + + try { + javax.naming.Context ctx = (javax.naming.Context) + new InitialContext().lookup("java:comp/env"); + Session mailSession = (Session) ctx.lookup("mail/Session"); + if (mailSession != null) { + ResourceBundle resources = ResourceBundle.getBundle( + "ApplicationResources", getLocaleInstance(user.getLocale())); + + String from = RollerRuntimeConfig.getProperty( + "user.account.activation.mail.from"); + + String cc[] = new String[0]; + String bcc[] = new String[0]; + String to[] = new String[] { user.getEmailAddress() }; + String subject = resources.getString( + "user.account.activation.mail.subject"); + String content; + + String rootURL = RollerRuntimeConfig.getAbsoluteContextURL(); + + if (rootURL == null || rootURL.trim().length() == 0) { + rootURL = RequestUtils.serverURL(request) + + request.getContextPath(); + } + + StringBuffer sb = new StringBuffer(); + + // activationURL= + // rootURL/roller-ui/user.do?method=activateUser&activationCode=***** + String activationURL = rootURL + + "/roller-ui/user.do?method=activateUser&activationCode=" + + user.getActivationCode(); + sb.append(MessageFormat.format( + resources.getString("user.account.activation.mail.content"), + new Object[] { user.getFullName(), user.getUserName(), + activationURL })); + content = sb.toString(); + + MailUtil.sendHTMLMessage(mailSession, from, to, cc, bcc, subject, content); + } + + } catch (MessagingException me) { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError( + "error.add.user.mailSendException")); + log.debug("ERROR sending email", me); + } catch (NamingException ne) { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError( + "error.add.user.mailSetupException")); + log.error("ERROR in mail setup?", ne); + } catch (MalformedURLException ue) { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError( + "error.add.user.mailSetupException")); + log.error("ERROR in absolute URL setting?", ue); + } + } + + /** + * Process GET of new user account activation page + * (allows to activate the user account ) + */ + public ActionForward activateUser(ActionMapping mapping, + ActionForm actionForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + ActionMessages errors = new ActionMessages(); + + try { + UserManager mgr = RollerFactory.getRoller().getUserManager(); + + if (request.getParameter("activationCode") == null) { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError( + "error.activate.user.missingActivationCode")); + } else { + String activationCode = request.getParameter("activationCode").toString(); + UserData user = mgr.getUserByActivationCode(activationCode); + + if (user != null) { + // enable user account + user.setEnabled(Boolean.TRUE); + user.setActivationCode(null); + mgr.saveUser(user); + RollerFactory.getRoller().flush(); + + /** + * activationStatus = 1:activated, 0:has to be activated, + * -1:error + */ + request.setAttribute("activationStatus", "1"); + + } else { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError( + "error.activate.user.invalidActivationCode")); + } + } + + } catch (RollerException e) { + errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(e.getMessage())); + saveErrors(request, errors); + mLogger.error("ERROR in activateUser", e); + } + + if (!errors.isEmpty()) { + saveErrors(request, errors); + /** + * activationStatus = 1:activated, 0:has to be activated, -1:error + */ + request.setAttribute("activationStatus", "-1"); + } + + return mapping.findForward("welcome.page"); + } + + /** + * Copied from WebsiteData.java by sedat + */ + private Locale getLocaleInstance(String locale) { + if (locale != null) { + String[] localeStr = StringUtils.split(locale, "_"); + if (localeStr.length == 1) { + if (localeStr[0] == null) + localeStr[0] = ""; + return new Locale(localeStr[0]); + } else if (localeStr.length == 2) { + if (localeStr[0] == null) + localeStr[0] = ""; + if (localeStr[1] == null) + localeStr[1] = ""; + return new Locale(localeStr[0], localeStr[1]); + } else if (localeStr.length == 3) { + if (localeStr[0] == null) + localeStr[0] = ""; + if (localeStr[1] == null) + localeStr[1] = ""; + if (localeStr[2] == null) + localeStr[2] = ""; + return new Locale(localeStr[0], localeStr[1], localeStr[2]); + } + } + return Locale.getDefault(); } } Modified: incubator/roller/trunk/web/WEB-INF/classes/ApplicationResources.properties URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/WEB-INF/classes/ApplicationResources.properties?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/web/WEB-INF/classes/ApplicationResources.properties (original) +++ incubator/roller/trunk/web/WEB-INF/classes/ApplicationResources.properties Tue Apr 3 15:03:02 2007 @@ -334,6 +334,8 @@ configForm.debugMode=Enable debug mode? configForm.userSettings=User Settings +configForm.accountActivation=Require new users to activate accounts via email +configForm.accountActivationFromAddress=Email address for account activation messages configForm.allowNewUsers=Allow New Users? configForm.registrationUrl=External registration url configForm.editorPages=Editor Pages @@ -1874,4 +1876,29 @@ yourWebsites.userCount=Members: yourWebsites.todaysHits=Todays hits: + +# ---------------------------------------------------------------- Activating User Account with E-mail + +error.add.user.mailSendException=ERROR sending email, is your address valid? +error.add.user.mailSetupException=ERROR in email configuration, contact Roller administrator +error.add.user.activationCodeInUse=\ +An error occured while generating an activation code for your user account. \ +Please try again. +error.activate.user.missingActivationCode=Activation code is missing. +error.activate.user.invalidActivationCode=\ +Invalid activation code. You must have already activated your account. \ +If not, please contact your Roller administrator for assistance. + +user.account.activation.mail.subject=Roller: Activation Code for your user account +user.account.activation.mail.content=\ +<html><body style=\"background: white; color: black; font-size: 12px\"> \ +<p>To activate your new Roller user account with username [{1}], please click the link below:</p> \ +<p><a href=\"{2}\">{2}</a></p></body></html> + +welcome.user.account.activated=Your user account is activated. +welcome.user.account.not.activated=In order to login to the system, you have to \ +activate your user account by clicking the link that is sent to you via e-mail. + + + Modified: incubator/roller/trunk/web/WEB-INF/classes/rollerRuntimeConfigDefs.xml URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/WEB-INF/classes/rollerRuntimeConfigDefs.xml?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/web/WEB-INF/classes/rollerRuntimeConfigDefs.xml (original) +++ incubator/roller/trunk/web/WEB-INF/classes/rollerRuntimeConfigDefs.xml Tue Apr 3 15:03:02 2007 @@ -94,6 +94,14 @@ <type>boolean</type> <default-value>true</default-value> </property-def> + <property-def name="user.account.activation.enabled" key="configForm.accountActivation"> + <type>boolean</type> + <default-value>true</default-value> + </property-def> + <property-def name="user.account.activation.mail.from" key="configForm.accountActivationFromAddress"> + <type>string</type> + <default-value></default-value> + </property-def> <property-def name="users.registration.url" key="configForm.registrationUrl"> <type>string</type> <default-value></default-value> Modified: incubator/roller/trunk/web/WEB-INF/jsps/core/welcome.jsp URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/WEB-INF/jsps/core/welcome.jsp?view=diff&rev=525301&r1=525300&r2=525301 ============================================================================== --- incubator/roller/trunk/web/WEB-INF/jsps/core/welcome.jsp (original) +++ incubator/roller/trunk/web/WEB-INF/jsps/core/welcome.jsp Tue Apr 3 15:03:02 2007 @@ -19,13 +19,35 @@ <h2><fmt:message key="welcome.title" /></h2> -<p><fmt:message key="welcome.accountCreated" /></p> +<% -<p><html:link forward="login-redirect"><fmt:message key="welcome.clickHere" /></html:link> -<fmt:message key="welcome.toLoginAndPost" /></p> -<br /> -<br /> -<br /> + String activationStatus = (String) request.getAttribute("activationStatus"); + if (activationStatus != null){ + if (activationStatus.equals("0")) { + %> + <p><fmt:message key="welcome.accountCreated" /></p> + <p><fmt:message key="welcome.user.account.not.activated" /></p> + <% + } else if(activationStatus.equals("1")) { + %> + <p><fmt:message key="welcome.user.account.activated" /></p> + <p><html:link forward="login-redirect"><fmt:message key="welcome.clickHere" /></html:link> + <fmt:message key="welcome.toLoginAndPost" /></p> + <% + } else if(activationStatus.equals("-1")) { + //error + } + } else { + %> + <p><fmt:message key="welcome.accountCreated" /></p> + <p><html:link forward="login-redirect"><fmt:message key="welcome.clickHere" /></html:link> + <fmt:message key="welcome.toLoginAndPost" /></p> + <% + } +%> +<br /> +<br /> +<br />
