mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
Add tests for the Change Password Endpoints
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
public class ChangePasswordNotAllowedException extends RuntimeException {
|
||||
|
||||
public ChangePasswordNotAllowedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class InvalidPasswordExceptionMapper implements ExceptionMapper<InvalidPasswordException> {
|
||||
@Override
|
||||
public Response toResponse(InvalidPasswordException exception) {
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public class MapperModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
||||
bind(MeToUserDtoMapper.class).to(Mappers.getMapper(MeToUserDtoMapper.class).getClass());
|
||||
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
||||
bind(UserCollectionToDtoMapper.class);
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ 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.NotFoundException;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
@@ -19,6 +22,7 @@ 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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -26,15 +30,20 @@ import javax.ws.rs.core.UriInfo;
|
||||
*/
|
||||
@Path(MeResource.ME_PATH_V2)
|
||||
public class MeResource {
|
||||
static final String ME_PATH_V2 = "v2/me/";
|
||||
public static final String ME_PATH_V2 = "v2/me/";
|
||||
|
||||
private final UserToUserDtoMapper userToDtoMapper;
|
||||
private final MeToUserDtoMapper meToUserDtoMapper;
|
||||
|
||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
||||
private final PasswordService passwordService;
|
||||
private final UserManager userManager;
|
||||
|
||||
@Inject
|
||||
public MeResource(UserToUserDtoMapper userToDtoMapper, UserManager manager) {
|
||||
this.userToDtoMapper = userToDtoMapper;
|
||||
public MeResource(MeToUserDtoMapper meToUserDtoMapper, UserManager manager, PasswordService passwordService) {
|
||||
this.meToUserDtoMapper = meToUserDtoMapper;
|
||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
||||
this.passwordService = passwordService;
|
||||
this.userManager = manager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +61,7 @@ public class MeResource {
|
||||
public Response get(@Context Request request, @Context UriInfo uriInfo) throws NotFoundException {
|
||||
|
||||
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||
return adapter.get(id, userToDtoMapper::map);
|
||||
return adapter.get(id, meToUserDtoMapper::map);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,8 +76,19 @@ public class MeResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||
public Response changePassword(PasswordChangeDto passwordChange) throws NotFoundException {
|
||||
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||
return adapter.get(id, userToDtoMapper::map);
|
||||
public Response changePassword(PasswordChangeDto passwordChangeDto) throws NotFoundException, ConcurrentModificationException {
|
||||
String name = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), userManager.getUserTypeChecker().andThen(getOldOriginalPasswordChecker(passwordChangeDto.getOldPassword())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Match given old password from the dto with the stored password before updating
|
||||
*/
|
||||
private Consumer<User> getOldOriginalPasswordChecker(String oldPassword) {
|
||||
return user -> {
|
||||
if (!user.getPassword().equals(passwordService.encryptPassword(oldPassword))) {
|
||||
throw new InvalidPasswordException("The password is invalid");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.user.UserPermissions;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class MeToUserDtoMapper extends UserToUserDtoMapper{
|
||||
|
||||
@Inject
|
||||
private UserManager userManager;
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(User user, @MappingTarget UserDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
||||
}
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||
}
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,6 +92,37 @@ class ResourceLinks {
|
||||
}
|
||||
}
|
||||
|
||||
MeLinks me() {
|
||||
return new MeLinks(uriInfoStore.get(), this.user());
|
||||
}
|
||||
|
||||
static class MeLinks {
|
||||
private final LinkBuilder meLinkBuilder;
|
||||
private UserLinks userLinks;
|
||||
|
||||
MeLinks(UriInfo uriInfo, UserLinks user) {
|
||||
meLinkBuilder = new LinkBuilder(uriInfo, MeResource.class);
|
||||
userLinks = user;
|
||||
}
|
||||
|
||||
String self() {
|
||||
return meLinkBuilder.method("get").parameters().href();
|
||||
}
|
||||
|
||||
String delete(String name) {
|
||||
return userLinks.delete(name);
|
||||
}
|
||||
|
||||
String update(String name) {
|
||||
return userLinks.update(name);
|
||||
}
|
||||
|
||||
public String passwordChange() {
|
||||
return meLinkBuilder.method("changePassword").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UserCollectionLinks userCollection() {
|
||||
return new UserCollectionLinks(uriInfoStore.get());
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class UserResource {
|
||||
|
||||
@@ -130,18 +128,7 @@ public class UserResource {
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response changePassword(@PathParam("id") String name, @Valid PasswordChangeDto passwordChangeDto) throws NotFoundException, ConcurrentModificationException {
|
||||
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), getUserTypeChecker());
|
||||
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), userManager.getUserTypeChecker());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only account of the default type "xml" can change their password
|
||||
*/
|
||||
private Consumer<User> getUserTypeChecker() {
|
||||
return user -> {
|
||||
if (!userManager.getDefaultType().equals(user.getType())) {
|
||||
throw new ChangePasswordNotAllowedException(MessageFormat.format("It is not possible to change password for User of type {0}", user.getType()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
||||
}
|
||||
if (userManager.getDefaultType().equals(user.getType())) {
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||
}
|
||||
target.add(linksBuilder.build());
|
||||
|
||||
Reference in New Issue
Block a user