SEBSERV-62 code cleanup, ClientConnectionController
This commit is contained in:
parent
7e453edb00
commit
ba49611c1c
12 changed files with 278 additions and 51 deletions
|
@ -61,6 +61,8 @@ public final class API {
|
|||
|
||||
public static final String EXAM_MONITORING_ENDPOINT = "/monitoring";
|
||||
|
||||
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
|
||||
|
||||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||
|
||||
public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
|
||||
|
@ -108,6 +110,7 @@ public final class API {
|
|||
public static final String EXAM_API_PARAM_EXAM_ID = "examId";
|
||||
|
||||
public static final String EXAM_API_SEB_CONNECTION_TOKEN = "SEBConnectionToken";
|
||||
public static final String EXAM_API_SEB_CONNECTION_TOKEN_PATH = "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
|
||||
|
||||
public static final String EXAM_API_USER_SESSION_ID = "seb_user_session_id";
|
||||
|
||||
|
|
|
@ -164,6 +164,13 @@ public final class Result<T> {
|
|||
return this.error != null;
|
||||
}
|
||||
|
||||
/** Indicates whether this Result refers to a value or not.
|
||||
*
|
||||
* @return true if this Result refers to a value (not null) and has no error */
|
||||
public boolean hasValue() {
|
||||
return this.value != null && this.error == null;
|
||||
}
|
||||
|
||||
/** If a value is present, performs the given action with the value,
|
||||
* otherwise performs the given empty-based action.
|
||||
*
|
||||
|
|
|
@ -11,12 +11,14 @@ package ch.ethz.seb.sebserver.webservice;
|
|||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
||||
@Lazy
|
||||
|
@ -39,6 +41,8 @@ public class WebserviceInfo {
|
|||
|
||||
private final String serverURLPrefix;
|
||||
|
||||
private final boolean isDistributed;
|
||||
|
||||
public WebserviceInfo(final Environment environment) {
|
||||
this.httpScheme = environment.getRequiredProperty(WEB_SERVICE_HTTP_SCHEME_KEY);
|
||||
this.hostAddress = environment.getRequiredProperty(WEB_SERVICE_HOST_ADDRESS_KEY);
|
||||
|
@ -53,6 +57,10 @@ public class WebserviceInfo {
|
|||
: this.hostAddress)
|
||||
.port(this.serverPort)
|
||||
.toUriString();
|
||||
|
||||
this.isDistributed = BooleanUtils.toBoolean(environment.getProperty(
|
||||
"sebserver.webservice.distributed",
|
||||
Constants.FALSE_STRING));
|
||||
}
|
||||
|
||||
public String getHttpScheme() {
|
||||
|
@ -113,6 +121,10 @@ public class WebserviceInfo {
|
|||
return this.serverURLPrefix;
|
||||
}
|
||||
|
||||
public boolean isDistributed() {
|
||||
return this.isDistributed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
|
|
@ -230,4 +230,26 @@ public interface AuthorizationService {
|
|||
return check(PrivilegeType.WRITE, grantEntity);
|
||||
}
|
||||
|
||||
/** Checks if the current user has a specified role.
|
||||
* If not a PermissionDeniedException is thrown for the given EntityType
|
||||
*
|
||||
* @param role The UserRole to check
|
||||
* @param institution the institution identifier
|
||||
* @param type EntityType for PermissionDeniedException
|
||||
* @throws PermissionDeniedException if current user don't have the specified UserRole */
|
||||
default void checkRole(final UserRole role, final Long institution, final EntityType type) {
|
||||
final SEBServerUser currentUser = this
|
||||
.getUserService()
|
||||
.getCurrentUser();
|
||||
|
||||
if (!currentUser.institutionId().equals(institution) ||
|
||||
!currentUser.getUserRoles().contains(role)) {
|
||||
|
||||
throw new PermissionDeniedException(
|
||||
type,
|
||||
PrivilegeType.READ,
|
||||
currentUser.getUserInfo().uuid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -172,6 +172,18 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
.withInstitutionalPrivilege(PrivilegeType.READ)
|
||||
.create();
|
||||
|
||||
// grants for SEB client connections
|
||||
addPrivilege(EntityType.SEB_CLIENT_CONFIGURATION)
|
||||
.forRole(UserRole.SEB_SERVER_ADMIN)
|
||||
.withBasePrivilege(PrivilegeType.READ)
|
||||
.forRole(UserRole.INSTITUTIONAL_ADMIN)
|
||||
.withInstitutionalPrivilege(PrivilegeType.READ)
|
||||
.andForRole(UserRole.EXAM_ADMIN)
|
||||
.withInstitutionalPrivilege(PrivilegeType.READ)
|
||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
|
||||
.create();
|
||||
|
||||
// TODO other entities
|
||||
|
||||
// grants for user activity logs
|
||||
|
|
|
@ -66,6 +66,11 @@ public class DistributedServerPingHandler implements PingHandlingStrategy {
|
|||
|
||||
@Override
|
||||
public void initForConnection(final Long connectionId, final String connectionToken) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Intitalize distributed ping handler for connection: {}", connectionId);
|
||||
}
|
||||
|
||||
final ClientEventRecord clientEventRecord = new ClientEventRecord();
|
||||
clientEventRecord.setConnectionId(connectionId);
|
||||
clientEventRecord.setType(EventType.LAST_PING.id);
|
||||
|
|
|
@ -92,8 +92,9 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) {
|
||||
return this.examDAO.allIdsOfInstituion(institutionId)
|
||||
.map(col -> col.stream()
|
||||
.map(examId -> this.examSessionCacheService.getRunningExam(examId))
|
||||
.filter(exam -> exam != null)
|
||||
.map(this::getRunningExam)
|
||||
.filter(Result::hasValue)
|
||||
.map(Result::get)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
@ -186,17 +187,21 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
}
|
||||
|
||||
private void flushCache(final Exam exam) {
|
||||
this.examSessionCacheService.evict(exam);
|
||||
this.examSessionCacheService.evictDefaultSebConfig(exam.id);
|
||||
this.clientConnectionDAO
|
||||
.getConnectionTokens(exam.id)
|
||||
.getOrElse(() -> Collections.emptyList())
|
||||
.forEach(token -> {
|
||||
// evict client connection
|
||||
this.examSessionCacheService.evictClientConnection(token);
|
||||
// evict also cached ping record
|
||||
this.examSessionCacheService.evictPingRecord(token);
|
||||
});
|
||||
try {
|
||||
this.examSessionCacheService.evict(exam);
|
||||
this.examSessionCacheService.evictDefaultSebConfig(exam.id);
|
||||
this.clientConnectionDAO
|
||||
.getConnectionTokens(exam.id)
|
||||
.getOrElse(() -> Collections.emptyList())
|
||||
.forEach(token -> {
|
||||
// evict client connection
|
||||
this.examSessionCacheService.evictClientConnection(token);
|
||||
// evict also cached ping record
|
||||
this.examSessionCacheService.evictPingRecord(token);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error while trying to flush cache for exam: ", exam, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.springframework.context.annotation.Lazy;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.PingHandlingStrategy;
|
||||
|
||||
@Lazy
|
||||
|
@ -21,19 +22,24 @@ public class PingHandlingStrategyFactory {
|
|||
|
||||
private final SingleServerPingHandler singleServerPingHandler;
|
||||
private final DistributedServerPingHandler distributedServerPingHandler;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
|
||||
protected PingHandlingStrategyFactory(
|
||||
final SingleServerPingHandler singleServerPingHandler,
|
||||
final DistributedServerPingHandler distributedServerPingHandler) {
|
||||
final DistributedServerPingHandler distributedServerPingHandler,
|
||||
final WebserviceInfo webserviceInfo) {
|
||||
|
||||
this.singleServerPingHandler = singleServerPingHandler;
|
||||
this.distributedServerPingHandler = distributedServerPingHandler;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
}
|
||||
|
||||
public PingHandlingStrategy get() {
|
||||
// NOTE not returns always DistributedServerPingHandler for testing
|
||||
// TODO: serve in case of distribution or single state
|
||||
return this.distributedServerPingHandler;
|
||||
if (this.webserviceInfo.isDistributed()) {
|
||||
return this.distributedServerPingHandler;
|
||||
} else {
|
||||
return this.singleServerPingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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 java.util.Collection;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
@RestController
|
||||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT)
|
||||
public class ClientEventController extends EntityController<ClientEvent, ClientEvent> {
|
||||
|
||||
private final ClientConnectionDAO clientConnectionDAO;
|
||||
|
||||
protected ClientEventController(
|
||||
final AuthorizationService authorization,
|
||||
final BulkActionService bulkActionService,
|
||||
final ClientEventDAO entityDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ClientConnectionDAO clientConnectionDAO) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
entityDAO,
|
||||
userActivityLogDAO,
|
||||
paginationService,
|
||||
beanValidationService);
|
||||
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientEvent create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientEvent savePut(@Valid final ClientEvent modifyData) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientEvent createNew(final POSTMapper postParams) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SqlTable getSQLTableOfEntity() {
|
||||
return ClientEventRecordDynamicSqlSupport.clientEventRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkReadPrivilege(final Long institutionId) {
|
||||
checkRead(institutionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ClientEvent> checkReadAccess(final ClientEvent entity) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final ClientConnection clientConnection = this.clientConnectionDAO
|
||||
.byPK(entity.connectionId)
|
||||
.getOrThrow();
|
||||
|
||||
checkRead(clientConnection.institutionId);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkModifyPrivilege(final Long institutionId) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.CLIENT_EVENT,
|
||||
PrivilegeType.MODIFY,
|
||||
this.authorization.getUserService().getCurrentUser().uuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ClientEvent> checkModifyAccess(final ClientEvent entity) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.CLIENT_EVENT,
|
||||
PrivilegeType.MODIFY,
|
||||
this.authorization.getUserService().getCurrentUser().uuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ClientEvent> checkWriteAccess(final ClientEvent entity) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.CLIENT_EVENT,
|
||||
PrivilegeType.WRITE,
|
||||
this.authorization.getUserService().getCurrentUser().uuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ClientEvent> checkCreateAccess(final ClientEvent entity) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.CLIENT_EVENT,
|
||||
PrivilegeType.WRITE,
|
||||
this.authorization.getUserService().getCurrentUser().uuid());
|
||||
}
|
||||
|
||||
private void checkRead(final Long institution) {
|
||||
this.authorization.checkRole(
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
institution,
|
||||
EntityType.CLIENT_EVENT);
|
||||
}
|
||||
|
||||
private void noModifyAccess() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -323,12 +323,6 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
protected void checkReadPrivilege() {
|
||||
this.authorization.check(
|
||||
PrivilegeType.READ,
|
||||
getGrantEntityType());
|
||||
}
|
||||
|
||||
protected void checkReadPrivilege(final Long institutionId) {
|
||||
this.authorization.check(
|
||||
PrivilegeType.READ,
|
||||
|
|
|
@ -33,9 +33,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
|
@ -70,10 +68,6 @@ public class ExamMonitoringController {
|
|||
.addUsersInstitutionDefaultPropertySupport(binder);
|
||||
}
|
||||
|
||||
// ******************
|
||||
// * GET (getAll)
|
||||
// ******************
|
||||
|
||||
/** Get a page of all currently running exams
|
||||
*
|
||||
* GET /{api}/{entity-type-endpoint-name}
|
||||
|
@ -105,17 +99,10 @@ public class ExamMonitoringController {
|
|||
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
|
||||
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
||||
|
||||
// check if user has EXAM_SUPPORTER privilege.
|
||||
final SEBServerUser currentUser = this.authorization
|
||||
.getUserService()
|
||||
.getCurrentUser();
|
||||
|
||||
if (!currentUser.getUserRoles().contains(UserRole.EXAM_SUPPORTER)) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.EXAM,
|
||||
PrivilegeType.READ,
|
||||
currentUser.getUserInfo().uuid);
|
||||
}
|
||||
this.authorization.checkRole(
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
institutionId,
|
||||
EntityType.EXAM);
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams);
|
||||
|
||||
|
@ -142,23 +129,43 @@ public class ExamMonitoringController {
|
|||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Collection<ClientConnectionData> getConnectionData(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId) {
|
||||
|
||||
// check if user has EXAM_SUPPORTER privilege.
|
||||
final SEBServerUser currentUser = this.authorization
|
||||
.getUserService()
|
||||
.getCurrentUser();
|
||||
|
||||
if (!currentUser.getUserRoles().contains(UserRole.EXAM_SUPPORTER)) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.EXAM,
|
||||
PrivilegeType.READ,
|
||||
currentUser.getUserInfo().uuid);
|
||||
}
|
||||
this.authorization.checkRole(
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
institutionId,
|
||||
EntityType.EXAM);
|
||||
|
||||
return this.examSessionService
|
||||
.getConnectionData(examId)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_API_SEB_CONNECTION_TOKEN_PATH,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ClientConnectionData getConnectionDataForSingleConnection(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId,
|
||||
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
||||
|
||||
this.authorization.checkRole(
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
institutionId,
|
||||
EntityType.EXAM);
|
||||
|
||||
return this.examSessionService
|
||||
.getConnectionData(connectionToken)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ spring.datasource.platform=dev
|
|||
spring.datasource.hikari.max-lifetime=600000
|
||||
|
||||
# webservice configuration
|
||||
sebserver.webservice.distributed=true
|
||||
sebserver.webservice.http.scheme=http
|
||||
sebserver.webservice.http.server.name=${server.address}
|
||||
sebserver.webservice.http.redirect.gui=/gui
|
||||
|
|
Loading…
Add table
Reference in a new issue