diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureService.java b/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureService.java deleted file mode 100644 index 6e67ec20..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureService.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2023 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.gbl; - -import ch.ethz.seb.sebserver.gbl.model.exam.CollectingStrategy; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; - -@Deprecated // we need another more flexible feature service that also take new User Role and Privileges into account - // SEBSERV-497 -public interface FeatureService { - - String FEATURE_SETTINGS_PREFIX = "sebserver.feature."; - - enum ConfigurableFeature { - SCREEN_PROCTORING("seb.screenProctoring"), - INSTITUTION("admin.institution"), - REMOTE_PROCTORING("seb.remoteProctoring"), - TEST_LMS("lms.testLMS"), - EXAM_NO_LMS("exam.noLMS"), - LIGHT_SETUP("setup.light") - - ; - - final String namespace; - - ConfigurableFeature(final String namespace) { - this.namespace = namespace; - } - } - - boolean isEnabled(ConfigurableFeature feature); - - boolean isEnabled(LmsType LmsType); - - boolean isEnabled(ProctoringServerType proctoringServerType); - - boolean isEnabled(CollectingStrategy collectingRoomStrategy); - - - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureServiceImpl.java deleted file mode 100644 index e364f9c7..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/FeatureServiceImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2023 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.gbl; - -import org.springframework.context.annotation.Lazy; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Service; - -import ch.ethz.seb.sebserver.gbl.model.exam.CollectingStrategy; -import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; - -@Lazy -@Service -@WebServiceProfile -@Deprecated // we need another more flexible feature service that also take new User Role and Privileges into account -// SEBSERV-497 -public class FeatureServiceImpl implements FeatureService { - - private final Environment environment; - - public FeatureServiceImpl(final Environment environment) { - this.environment = environment; - } - - @Override - public boolean isEnabled(final LmsType LmsType) { - return this.environment.getProperty(toConfigName( - FEATURE_SETTINGS_PREFIX + LmsType.class.getSimpleName() + "." - + LmsType.name()), - Boolean.class, - Boolean.TRUE); - } - - @Override - public boolean isEnabled(final CollectingStrategy collectingRoomStrategy) { - return this.environment.getProperty(toConfigName( - FEATURE_SETTINGS_PREFIX + CollectingStrategy.class.getSimpleName() + "." - + collectingRoomStrategy.name()), - Boolean.class, - Boolean.TRUE); - } - - @Override - public boolean isEnabled(final ProctoringServerType proctoringServerType) { - return this.environment.getProperty(toConfigName( - FEATURE_SETTINGS_PREFIX + ProctoringServerType.class.getSimpleName() + "." - + proctoringServerType.name()), - Boolean.class, - Boolean.TRUE); - } - - @Override - public boolean isEnabled(final ConfigurableFeature feature) { - return this.environment.getProperty(toConfigName( - FEATURE_SETTINGS_PREFIX + feature.namespace + ".enabled"), - Boolean.class, - Boolean.FALSE); - } - - private String toConfigName(final String key) { - return key.replaceAll("_", "-"); - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 0beff3fd..4f06b048 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -66,6 +66,8 @@ public final class API { public static final String LOGIN_PATH_SEGMENT = "/loglogin"; public static final String LOGOUT_PATH_SEGMENT = "/loglogout"; + public static final String FEATURES_PATH_SEGMENT = "/features"; + public static final String INFO_ENDPOINT = "/info"; public static final String INFO_PARAM_INST_SUFFIX = "urlSuffix"; public static final String INFO_INST_PATH_SEGMENT = "/institution"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java new file mode 100644 index 00000000..00203e9f --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserFeatures.java @@ -0,0 +1,71 @@ +package ch.ethz.seb.sebserver.gbl.model.user; + +import java.util.Map; + +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserFeatures { + + public static final String ATTR_DEFAULT = "missingFeatureDefault"; + public static final String ATTR_FEATURE_PRIVILEGES = "featurePrivileges"; + + public enum Feature { + INSTITUTION("admin.institution"), + SCREEN_PROCTORING("seb.screenProctoring"), + + LIVE_PROCTORING("seb.liveProctoring"), + TEST_LMS("lms.type.MOCKUP"), + EXAM_NO_LMS("exam.noLMS"), + + ; + + public final String featureName; + + Feature(final String featureName) { + this.featureName = featureName; + } + } + + @JsonProperty(Domain.USER.ATTR_ID) + public final String userId; + @JsonProperty(ATTR_DEFAULT) + public final Boolean missingFeatureDefault; + @JsonProperty(ATTR_FEATURE_PRIVILEGES) + public final Map featurePrivileges; + + @JsonCreator + public UserFeatures( + @JsonProperty(Domain.USER.ATTR_ID) final String userId, + @JsonProperty(ATTR_DEFAULT) final Boolean missingFeatureDefault, + @JsonProperty(ATTR_FEATURE_PRIVILEGES) final Map featurePrivileges) { + + this.userId = userId; + this.missingFeatureDefault = missingFeatureDefault; + this.featurePrivileges = Utils.immutableMapOf(featurePrivileges); + } + + public String getUserId() { + return userId; + } + + public Boolean getMissingFeatureDefault() { + return missingFeatureDefault; + } + + public Map getFeaturePrivileges() { + return featurePrivileges; + } + + public boolean isFeatureEnabled(final Feature feature) { + return featurePrivileges.getOrDefault(feature.featureName, missingFeatureDefault); + } + + public boolean isFeatureEnabled(final String featureName) { + return featurePrivileges.getOrDefault(featureName, missingFeatureDefault); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiServiceInfo.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiServiceInfo.java index d1dbfd89..48a46e95 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/GuiServiceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiServiceInfo.java @@ -8,9 +8,6 @@ package ch.ethz.seb.sebserver.gui; -import static ch.ethz.seb.sebserver.gbl.FeatureService.ConfigurableFeature.LIGHT_SETUP; - -import ch.ethz.seb.sebserver.gbl.FeatureService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -35,10 +32,7 @@ public class GuiServiceInfo { private final boolean distributedSetup; private final boolean multilingualGUI; - public final FeatureService featureService; - public GuiServiceInfo( - final FeatureService featureService, @Value("${sebserver.version:--}") final String sebServerVersion, @Value("${server.address}") final String internalServer, @Value("${server.port}") final String internalPort, @@ -50,7 +44,6 @@ public class GuiServiceInfo { @Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup, @Value("${sebserver.gui.multilingual:false}") final boolean multilingualGUI) { - this.featureService = featureService; if (StringUtils.isBlank(externalScheme)) { throw new RuntimeException("Missing mandatory inital parameter sebserver.gui.http.external.servername"); } @@ -88,10 +81,6 @@ public class GuiServiceInfo { this.multilingualGUI = multilingualGUI; } - public boolean isLightSetup() { - return this.featureService.isEnabled(LIGHT_SETUP); - } - public String getExternalScheme() { return this.externalScheme; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java index 880cfab7..fa8c9ed0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java @@ -8,12 +8,11 @@ package ch.ethz.seb.sebserver.gui.content.exam; -import static ch.ethz.seb.sebserver.gbl.FeatureService.ConfigurableFeature.SCREEN_PROCTORING; +import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.SCREEN_PROCTORING; import java.util.*; import java.util.function.Function; -import ch.ethz.seb.sebserver.gbl.FeatureService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.*; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -124,7 +123,6 @@ public class ExamForm implements TemplateComposer { private final ExamIndicatorsList examIndicatorsList; private final ExamClientGroupList examClientGroupList; private final ExamCreateClientConfigPopup examCreateClientConfigPopup; - private final FeatureService featureService; protected ExamForm( final PageService pageService, @@ -137,8 +135,7 @@ public class ExamForm implements TemplateComposer { final ExamFormConfigs examFormConfigs, final ExamIndicatorsList examIndicatorsList, final ExamClientGroupList examClientGroupList, - final ExamCreateClientConfigPopup examCreateClientConfigPopup, - final FeatureService featureService) { + final ExamCreateClientConfigPopup examCreateClientConfigPopup) { this.pageService = pageService; this.resourceService = pageService.getResourceService(); @@ -152,7 +149,6 @@ public class ExamForm implements TemplateComposer { this.examIndicatorsList = examIndicatorsList; this.examClientGroupList = examClientGroupList; this.examCreateClientConfigPopup = examCreateClientConfigPopup; - this.featureService = featureService; this.consistencyMessageMapping = new HashMap<>(); this.consistencyMessageMapping.put( @@ -255,7 +251,7 @@ public class ExamForm implements TemplateComposer { .map(ProctoringServiceSettings::getEnableProctoring) .getOr(false); - final boolean spsFeatureEnabled = this.featureService.isEnabled(SCREEN_PROCTORING); + final boolean spsFeatureEnabled = currentUser.isFeatureEnabled(SCREEN_PROCTORING); final boolean screenProctoringEnabled = readonly && spsFeatureEnabled && this.restService .getBuilder(GetScreenProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java index 4c3f7550..d8c3c077 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamList.java @@ -8,14 +8,13 @@ package ch.ethz.seb.sebserver.gui.content.exam; -import static ch.ethz.seb.sebserver.gbl.FeatureService.ConfigurableFeature.EXAM_NO_LMS; +import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.EXAM_NO_LMS; import static ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys.NEW_EXAM_NO_LMS; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; import java.util.function.Function; -import ch.ethz.seb.sebserver.gbl.FeatureService; import org.apache.commons.lang3.BooleanUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.widgets.Composite; @@ -149,7 +148,6 @@ public class ExamList implements TemplateComposer { final CurrentUser currentUser = this.resourceService.getCurrentUser(); final RestService restService = this.resourceService.getRestService(); final I18nSupport i18nSupport = this.resourceService.getI18nSupport(); - final FeatureService featureService = this.pageService.getFeatureService(); // content page layout with title final Composite content = widgetFactory.defaultPageLayout( @@ -286,7 +284,7 @@ public class ExamList implements TemplateComposer { .newAction(ActionDefinition.EXAM_NEW) .withAttribute(NEW_EXAM_NO_LMS, Constants.TRUE_STRING) - .publishIf(() -> userGrant.iw() && featureService.isEnabled(EXAM_NO_LMS)) + .publishIf(() -> userGrant.iw() && currentUser.isFeatureEnabled(EXAM_NO_LMS)) ; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index a9b34818..c6252619 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -8,8 +8,6 @@ package ch.ethz.seb.sebserver.gui.content.monitoring; -import static ch.ethz.seb.sebserver.gbl.FeatureService.ConfigurableFeature.SCREEN_PROCTORING; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -20,7 +18,6 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; -import ch.ethz.seb.sebserver.gbl.FeatureService; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.swt.SWT; @@ -112,7 +109,6 @@ public class MonitoringRunningExam implements TemplateComposer { private final MonitoringExamSearchPopup monitoringExamSearchPopup; private final SEBSendLockPopup sebSendLockPopup; private final MonitoringProctoringService monitoringProctoringService; - private final FeatureService featureService; private final boolean distributedSetup; private final long pollInterval; @@ -125,7 +121,6 @@ public class MonitoringRunningExam implements TemplateComposer { final SEBSendLockPopup sebSendLockPopup, final MonitoringProctoringService monitoringProctoringService, final GuiServiceInfo guiServiceInfo, - final FeatureService featureService, @Value("${sebserver.gui.webservice.poll-interval:2000}") final long pollInterval) { this.serverPushService = serverPushService; @@ -139,7 +134,6 @@ public class MonitoringRunningExam implements TemplateComposer { this.distributedSetup = guiServiceInfo.isDistributedSetup(); this.monitoringExamSearchPopup = monitoringExamSearchPopup; this.sebSendLockPopup = sebSendLockPopup; - this.featureService = featureService; } @Override @@ -321,9 +315,7 @@ public class MonitoringRunningExam implements TemplateComposer { final PageActionBuilder actionBuilder = this.pageService .pageActionBuilder(pageContext.clearEntityKeys()); - final boolean spsFeatureEnabled = this.featureService.isEnabled(SCREEN_PROCTORING); - final boolean proctoringEnabled = spsFeatureEnabled && - proctoringSettings != null && + final boolean proctoringEnabled = proctoringSettings != null && BooleanUtils.toBoolean(proctoringSettings.enableProctoring); final boolean screenProctoringEnabled = screenProctoringSettings != null && BooleanUtils.toBoolean(screenProctoringSettings.enableScreenProctoring); 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 ebd24a05..546ace1a 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 @@ -92,7 +92,8 @@ import ch.ethz.seb.sebserver.gui.service.session.MonitoringEntry; @Service @GuiProfile /** Defines functionality to get resources or functions of resources to feed e.g. selection or - * combo-box content. */ + * combo-box content. + * */ public class ResourceService { private static final Logger log = LoggerFactory.getLogger(ResourceService.class); @@ -139,8 +140,6 @@ public class ResourceService { public static final String EXAM_PROCTORING_FEATURES_PREFIX = "sebserver.exam.proctoring.form.features."; public static final String VDI_TYPE_PREFIX = "sebserver.clientconfig.form.vditype."; - private static final String DISABLE_LMS_FLAG = "sebserver.gui.webservice.lms.disable."; - public static final EnumSet ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of( AttributeType.LABEL, AttributeType.COMPOSITE_TABLE, @@ -159,7 +158,6 @@ public class ResourceService { private final I18nSupport i18nSupport; private final RestService restService; private final CurrentUser currentUser; - private final EnumSet disabledLmsTypes; protected ResourceService( final I18nSupport i18nSupport, @@ -170,13 +168,6 @@ public class ResourceService { this.i18nSupport = i18nSupport; this.restService = restService; this.currentUser = currentUser; - - this.disabledLmsTypes = EnumSet.noneOf(LmsType.class); - final List disabled = Arrays.asList(LmsType.values()).stream() - .filter(lmsType -> lmsType.features.isEmpty() || - environment.getProperty(DISABLE_LMS_FLAG + lmsType.name(), Boolean.class, false) == true) - .collect(Collectors.toList()); - this.disabledLmsTypes.addAll(disabled); } public I18nSupport getI18nSupport() { @@ -206,7 +197,7 @@ public class ResourceService { public List> lmsTypeResources() { return Arrays.stream(LmsType.values()) - .filter(lmsType -> !this.disabledLmsTypes.contains(lmsType)) + .filter(lmsType -> this.currentUser.isFeatureEnabled("lms.type." + lmsType.name())) .map(lmsType -> new Tuple<>( lmsType.name(), this.i18nSupport.getText(LMSSETUP_TYPE_PREFIX + lmsType.name(), lmsType.name()))) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java index da4481eb..1d4c33f1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java @@ -17,7 +17,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import ch.ethz.seb.sebserver.gbl.FeatureService; import ch.ethz.seb.sebserver.gbl.util.Cryptor; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; @@ -76,8 +75,6 @@ public interface PageService { Cryptor getCryptor(); - FeatureService getFeatureService(); - /** Get the WidgetFactory service * * @return the WidgetFactory service */ diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java index 921afff6..6db5224a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java @@ -20,7 +20,6 @@ import java.util.function.Supplier; import javax.servlet.http.HttpSession; -import ch.ethz.seb.sebserver.gbl.FeatureService; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.widgets.TreeItem; import org.slf4j.Logger; @@ -89,7 +88,6 @@ public class PageServiceImpl implements PageService { private final ResourceService resourceService; private final CurrentUser currentUser; private final ServerPushService serverPushService; - private final FeatureService featureService; public PageServiceImpl( final Cryptor cryptor, @@ -98,8 +96,7 @@ public class PageServiceImpl implements PageService { final PolyglotPageService polyglotPageService, final ResourceService resourceService, final CurrentUser currentUser, - final ServerPushService serverPushService, - final FeatureService featureService) { + final ServerPushService serverPushService) { this.cryptor = cryptor; this.jsonMapper = jsonMapper; @@ -108,7 +105,6 @@ public class PageServiceImpl implements PageService { this.resourceService = resourceService; this.currentUser = currentUser; this.serverPushService = serverPushService; - this.featureService = featureService; } @Override @@ -116,11 +112,6 @@ public class PageServiceImpl implements PageService { return this.cryptor; } - @Override - public FeatureService getFeatureService() { - return this.featureService; - } - @Override public WidgetFactory getWidgetFactory() { return this.widgetFactory; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/GetUserFeatures.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/GetUserFeatures.java new file mode 100644 index 00000000..c7dafbec --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/GetUserFeatures.java @@ -0,0 +1,29 @@ +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import com.fasterxml.jackson.core.type.TypeReference; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Lazy +@Component +@GuiProfile +public class GetUserFeatures extends RestCall { + + public GetUserFeatures() { + super(new TypeKey<>( + CallType.GET_SINGLE, + EntityType.USER, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.USER_ACCOUNT_ENDPOINT + API.CURRENT_USER_PATH_SEGMENT + API.FEATURES_PATH_SEGMENT); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java index 51b3f9d2..5ef8ad1c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; @@ -47,6 +48,8 @@ public class CurrentUser { private final AuthorizationContextHolder authorizationContextHolder; private SEBServerAuthorizationContext authContext = null; private Map privileges = null; + + private UserFeatures features = null; private final Map attributes; private final ProctoringGUIService proctoringGUIService; @@ -192,6 +195,7 @@ public class CurrentUser { this.proctoringGUIService.clear(); this.privileges = null; + this.features = null; if (isAvailable()) { if (this.authContext.logout()) { @@ -272,6 +276,42 @@ public class CurrentUser { } } + public boolean isFeatureEnabled(final UserFeatures.Feature feature) { + loadFeatures(); + return this.features != null && this.features.isFeatureEnabled(feature); + } + + public boolean isFeatureEnabled(final String featureName) { + loadFeatures(); + return this.features != null && this.features.isFeatureEnabled(featureName); + } + + private void loadFeatures() { + if (this.features != null) { + return; + } + + updateContext(); + if (this.authContext != null) { + try { + final WebserviceURIService webserviceURIService = + this.authorizationContextHolder.getWebserviceURIService(); + this.features = this.authContext.getRestTemplate() + .exchange( + webserviceURIService.getURIBuilder() + .path(API.USER_ACCOUNT_ENDPOINT + API.CURRENT_USER_PATH_SEGMENT + API.FEATURES_PATH_SEGMENT) + .toUriString(), + HttpMethod.GET, + HttpEntity.EMPTY, + UserFeatures.class) + .getBody(); + + } catch (final Exception e) { + log.error("Failed to load user feature privileges: ", e); + } + } + } + /** Wrapper can be used for base and institutional grant checks for a specified EntityType */ public class GrantCheck { private final EntityType entityType; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java index c6c3c5c9..55756f20 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInfo.java @@ -8,20 +8,14 @@ package ch.ethz.seb.sebserver.webservice; -import static ch.ethz.seb.sebserver.gbl.FeatureService.ConfigurableFeature.LIGHT_SETUP; - import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; -import ch.ethz.seb.sebserver.gbl.FeatureService; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; import ch.ethz.seb.sebserver.gbl.model.exam.SPSAPIAccessData; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.FeatureService; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -66,6 +60,7 @@ public class WebserviceInfo { private final String discoveryEndpoint; private final String contextPath; + private final boolean isLightSetup; private final String serverURLPrefix; private final boolean isDistributed; private final String webserviceUUID; @@ -76,6 +71,7 @@ public class WebserviceInfo { private final Set activeProfiles; private final WebserviceInfoDAO webserviceInfoDAO; + private final FeatureService featureService; private boolean isMaster = false; @Value("${sebserver.webservice.api.admin.accessTokenValiditySeconds:3600}") @@ -85,18 +81,16 @@ public class WebserviceInfo { @Value("${sebserver.webservice.api.exam.accessTokenValiditySeconds:43200}") private int examAPITokenValiditySeconds; - public final FeatureService featureService; - private final ScreenProctoringServiceBundle screenProctoringServiceBundle; public WebserviceInfo( final WebserviceInfoDAO webserviceInfoDAO, + final FeatureService featureService, final Environment environment, - final Cryptor cryptor, - final FeatureService featureService) { + final Cryptor cryptor) { - this.featureService = featureService; this.webserviceInfoDAO = webserviceInfoDAO; + this.featureService = featureService; this.sebServerVersion = environment.getRequiredProperty(VERSION_KEY); this.testProperty = environment.getProperty(WEB_SERVICE_TEST_PROPERTY, "NOT_AVAILABLE"); this.httpScheme = environment.getRequiredProperty(WEB_SERVICE_HTTP_SCHEME_KEY); @@ -135,6 +129,9 @@ public class WebserviceInfo { } this.serverURLPrefix = builder.toUriString(); + this.isLightSetup = BooleanUtils.toBoolean(environment.getProperty( + "sebserver.webservice.light.setup", + Constants.FALSE_STRING)); this.isDistributed = BooleanUtils.toBoolean(environment.getProperty( "sebserver.webservice.distributed", Constants.FALSE_STRING)); @@ -180,8 +177,11 @@ public class WebserviceInfo { } } - public boolean isLightSetup() { - return this.featureService.isEnabled(LIGHT_SETUP); + public Map configuredFeatures() { + return Arrays.stream(UserFeatures.Feature.values()).collect(Collectors.toMap( + f -> f.featureName, + featureService::isEnabledByConfig + )); } public boolean isMaster() { @@ -276,7 +276,7 @@ public class WebserviceInfo { /** Get the server URL prefix in the form of; * [scheme{http|https}]://[server-address{DNS-name|IP}]:[port] - * + *

* E.g.: https://seb.server.ch:8080 * * @return the server URL prefix */ @@ -284,6 +284,9 @@ public class WebserviceInfo { return this.serverURLPrefix; } + public boolean isLightSetup() { + return this.isLightSetup; + } public boolean isDistributed() { return this.isDistributed; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java index 2bfe9696..d7169516 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -121,10 +121,11 @@ public class WebserviceInit implements ApplicationListener "); - SEBServerInit.INIT_LOGGER.info("----> SEB Server light setup enabled"); - } + SEBServerInit.INIT_LOGGER.info("----> "); + SEBServerInit.INIT_LOGGER.info("----> Configured Features:"); + this.webserviceInfo.configuredFeatures().entrySet().stream().forEach(entry -> { + SEBServerInit.INIT_LOGGER.info("----> {} --> {}", entry.getKey(), entry.getValue()); + }); SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Working with ping service: {}", 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 55730e2b..913b0ad1 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 @@ -18,13 +18,14 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.model.GrantEntity; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; /** A service to check authorization grants for a given user for entity-types and -instances - * + *

* If there is one or more GrantEntity objects within an authenticated user-request, this service * can be used check the authenticated user access grant within the object. Check if a given user * has write, modify or even read rights on an entity instance or on an entity type. */ @@ -40,17 +41,19 @@ public interface AuthorizationService { * @return all registered Privileges */ Collection getAllPrivileges(); + + /** Check grant on privilege type for specified EntityType and for the given user and institution. - * + *

* This makes a privilege grant check for every UserRole given. The first found successful grant * will immediately return true - * + *

* The privilege grant check function always checks first the base privilege with no institutional or owner grant. * If user has a grant on base privileges this returns true without checking further institutional or owner grant * If user has no base privilege grant the function checks further grants, first the institutional grant, where * the institution id and the users institution id must match and further more the owner grant, where ownerId * and the users id must match. - * + *

* see Privilege.hasGrant for more information how the overall grant function works * * @param privilegeType The privilege type to check @@ -71,7 +74,7 @@ public interface AuthorizationService { Set userRoles); /** Check grant for a given privilege type and entity type for the current user. - * + *

* NOTE: This only checks the base privilege grant because there is no Entity specific information * * @param privilegeType the privilege type to check @@ -109,11 +112,11 @@ public interface AuthorizationService { /** Check base privilege grant and institutional privilege grant for a given privilege type * on a given entity type. - * + *

* If the question is similar like this: * "Has the current user that belongs to institution A the right to create an entity of * type X on institution B", then this is the answer, use: - * + *

* hasPrivilege(PrivilegeType.WRITE, EntityType.X, B) * * @param privilegeType the privilege type to check diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/FeatureService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/FeatureService.java new file mode 100644 index 00000000..3f1ccca1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/FeatureService.java @@ -0,0 +1,21 @@ +package ch.ethz.seb.sebserver.webservice.servicelayer.authorization; + +import java.util.Map; + +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.gbl.util.Result; + +public interface FeatureService { + + public static final String FEATURE_CONFIG_PREFIX = "sebserver.feature."; + + /** Get all feature flags for current user. + * + * @return UserFeatures all feature flags for current user */ + Result getCurrentUserFeatures(); + + Map getUserRoleDefaults(UserRole role); + + boolean isEnabledByConfig(final UserFeatures.Feature feature); +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java index 14ad2f4d..99ab9543 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/UserService.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization; import java.security.Principal; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; import org.springframework.security.core.Authentication; import org.springframework.web.bind.WebDataBinder; @@ -29,10 +30,10 @@ public interface UserService { SEBServerUser getCurrentUser(); /** Extracts the internal SEBServerUser from a given Principal. - * + *

* This is attended to apply some known strategies to extract the internal user from Principal. If there is no * internal user found on the given Principal, a IllegalArgumentException is thrown. - * + *

* If there is certainly a internal user within the given Principal but no strategy that finds it, this method can * be extended with the needed strategy. * @@ -54,7 +55,7 @@ public interface UserService { /** Used to set authentication on different thread. * - * @param authentication */ + * @param authentication the Authentication context*/ void setAuthenticationIfAbsent(Authentication authentication); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/FeatureServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/FeatureServiceImpl.java new file mode 100644 index 00000000..80d50f29 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/FeatureServiceImpl.java @@ -0,0 +1,82 @@ +package ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl; + +import java.util.*; +import java.util.stream.Collectors; + +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.FeatureService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +@Lazy +@Service +@WebServiceProfile +public class FeatureServiceImpl implements FeatureService { + + private final Environment environment; + private final UserService userService; + private final UserDAO userDAO; + + public FeatureServiceImpl( + final Environment environment, + final UserService userService, + final UserDAO userDAO) { + + this.environment = environment; + this.userService = userService; + this.userDAO = userDAO; + } + + + @Override + public Result getCurrentUserFeatures() { + return Result.tryCatch(() -> { + final String userId = userService.getCurrentUser().getUserInfo().uuid; + final EnumSet userEnabledFeatures = getUserEnabledFeatures(userId); + final Map features = Arrays.stream(Feature.values()).collect(Collectors.toMap( + f -> f.featureName, + f -> isEnabledByConfig(f) && userEnabledFeatures.contains(f) + )); + + return new UserFeatures(userId, true, features); + }); + } + + @Override + public Map getUserRoleDefaults(final UserRole role) { + // TODO implement this when user role based features are available + return Collections.emptyMap(); + } + + public EnumSet getUserEnabledFeatures(final String userId) { + // TODO implement this when user role based features are available + return EnumSet.allOf(Feature.class); + } + + @Override + public boolean isEnabledByConfig(final Feature feature) { + final String configName = getConfigName(feature); + try { + return this.environment.getProperty(configName, Boolean.class, false) + || this.environment.getProperty(configName + ".enabled", Boolean.class); + } catch (final Exception e) { + // NOTE: for now if there is not explicitly disabled from config, the feature is considered enabled + return true; + } + } + + private String getConfigName(final Feature feature) { + return getConfigName(feature.featureName); + } + + private String getConfigName(final String featureName) { + return FEATURE_CONFIG_PREFIX + featureName; + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java index 8fbbab22..da0a974d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java @@ -16,7 +16,9 @@ import java.util.List; import javax.validation.Valid; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; +import ch.ethz.seb.sebserver.gbl.model.user.*; import ch.ethz.seb.sebserver.gbl.util.Pair; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.FeatureService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.beans.factory.annotation.Qualifier; @@ -38,12 +40,6 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; -import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; -import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; -import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; -import ch.ethz.seb.sebserver.gbl.model.user.UserMod; -import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport; @@ -66,6 +62,8 @@ public class UserAccountController extends ActivatableEntityController