From 3823c033b9a4fea0fb7d0abf5c6449cc83532667 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 13 Aug 2019 09:45:37 +0200 Subject: [PATCH] added configuration option for login info url --- .../sonia/scm/config/ScmConfiguration.java | 16 ++++++++ .../packages/ui-types/src/Config.js | 1 + scm-ui/public/locales/de/config.json | 6 ++- scm-ui/public/locales/en/config.json | 6 ++- .../src/admin/components/form/ConfigForm.js | 2 + .../admin/components/form/GeneralSettings.js | 15 ++++++++ scm-ui/src/components/LoginInfo.js | 4 +- scm-ui/src/containers/Login.js | 18 ++++++--- scm-ui/src/modules/indexResource.js | 4 ++ .../sonia/scm/api/v2/resources/ConfigDto.java | 1 + .../api/v2/resources/IndexDtoGenerator.java | 11 +++++- ...ConfigDtoToScmConfigurationMapperTest.java | 2 + .../api/v2/resources/IndexResourceTest.java | 37 +++++++++++++++++-- ...ScmConfigurationToConfigDtoMapperTest.java | 2 + 14 files changed, 111 insertions(+), 14 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index 8d3db8b348..d23bbaf07d 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -75,6 +75,11 @@ public class ScmConfiguration implements Configuration { public static final String DEFAULT_PLUGINURL = "http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false"; + /** + * Default url for login information (plugin and feature tips on the login page). + */ + public static final String DEFAULT_LOGIN_INFO_URL = "https://login-info.scm-manager.org/api/v1/login-info"; + /** * Default plugin url from version 1.0 */ @@ -177,6 +182,9 @@ public class ScmConfiguration implements Configuration { @XmlElement(name = "namespace-strategy") private String namespaceStrategy = "UsernameNamespaceStrategy"; + @XmlElement(name = "login-info-url") + private String loginInfoUrl = DEFAULT_LOGIN_INFO_URL; + /** * Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)} @@ -216,6 +224,7 @@ public class ScmConfiguration implements Configuration { this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout; this.enabledXsrfProtection = other.enabledXsrfProtection; this.namespaceStrategy = other.namespaceStrategy; + this.loginInfoUrl = other.loginInfoUrl; } /** @@ -350,6 +359,9 @@ public class ScmConfiguration implements Configuration { return namespaceStrategy; } + public String getLoginInfoUrl() { + return loginInfoUrl; + } /** * Returns true if failed authenticators are skipped. @@ -477,6 +489,10 @@ public class ScmConfiguration implements Configuration { this.namespaceStrategy = namespaceStrategy; } + public void setLoginInfoUrl(String loginInfoUrl) { + this.loginInfoUrl = loginInfoUrl; + } + @Override // Only for permission checks, don't serialize to XML @XmlTransient diff --git a/scm-ui-components/packages/ui-types/src/Config.js b/scm-ui-components/packages/ui-types/src/Config.js index 0e82076848..5a9522585f 100644 --- a/scm-ui-components/packages/ui-types/src/Config.js +++ b/scm-ui-components/packages/ui-types/src/Config.js @@ -21,5 +21,6 @@ export type Config = { loginAttemptLimitTimeout: number, enabledXsrfProtection: boolean, namespaceStrategy: string, + loginInfoUrl: string, _links: Links }; diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index 20977583ac..f00b3e2357 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -43,7 +43,8 @@ "skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen", "plugin-url": "Plugin URL", "enabled-xsrf-protection": "XSRF Protection aktivieren", - "namespace-strategy": "Namespace Strategie" + "namespace-strategy": "Namespace Strategie", + "login-info-url": "Login Info URL", }, "validation": { "date-format-invalid": "Das Datumsformat ist ungültig", @@ -73,6 +74,7 @@ "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.", "proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.", "enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.", - "nameSpaceStrategyHelpText": "Strategie für Namespaces." + "nameSpaceStrategyHelpText": "Strategie für Namespaces.", + "loginInfoUrlHelpText": "URL zu den Login Informationen (Plugin und Feature Tipps auf der Login Seite)." } } diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index 1aa13a3150..62215df827 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -43,7 +43,8 @@ "skip-failed-authenticators": "Skip Failed Authenticators", "plugin-url": "Plugin URL", "enabled-xsrf-protection": "Enabled XSRF Protection", - "namespace-strategy": "Namespace Strategy" + "namespace-strategy": "Namespace Strategy", + "login-info-url": "Login Info URL" }, "validation": { "date-format-invalid": "The date format is not valid", @@ -73,6 +74,7 @@ "proxyUserHelpText": "The username for the proxy server authentication.", "proxyExcludesHelpText": "Glob patterns for hostnames, which should be excluded from proxy settings.", "enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.", - "nameSpaceStrategyHelpText": "The namespace strategy." + "nameSpaceStrategyHelpText": "The namespace strategy.", + "loginInfoUrlHelpText": "URL to login information (plugin and feature tips at login page)." } } diff --git a/scm-ui/src/admin/components/form/ConfigForm.js b/scm-ui/src/admin/components/form/ConfigForm.js index 20e2db2599..25a24c4d28 100644 --- a/scm-ui/src/admin/components/form/ConfigForm.js +++ b/scm-ui/src/admin/components/form/ConfigForm.js @@ -54,6 +54,7 @@ class ConfigForm extends React.Component { loginAttemptLimitTimeout: 0, enabledXsrfProtection: true, namespaceStrategy: "", + loginInfoUrl: "", _links: {} }, showNotification: false, @@ -119,6 +120,7 @@ class ConfigForm extends React.Component { {noPermissionNotification} { const { t, realmDescription, + loginInfoUrl, enabledXsrfProtection, namespaceStrategy, hasUpdatePermission, @@ -57,6 +59,15 @@ class GeneralSettings extends React.Component {
+
+ +
{ ); } + handleLoginInfoUrlChange = (value: string) => { + this.props.onChange(true, value, "loginInfoUrl"); + }; + handleRealmDescriptionChange = (value: string) => { this.props.onChange(true, value, "realmDescription"); }; diff --git a/scm-ui/src/components/LoginInfo.js b/scm-ui/src/components/LoginInfo.js index 3bd80eaddc..63ce6f3dbe 100644 --- a/scm-ui/src/components/LoginInfo.js +++ b/scm-ui/src/components/LoginInfo.js @@ -4,6 +4,7 @@ import InfoBox from "./InfoBox"; import type { InfoItem } from "./InfoItem"; type Props = { + loginInfoLink: string }; type State = { @@ -21,7 +22,8 @@ class LoginInfo extends React.Component { } componentDidMount() { - fetch("https://login-info.scm-manager.org/api/v1/login-info") + const { loginInfoLink } = this.props; + fetch(loginInfoLink) .then(response => response.json()) .then(info => { this.setState({ diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index 5d63698168..4b30f9cb0f 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -8,7 +8,7 @@ import { getLoginFailure } from "../modules/auth"; import { connect } from "react-redux"; -import { getLoginLink } from "../modules/indexResource"; +import { getLoginLink, getLoginInfoLink } from "../modules/indexResource"; import LoginForm from "../components/LoginForm"; import LoginInfo from "../components/LoginInfo"; import classNames from "classnames"; @@ -25,6 +25,7 @@ type Props = { loading: boolean, error?: Error, link: string, + loginInfoLink?: string, // dispatcher props login: (link: string, username: string, password: string) => void, @@ -49,19 +50,24 @@ class Login extends React.Component { }; render() { - const { authenticated, loading, error, classes } = this.props; + const { authenticated, loginInfoLink, loading, error, classes } = this.props; if (authenticated) { return this.renderRedirect(); } + let loginInfo; + if (loginInfoLink) { + loginInfo = + } + return (
-
+
- + {loginInfo}
@@ -75,11 +81,13 @@ const mapStateToProps = state => { const loading = isLoginPending(state); const error = getLoginFailure(state); const link = getLoginLink(state); + const loginInfoLink = getLoginInfoLink(state); return { authenticated, loading, error, - link + link, + loginInfoLink }; }; diff --git a/scm-ui/src/modules/indexResource.js b/scm-ui/src/modules/indexResource.js index 9bfa620674..9676faffba 100644 --- a/scm-ui/src/modules/indexResource.js +++ b/scm-ui/src/modules/indexResource.js @@ -168,6 +168,10 @@ export function getSvnConfigLink(state: Object) { return getLink(state, "svnConfig"); } +export function getLoginInfoLink(state: Object) { + return getLink(state, "loginInfo"); +} + export function getUserAutoCompleteLink(state: Object): string { const link = getLinkCollection(state, "autocomplete").find( i => i.name === "users" diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java index 1852d6fdc4..30d936d4c5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -32,6 +32,7 @@ public class ConfigDto extends HalRepresentation { private long loginAttemptLimitTimeout; private boolean enabledXsrfProtection; private String namespaceStrategy; + private String loginInfoUrl; @Override @SuppressWarnings("squid:S1185") // We want to have this method available in this package diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index c7b52861dc..cace57577c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Link; @@ -7,6 +8,7 @@ import de.otto.edison.hal.Links; import org.apache.shiro.SecurityUtils; import sonia.scm.SCMContextProvider; import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.config.ScmConfiguration; import sonia.scm.group.GroupPermissions; import sonia.scm.plugin.PluginPermissions; import sonia.scm.repository.RepositoryRolePermissions; @@ -23,11 +25,13 @@ public class IndexDtoGenerator extends HalAppenderMapper { private final ResourceLinks resourceLinks; private final SCMContextProvider scmContextProvider; + private final ScmConfiguration configuration; @Inject - public IndexDtoGenerator(ResourceLinks resourceLinks, SCMContextProvider scmContextProvider) { + public IndexDtoGenerator(ResourceLinks resourceLinks, SCMContextProvider scmContextProvider, ScmConfiguration configuration) { this.resourceLinks = resourceLinks; this.scmContextProvider = scmContextProvider; + this.configuration = configuration; } public IndexDto generate() { @@ -36,6 +40,11 @@ public class IndexDtoGenerator extends HalAppenderMapper { builder.self(resourceLinks.index().self()); builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self())); + String loginInfoUrl = configuration.getLoginInfoUrl(); + if (!Strings.isNullOrEmpty(loginInfoUrl)) { + builder.single(link("loginInfo", loginInfoUrl)); + } + if (SecurityUtils.getSubject().isAuthenticated()) { builder.single( link("me", resourceLinks.me().self()), diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java index dd09e50266..e7ae446185 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java @@ -50,6 +50,7 @@ public class ConfigDtoToScmConfigurationMapperTest { assertEquals(40 , config.getLoginAttemptLimitTimeout()); assertTrue(config.isEnabledXsrfProtection()); assertEquals("username", config.getNamespaceStrategy()); + assertEquals("https://scm-manager.org/login-info", config.getLoginInfoUrl()); } private ConfigDto createDefaultDto() { @@ -73,6 +74,7 @@ public class ConfigDtoToScmConfigurationMapperTest { configDto.setLoginAttemptLimitTimeout(40); configDto.setEnabledXsrfProtection(true); configDto.setNamespaceStrategy("username"); + configDto.setLoginInfoUrl("https://scm-manager.org/login-info"); return configDto; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java index 93099cf5ea..9dfa5fca28 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java @@ -3,9 +3,11 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import org.assertj.core.api.Assertions; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import sonia.scm.SCMContextProvider; +import sonia.scm.config.ScmConfiguration; import java.net.URI; import java.util.Optional; @@ -19,9 +21,22 @@ public class IndexResourceTest { @Rule public final ShiroRule shiroRule = new ShiroRule(); - private final SCMContextProvider scmContextProvider = mock(SCMContextProvider.class); - private final IndexDtoGenerator indexDtoGenerator = new IndexDtoGenerator(ResourceLinksMock.createMock(URI.create("/")), scmContextProvider); - private final IndexResource indexResource = new IndexResource(indexDtoGenerator); + private ScmConfiguration configuration; + private SCMContextProvider scmContextProvider; + private IndexResource indexResource; + + + @Before + public void setUpObjectUnderTest() { + this.configuration = new ScmConfiguration(); + this.scmContextProvider = mock(SCMContextProvider.class); + IndexDtoGenerator generator = new IndexDtoGenerator( + ResourceLinksMock.createMock(URI.create("/")), + scmContextProvider, + configuration + ); + this.indexResource = new IndexResource(generator); + } @Test public void shouldRenderLoginUrlsForUnauthenticatedRequest() { @@ -30,6 +45,22 @@ public class IndexResourceTest { Assertions.assertThat(index.getLinks().getLinkBy("login")).matches(Optional::isPresent); } + @Test + public void shouldRenderLoginInfoUrl() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinkBy("loginInfo")).isPresent(); + } + + @Test + public void shouldNotRenderLoginInfoUrlWhenNoUrlIsConfigured() { + configuration.setLoginInfoUrl(""); + + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinkBy("loginInfo")).isNotPresent(); + } + @Test public void shouldRenderSelfLinkForUnauthenticatedRequest() { IndexDto index = indexResource.getIndex(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java index ee940a9721..6ae6d5d2f1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java @@ -80,6 +80,7 @@ public class ScmConfigurationToConfigDtoMapperTest { assertEquals(2 , dto.getLoginAttemptLimitTimeout()); assertTrue(dto.isEnabledXsrfProtection()); assertEquals("username", dto.getNamespaceStrategy()); + assertEquals("https://scm-manager.org/login-info", dto.getLoginInfoUrl()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); @@ -118,6 +119,7 @@ public class ScmConfigurationToConfigDtoMapperTest { config.setLoginAttemptLimitTimeout(2); config.setEnabledXsrfProtection(true); config.setNamespaceStrategy("username"); + config.setLoginInfoUrl("https://scm-manager.org/login-info"); return config; }