diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index 7912beba..f62042b7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -80,7 +80,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E .ignoring() .antMatchers("/error") .antMatchers(this.examAPIDiscoveryEndpoint) - .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**"); + .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**") + .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**") + .antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT); } @RequestMapping("/error") diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 5310e5bf..d7d5c99d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -23,6 +23,9 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; /** Global Constants used in SEB Server web-service as well as in web-gui component */ public final class Constants { + public static final String DEFAULT_LANG_CODE = "en"; + public static final String DEFAULT_TIME_ZONE_CODE = "UTC"; + public static final int SEB_FILE_HEADER_SIZE = 4; public static final int JN_CRYPTOR_ITERATIONS = 10000; public static final int JN_CRYPTOR_VERSION_HEADER_SIZE = 1; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 30a9f034..c0f0c0ba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -46,8 +46,13 @@ public final class API { public static final String SELF_PATH_SEGMENT = "/self"; public static final String INFO_ENDPOINT = "/info"; + public static final String INFO_PARAM_INST_SUFFIX = "urlSuffix"; + public static final String INFO_INST_PATH_SEGMENT = "/institution"; + public static final String INFO_INST_ENDPOINT = INFO_INST_PATH_SEGMENT + "/{" + INFO_PARAM_INST_SUFFIX + "}"; public static final String LOGO_PATH_SEGMENT = "/logo"; - public static final String INSTITUTIONAL_LOGO_PATH = LOGO_PATH_SEGMENT + "/{urlSuffix}"; + + public static final String INSTITUTIONAL_LOGO_PATH = LOGO_PATH_SEGMENT + "/{" + INFO_PARAM_INST_SUFFIX + "}"; + public static final String REGISTER_ENDPOINT = "/register"; public static final String NAMES_PATH_SEGMENT = "/names"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java index e9c0ad1d..4247689e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java @@ -436,6 +436,10 @@ public final class Utils { return toCharArray(CharBuffer.wrap(chars)); } + public static void clear(final CharSequence charSequence) { + clearCharArray(toCharArray(charSequence)); + } + public static String toString(final CharSequence charSequence) { if (charSequence == null) { return null; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java index ee96baa2..321414f7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java @@ -38,8 +38,6 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter { public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( // OAuth entry-points new AntPathRequestMatcher(API.OAUTH_REVOKE_TOKEN_ENDPOINT), - // GUI entry-point -// new AntPathRequestMatcher(guiEntryPoint), // RAP/RWT resources has to be accessible new AntPathRequestMatcher("/rwt-resources/**"), // project specific static resources diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java index 3717cc7b..b157382d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64InputStream; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -40,12 +41,16 @@ import org.springframework.web.client.RestTemplate; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; 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.auth.WebserviceURIService; import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection; @Lazy @Component -final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint { +@GuiProfile +public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private static final String INST_SUFFIX_ATTRIBUTE = "instSuffix"; private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class); @@ -111,7 +116,7 @@ final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntry final String logoImageBase64 = requestLogoImage(institutionalEndpoint); if (StringUtils.isNotBlank(logoImageBase64)) { request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64); - request.getSession().setAttribute("themeId", "sms"); + request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, institutionalEndpoint); forwardToEntryPoint(request, response, this.guiEntryPoint); } else { request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE); @@ -132,19 +137,31 @@ final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntry dispatcher.forward(request, response); } - private String extractInstitutionalEndpoint(final HttpServletRequest request) { + public static String extractInstitutionalEndpoint(final HttpServletRequest request) { final String requestURI = request.getRequestURI(); if (log.isDebugEnabled()) { log.debug("Trying to verify insitution from requested entrypoint url: {}", requestURI); } - final String instPrefix = requestURI.replaceAll("/", ""); - if (StringUtils.isBlank(instPrefix)) { + try { + return requestURI.substring( + requestURI.lastIndexOf(Constants.SLASH) + 1, + requestURI.length()); + } catch (final Exception e) { + log.error("Fauled to extract institutional URL suffix: {}", e.getMessage()); return null; } + } - return instPrefix; + public static String extractInstitutionalEndpoint() { + try { + final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE); + return (attribute != null) ? String.valueOf(attribute) : null; + } catch (final Exception e) { + log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage()); + return null; + } } private String requestLogoImage(final String institutionalEndpoint) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LoginPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LoginPage.java index bd60d783..305dcc67 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LoginPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LoginPage.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.gui.content; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; @@ -20,6 +21,7 @@ import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -28,6 +30,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.impl.DefaultRegisterPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext; import ch.ethz.seb.sebserver.gui.widget.Message; @@ -44,12 +47,20 @@ public class LoginPage implements TemplateComposer { private final AuthorizationContextHolder authorizationContextHolder; private final WidgetFactory widgetFactory; private final I18nSupport i18nSupport; + private final DefaultRegisterPage defaultRegisterPage; + private final boolean registreringEnabled; + + public LoginPage( + final PageService pageService, + final DefaultRegisterPage defaultRegisterPage, + @Value("${sebserver.gui.self-registering:false}") final Boolean registreringEnabled) { - public LoginPage(final PageService pageService) { this.pageService = pageService; this.authorizationContextHolder = pageService.getAuthorizationContextHolder(); this.widgetFactory = pageService.getWidgetFactory(); this.i18nSupport = pageService.getI18nSupport(); + this.defaultRegisterPage = defaultRegisterPage; + this.registreringEnabled = BooleanUtils.toBoolean(registreringEnabled); } @Override @@ -61,7 +72,7 @@ public class LoginPage implements TemplateComposer { rowLayout.marginWidth = 20; rowLayout.marginRight = 100; loginGroup.setLayout(rowLayout); - loginGroup.setData(RWT.CUSTOM_VARIANT, "login"); + loginGroup.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key); final Label name = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.username"); name.setLayoutData(new GridData(300, -1)); @@ -75,15 +86,19 @@ public class LoginPage implements TemplateComposer { final Text loginPassword = this.widgetFactory.passwordInput(loginGroup); loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); - final Button button = this.widgetFactory.buttonLocalized(loginGroup, "sebserver.login.login"); - gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); - gridData.verticalIndent = 10; - button.setLayoutData(gridData); - final SEBServerAuthorizationContext authorizationContext = this.authorizationContextHolder .getAuthorizationContext(RWT.getUISession().getHttpSession()); - button.addListener(SWT.Selection, event -> { + final Composite buttons = new Composite(loginGroup, SWT.NONE); + buttons.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + buttons.setLayout(new GridLayout(2, false)); + buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key); + + final Button loginButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.login"); + gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); + gridData.verticalIndent = 10; + loginButton.setLayoutData(gridData); + loginButton.addListener(SWT.Selection, event -> { login( pageContext, loginName.getText(), @@ -117,6 +132,15 @@ public class LoginPage implements TemplateComposer { } }); + if (this.registreringEnabled) { + final Button registerButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.register"); + gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); + gridData.verticalIndent = 10; + registerButton.setLayoutData(gridData); + registerButton.addListener(SWT.Selection, event -> { + pageContext.forwardToPage(this.defaultRegisterPage); + }); + } } private void login( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java new file mode 100644 index 00000000..cad8eb43 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2020 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.content; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.BooleanUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityName; +import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gui.InstitutionalAuthenticationEntryPoint; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +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.PageService; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionInfo; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Lazy +@Component +@GuiProfile +public class RegisterPage implements TemplateComposer { + + private static final Logger log = LoggerFactory.getLogger(RegisterPage.class); + + static final LocTextKey TITLE_TEXT_KEY = + new LocTextKey("sebserver.login.register.form.title"); + static final LocTextKey FORM_PASSWORD_CONFIRM_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.password.confirm"); + static final LocTextKey FORM_PASSWORD_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.password"); + static final LocTextKey FORM_ROLES_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.roles"); + static final LocTextKey FORM_TIMEZONE_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.timezone"); + static final LocTextKey FORM_MAIL_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.mail"); + static final LocTextKey FORM_USERNAME_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.username"); + static final LocTextKey FORM_NAME_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.name"); + static final LocTextKey FORM_INSTITUTION_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.institution"); + static final LocTextKey FORM_LANG_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.language"); + + private final PageService pageService; + private final ResourceService resourceService; + private final WidgetFactory widgetFactory; + private final I18nSupport i18nSupport; + private final WebserviceURIService webserviceURIService; + private final RestTemplate restTemplate; + private final boolean multilingual; + + protected RegisterPage( + final PageService pageService, + final WebserviceURIService webserviceURIService, + final ClientHttpRequestFactoryService clientHttpRequestFactoryService, + @Value("${sebserver.gui.multilingual:false}") final Boolean multilingual) { + + this.pageService = pageService; + this.resourceService = pageService.getResourceService(); + this.widgetFactory = pageService.getWidgetFactory(); + this.i18nSupport = pageService.getI18nSupport(); + this.webserviceURIService = webserviceURIService; + this.multilingual = BooleanUtils.toBoolean(multilingual); + + this.restTemplate = new RestTemplate(); + + final ClientHttpRequestFactory clientHttpRequestFactory = clientHttpRequestFactoryService + .getClientHttpRequestFactory() + .getOrThrow(); + + this.restTemplate.setRequestFactory(clientHttpRequestFactory); + } + + @Override + public void compose(final PageContext pageContext) { + + final String institutionId = InstitutionalAuthenticationEntryPoint + .extractInstitutionalEndpoint(); + + final List institutions = this.pageService + .getRestService() + .getBuilder(GetInstitutionInfo.class) + .withRestTemplate(this.restTemplate) + .withURIVariable(API.INFO_PARAM_INST_SUFFIX, institutionId) + .call() + .getOrThrow(); + + final boolean definedInstitution = institutions.size() == 1; + final Supplier>> instResources = () -> institutions + .stream() + .map(entityName -> new Tuple<>(entityName.modelId, entityName.name)) + .sorted(ResourceService.RESOURCE_COMPARATOR) + .collect(Collectors.toList()); + + final Composite content = this.widgetFactory.defaultPageLayout( + pageContext.getParent(), + TITLE_TEXT_KEY); + content.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key); + + // The UserAccount form + final FormBuilder formBuilder = this.pageService.formBuilder( + pageContext.copyOf(content)) + .readonly(false) + .putStaticValueIf( + () -> !this.multilingual, + Domain.USER.ATTR_LANGUAGE, + "en") + .addField(FormBuilder.singleSelection( + Domain.USER.ATTR_INSTITUTION_ID, + FORM_INSTITUTION_TEXT_KEY, + (definedInstitution) ? institutions.get(0).modelId : null, + instResources) + .readonly(definedInstitution)) + .addField(FormBuilder.text( + Domain.USER.ATTR_NAME, + FORM_NAME_TEXT_KEY)) + .addField(FormBuilder.text( + Domain.USER.ATTR_USERNAME, + FORM_USERNAME_TEXT_KEY)) + .addField(FormBuilder.text( + Domain.USER.ATTR_EMAIL, + FORM_MAIL_TEXT_KEY)) + .addFieldIf( + () -> this.multilingual, + () -> FormBuilder.singleSelection( + Domain.USER.ATTR_LANGUAGE, + FORM_LANG_TEXT_KEY, + Constants.DEFAULT_LANG_CODE, + this.resourceService::languageResources)) + .addField(FormBuilder.singleSelection( + Domain.USER.ATTR_TIMEZONE, + FORM_TIMEZONE_TEXT_KEY, + Constants.DEFAULT_TIME_ZONE_CODE, + this.resourceService::timeZoneResources)) + .addField(FormBuilder.text( + PasswordChange.ATTR_NAME_NEW_PASSWORD, + FORM_PASSWORD_TEXT_KEY) + .asPasswordField()) + .addField(FormBuilder.text( + PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, + FORM_PASSWORD_CONFIRM_TEXT_KEY) + .asPasswordField()); + + //formBuilder.formParent.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key); + formBuilder.build(); + + final Composite buttons = new Composite(content, SWT.NONE); + buttons.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + buttons.setLayout(new GridLayout(2, false)); + buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key); + + final Button registerButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.register"); + GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); + gridData.verticalIndent = 10; + registerButton.setLayoutData(gridData); + registerButton.addListener(SWT.Selection, event -> { + + }); + + final Button cancelButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.overall.action.cancel"); + gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); + gridData.verticalIndent = 10; + cancelButton.setLayoutData(gridData); + cancelButton.addListener(SWT.Selection, event -> { + pageContext.forwardToLoginPage(); + }); + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java index 294a4203..265de53b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java @@ -10,8 +10,10 @@ package ch.ethz.seb.sebserver.gui.content; import java.util.function.BooleanSupplier; +import org.apache.commons.lang3.BooleanUtils; import org.apache.tomcat.util.buf.StringUtils; import org.eclipse.swt.widgets.Composite; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -74,16 +76,21 @@ public class UserAccountForm implements TemplateComposer { new LocTextKey("sebserver.useraccount.form.name"); static final LocTextKey FORM_INSTITUTION_TEXT_KEY = new LocTextKey("sebserver.useraccount.form.institution"); + static final LocTextKey FORM_LANG_TEXT_KEY = + new LocTextKey("sebserver.useraccount.form.language"); private final PageService pageService; private final ResourceService resourceService; + private final boolean multilingual; protected UserAccountForm( final PageService pageService, - final ResourceService resourceService) { + final ResourceService resourceService, + @Value("${sebserver.gui.multilingual:false}") final Boolean multilingual) { this.pageService = pageService; this.resourceService = resourceService; + this.multilingual = BooleanUtils.toBoolean(multilingual); } @Override @@ -149,7 +156,8 @@ public class UserAccountForm implements TemplateComposer { .putStaticValueIf(isNotNew, Domain.USER.ATTR_INSTITUTION_ID, String.valueOf(userAccount.getInstitutionId())) - .putStaticValue( + .putStaticValueIf( + () -> !this.multilingual, Domain.USER.ATTR_LANGUAGE, "en") .addFieldIf( @@ -172,12 +180,13 @@ public class UserAccountForm implements TemplateComposer { Domain.USER.ATTR_EMAIL, FORM_MAIL_TEXT_KEY, userAccount.getEmail())) -// .addField(FormBuilder.singleSelection( -// Domain.USER.ATTR_LANGUAGE, -// "sebserver.useraccount.form.language", -// userAccount.getLanguage().getLanguage(), -// this.resourceService::languageResources) -// .readonly(true)) + .addFieldIf( + () -> this.multilingual, + () -> FormBuilder.singleSelection( + Domain.USER.ATTR_LANGUAGE, + FORM_LANG_TEXT_KEY, + userAccount.getLanguage().getLanguage(), + this.resourceService::languageResources)) .addField(FormBuilder.singleSelection( Domain.USER.ATTR_TIMEZONE, FORM_TIMEZONE_TEXT_KEY, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java index 588759cc..a39744a2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultLoginPage.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.page.impl; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.content.LoginPage; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; @@ -21,6 +22,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; * SEBLogin template */ @Lazy @Component +@GuiProfile public class DefaultLoginPage implements PageDefinition { @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java index f34165f9..a0b72f72 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultMainPage.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.page.impl; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.content.MainPage; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; @@ -21,6 +22,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; * SEBMainPage template */ @Lazy @Component +@GuiProfile public class DefaultMainPage implements PageDefinition { @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultRegisterPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultRegisterPage.java new file mode 100644 index 00000000..01b4848f --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/DefaultRegisterPage.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 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.page.impl; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.RegisterPage; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; +import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; + +@Lazy +@Component +@GuiProfile +public class DefaultRegisterPage implements PageDefinition { + + @Override + public Class composer() { + return DefaultPageLayout.class; + } + + @Override + public PageContext applyPageContext(final PageContext pageContext) { + return pageContext.withAttribute( + AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME, + RegisterPage.class.getName()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index 369182d8..9895de7e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -59,6 +59,7 @@ public abstract class RestCall { GET_DEPENDENCIES, GET_LIST, NEW, + REGISTER, SAVE, DELETE, ACTIVATION_ACTIVATE, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitutionInfo.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitutionInfo.java new file mode 100644 index 00000000..fd6efd3f --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/institution/GetInstitutionInfo.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 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.institution; + +import java.util.List; + +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.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityName; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetInstitutionInfo extends RestCall> { + + public GetInstitutionInfo() { + super(new TypeKey<>( + CallType.GET_NAMES, + EntityType.INSTITUTION, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/RegisterNewUser.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/RegisterNewUser.java new file mode 100644 index 00000000..546813fd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/RegisterNewUser.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 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.useraccount; + +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.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class RegisterNewUser extends RestCall { + + public RegisterNewUser() { + super(new TypeKey<>( + CallType.REGISTER, + EntityType.USER, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.REGISTER_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/WebserviceURIService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/WebserviceURIService.java index 150fae4c..a3565b49 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/WebserviceURIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/WebserviceURIService.java @@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @GuiProfile public class WebserviceURIService { + private final String servletContextPath; private final String webserviceServerAddress; private final UriComponentsBuilder webserviceURIBuilder; @@ -26,12 +27,15 @@ public class WebserviceURIService { @Value("${sebserver.gui.webservice.protocol}") final String webserviceProtocol, @Value("${sebserver.gui.webservice.address}") final String webserviceServerAdress, @Value("${sebserver.gui.webservice.port}") final String webserviceServerPort, + @Value("${server.servlet.context-path}") final String servletContextPath, @Value("${sebserver.gui.webservice.apipath}") final String webserviceAPIPath) { + this.servletContextPath = servletContextPath; this.webserviceServerAddress = webserviceProtocol + "://" + webserviceServerAdress + ":" + webserviceServerPort; this.webserviceURIBuilder = UriComponentsBuilder .fromHttpUrl(webserviceProtocol + "://" + webserviceServerAdress) .port(webserviceServerPort) + .path(servletContextPath) .path(webserviceAPIPath); } @@ -45,12 +49,14 @@ public class WebserviceURIService { public String getOAuthTokenURI() { return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress) + .path(this.servletContextPath) .path(API.OAUTH_TOKEN_ENDPOINT) .toUriString(); } public String getOAuthRevokeTokenURI() { return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress) + .path(this.servletContextPath) .path(API.OAUTH_REVOKE_TOKEN_ENDPOINT) .toUriString(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index c3cc2b7e..5a3121b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -152,7 +152,10 @@ public class WidgetFactory { CONFIG_INPUT_READONLY("inputreadonly"), DARK_COLOR_LABEL("colordark"), - LIGHT_COLOR_LABEL("colorlight") + LIGHT_COLOR_LABEL("colorlight"), + + LOGIN("login"), + LOGIN_BACK("login-back") ; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java index 9d59c2df..2082757c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java @@ -9,7 +9,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import java.util.Collection; +import java.util.stream.Collectors; +import org.apache.commons.lang3.BooleanUtils; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; @@ -51,12 +54,28 @@ public class InfoController { .all(null, true) .getOrThrow() .stream() - .filter(inst -> inst.urlSuffix != null && urlSuffix.endsWith(inst.urlSuffix)) + .filter(inst -> inst.urlSuffix != null && urlSuffix.equals(inst.urlSuffix)) .findFirst() .map(inst -> inst.logoImage) .orElse(null); } + @RequestMapping( + path = API.INFO_INST_ENDPOINT, + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + public Collection getInstitutionInfo(@PathVariable(required = false) final String urlSuffix) { + return this.institutionDAO + .all(null, true) + .getOrThrow() + .stream() + .filter(inst -> BooleanUtils.isTrue(inst.active) && + (inst.urlSuffix == null || + urlSuffix.equals(inst.urlSuffix))) + .map(inst -> inst.getEntityKey()) + .collect(Collectors.toList()); + } + @RequestMapping( path = API.PRIVILEGES_PATH_SEGMENT, method = RequestMethod.GET, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java new file mode 100644 index 00000000..60c1ad84 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020 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.webservice.weblayer.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTimeZone; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; +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; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; + +@WebServiceProfile +@RestController +@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.REGISTER_ENDPOINT) +public class RegisterUserController { + + private final InstitutionDAO institutionDAO; + private final UserActivityLogDAO userActivityLogDAO; + private final UserDAO userDAO; + private final ClientCredentialService clientCredentialService; + + protected RegisterUserController( + final InstitutionDAO institutionDAO, + final UserActivityLogDAO userActivityLogDAO, + final UserDAO userDAO, + final ClientCredentialService clientCredentialService, + @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) { + + this.institutionDAO = institutionDAO; + this.userActivityLogDAO = userActivityLogDAO; + this.userDAO = userDAO; + this.clientCredentialService = clientCredentialService; + } + + @RequestMapping( + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public UserInfo registerNewUser( + @RequestParam(name = Domain.USER.ATTR_INSTITUTION_ID, required = true) final String institutionId, + @RequestParam(name = Domain.USER.ATTR_NAME, required = true) final String name, + @RequestParam(name = Domain.USER.ATTR_USERNAME, required = true) final String username, + @RequestParam(name = Domain.USER.ATTR_EMAIL, required = false) final String email, + @RequestParam( + name = Domain.USER.ATTR_LANGUAGE, + required = false, + defaultValue = Constants.DEFAULT_LANG_CODE) final String lang, + @RequestParam( + name = Domain.USER.ATTR_TIMEZONE, + required = false, + defaultValue = Constants.DEFAULT_TIME_ZONE_CODE) final String timezone, + @RequestParam(name = PasswordChange.ATTR_NAME_NEW_PASSWORD, required = true) final String pwd, + @RequestParam(name = PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, required = true) final String rpwd) { + + final Collection errors = new ArrayList<>(); + + // check institution info + Long instId = null; + if (StringUtils.isNotBlank(institutionId)) { + try { + instId = Long.parseLong(institutionId); + } catch (final Exception e) { + instId = this.institutionDAO + .all(null, true) + .getOrThrow() + .stream() + .filter(inst -> inst.urlSuffix != null && institutionId.equals(inst.urlSuffix)) + .findFirst() + .map(inst -> inst.id) + .orElse(null); + } + } + + if (instId == null) { + errors.add(APIMessage.fieldValidationError( + new FieldError( + "user", + Domain.USER.ATTR_INSTITUTION_ID, + "user:institutionId:notNull"))); + } + + // check password-match + final CharSequence rawPWD = this.clientCredentialService.decrypt(pwd); + final CharSequence rawRPWD = this.clientCredentialService.decrypt(rpwd); + if (!rawPWD.equals(rawRPWD)) { + errors.add(APIMessage.fieldValidationError( + new FieldError( + "passwordChange", + PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, + "user:confirmNewPassword:password.mismatch"))); + } + + if (!errors.isEmpty()) { + throw new APIMessageException(errors); + } + + final UserMod user = new UserMod( + null, + instId, + name, + username, + rawPWD, + rawRPWD, + email, + Locale.forLanguageTag(lang), + DateTimeZone.forID(timezone), + new HashSet<>(Arrays.asList(UserRole.EXAM_SUPPORTER.name()))); + + return this.userDAO.createNew(user) + .flatMap(this.userActivityLogDAO::logCreate) + .map(u -> { + Utils.clear(rawPWD); + Utils.clear(rawRPWD); + return u; + }) + .getOrThrow(); + } + +} diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index 57722038..3efe1d93 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -1,6 +1,5 @@ server.address=localhost server.port=8080 -server.servlet.context-path=/ sebserver.gui.entrypoint=/gui sebserver.gui.webservice.protocol=http diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 568065da..0ab3a411 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -1,6 +1,5 @@ server.address=localhost server.port=8090 -server.servlet.context-path=/ logging.file=log/sebserver.log diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index edf11caf..7a9c6c95 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -110,6 +110,7 @@ sebserver.webservice.lms.address.alias= ########################################################## ### SEB Server GUI configuration +sebserver.gui.self-registering=true sebserver.gui.multilingual=false sebserver.gui.supported.languages=en sebserver.gui.entrypoint=/gui diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0aec533f..f26cda57 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -107,6 +107,10 @@ sebserver.logout.invalid-session.message=You have been signed out because of a u sebserver.login.password.change=Information sebserver.login.password.change.success=The password was successfully changed. Please sign in with your new password +sebserver.login.register=Register +sebserver.login.register.form.title=Register As New User + + ################################ # Main Page ################################ diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index b73a2860..573882da 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -120,6 +120,10 @@ Label.colorlight { padding: 2px 5px 2px 5px; } +Composite { + background-color: transparent; +} + Composite.bordered { border: 2px; } @@ -177,6 +181,12 @@ Composite.login { border-radius: 2px; } +Composite.login-back { + background-color: #EAECEE; + margin: 0px 0 0 0; + padding: 0px 0px 0px 0px; +} + Composite.error { border: 1px solid #aa0000; border-radius: 1px; diff --git a/src/main/resources/static/images/save.png b/src/main/resources/static/images/save.png index c318014d..0c878999 100644 Binary files a/src/main/resources/static/images/save.png and b/src/main/resources/static/images/save.png differ diff --git a/src/main/resources/static/images/save_alt.png b/src/main/resources/static/images/save_alt.png new file mode 100644 index 00000000..c318014d Binary files /dev/null and b/src/main/resources/static/images/save_alt.png differ diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/GuiIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/GuiIntegrationTest.java index 7757bbcf..0df19573 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/GuiIntegrationTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/GuiIntegrationTest.java @@ -74,7 +74,7 @@ public abstract class GuiIntegrationTest { final HttpSession sessionMock = Mockito.mock(HttpSession.class); final WebserviceURIService webserviceURIService = new WebserviceURIService( - "http", "localhost", "8080", this.endpoint); + "http", "localhost", "8080", "/", this.endpoint); final ClientHttpRequestFactoryService clientHttpRequestFactoryService = Mockito .mock(ClientHttpRequestFactoryService.class);