From b74c711ebb2c72b0b1d11fa3f058e96032eca8ad Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 7 Mar 2019 12:10:48 +0100 Subject: [PATCH] SEBSERV-30 LmsSetup list and form; fixed tests and code cleanup --- .../ch/ethz/seb/sebserver/gbl/Constants.java | 2 + .../ch/ethz/seb/sebserver/gbl/api/API.java | 4 + .../gbl/model/institution/Institution.java | 4 + .../gbl/model/institution/LmsSetup.java | 10 +- .../seb/sebserver/gbl/model/user/UserMod.java | 34 +--- .../seb/sebserver/gui/RAPConfiguration.java | 15 ++ .../gui/content/InstitutionForm.java | 6 +- .../gui/content/InstitutionList.java | 5 +- .../sebserver/gui/content/LmsSetupForm.java | 191 +++++++++++++++++- .../sebserver/gui/content/LmsSetupList.java | 22 +- .../gui/content/UserAccountForm.java | 29 ++- .../gui/content/UserAccountList.java | 5 +- .../gui/content/action/ActionDefinition.java | 117 ++++++++--- .../seb/sebserver/gui/form/FormBuilder.java | 5 +- .../gui/form/SelectionFieldBuilder.java | 4 +- .../service/i18n/impl/I18nSupportImpl.java | 37 +++- .../service/page/PageMessageException.java | 20 ++ .../gui/service/page/action/Action.java | 48 ++--- .../service/page/impl/DefaultPageLayout.java | 13 +- .../service/page/impl/PageContextImpl.java | 2 +- .../gui/service/remote/DownloadService.java | 73 +++++++ .../remote/DownloadServiceHandler.java | 20 ++ .../remote/SebClientConfigDownload.java | 109 ++++++++++ .../remote/webservice/api/RestCall.java | 13 +- .../remote/webservice/api/RestService.java | 17 ++ .../api/lmssetup/ActivateLmsSetup.java | 37 ++++ .../api/lmssetup/DeactivateLmsSetup.java | 37 ++++ .../api/lmssetup/ExportSEBConfig.java | 62 ++++++ .../webservice/api/lmssetup/NewLmsSetup.java | 37 ++++ .../webservice/api/lmssetup/SaveLmsSetup.java | 36 ++++ .../sebserver/gui/table/TableNavigator.java | 10 +- .../sebserver/gui/widget/WidgetFactory.java | 1 + .../sebserver/webservice/WebServiceInit.java | 24 +++ .../dao/impl/LmsSetupDAOImpl.java | 14 +- .../lms/impl/LmsAPIServiceImpl.java | 10 +- ...fig.java => WebServiceSecurityConfig.java} | 13 +- .../api/ActivatableEntityController.java | 6 +- .../weblayer/api/EntityController.java | 12 +- .../weblayer/api/LmsSetupController.java | 88 ++++++-- .../weblayer/api/UserAccountController.java | 66 +++--- .../weblayer/oauth/AdminAPIClientDetails.java | 2 +- .../oauth/AuthorizationServerConfig.java | 4 +- .../oauth/WebClientDetailsService.java | 2 +- ...a => WebserviceResourceConfiguration.java} | 4 +- .../config/application-dev-gui.properties | 2 + src/main/resources/messages.properties | 26 ++- .../swt/internal/SWTMessages_de.properties | 7 + .../swt/internal/SWTMessages_en.properties | 7 + src/main/resources/schema-demo.sql | 4 +- src/main/resources/schema-dev.sql | 4 +- src/main/resources/static/css/sebserver.css | 17 +- .../model/institution/InstitutionTest.java | 51 +---- .../gbl/model/user/UserInfoTest.java | 46 +---- .../api/admin/LmsSetupAPITest.java | 75 +++++-- src/test/resources/schema-test.sql | 4 +- 55 files changed, 1196 insertions(+), 317 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ActivateLmsSetup.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/DeactivateLmsSetup.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ExportSEBConfig.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/NewLmsSetup.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/SaveLmsSetup.java rename src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/{ClientSessionWebSecurityConfig.java => WebServiceSecurityConfig.java} (92%) rename src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/{WebResourceServerConfiguration.java => WebserviceResourceConfiguration.java} (95%) create mode 100644 src/main/resources/org/eclipse/swt/internal/SWTMessages_de.properties create mode 100644 src/main/resources/org/eclipse/swt/internal/SWTMessages_en.properties diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 23360ad3..6b873f8e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -20,6 +20,8 @@ public final class Constants { public static final String FORM_URL_ENCODED_SEPARATOR = "&"; public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "="; + public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + /** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */ public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss") 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 2ba1e9d3..ece72450 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 @@ -37,6 +37,10 @@ public final class API { public static final String INSTITUTION_ENDPOINT = "/institution"; public static final String LMS_SETUP_ENDPOINT = "/lms_setup"; + public static final String SEB_CONFIG_EXPORT_PATH_SEGMENT = "/sebconfig"; + public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test"; + public static final String SEB_CONFIG_EXPORT_ENDPOINT = LMS_SETUP_ENDPOINT + "/sebconfig"; + public static final String LMS_SETUP_TEST_ENDPOINT = LMS_SETUP_ENDPOINT + "/test"; public static final String USER_ACCOUNT_ENDPOINT = "/useraccount"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java index 8765707b..f40a8ec5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/Institution.java @@ -117,4 +117,8 @@ public final class Institution implements GrantEntity, Activatable { + ", active=" + this.active + "]"; } + public static Institution createNew() { + return new Institution(null, null, null, null, false); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java index 1dfe1d89..57070518 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java @@ -50,11 +50,11 @@ public final class LmsSetup implements GrantEntity, Activatable { public final LmsType lmsType; @JsonProperty(LMS_SETUP.ATTR_LMS_CLIENTNAME) - @Size(min = 3, max = 255, message = "lmsSetup:lmsAuthName:size:{min}:{max}:${validatedValue}") + @Size(min = 3, max = 255, message = "lmsSetup:lmsClientname:size:{min}:{max}:${validatedValue}") public final String lmsAuthName; @JsonProperty(LMS_SETUP.ATTR_LMS_CLIENTSECRET) - @Size(min = 8, max = 255, message = "lmsSetup:lmsAuthSecret:size:{min}:{max}:${validatedValue}") + @Size(min = 8, max = 255, message = "lmsSetup:lmsClientsecret:size:{min}:{max}:${validatedValue}") public final String lmsAuthSecret; @JsonProperty(LMS_SETUP.ATTR_LMS_URL) @@ -64,11 +64,9 @@ public final class LmsSetup implements GrantEntity, Activatable { public final String lmsRestApiToken; @JsonProperty(LMS_SETUP.ATTR_SEB_CLIENTNAME) - @Size(min = 3, max = 255, message = "lmsSetup:sebAuthName:size:{min}:{max}:${validatedValue}") public final String sebAuthName; @JsonProperty(LMS_SETUP.ATTR_SEB_CLIENTSECRET) - @Size(min = 8, max = 255, message = "lmsSetup:sebAuthSecret:size:{min}:{max}:${validatedValue}") public final String sebAuthSecret; /** Indicates whether this LmsSetup is active or not */ @@ -203,4 +201,8 @@ public final class LmsSetup implements GrantEntity, Activatable { lmsSetup.name); } + public static LmsSetup createNew(final Long institutionId) { + return new LmsSetup(null, institutionId, null, null, null, null, null, null, null, null, false); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java index 0c682e01..496ede86 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Locale; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import javax.validation.constraints.Email; @@ -112,19 +113,6 @@ public final class UserMod implements UserAccount { : Collections.emptySet(); } - public UserMod(final UserInfo userInfo, final String newPassword, final String confirmNewPassword) { - this.uuid = userInfo.uuid; - this.institutionId = userInfo.institutionId; - this.newPassword = newPassword; - this.confirmNewPassword = confirmNewPassword; - this.name = userInfo.name; - this.username = userInfo.username; - this.email = userInfo.email; - this.language = userInfo.language; - this.timeZone = userInfo.timeZone; - this.roles = userInfo.roles; - } - public UserMod(final String modelId, final POSTMapper postAttrMapper) { this.uuid = modelId; this.institutionId = postAttrMapper.getLong(USER.ATTR_INSTITUTION_ID); @@ -138,19 +126,6 @@ public final class UserMod implements UserAccount { this.roles = postAttrMapper.getStringSet(USER_ROLE.REFERENCE_NAME); } - public UserMod(final String modelId, final Long institutionId) { - this.uuid = modelId; - this.institutionId = institutionId; - this.newPassword = null; - this.confirmNewPassword = null; - this.name = null; - this.username = null; - this.email = null; - this.language = Locale.ENGLISH; - this.timeZone = DateTimeZone.UTC; - this.roles = Collections.emptySet(); - } - @Override public String getModelId() { return this.uuid; @@ -257,4 +232,11 @@ public final class UserMod implements UserAccount { + ", newPassword=" + this.newPassword + ", retypedNewPassword=" + this.confirmNewPassword + "]"; } + public static UserMod createNew(final Long institutionId) { + return new UserMod( + UUID.randomUUID().toString(), + institutionId, + null, null, null, null, null, null, null, null); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java index 9265e383..619d03d2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/RAPConfiguration.java @@ -21,12 +21,14 @@ import org.eclipse.rap.rwt.application.ApplicationConfiguration; import org.eclipse.rap.rwt.application.EntryPoint; import org.eclipse.rap.rwt.application.EntryPointFactory; import org.eclipse.rap.rwt.client.WebClient; +import org.eclipse.rap.rwt.service.ServiceManager; import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; +import ch.ethz.seb.sebserver.gui.service.remote.DownloadService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext; @@ -63,6 +65,8 @@ public class RAPConfiguration implements ApplicationConfiguration { private static final EntryPointFactory RAPSpringEntryPointFactory = new EntryPointFactory() { + private boolean serviceInistialized = false; + @Override public EntryPoint create() { return new AbstractEntryPoint() { @@ -83,6 +87,7 @@ public class RAPConfiguration implements ApplicationConfiguration { } final WebApplicationContext webApplicationContext = getWebApplicationContext(httpSession); + initSpringBasedRAPServices(webApplicationContext); final EntryPointService entryPointService = webApplicationContext .getBean(EntryPointService.class); @@ -93,9 +98,19 @@ public class RAPConfiguration implements ApplicationConfiguration { entryPointService.loadLoginPage(parent); } } + }; } + private void initSpringBasedRAPServices(final WebApplicationContext webApplicationContext) { + if (!this.serviceInistialized) { + final ServiceManager manager = RWT.getServiceManager(); + final DownloadService downloadService = webApplicationContext.getBean(DownloadService.class); + manager.registerServiceHandler(DownloadService.DOWNLOAD_SERVICE_NAME, downloadService); + this.serviceInistialized = true; + } + } + private boolean isAuthenticated( final HttpSession httpSession, final WebApplicationContext webApplicationContext) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java index 46c446ab..67afa25e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java @@ -66,7 +66,7 @@ public class InstitutionForm implements TemplateComposer { final boolean isNew = entityKey == null; // get data or create new. Handle error if happen final Institution institution = (entityKey == null) - ? new Institution(null, null, null, null, false) + ? Institution.createNew() : this.restService .getBuilder(GetInstitution.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) @@ -143,13 +143,13 @@ public class InstitutionForm implements TemplateComposer { .createAction(ActionDefinition.INSTITUTION_DEACTIVATE) .withEntityKey(entityKey) - .withExec(Action.activation(this.restService, false)) + .withExec(this.restService::activation) .withConfirm(PageUtils.confirmDeactivation(institution, this.restService)) .publishIf(() -> writeGrant && isReadonly && institution.isActive()) .createAction(ActionDefinition.INSTITUTION_ACTIVATE) .withEntityKey(entityKey) - .withExec(Action.activation(this.restService, true)) + .withExec(this.restService::activation) .publishIf(() -> writeGrant && isReadonly && !institution.isActive()) .createAction(ActionDefinition.INSTITUTION_SAVE) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java index a281abbf..20c178ca 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java @@ -79,6 +79,7 @@ public class InstitutionList implements TemplateComposer { // propagate content actions to action-pane final GrantCheck instGrant = this.currentUser.grantCheck(EntityType.INSTITUTION); final GrantCheck userGrant = this.currentUser.grantCheck(EntityType.USER); + final LocTextKey emptySelectionText = new LocTextKey("sebserver.institution.info.pleaseSelect"); pageContext.clearEntityKeys() .createAction(ActionDefinition.INSTITUTION_NEW) @@ -88,11 +89,11 @@ public class InstitutionList implements TemplateComposer { .publishIf(userGrant::w) .createAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) - .withSelect(table::getSelection, Action.applySingleSelection("sebserver.institution.info.pleaseSelect")) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) .publishIf(() -> table.hasAnyContent()) .createAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST) - .withSelect(table::getSelection, Action.applySingleSelection("sebserver.institution.info.pleaseSelect")) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) .publishIf(() -> instGrant.m() && table.hasAnyContent()); ; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java index 9f949675..36f7418b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java @@ -8,34 +8,221 @@ package ch.ethz.seb.sebserver.gui.content; +import java.util.function.BooleanSupplier; + +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.service.UrlLauncher; +import org.eclipse.swt.widgets.Composite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +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.user.UserInfo; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.form.PageFormService; import ch.ethz.seb.sebserver.gui.service.ResourceService; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageUtils; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; +import ch.ethz.seb.sebserver.gui.service.remote.SebClientConfigDownload; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.NewLmsSetup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.SaveLmsSetup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Lazy @Component @GuiProfile public class LmsSetupForm implements TemplateComposer { + private static final Logger log = LoggerFactory.getLogger(LmsSetupForm.class); + private final PageFormService pageFormService; private final ResourceService resourceService; + private final SebClientConfigDownload sebClientConfigDownload; protected LmsSetupForm( final PageFormService pageFormService, - final ResourceService resourceService) { + final ResourceService resourceService, + final SebClientConfigDownload sebClientConfigDownload) { this.pageFormService = pageFormService; this.resourceService = resourceService; + this.sebClientConfigDownload = sebClientConfigDownload; } @Override public void compose(final PageContext pageContext) { - // TODO Auto-generated method stub + final CurrentUser currentUser = this.resourceService.getCurrentUser(); + final RestService restService = this.resourceService.getRestService(); + final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); + + final UserInfo user = currentUser.get(); + final EntityKey entityKey = pageContext.getEntityKey(); + final EntityKey parentEntityKey = pageContext.getParentEntityKey(); + final boolean readonly = pageContext.isReadonly(); + + final BooleanSupplier isNew = () -> entityKey == null; + final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean(); + final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN); + + // get data or create new. handle error if happen + final LmsSetup lmsSetup = isNew.getAsBoolean() + ? LmsSetup.createNew((parentEntityKey != null) + ? Long.valueOf(parentEntityKey.modelId) + : user.institutionId) + : restService + .getBuilder(GetLmsSetup.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .get(pageContext::notifyError); + + if (lmsSetup == null) { + log.error( + "Failed to get LmsSetup. " + + "Error was notified to the User. " + + "See previous logs for more infomation"); + return; + } + + // new PageContext with actual EntityKey + final PageContext formContext = pageContext.withEntityKey(lmsSetup.getEntityKey()); + // the default page layout with title + final LocTextKey titleKey = new LocTextKey( + isNotNew.getAsBoolean() + ? "sebserver.lmssetup.form.title" + : "sebserver.lmssetup.form.title.new"); + final Composite content = widgetFactory.defaultPageLayout( + formContext.getParent(), + titleKey); + + final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup); + final boolean writeGrant = userGrantCheck.w(); + final boolean modifyGrant = userGrantCheck.m(); + final boolean istitutionActive = restService.getBuilder(GetInstitution.class) + .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId())) + .call() + .map(inst -> inst.active) + .getOr(false); + + // The UserAccount form + final LmsType lmsType = lmsSetup.getLmsType(); + final FormHandle formHandle = this.pageFormService.getBuilder( + formContext.copyOf(content), 4) + .readonly(readonly) + .putStaticValueIf(isNotNew, + Domain.LMS_SETUP.ATTR_ID, + lmsSetup.getModelId()) + .putStaticValueIf(isNotNew, + Domain.LMS_SETUP.ATTR_INSTITUTION_ID, + String.valueOf(lmsSetup.getInstitutionId())) + .addField(FormBuilder.singleSelection( + Domain.LMS_SETUP.ATTR_INSTITUTION_ID, + "sebserver.lmssetup.form.institution", + String.valueOf(lmsSetup.getInstitutionId()), + () -> this.resourceService.institutionResource()) + .withCondition(isSEBAdmin) + .readonlyIf(isNotNew)) + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_NAME, + "sebserver.lmssetup.form.name", + lmsSetup.getName())) + .addField(FormBuilder.singleSelection( + Domain.LMS_SETUP.ATTR_LMS_TYPE, + "sebserver.lmssetup.form.type", + (lmsType != null) ? lmsType.name() : null, + this.resourceService::lmsTypeResources) + .readonlyIf(isNotNew)) + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, + "sebserver.lmssetup.form.clientname.seb", + lmsSetup.getSebAuthName()) + .readonlyIf(isNotNew)) + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, + "sebserver.lmssetup.form.secret.seb") + .asPasswordField() + .withCondition(isNew)) + + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_LMS_URL, + "sebserver.lmssetup.form.url", + lmsSetup.getLmsApiUrl()) + .withCondition(() -> isNotNew.getAsBoolean() && lmsType != LmsType.MOCKUP)) + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_LMS_CLIENTNAME, + "sebserver.lmssetup.form.clientname.lms", + lmsSetup.getLmsAuthName()) + .withCondition(() -> isNotNew.getAsBoolean() && lmsType != LmsType.MOCKUP)) + .addField(FormBuilder.text( + Domain.LMS_SETUP.ATTR_LMS_CLIENTSECRET, + "sebserver.lmssetup.form.secret.lms") + .asPasswordField() + .withCondition(() -> isNotNew.getAsBoolean() && lmsType != LmsType.MOCKUP)) + + .buildFor((entityKey == null) + ? restService.getRestCall(NewLmsSetup.class) + : restService.getRestCall(SaveLmsSetup.class)); + + ; + + // propagate content actions to action-pane + final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); + formContext.clearEntityKeys() + + .createAction(ActionDefinition.LMS_SETUP_NEW) + .publishIf(() -> writeGrant && readonly && istitutionActive) + + .createAction(ActionDefinition.LMS_SETUP_MODIFY) + .withEntityKey(entityKey) + .publishIf(() -> modifyGrant && readonly && istitutionActive) + + .createAction(ActionDefinition.LMS_SETUP_EXPORT_SEB_CONFIG) + .withEntityKey(entityKey) + .withExec(action -> { + final String downloadURL = this.sebClientConfigDownload.downloadSEBClientConfigURL( + entityKey.modelId); + urlLauncher.openURL(downloadURL); + return action; + }) + .publishIf(() -> writeGrant && readonly && lmsSetup.isActive()) + + .createAction(ActionDefinition.LMS_SETUP_DEACTIVATE) + .withEntityKey(entityKey) + .withExec(restService::activation) + .withConfirm(PageUtils.confirmDeactivation(lmsSetup, restService)) + .publishIf(() -> writeGrant && readonly && istitutionActive && lmsSetup.isActive()) + + .createAction(ActionDefinition.LMS_SETUP_ACTIVATE) + .withEntityKey(entityKey) + .withExec(restService::activation) + .publishIf(() -> writeGrant && readonly && istitutionActive && !lmsSetup.isActive()) + + .createAction(ActionDefinition.LMS_SETUP_SAVE) + .withExec(formHandle::postChanges) + .publishIf(() -> !readonly) + + .createAction(ActionDefinition.LMS_SETUP_CANCEL_MODIFY) + .withEntityKey(entityKey) + .withExec(Action::onEmptyEntityKeyGoToActivityHome) + .withConfirm("sebserver.overall.action.modify.cancel.confirm") + .publishIf(() -> !readonly); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java index b497da96..cb2eb212 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java @@ -16,18 +16,21 @@ 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.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.service.ResourceService; -import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetups; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; import ch.ethz.seb.sebserver.gui.table.EntityTable; @@ -57,7 +60,6 @@ public class LmsSetupList implements TemplateComposer { public void compose(final PageContext pageContext) { final CurrentUser currentUser = this.resourceService.getCurrentUser(); final RestService restService = this.resourceService.getRestService(); - final I18nSupport i18nSupport = this.widgetFactory.getI18nSupport(); // content page layout with title final Composite content = this.widgetFactory.defaultPageLayout( @@ -107,6 +109,22 @@ public class LmsSetupList implements TemplateComposer { true)) .compose(content); + // propagate content actions to action-pane + final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP); + final LocTextKey emptySelectionText = new LocTextKey("sebserver.lmssetup.info.pleaseSelect"); + pageContext.clearEntityKeys() + + .createAction(ActionDefinition.LMS_SETUP_NEW) + .publishIf(userGrant::w) + + .createAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) + .publishIf(() -> table.hasAnyContent()) + + .createAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) + .publishIf(() -> userGrant.m() && table.hasAnyContent()); + } private String lmsSetupTypeName(final LmsSetup lmsSetup) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java index 36cdfc35..ebe86cd6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.gui.content; -import java.util.UUID; import java.util.function.BooleanSupplier; import org.apache.tomcat.util.buf.StringUtils; @@ -73,20 +72,19 @@ public class UserAccountForm implements TemplateComposer { final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); final UserInfo user = currentUser.get(); - final EntityKey entityKey = pageContext.getEntityKey(); final EntityKey parentEntityKey = pageContext.getParentEntityKey(); + final boolean readonly = pageContext.isReadonly(); + final BooleanSupplier isNew = () -> entityKey == null; final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean(); final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN); - final boolean readonly = pageContext.isReadonly(); + // get data or create new. handle error if happen final UserAccount userAccount = isNew.getAsBoolean() - ? new UserMod( - UUID.randomUUID().toString(), - (parentEntityKey != null) - ? Long.valueOf(parentEntityKey.modelId) - : user.institutionId) + ? UserMod.createNew((parentEntityKey != null) + ? Long.valueOf(parentEntityKey.modelId) + : user.institutionId) : restService .getBuilder(GetUserAccount.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) @@ -101,20 +99,19 @@ public class UserAccountForm implements TemplateComposer { return; } + // new PageContext with actual EntityKey + final PageContext formContext = pageContext.withEntityKey(userAccount.getEntityKey()); + final boolean ownAccount = user.uuid.equals(userAccount.getModelId()); final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount); final boolean writeGrant = userGrantCheck.w(); final boolean modifyGrant = userGrantCheck.m(); - // modifying an UserAccount is not possible if the root institution is inactive final boolean istitutionActive = restService.getBuilder(GetInstitution.class) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId())) .call() .map(inst -> inst.active) .getOr(false); - // new PageContext with actual EntityKey - final PageContext formContext = pageContext.withEntityKey(userAccount.getEntityKey()); - if (log.isDebugEnabled()) { log.debug("UserAccount Form for user {}", userAccount.getName()); } @@ -190,7 +187,6 @@ public class UserAccountForm implements TemplateComposer { : restService.getRestCall(SaveUserAccount.class)); // propagate content actions to action-pane - formContext.clearEntityKeys() .createAction(ActionDefinition.USER_ACCOUNT_NEW) @@ -205,12 +201,14 @@ public class UserAccountForm implements TemplateComposer { .publishIf(() -> modifyGrant && readonly && istitutionActive && userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) - .withExec(Action.activation(restService, false)) + .withEntityKey(entityKey) + .withExec(restService::activation) .withConfirm(PageUtils.confirmDeactivation(userAccount, restService)) .publishIf(() -> writeGrant && readonly && istitutionActive && userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) - .withExec(Action.activation(restService, true)) + .withEntityKey(entityKey) + .withExec(restService::activation) .publishIf(() -> writeGrant && readonly && istitutionActive && !userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_SAVE) @@ -225,6 +223,7 @@ public class UserAccountForm implements TemplateComposer { .publishIf(() -> !readonly) .createAction(ActionDefinition.USER_ACCOUNT_CANCEL_MODIFY) + .withEntityKey(entityKey) .withExec(Action::onEmptyEntityKeyGoToActivityHome) .withConfirm("sebserver.overall.action.modify.cancel.confirm") .publishIf(() -> !readonly); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java index a4b4aac9..3768b77f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java @@ -119,17 +119,18 @@ public class UserAccountList implements TemplateComposer { // propagate content actions to action-pane final GrantCheck userGrant = currentUser.grantCheck(EntityType.USER); + final LocTextKey emptySelectionText = new LocTextKey("sebserver.useraccount.info.pleaseSelect"); pageContext.clearEntityKeys() .createAction(ActionDefinition.USER_ACCOUNT_NEW) .publishIf(userGrant::w) .createAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST) - .withSelect(table::getSelection, Action.applySingleSelection("sebserver.useraccount.info.pleaseSelect")) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) .publishIf(() -> table.hasAnyContent()) .createAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST) - .withSelect(table::getSelection, Action.applySingleSelection("sebserver.useraccount.info.pleaseSelect")) + .withSelect(table::getSelection, Action::applySingleSelection, emptySelectionText) .publishIf(() -> userGrant.m() && table.hasAnyContent()); } 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 75a96a7d..b689c38a 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 @@ -17,6 +17,13 @@ import ch.ethz.seb.sebserver.gui.content.UserAccountForm; import ch.ethz.seb.sebserver.gui.content.UserAccountList; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeactivateInstitution; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ActivateLmsSetup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeactivateLmsSetup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ActivateUserAccount; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.DeactivateUserAccount; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; /** Enumeration of static action data for each action within the SEB Server GUI */ @@ -64,11 +71,13 @@ public enum ActionDefinition { new LocTextKey("sebserver.institution.action.activate"), ImageIcon.INACTIVE, InstitutionForm.class, + ActivateInstitution.class, INSTITUTION_VIEW_LIST), INSTITUTION_DEACTIVATE( new LocTextKey("sebserver.institution.action.deactivate"), ImageIcon.ACTIVE, InstitutionForm.class, + DeactivateInstitution.class, INSTITUTION_VIEW_LIST), INSTITUTION_DELETE( new LocTextKey("sebserver.institution.action.modify"), @@ -117,11 +126,13 @@ public enum ActionDefinition { new LocTextKey("sebserver.useraccount.action.activate"), ImageIcon.INACTIVE, UserAccountForm.class, + ActivateUserAccount.class, USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_DEACTIVATE( new LocTextKey("sebserver.useraccount.action.deactivate"), ImageIcon.ACTIVE, UserAccountForm.class, + DeactivateUserAccount.class, USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_DELETE( new LocTextKey("sebserver.useraccount.action.modify"), @@ -140,18 +151,66 @@ public enum ActionDefinition { USER_ACCOUNT_VIEW_LIST), LMS_SETUP_VIEW_LIST( - new LocTextKey("sebserver.lmssetup.list.title"), + new LocTextKey("sebserver.lmssetup.action.list"), LmsSetupList.class), LMS_SETUP_VIEW_FORM( - new LocTextKey("sebserver.useraccount.action.form"), + new LocTextKey("sebserver.lmssetup.action.form"), LmsSetupForm.class, - USER_ACCOUNT_VIEW_LIST), + LMS_SETUP_VIEW_LIST), + LMS_SETUP_NEW( + new LocTextKey("sebserver.lmssetup.action.new"), + ImageIcon.NEW, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST, false), + LMS_SETUP_VIEW_FROM_LIST( + new LocTextKey("sebserver.lmssetup.action.list.view"), + ImageIcon.SHOW, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST), + LMS_SETUP_MODIFY_FROM_LIST( + new LocTextKey("sebserver.lmssetup.action.list.modify"), + ImageIcon.EDIT, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST, false), + LMS_SETUP_MODIFY( + new LocTextKey("sebserver.lmssetup.action.modify"), + ImageIcon.EDIT, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST, false), + LMS_SETUP_EXPORT_SEB_CONFIG( + new LocTextKey("sebserver.lmssetup.action.export.sebconfig"), + ImageIcon.SAVE, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST), + LMS_SETUP_CANCEL_MODIFY( + new LocTextKey("sebserver.overall.action.modify.cancel"), + ImageIcon.CANCEL, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST), + LMS_SETUP_SAVE( + new LocTextKey("sebserver.lmssetup.action.save"), + ImageIcon.SAVE, + LmsSetupForm.class, + LMS_SETUP_VIEW_LIST), + LMS_SETUP_ACTIVATE( + new LocTextKey("sebserver.lmssetup.action.activate"), + ImageIcon.INACTIVE, + LmsSetupForm.class, + ActivateLmsSetup.class, + LMS_SETUP_VIEW_LIST), + LMS_SETUP_DEACTIVATE( + new LocTextKey("sebserver.lmssetup.action.deactivate"), + ImageIcon.ACTIVE, + LmsSetupForm.class, + DeactivateLmsSetup.class, + LMS_SETUP_VIEW_LIST), ; public final LocTextKey title; public final ImageIcon icon; public final Class contentPaneComposer; public final Class actionPaneComposer; + public final Class> restCallType; public final ActionDefinition activityAlias; public final String category; public final boolean readonly; @@ -160,13 +219,7 @@ public enum ActionDefinition { final LocTextKey title, final Class contentPaneComposer) { - this.title = title; - this.icon = null; - this.contentPaneComposer = contentPaneComposer; - this.actionPaneComposer = ActionPane.class; - this.activityAlias = null; - this.category = null; - this.readonly = true; + this(title, null, contentPaneComposer, ActionPane.class, null, null, null, true); } private ActionDefinition( @@ -174,13 +227,7 @@ public enum ActionDefinition { final Class contentPaneComposer, final ActionDefinition activityAlias) { - this.title = title; - this.icon = null; - this.contentPaneComposer = contentPaneComposer; - this.actionPaneComposer = ActionPane.class; - this.activityAlias = activityAlias; - this.category = null; - this.readonly = true; + this(title, null, contentPaneComposer, ActionPane.class, null, activityAlias, null, true); } private ActionDefinition( @@ -189,13 +236,17 @@ public enum ActionDefinition { final Class contentPaneComposer, final ActionDefinition activityAlias) { - this.title = title; - this.icon = icon; - this.contentPaneComposer = contentPaneComposer; - this.actionPaneComposer = ActionPane.class; - this.activityAlias = activityAlias; - this.category = null; - this.readonly = true; + this(title, icon, contentPaneComposer, ActionPane.class, null, activityAlias, null, true); + } + + private ActionDefinition( + final LocTextKey title, + final ImageIcon icon, + final Class contentPaneComposer, + final Class> restCallType, + final ActionDefinition activityAlias) { + + this(title, icon, contentPaneComposer, ActionPane.class, restCallType, activityAlias, null, true); } private ActionDefinition( @@ -205,12 +256,26 @@ public enum ActionDefinition { final ActionDefinition activityAlias, final boolean readonly) { + this(title, icon, contentPaneComposer, ActionPane.class, null, activityAlias, null, readonly); + } + + private ActionDefinition( + final LocTextKey title, + final ImageIcon icon, + final Class contentPaneComposer, + final Class actionPaneComposer, + final Class> restCallType, + final ActionDefinition activityAlias, + final String category, + final boolean readonly) { + this.title = title; this.icon = icon; this.contentPaneComposer = contentPaneComposer; - this.actionPaneComposer = ActionPane.class; + this.actionPaneComposer = actionPaneComposer; + this.restCallType = restCallType; this.activityAlias = activityAlias; - this.category = null; + this.category = category; this.readonly = readonly; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java index 87ab2c09..05212a3c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java @@ -31,6 +31,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; public class FormBuilder { @@ -212,10 +213,10 @@ public class FormBuilder { Label labelLocalized(final Composite parent, final String locTextKey, final int hspan) { final Label label = this.widgetFactory.labelLocalized(parent, locTextKey); - final GridData gridData = new GridData(SWT.RIGHT, SWT.TOP, true, false, hspan, 1); + final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, true, false, hspan, 1); gridData.verticalIndent = 4; label.setLayoutData(gridData); - label.setData(RWT.CUSTOM_VARIANT, "head"); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key); return label; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java index eeb44665..5900552a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java @@ -93,7 +93,7 @@ public final class SelectionFieldBuilder extends FieldBuilder { if (this.multi) { final Composite composite = new Composite(builder.formParent, SWT.NONE); final GridLayout gridLayout = new GridLayout(1, true); - gridLayout.verticalSpacing = 1; + gridLayout.horizontalSpacing = 0; gridLayout.marginLeft = 0; gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; @@ -119,7 +119,7 @@ public final class SelectionFieldBuilder extends FieldBuilder { private Label buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) { final Label label = new Label(composite, SWT.NONE); - final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, hspan, 1); + final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, hspan, 1); gridData.verticalIndent = 0; gridData.horizontalIndent = 0; label.setLayoutData(gridData); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/I18nSupportImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/I18nSupportImpl.java index ab550f4e..6fabb448 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/I18nSupportImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/i18n/impl/I18nSupportImpl.java @@ -11,18 +11,23 @@ package ch.ethz.seb.sebserver.gui.service.i18n.impl; import java.util.Arrays; import java.util.Collection; import java.util.Locale; +import java.util.stream.Collectors; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.rap.rwt.RWT; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @@ -40,24 +45,44 @@ public class I18nSupportImpl implements I18nSupport { private final CurrentUser currentUser; private final MessageSource messageSource; private final Locale defaultLocale = Locale.ENGLISH; + private final Collection supportedLanguages; public I18nSupportImpl( final CurrentUser currentUser, final MessageSource messageSource, - @Value("${sebserver.gui.date.displayformat}") final String displayDateFormat) { + final Environment environment) { this.currentUser = currentUser; this.messageSource = messageSource; this.displayDateFormatter = DateTimeFormat - .forPattern(displayDateFormat) + .forPattern(environment.getProperty( + "sebserver.gui.date.displayformat", + Constants.DEFAULT_DATE_FORMAT)) .withZoneUTC(); - } - private static final Collection SUPPORTED_LANGUAGES = Arrays.asList(Locale.ENGLISH, Locale.GERMAN); + final boolean multilingual = BooleanUtils.toBoolean(environment.getProperty( + "sebserver.gui.multilingual", + "false")); + if (multilingual) { + final String languagesString = environment.getProperty( + "sebserver.gui.languages", + Locale.ENGLISH.getLanguage()); + + this.supportedLanguages = Utils.immutableCollectionOf( + Arrays.asList(StringUtils.split(languagesString, Constants.LIST_SEPARATOR)) + .stream() + .map(s -> Locale.forLanguageTag(s)) + .collect(Collectors.toList())); + + } else { + this.supportedLanguages = Utils.immutableCollectionOf(Locale.ENGLISH); + } + + } @Override public Collection supportedLanguages() { - return SUPPORTED_LANGUAGES; + return this.supportedLanguages; } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java index 7b175b7c..5bc4fe9a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageMessageException.java @@ -8,16 +8,36 @@ package ch.ethz.seb.sebserver.gui.service.page; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; + public class PageMessageException extends RuntimeException { private static final long serialVersionUID = -6967378384991469166L; + private final LocTextKey textKey; + public PageMessageException(final String message, final Throwable cause) { super(message, cause); + this.textKey = new LocTextKey(message); } public PageMessageException(final String message) { super(message); + this.textKey = new LocTextKey(message); + } + + public PageMessageException(final LocTextKey message, final Throwable cause) { + super(message.name, cause); + this.textKey = message; + } + + public PageMessageException(final LocTextKey message) { + super(message.name); + this.textKey = message; + } + + public LocTextKey getMessageKey() { + return this.textKey; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java index 40f578e2..647a6d32 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java @@ -13,11 +13,9 @@ import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Supplier; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; @@ -28,9 +26,6 @@ import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeactivateInstitution; public final class Action implements Runnable { @@ -39,9 +34,9 @@ public final class Action implements Runnable { public final ActionDefinition definition; Supplier confirm; LocTextKey successMessage; - boolean updateOnSelection; Supplier> selectionSupplier; + LocTextKey noSelectionMessage; private final PageContext originalPageContext; private PageContext pageContext; @@ -104,9 +99,14 @@ public final class Action implements Runnable { return this; } - public Action withSelect(final Supplier> selectionSupplier, final Function exec) { + public Action withSelect( + final Supplier> selectionSupplier, + final Function exec, + final LocTextKey noSelectionMessage) { + this.selectionSupplier = selectionSupplier; this.exec = exec; + this.noSelectionMessage = noSelectionMessage; return this; } @@ -125,11 +125,6 @@ public final class Action implements Runnable { return this; } - public Action withUpdateOnSelection() { - this.updateOnSelection = true; - return this; - } - public Action resetEntityKey() { this.pageContext = this.pageContext.withEntityKey(null); return this; @@ -193,8 +188,8 @@ public final class Action implements Runnable { return this.originalPageContext; } - public EntityKey getSingleSelection(final String messageOnEmptySelection) { - final Set selection = getMultiSelection(messageOnEmptySelection); + public EntityKey getSingleSelection() { + final Set selection = getMultiSelection(); if (selection != null) { return selection.iterator().next(); } @@ -202,12 +197,12 @@ public final class Action implements Runnable { return null; } - public Set getMultiSelection(final String messageOnEmptySelection) { + public Set getMultiSelection() { if (this.selectionSupplier != null) { final Set selection = this.selectionSupplier.get(); if (selection.isEmpty()) { - if (StringUtils.isNoneBlank(messageOnEmptySelection)) { - throw new PageMessageException(messageOnEmptySelection); + if (this.noSelectionMessage != null) { + throw new PageMessageException(this.noSelectionMessage); } return null; @@ -216,26 +211,15 @@ public final class Action implements Runnable { return selection; } - if (StringUtils.isNoneBlank(messageOnEmptySelection)) { - throw new PageMessageException(messageOnEmptySelection); + if (this.noSelectionMessage != null) { + throw new PageMessageException(this.noSelectionMessage); } return null; } - public static Function applySingleSelection(final String messageOnEmptySelection) { - return action -> action.withEntityKey(action.getSingleSelection(messageOnEmptySelection)); - } - - public static Function activation(final RestService restService, final boolean activate) { - return action -> restService - .getBuilder((activate) ? ActivateInstitution.class : DeactivateInstitution.class) - .withURIVariable( - API.PARAM_MODEL_ID, - action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) - .call() - .map(report -> action) - .getOrThrow(); + public static Action applySingleSelection(final Action action) { + return action.withEntityKey(action.getSingleSelection()); } public static Action onEmptyEntityKeyGoToActivityHome(final Action action) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java index b1b79c55..79f98e54 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultPageLayout.java @@ -12,6 +12,7 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import org.apache.commons.codec.binary.Base64InputStream; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; @@ -25,8 +26,8 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.MessageBox; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.api.API; @@ -50,17 +51,19 @@ public class DefaultPageLayout implements TemplateComposer { private final PolyglotPageService polyglotPageService; private final AuthorizationContextHolder authorizationContextHolder; private final String sebServerVersion; + private final boolean multilingual; public DefaultPageLayout( final WidgetFactory widgetFactory, final PolyglotPageService polyglotPageService, final AuthorizationContextHolder authorizationContextHolder, - @Value("${sebserver.version}") final String sebServerVersion) { + final Environment environment) { this.widgetFactory = widgetFactory; this.polyglotPageService = polyglotPageService; this.authorizationContextHolder = authorizationContextHolder; - this.sebServerVersion = sebServerVersion; + this.sebServerVersion = environment.getProperty("sebserver.version", "--"); + this.multilingual = BooleanUtils.toBoolean(environment.getProperty("sebserver.gui.multilingual", "false")); } @Override @@ -182,7 +185,9 @@ public class DefaultPageLayout implements TemplateComposer { rowLayout.marginRight = 70; langSupport.setLayout(rowLayout); - this.widgetFactory.createLanguageSelector(pageContext.copyOf(langSupport)); + if (this.multilingual) { + this.widgetFactory.createLanguageSelector(pageContext.copyOf(langSupport)); + } } private void composeContent(final PageContext pageContext) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index a7ec0f67..1e5b820c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -289,7 +289,7 @@ public class PageContextImpl implements PageContext { final MessageBox messageBox = new Message( getShell(), this.i18nSupport.getText("sebserver.page.message"), - this.i18nSupport.getText(pme.getMessage()), + this.i18nSupport.getText(pme.getMessageKey()), SWT.NONE); messageBox.setMarkupEnabled(true); messageBox.open(null); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java new file mode 100644 index 00000000..2eeebdec --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadService.java @@ -0,0 +1,73 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.service.ServiceHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; + +@Lazy +@Service +@GuiProfile +public class DownloadService implements ServiceHandler { + + private static final Logger log = LoggerFactory.getLogger(DownloadService.class); + + public static final String DOWNLOAD_SERVICE_NAME = "DOWNLOAD_SERVICE"; + public static final String HANDLER_NAME_PARAMETER = "download-handler-name"; + + private final Map handler; + + protected DownloadService(final Collection handler) { + this.handler = handler + .stream() + .collect(Collectors.toMap(h -> h.getName(), Function.identity())); + } + + @Override + public void service( + final HttpServletRequest request, + final HttpServletResponse response) throws IOException, ServletException { + + log.debug("Received download service request: {}", request.getRequestURI()); + + final String handlerName = request.getParameter(HANDLER_NAME_PARAMETER); + if (StringUtils.isBlank(handlerName)) { + log.error("Missing request parameter {}. Ignoring download service request", + HANDLER_NAME_PARAMETER); + return; + } + + if (!this.handler.containsKey(handlerName)) { + log.error("Missing DownloadServiceHandler with name {}. Ignoring download service request", + handlerName); + return; + } + + this.handler + .get(handlerName) + .processDownload(request, response); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java new file mode 100644 index 00000000..fd321102 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/DownloadServiceHandler.java @@ -0,0 +1,20 @@ +/* + * 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; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface DownloadServiceHandler { + + String getName(); + + void processDownload(final HttpServletRequest request, final HttpServletResponse response); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java new file mode 100644 index 00000000..1bf0edfd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/SebClientConfigDownload.java @@ -0,0 +1,109 @@ +/* + * 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; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +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.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ExportSEBConfig; + +@Lazy +@Component +@GuiProfile +public class SebClientConfigDownload implements DownloadServiceHandler { + + private static final Logger log = LoggerFactory.getLogger(SebClientConfigDownload.class); + + // TODO must this be configurable? + public static final String SEB_CLIENT_CONFIG_FILE_NAME = "SebClientSettings.seb"; + + private final RestService restService; + + protected SebClientConfigDownload(final RestService restService) { + this.restService = restService; + } + + @Override + public String getName() { + return SEB_CLIENT_CONFIG_FILE_NAME; + } + + @Override + public void processDownload(final HttpServletRequest request, final HttpServletResponse response) { + try { + + log.debug("download requested... trying to get needed parameter from request"); + + final String modelId = request.getParameter(API.PARAM_MODEL_ID); + if (StringUtils.isBlank(modelId)) { + log.error("No needed modelId parameter found within HttpServletRequest. Download request is ignored"); + return; + } + + log.debug("Found modelId: {} for {} download. Trying to request webservice...", modelId, + SEB_CLIENT_CONFIG_FILE_NAME); + + final byte[] configFile = this.restService.getBuilder(ExportSEBConfig.class) + .withURIVariable(API.PARAM_MODEL_ID, modelId) + .call() + .getOrThrow(); + + if (configFile == null) { + log.error("No or empty SEB Client configuration received from webservice. Download request is ignored"); + return; + } + + log.debug("Sucessfully get SEB Client configuration from webservice. File size: {}", configFile.length); + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setContentLength(configFile.length); + response.setHeader( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + SEB_CLIENT_CONFIG_FILE_NAME + "\""); + + log.debug("Write the SEB Client configuration to response output"); + + response.getOutputStream().write(configFile); + + } catch (final Exception e) { + log.error( + "Unexpected error while trying to start SEB Client configuration download. The download is ignored. Cause: ", + e); + } + } + + public String downloadSEBClientConfigURL(final String modelId) { + final StringBuilder url = new StringBuilder() + .append(RWT.getServiceManager() + .getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME)) + .append(Constants.FORM_URL_ENCODED_SEPARATOR) + .append(API.PARAM_MODEL_ID) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(modelId) + .append(Constants.FORM_URL_ENCODED_SEPARATOR) + .append(DownloadService.HANDLER_NAME_PARAMETER) + .append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR) + .append(SebClientConfigDownload.SEB_CLIENT_CONFIG_FILE_NAME); + return url.toString(); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index e728c829..bf48db2c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -33,14 +33,15 @@ import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder; public abstract class RestCall { private static final Logger log = LoggerFactory.getLogger(RestCall.class); - private RestService restService; - private JSONMapper jsonMapper; + protected RestService restService; + protected JSONMapper jsonMapper; protected final TypeReference typeRef; protected final HttpMethod httpMethod; protected final MediaType contentType; @@ -226,14 +227,14 @@ public abstract class RestCall { return RestCall.this.exchange(this); } - protected String buildURI() { + public String buildURI() { return RestCall.this.restService.getWebserviceURIBuilder() .path(RestCall.this.path) .queryParams(this.queryParams) .toUriString(); } - protected HttpEntity buildRequestEntity() { + public HttpEntity buildRequestEntity() { if (this.body != null) { return new HttpEntity<>(this.body, this.httpHeaders); } else { @@ -241,6 +242,10 @@ public abstract class RestCall { } } + public Map getURIVariables() { + return Utils.immutableMapOf(this.uriVariables); + } + @Override public String toString() { return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams=" diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java index f9200718..6d6a50dd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java @@ -17,8 +17,11 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService; @@ -72,4 +75,18 @@ public class RestService { return restCall.newBuilder(); } + public Action activation(final Action action) { + if (action.definition.restCallType == null) { + throw new IllegalArgumentException("ActionDefinition needs to define a restCallType to use this action"); + } + + return this.getBuilder(action.definition.restCallType) + .withURIVariable( + API.PARAM_MODEL_ID, + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) + .call() + .map(report -> action) + .getOrThrow(); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ActivateLmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ActivateLmsSetup.java new file mode 100644 index 00000000..5be556c2 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ActivateLmsSetup.java @@ -0,0 +1,37 @@ +/* + * 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.lmssetup; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +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.model.EntityProcessingReport; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class ActivateLmsSetup extends RestCall { + + protected ActivateLmsSetup() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.LMS_SETUP_ENDPOINT + API.PATH_VAR_ACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/DeactivateLmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/DeactivateLmsSetup.java new file mode 100644 index 00000000..7edc669e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/DeactivateLmsSetup.java @@ -0,0 +1,37 @@ +/* + * 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.lmssetup; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +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.model.EntityProcessingReport; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class DeactivateLmsSetup extends RestCall { + + protected DeactivateLmsSetup() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.LMS_SETUP_ENDPOINT + API.PATH_VAR_INACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ExportSEBConfig.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ExportSEBConfig.java new file mode 100644 index 00000000..50c41648 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/ExportSEBConfig.java @@ -0,0 +1,62 @@ +/* + * 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.lmssetup; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class ExportSEBConfig extends RestCall { + + protected ExportSEBConfig() { + super( + new TypeReference() { + }, + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.SEB_CONFIG_EXPORT_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT); + } + + @Override + protected Result exchange(final RestCallBuilder builder) { + try { + final ResponseEntity responseEntity = this.restService + .getWebserviceAPIRestTemplate() + .exchange( + builder.buildURI(), + this.httpMethod, + builder.buildRequestEntity(), + byte[].class, + builder.getURIVariables()); + + if (responseEntity.getStatusCode() == HttpStatus.OK) { + return Result.of(responseEntity.getBody()); + } + + return Result.ofRuntimeError( + "Error while trying to export SEB Config from webservice. Response: " + responseEntity); + } catch (final Throwable t) { + return Result.ofError(t); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/NewLmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/NewLmsSetup.java new file mode 100644 index 00000000..66079447 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/NewLmsSetup.java @@ -0,0 +1,37 @@ +/* + * 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.lmssetup; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +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.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class NewLmsSetup extends RestCall { + + protected NewLmsSetup() { + super( + new TypeReference() { + }, + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.LMS_SETUP_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/SaveLmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/SaveLmsSetup.java new file mode 100644 index 00000000..12b9aab8 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/SaveLmsSetup.java @@ -0,0 +1,36 @@ +/* + * 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.lmssetup; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +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.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class SaveLmsSetup extends RestCall { + + protected SaveLmsSetup() { + super( + new TypeReference() { + }, + HttpMethod.PUT, + MediaType.APPLICATION_JSON_UTF8, + API.LMS_SETUP_ENDPOINT); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableNavigator.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableNavigator.java index e26ec885..39848c9e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableNavigator.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableNavigator.java @@ -109,9 +109,13 @@ public class TableNavigator { final Label forward = new Label(parent, SWT.NONE); forward.setText(">"); forward.setData(RWT.CUSTOM_VARIANT, "action"); - forward.addListener(SWT.MouseDown, event -> { - this.entityTable.selectPage(pageNumber + 1); - }); + if (visible) { + forward.addListener(SWT.MouseDown, event -> { + this.entityTable.selectPage(pageNumber + 1); + }); + } else { + forward.setVisible(false); + } } private void createBackwardLabel( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index 117fa460..09137566 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -106,6 +106,7 @@ public class WidgetFactory { SELECTION_READONLY("selectionReadonly"), FOOTER("footer"), + TITLE_LABEL("head") ; 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 9977d26d..70d2a345 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebServiceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebServiceInit.java @@ -8,10 +8,14 @@ package ch.ethz.seb.sebserver.webservice; +import java.net.InetAddress; +import java.net.UnknownHostException; + import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; @@ -19,11 +23,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +// TODO check if DataSourceAutoConfiguration and TokenStore bean definition is really needed here +// or if it is possible to move them to the WebServiceSecurityConfig. +// test with starting web and gui separately as well as together @Configuration @WebServiceProfile @Import(DataSourceAutoConfiguration.class) @@ -31,10 +39,26 @@ public class WebServiceInit implements ApplicationListener config server address: {}", this.environment.getProperty("server.address")); + log.info("----> config server port: {}", this.environment.getProperty("server.port")); + + log.info("----> local host address: {}", InetAddress.getLocalHost().getHostAddress()); + log.info("----> local host name: {}", InetAddress.getLocalHost().getHostName()); + + log.info("----> remote host address: {}", InetAddress.getLoopbackAddress().getHostAddress()); + log.info("----> remote host address: {}", InetAddress.getLoopbackAddress().getHostName()); + } catch (final UnknownHostException e) { + log.error("Unknown Host: ", e); + } + // TODO whatever has to be initialized for the web-service component right after startup comes here } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java index ddb1553c..a0be67a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java @@ -148,10 +148,8 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { ? this.internalEncryptionService.encrypt(lmsSetup.lmsAuthSecret) : null, lmsSetup.lmsRestApiToken, - lmsSetup.sebAuthName, - (StringUtils.isNotBlank(lmsSetup.sebAuthSecret)) - ? this.clientPasswordEncoder.encode(lmsSetup.sebAuthSecret) - : null, + null, + null, null); this.lmsSetupRecordMapper.updateByPrimaryKeySelective(newRecord); @@ -173,10 +171,14 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { (lmsSetup.lmsType != null) ? lmsSetup.lmsType.name() : null, lmsSetup.lmsApiUrl, lmsSetup.lmsAuthName, - lmsSetup.lmsAuthSecret, + (StringUtils.isNotBlank(lmsSetup.lmsAuthSecret)) + ? this.internalEncryptionService.encrypt(lmsSetup.lmsAuthSecret) + : null, lmsSetup.lmsRestApiToken, lmsSetup.sebAuthName, - lmsSetup.sebAuthSecret, + (StringUtils.isNotBlank(lmsSetup.sebAuthSecret)) + ? this.internalEncryptionService.encrypt(lmsSetup.sebAuthSecret) + : null, BooleanUtils.toInteger(false)); this.lmsSetupRecordMapper.insert(newRecord); 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 43b6a2a8..7883e3a2 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 @@ -8,7 +8,9 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; +import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; @@ -97,7 +99,13 @@ public class LmsAPIServiceImpl implements LmsAPIService { // To Clarify : How the file should be encrypted (use case) maybe we need another encryption-secret for this that can be given by // an administrator on SEB start configuration creation time - return Result.ofTODO(); + return Result.tryCatch(() -> { + try { + return new ByteArrayInputStream("TODO".getBytes("UTF-8")); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("cause: ", e); + } + }); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceSecurityConfig.java similarity index 92% rename from src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceSecurityConfig.java index df099a59..25f7af7a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceSecurityConfig.java @@ -28,7 +28,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -46,7 +45,7 @@ import org.springframework.security.web.AuthenticationEntryPoint; import ch.ethz.seb.sebserver.WebSecurityConfig; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebClientDetailsService; -import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebResourceServerConfiguration; +import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration; /** This is the main web-security Spring configuration for SEB-Server webservice API * @@ -65,13 +64,13 @@ import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebResourceServerConfigur * and is by default set to "/exam-api/**" */ @WebServiceProfile @Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true) +//@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity @Order(5) @Import(DataSourceAutoConfiguration.class) -public class ClientSessionWebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter { - private static final Logger log = LoggerFactory.getLogger(ClientSessionWebSecurityConfig.class); + private static final Logger log = LoggerFactory.getLogger(WebServiceSecurityConfig.class); /** Spring bean name of single AuthenticationManager bean */ public static final String AUTHENTICATION_MANAGER = "AUTHENTICATION_MANAGER"; @@ -157,7 +156,7 @@ public class ClientSessionWebSecurityConfig extends WebSecurityConfigurerAdapter // NOTE: We need two different class types here to support Spring configuration for different // ResourceServerConfiguration. There is a class type now for the Admin API as well as for the Exam API - private static final class AdminAPIResourceServerConfiguration extends WebResourceServerConfiguration { + private static final class AdminAPIResourceServerConfiguration extends WebserviceResourceConfiguration { public AdminAPIResourceServerConfiguration( final TokenStore tokenStore, @@ -180,7 +179,7 @@ public class ClientSessionWebSecurityConfig extends WebSecurityConfigurerAdapter // NOTE: We need two different class types here to support Spring configuration for different // ResourceServerConfiguration. There is a class type now for the Admin API as well as for the Exam API - private static final class ExamAPIClientResourceServerConfiguration extends WebResourceServerConfiguration { + private static final class ExamAPIClientResourceServerConfiguration extends WebserviceResourceConfiguration { public ExamAPIClientResourceServerConfiguration( final TokenStore tokenStore, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java index 9b8d48f0..16805146 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java @@ -139,8 +139,12 @@ public abstract class ActivatableEntityController this.bulkActionService.createReport(bulkAction)); } + protected Result validForActivation(final T entity) { + return Result.of(entity); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index 74985de1..a62eafac 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -248,9 +248,8 @@ public abstract class EntityController validForCreate(final M entity) { + if (entity.getModelId() == null) { + return this.beanValidationService.validateBean(entity); + } else { + return Result.ofError( + new IllegalAPIArgumentException("Model identifier already defined: " + entity.getModelId())); + } + } + protected Result validForSave(final T entity) { if (entity.getModelId() != null) { return Result.of(entity); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java index febfe533..8bcbd385 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java @@ -11,11 +11,15 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import java.io.InputStream; import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.StringUtils; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -28,6 +32,7 @@ import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; 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.LmsSetupRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; @@ -35,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; +import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationException; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @WebServiceProfile @@ -74,33 +80,28 @@ public class LmsSetupController extends ActivatableEntityController validForCreate(final LmsSetup entity) { + + final SEBClientAuth clientAuth = new SEBClientAuth(entity.sebAuthName, entity.sebAuthSecret); + + final Result result = super.validForCreate(entity); + if (result.hasError()) { + final Throwable error = result.getError(); + if (error instanceof BeanValidationException) { + final BeanValidationException beanValidationException = (BeanValidationException) error; + final Result validateSebAuth = this.beanValidationService.validateBean(clientAuth); + if (validateSebAuth.hasError()) { + final Throwable sebAuthError = validateSebAuth.getError(); + if (sebAuthError instanceof BeanValidationException) { + final BindingResult bindingResult = beanValidationException.getBindingResult(); + bindingResult.addAllErrors(((BeanValidationException) sebAuthError).getBindingResult()); + return Result.ofError(new BeanValidationException(bindingResult)); + } else { + return validateSebAuth + .map(ce -> entity); + } + } + } + return result; + } else { + return this.beanValidationService.validateBean(clientAuth) + .map(ca -> entity); + } + } + + @Override + protected Result validForSave(final LmsSetup entity) { + return super.validForSave(entity) + .map(setup -> { + + if (StringUtils.isNoneBlank(entity.sebAuthName) + || StringUtils.isNoneBlank(entity.sebAuthSecret)) { + + throw new IllegalAPIArgumentException( + "SEB Client Authentication cannot be changed after creation"); + } + return setup; + }); + } + + public static final class SEBClientAuth { + + @NotNull(message = "lmsSetup:sebClientname:notNull") + @Size(min = 3, max = 255, message = "lmsSetup:sebClientname:size:{min}:{max}:${validatedValue}") + public final String sebAuthName; + + @NotNull(message = "lmsSetup:sebClientsecret:notNull") + @Size(min = 8, max = 255, message = "lmsSetup:sebClientsecret:size:{min}:{max}:${validatedValue}") + public final String sebAuthSecret; + + protected SEBClientAuth(final String authName, final String authSecret) { + this.sebAuthName = authName; + this.sebAuthSecret = authSecret; + } + } + } 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 3890b6a3..cff1c5fa 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 @@ -34,6 +34,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; 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.UserMod; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; @@ -113,15 +114,53 @@ public class UserAccountController extends ActivatableEntityController validForCreate(final UserMod userInfo) { + return super.validForCreate(userInfo) + .flatMap(this::additionalConsistencyChecks); + } + @Override protected Result validForSave(final UserInfo userInfo) { + return super.validForSave(userInfo) + .flatMap(this::additionalConsistencyChecks); + } + + /** Additional consistency checks that has to be checked before create and save actions */ + private Result additionalConsistencyChecks(final T userInfo) { return Result.tryCatch(() -> { final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser(); final EnumSet rolesOfCurrentUser = currentUser.getUserRoles(); final EnumSet userRolesOfAccount = userInfo.getUserRoles(); // check of institution of UserInfo is active. Otherwise save is not valid - if (!this.beanValidationService.isActive(new EntityKey(userInfo.institutionId, EntityType.INSTITUTION))) { + if (!this.beanValidationService + .isActive(new EntityKey(userInfo.getInstitutionId(), EntityType.INSTITUTION))) { throw new IllegalAPIArgumentException( "User within an inactive institution cannot be created nor modified"); } @@ -174,31 +213,6 @@ public class UserAccountController extends ActivatableEntityController revokeAccessToken(final UserInfo userInfo) { return Result.tryCatch(() -> { this.applicationEventPublisher.publishEvent( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminAPIClientDetails.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminAPIClientDetails.java index 0403b4b2..282d5bf6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminAPIClientDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminAPIClientDetails.java @@ -33,7 +33,7 @@ public final class AdminAPIClientDetails extends BaseClientDetails { super( clientId, - WebResourceServerConfiguration.ADMIN_API_RESOURCE_ID, + WebserviceResourceConfiguration.ADMIN_API_RESOURCE_ID, "read,write", "password,refresh_token", null); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java index b16562a6..d55feac8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java @@ -25,7 +25,7 @@ import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenCo import ch.ethz.seb.sebserver.WebSecurityConfig; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.webservice.weblayer.ClientSessionWebSecurityConfig; +import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceSecurityConfig; import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceUserDetails; /** This is the main Spring configuration of OAuth2 Authorization Server. @@ -52,7 +52,7 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) private PasswordEncoder clientPasswordEncoder; @Autowired - @Qualifier(ClientSessionWebSecurityConfig.AUTHENTICATION_MANAGER) + @Qualifier(WebServiceSecurityConfig.AUTHENTICATION_MANAGER) private AuthenticationManager authenticationManager; @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java index 61adffd0..c5aaff6a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java @@ -75,7 +75,7 @@ public class WebClientDetailsService implements ClientDetailsService { if ("test".equals(clientId)) { final BaseClientDetails baseClientDetails = new BaseClientDetails( clientId, - WebResourceServerConfiguration.EXAM_API_RESOURCE_ID, + WebserviceResourceConfiguration.EXAM_API_RESOURCE_ID, null, "client_credentials,refresh_token", ""); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebResourceServerConfiguration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebserviceResourceConfiguration.java similarity index 95% rename from src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebResourceServerConfiguration.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebserviceResourceConfiguration.java index c7481f89..b96097c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebResourceServerConfiguration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebserviceResourceConfiguration.java @@ -24,14 +24,14 @@ import org.springframework.security.web.AuthenticationEntryPoint; /** Abstract Spring ResourceServerConfiguration to configure different resource services * for different API's. */ -public abstract class WebResourceServerConfiguration extends ResourceServerConfiguration { +public abstract class WebserviceResourceConfiguration extends ResourceServerConfiguration { /** The resource identifier of Administration API resources */ public static final String ADMIN_API_RESOURCE_ID = "seb-server-administration-api"; /** The resource identifier of the Exam API resources */ public static final String EXAM_API_RESOURCE_ID = "seb-server-exam-api"; - public WebResourceServerConfiguration( + public WebserviceResourceConfiguration( final TokenStore tokenStore, final WebClientDetailsService webServiceClientDetails, final AuthenticationManager authenticationManager, diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index 262ad9f2..a0032374 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -15,4 +15,6 @@ sebserver.gui.webservice.apipath=/admin-api/v1 sebserver.gui.theme=css/sebserver.css sebserver.gui.list.page.size=20 sebserver.gui.date.displayformat=EEEE, dd MMMM yyyy - HH:mm +sebserver.gui.multilingual=true +sebserver.gui.languages=en,de diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 57fdf909..aa360146 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -147,14 +147,36 @@ sebserver.lmssetup.type.MOODLE=Moodle sebserver.lmssetup.type.OPEN_EDX=Open edX sebserver.lmssetup.list.empty=No LMS Setup has been found. Please adapt the filter or create a new LMS Setup -sebserver.lmssetup.list.title=LMS Setup +sebserver.lmssetup.list.title=Learning Management System Setups sebserver.lmssetup.list.column.institution=Institution sebserver.lmssetup.list.column.name=Name sebserver.lmssetup.list.column.type=LMS Type sebserver.lmssetup.list.column.active=Active -sebserver.lmssetup.action.list=LMS Setups +sebserver.lmssetup.action.list=LMS Setup sebserver.lmssetup.action.form=LMS Setup +sebserver.lmssetup.action.new=New LMS Setup +sebserver.lmssetup.action.list.view=View Selected +sebserver.lmssetup.action.list.modify=Edit Selected +sebserver.lmssetup.action.modify=Edit +sebserver.lmssetup.action.save=Save LMS Setup +sebserver.lmssetup.action.activate=Active +sebserver.lmssetup.action.deactivate=Active +sebserver.lmssetup.action.delete=Delete LMS Setup +sebserver.lmssetup.action.export.sebconfig=Export SEB-Client Config + +sebserver.lmssetup.info.pleaseSelect=Please Select a LMS Setup first + +sebserver.lmssetup.form.title=Learning Management System Setup +sebserver.lmssetup.form.title.new=New Learning Management System Setup +sebserver.lmssetup.form.institution=Institution +sebserver.lmssetup.form.name=Name +sebserver.lmssetup.form.type=Type +sebserver.lmssetup.form.clientname.seb=SEB Auth. Name +sebserver.lmssetup.form.secret.seb=SEB Auth. Password +sebserver.lmssetup.form.url=LMS Server Address +sebserver.lmssetup.form.clientname.lms=LMS Server Username +sebserver.lmssetup.form.secret.lms=LMS Server Password diff --git a/src/main/resources/org/eclipse/swt/internal/SWTMessages_de.properties b/src/main/resources/org/eclipse/swt/internal/SWTMessages_de.properties new file mode 100644 index 00000000..cc876e87 --- /dev/null +++ b/src/main/resources/org/eclipse/swt/internal/SWTMessages_de.properties @@ -0,0 +1,7 @@ +SWT_Yes=Ja +SWT_No=Nein +SWT_OK=OK +SWT_Cancel=Abbrechen +SWT_Abort=Abbrechen +SWT_Retry=New Versuchen +SWT_Ignore=Ignorieren \ No newline at end of file diff --git a/src/main/resources/org/eclipse/swt/internal/SWTMessages_en.properties b/src/main/resources/org/eclipse/swt/internal/SWTMessages_en.properties new file mode 100644 index 00000000..088ea677 --- /dev/null +++ b/src/main/resources/org/eclipse/swt/internal/SWTMessages_en.properties @@ -0,0 +1,7 @@ +SWT_Yes=Yes +SWT_No=No +SWT_OK=OK +SWT_Cancel=Cancel +SWT_Abort=Abort +SWT_Retry=Retry +SWT_Ignore=Ignore \ No newline at end of file diff --git a/src/main/resources/schema-demo.sql b/src/main/resources/schema-demo.sql index 18a98938..dbf55df6 100644 --- a/src/main/resources/schema-demo.sql +++ b/src/main/resources/schema-demo.sql @@ -30,8 +30,8 @@ CREATE TABLE IF NOT EXISTS `lms_setup` ( `name` VARCHAR(255) NOT NULL, `lms_type` VARCHAR(45) NOT NULL, `lms_url` VARCHAR(255) NULL, - `lms_clientname` VARCHAR(255) NOT NULL, - `lms_clientsecret` VARCHAR(255) NOT NULL, + `lms_clientname` VARCHAR(255) NULL, + `lms_clientsecret` VARCHAR(255) NULL, `lms_rest_api_token` VARCHAR(4000) NULL, `seb_clientname` VARCHAR(255) NOT NULL, `seb_clientsecret` VARCHAR(255) NOT NULL, diff --git a/src/main/resources/schema-dev.sql b/src/main/resources/schema-dev.sql index cab6619f..c94c0a77 100644 --- a/src/main/resources/schema-dev.sql +++ b/src/main/resources/schema-dev.sql @@ -34,8 +34,8 @@ CREATE TABLE IF NOT EXISTS `lms_setup` ( `name` VARCHAR(255) NOT NULL, `lms_type` VARCHAR(45) NOT NULL, `lms_url` VARCHAR(255) NULL, - `lms_clientname` VARCHAR(255) NOT NULL, - `lms_clientsecret` VARCHAR(255) NOT NULL, + `lms_clientname` VARCHAR(255) NULL, + `lms_clientsecret` VARCHAR(255) NULL, `lms_rest_api_token` VARCHAR(4000) NULL, `seb_clientname` VARCHAR(255) NOT NULL, `seb_clientsecret` VARCHAR(255) NOT NULL, diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index 4920ad39..3a11cf86 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -76,7 +76,7 @@ Label.selection { } Label.selectionReadonly { - padding: 4px 6px 3px 6px; + padding: 4px 6px 3px 0px; } Label:hover.selection { @@ -186,18 +186,10 @@ Text { } Text.error { - font: 12px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif; - border: none; - border-radius: 0; - padding: 3px 10px 3px 10px; - color: #4a4a4a; background-repeat: repeat; background-position: left top; background-color: #82BE1E; background-image: none; - text-shadow: none; - box-shadow: none; - opacity: 0.5; } Text[MULTI] { @@ -254,6 +246,13 @@ Combo-Field { padding: 3px 10px 3px 10px; } +Combo.error { + background-repeat: repeat; + background-position: left top; + background-color: #82BE1E; + background-image: none; +} + /* Message titlebar */ Shell.message { diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java index 6e644a03..c993dacd 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/institution/InstitutionTest.java @@ -20,7 +20,6 @@ import org.junit.Test; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectWriter; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.EntityName; @@ -47,52 +46,20 @@ public class InstitutionTest { new Institution(3L, "InstThree", "three", "", true))); final JSONMapper jsonMapper = new JSONMapper(); - final ObjectWriter writerWithDefaultPrettyPrinter = jsonMapper.writerWithDefaultPrettyPrinter(); - String json = writerWithDefaultPrettyPrinter.writeValueAsString(page); - assertEquals("{\r\n" + - " \"number_of_pages\" : 2,\r\n" + - " \"page_number\" : 1,\r\n" + - " \"sort\" : \"name\",\r\n" + - " \"content\" : [ {\r\n" + - " \"id\" : 1,\r\n" + - " \"name\" : \"InstOne\",\r\n" + - " \"urlSuffix\" : \"one\",\r\n" + - " \"logoImage\" : \"\",\r\n" + - " \"active\" : true\r\n" + - " }, {\r\n" + - " \"id\" : 2,\r\n" + - " \"name\" : \"InstTwo\",\r\n" + - " \"urlSuffix\" : \"two\",\r\n" + - " \"logoImage\" : \"\",\r\n" + - " \"active\" : true\r\n" + - " }, {\r\n" + - " \"id\" : 3,\r\n" + - " \"name\" : \"InstThree\",\r\n" + - " \"urlSuffix\" : \"three\",\r\n" + - " \"logoImage\" : \"\",\r\n" + - " \"active\" : true\r\n" + - " } ],\r\n" + - " \"page_size\" : 3\r\n" + - "}", json); + //final ObjectWriter writerWithDefaultPrettyPrinter = jsonMapper.writerWithDefaultPrettyPrinter(); + String json = jsonMapper.writeValueAsString(page); + assertEquals( + "{\"number_of_pages\":2,\"page_number\":1,\"sort\":\"name\",\"content\":[{\"id\":1,\"name\":\"InstOne\",\"urlSuffix\":\"one\",\"logoImage\":\"\",\"active\":true},{\"id\":2,\"name\":\"InstTwo\",\"urlSuffix\":\"two\",\"logoImage\":\"\",\"active\":true},{\"id\":3,\"name\":\"InstThree\",\"urlSuffix\":\"three\",\"logoImage\":\"\",\"active\":true}],\"page_size\":3}", + json); final List namesList = page.content.stream() .map(inst -> new EntityName(inst.getEntityKey(), inst.name)) .collect(Collectors.toList()); - json = writerWithDefaultPrettyPrinter.writeValueAsString(namesList); - assertEquals("[ {\r\n" + - " \"entityType\" : \"INSTITUTION\",\r\n" + - " \"modelId\" : \"1\",\r\n" + - " \"name\" : \"InstOne\"\r\n" + - "}, {\r\n" + - " \"entityType\" : \"INSTITUTION\",\r\n" + - " \"modelId\" : \"2\",\r\n" + - " \"name\" : \"InstTwo\"\r\n" + - "}, {\r\n" + - " \"entityType\" : \"INSTITUTION\",\r\n" + - " \"modelId\" : \"3\",\r\n" + - " \"name\" : \"InstThree\"\r\n" + - "} ]", json); + json = jsonMapper.writeValueAsString(namesList); + assertEquals( + "[{\"entityType\":\"INSTITUTION\",\"modelId\":\"1\",\"name\":\"InstOne\"},{\"entityType\":\"INSTITUTION\",\"modelId\":\"2\",\"name\":\"InstTwo\"},{\"entityType\":\"INSTITUTION\",\"modelId\":\"3\",\"name\":\"InstThree\"}]", + json); } } diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfoTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfoTest.java index 49f4c2bd..186f5a5f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfoTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfoTest.java @@ -17,8 +17,6 @@ import java.util.Locale; import org.joda.time.DateTimeZone; import org.junit.Test; -import com.fasterxml.jackson.databind.ObjectWriter; - import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Page; @@ -35,45 +33,11 @@ public class UserInfoTest { new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name()))))); final JSONMapper jsonMapper = new JSONMapper(); - final ObjectWriter writerWithDefaultPrettyPrinter = jsonMapper.writerWithDefaultPrettyPrinter(); - final String json = writerWithDefaultPrettyPrinter.writeValueAsString(page); - assertEquals("{\r\n" + - " \"number_of_pages\" : 2,\r\n" + - " \"page_number\" : 1,\r\n" + - " \"sort\" : \"name\",\r\n" + - " \"content\" : [ {\r\n" + - " \"uuid\" : \"id1\",\r\n" + - " \"institutionId\" : 1,\r\n" + - " \"name\" : \"user1\",\r\n" + - " \"username\" : \"user1\",\r\n" + - " \"email\" : \"user1@inst2.none\",\r\n" + - " \"active\" : true,\r\n" + - " \"language\" : \"en\",\r\n" + - " \"timezone\" : \"UTC\",\r\n" + - " \"userRoles\" : [ \"EXAM_ADMIN\" ]\r\n" + - " }, {\r\n" + - " \"uuid\" : \"id2\",\r\n" + - " \"institutionId\" : 3,\r\n" + - " \"name\" : \"user2\",\r\n" + - " \"username\" : \"user2\",\r\n" + - " \"email\" : \"user2@inst2.none\",\r\n" + - " \"active\" : true,\r\n" + - " \"language\" : \"en\",\r\n" + - " \"timezone\" : \"UTC\",\r\n" + - " \"userRoles\" : [ \"EXAM_ADMIN\" ]\r\n" + - " }, {\r\n" + - " \"uuid\" : \"id3\",\r\n" + - " \"institutionId\" : 4,\r\n" + - " \"name\" : \"user3\",\r\n" + - " \"username\" : \"user3\",\r\n" + - " \"email\" : \"user3@inst2.none\",\r\n" + - " \"active\" : false,\r\n" + - " \"language\" : \"de\",\r\n" + - " \"timezone\" : \"UTC\",\r\n" + - " \"userRoles\" : [ \"EXAM_ADMIN\" ]\r\n" + - " } ],\r\n" + - " \"page_size\" : 3\r\n" + - "}", json); + //final ObjectWriter writerWithDefaultPrettyPrinter = jsonMapper.writerWithDefaultPrettyPrinter(); + final String json = jsonMapper.writeValueAsString(page); + assertEquals( + "{\"number_of_pages\":2,\"page_number\":1,\"sort\":\"name\",\"content\":[{\"uuid\":\"id1\",\"institutionId\":1,\"name\":\"user1\",\"username\":\"user1\",\"email\":\"user1@inst2.none\",\"active\":true,\"language\":\"en\",\"timezone\":\"UTC\",\"userRoles\":[\"EXAM_ADMIN\"]},{\"uuid\":\"id2\",\"institutionId\":3,\"name\":\"user2\",\"username\":\"user2\",\"email\":\"user2@inst2.none\",\"active\":true,\"language\":\"en\",\"timezone\":\"UTC\",\"userRoles\":[\"EXAM_ADMIN\"]},{\"uuid\":\"id3\",\"institutionId\":4,\"name\":\"user3\",\"username\":\"user3\",\"email\":\"user3@inst2.none\",\"active\":false,\"language\":\"de\",\"timezone\":\"UTC\",\"userRoles\":[\"EXAM_ADMIN\"]}],\"page_size\":3}", + json); } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/LmsSetupAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/LmsSetupAPITest.java index c924a20c..16450204 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/LmsSetupAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/LmsSetupAPITest.java @@ -38,9 +38,11 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withAccessToken(getAdminInstitution1Access()) .withPath(API.LMS_SETUP_ENDPOINT) .withMethod(HttpMethod.POST) - .withAttribute("name", "new LmsSetup 1") + .withAttribute(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, "1") + .withAttribute(Domain.LMS_SETUP.ATTR_NAME, "new LmsSetup 1") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb1Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }); @@ -53,7 +55,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { assertFalse(lmsSetup.active); // set lms server and credentials - final LmsSetup modified = new LmsSetup( + LmsSetup modified = new LmsSetup( lmsSetup.id, lmsSetup.institutionId, lmsSetup.name, @@ -62,8 +64,8 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { "lms1Secret", "https://www.lms1.com", null, - "seb1Name", - "seb1Secret", + null, + null, null); lmsSetup = new RestAPITestHelper() @@ -87,6 +89,34 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { assertEquals(null, lmsSetup.sebAuthSecret); assertFalse(lmsSetup.active); + // trying to rest seb-client credentials should not be possible + modified = new LmsSetup( + lmsSetup.id, + lmsSetup.institutionId, + lmsSetup.name, + lmsSetup.lmsType, + "lms1Name", + "lms1Secret", + "https://www.lms1.com", + null, + "seb2Name", + "shouldNotBePossible", + null); + + final List errors = new RestAPITestHelper() + .withAccessToken(getAdminInstitution1Access()) + .withPath(API.LMS_SETUP_ENDPOINT) + .withMethod(HttpMethod.PUT) + .withBodyJson(modified) + .withExpectedStatus(HttpStatus.BAD_REQUEST) + .getAsObject(new TypeReference>() { + }); + + assertNotNull(errors); + assertTrue(errors.size() == 1); + assertEquals("SEB Client Authentication cannot be changed after creation", + errors.get(0).details); + // activate EntityProcessingReport report = new RestAPITestHelper() .withAccessToken(getAdminInstitution1Access()) @@ -180,20 +210,20 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { @Test public void testValidationOnCreate() throws Exception { - // create new institution with seb-admin + // create new LmsSetup with seb-admin final List errors = new RestAPITestHelper() .withAccessToken(getAdminInstitution1Access()) .withPath(API.LMS_SETUP_ENDPOINT) .withMethod(HttpMethod.POST) - .withAttribute("name", "new LmsSetup 1") - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, "1") + .withAttribute(Domain.LMS_SETUP.ATTR_NAME, "new LmsSetup 1") + .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) .getAsObject(new TypeReference>() { }); assertNotNull(errors); - assertTrue(errors.size() == 1); + assertTrue(errors.size() == 2); assertEquals("Field validation error", errors.get(0).systemMessage); - assertEquals("[lmsSetup, lmsType, notNull]", String.valueOf(errors.get(0).attributes)); } @Test @@ -205,7 +235,8 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withMethod(HttpMethod.POST) .withAttribute("name", "new LmsSetup 1") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb1Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }); @@ -215,7 +246,8 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withMethod(HttpMethod.POST) .withAttribute("name", "new LmsSetup 2") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb2Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }); @@ -242,7 +274,8 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withMethod(HttpMethod.POST) .withAttribute("name", "new LmsSetup 1") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb2Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }); @@ -252,7 +285,8 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withMethod(HttpMethod.POST) .withAttribute("name", "new LmsSetup 2") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb2Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }); @@ -275,9 +309,11 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withAccessToken(getSebAdminAccess()) .withPath(API.LMS_SETUP_ENDPOINT) .withMethod(HttpMethod.POST) + .withAttribute(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, "1") .withAttribute("name", "new LmsSetup 1") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb2Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }).id; @@ -286,10 +322,11 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { .withAccessToken(getSebAdminAccess()) .withPath(API.LMS_SETUP_ENDPOINT) .withMethod(HttpMethod.POST) - .withAttribute("name", "new LmsSetup 2") .withAttribute(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, "2") + .withAttribute("name", "new LmsSetup 2") .withAttribute(Domain.LMS_SETUP.ATTR_LMS_TYPE, LmsType.MOCKUP.name()) - .withAttribute("active", "false") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTNAME, "seb2Name") + .withAttribute(Domain.LMS_SETUP.ATTR_SEB_CLIENTSECRET, "12345678") .withExpectedStatus(HttpStatus.OK) .getAsObject(new TypeReference() { }).id; @@ -305,7 +342,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { assertNotNull(lmsSetup); assertTrue(lmsSetup.id.longValue() == id1.longValue()); - // a seb-admin is also able to get an institution that is not the one he self belongs to + // a seb-admin is also able to get lms setup that is not the own institution lmsSetup = new RestAPITestHelper() .withAccessToken(getSebAdminAccess()) .withPath(API.LMS_SETUP_ENDPOINT) @@ -317,7 +354,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester { assertNotNull(lmsSetup); assertTrue(lmsSetup.id.longValue() == id2.longValue()); - // but a institutional-admin is not able to get an institution that is not the one he self belongs to + // but a institutional-admin is not able to get lms setup that is on another institution new RestAPITestHelper() .withAccessToken(getAdminInstitution1Access()) .withPath(API.LMS_SETUP_ENDPOINT) diff --git a/src/test/resources/schema-test.sql b/src/test/resources/schema-test.sql index 18a98938..dbf55df6 100644 --- a/src/test/resources/schema-test.sql +++ b/src/test/resources/schema-test.sql @@ -30,8 +30,8 @@ CREATE TABLE IF NOT EXISTS `lms_setup` ( `name` VARCHAR(255) NOT NULL, `lms_type` VARCHAR(45) NOT NULL, `lms_url` VARCHAR(255) NULL, - `lms_clientname` VARCHAR(255) NOT NULL, - `lms_clientsecret` VARCHAR(255) NOT NULL, + `lms_clientname` VARCHAR(255) NULL, + `lms_clientsecret` VARCHAR(255) NULL, `lms_rest_api_token` VARCHAR(4000) NULL, `seb_clientname` VARCHAR(255) NOT NULL, `seb_clientsecret` VARCHAR(255) NOT NULL,