=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2013-12-25 15:01:48 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2014-01-23 15:04:07 +0000 @@ -79,6 +79,7 @@ final String KEY_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "scheduleAggregateQueryBuilderTackStrategy"; final String KEY_CONFIGURATION = "keyConfig"; final String KEY_ACCOUNT_RECOVERY = "keyAccountRecovery"; + final String KEY_ACCOUNT_INVITE = "keyAccountInvite"; final String KEY_LAST_MONITORING_RUN = "keyLastMonitoringRun"; final String KEY_GOOGLE_ANALYTICS_UA = "googleAnalyticsUA"; final String KEY_CREDENTIALS_EXPIRES = "credentialsExpires"; @@ -135,6 +136,8 @@ boolean accountRecoveryEnabled(); + boolean accountInviteEnabled(); + boolean selfRegistrationNoRecaptcha(); boolean emailEnabled(); === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultSecurityService.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultSecurityService.java 2014-01-13 15:16:22 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultSecurityService.java 2014-01-25 18:46:36 +0000 @@ -46,7 +46,6 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.Arrays; -import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -61,10 +60,13 @@ { private static final Log log = LogFactory.getLog( DefaultSecurityService.class ); - private static final String RESTORE_PATH = "/dhis-web-commons/security/restore.action"; - - private static final int TOKEN_LENGTH = 50; - private static final int CODE_LENGTH = 15; + private static final String RESTORE_PATH = "/dhis-web-commons/security/"; + + private static final int INVITED_USERNAME_UNIQUE_LENGTH = 15; + private static final int INVITED_USER_PASSWORD_LENGTH = 40; + + private static final int RESTORE_TOKEN_LENGTH = 50; + private static final int RESTORE_CODE_LENGTH = 15; // ------------------------------------------------------------------------- // Dependencies @@ -105,7 +107,25 @@ // SecurityService implementation // ------------------------------------------------------------------------- - public boolean sendRestoreMessage( UserCredentials credentials, String rootPath ) + public boolean prepareUserForInvite( UserCredentials credentials ) + { + if ( credentials == null || credentials.getUser() == null ) + { + return false; + } + + String username = "invitedUser_" + CodeGenerator.generateCode( INVITED_USERNAME_UNIQUE_LENGTH ); + String rawPassword = CodeGenerator.generateCode( INVITED_USER_PASSWORD_LENGTH ); + + credentials.getUser().setSurname( "(TBD)" ); + credentials.getUser().setFirstName( "(TBD)" ); + credentials.setUsername( username ); + credentials.setPassword( passwordManager.encodePassword( username, rawPassword ) ); + + return true; + } + + public boolean sendRestoreMessage( UserCredentials credentials, String rootPath, RestoreType restoreType ) { if ( credentials == null || rootPath == null ) { @@ -114,58 +134,58 @@ if ( credentials.getUser() == null || credentials.getUser().getEmail() == null ) { - log.info( "Could not send message as user does not exist or has no email: " + credentials ); + log.info( "Could not send " + restoreType.name() + " message as user does not exist or has no email: " + credentials ); return false; } if ( !ValidationUtils.emailIsValid( credentials.getUser().getEmail() ) ) { - log.info( "Could not send message as email is invalid" ); + log.info( "Could not send " + restoreType.name() + " message as email is invalid" ); return false; } if ( !systemSettingManager.emailEnabled() ) { - log.info( "Could not send message as email is not configured" ); + log.info( "Could not send " + restoreType.name() + " message as email is not configured" ); return false; } if ( credentials.hasAnyAuthority( Arrays.asList( UserAuthorityGroup.CRITICAL_AUTHS ) ) ) { - log.info( "Not allowed to recover credentials with critical authorities" ); + log.info( "Not allowed to " + restoreType.name() + " users with critical authorities" ); return false; } - String[] result = initRestore( credentials ); + String[] result = initRestore( credentials, restoreType ); Set users = new HashSet(); users.add( credentials.getUser() ); Map vars = new HashMap(); vars.put( "rootPath", rootPath ); - vars.put( "restorePath", rootPath + RESTORE_PATH ); + vars.put( "restorePath", rootPath + RESTORE_PATH + restoreType.getAction() ); vars.put( "token", result[0] ); vars.put( "code", result[1] ); vars.put( "username", credentials.getUsername() ); - String text1 = new VelocityManager().render( vars, "restore_message1" ); - String text2 = new VelocityManager().render( vars, "restore_message2" ); + String text1 = new VelocityManager().render( vars, restoreType.getEmailTemplate() + "1" ); + String text2 = new VelocityManager().render( vars, restoreType.getEmailTemplate() + "2" ); - emailMessageSender.sendMessage( "User account restore confirmation (message 1 of 2)", text1, null, users, true ); - emailMessageSender.sendMessage( "User account restore confirmation (message 2 of 2)", text2, null, users, true ); + emailMessageSender.sendMessage( restoreType.getEmailSubject() + " (message 1 of 2)", text1, null, users, true ); + emailMessageSender.sendMessage( restoreType.getEmailSubject() + " (message 2 of 2)", text2, null, users, true ); return true; } - public String[] initRestore( UserCredentials credentials ) + public String[] initRestore( UserCredentials credentials, RestoreType restoreType ) { - String token = CodeGenerator.generateCode( TOKEN_LENGTH ); - String code = CodeGenerator.generateCode( CODE_LENGTH ); + String token = restoreType.getTokenPrefix() + CodeGenerator.generateCode( RESTORE_TOKEN_LENGTH ); + String code = CodeGenerator.generateCode( RESTORE_CODE_LENGTH ); String hashedToken = passwordManager.encodePassword( credentials.getUsername(), token ); String hashedCode = passwordManager.encodePassword( credentials.getUsername(), code ); - Date expiry = new Cal().now().add( Calendar.HOUR_OF_DAY, 1 ).time(); + Date expiry = new Cal().now().add( restoreType.getExpiryIntervalType(), restoreType.getExpiryIntervalCount() ).time(); credentials.setRestoreToken( hashedToken ); credentials.setRestoreCode( hashedCode ); @@ -177,24 +197,15 @@ return result; } - public boolean restore( UserCredentials credentials, String token, String code, String newPassword ) + public boolean restore( UserCredentials credentials, String token, String code, String newPassword, RestoreType restoreType ) { - if ( credentials == null || token == null || code == null || newPassword == null ) + if ( credentials == null || token == null || code == null || newPassword == null + || !canRestoreNow( credentials, token, code, restoreType ) ) { return false; } String username = credentials.getUsername(); - - token = passwordManager.encodePassword( username, token ); - code = passwordManager.encodePassword( username, code ); - - Date date = new Cal().now().time(); - - if ( !credentials.canRestore( token, code, date ) ) - { - return false; - } newPassword = passwordManager.encodePassword( username, newPassword ); @@ -209,13 +220,36 @@ return true; } - public boolean verifyToken( UserCredentials credentials, String token ) + public boolean canRestoreNow( UserCredentials credentials, String token, String code, RestoreType restoreType ) + { + if ( !verifyToken ( credentials, token, restoreType ) ) + { + return false; + } + + String username = credentials.getUsername(); + + String encodedToken = passwordManager.encodePassword( username, token ); + String encodedCode = passwordManager.encodePassword( username, code ); + + Date date = new Cal().now().time(); + + return credentials.canRestore( encodedToken, encodedCode, date ); + } + + public boolean verifyToken( UserCredentials credentials, String token, RestoreType restoreType ) { if ( credentials == null || token == null ) { return false; } + if ( !token.startsWith( restoreType.getTokenPrefix() ) ) + { + log.info( "Wrong prefix for restore type " + restoreType.name() + " on token: " + token ); + return false; + } + if ( credentials.getRestoreToken() == null ) { log.info( "Could not verify token as user has no token: " + credentials ); === added file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/RestoreType.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/RestoreType.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/RestoreType.java 2014-01-17 03:48:57 +0000 @@ -0,0 +1,122 @@ +package org.hisp.dhis.security; +/* + * Copyright (c) 2004-2013, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.Calendar; + +/** + * Type of user account restore operation. + * + * @author Jim Grace + */ + +public enum RestoreType +{ + RECOVER_PASSWORD ("R", Calendar.HOUR_OF_DAY, 1, "restore_message", "User account restore confirmation", "restore.action" ), + INVITE ("I", Calendar.MONTH, 3, "invite_message", "Create DHIS 2 user account invitation", "invite.action" ); + + /** + * Prefix to be used on restore token. This prevents one type of restore + * URL from being hacked and used for a different type of restore. + */ + private final String tokenPrefix; + + /** + * Type of Calendar interval before the restore expires. + */ + private final int expiryIntervalType; + + /** + * Count of Calendar intervals before the restore expires. + */ + private final int expiryIntervalCount; + + /** + * Name of the email template for this restore action type. + */ + private final String emailTemplate; + + /** + * Subject line of the email for this restore action type. + */ + private final String emailSubject; + + /** + * Return web action to put in the email message. + */ + private final String action; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + RestoreType( String tokenPrefix, int expiryIntervalType, int expiryIntervalCount, + String emailTemplate, String emailSubject, String action ) + { + this.tokenPrefix = tokenPrefix; + this.expiryIntervalType = expiryIntervalType; + this.expiryIntervalCount = expiryIntervalCount; + this.emailTemplate = emailTemplate; + this.emailSubject = emailSubject; + this.action = action; + } + + // ------------------------------------------------------------------------- + // Getters + // ------------------------------------------------------------------------- + + public String getTokenPrefix() + { + return tokenPrefix; + } + + public int getExpiryIntervalType() + { + return expiryIntervalType; + } + + public int getExpiryIntervalCount() + { + return expiryIntervalCount; + } + + public String getEmailTemplate() + { + return emailTemplate; + } + + public String getEmailSubject() + { + return emailSubject; + } + + public String getAction() + { + return action; + } +} === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/SecurityService.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/SecurityService.java 2014-01-13 15:16:22 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/SecurityService.java 2014-01-25 18:46:36 +0000 @@ -37,27 +37,42 @@ public interface SecurityService { /** - * Will invoke the initiateRestore method and dispatch email messages with + * Sets information for a user who will be invited by email to finish + * setting up their user account. + * + * @param credentials the credentials of the user to invite. + * @return true if the invitation was sent, otherwise false. + */ + boolean prepareUserForInvite( UserCredentials credentials ); + + /** + * Invokes the initRestore method and dispatches email messages with * restore information to the user. + *

+ * In the case of inviting a user to finish setting up an account, + * the user account must already be configured with the profile desired + * for the user (e.g., locale, organisation unit(s), role(s), etc.) * * @param credentials the credentials for the user to send restore message. * @param rootPath the root path of the request. + * @param restoreType type of restore operation (e.g. pw recovery, invite). * @return false if any of the arguments are null or if the user credentials * identified by the user name does not exist, true otherwise. */ - boolean sendRestoreMessage( UserCredentials credentials, String rootPath ); + boolean sendRestoreMessage( UserCredentials credentials, String rootPath, RestoreType restoreType ); /** - * Will populate the restoreToken and restoreCode property of the given - * credentials with a hashed version of auto-generated values. Will set the - * restoreExpiry property with a date time one hour from now. Changes will be - * persisted. + * Populates the restoreToken and restoreCode property of the given + * credentials with a hashed version of auto-generated values. Sets the + * restoreExpiry property with a date time some interval from now depending + * on the restore type. Changes are persisted. * * @param credentials the user credentials. + * @param restoreType type of restore operation (e.g. pw recovery, invite). * @return an array where index 0 is the clear-text token and index 1 the * clear-text code. */ - String[] initRestore( UserCredentials credentials ); + String[] initRestore( UserCredentials credentials, RestoreType restoreType ); /** * Tests whether the given token and code are valid for the given user name. @@ -70,9 +85,24 @@ * @param token the token. * @param code the code. * @param newPassword the proposed new password. - * @return true or false. - */ - boolean restore( UserCredentials credentials, String token, String code, String newPassword ); + * @param restoreType type of restore operation (e.g. pw recovery, invite). + * @return true or false. + */ + boolean restore( UserCredentials credentials, String token, String code, String newPassword, RestoreType restoreType ); + + /** + * Tests whether the given token and code are valid for the given user name. + * In order to succeed, the given token and code must match the ones on the + * credentials, and the current date must be before the expiry date time of + * the credentials. + * + * @param credentials the user credentials. + * @param token the token. + * @param code the code. + * @param restoreType type of restore operation (e.g. pw recovery, invite). + * @return true or false. + */ + boolean canRestoreNow( UserCredentials credentials, String token, String code, RestoreType restoreType ); /** * Tests whether the given token in combination with the given user name is @@ -85,7 +115,7 @@ * identified by the user name does not exist, true if the arguments * are valid. */ - boolean verifyToken( UserCredentials credentials, String token ); + boolean verifyToken( UserCredentials credentials, String token, RestoreType restoreType ); /** * Checks whether current user has read access to object. === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java 2013-12-19 13:25:04 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/setting/DefaultSystemSettingManager.java 2014-01-23 15:04:07 +0000 @@ -163,6 +163,11 @@ return (Boolean) getSystemSetting( KEY_ACCOUNT_RECOVERY, false ); } + public boolean accountInviteEnabled() + { + return (Boolean) getSystemSetting( KEY_ACCOUNT_INVITE, false ); + } + @Override public boolean selfRegistrationNoRecaptcha() { === added file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message1.vm' --- dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message1.vm 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message1.vm 2014-01-17 03:48:57 +0000 @@ -0,0 +1,11 @@ +This is an invitation to create a user account on the DHIS 2 system at ${object.rootPath}. +You have been sent two emails, where this is the first one. Please follow the +link below. In the next step you will be asked to enter a code which has been +sent to you in the other email. + + +${object.restorePath}?username=${object.username}&token=${object.token} + + +You must respond to this invitation within 3 months. If you take no action, +the invitation will expire at that time. === added file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message2.vm' --- dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message2.vm 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/resources/invite_message2.vm 2014-01-23 15:04:07 +0000 @@ -0,0 +1,11 @@ +This is an invitation to create a user account on the DHIS 2 system at ${object.rootPath}. +You have been sent two emails, where this is the second one. Please read the +first email and follow the instructions. Please use the code below to complete +the new account form. + + +${object.code} + + +You must respond to this invitation within 3 months. If you take no action, +the invitation will expire at that time. === modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/SecurityServiceTest.java' --- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/SecurityServiceTest.java 2014-01-13 15:16:22 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/SecurityServiceTest.java 2014-01-25 18:46:36 +0000 @@ -44,7 +44,9 @@ extends DhisSpringTest { private UserCredentials credentials; - + + private UserCredentials otherCredentials; + @Autowired private UserService userService; @@ -60,37 +62,142 @@ credentials = new UserCredentials(); credentials.setUsername( "johndoe" ); credentials.setPassword( "" ); - - User user = createUser( 'A' ); - user.setEmail( "valid@email.com" ); - user.setUserCredentials( credentials ); - credentials.setUser( user ); + + User userA = createUser( 'A' ); + userA.setEmail( "validA@email.com" ); + userA.setUserCredentials( credentials ); + credentials.setUser( userA ); userService.addUserCredentials( credentials ); - } - - @Test - public void testRestore() - { - String[] result = securityService.initRestore( credentials ); - - assertNotNull( result[0] ); - assertNotNull( result[1] ); - assertNotNull( credentials.getRestoreToken() ); - assertNotNull( credentials.getRestoreCode() ); - assertNotNull( credentials.getRestoreExpiry() ); - - boolean verified = securityService.verifyToken( credentials, result[0] ); - - assertTrue( verified ); - - String password = "NewPassword1"; - - boolean restored = securityService.restore( credentials, result[0], result[1], password ); - - assertTrue( restored ); - - String hashedPassword = passwordManager.encodePassword( credentials.getUsername(), password ); - + + otherCredentials = new UserCredentials(); + otherCredentials.setUsername( "janesmith" ); + otherCredentials.setPassword( "" ); + + User userB = createUser( 'B' ); + userB.setEmail( "validB@email.com" ); + userB.setUserCredentials( otherCredentials ); + otherCredentials.setUser( userB ); + userService.addUserCredentials( otherCredentials ); + } + + @Test + public void testRestoreRecoverPassword() + { + String[] result = securityService.initRestore( credentials, RestoreType.RECOVER_PASSWORD ); + + String token = result[0]; + String code = result[1]; + + assertNotNull( token ); + assertNotNull( code ); + assertNotNull( credentials.getRestoreToken() ); + assertNotNull( credentials.getRestoreCode() ); + assertNotNull( credentials.getRestoreExpiry() ); + + // + // verifyToken() + // + assertFalse( securityService.verifyToken( otherCredentials, token, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.verifyToken( credentials, "wrongToken", RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.verifyToken( credentials, token, RestoreType.INVITE ) ); + + assertTrue( securityService.verifyToken( credentials, token, RestoreType.RECOVER_PASSWORD ) ); + + // + // canRestoreNow() + // + assertFalse( securityService.canRestoreNow( otherCredentials, token, code, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.canRestoreNow( credentials, "wrongToken", code, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.canRestoreNow( credentials, token, "wrongCode", RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.canRestoreNow( credentials, token, code, RestoreType.INVITE ) ); + + assertTrue( securityService.canRestoreNow( credentials, token, code, RestoreType.RECOVER_PASSWORD ) ); + + // + // restore() + // + String password = "NewPassword1"; + + assertFalse( securityService.restore( otherCredentials, token, code, password, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.restore( credentials, "wrongToken", code, password, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.restore( credentials, token, "wrongCode", password, RestoreType.RECOVER_PASSWORD ) ); + + assertFalse( securityService.restore( credentials, token, code, password, RestoreType.INVITE ) ); + + assertTrue( securityService.restore( credentials, token, code, password, RestoreType.RECOVER_PASSWORD ) ); + + // + // check password + // + String hashedPassword = passwordManager.encodePassword( credentials.getUsername(), password ); + + assertEquals( hashedPassword, credentials.getPassword() ); + } + + @Test + public void testRestoreInvite() + { + String[] result = securityService.initRestore( credentials, RestoreType.INVITE ); + String token = result[0]; + String code = result[1]; + + assertNotNull( token ); + assertNotNull( code ); + assertNotNull( credentials.getRestoreToken() ); + assertNotNull( credentials.getRestoreCode() ); + assertNotNull( credentials.getRestoreExpiry() ); + + // + // verifyToken() + // + assertFalse( securityService.verifyToken( otherCredentials, token, RestoreType.INVITE ) ); + + assertFalse( securityService.verifyToken( credentials, "wrongToken", RestoreType.INVITE ) ); + + assertFalse( securityService.verifyToken( credentials, token, RestoreType.RECOVER_PASSWORD ) ); + + assertTrue( securityService.verifyToken( credentials, token, RestoreType.INVITE ) ); + + // + // canRestoreNow() + // + assertFalse( securityService.canRestoreNow( otherCredentials, token, code, RestoreType.INVITE ) ); + + assertFalse( securityService.canRestoreNow( credentials, "wrongToken", code, RestoreType.INVITE ) ); + + assertFalse( securityService.canRestoreNow( credentials, token, "wrongCode", RestoreType.INVITE ) ); + + assertFalse( securityService.canRestoreNow( credentials, token, code, RestoreType.RECOVER_PASSWORD ) ); + + assertTrue( securityService.canRestoreNow( credentials, token, code, RestoreType.INVITE ) ); + + // + // restore() + // + String password = "NewPassword1"; + + assertFalse( securityService.restore( otherCredentials, token, code, password, RestoreType.INVITE ) ); + + assertFalse( securityService.restore( credentials, "wrongToken", code, password, RestoreType.INVITE ) ); + + assertFalse( securityService.restore( credentials, token, "wrongCode", password, RestoreType.INVITE ) ); + + assertFalse( securityService.restore( credentials, token, code, password, RestoreType.RECOVER_PASSWORD ) ); + + assertTrue( securityService.restore( credentials, token, code, password, RestoreType.INVITE ) ); + + // + // check password + // + String hashedPassword = passwordManager.encodePassword( credentials.getUsername(), password ); + assertEquals( hashedPassword, credentials.getPassword() ); } } === modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java' --- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java 2014-01-13 15:16:22 +0000 +++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java 2014-01-23 15:04:07 +0000 @@ -35,7 +35,9 @@ import org.hisp.dhis.api.utils.ContextUtils; import org.hisp.dhis.configuration.ConfigurationService; import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.period.Cal; import org.hisp.dhis.security.PasswordManager; +import org.hisp.dhis.security.RestoreType; import org.hisp.dhis.security.SecurityService; import org.hisp.dhis.setting.SystemSettingManager; import org.hisp.dhis.system.util.ValidationUtils; @@ -131,7 +133,7 @@ return "User does not exist: " + username; } - boolean recover = securityService.sendRestoreMessage( credentials, rootPath ); + boolean recover = securityService.sendRestoreMessage( credentials, rootPath, RestoreType.RECOVER_PASSWORD ); if ( !recover ) { @@ -180,7 +182,7 @@ return "User does not exist: " + username; } - boolean restore = securityService.restore( credentials, token, code, password ); + boolean restore = securityService.restore( credentials, token, code, password, RestoreType.RECOVER_PASSWORD ); if ( !restore ) { @@ -203,17 +205,51 @@ @RequestParam String email, @RequestParam String phoneNumber, @RequestParam String employer, + @RequestParam String inviteUsername, + @RequestParam String inviteToken, + @RequestParam String inviteCode, @RequestParam( value = "recaptcha_challenge_field", required = false ) String recapChallenge, @RequestParam( value = "recaptcha_response_field", required = false ) String recapResponse, HttpServletRequest request, HttpServletResponse response ) { - boolean allowed = configurationService.getConfiguration().selfRegistrationAllowed(); - - if ( !allowed ) - { - response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); - return "User self registration is not allowed"; + UserCredentials credentials = null; + + boolean invitedByEmail = !inviteUsername.isEmpty(); + + if ( invitedByEmail ) + { + if ( !systemSettingManager.accountInviteEnabled() ) + { + response.setStatus( HttpServletResponse.SC_CONFLICT ); + return "Account invite is not enabled"; + } + + credentials = userService.getUserCredentialsByUsername( inviteUsername ); + + if ( credentials == null ) + { + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "Invitation link not valid"; + } + + boolean canRestore = securityService.canRestoreNow( credentials, inviteToken, inviteCode, RestoreType.INVITE ); + + if ( !canRestore ) + { + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "Invitation code not valid"; + } + } + else + { + boolean allowed = configurationService.getConfiguration().selfRegistrationAllowed(); + + if ( !allowed ) + { + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "User self registration is not allowed"; + } } // --------------------------------------------------------------------- @@ -240,9 +276,9 @@ return "User name is not specified or invalid"; } - UserCredentials credentials = userService.getUserCredentialsByUsername( username ); + UserCredentials usernameAlreadyTakenCredentials = userService.getUserCredentialsByUsername( username ); - if ( credentials != null ) + if ( usernameAlreadyTakenCredentials != null ) { response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); return "User name is already taken"; @@ -333,32 +369,64 @@ // Create and save user, return 201 // --------------------------------------------------------------------- - UserAuthorityGroup userRole = configurationService.getConfiguration().getSelfRegistrationRole(); - OrganisationUnit orgUnit = configurationService.getConfiguration().getSelfRegistrationOrgUnit(); - - User user = new User(); - user.setFirstName( firstName ); - user.setSurname( surname ); - user.setEmail( email ); - user.setPhoneNumber( phoneNumber ); - user.setEmployer( employer ); - user.getOrganisationUnits().add( orgUnit ); - - credentials = new UserCredentials(); - credentials.setUsername( username ); - credentials.setPassword( passwordManager.encodePassword( username, password ) ); - credentials.setSelfRegistered( true ); - credentials.setUser( user ); - credentials.getUserAuthorityGroups().add( userRole ); - - user.setUserCredentials( credentials ); - - userService.addUser( user ); - userService.addUserCredentials( credentials ); - - authenticate( username, password, userRole, request ); - - log.info( "Created user with username: " + username ); + if ( invitedByEmail ) + { + boolean restored = securityService.restore( credentials, inviteToken, inviteCode, password, RestoreType.INVITE ); + + if ( !restored ) + { + log.info( "Invite restore failed for: " + inviteUsername ); + + response.setStatus( HttpServletResponse.SC_BAD_REQUEST ); + return "Unable to create invited user account"; + } + + User user = credentials.getUser(); + user.setFirstName( firstName ); + user.setSurname( surname ); + user.setEmail( email ); + user.setPhoneNumber( phoneNumber ); + user.setEmployer( employer ); + + credentials.setUsername( username ); + credentials.setPassword( passwordManager.encodePassword( username, password ) ); + + userService.updateUser( user ); + userService.updateUserCredentials( credentials ); + + log.info( "User " + username + " accepted invitation for " + inviteUsername ); + } + else + { + UserAuthorityGroup userRole = configurationService.getConfiguration().getSelfRegistrationRole(); + OrganisationUnit orgUnit = configurationService.getConfiguration().getSelfRegistrationOrgUnit(); + + User user = new User(); + user.setFirstName( firstName ); + user.setSurname( surname ); + user.setEmail( email ); + user.setPhoneNumber( phoneNumber ); + user.setEmployer( employer ); + user.getOrganisationUnits().add( orgUnit ); + + credentials = new UserCredentials(); + credentials.setUsername( username ); + credentials.setPassword( passwordManager.encodePassword( username, password ) ); + credentials.setSelfRegistered( true ); + credentials.setUser( user ); + credentials.getUserAuthorityGroups().add( userRole ); + + user.setUserCredentials( credentials ); + + userService.addUser( user ); + userService.addUserCredentials( credentials ); + + log.info( "Created user with username: " + username ); + } + + Set authorities = getAuthorities( credentials.getUserAuthorityGroups() ); + + authenticate( username, password, authorities, request ); response.setStatus( HttpServletResponse.SC_CREATED ); return "Account created"; @@ -473,23 +541,9 @@ session.setAttribute( "SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext() ); } - private void authenticate( String username, String rawPassword, UserAuthorityGroup userRole, HttpServletRequest request ) - { - UsernamePasswordAuthenticationToken token = - new UsernamePasswordAuthenticationToken( username, rawPassword, getAuthorities( userRole ) ); - - Authentication auth = authenticationManager.authenticate( token ); - - SecurityContextHolder.getContext().setAuthentication( auth ); - - HttpSession session = request.getSession(); - - session.setAttribute( "SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext() ); - } - - private Collection getAuthorities( Set userRoles ) - { - Collection auths = new HashSet(); + private Set getAuthorities( Set userRoles ) + { + Set auths = new HashSet(); for ( UserAuthorityGroup userRole : userRoles ) { @@ -499,9 +553,9 @@ return auths; } - private Collection getAuthorities( UserAuthorityGroup userRole ) + private Set getAuthorities( UserAuthorityGroup userRole ) { - Collection auths = new HashSet(); + Set auths = new HashSet(); for ( String auth : userRole.getAuthorities() ) { === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js 2013-12-19 13:25:04 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js 2014-01-17 03:48:57 +0000 @@ -28,6 +28,11 @@ email: true, rangelength: [ 4, 80 ] }, + inviteEmail : { + required : true, + email : true, + rangelength : [ 4, 80 ] + }, phoneNumber: { required: true, rangelength: [ 6, 30 ] === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/updateUserAccountForm.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/updateUserAccountForm.js 2012-10-11 17:21:32 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/updateUserAccountForm.js 2014-01-17 03:48:57 +0000 @@ -5,6 +5,7 @@ /* some customization is needed for the updateUserAccount validation rules */ rules["rawPassword"].required = false; rules["retypePassword"].required = false; + rules["inviteEmail"].required = false; rules["oldPassword"] = { required: true === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js 2014-01-14 13:07:02 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js 2014-01-17 03:48:57 +0000 @@ -31,6 +31,11 @@ "email" : true, "rangelength" : [ 0, 160 ] }, + "inviteEmail" : { + "required" : true, + "email" : true, + "rangelength" : [ 4, 160 ] + }, "phoneNumber" : { "rangelength" : [ 0, 80 ] }, === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm 2013-12-19 13:25:04 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm 2014-01-23 15:04:07 +0000 @@ -27,6 +27,16 @@

+ + + + + + - + === modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java' --- dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java 2013-12-25 15:01:48 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/interceptor/SystemSettingInterceptor.java 2014-01-23 15:04:07 +0000 @@ -94,6 +94,7 @@ map.put( KEY_PHONE_NUMBER_AREA_CODE, systemSettingManager.getSystemSetting( KEY_PHONE_NUMBER_AREA_CODE, "" ) ); map.put( KEY_MULTI_ORGANISATION_UNIT_FORMS, systemSettingManager.getSystemSetting( KEY_MULTI_ORGANISATION_UNIT_FORMS, false ) ); map.put( KEY_ACCOUNT_RECOVERY, systemSettingManager.getSystemSetting( KEY_ACCOUNT_RECOVERY, false ) ); + map.put( KEY_ACCOUNT_INVITE, systemSettingManager.getSystemSetting( KEY_ACCOUNT_INVITE, false ) ); map.put( KEY_CONFIGURATION, configurationService.getConfiguration() ); map.put( KEY_APP_BASE_URL, systemSettingManager.getSystemSetting( KEY_APP_BASE_URL ) ); map.put( KEY_GOOGLE_ANALYTICS_UA, systemSettingManager.getSystemSetting( KEY_GOOGLE_ANALYTICS_UA, "" ) ); === added file 'dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsInviteTokenValidAction.java' --- dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsInviteTokenValidAction.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsInviteTokenValidAction.java 2014-01-23 15:04:07 +0000 @@ -0,0 +1,132 @@ +package org.hisp.dhis.useraccount.action; + +/* + * Copyright (c) 2004-2013, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.hisp.dhis.security.RestoreType; +import org.hisp.dhis.security.SecurityService; +import org.hisp.dhis.setting.SystemSettingManager; +import org.hisp.dhis.user.UserCredentials; +import org.hisp.dhis.user.UserService; +import org.springframework.beans.factory.annotation.Autowired; + +import com.opensymphony.xwork2.Action; + +/** + * @author Jim Grace + */ +public class IsInviteTokenValidAction + implements Action +{ + @Autowired + private SystemSettingManager systemSettingManager; + + @Autowired + private SecurityService securityService; + + @Autowired + private UserService userService; + + // ------------------------------------------------------------------------- + // Input + // ------------------------------------------------------------------------- + + private String username; + + public String getUsername() + { + return username; + } + + public void setUsername( String username ) + { + this.username = username; + } + + private String token; + + public String getToken() + { + return token; + } + + public void setToken( String token ) + { + this.token = token; + } + + // ------------------------------------------------------------------------- + // Output + // ------------------------------------------------------------------------- + + private UserCredentials userCredentials; + + public UserCredentials getUserCredentials() + { + return userCredentials; + } + + private final String accountAction = "invited"; + + public String getAccountAction() + { + return accountAction; + } + + private String email; + + public String getEmail() + { + return email; + } + + // ------------------------------------------------------------------------- + // Action implementation + // ------------------------------------------------------------------------- + + public String execute() + { + if ( !systemSettingManager.accountInviteEnabled() ) + { + return ERROR; + } + + userCredentials = userService.getUserCredentialsByUsername( username ); + + if ( userCredentials == null ) + { + return ERROR; + } + + email = userCredentials.getUser().getEmail(); + + boolean verified = securityService.verifyToken( userCredentials, token, RestoreType.INVITE ); + + return verified ? SUCCESS : ERROR; + } +} === modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsRestoreTokenValidAction.java' --- dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsRestoreTokenValidAction.java 2014-01-13 15:44:28 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/useraccount/action/IsRestoreTokenValidAction.java 2014-01-17 03:48:57 +0000 @@ -28,6 +28,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.hisp.dhis.security.RestoreType; import org.hisp.dhis.security.SecurityService; import org.hisp.dhis.user.UserCredentials; import org.hisp.dhis.user.UserService; @@ -88,7 +89,7 @@ return ERROR; } - boolean verified = securityService.verifyToken( credentials, token ); + boolean verified = securityService.verifyToken( credentials, token, RestoreType.RECOVER_PASSWORD ); return verified ? SUCCESS : ERROR; } === modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/resources/META-INF/dhis/beans.xml' --- dhis-2/dhis-web/dhis-web-commons/src/main/resources/META-INF/dhis/beans.xml 2014-01-08 17:41:22 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/resources/META-INF/dhis/beans.xml 2014-01-23 15:04:07 +0000 @@ -577,6 +577,9 @@ + + === modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/resources/dhis-web-commons.xml' --- dhis-2/dhis-web/dhis-web-commons/src/main/resources/dhis-web-commons.xml 2014-01-08 17:41:22 +0000 +++ dhis-2/dhis-web/dhis-web-commons/src/main/resources/dhis-web-commons.xml 2014-01-23 15:04:07 +0000 @@ -146,10 +146,15 @@ - /dhis-web-commons/useraccount/restore.vm - login.action - - + /dhis-web-commons/useraccount/restore.vm + login.action + + + + /dhis-web-commons/useraccount/account.vm + login.action + + === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java 2013-12-19 13:25:04 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/SetAccessSettingsAction.java 2014-01-23 15:04:07 +0000 @@ -91,6 +91,13 @@ this.accountRecovery = accountRecovery; } + private Boolean accountInvite; + + public void setAccountInvite( Boolean accountInvite ) + { + this.accountInvite = accountInvite; + } + private Integer credentialsExpires; public void setCredentialsExpires( Integer credentialsExpires ) @@ -141,6 +148,7 @@ configurationService.setConfiguration( config ); systemSettingManager.saveSystemSetting( KEY_ACCOUNT_RECOVERY, accountRecovery ); + systemSettingManager.saveSystemSetting( KEY_ACCOUNT_INVITE, accountInvite ); systemSettingManager.saveSystemSetting( KEY_SELF_REGISTRATION_NO_RECAPTCHA, selfRegistrationNoRecaptcha ); if ( credentialsExpires != null ) === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties 2013-12-30 13:02:41 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/resources/org/hisp/dhis/settings/i18n_module.properties 2014-01-23 15:04:07 +0000 @@ -56,6 +56,7 @@ self_registration_account_organisation_unit=Self registration account organisation unit access=Access enable_user_account_recovery=Enable user account recovery +enable_user_account_invite=Enable user account invite select_organisation_unit=Select organisation unit application_notification=Application notification multi_organisation_unit_forms=Enable multi-organisation unit forms === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm 2013-12-30 13:02:41 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/webapp/dhis-web-maintenance-settings/systemAccessSettings.vm 2014-01-23 15:04:07 +0000 @@ -6,6 +6,7 @@ selfRegistrationOrgUnit: jQuery( "#selfRegistrationOrgUnit" ).val(), selfRegistrationNoRecaptcha: jQuery( '#selfRegistrationNoRecaptcha' ).is( ':checked' ), accountRecovery: jQuery( '#accountRecovery' ).is( ':checked' ), + accountInvite: jQuery( '#accountInvite' ).is( ':checked' ), credentialsExpires: jQuery( '#credentialsExpires' ).val() }, function( json ) { if ( json.response == "success" ) { @@ -50,8 +51,13 @@
- - + + +
+ +
+ +
$i18n.getString( "user_credentials_expires" )
=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/pom.xml' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/pom.xml 2014-01-09 21:56:49 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/pom.xml 2014-01-17 03:48:57 +0000 @@ -18,6 +18,11 @@ + + javax.servlet + servlet-api + + @@ -51,7 +56,7 @@ - - ../../../ - + + ../../../ + === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java 2013-09-16 17:07:25 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java 2014-01-25 18:46:36 +0000 @@ -33,11 +33,17 @@ import java.util.HashSet; import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.struts2.ServletActionContext; +import org.hisp.dhis.api.utils.ContextUtils; import org.hisp.dhis.attribute.AttributeService; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.oust.manager.SelectionTreeManager; import org.hisp.dhis.ouwt.manager.OrganisationUnitSelectionManager; import org.hisp.dhis.security.PasswordManager; +import org.hisp.dhis.security.RestoreType; +import org.hisp.dhis.security.SecurityService; import org.hisp.dhis.system.util.AttributeUtils; import org.hisp.dhis.system.util.LocaleUtils; import org.hisp.dhis.user.CurrentUserService; @@ -54,8 +60,10 @@ * @author Torgeir Lorange Ostby */ public class AddUserAction - implements Action + implements Action { + private String ACCOUNT_ACTION_INVITE = "invite"; + // ------------------------------------------------------------------------- // Dependencies // ------------------------------------------------------------------------- @@ -81,6 +89,13 @@ this.userService = userService; } + private SecurityService securityService; + + public void setSecurityService( SecurityService securityService ) + { + this.securityService = securityService; + } + private PasswordManager passwordManager; public void setPasswordManager( PasswordManager passwordManager ) @@ -106,6 +121,13 @@ // Input & Output // ------------------------------------------------------------------------- + private String accountAction; + + public void setAccountAction( String accountAction ) + { + this.accountAction = accountAction; + } + private String username; public void setUsername( String username ) @@ -141,6 +163,13 @@ this.email = email; } + private String inviteEmail; + + public void setInviteEmail( String inviteEmail ) + { + this.inviteEmail = inviteEmail; + } + private String phoneNumber; public void setPhoneNumber( String phoneNumber ) @@ -161,7 +190,7 @@ } private String localeUi; - + public void setLocaleUi( String localeUi ) { this.localeUi = localeUi; @@ -187,16 +216,16 @@ { this.jsonAttributeValues = jsonAttributeValues; } - + // ------------------------------------------------------------------------- // Action implementation // ------------------------------------------------------------------------- public String execute() - throws Exception + throws Exception { UserCredentials currentUserCredentials = currentUserService.getCurrentUser() != null ? currentUserService - .getCurrentUser().getUserCredentials() : null; + .getCurrentUser().getUserCredentials() : null; // --------------------------------------------------------------------- // Prepare values @@ -208,6 +237,7 @@ } username = username.trim(); + inviteEmail = inviteEmail.trim(); // --------------------------------------------------------------------- // Create userCredentials and user @@ -215,18 +245,31 @@ Collection orgUnits = selectionTreeManager.getReloadedSelectedOrganisationUnits(); + UserCredentials userCredentials = new UserCredentials(); User user = new User(); - user.setSurname( surname ); - user.setFirstName( firstName ); - user.setEmail( email ); - user.setPhoneNumber( phoneNumber ); + + userCredentials.setUser( user ); + user.setUserCredentials( userCredentials ); + + if ( ACCOUNT_ACTION_INVITE.equals( accountAction ) ) + { + user.setEmail( inviteEmail ); + + securityService.prepareUserForInvite ( userCredentials ); + } + else + { + user.setSurname( surname ); + user.setFirstName( firstName ); + user.setEmail( email ); + user.setPhoneNumber( phoneNumber ); + + userCredentials.setUsername( username ); + userCredentials.setPassword( passwordManager.encodePassword( username, rawPassword ) ); + } + user.updateOrganisationUnits( new HashSet( orgUnits ) ); - UserCredentials userCredentials = new UserCredentials(); - userCredentials.setUser( user ); - userCredentials.setUsername( username ); - userCredentials.setPassword( passwordManager.encodePassword( username, rawPassword ) ); - for ( String id : selectedList ) { UserAuthorityGroup group = userService.getUserAuthorityGroup( Integer.parseInt( id ) ); @@ -237,12 +280,10 @@ } } - user.setUserCredentials( userCredentials ); - if ( jsonAttributeValues != null ) { AttributeUtils.updateAttributeValuesFromJson( user.getAttributeValues(), jsonAttributeValues, - attributeService ); + attributeService ); } userService.addUser( user ); @@ -252,10 +293,22 @@ { selectionManager.setSelectedOrganisationUnits( orgUnits ); } - + userService.addUserSetting( new UserSetting( user, UserSettingService.KEY_UI_LOCALE, LocaleUtils.getLocale( localeUi ) ) ); userService.addUserSetting( new UserSetting( user, UserSettingService.KEY_DB_LOCALE, LocaleUtils.getLocale( localeDb ) ) ); - + + if ( ACCOUNT_ACTION_INVITE.equals( accountAction ) ) + { + securityService.sendRestoreMessage( userCredentials, getRootPath(), RestoreType.INVITE ); + } + return SUCCESS; } + + private String getRootPath() + { + HttpServletRequest request = ServletActionContext.getRequest(); + + return ContextUtils.getContextPath( request ); + } } === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml 2013-10-13 16:19:17 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml 2014-01-17 03:48:57 +0000 @@ -8,6 +8,7 @@ + === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties 2014-01-22 09:14:39 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties 2014-01-23 15:03:16 +0000 @@ -252,6 +252,9 @@ #-- User module ---------------------------------------------------------------# user_management=User management +action=Action +create_account_with_user_details=Create account with user details +email_invitation_to_create_account=Email invitation to create account username=Username fullname=Fullname role=Role === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml 2014-01-22 09:14:39 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml 2014-01-23 15:04:07 +0000 @@ -55,6 +55,7 @@ user.action?currentPage=${keyCurrentPage}&key=${keyCurrentKey} showAddUserForm.action + javascript/user.js F_USER_ADD === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/addUserForm.vm' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/addUserForm.vm 2014-01-13 16:34:27 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/addUserForm.vm 2014-01-23 15:04:07 +0000 @@ -1,5 +1,6 @@
+ + + +
@@ -46,7 +56,7 @@