SEBSERV-30 LmsSetup list and form; fixed tests and code cleanup

This commit is contained in:
anhefti 2019-03-07 12:10:48 +01:00
parent e799a0214f
commit b74c711ebb
55 changed files with 1196 additions and 317 deletions

View file

@ -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")

View file

@ -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";

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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());
;

View file

@ -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<LmsSetup> 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);
}

View file

@ -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) {

View file

@ -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);

View file

@ -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());
}

View file

@ -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<? extends TemplateComposer> contentPaneComposer;
public final Class<? extends TemplateComposer> actionPaneComposer;
public final Class<? extends RestCall<?>> 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<? extends TemplateComposer> 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<? extends TemplateComposer> 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<? extends TemplateComposer> 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<? extends TemplateComposer> contentPaneComposer,
final Class<? extends RestCall<?>> 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<? extends TemplateComposer> contentPaneComposer,
final Class<? extends TemplateComposer> actionPaneComposer,
final Class<? extends RestCall<?>> 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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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<Locale> 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<Locale> 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<Locale> supportedLanguages() {
return SUPPORTED_LANGUAGES;
return this.supportedLanguages;
}
@Override

View file

@ -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;
}
}

View file

@ -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<LocTextKey> confirm;
LocTextKey successMessage;
boolean updateOnSelection;
Supplier<Set<EntityKey>> 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<Set<EntityKey>> selectionSupplier, final Function<Action, Action> exec) {
public Action withSelect(
final Supplier<Set<EntityKey>> selectionSupplier,
final Function<Action, Action> 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<EntityKey> selection = getMultiSelection(messageOnEmptySelection);
public EntityKey getSingleSelection() {
final Set<EntityKey> selection = getMultiSelection();
if (selection != null) {
return selection.iterator().next();
}
@ -202,12 +197,12 @@ public final class Action implements Runnable {
return null;
}
public Set<EntityKey> getMultiSelection(final String messageOnEmptySelection) {
public Set<EntityKey> getMultiSelection() {
if (this.selectionSupplier != null) {
final Set<EntityKey> 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<Action, Action> applySingleSelection(final String messageOnEmptySelection) {
return action -> action.withEntityKey(action.getSingleSelection(messageOnEmptySelection));
}
public static Function<Action, Action> 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) {

View file

@ -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) {

View file

@ -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);

View file

@ -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<String, DownloadServiceHandler> handler;
protected DownloadService(final Collection<DownloadServiceHandler> 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);
}
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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<T> {
private static final Logger log = LoggerFactory.getLogger(RestCall.class);
private RestService restService;
private JSONMapper jsonMapper;
protected RestService restService;
protected JSONMapper jsonMapper;
protected final TypeReference<T> typeRef;
protected final HttpMethod httpMethod;
protected final MediaType contentType;
@ -226,14 +227,14 @@ public abstract class RestCall<T> {
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<T> {
}
}
public Map<String, String> getURIVariables() {
return Utils.immutableMapOf(this.uriVariables);
}
@Override
public String toString() {
return "RestCallBuilder [httpHeaders=" + this.httpHeaders + ", body=" + this.body + ", queryParams="

View file

@ -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();
}
}

View file

@ -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<EntityProcessingReport> {
protected ActivateLmsSetup() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.LMS_SETUP_ENDPOINT + API.PATH_VAR_ACTIVE);
}
}

View file

@ -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<EntityProcessingReport> {
protected DeactivateLmsSetup() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.LMS_SETUP_ENDPOINT + API.PATH_VAR_INACTIVE);
}
}

View file

@ -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<byte[]> {
protected ExportSEBConfig() {
super(
new TypeReference<byte[]>() {
},
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.SEB_CONFIG_EXPORT_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
}
@Override
protected Result<byte[]> exchange(final RestCallBuilder builder) {
try {
final ResponseEntity<byte[]> 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);
}
}
}

View file

@ -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<LmsSetup> {
protected NewLmsSetup() {
super(
new TypeReference<LmsSetup>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.LMS_SETUP_ENDPOINT);
}
}

View file

@ -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<LmsSetup> {
protected SaveLmsSetup() {
super(
new TypeReference<LmsSetup>() {
},
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,
API.LMS_SETUP_ENDPOINT);
}
}

View file

@ -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(

View file

@ -106,6 +106,7 @@ public class WidgetFactory {
SELECTION_READONLY("selectionReadonly"),
FOOTER("footer"),
TITLE_LABEL("head")
;

View file

@ -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<ApplicationReadyEvent
private static final Logger log = LoggerFactory.getLogger(WebServiceInit.class);
@Autowired
private Environment environment;
@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
log.info("Initialize SEB-Server Web-Service Component");
try {
log.info("----> 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
}

View file

@ -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);

View file

@ -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);
}
});
}
}

View file

@ -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,

View file

@ -139,8 +139,12 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
return this.entityDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite)
.flatMap(this::validForSave)
.flatMap(this::validForActivation)
.flatMap(entity -> this.bulkActionService.createReport(bulkAction));
}
protected Result<T> validForActivation(final T entity) {
return Result.of(entity);
}
}

View file

@ -248,9 +248,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
final M requestModel = this.createNew(postMap);
return this.beanValidationService.validateBean(requestModel)
return this.validForCreate(requestModel)
.flatMap(this.entityDAO::createNew)
.flatMap(this::validForSave)
.flatMap(this.userActivityLogDAO::logCreate)
.flatMap(this::notifyCreated)
.getOrThrow();
@ -341,6 +340,15 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
return Result.of(entity);
}
protected Result<M> 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<T> validForSave(final T entity) {
if (entity.getModelId() != null) {
return Result.of(entity);

View file

@ -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<LmsSetup, Lm
}
@RequestMapping(
path = "/SEB_Configuration" + API.MODEL_ID_VAR_PATH_SEGMENT,
path = API.SEB_CONFIG_EXPORT_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) // TODO check if this is the right format
public void downloadSEBConfig(
@PathVariable final Long modelId,
final HttpServletResponse response) {
final HttpServletResponse response) throws Exception {
this.authorization.check(PrivilegeType.WRITE, EntityType.LMS_SETUP);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setStatus(HttpStatus.OK.value());
try {
final InputStream sebConfigFileIn = this.lmsAPIService
.createSEBStartConfiguration(modelId)
.getOrThrow();
final InputStream sebConfigFileIn = this.lmsAPIService
.createSEBStartConfiguration(modelId)
.getOrThrow();
IOUtils.copyLarge(sebConfigFileIn, response.getOutputStream());
response.flushBuffer();
} catch (final Exception e) {
throw new RuntimeException("Unexpected error while trying to creae SEB start config: ", e);
}
IOUtils.copyLarge(sebConfigFileIn, response.getOutputStream());
response.flushBuffer();
}
@RequestMapping(
path = "/connection_report" + API.MODEL_ID_VAR_PATH_SEGMENT,
path = API.LMS_SETUP_TEST_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public LmsSetupTestResult connectionReport(@PathVariable final Long modelId) {
@ -117,4 +118,65 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
return new LmsSetup(null, postParams);
}
@Override
protected Result<LmsSetup> validForCreate(final LmsSetup entity) {
final SEBClientAuth clientAuth = new SEBClientAuth(entity.sebAuthName, entity.sebAuthSecret);
final Result<LmsSetup> result = super.validForCreate(entity);
if (result.hasError()) {
final Throwable error = result.getError();
if (error instanceof BeanValidationException) {
final BeanValidationException beanValidationException = (BeanValidationException) error;
final Result<SEBClientAuth> 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<LmsSetup> 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;
}
}
}

View file

@ -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<UserInfo,
.collect(Collectors.toList()));
}
private UserInfo checkPasswordChange(final UserInfo info, final PasswordChange passwordChange) {
final SEBServerUser authUser = this.userDAO.sebServerUserByUsername(info.username)
.getOrThrow();
if (!this.userPasswordEncoder.matches(passwordChange.getOldPassword(), authUser.getPassword())) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_OLD_PASSWORD,
"user:oldPassword:password.wrong")));
}
if (!passwordChange.newPasswordMatch()) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD,
"user:retypedNewPassword:password.mismatch")));
}
return info;
}
@Override
protected Result<UserMod> validForCreate(final UserMod userInfo) {
return super.validForCreate(userInfo)
.flatMap(this::additionalConsistencyChecks);
}
@Override
protected Result<UserInfo> 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 <T extends UserAccount> Result<T> additionalConsistencyChecks(final T userInfo) {
return Result.tryCatch(() -> {
final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser();
final EnumSet<UserRole> rolesOfCurrentUser = currentUser.getUserRoles();
final EnumSet<UserRole> 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<UserInfo,
.getOrThrow();
}
private UserInfo checkPasswordChange(final UserInfo info, final PasswordChange passwordChange) {
final SEBServerUser authUser = this.userDAO.sebServerUserByUsername(info.username)
.getOrThrow();
if (!this.userPasswordEncoder.matches(passwordChange.getOldPassword(), authUser.getPassword())) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_OLD_PASSWORD,
"user:oldPassword:password.wrong")));
}
if (!passwordChange.newPasswordMatch()) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD,
"user:retypedNewPassword:password.mismatch")));
}
return info;
}
private Result<UserInfo> revokeAccessToken(final UserInfo userInfo) {
return Result.tryCatch(() -> {
this.applicationEventPublisher.publishEvent(

View file

@ -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);

View file

@ -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

View file

@ -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",
"");

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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 {

View file

@ -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<EntityName> 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);
}
}

View file

@ -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);
}

View file

@ -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<LmsSetup>() {
});
@ -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<APIMessage> errors = new RestAPITestHelper()
.withAccessToken(getAdminInstitution1Access())
.withPath(API.LMS_SETUP_ENDPOINT)
.withMethod(HttpMethod.PUT)
.withBodyJson(modified)
.withExpectedStatus(HttpStatus.BAD_REQUEST)
.getAsObject(new TypeReference<List<APIMessage>>() {
});
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<APIMessage> 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<List<APIMessage>>() {
});
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<LmsSetup>() {
});
@ -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<LmsSetup>() {
});
@ -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<LmsSetup>() {
});
@ -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<LmsSetup>() {
});
@ -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<LmsSetup>() {
}).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<LmsSetup>() {
}).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)

View file

@ -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,