mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
Move password logic to manager
This commit is contained in:
@@ -35,7 +35,6 @@ package sonia.scm.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.sdorra.ssp.PermissionCheck;
|
||||
import org.apache.commons.beanutils.BeanComparator;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.slf4j.Logger;
|
||||
@@ -64,8 +63,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -199,24 +196,27 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
||||
return response;
|
||||
}
|
||||
|
||||
public Response update(T item, Function<T, PermissionCheck> permissionChecker ){
|
||||
Consumer<Manager> updateAction = mng -> mng.modify(item, permissionChecker);
|
||||
return applyUpdate(item, updateAction);
|
||||
}
|
||||
|
||||
public Response update(String name, T item) {
|
||||
Consumer<Manager> updateAction = mng -> mng.modify(item);
|
||||
return applyUpdate(item, updateAction);
|
||||
}
|
||||
|
||||
public Response applyUpdate(T item, Consumer<Manager> updateAction) {
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* @param item
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Response update(String name, T item)
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
preUpdate(item);
|
||||
|
||||
try
|
||||
{
|
||||
updateAction.accept(manager);
|
||||
manager.modify(item);
|
||||
response = Response.noContent().build();
|
||||
}
|
||||
catch (AuthorizationException ex)
|
||||
|
||||
@@ -47,54 +47,6 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
return singleAdapter.get(loadBy(id), mapToDto);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the authenticated user is the same user that want to change password than return the changeOwnPassword verification function
|
||||
* if the authenticated user is different he should have the modify permission to be able to modify passwords of other users
|
||||
*
|
||||
* @param usernameToChangePassword the user name of the user we want to change password
|
||||
* @return function to verify permission
|
||||
*/
|
||||
private Function<MODEL_OBJECT, PermissionCheck> getChangePasswordPermission(String usernameToChangePassword) {
|
||||
AssertUtil.assertIsNotEmpty(usernameToChangePassword);
|
||||
return model -> {
|
||||
User user = (User) model;
|
||||
if (usernameToChangePassword.equals(AuthenticationUtil.getAuthenticatedUsername())) {
|
||||
return UserPermissions.changeOwnPassword();
|
||||
}
|
||||
return UserPermissions.modify(user);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a user can modify the password
|
||||
*
|
||||
* 1 - the permission changeOwnPassword should be checked
|
||||
* 2 - Only account of the default type "xml" can change their password
|
||||
*
|
||||
*/
|
||||
private Consumer<MODEL_OBJECT> getChangePasswordChecker() {
|
||||
return model -> {
|
||||
User user = (User) model;
|
||||
UserPermissions.changeOwnPassword().check();
|
||||
UserManager userManager = (UserManager) manager;
|
||||
if (!userManager.isTypeDefault(user)) {
|
||||
throw new ChangePasswordNotAllowedException(MessageFormat.format(WRONG_USER_TYPE, user.getType()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Response changePassword(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges ) throws ConcurrentModificationException {
|
||||
return singleAdapter.changePassword(
|
||||
loadBy(id),
|
||||
applyChanges,
|
||||
idStaysTheSame(id),
|
||||
getChangePasswordChecker(),
|
||||
getChangePasswordPermission(id));
|
||||
}
|
||||
|
||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws ConcurrentModificationException {
|
||||
return singleAdapter.update(
|
||||
loadBy(id),
|
||||
|
||||
@@ -5,14 +5,12 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
@@ -22,9 +20,6 @@ import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Request;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static sonia.scm.user.InvalidPasswordException.INVALID_MATCHING;
|
||||
|
||||
|
||||
/**
|
||||
@@ -78,12 +73,8 @@ public class MeResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||
public Response changePassword(PasswordChangeDto passwordChangeDto) throws ConcurrentModificationException {
|
||||
String name = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||
if (passwordChangeDto.getOldPassword() == null){
|
||||
throw new ChangePasswordNotAllowedException(ChangePasswordNotAllowedException.OLD_PASSWORD_REQUIRED);
|
||||
}
|
||||
return adapter.changePassword(name, user -> user.clone().changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword()), passwordService.encryptPassword(passwordChangeDto.getOldPassword())));
|
||||
public Response changePassword(@Valid PasswordChangeDto passwordChangeDto) {
|
||||
userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChangeDto.getOldPassword()), passwordService.encryptPassword(passwordChangeDto.getNewPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.hibernate.validator.constraints.NotEmpty;
|
||||
@ToString
|
||||
public class PasswordChangeDto {
|
||||
|
||||
@NotEmpty
|
||||
private String oldPassword;
|
||||
|
||||
@NotEmpty
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class PasswordOverwriteDto {
|
||||
@NotEmpty
|
||||
private String newPassword;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class ResourceLinks {
|
||||
}
|
||||
|
||||
public String passwordChange(String name) {
|
||||
return userLinkBuilder.method("getUserResource").parameters(name).method("changePassword").parameters().href();
|
||||
return userLinkBuilder.method("getUserResource").parameters(name).method("overwritePassword").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.github.sdorra.ssp.PermissionCheck;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.Manager;
|
||||
@@ -17,6 +16,8 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||
|
||||
/**
|
||||
* Adapter from resource http endpoints to managers, for Single resources (e.g. {@code /user/name}).
|
||||
*
|
||||
@@ -53,32 +54,26 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
.map(Response.ResponseBuilder::build)
|
||||
.orElseThrow(NotFoundException::new);
|
||||
}
|
||||
public Response changePassword(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey, Consumer<MODEL_OBJECT> checker, Function<MODEL_OBJECT, PermissionCheck> permissionCheck) throws ConcurrentModificationException {
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
|
||||
checkForUpdate(hasSameKey, existingModelObject, changedModelObject);
|
||||
checker.accept(existingModelObject);
|
||||
return update(changedModelObject, permissionCheck);
|
||||
return update(reader,applyChanges,hasSameKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model object for the given id according to the given function and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no matching object for the id or missing privileges.
|
||||
*/
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) throws ConcurrentModificationException {
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
|
||||
checkForUpdate(hasSameKey, existingModelObject, changedModelObject);
|
||||
return update(getId(existingModelObject), changedModelObject);
|
||||
}
|
||||
|
||||
public void checkForUpdate(Predicate<MODEL_OBJECT> hasSameKey, MODEL_OBJECT existingModelObject, MODEL_OBJECT changedModelObject) throws ConcurrentModificationException {
|
||||
if (!hasSameKey.test(changedModelObject)) {
|
||||
throw new IllegalArgumentException("illegal change of id");
|
||||
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
|
||||
}
|
||||
else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
return update(getId(existingModelObject), changedModelObject);
|
||||
}
|
||||
|
||||
private boolean modelObjectWasModifiedConcurrently(MODEL_OBJECT existing, MODEL_OBJECT updated) {
|
||||
|
||||
@@ -3,10 +3,8 @@ package sonia.scm.api.v2.resources;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
@@ -116,11 +114,11 @@ public class UserResource {
|
||||
* <strong>Note:</strong> This method requires "user:changeOwnPassword" privilege to modify the own password.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
* @param passwordChangeDto change password object to modify password. the old password is here not required
|
||||
* @param passwordOverwriteDto change password object to modify password. the old password is here not required
|
||||
*/
|
||||
@PUT
|
||||
@Path("password")
|
||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||
@Consumes(VndMediaType.PASSWORD_OVERWRITE)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 400, condition = "Invalid body, e.g. the user type is not xml or the given oldPassword do not match the stored one"),
|
||||
@@ -130,12 +128,8 @@ public class UserResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response changePassword(@PathParam("id") String name, @Valid PasswordChangeDto passwordChangeDto) throws ConcurrentModificationException {
|
||||
String currentUserName = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||
if (currentUserName.equals(name) && passwordChangeDto.getOldPassword() == null){
|
||||
throw new ChangePasswordNotAllowedException(ChangePasswordNotAllowedException.OLD_PASSWORD_REQUIRED);
|
||||
}
|
||||
return adapter.changePassword(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())));
|
||||
public Response overwritePassword(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwriteDto) {
|
||||
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwriteDto.getNewPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
||||
}
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||
}
|
||||
}
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
builder.add(canReadOwnUser(user));
|
||||
builder.add(getUserAutocompletePermission());
|
||||
builder.add(getGroupAutocompletePermission());
|
||||
builder.add(getChangeOwnPasswordPermission());
|
||||
builder.add(getChangeOwnPasswordPermission(user));
|
||||
permissions = builder.build();
|
||||
}
|
||||
|
||||
@@ -273,8 +273,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
return GroupPermissions.autocomplete().asShiroString();
|
||||
}
|
||||
|
||||
private String getChangeOwnPasswordPermission() {
|
||||
return UserPermissions.changeOwnPassword().asShiroString();
|
||||
private String getChangeOwnPasswordPermission(User user) {
|
||||
return UserPermissions.changePassword(user).asShiroString();
|
||||
}
|
||||
|
||||
private String getUserAutocompletePermission() {
|
||||
|
||||
@@ -33,12 +33,10 @@
|
||||
|
||||
package sonia.scm.user;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.sdorra.ssp.PermissionActionCheck;
|
||||
import com.github.sdorra.ssp.PermissionCheck;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
@@ -64,9 +62,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -196,15 +191,10 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
*/
|
||||
@Override
|
||||
public void modify(User user) {
|
||||
modify(user,UserPermissions::modify);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(User user, Function<User, PermissionCheck> permissionChecker) {
|
||||
logger.info("modify user {} of type {}", user.getName(), user.getType());
|
||||
managerDaoAdapter.modify(
|
||||
user,
|
||||
permissionChecker,
|
||||
UserPermissions::modify,
|
||||
notModified -> fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified),
|
||||
notModified -> fireEvent(HandlerEventType.MODIFY, user, notModified));
|
||||
}
|
||||
@@ -408,6 +398,36 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void changePasswordForLoggedInUser(String oldPassword, String newPassword) {
|
||||
User user = get((String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal());
|
||||
|
||||
if (!user.getPassword().equals(oldPassword)) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
|
||||
user.setPassword(newPassword);
|
||||
|
||||
managerDaoAdapter.modify(
|
||||
user,
|
||||
UserPermissions::changePassword,
|
||||
notModified -> fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified),
|
||||
notModified -> fireEvent(HandlerEventType.MODIFY, user, notModified));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overwritePassword(String userId, String newPassword) {
|
||||
User user = get(userId);
|
||||
if (user == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!isTypeDefault(user)) {
|
||||
throw new ChangePasswordNotAllowedException(user.getType());
|
||||
}
|
||||
user.setPassword(newPassword);
|
||||
this.modify(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user