SEBSERV-8 #more tests and fixes
This commit is contained in:
parent
b7a6bd831b
commit
ad9865bfa3
12 changed files with 469 additions and 39 deletions
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gbl.model.institution;
|
||||
|
||||
public class Institution {
|
||||
|
||||
}
|
|
@ -65,7 +65,7 @@ public class UserActivityLog implements Entity {
|
|||
return (this.id != null) ? String.valueOf(this.id) : null;
|
||||
}
|
||||
|
||||
public String getUserUUID() {
|
||||
public String getUserUuid() {
|
||||
return this.userUUID;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ public final class UserInfo implements GrantEntity, Serializable {
|
|||
@JsonIgnore
|
||||
@Override
|
||||
public String getOwnerUUID() {
|
||||
return null;
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
|
|
@ -8,26 +8,66 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.model.user;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public final class UserMod {
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
|
||||
|
||||
public final class UserMod implements GrantEntity {
|
||||
|
||||
public static final String ATTR_NAME_USER_INFO = "userInfo";
|
||||
public static final String ATTR_NAME_NEW_PASSWORD = "newPassword";
|
||||
public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword";
|
||||
|
||||
@JsonProperty(ATTR_NAME_USER_INFO)
|
||||
private final UserInfo userInfo;
|
||||
|
||||
@Size(min = 8, max = 255, message = "userInfo:password:size:{min}:{max}:${validatedValue}")
|
||||
@JsonProperty(ATTR_NAME_NEW_PASSWORD)
|
||||
private final String newPassword;
|
||||
|
||||
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD)
|
||||
private final String retypedNewPassword;
|
||||
|
||||
@JsonCreator
|
||||
public UserMod(
|
||||
@JsonProperty("userInfo") final UserInfo userInfo,
|
||||
@JsonProperty("newPassword") final String newPassword,
|
||||
@JsonProperty("retypedNewPassword") final String retypedNewPassword) {
|
||||
@JsonProperty(ATTR_NAME_USER_INFO) final UserInfo userInfo,
|
||||
@JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword,
|
||||
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword) {
|
||||
|
||||
this.userInfo = userInfo;
|
||||
this.newPassword = newPassword;
|
||||
this.retypedNewPassword = retypedNewPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getId() {
|
||||
return this.userInfo.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public EntityType entityType() {
|
||||
return this.userInfo.entityType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Long getInstitutionId() {
|
||||
return this.userInfo.getInstitutionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getOwnerUUID() {
|
||||
return this.userInfo.getOwnerUUID();
|
||||
}
|
||||
|
||||
public UserInfo getUserInfo() {
|
||||
return this.userInfo;
|
||||
}
|
||||
|
@ -81,5 +121,4 @@ public final class UserMod {
|
|||
public String toString() {
|
||||
return "UserMod [userInfo=" + this.userInfo + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
59
src/main/java/ch/ethz/seb/sebserver/gbl/util/Tuple.java
Normal file
59
src/main/java/ch/ethz/seb/sebserver/gbl/util/Tuple.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gbl.util;
|
||||
|
||||
public class Tuple<T> {
|
||||
|
||||
public final T _1;
|
||||
public final T _2;
|
||||
|
||||
public Tuple(final T _1, final T _2) {
|
||||
super();
|
||||
this._1 = _1;
|
||||
this._2 = _2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this._1 == null) ? 0 : this._1.hashCode());
|
||||
result = prime * result + ((this._2 == null) ? 0 : this._2.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Tuple other = (Tuple) obj;
|
||||
if (this._1 == null) {
|
||||
if (other._1 != null)
|
||||
return false;
|
||||
} else if (!this._1.equals(other._1))
|
||||
return false;
|
||||
if (this._2 == null) {
|
||||
if (other._2 != null)
|
||||
return false;
|
||||
} else if (!this._2.equals(other._2))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "( " + this._1 + ", " + this._2 + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -85,7 +85,9 @@ public class AuthorizationGrantServiceImpl implements AuthorizationGrantService
|
|||
.andForRole(UserRole.INSTITUTIONAL_ADMIN)
|
||||
.withInstitutionalPrivilege(PrivilegeType.WRITE)
|
||||
.andForRole(UserRole.EXAM_ADMIN)
|
||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||
.create();
|
||||
|
||||
// grants for user activity logs
|
||||
|
|
|
@ -28,6 +28,19 @@ public interface UserActivityLogDAO extends UserRelatedEntityDAO<UserActivityLog
|
|||
DELETE
|
||||
}
|
||||
|
||||
/** Creates a user activity log entry for the current user.
|
||||
*
|
||||
* @param actionType the action type
|
||||
* @param entity the Entity
|
||||
* @param message an optional message */
|
||||
<E extends Entity> Result<E> logUserActivity(ActivityType actionType, E entity, String message);
|
||||
|
||||
/** Creates a user activity log entry for the current user.
|
||||
*
|
||||
* @param actionType the action type
|
||||
* @param entity the Entity */
|
||||
<E extends Entity> Result<E> logUserActivity(ActivityType actionType, E entity);
|
||||
|
||||
/** Creates a user activity log entry.
|
||||
*
|
||||
* @param user for specified SEBServerUser instance
|
||||
|
|
|
@ -62,6 +62,20 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
return EntityType.USER_ACTIVITY_LOG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Entity> Result<E> logUserActivity(
|
||||
final ActivityType actionType,
|
||||
final E entity,
|
||||
final String message) {
|
||||
|
||||
return logUserActivity(this.userService.getCurrentUser(), actionType, entity, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Entity> Result<E> logUserActivity(final ActivityType actionType, final E entity) {
|
||||
return logUserActivity(this.userService.getCurrentUser(), actionType, entity, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logUserActivity(
|
||||
|
@ -72,7 +86,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
|
||||
try {
|
||||
|
||||
this.userLogRecordMapper.insert(new UserActivityLogRecord(
|
||||
this.userLogRecordMapper.insertSelective(new UserActivityLogRecord(
|
||||
null,
|
||||
user.getUserInfo().uuid,
|
||||
System.currentTimeMillis(),
|
||||
|
|
|
@ -25,12 +25,15 @@ import org.apache.commons.lang3.BooleanUtils;
|
|||
import org.joda.time.DateTimeZone;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserFilter;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
|
@ -57,13 +60,16 @@ public class UserDaoImpl implements UserDAO {
|
|||
|
||||
private final UserRecordMapper userRecordMapper;
|
||||
private final RoleRecordMapper roleRecordMapper;
|
||||
private final PasswordEncoder userPasswordEncoder;
|
||||
|
||||
public UserDaoImpl(
|
||||
final UserRecordMapper userRecordMapper,
|
||||
final RoleRecordMapper roleRecordMapper) {
|
||||
final RoleRecordMapper roleRecordMapper,
|
||||
@Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) {
|
||||
|
||||
this.userRecordMapper = userRecordMapper;
|
||||
this.roleRecordMapper = roleRecordMapper;
|
||||
this.userPasswordEncoder = userPasswordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -215,7 +221,7 @@ public class UserDaoImpl implements UserDAO {
|
|||
null,
|
||||
userInfo.name,
|
||||
userInfo.userName,
|
||||
(changePWD) ? userMod.getNewPassword() : null,
|
||||
(changePWD) ? this.userPasswordEncoder.encode(userMod.getNewPassword()) : null,
|
||||
userInfo.email,
|
||||
userInfo.locale.toLanguageTag(),
|
||||
userInfo.timeZone.getID(),
|
||||
|
@ -230,9 +236,6 @@ public class UserDaoImpl implements UserDAO {
|
|||
|
||||
private Result<UserInfo> createNewUser(final SEBServerUser principal, final UserMod userMod) {
|
||||
final UserInfo userInfo = userMod.getUserInfo();
|
||||
if (userInfo.institutionId == null) {
|
||||
return Result.ofError(new IllegalArgumentException("The users institution cannot be null"));
|
||||
}
|
||||
|
||||
if (!userMod.newPasswordMatch()) {
|
||||
return Result.ofError(new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH));
|
||||
|
@ -244,7 +247,7 @@ public class UserDaoImpl implements UserDAO {
|
|||
UUID.randomUUID().toString(),
|
||||
userInfo.name,
|
||||
userInfo.userName,
|
||||
userMod.getNewPassword(),
|
||||
this.userPasswordEncoder.encode(userMod.getNewPassword()),
|
||||
userInfo.email,
|
||||
userInfo.locale.toLanguageTag(),
|
||||
userInfo.timeZone.getID(),
|
||||
|
@ -345,5 +348,4 @@ public class UserDaoImpl implements UserDAO {
|
|||
userInfo,
|
||||
record.getPassword()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collection;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
@ -25,6 +26,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserFilter;
|
|||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser;
|
||||
|
@ -32,6 +34,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO.ActivityType;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint;
|
||||
|
||||
@WebServiceProfile
|
||||
@RestController
|
||||
|
@ -42,17 +45,20 @@ public class UserAccountController {
|
|||
private final AuthorizationGrantService authorizationGrantService;
|
||||
private final UserService userService;
|
||||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public UserAccountController(
|
||||
final UserDAO userDao,
|
||||
final AuthorizationGrantService authorizationGrantService,
|
||||
final UserService userService,
|
||||
final UserActivityLogDAO userActivityLogDAO) {
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
this.userDao = userDao;
|
||||
this.authorizationGrantService = authorizationGrantService;
|
||||
this.userService = userService;
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
|
@ -123,7 +129,11 @@ public class UserAccountController {
|
|||
@RequestBody final UserMod userData,
|
||||
final Principal principal) {
|
||||
|
||||
return _saveUser(userData, principal, PrivilegeType.WRITE);
|
||||
return _saveUser(
|
||||
userData,
|
||||
this.userService.extractFromPrincipal(principal),
|
||||
PrivilegeType.WRITE)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/save", method = RequestMethod.POST)
|
||||
|
@ -131,31 +141,33 @@ public class UserAccountController {
|
|||
@RequestBody final UserMod userData,
|
||||
final Principal principal) {
|
||||
|
||||
return _saveUser(userData, principal, PrivilegeType.MODIFY);
|
||||
return _saveUser(
|
||||
userData,
|
||||
this.userService.extractFromPrincipal(principal),
|
||||
PrivilegeType.MODIFY)
|
||||
.getOrThrow();
|
||||
|
||||
}
|
||||
|
||||
private UserInfo _saveUser(
|
||||
private Result<UserInfo> _saveUser(
|
||||
final UserMod userData,
|
||||
final Principal principal,
|
||||
final PrivilegeType grantType) {
|
||||
final SEBServerUser admin,
|
||||
final PrivilegeType privilegeType) {
|
||||
|
||||
// fist check if current user has any privileges for this action
|
||||
this.authorizationGrantService.checkHasAnyPrivilege(
|
||||
EntityType.USER,
|
||||
grantType);
|
||||
|
||||
final SEBServerUser admin = this.userService.extractFromPrincipal(principal);
|
||||
final ActivityType actionType = (userData.getUserInfo().uuid == null)
|
||||
? ActivityType.CREATE
|
||||
: ActivityType.MODIFY;
|
||||
|
||||
return this.userDao
|
||||
.save(admin, userData)
|
||||
.flatMap(userInfo -> this.userActivityLogDAO.logUserActivity(
|
||||
admin,
|
||||
actionType,
|
||||
userInfo))
|
||||
.getOrThrow();
|
||||
return this.authorizationGrantService
|
||||
.checkGrantOnEntity(userData, privilegeType)
|
||||
.flatMap(data -> this.userDao.save(admin, data))
|
||||
.flatMap(userInfo -> this.userActivityLogDAO.logUserActivity(actionType, userInfo))
|
||||
.flatMap(userInfo -> {
|
||||
// handle password change; revoke access tokens if password has changed
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new RevokeTokenEndpoint.RevokeTokenEvent(this, userInfo.userName));
|
||||
return Result.of(userInfo);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
||||
@Controller
|
||||
@WebServiceProfile
|
||||
public class RevokeTokenEndpoint {
|
||||
|
||||
private final ConsumerTokenServices tokenServices;
|
||||
private final AdminAPIClientDetails adminAPIClientDetails;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
public RevokeTokenEndpoint(
|
||||
final ConsumerTokenServices tokenServices,
|
||||
final AdminAPIClientDetails adminAPIClientDetails,
|
||||
final TokenStore tokenStore) {
|
||||
|
||||
this.tokenServices = tokenServices;
|
||||
this.adminAPIClientDetails = adminAPIClientDetails;
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/oauth/revoke-token", method = RequestMethod.DELETE)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public void logout(final HttpServletRequest request) {
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null) {
|
||||
final String tokenId = authHeader.substring("Bearer".length() + 1);
|
||||
this.tokenServices.revokeToken(tokenId);
|
||||
}
|
||||
}
|
||||
|
||||
@EventListener(RevokeTokenEvent.class)
|
||||
private void revokeAccessToken(final RevokeTokenEvent event) {
|
||||
final String clientId = this.adminAPIClientDetails.getClientId();
|
||||
final Collection<OAuth2AccessToken> tokens = this.tokenStore
|
||||
.findTokensByClientIdAndUserName(clientId, event.userName);
|
||||
|
||||
if (tokens != null) {
|
||||
for (final OAuth2AccessToken token : tokens) {
|
||||
this.tokenStore.removeAccessToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RevokeTokenEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = 5776699085388043743L;
|
||||
|
||||
public final String userName;
|
||||
|
||||
public RevokeTokenEvent(final Object source, final String userName) {
|
||||
super(source);
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -9,8 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.webservice.integration.api;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -19,6 +18,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.Test;
|
||||
|
@ -105,7 +106,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
|
|||
assertEquals(
|
||||
"{\"messageCode\":\"1001\","
|
||||
+ "\"systemMessage\":\"FORBIDDEN\","
|
||||
+ "\"details\":\"No grant: READ_ONLY on type: USER entity institution: 1 entity owner: null for user: user3\","
|
||||
+ "\"details\":\"No grant: READ_ONLY on type: USER entity institution: 1 entity owner: user1 for user: user3\","
|
||||
+ "\"attributes\":[]}",
|
||||
contentAsString);
|
||||
}
|
||||
|
@ -146,8 +147,6 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
|
|||
assertNotNull(getUserInfo("admin", userInfos));
|
||||
assertNotNull(getUserInfo("inst1Admin", userInfos));
|
||||
assertNotNull(getUserInfo("examSupporter", userInfos));
|
||||
|
||||
// TODO more tests
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -193,6 +192,15 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
|
|||
assertNotNull(getUserInfo("examSupporter", userInfos));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerGet() throws Exception {
|
||||
final String examAdminToken1 = getExamAdmin1();
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/me")
|
||||
.header("Authorization", "Bearer " + examAdminToken1))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createUserTest() throws Exception {
|
||||
final UserInfo userInfo = new UserInfo(
|
||||
|
@ -247,6 +255,192 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
|
|||
assertEquals(createdUserGet.uuid, userActivityLog.entityId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyUserTest() throws Exception {
|
||||
final String token = getSebAdminAccess();
|
||||
final UserInfo user = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user7")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
});
|
||||
|
||||
assertNotNull(user);
|
||||
assertEquals("User", user.name);
|
||||
assertEquals("user1", user.userName);
|
||||
assertEquals("user@nomail.nomail", user.email);
|
||||
assertEquals("[EXAM_SUPPORTER]", String.valueOf(user.roles));
|
||||
|
||||
// change userName, email and roles
|
||||
final UserMod modifiedUser = new UserMod(new UserInfo(
|
||||
user.getUuid(),
|
||||
user.getInstitutionId(),
|
||||
user.getName(),
|
||||
"newUser1",
|
||||
"newUser@nomail.nomail",
|
||||
user.getActive(),
|
||||
user.getLocale(),
|
||||
user.getTimeZone(),
|
||||
Stream.of(UserRole.EXAM_ADMIN.name(), UserRole.EXAM_SUPPORTER.name()).collect(Collectors.toSet())),
|
||||
null, null);
|
||||
final String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
|
||||
|
||||
UserInfo modifiedUserResult = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/save")
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(modifiedUserJson))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
});
|
||||
|
||||
assertNotNull(modifiedUserResult);
|
||||
assertEquals("User", modifiedUserResult.name);
|
||||
assertEquals("newUser1", modifiedUserResult.userName);
|
||||
assertEquals("newUser@nomail.nomail", modifiedUserResult.email);
|
||||
assertEquals("[EXAM_ADMIN, EXAM_SUPPORTER]", String.valueOf(modifiedUserResult.roles));
|
||||
|
||||
// double check by getting the user by uuis
|
||||
modifiedUserResult = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/" + modifiedUserResult.uuid)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
});
|
||||
|
||||
assertNotNull(modifiedUserResult);
|
||||
assertEquals("User", modifiedUserResult.name);
|
||||
assertEquals("newUser1", modifiedUserResult.userName);
|
||||
assertEquals("newUser@nomail.nomail", modifiedUserResult.email);
|
||||
assertEquals("[EXAM_ADMIN, EXAM_SUPPORTER]", String.valueOf(modifiedUserResult.roles));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOwnerModifyPossible() throws Exception {
|
||||
final String examAdminToken1 = getExamAdmin1();
|
||||
final UserInfo examAdmin = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/me")
|
||||
.header("Authorization", "Bearer " + examAdminToken1))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
});
|
||||
|
||||
final UserMod modifiedUser = new UserMod(examAdmin, null, null);
|
||||
final String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
|
||||
|
||||
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/save")
|
||||
.header("Authorization", "Bearer " + examAdminToken1)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(modifiedUserJson))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void institutionalAdminTryToCreateOrModifyUserForOtherInstituionNotPossible() throws Exception {
|
||||
final UserInfo userInfo = new UserInfo(
|
||||
null, 2L, "NewTestUser", "NewTestUser",
|
||||
"", true, Locale.CANADA, DateTimeZone.UTC,
|
||||
new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name())));
|
||||
final UserMod newUser = new UserMod(userInfo, "123", "123");
|
||||
final String newUserJson = this.jsonMapper.writeValueAsString(newUser);
|
||||
|
||||
final String token = getAdminInstitution1Access();
|
||||
this.mockMvc.perform(put(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/create")
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(newUserJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/save")
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(newUserJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unauthorizedAdminTryToCreateUserNotPossible() throws Exception {
|
||||
final UserInfo userInfo = new UserInfo(
|
||||
null, 2L, "NewTestUser", "NewTestUser",
|
||||
"", true, Locale.CANADA, DateTimeZone.UTC,
|
||||
new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name())));
|
||||
final UserMod newUser = new UserMod(userInfo, "123", "123");
|
||||
final String newUserJson = this.jsonMapper.writeValueAsString(newUser);
|
||||
|
||||
final String token = getExamAdmin1();
|
||||
this.mockMvc.perform(put(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/create")
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(newUserJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/save")
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(newUserJson))
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyUserPassword() throws Exception {
|
||||
final String examAdminToken1 = getExamAdmin1();
|
||||
assertNotNull(examAdminToken1);
|
||||
|
||||
// a SEB Server Admin now changes the password of ExamAdmin1
|
||||
final String sebAdminToken = getSebAdminAccess();
|
||||
final UserInfo examAdmin1 = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4")
|
||||
.header("Authorization", "Bearer " + sebAdminToken))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
});
|
||||
|
||||
final UserMod modifiedUser = new UserMod(
|
||||
UserInfo.of(examAdmin1),
|
||||
"newPassword",
|
||||
"newPassword");
|
||||
final String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
|
||||
|
||||
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/save")
|
||||
.header("Authorization", "Bearer " + sebAdminToken)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.content(modifiedUserJson))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
// now it should not be possible to get a access token for ExamAdmin1 with the standard password
|
||||
try {
|
||||
getExamAdmin1();
|
||||
fail("AssertionError expected here");
|
||||
} catch (final AssertionError e) {
|
||||
assertEquals("Status expected:<200> but was:<400>", e.getMessage());
|
||||
}
|
||||
|
||||
// it should also not be possible to use an old token again after password change
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/me")
|
||||
.header("Authorization", "Bearer " + examAdminToken1))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
// but it should be possible to get a new access token and request again
|
||||
final String examAdminToken2 = obtainAccessToken("examAdmin1", "newPassword");
|
||||
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/me")
|
||||
.header("Authorization", "Bearer " + examAdminToken2))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
}
|
||||
|
||||
private UserInfo getUserInfo(final String name, final Collection<UserInfo> infos) {
|
||||
return infos
|
||||
.stream()
|
||||
|
|
Loading…
Reference in a new issue