Skip to content

Commit

Permalink
#122 add change password
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone committed May 11, 2016
1 parent c7f1e8c commit 52f5e70
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 8 deletions.
33 changes: 32 additions & 1 deletion src/main/java/alfio/controller/api/admin/UsersApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import alfio.util.ImageUtil;
import alfio.util.Json;
import alfio.util.ValidationResult;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
Expand Down Expand Up @@ -137,6 +139,12 @@ public String editUser(@RequestBody UserModification userModification, Principal
return OK;
}

@RequestMapping(value = "/users/update-password", method = POST)
public ValidationResult updatePassword(@RequestBody PasswordModification passwordModification, Principal principal) {
return userManager.validateNewPassword(principal.getName(), passwordModification.oldPassword, passwordModification.newPassword, passwordModification.newPasswordConfirm)
.ifSuccess(() -> userManager.updatePassword(principal.getName(), passwordModification.newPassword));
}

@RequestMapping(value = "/users/new", method = POST)
public UserWithPassword insertUser(@RequestBody UserModification userModification, HttpSession session, Principal principal) {
Role requested = Role.valueOf(userModification.getRole());
Expand Down Expand Up @@ -179,6 +187,13 @@ public UserModification loadUser(@PathVariable("id") int userId) {
return new UserModification(user.getId(), userOrganizations.get(0).getId(), userManager.getUserRole(user).name(), user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmailAddress());
}

@RequestMapping(value = "/users/current", method = GET)
public UserModification loadCurrentUser(Principal principal) {
User user = userManager.findUserByUsername(principal.getName());
List<Organization> userOrganizations = userManager.findUserOrganizations(user);
return new UserModification(user.getId(), userOrganizations.get(0).getId(), userManager.getUserRole(user).name(), user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmailAddress());
}

@RequestMapping(value = "/users/{id}/reset-password", method = PUT)
public UserWithPassword resetPassword(@PathVariable("id") int userId, HttpSession session) {
UserWithPassword userWithPassword = userManager.resetPassword(userId);
Expand All @@ -190,7 +205,7 @@ private void storePasswordImage(HttpSession session, UserWithPassword userWithPa
session.setAttribute(USER_WITH_PASSWORD_KEY, userWithPassword);
}

static final class RoleDescriptor {
private static final class RoleDescriptor {
private final Role role;

RoleDescriptor(Role role) {
Expand All @@ -205,4 +220,20 @@ public String getDescription() {
return role.getDescription();
}
}

private static final class PasswordModification {

private final String oldPassword;
private final String newPassword;
private final String newPasswordConfirm;

@JsonCreator
private PasswordModification(@JsonProperty("oldPassword") String oldPassword,
@JsonProperty("newPassword") String newPassword,
@JsonProperty("newPasswordConfirm") String newPasswordConfirm) {
this.oldPassword = oldPassword;
this.newPassword = newPassword;
this.newPasswordConfirm = newPasswordConfirm;
}
}
}
44 changes: 39 additions & 5 deletions src/main/java/alfio/manager/user/UserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,18 @@ public ValidationResult validateOrganization(Integer id, String name, String ema

@Transactional
public void editUser(int id, int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, String currentUsername) {
int userOrganizationResult = userOrganizationRepository.updateUserOrganization(id, organizationId);
Assert.isTrue(userOrganizationResult == 1, "unexpected error during organization update");
boolean admin = ADMIN_USERNAME.equals(username) && Role.ADMIN == role;
if(!admin) {
int userOrganizationResult = userOrganizationRepository.updateUserOrganization(id, organizationId);
Assert.isTrue(userOrganizationResult == 1, "unexpected error during organization update");
}
int userResult = userRepository.update(id, username, firstName, lastName, emailAddress);
Assert.isTrue(userResult == 1, "unexpected error during user update");
Assert.isTrue(getAvailableRoles(currentUsername).contains(role), "cannot assign role "+role);
authorityRepository.revokeAll(username);
authorityRepository.create(username, role.getRoleName());
if(!admin) {
Assert.isTrue(getAvailableRoles(currentUsername).contains(role), "cannot assign role "+role);
authorityRepository.revokeAll(username);
authorityRepository.create(username, role.getRoleName());
}
}

@Transactional
Expand All @@ -196,6 +201,14 @@ public UserWithPassword resetPassword(int userId) {
return new UserWithPassword(user, password, UUID.randomUUID().toString());
}

@Transactional
public boolean updatePassword(String username, String newPassword) {
User user = userRepository.findByUsername(username).stream().findFirst().orElseThrow(IllegalStateException::new);
Validate.isTrue(PasswordGenerator.isValid(newPassword), "invalid password");
//Validate.isTrue(userRepository.resetPassword(user.getId(), passwordEncoder.encode(newPassword)) == 1, "error during password update");
return true;
}

@Transactional
public void deleteUser(int userId, String currentUsername) {
User currentUser = userRepository.findEnabledByUsername(currentUsername).orElseThrow(IllegalArgumentException::new);
Expand All @@ -219,5 +232,26 @@ public ValidationResult validateUser(Integer id, String username, int organizati
.collect(toList()));
}

public ValidationResult validateNewPassword(String username, String oldPassword, String newPassword, String newPasswordConfirm) {
return userRepository.findByUsername(username)
.stream()
.findFirst()
.map(u -> {
List<ValidationResult.ValidationError> errors = new ArrayList<>();
Optional<String> password = userRepository.findPasswordByUsername(username);
if(!password.filter(p -> passwordEncoder.matches(oldPassword, p)).isPresent()) {
errors.add(new ValidationResult.ValidationError("old-password-invalid", "wrong password"));
}
if(!PasswordGenerator.isValid(newPassword)) {
errors.add(new ValidationResult.ValidationError("new-password-invalid", "new password is not strong enough"));
}
if(!StringUtils.equals(newPassword, newPasswordConfirm)) {
errors.add(new ValidationResult.ValidationError("new-password-does-not-match", "new password has not been confirmed"));
}
return ValidationResult.of(errors);
})
.orElseGet(ValidationResult::failed);
}


}
3 changes: 3 additions & 0 deletions src/main/java/alfio/repository/user/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public interface UserRepository {
@Query("select * from ba_user where username = :username and enabled = true")
Optional<User> findEnabledByUsername(@Bind("username") String username);

@Query("select password from ba_user where username = :username and enabled = true")
Optional<String> findPasswordByUsername(@Bind("username") String username);

@Query("INSERT INTO ba_user(username, password, first_name, last_name, email_address, enabled) VALUES"
+ " (:username, :password, :first_name, :last_name, :email_address, :enabled)")
@AutoGeneratedKey("id")
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/alfio/util/PasswordGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.util.*;
import java.util.function.IntConsumer;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

public final class PasswordGenerator {
Expand All @@ -31,6 +32,7 @@ public final class PasswordGenerator {
private static final boolean DEV_MODE;
private static final int MAX_LENGTH = 14;
private static final int MIN_LENGTH = 10;
private static final Pattern VALIDATION_PATTERN;

static {
List<Character> chars = new LinkedList<>();
Expand All @@ -56,6 +58,7 @@ public final class PasswordGenerator {
DEV_MODE = Arrays.stream(Optional.ofNullable(System.getProperty("spring.profiles.active")).map(p -> p.split(",")).orElse(new String[0]))
.map(StringUtils::trim)
.anyMatch(Initializer.PROFILE_DEV::equals);
VALIDATION_PATTERN = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*\\p{Punct})(?=\\S+$).{"+MIN_LENGTH+",}$");//source: http://stackoverflow.com/a/3802238
}

private PasswordGenerator() {
Expand All @@ -69,4 +72,8 @@ public static String generateRandomPassword() {
int length = MIN_LENGTH + r.nextInt(MAX_LENGTH - MIN_LENGTH + 1);
return RandomStringUtils.random(length, PASSWORD_CHARACTERS);
}

public static boolean isValid(String password) {
return StringUtils.isNotBlank(password) && VALIDATION_PATTERN.matcher(password).matches();
}
}
5 changes: 3 additions & 2 deletions src/main/webapp/WEB-INF/templates/admin/index.ms
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@
<li data-ui-sref-active="active"><a data-ui-sref="index">Dashboard</a></li>
<li data-ui-sref-active="active"><a data-ui-sref="configuration.system">Configuration</a></li>
<li class="nav-divider"></li>
<li class="visible-xs"><span><i class="fa fa-user"></i> {{username}}</span></li>
<li class="visible-xs"><a href="#" data-ui-sref="edit-current-user"><i class="fa fa-user"></i> {{username}}</a></li>
<li class="visible-xs"><a href="">Change Password</a></li>
<li class="visible-xs"><a href="" data-ng-click="doLogout()">Logout</a></li>
</ul>
</div>
</div>
<div class="navbar-right hidden-xs">
<span class="navbar-text"><i class="fa fa-user"></i> {{username}}</span>
<span class="navbar-text"><a href="#" data-ui-sref="edit-current-user"><i class="fa fa-user"></i> {{username}}</a></span>
<span class="navbar-text"><a href="" data-ng-click="doLogout()">Logout</a></span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<div class="container wMarginBottom30px">
<div class="page-header">
<h2>Edit your account: {{editCurrentUserCtrl.user.username}}</h2>
<span>Review and edit your account details</span>
</div>
<form role="form" name="editCurrentUserCtrl.editUser" data-error-sensitive data-ng-submit="editCurrentUserCtrl.saveUserInfo()">
<div class="page-header">
<h4>Personal information</h4>
</div>
<div class="form-group">
<label for="firstName">First Name</label>
<input class="form-control" name="name" id="firstName" data-ng-model="editCurrentUserCtrl.user.firstName" required data-ng-minlength="2">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input class="form-control" name="lastName" id="lastName" data-ng-model="editCurrentUserCtrl.user.lastName" required data-ng-minlength="2">
<field-error data-form-obj="editUser" data-field-obj="editCurrentUserCtrl.user.lastName"></field-error>
</div>
<div class="form-group">
<label for="emailAddress">Email Address</label>
<input type="email" class="form-control" name="emailAddress" id="emailAddress" data-ng-model="editCurrentUserCtrl.user.emailAddress" required>
</div>
<div class="form-group pull-right" data-ng-if="!editCurrentUserCtrl.loading">
<button class="btn btn-success">Update</button>
<button class="btn btn-default" data-ng-click="editCurrentUserCtrl.doReset()">Cancel</button>
</div>
<div class="text-center" data-ng-if="editCurrentUserCtrl.loading">
<i class="fa fa-cog fa-2x fa-spin"></i> loading...
</div>
</form>
<div class="clearfix"></div>
<div class="page-header">
<h4>Change Password</h4>
</div>
<form role="form" name="editCurrentUserCtrl.changePassword" data-error-sensitive data-ng-submit="editCurrentUserCtrl.updatePassword()">
<div class="form-group">
<label for="oldPassword">Current Password</label>
<input class="form-control" name="oldPassword" id="oldPassword" data-ng-model="editCurrentUserCtrl.passwordContainer.oldPassword" required>
</div>
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" class="form-control" name="newPassword" id="newPassword" data-ng-model="editCurrentUserCtrl.passwordContainer.newPassword" required>
</div>
<div class="form-group">
<label for="newPasswordConfirm">Confirm new Password</label>
<input type="password" class="form-control" name="newPasswordConfirm" id="newPasswordConfirm" data-ng-model="editCurrentUserCtrl.passwordContainer.newPasswordConfirm" required>
</div>
<div class="text-center" data-ng-if="editCurrentUserCtrl.loading">
<i class="fa fa-cog fa-2x fa-spin"></i> loading...
</div>
<div class="form-group pull-right" data-ng-if="!editCurrentUserCtrl.loading">
<button class="btn btn-success">Update</button>
<button type="reset" class="btn btn-default">Cancel</button>
</div>

<div data-ng-messages="editCurrentUserCtrl.changePassword.$error" class="text-danger" data-ng-messages-multiple>
<div data-ng-message="new-password-invalid">
<span>The new password doesn't match the required format. It must be <strong>at least</strong> 8 characters and it must: </span>
<ul>
<li>contain <strong>at least</strong> an uppercase letter <code>A-Z</code></li>
<li>contain <strong>at least</strong> a lowercase letter <code>a-z</code></li>
<li>contain <strong>at least</strong> a punctuation character <code>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~}</code></li>
<li><strong>not</strong> contain spaces</li>
</ul>
</div>
<div data-ng-message="new-password-does-not-match">"New password" and "Confirm new password" don't match</div>
<div data-ng-message="old-password-invalid">Current password is not valid</div>
<div data-ng-message="pattern">The value should match the following pattern: {{requiredPattern}}</div>
</div>

</form>

</div>
70 changes: 70 additions & 0 deletions src/main/webapp/resources/js/admin/feature/user/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,23 @@
}
}
})
.state('edit-current-user', {
url: "/profile/edit",
templateUrl: "/resources/angular-templates/admin/partials/main/edit-current-user.html",
controller: 'EditCurrentUserController',
controllerAs: 'editCurrentUserCtrl',
resolve: {
user: ['UserService', function (UserService) {
return UserService.loadCurrentUser().then(function(resp) {
return resp.data;
});
}]
}
})
}])
.controller('EditOrganizationController', EditOrganizationController)
.controller('EditUserController', EditUserController)
.controller('EditCurrentUserController', EditCurrentUserController)
.controller('OrganizationListController', OrganizationListController)
.controller('UsersListController', UsersListController)
.service('OrganizationService', OrganizationService)
Expand Down Expand Up @@ -133,6 +147,56 @@

EditUserController.$inject = ['$state', '$stateParams', '$rootScope', '$q', 'OrganizationService', 'UserService', 'ValidationService', '$q'];

function EditCurrentUserController($state, user, UserService, ValidationService) {
var self = this;
self.user = user;
self.original = user;

self.saveUserInfo = function() {
if(self.editUser.$valid) {
self.loading = true;
var promise = UserService.checkUser(self.user).then(function() {
return UserService.editUser(self.user).then(function() {
self.original = angular.copy(self.user);
self.loading = false;
});
});
promise.then(function() {}, function(err) {
self.loading = false;
self.error = true;
});
}
};

self.doReset = function() {
self.user = angular.copy(self.original);
self.passwordContainer = {};
self.changePasswordErrors = {};
};

self.updatePassword = function() {
if(self.changePassword.$valid) {
self.loading = true;
UserService.updatePassword(self.passwordContainer).then(function(result) {
var validationResult = result.data;
if(validationResult.success) {
self.passwordContainer = {};
self.changePasswordErrors = {};
alert('succeeded');
} else {
angular.forEach(validationResult.validationErrors, function(e) {
self.changePassword.$setValidity(e.fieldName, false);
});
}
self.loading = false;
});
}
};

}

EditCurrentUserController.$inject = ['$state', 'user', 'UserService', 'ValidationService'];

function OrganizationService($http, HttpErrorHandler) {
return {
getAllOrganizations : function() {
Expand Down Expand Up @@ -173,6 +237,12 @@
loadUser: function(userId) {
return $http.get('/admin/api/users/'+userId+'.json').error(HttpErrorHandler.handle);
},
loadCurrentUser: function() {
return $http.get('/admin/api/users/current.json').error(HttpErrorHandler.handle);
},
updatePassword: function(passwordContainer) {
return $http.post('/admin/api/users/update-password.json', passwordContainer).error(HttpErrorHandler.handle);
},
deleteUser: function(user) {
return $http['delete']('/admin/api/users/'+user.id).error(HttpErrorHandler.handle);
},
Expand Down

0 comments on commit 52f5e70

Please sign in to comment.