SEBSERV-65 SEBSERV-82 SEBSERV-83 bug-fixes
This commit is contained in:
parent
2b3b44419c
commit
dd826a3770
22 changed files with 246 additions and 69 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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/"));
|
||||
|
|
|
@ -41,5 +41,4 @@ public class PingHandlingStrategyFactory {
|
|||
return this.singleServerPingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
Loading…
Reference in a new issue