SEBSERV-8 #more fixes and tests

This commit is contained in:
anhefti 2018-12-14 22:31:15 +01:00
parent 58881bf763
commit 02ca751748
17 changed files with 219 additions and 93 deletions

View file

@ -8,7 +8,7 @@
package ch.ethz.seb.sebserver.gbl.model;
public interface Entity {
public interface Entity extends ModelIdAware {
EntityType entityType();

View file

@ -17,6 +17,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO.Acti
public class UserActivityLog implements Entity {
@JsonIgnore
public final Long id;
@JsonProperty("userId")
public final String userId;
@JsonProperty("timestamp")
@ -31,13 +33,15 @@ public class UserActivityLog implements Entity {
public final String message;
public UserActivityLog(
@JsonProperty("userId") final String userId,
@JsonProperty("timestamp") final Long timestamp,
@JsonProperty("actionType") final ActionType actionType,
@JsonProperty("entityType") final EntityType entityType,
@JsonProperty("entityId") final String entityId,
@JsonProperty("message") final String message) {
final Long id,
final String userId,
final Long timestamp,
final ActionType actionType,
final EntityType entityType,
final String entityId,
final String message) {
this.id = id;
this.userId = userId;
this.timestamp = timestamp;
this.actionType = actionType;
@ -52,6 +56,12 @@ public class UserActivityLog implements Entity {
return EntityType.USER_LOG;
}
@JsonIgnore
@Override
public String getId() {
return String.valueOf(this.id);
}
public String getUserId() {
return this.userId;
}
@ -78,9 +88,10 @@ public class UserActivityLog implements Entity {
@Override
public String toString() {
return "UserActivityLog [userId=" + this.userId + ", timestamp=" + this.timestamp + ", actionType="
+ this.actionType
+ ", entityType=" + this.entityType + ", entityId=" + this.entityId + ", message=" + this.message + "]";
return "UserActivityLog [id=" + this.id + ", userId=" + this.userId + ", timestamp=" + this.timestamp
+ ", actionType="
+ this.actionType + ", entityType=" + this.entityType + ", entityId=" + this.entityId + ", message="
+ this.message + "]";
}
}

View file

@ -10,8 +10,6 @@ package ch.ethz.seb.sebserver.gbl.model.user;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.BooleanUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@ -47,7 +45,7 @@ public final class UserFilter {
this.name = name;
this.userName = userName;
this.email = email;
this.active = BooleanUtils.isFalse(active);
this.active = (active != null) ? active : true;
this.locale = locale;
}

View file

@ -118,6 +118,12 @@ public final class UserInfo implements GrantEntity, Serializable {
return EntityType.USER;
}
@JsonIgnore
@Override
public String getId() {
return this.uuid;
}
public String getUuid() {
return this.uuid;
}

View file

@ -22,4 +22,9 @@ public enum UserRole implements Entity {
public EntityType entityType() {
return EntityType.USER_ROLE;
}
@Override
public String getId() {
return name();
}
}

View file

@ -21,19 +21,30 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
* has write, modify or even read-only rights on an entity instance or on an entity type. */
public interface AuthorizationGrantService {
/** Check a specified GrantType for a given GrantEntity for given user-principal and
* returns a with a Result of the granted entity instance or with a Result of a
* NoGrantException.
/** Check a specified GrantType for a given GrantEntity and for a given user-principal.
* Use this to check a grant for a given entity instance by passing also the user-principal to check for.
*
* @param entity The GrantEntity to check specified GrantType for
* @param grantType The GrantType
* @param principal the user principal
* @return a with a Result of the granted entity instance or with a Result of a NoGrantException */
* @return a with a Result of the granted entity instance or with a Result of a PermissionDeniedException */
<E extends GrantEntity> Result<E> checkGrantForEntity(
final E entity,
final GrantType grantType,
final Principal principal);
/** Check a specified GrantType for a given entity type and for a given user-principal.
* Use this to check a base-grant for a given entity type by passing also the user-principal to check for.
*
* @param entityType The EntityType to check specified GrantType for
* @param grantType The GrantType
* @param principal the user principal
* @return a with a Result of the granted entity-type or with a Result of a PermissionDeniedException */
Result<EntityType> checkGrantForType(
final EntityType entityType,
final GrantType grantType,
final Principal principal);
/** Checks if a given user has a specified grant for a given entity-type
*
* NOTE: within this method only base-privileges for a given entity-type are checked

View file

@ -117,6 +117,19 @@ public class AuthorizationGrantServiceImpl implements AuthorizationGrantService
}
}
@Override
public Result<EntityType> checkGrantForType(
final EntityType entityType,
final GrantType grantType,
final Principal principal) {
if (hasBaseGrant(entityType, grantType, principal)) {
return Result.of(entityType);
} else {
return Result.ofError(new PermissionDeniedException(entityType, grantType, principal.getName()));
}
}
@Override
public boolean hasBaseGrant(
final EntityType entityType,

View file

@ -11,7 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection;
import java.util.function.Predicate;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.UserLogRecord;
@ -32,14 +32,12 @@ public interface UserActivityLogDAO extends UserRelatedEntityDAO<UserActivityLog
*
* @param user for specified SEBServerUser instance
* @param actionType the action type
* @param entityType the entity type
* @param entityId the entity id (primary key or UUID)
* @param entity the Entity
* @param message an optional message */
void logUserActivity(
<E extends Entity> Result<E> logUserActivity(
SEBServerUser user,
ActionType actionType,
EntityType entityType,
String entityId,
E entity,
String message);
/** Creates a user activity log entry.
@ -48,13 +46,12 @@ public interface UserActivityLogDAO extends UserRelatedEntityDAO<UserActivityLog
* @param actionType the action type
* @param entityType the entity type
* @param entityId the entity id (primary key or UUID) */
default void logUserActivity(
default <E extends Entity> Result<E> logUserActivity(
final SEBServerUser user,
final ActionType actionType,
final EntityType entityType,
final String entityId) {
final E entity) {
logUserActivity(user, actionType, entityType, entityId);
return logUserActivity(user, actionType, entity, null);
}
Result<Collection<UserActivityLog>> allForUser(

View file

@ -17,9 +17,13 @@ import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
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.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -30,6 +34,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@Lazy
@Component
public class UserActivityLogDAOImpl implements UserActivityLogDAO {
private static final Logger log = LoggerFactory.getLogger(UserActivityLogDAOImpl.class);
@ -52,11 +58,10 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override
@Transactional
public void logUserActivity(
public <E extends Entity> Result<E> logUserActivity(
final SEBServerUser user,
final ActionType actionType,
final EntityType entityType,
final String entityId,
final E entity,
final String message) {
try {
@ -66,18 +71,26 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
user.getUserInfo().uuid,
System.currentTimeMillis(),
actionType.name(),
entityType.name(),
entityId,
entity.entityType().name(),
entity.getId(),
message));
return Result.of(entity);
} catch (final Throwable t) {
log.error(
"Unexpected error while trying to log user activity for user {}, action-type: {} entity-type: {} entity-id: {}",
user.getUserInfo().uuid,
actionType,
entityType,
entityId,
entity.entityType().name(),
entity.getId(),
t);
TransactionInterceptor
.currentTransactionStatus()
.setRollbackOnly();
return Result.ofError(t);
}
}
@ -214,6 +227,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
try {
return Result.of(new UserActivityLog(
record.getId(),
record.getUserUuid(),
record.getTimestamp(),
ActionType.valueOf(record.getActionType()),

View file

@ -234,7 +234,7 @@ public class UserDaoImpl implements UserDAO {
return Result.ofError(new IllegalArgumentException("The users institution cannot be null"));
}
if (userMod.newPasswordMatch()) {
if (!userMod.newPasswordMatch()) {
return Result.ofError(new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH));
}

View file

@ -1,39 +0,0 @@
/*
* 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;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
@WebServiceProfile
public class WebExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(
final Exception ex,
final Object body,
final HttpHeaders headers,
final HttpStatus status, final WebRequest request) {
// TODO here we can handle exception within the Rest API
ex.printStackTrace();
return super.handleExceptionInternal(ex, body, headers, status, request);
}
}

View file

@ -82,4 +82,14 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
.createErrorResponse(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleUnexpected(
final Exception ex,
final WebRequest request) {
log.error("Unexpected internal error catched at the API endpoint: ", ex);
return APIMessage.ErrorMessage.UNEXPECTED
.createErrorResponse(ex.getMessage());
}
}

View file

@ -23,11 +23,17 @@ import org.springframework.web.bind.annotation.RestController;
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;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser;
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.ActionType;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
@WebServiceProfile
@RestController
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}/useraccount")
public class UserAccountController {
@ -35,15 +41,18 @@ public class UserAccountController {
private final UserDAO userDao;
private final AuthorizationGrantService authorizationGrantService;
private final UserService userService;
private final UserActivityLogDAO userActivityLogDAO;
public UserAccountController(
final UserDAO userDao,
final AuthorizationGrantService authorizationGrantService,
final UserService userService) {
final UserService userService,
final UserActivityLogDAO userActivityLogDAO) {
this.userDao = userDao;
this.authorizationGrantService = authorizationGrantService;
this.userService = userService;
this.userActivityLogDAO = userActivityLogDAO;
}
@RequestMapping(method = RequestMethod.GET)
@ -71,8 +80,9 @@ public class UserAccountController {
if (filter == null) {
return this.userDao
.all(grantFilter)
.all(userInfo -> userInfo.active && grantFilter.test(userInfo))
.getOrThrow();
} else {
return this.userDao
@ -104,12 +114,45 @@ public class UserAccountController {
}
// @RequestMapping(value = "/", method = RequestMethod.POST)
// public UserInfo save(
// @PathVariable final Long institutionId,
// @RequestBody final UserFilter filter,
// final Principal principal) {
//
// }
@RequestMapping(value = "/create", method = RequestMethod.PUT)
public UserInfo createUser(
@RequestBody final UserMod userData,
final Principal principal) {
return _saveUser(userData, principal, GrantType.WRITE);
}
@RequestMapping(value = "/save", method = RequestMethod.POST)
public UserInfo saveUser(
@RequestBody final UserMod userData,
final Principal principal) {
return _saveUser(userData, principal, GrantType.MODIFY);
}
private UserInfo _saveUser(
final UserMod userData,
final Principal principal,
final GrantType grantType) {
this.authorizationGrantService.checkGrantForType(
EntityType.USER,
grantType,
principal)
.getOrThrow();
final SEBServerUser admin = this.userService.extractFromPrincipal(principal);
final ActionType actionType = (userData.getUserInfo().uuid == null)
? ActionType.CREATE
: ActionType.MODIFY;
return this.userDao
.save(admin, userData)
.flatMap(userInfo -> this.userActivityLogDAO.logUserActivity(
admin,
actionType,
userInfo))
.getOrThrow();
}
}

View file

@ -0,0 +1,25 @@
/*
* 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.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@WebServiceProfile
@RestController
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}/useraccount")
public class UserActionLogController {
public UserActionLogController() {
System.out.println("UserActionLogController");
}
}

View file

@ -9,7 +9,6 @@
package ch.ethz.seb.sebserver.gbl.model.user;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
@ -26,6 +25,7 @@ public class UserActivityLogTest {
@Test
public void testFromToJson() throws IOException {
final UserActivityLog testModel = new UserActivityLog(
1L,
"testUser",
123l,
ActionType.CREATE,
@ -43,15 +43,6 @@ public class UserActivityLogTest {
+ "\"entityId\":\"321\","
+ "\"message\":\"noComment\"}",
jsonValue);
final UserActivityLog deserialized = this.jsonMapper.readValue(jsonValue, UserActivityLog.class);
assertNotNull(deserialized);
assertEquals(testModel.userId, deserialized.userId);
assertEquals(testModel.timestamp, deserialized.timestamp);
assertEquals(testModel.actionType, deserialized.actionType);
assertEquals(testModel.entityType, deserialized.entityType);
assertEquals(testModel.entityId, deserialized.entityId);
assertEquals(testModel.message, deserialized.message);
}
}

View file

@ -10,12 +10,17 @@ 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.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import org.springframework.http.MediaType;
@ -23,6 +28,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
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.model.user.UserRole;
public class UserAPITest extends AdministrationAPIIntegrationTest {
@ -182,6 +189,35 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
assertNotNull(getUserInfo("examSupporter", userInfos));
}
@Test
public void createUserTest() throws Exception {
final UserInfo userInfo = new UserInfo(
null, 1L, "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 = getSebAdminAccess();
final UserInfo createdUser = this.jsonMapper.readValue(
this.mockMvc.perform(put(this.endpoint + "/useraccount/create")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(newUserJson))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<UserInfo>() {
});
assertNotNull(createdUser);
assertEquals("NewTestUser", createdUser.name);
// TODO get newly created user and check equality
// TODO get user activity log for newly created user
}
private UserInfo getUserInfo(final String name, final Collection<UserInfo> infos) {
return infos
.stream()

View file

@ -95,6 +95,11 @@ public class AuthorizationGrantServiceTest {
return owner;
}
@Override
public String getId() {
return "1";
}
};
}