registration page

This commit is contained in:
anhefti 2020-01-23 16:46:05 +01:00
parent ed8387216b
commit 2f64cf92e0
27 changed files with 615 additions and 31 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<EntityName> 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<List<Tuple<String>>> 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();
});
}
}

View file

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

View file

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

View file

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

View file

@ -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<? extends TemplateComposer> composer() {
return DefaultPageLayout.class;
}
@Override
public PageContext applyPageContext(final PageContext pageContext) {
return pageContext.withAttribute(
AttributeKeys.PAGE_TEMPLATE_COMPOSER_NAME,
RegisterPage.class.getName());
}
}

View file

@ -59,6 +59,7 @@ public abstract class RestCall<T> {
GET_DEPENDENCIES,
GET_LIST,
NEW,
REGISTER,
SAVE,
DELETE,
ACTIVATION_ACTIVATE,

View file

@ -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<List<EntityName>> {
public GetInstitutionInfo() {
super(new TypeKey<>(
CallType.GET_NAMES,
EntityType.INSTITUTION,
new TypeReference<List<EntityName>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT);
}
}

View file

@ -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<UserInfo> {
public RegisterNewUser() {
super(new TypeKey<>(
CallType.REGISTER,
EntityType.USER,
new TypeReference<UserInfo>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.REGISTER_ENDPOINT);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
server.address=localhost
server.port=8080
server.servlet.context-path=/
sebserver.gui.entrypoint=/gui
sebserver.gui.webservice.protocol=http

View file

@ -1,6 +1,5 @@
server.address=localhost
server.port=8090
server.servlet.context-path=/
logging.file=log/sebserver.log

View file

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

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

View file

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