SEBSERV-65 SEBSERV-82 SEBSERV-83 bug-fixes

This commit is contained in:
anhefti 2019-08-22 09:26:46 +02:00
parent 2b3b44419c
commit dd826a3770
22 changed files with 246 additions and 69 deletions

View file

@ -8,11 +8,15 @@
package ch.ethz.seb.sebserver.gbl.api.authorization;
import java.util.Arrays;
import java.util.EnumSet;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
@ -95,7 +99,8 @@ public final class Privilege {
* @param privilegeType the type of privilege to check (READ_ONLY, MODIFY, WRITE...)
* @param institutionId the institution identifier of an Entity for the institutional grant check,
* may be null in case the institutional grant check should be skipped
* @param ownerId the owner identifier of an Entity for ownership grant check, may be null in case
* @param ownerId the owner identifier of an Entity for ownership grant check.
* This can be a single id or a comma-separated list of user ids and may be null in case
* the ownership grant check should be skipped
* @return true if there is any grant within the given context or false on deny */
public final boolean hasGrant(
@ -111,7 +116,16 @@ public final class Privilege {
&& userInstitutionId.longValue() == institutionId
.longValue())
|| (this.hasOwnershipPrivilege(privilegeType)
&& userId.equals(ownerId)));
&& isOwner(ownerId, userId)));
}
private boolean isOwner(final String ownerId, final String userId) {
if (StringUtils.isBlank(ownerId)) {
return false;
}
return Arrays.asList(StringUtils.split(ownerId, Constants.LIST_SEPARATOR))
.contains(userId);
}
@Override

View file

@ -8,11 +8,13 @@
package ch.ethz.seb.sebserver.gbl.model.exam;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@ -205,9 +207,25 @@ public final class Exam implements GrantEntity, Activatable {
return this.institutionId;
}
public boolean isOwner(final String userId) {
if (StringUtils.isBlank(userId)) {
return false;
}
if (userId.equals(this.owner)) {
return true;
}
return this.supporter.contains(userId);
}
@Override
public String getOwnerId() {
return this.owner;
final ArrayList<String> owners = new ArrayList<>(this.supporter);
if (!StringUtils.isBlank(this.owner)) {
owners.add(this.owner);
}
return StringUtils.join(owners, Constants.LIST_SEPARATOR);
}
public Long getLmsSetupId() {
@ -261,7 +279,7 @@ public final class Exam implements GrantEntity, Activatable {
return ExamStatus.UP_COMING;
}
// expand time frame
// expanded time frame
final DateTime expStartTime = this.startTime.minusHours(EXAM_RUN_TIME_EXPAND_HOURS);
final DateTime expEndTime = (this.endTime != null)
? this.endTime.plusHours(EXAM_RUN_TIME_EXPAND_HOURS) : null;

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gbl.model.user;
import java.io.Serializable;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
@ -22,6 +23,7 @@ import javax.validation.constraints.Size;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -201,6 +203,13 @@ public final class UserInfo implements UserAccount, Activatable, Serializable {
return this.roles.contains(userRole.name());
}
public boolean hasAnyRole(final UserRole... userRole) {
if (userRole == null) {
return false;
}
return CollectionUtils.containsAny(getUserRoles(), Arrays.asList(userRole));
}
@JsonIgnore
@Override
public EntityKey getEntityKey() {

View file

@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
@ -171,7 +172,9 @@ public class ExamForm implements TemplateComposer {
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
final boolean modifyGrant = userGrantCheck.m();
final ExamStatus examStatus = exam.getStatus();
final boolean editable = examStatus == ExamStatus.UP_COMING;
final boolean editable = examStatus == ExamStatus.UP_COMING ||
examStatus == ExamStatus.RUNNING &&
currentUser.get().hasRole(UserRole.EXAM_ADMIN);
// The Exam form
final FormHandle<Exam> formHandle = this.pageService.formBuilder(
@ -299,14 +302,26 @@ public class ExamForm implements TemplateComposer {
this.resourceService::localizedExamConfigStatusName))
.withDefaultActionIf(
() -> editable,
() -> actionBuilder
.newAction(ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST)
t -> actionBuilder
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withSelectionSupplier(() -> {
final ExamConfigurationMap selectedROWData = t.getSelectedROWData();
final HashSet<EntityKey> result = new HashSet<>();
if (selectedROWData != null) {
result.add(new EntityKey(
selectedROWData.configurationNodeId,
EntityType.CONFIGURATION_NODE));
}
return result;
})
.create())
.compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent())
? configurationTable.getFirstRowData().getEntityKey()
? new EntityKey(
configurationTable.getFirstRowData().configurationNodeId,
EntityType.CONFIGURATION_NODE)
: null;
actionBuilder
@ -315,7 +330,7 @@ public class ExamForm implements TemplateComposer {
.withParentEntityKey(entityKey)
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
.newAction(ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST)
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withParentEntityKey(entityKey)
.withEntityKey(configMapKey)
.publishIf(() -> modifyGrant && editable && configurationTable.hasAnyContent())

View file

@ -141,7 +141,7 @@ public class SebClientLogs implements TemplateComposer {
this.examFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
this.resourceService::getExamResources);
this.resourceService::getExamLogSelectionResources);
this.clientSessionFilter = new TableFilterAttribute(
CriteriaType.TEXT,

View file

@ -109,7 +109,7 @@ public enum ActionDefinition {
ActionCategory.FORM),
USER_ACCOUNT_CHANGE_PASSOWRD(
new LocTextKey("sebserver.useraccount.action.change.password"),
ImageIcon.EDIT,
ImageIcon.SECURE,
PageStateDefinition.USER_ACCOUNT_PASSWORD_CHANGE,
ActionCategory.FORM),
USER_ACCOUNT_CHANGE_PASSOWRD_SAVE(
@ -237,6 +237,11 @@ public enum ActionDefinition {
ImageIcon.EDIT,
PageStateDefinition.EXAM_CONFIG_MAP_EDIT,
ActionCategory.EXAM_CONFIG_MAPPING_LIST),
EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP(
new LocTextKey("sebserver.examconfig.action.view"),
ImageIcon.SHOW,
PageStateDefinition.SEB_EXAM_CONFIG_VIEW,
ActionCategory.EXAM_CONFIG_MAPPING_LIST),
EXAM_CONFIGURATION_DELETE_FROM_LIST(
new LocTextKey("sebserver.exam.configuration.action.list.delete"),
ImageIcon.DELETE,

View file

@ -109,8 +109,7 @@ public class ActivitiesPane implements TemplateComposer {
// User Account
// if current user has role seb-server admin or institutional-admin, show list
if (this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN) ||
this.currentUser.get().hasRole(UserRole.INSTITUTIONAL_ADMIN)) {
if (this.currentUser.get().hasAnyRole(UserRole.SEB_SERVER_ADMIN, UserRole.INSTITUTIONAL_ADMIN)) {
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
navigation,
@ -146,10 +145,10 @@ public class ActivitiesPane implements TemplateComposer {
}
// Exam (Quiz Discovery)
if (this.currentUser.hasInstitutionalPrivilege(PrivilegeType.READ, EntityType.EXAM)) {
if (this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.EXAM_ADMIN) ||
this.currentUser.hasInstitutionalPrivilege(PrivilegeType.READ, EntityType.EXAM)) {
// Quiz Discovery
// TODO discussion if this should be visible on Activity Pane or just over the Exam activity and Import action
final TreeItem quizDiscovery = this.widgetFactory.treeItemLocalized(
navigation,
ActivityDefinition.QUIZ_DISCOVERY.displayName);
@ -220,7 +219,7 @@ public class ActivitiesPane implements TemplateComposer {
}
// Monitoring exams
if (this.currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)) {
if (this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER)) {
final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
navigation,
ActivityDefinition.MONITORING_EXAMS.displayName);
@ -237,7 +236,8 @@ public class ActivitiesPane implements TemplateComposer {
EntityType.USER_ACTIVITY_LOG);
final boolean viewSebClientLogs = this.currentUser.hasInstitutionalPrivilege(
PrivilegeType.READ,
EntityType.EXAM);
EntityType.EXAM) ||
this.currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
TreeItem logRoot = null;
if (viewUserActivityLogs && viewSebClientLogs) {

View file

@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
@ -52,6 +53,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExams;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
@ -419,6 +421,20 @@ public class ResourceService {
.getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name());
}
public List<Tuple<String>> getExamLogSelectionResources() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetExams.class)
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.getInstitutionId()))
.call()
.getOr(Collections.emptyList())
.stream()
.filter(exam -> exam != null
&& (exam.getStatus() == ExamStatus.RUNNING || exam.getStatus() == ExamStatus.FINISHED))
.map(exam -> new Tuple<>(exam.getModelId(), exam.name))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public List<Tuple<String>> getExamResources() {
final UserInfo userInfo = this.currentUser.get();
return this.restService.getBuilder(GetExamNames.class)

View file

@ -0,0 +1,38 @@
/*
* 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.gui.service.remote.webservice.api.exam;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.PageToListCallAdapter;
@Lazy
@Component
@GuiProfile
public class GetExams extends PageToListCallAdapter<Exam> {
public GetExams() {
super(
GetExamPage.class,
EntityType.EXAM,
new TypeReference<List<Exam>>() {
},
API.EXAM_ADMINISTRATION_ENDPOINT);
}
}

View file

@ -101,6 +101,17 @@ public class TableBuilder<ROW extends Entity> {
return this;
}
public TableBuilder<ROW> withDefaultActionIf(
final BooleanSupplier condition,
final Function<EntityTable<ROW>, PageAction> defaultActionFunction) {
if (condition.getAsBoolean()) {
return withDefaultAction(defaultActionFunction);
}
return this;
}
public TableBuilder<ROW> withDefaultAction(final Function<EntityTable<ROW>, PageAction> defaultActionFunction) {
this.defaultActionFunction = defaultActionFunction;
return this;

View file

@ -177,14 +177,31 @@ public interface AuthorizationService {
* @param entityType the type of the entity to check the given privilege type on
* @param institutionId the institution identifier for institutional privilege grant check */
default void check(final PrivilegeType privilegeType, final EntityType entityType, final Long institutionId) {
if (!hasGrant(privilegeType, entityType, institutionId)) {
throw new PermissionDeniedException(
entityType,
privilegeType,
getUserService().getCurrentUser().getUserInfo());
// check institutional grant
if (hasGrant(privilegeType, entityType, institutionId)) {
return;
}
// if there is no institutional grant the user may have owner based grant on the specified realm
if (hasOwnerPrivilege(privilegeType, entityType, institutionId)) {
return;
}
throw new PermissionDeniedException(
entityType,
privilegeType,
getUserService().getCurrentUser().getUserInfo());
}
/** Indicates if the current user has an owner privilege for this give entity type and institution
*
* @param privilegeType the privilege type to check
* @param entityType entityType the type of the entity to check ownership privilege
* @param institutionId the institution identifier for institutional privilege grant check
* @return true if the current user has owner privilege */
boolean hasOwnerPrivilege(final PrivilegeType privilegeType, final EntityType entityType, Long institutionId);
/** Check grant by using corresponding hasGrant(XY) method and throws PermissionDeniedException
* on deny or return the given grantEntity within a Result on successful grant.
* This is useful to use with a Result based functional chain.

View file

@ -111,7 +111,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
.andForRole(UserRole.EXAM_ADMIN)
.withInstitutionalPrivilege(PrivilegeType.WRITE)
.andForRole(UserRole.EXAM_SUPPORTER)
.withInstitutionalPrivilege(PrivilegeType.READ)
.withOwnerPrivilege(PrivilegeType.MODIFY)
.create();
// grants for configuration node
@ -181,7 +181,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
.andForRole(UserRole.EXAM_ADMIN)
.withInstitutionalPrivilege(PrivilegeType.READ)
.andForRole(UserRole.EXAM_SUPPORTER)
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
.withOwnerPrivilege(PrivilegeType.READ)
.create();
// grants for user activity logs
@ -217,6 +217,27 @@ public class AuthorizationServiceImpl implements AuthorizationService {
.isPresent();
}
@Override
public boolean hasOwnerPrivilege(
final PrivilegeType privilegeType,
final EntityType entityType,
final Long institutionId) {
final SEBServerUser currentUser = this.getUserService().getCurrentUser();
if (!currentUser.institutionId().equals(institutionId)) {
return false;
}
return currentUser.getUserRoles()
.stream()
.map(role -> new RoleTypeKey(entityType, role))
.map(key -> this.privileges.get(key))
.filter(priv -> (priv != null) && priv.hasOwnershipPrivilege(privilegeType))
.findFirst()
.isPresent();
}
private PrivilegeBuilder addPrivilege(final EntityType entityType) {
return new PrivilegeBuilder(entityType);
}

View file

@ -10,12 +10,24 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection;
import org.springframework.cache.annotation.CacheEvict;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService;
/** Concrete EntityDAO interface of Exam entities */
public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSupportDAO<Exam> {
Result<Collection<Long>> allIdsOfInstituion(Long institutionId);
@Override
@CacheEvict(
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
key = "#exam.id")
Result<Exam> save(Exam exam);
Result<Exam> byClientConnection(Long connectionId);
}

View file

@ -38,6 +38,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
@ -54,13 +55,16 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
public class ExamDAOImpl implements ExamDAO {
private final ExamRecordMapper examRecordMapper;
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final LmsAPIService lmsAPIService;
public ExamDAOImpl(
final ExamRecordMapper examRecordMapper,
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final LmsAPIService lmsAPIService) {
this.examRecordMapper = examRecordMapper;
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.lmsAPIService = lmsAPIService;
}
@ -76,6 +80,17 @@ public class ExamDAOImpl implements ExamDAO {
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Exam> byClientConnection(final Long connectionId) {
return Result.tryCatch(() -> {
return this.clientConnectionRecordMapper.selectByPrimaryKey(connectionId);
})
.flatMap(ccRecord -> recordById(ccRecord.getExamId()))
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Exam>> all(final Long institutionId, final Boolean active) {

View file

@ -195,8 +195,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
case MOCKUP:
return new MockupLmsAPITemplate(
lmsSetup,
credentials,
this.clientCredentialService);
credentials);
case OPEN_EDX:
return new OpenEdxLmsAPITemplate(
this.asyncService,

View file

@ -23,7 +23,6 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
@ -33,18 +32,15 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
private final ClientCredentialService clientCredentialService;
private final LmsSetup lmsSetup;
private final ClientCredentials credentials;
private final Collection<QuizData> mockups;
MockupLmsAPITemplate(
final LmsSetup lmsSetup,
final ClientCredentials credentials,
final ClientCredentialService clientCredentialService) {
final ClientCredentials credentials) {
this.lmsSetup = lmsSetup;
this.clientCredentialService = clientCredentialService;
this.credentials = credentials;
final Long lmsSetupId = lmsSetup.id;
@ -68,7 +64,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6", "Demo Quit Mockup",
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
"2019-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7", "Demo Quit Mockup",
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));

View file

@ -41,5 +41,4 @@ public class PingHandlingStrategyFactory {
return this.singleServerPingHandler;
}
}
}

View file

@ -23,19 +23,20 @@ import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.Page;
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.session.ExtendedClientEvent;
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.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
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.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@ -45,7 +46,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT)
public class ClientEventController extends ReadonlyEntityController<ClientEvent, ClientEvent> {
private final ClientConnectionDAO clientConnectionDAO;
private final ExamDAO examDAO;
private final ClientEventDAO clientEventDAO;
protected ClientEventController(
@ -55,7 +56,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ClientConnectionDAO clientConnectionDAO) {
final ExamDAO examDAO) {
super(authorization,
bulkActionService,
@ -64,7 +65,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
paginationService,
beanValidationService);
this.clientConnectionDAO = clientConnectionDAO;
this.examDAO = examDAO;
this.clientEventDAO = entityDAO;
}
@ -83,7 +84,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
// at least current user must have read access for specified entity type within its own institution
// at least current user must have base read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
@ -113,34 +114,22 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
return ClientEventRecordDynamicSqlSupport.clientEventRecord;
}
@Override
protected GrantEntity toGrantEntity(final ClientEvent entity) {
return this.examDAO
.byClientConnection(entity.connectionId)
.getOrThrow();
}
@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();
this.authorization.checkRead(clientConnection);
return entity;
});
}
@Override
protected boolean hasReadAccess(final ClientEvent entity) {
return true;
}
private void checkRead(final Long institutionId) {
this.authorization.check(
PrivilegeType.READ,
EntityType.CLIENT_CONNECTION,
institutionId);
final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser();
if (currentUser.institutionId().longValue() != institutionId.longValue()) {
throw new PermissionDeniedException(
EntityType.CLIENT_EVENT,
PrivilegeType.READ,
currentUser.getUserInfo());
}
}
}

View file

@ -122,7 +122,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
final List<Exam> exams = new ArrayList<>(
this.examDAO
.allMatching(new FilterMap(allRequestParams))
.allMatching(new FilterMap(allRequestParams), this::hasReadAccess)
.getOrThrow());
return buildSortedExamPage(

View file

@ -198,7 +198,9 @@ public class ExamMonitoringController {
if (exam == null) {
return false;
}
return exam.institutionId.equals(institution) && this.authorization.hasReadGrant(exam);
final String userId = this.authorization.getUserService().getCurrentUser().getUserInfo().uuid;
return exam.institutionId.equals(institution) && exam.isOwner(userId);
}
}

View file

@ -18,6 +18,7 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.platform=demo
# webservice configuration
sebserver.webservice.distributed=false
sebserver.webservice.http.scheme=http
sebserver.webservice.http.server.name=ralph.ethz.ch
sebserver.webservice.http.redirect.gui=${sebserver.gui.entrypoint}

View file

@ -13,7 +13,7 @@ spring.datasource.platform=dev
spring.datasource.hikari.max-lifetime=600000
# webservice configuration
sebserver.webservice.distributed=true
sebserver.webservice.distributed=false
sebserver.webservice.http.scheme=http
sebserver.webservice.http.server.name=${server.address}