SEBSERV-143 and SEBSERV-144

This commit is contained in:
anhefti 2021-01-13 11:58:30 +01:00
parent a7e0dded7f
commit 5f969a22cf
15 changed files with 120 additions and 48 deletions

View file

@ -683,4 +683,8 @@ public final class Utils {
return StringUtils.truncate(text, toChars - 3) + "...";
}
public static String valueOrEmptyNote(final String value) {
return StringUtils.isBlank(value) ? Constants.EMPTY_NOTE : value;
}
}

View file

@ -194,8 +194,7 @@ public class ExamDeletePopup {
"FORM_REPORT_LIST_TYPE",
FORM_REPORT_LIST_TYPE,
dep -> i18nSupport
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name()) +
" (" + dep.self.getModelId() + ")"))
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name())))
.withColumn(new ColumnDefinition<>(
"FORM_REPORT_LIST_NAME",
FORM_REPORT_LIST_NAME,

View file

@ -411,13 +411,13 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
.withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService))
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
.noEventPropagation()
.publishIf(() -> proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
.withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService))
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
.noEventPropagation()
.publishIf(() -> !proctoringEnabled && readonly)

View file

@ -134,7 +134,7 @@ public class ExamFormConfigs implements TemplateComposer {
this.resourceService::localizedExamConfigStatusName)
.widthProportion(1))
.withDefaultActionIf(
() -> modifyGrant,
() -> true,
this::viewExamConfigPageAction)
.withSelectionListener(this.pageService.getSelectionPublisher(

View file

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
@ -67,11 +68,14 @@ public class ExamProctoringSettings {
private final static LocTextKey SEB_PROCTORING_FORM_SECRET =
new LocTextKey("sebserver.exam.proctoring.form.secret");
Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
return action -> {
final PageContext pageContext = action.pageContext();
final PageContext pageContext = action.pageContext()
.withAttribute(
PageContext.AttributeKeys.FORCE_READ_ONLY,
(modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING);
final ModalInputDialog<FormHandle<?>> dialog =
new ModalInputDialog<FormHandle<?>>(
action.pageContext().getParent().getShell(),
@ -81,18 +85,25 @@ public class ExamProctoringSettings {
final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm(
pageService,
action.pageContext());
pageContext);
final Predicate<FormHandle<?>> doBind = formHandle -> doSaveSettings(
pageService,
pageContext,
formHandle);
dialog.open(
SEB_PROCTORING_FORM_TITLE,
doBind,
Utils.EMPTY_EXECUTION,
bindFormContext);
if (modifyGrant) {
dialog.open(
SEB_PROCTORING_FORM_TITLE,
doBind,
Utils.EMPTY_EXECUTION,
bindFormContext);
} else {
dialog.open(
SEB_PROCTORING_FORM_TITLE,
pageContext,
pc -> bindFormContext.compose(pc.getParent()));
}
return action;
};

View file

@ -229,7 +229,7 @@ public class SEBExamConfigForm implements TemplateComposer {
: ActionDefinition.SEB_EXAM_CONFIG_MODIFY)
.withEntityKey(entityKey)
.withAttribute(PageContext.AttributeKeys.READ_ONLY, String.valueOf(!modifyGrant))
.publishIf(() -> modifyGrant && isReadonly)
.publishIf(() -> isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG)
.withEntityKey(entityKey)

View file

@ -17,6 +17,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
@ -63,8 +64,8 @@ public class UserAccountDeletePopup {
private static final Logger log = LoggerFactory.getLogger(UserAccountDeletePopup.class);
private final static String ARG_WITH_CONFIGS = "WITH_CONFIGS";
private final static String ARG_WITH_EXAMS = "WITH_EXAMS";
private final static String ARG_ALL_DEPENDENCIES = "ALL_DEPENDENCIES";
private final static String ARG_EXAMS_AND_DEPENDENCIES = "EXAMS_AND_DEPENDENCIES";
private final static LocTextKey FORM_TITLE =
new LocTextKey("sebserver.useraccount.delete.form.title");
@ -85,10 +86,10 @@ public class UserAccountDeletePopup {
new LocTextKey("sebserver.useraccount.delete.form.report.empty");
private final static LocTextKey FORM_NAME =
new LocTextKey("sebserver.useraccount.delete.form.accountName");
private final static LocTextKey FORM_CONFIGS =
new LocTextKey("sebserver.useraccount.delete.form.deleteAlsoConfigs");
private final static LocTextKey FORM_EXAMS =
new LocTextKey("sebserver.useraccount.delete.form.deleteAlsoExams");
private final static LocTextKey FORM_ALL_DEPS =
new LocTextKey("sebserver.useraccount.delete.form.deleteAllDeps");
private final static LocTextKey FORM_EXAM_DEPS =
new LocTextKey("sebserver.useraccount.delete.form.deleteExams");
private final static LocTextKey ACTION_DELETE =
new LocTextKey("sebserver.useraccount.delete.form.action.delete");
private final static LocTextKey ACTION_REPORT =
@ -171,8 +172,8 @@ public class UserAccountDeletePopup {
final PageContext pageContext) {
try {
final boolean withConfigs = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_WITH_CONFIGS));
final boolean withExams = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_WITH_EXAMS));
final boolean allDeps = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_ALL_DEPENDENCIES));
final boolean examDepsOnly = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_EXAMS_AND_DEPENDENCIES));
final EntityKey entityKey = pageContext.getEntityKey();
final UserInfo currentUser = pageService.getCurrentUser().get();
final boolean ownAccount = currentUser.getModelId().equals(entityKey.modelId);
@ -187,12 +188,14 @@ public class UserAccountDeletePopup {
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name())
.withQueryParam(API.PARAM_BULK_ACTION_ADD_INCLUDES, Constants.TRUE_STRING);
if (withConfigs) {
if (allDeps) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.CONFIGURATION_NODE.name());
}
if (withExams) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.EXAM.name());
} else if (examDepsOnly) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.EXAM.name());
@ -270,20 +273,35 @@ public class UserAccountDeletePopup {
.addFieldIf(
() -> showDeps,
() -> FormBuilder.checkbox(
ARG_WITH_CONFIGS,
FORM_CONFIGS))
ARG_ALL_DEPENDENCIES,
FORM_ALL_DEPS))
.addFieldIf(
() -> showDeps,
() -> FormBuilder.checkbox(
ARG_WITH_EXAMS,
FORM_EXAMS))
ARG_EXAMS_AND_DEPENDENCIES,
FORM_EXAM_DEPS))
.build();
final Form form = formHandle.getForm();
form.getFieldInput(ARG_ALL_DEPENDENCIES)
.addListener(SWT.Selection, event -> {
if (Constants.TRUE_STRING.equals(form.getFieldValue(ARG_ALL_DEPENDENCIES))) {
form.setFieldValue(ARG_EXAMS_AND_DEPENDENCIES, Constants.FALSE_STRING);
}
});
form.getFieldInput(ARG_EXAMS_AND_DEPENDENCIES)
.addListener(SWT.Selection, event -> {
if (Constants.TRUE_STRING.equals(form.getFieldValue(ARG_EXAMS_AND_DEPENDENCIES))) {
form.setFieldValue(ARG_ALL_DEPENDENCIES, Constants.FALSE_STRING);
}
});
return () -> pageContext
.withAttribute(ARG_WITH_CONFIGS, form.getFieldValue(ARG_WITH_CONFIGS))
.withAttribute(ARG_WITH_EXAMS, form.getFieldValue(ARG_WITH_EXAMS));
.withAttribute(ARG_ALL_DEPENDENCIES, form.getFieldValue(ARG_ALL_DEPENDENCIES))
.withAttribute(ARG_EXAMS_AND_DEPENDENCIES, form.getFieldValue(ARG_EXAMS_AND_DEPENDENCIES));
}
private Supplier<PageContext> composeReportDialog(
@ -303,8 +321,8 @@ public class UserAccountDeletePopup {
try {
// get selection
final boolean withConfigs = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_WITH_CONFIGS));
final boolean withExams = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_WITH_EXAMS));
final boolean allDeps = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_ALL_DEPENDENCIES));
final boolean examDepsOnly = BooleanUtils.toBoolean(pageContext.getAttribute(ARG_EXAMS_AND_DEPENDENCIES));
// get dependencies
final EntityKey entityKey = pageContext.getEntityKey();
@ -314,12 +332,14 @@ public class UserAccountDeletePopup {
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name())
.withQueryParam(API.PARAM_BULK_ACTION_ADD_INCLUDES, Constants.TRUE_STRING);
if (withConfigs) {
if (allDeps) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.CONFIGURATION_NODE.name());
}
if (withExams) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.EXAM.name());
} else if (examDepsOnly) {
restCallBuilder.withQueryParam(
API.PARAM_BULK_ACTION_INCLUDES,
EntityType.EXAM.name());
@ -333,8 +353,7 @@ public class UserAccountDeletePopup {
"FORM_REPORT_LIST_TYPE",
FORM_REPORT_LIST_TYPE,
dep -> i18nSupport
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name()) +
" (" + dep.self.getModelId() + ")"))
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name())))
.withColumn(new ColumnDefinition<>(
"FORM_REPORT_LIST_NAME",
FORM_REPORT_LIST_NAME,

View file

@ -81,7 +81,7 @@ public class BulkActionServiceImpl implements BulkActionService {
EntityType.INDICATOR,
EntityType.CLIENT_CONNECTION));
this.directDependancyMap.put(EntityType.CONFIGURATION_NODE,
EnumSet.noneOf(EntityType.class));
EnumSet.of(EntityType.EXAM_CONFIGURATION_MAP));
}
@Override

View file

@ -531,8 +531,9 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
return new EntityDependency(
parent,
new EntityKey(model.getId(), EntityType.EXAM_CONFIGURATION_MAP),
model.getExamName() + " / " + model.getConfigName(),
model.getExamDescription() + " / " + model.getConfigDescription());
Utils.valueOrEmptyNote(model.getExamName()) + " / " + Utils.valueOrEmptyNote(model.getConfigName()),
Utils.valueOrEmptyNote(model.getExamDescription()) + " / "
+ Utils.valueOrEmptyNote(model.getConfigDescription()));
}
private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {

View file

@ -8,6 +8,10 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@ -16,6 +20,7 @@ import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
@ -147,4 +152,19 @@ public interface LmsAPITemplate {
* @return Result refer to the given Exam if successful or to an error if not */
Result<Exam> releaseSEBClientRestriction(Exam exam);
/** This is used th verify if a given LMS Setup URL is available (valid)
*
* @param urlString the URL string given by the LMS Setup attribute
* @return true if SEB Server was able to ping the address. */
static boolean pingHost(final String urlString) {
try (Socket socket = new Socket()) {
final URL url = new URL(urlString);
final int port = (url.getPort() >= 0) ? url.getPort() : 80;
socket.connect(new InetSocketAddress(url.getHost(), port), (int) Constants.SECOND_IN_MILLIS * 5);
return true;
} catch (final IOException e) {
return false; // Either timeout or unreachable or failed DNS lookup.
}
}
}

View file

@ -42,6 +42,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
final class OpenEdxRestTemplateFactory {
@ -81,6 +82,13 @@ final class OpenEdxRestTemplateFactory {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:notNull"));
} else {
// try to connect to the url
if (!LmsAPITemplate.pingHost(this.lmsSetup.lmsApiUrl)) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:url.invalid"));
}
}
if (!this.credentials.hasClientId()) {
missingAttrs.add(APIMessage.fieldValidationError(

View file

@ -50,6 +50,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
class MoodleRestTemplateFactory {
@ -92,6 +93,13 @@ class MoodleRestTemplateFactory {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:notNull"));
} else {
// try to connect to the url
if (!LmsAPITemplate.pingHost(this.lmsSetup.lmsApiUrl)) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:url.invalid"));
}
}
if (StringUtils.isBlank(this.lmsSetup.lmsRestApiToken)) {

View file

@ -244,7 +244,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
checkModifyPrivilege(institutionId);
checkReadPrivilege(institutionId);
return this.examSessionService
.checkRunningExamConsistency(modelId)
.getOrThrow();

View file

@ -89,6 +89,7 @@ sebserver.form.validation.fieldError.invalidURL=The input does not match the URL
sebserver.form.validation.fieldError.exists=This name already exists. Please choose another one.
sebserver.form.validation.fieldError.email=Invalid mail address
sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be available within the given URL
sebserver.form.validation.fieldError.url.invalid=Invalid URL. The given URL cannot be reached.
sebserver.error.unexpected=Unexpected Error
sebserver.page.message=Information
sebserver.dialog.confirm.title=Confirmation
@ -264,10 +265,10 @@ sebserver.useraccount.delete.form.report.list.type=Type
sebserver.useraccount.delete.form.report.list.name=Name
sebserver.useraccount.delete.form.report.list.description=Description
sebserver.useraccount.delete.form.accountName=Name
sebserver.useraccount.delete.form.deleteAlsoConfigs=Include all Exam Configurations
sebserver.useraccount.delete.form.deleteAlsoConfigs.tooltip=This includes all Exam Configuration which this uses has created and is owner of
sebserver.useraccount.delete.form.deleteAlsoExams=Include all Exams
sebserver.useraccount.delete.form.deleteAlsoExams.tooltip=This includes all Exams which the user has imported and is owner of.<br/>This also includes all Client Connections and all SEB Client Logs that where created within an Exam that is part of this deletion.
sebserver.useraccount.delete.form.deleteAllDeps=Include all Dependencies
sebserver.useraccount.delete.form.deleteAllDeps.tooltip=This includes all Exams and Exam Configuration which this uses has created and is owner of.<br/>This also includes all Client Connections and all SEB Client Logs that where created within an Exam that is part of this deletion.
sebserver.useraccount.delete.form.deleteExams=Include all Exams (keep Exam Configurations)
sebserver.useraccount.delete.form.deleteExams.tooltip=This includes all Exams which the user has imported and is owner of but not the Exam Configuration.<br/>This also includes all Client Connections and all SEB Client Logs that where created within an Exam that is part of this deletion.
sebserver.useraccount.delete.form.action.delete=Delete
sebserver.useraccount.delete.form.action.report=Show Report
sebserver.useraccount.delete.confirm.title=Deletion Successful
@ -635,7 +636,7 @@ sebserver.exam.delete.confirm.title=Deletion Successful
sebserver.exam.delete.confirm.message=The Exam ({0}) was successfully deleted.<br/>Also the following number dependencies where successfully deleted: {1}.<br/><br/>And there where {2} errors.
sebserver.exam.delete.report.list.empty=No dependencies will be deleted.
sebserver.exam.proctoring.actions.open=Set Proctoring
sebserver.exam.proctoring.actions.open=Proctoring Settings
sebserver.exam.proctoring.form.title=Exam Proctoring Settings
sebserver.exam.proctoring.form.info.title=Remote Proctoring
sebserver.exam.proctoring.form.info=This allows you to integrate a supported external proctoring service.

View file

@ -2310,7 +2310,8 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
"[CONFIGURATION_NODE, "
+ "CONFIGURATION_NODE, "
+ "CONFIGURATION_NODE, "
+ "CONFIGURATION_NODE]",
+ "CONFIGURATION_NODE, "
+ "EXAM_CONFIGURATION_MAP]",
dependencies.stream().map(dep -> dep.self.entityType).collect(Collectors.toList()).toString());
// only with exam and configuration dependencies