From 97f174d7409d7ca167ad86cf399dca7e70b0088e Mon Sep 17 00:00:00 2001 From: anhefti Date: Mon, 30 May 2022 16:03:28 +0200 Subject: [PATCH] SEBSERV-131 implementation --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 + .../gbl/client/ClientCredentials.java | 38 +++++++++- .../gui/content/action/ActionDefinition.java | 5 ++ .../content/configs/SEBClientConfigForm.java | 74 +++++++++++++++++++ .../clientconfig/GetClientCredentials.java | 41 ++++++++++ .../api/SEBClientConfigController.java | 20 +++++ src/main/resources/messages.properties | 6 ++ 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientCredentials.java 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 715e117f..87d85745 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 @@ -157,6 +157,7 @@ public final class API { public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration"; + public static final String SEB_CLIENT_CONFIG_CREDENTIALS_PATH_SEGMENT = "/credentials"; public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download"; public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentials.java b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentials.java index 52d5304d..f98378cd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentials.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/client/ClientCredentials.java @@ -8,20 +8,34 @@ package ch.ethz.seb.sebserver.gbl.client; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + /** Defines a simple data bean holding (encrypted) client credentials */ +@JsonIgnoreProperties(ignoreUnknown = true) public final class ClientCredentials { + public static final String ATTR_CLIENT_ID = "clientId"; + public static final String ATTR_SECRET = "secret"; + public static final String ATTR_ACCESS_TOKEN = "accessToken"; + /** The client id or client name parameter */ + @JsonProperty(ATTR_CLIENT_ID) public final CharSequence clientId; /** The client secret parameter */ + @JsonProperty(ATTR_SECRET) public final CharSequence secret; /** An client access token if supported */ + @JsonProperty(ATTR_ACCESS_TOKEN) public final CharSequence accessToken; + @JsonCreator public ClientCredentials( - final CharSequence clientId, - final CharSequence secret, - final CharSequence accessToken) { + @JsonProperty(ATTR_CLIENT_ID) final CharSequence clientId, + @JsonProperty(ATTR_SECRET) final CharSequence secret, + @JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken) { this.clientId = clientId; this.secret = secret; @@ -35,26 +49,44 @@ public final class ClientCredentials { this(clientId, secret, null); } + public CharSequence getClientId() { + return this.clientId; + } + + public CharSequence getSecret() { + return this.secret; + } + + public CharSequence getAccessToken() { + return this.accessToken; + } + + @JsonIgnore public boolean hasClientId() { return this.clientId != null && this.clientId.length() > 0; } + @JsonIgnore public boolean hasSecret() { return this.secret != null && this.secret.length() > 0; } + @JsonIgnore public boolean hasAccessToken() { return this.accessToken != null && this.accessToken.length() > 0; } + @JsonIgnore public String clientIdAsString() { return hasClientId() ? this.clientId.toString() : null; } + @JsonIgnore public String secretAsString() { return hasSecret() ? this.secret.toString() : null; } + @JsonIgnore public String accessTokenAsString() { return hasAccessToken() ? this.accessToken.toString() : null; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index ec138370..5d35d579 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -532,6 +532,11 @@ public enum ActionDefinition { ImageIcon.DELETE, PageStateDefinitionImpl.SEB_CLIENT_CONFIG_LIST, ActionCategory.FORM), + SEB_CLIENT_CONFIG_SHOW_CREDENTIALS( + new LocTextKey("sebserver.clientconfig.action.credentials"), + ImageIcon.SECURE, + PageStateDefinitionImpl.SEB_CLIENT_CONFIG_VIEW, + ActionCategory.FORM), SEB_EXAM_CONFIG_LIST( new LocTextKey("sebserver.examconfig.action.list"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java index 0f3633df..be3c58b6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBClientConfigForm.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.gui.content.configs; import java.io.IOException; +import java.util.function.Function; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -27,6 +28,7 @@ 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.api.EntityType; +import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; @@ -44,6 +46,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; 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.ModalInputDialog; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService; import ch.ethz.seb.sebserver.gui.service.remote.download.SEBClientConfigDownload; @@ -52,11 +55,13 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig. import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeleteClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientCredentials; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig; 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; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -76,6 +81,15 @@ public class SEBClientConfigForm implements TemplateComposer { private static final LocTextKey FORM_UPDATE_TIME_TEXT_KEY = new LocTextKey("sebserver.clientconfig.form.update.time"); + private static final LocTextKey CLIENT_CREDENTIALS_TITLE_TEXT_KEY = + new LocTextKey("sebserver.clientconfig.form.credentials.title"); + private static final LocTextKey CLIENT_CREDENTIALS_INFO_TEXT_KEY = + new LocTextKey("sebserver.clientconfig.form.credentials.info"); + private static final LocTextKey CLIENT_CREDENTIALS_NAME_TEXT_KEY = + new LocTextKey("sebserver.clientconfig.form.credentials.name"); + private static final LocTextKey CLIENT_CREDENTIALS_SECRET_TEXT_KEY = + new LocTextKey("sebserver.clientconfig.form.credentials.secret"); + private static final LocTextKey FORM_DATE_TEXT_KEY = new LocTextKey("sebserver.clientconfig.form.date"); private static final LocTextKey CLIENT_PURPOSE_TEXT_KEY = @@ -210,6 +224,12 @@ public class SEBClientConfigForm implements TemplateComposer { .withExec(this::deleteConnectionConfig) .publishIf(() -> modifyGrant && isReadonly) + .newAction(ActionDefinition.SEB_CLIENT_CONFIG_SHOW_CREDENTIALS) + .withEntityKey(entityKey) + .withExec(getClientCredentialFunction(this.pageService, this.cryptor)) + .ignoreMoveAwayFromEdit() + .publishIf(() -> modifyGrant && isReadonly) + .newAction(ActionDefinition.SEB_CLIENT_CONFIG_EXPORT) .withEntityKey(entityKey) .withExec(action -> { @@ -558,6 +578,60 @@ public class SEBClientConfigForm implements TemplateComposer { } } + public static Function getClientCredentialFunction( + final PageService pageService, + final Cryptor cryptor) { + + final RestService restService = pageService.getResourceService().getRestService(); + return action -> { + + final ClientCredentials credentials = restService + .getBuilder(GetClientCredentials.class) + .withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId) + .call() + .getOrThrow(); + + final WidgetFactory widgetFactory = pageService.getWidgetFactory(); + final ModalInputDialog dialog = new ModalInputDialog<>( + action.pageContext().getParent().getShell(), + widgetFactory); + + dialog.setDialogWidth(720); + + dialog.open( + CLIENT_CREDENTIALS_TITLE_TEXT_KEY, + action.pageContext(), + pc -> { + + final Composite content = widgetFactory.defaultPageLayout( + pc.getParent()); + + widgetFactory.labelLocalized( + content, + CustomVariant.TEXT_H3, + CLIENT_CREDENTIALS_INFO_TEXT_KEY); + + pageService.formBuilder( + action.pageContext().copyOf(content)) + .readonly(true) + .withDefaultSpanLabel(1) + .withDefaultSpanInput(6) + + .addField(FormBuilder.text( + "ClientId", + CLIENT_CREDENTIALS_NAME_TEXT_KEY, + credentials.clientIdAsString())) + + .addField(FormBuilder.password( + "ClientSecret", + CLIENT_CREDENTIALS_SECRET_TEXT_KEY, + cryptor.decrypt(credentials.secret).getOrThrow())) + .build(); + }); + return action; + }; + } + private static final class FormHandleAnchor { FormHandle formHandle; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientCredentials.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientCredentials.java new file mode 100644 index 00000000..1eeb6ee3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientCredentials.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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.seb.clientconfig; + +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.client.ClientCredentials; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetClientCredentials extends RestCall { + + public GetClientCredentials() { + super(new TypeKey<>( + CallType.GET_SINGLE, + null, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.SEB_CLIENT_CONFIG_ENDPOINT + + API.SEB_CLIENT_CONFIG_CREDENTIALS_PATH_SEGMENT + + API.MODEL_ID_VAR_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java index 605dd841..3aac116f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java @@ -37,6 +37,7 @@ 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.POSTMapper; +import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.model.Entity; @@ -81,6 +82,25 @@ public class SEBClientConfigController extends ActivatableEntityController ((SEBClientConfigDAO) this.entityDAO).getSEBClientCredentials(modelId)) + .getOrThrow(); + } + @RequestMapping( path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT, method = RequestMethod.GET, diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index b9d3b453..4b4127d7 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -810,6 +810,11 @@ sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to b sebserver.clientconfig.form.type.async=Use asymmetric-only encryption sebserver.clientconfig.form.type.async.tooltip=Use old asymmetric-only encryption (for SEB < 2.2) +sebserver.clientconfig.form.credentials.title=Client Credentials of Connection Configuration +sebserver.clientconfig.form.credentials.info=A SEB client that loads this connection configuration
uses the following credentials to securely connect to the SEB Server. +sebserver.clientconfig.form.credentials.name=Client Id +sebserver.clientconfig.form.credentials.secret=Secret + sebserver.clientconfig.config.purpose.START_EXAM=Starting an Exam sebserver.clientconfig.config.purpose.START_EXAM.tooltip=If the connection configuration is loaded via a SEB-Link, the local configuration will not be overwritten. sebserver.clientconfig.config.purpose.CONFIGURE_CLIENT=Configure a Client @@ -820,6 +825,7 @@ sebserver.clientconfig.action.list.view=View Connection Configuration sebserver.clientconfig.action.list.modify=Edit Connection Configuration sebserver.clientconfig.action.modify=Edit Connection Configuration sebserver.clientconfig.action.save=Save Connection Configuration +sebserver.clientconfig.action.credentials=Show Client Credentials sebserver.clientconfig.action.activate=Activate Connection Configuration sebserver.clientconfig.action.deactivate=Deactivate Connection Configuration sebserver.clientconfig.action.export=Export Connection Configuration