diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/authorization/Privilege.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/authorization/Privilege.java index b8d17628..c0b555cc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/authorization/Privilege.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/authorization/Privilege.java @@ -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 diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Exam.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Exam.java index 1be8d22c..8b9fb1fb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Exam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Exam.java @@ -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 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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java index f8d15393..2b23e698 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java @@ -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() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index f3930bd3..a30b123b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -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 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 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()) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java index d1a61994..781ecebb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 6914456f..bf480b75 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index 58687e0a..988e3734 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 3aefd570..70ce26c5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -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> 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> getExamResources() { final UserInfo userInfo = this.currentUser.get(); return this.restService.getBuilder(GetExamNames.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java new file mode 100644 index 00000000..79b8aa68 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java @@ -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 { + + public GetExams() { + super( + GetExamPage.class, + EntityType.EXAM, + new TypeReference>() { + }, + API.EXAM_ADMINISTRATION_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java index fc54bf02..4a118380 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableBuilder.java @@ -101,6 +101,17 @@ public class TableBuilder { return this; } + public TableBuilder withDefaultActionIf( + final BooleanSupplier condition, + final Function, PageAction> defaultActionFunction) { + + if (condition.getAsBoolean()) { + return withDefaultAction(defaultActionFunction); + } + + return this; + } + public TableBuilder withDefaultAction(final Function, PageAction> defaultActionFunction) { this.defaultActionFunction = defaultActionFunction; return this; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationService.java index a2834220..9839a19e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationService.java @@ -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. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java index d03d519d..baccaeb8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java @@ -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); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java index 4b07421d..c48fc554 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java @@ -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, BulkActionSupportDAO { Result> allIdsOfInstituion(Long institutionId); + + @Override + @CacheEvict( + cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM, + key = "#exam.id") + Result save(Exam exam); + + Result byClientConnection(Long connectionId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index 49161dae..24d7bdda 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -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 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> all(final Long institutionId, final Boolean active) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java index 16c0e4e0..998b61f4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java index 3bca575c..7745f17a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java @@ -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 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/")); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingHandlingStrategyFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingHandlingStrategyFactory.java index b9aedf62..946bc05d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingHandlingStrategyFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingHandlingStrategyFactory.java @@ -41,5 +41,4 @@ public class PingHandlingStrategyFactory { return this.singleServerPingHandler; } } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java index 277e839d..343b1b3e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java @@ -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 { - private final ClientConnectionDAO clientConnectionDAO; + private final ExamDAO examDAO; private final ClientEventDAO clientEventDAO; protected ClientEventController( @@ -55,7 +56,7 @@ public class ClientEventController extends ReadonlyEntityController 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 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()); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index c3dcb45f..79b41678 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -122,7 +122,7 @@ public class ExamAdministrationController extends ActivatableEntityController exams = new ArrayList<>( this.examDAO - .allMatching(new FilterMap(allRequestParams)) + .allMatching(new FilterMap(allRequestParams), this::hasReadAccess) .getOrThrow()); return buildSortedExamPage( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 152e69cb..59f73323 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -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); } } diff --git a/src/main/resources/config/application-demo.properties b/src/main/resources/config/application-demo.properties index 42109f7c..5e400d6f 100644 --- a/src/main/resources/config/application-demo.properties +++ b/src/main/resources/config/application-demo.properties @@ -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} diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 4db65f9d..220d38e9 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -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}