diff --git a/docs/de/user/admin/assets/administration-cloudogu-platform-confirmation.png b/docs/de/user/admin/assets/administration-cloudogu-platform-confirmation.png deleted file mode 100644 index c447517010..0000000000 Binary files a/docs/de/user/admin/assets/administration-cloudogu-platform-confirmation.png and /dev/null differ diff --git a/docs/de/user/admin/assets/administration-plugin-center-connected.png b/docs/de/user/admin/assets/administration-plugin-center-connected.png deleted file mode 100644 index 38f7536c24..0000000000 Binary files a/docs/de/user/admin/assets/administration-plugin-center-connected.png and /dev/null differ diff --git a/docs/de/user/admin/assets/administration-plugin-center-not-connected.png b/docs/de/user/admin/assets/administration-plugin-center-not-connected.png deleted file mode 100644 index 22d5d4413a..0000000000 Binary files a/docs/de/user/admin/assets/administration-plugin-center-not-connected.png and /dev/null differ diff --git a/docs/de/user/admin/assets/administration-setings-connected.png b/docs/de/user/admin/assets/administration-setings-connected.png deleted file mode 100644 index 9b326e56ec..0000000000 Binary files a/docs/de/user/admin/assets/administration-setings-connected.png and /dev/null differ diff --git a/docs/de/user/admin/assets/administration-setings-not-connected.png b/docs/de/user/admin/assets/administration-setings-not-connected.png deleted file mode 100644 index a1a0f43d09..0000000000 Binary files a/docs/de/user/admin/assets/administration-setings-not-connected.png and /dev/null differ diff --git a/docs/de/user/admin/assets/cloudogu-platform-login.png b/docs/de/user/admin/assets/cloudogu-platform-login.png deleted file mode 100644 index 5806089757..0000000000 Binary files a/docs/de/user/admin/assets/cloudogu-platform-login.png and /dev/null differ diff --git a/docs/de/user/admin/plugins.md b/docs/de/user/admin/plugins.md index 2b6c90cc78..4f765e7681 100644 --- a/docs/de/user/admin/plugins.md +++ b/docs/de/user/admin/plugins.md @@ -8,26 +8,6 @@ Die Plugins können über Aktions-Icons auf den Kacheln verwaltet werden. System Damit Änderungen der Plugins wirksam werden, muss der SCM-Manager-Server neu gestartet werden. Das kann nach jeder einzelnen Aktion erfolgen. Es ist aber auch möglich viele unterschiedliche Aktionen wie Installieren, Aktualisieren und Löschen in eine Warteschlange einzureihen und alle Aktionen mit einem einzigen Neustart auszuführen. Wird eine Aktion (Installieren, Deinstallieren, Aktualisieren) für ein Plugin ausgewählt, erscheinen die Schaltflächen „Änderungen ausführen“ und „Änderungen abbrechen“. Über „Änderungen ausführen“ öffnet sich ein Pop-Up Fenster, in dem die aktuelle Warteschlange (alle ausgeführten Aktionen ohne Neustart) angezeigt werden. Der Anwender hat nun die Möglichkeit zu entscheiden, ob die Änderungen durch einen Neustart ausgeführt werden sollen. Falls Aktionen, die sich bereits in der Warteschlange befinden nicht mehr erwünscht sind, kann die gesamte Warteschlange über den Button „Änderungen abbrechen“ verworfen werden. -### cloudogu platform-Plugins -Einige besondere Plugins sind nur für Instanzen des SCM-Managers verfügbar, die mit der cloudogu platform verbunden sind. Der SCM-Manager kann über den Button „Mit cloudogu platform verbinden“ mit der cloudogu platform verbunden werden. -[Mehr Details zur Datenverarbeitung.](https://scm-manager.org/data-processing) - -![Plugin-Center nicht verbunden, Button zur Verbindung mit der cloudogu platform](assets/administration-plugin-center-not-connected.png) -Sie werden dann zur cloudogu platform-Login-Maske weitergeleitet. -![cloudogu platform-Login-Maske](assets/cloudogu-platform-login.png) -Wenn Sie über ein cloudogu platform-Konto verfügen, können Sie sich einloggen. Ansonsten erstellen Sie über einen konföderierten Identitätsanbieter (Google oder github) oder Ihre Email-Adresse ein Konto. -Anschließend werden Sie zurück zum SCM-Manager geleitet und können Details zur verbundenen Instanz und Konto überprüfen. Mit „Verbinden“ bestätigen Sie die Verbindung, mit „Abbrechen“ brechen Sie den Vorgang ab. -![Bestätigung der Verbindung mit mcloudogu platform](assets/administration-cloudogu-platform-confirmation.png) -Jetzt können Sie im Plugin-Center cloudogu platform-Plugins genau wie Basis-Plugins installieren. -![SCM-Manager mit cloudogu platform verbunden](assets/administration-plugin-center-connected.png) -Eine Instanz des SCM-Managers muss nur mit einem Konto verbunden werden, damit die cloudogu platform-Plugins für die gesamte Instanz zur Verfügung stehen. -Sie können die Verbindung zur cloudogu platform jederzeit unter Plugin Center Einstellungen in den Settings lösen. - -#### Was ist die cloudogu platform und warum sollte ich ein Konto erstellen? -Die cloudogu platform ist nicht nur die Heimat der SCM-Manager-Community. Sie können sich auch mit anderen Nutzenden austauschen, Bugs melden oder neue Funktionen im Forum zur Diskussion stellen. -Die cloudogu platform bietet weiter besondere Plugins speziell für die Community an. In der Zukunft folgen weitere nützliche Plugins, die auch gemeinsam mit Partnern bereitgestellt werden. -Nutzen Sie erweiterte Plugin-Funktionen im SCM-Managers, treten Sie mit den Entwicklern in Kontakt und schließen Sie sich der [cloudogu platform](https://platform.cloudogu.com) kostenfrei an! - ### Installiert Auf der Übersicht für installierte Plugins werden alle auf der SCM-Manager Instanz installierten Plugins angezeigt. Optionale Plugins können hier aktualisiert und deinstalliert werden. diff --git a/docs/de/user/admin/settings.md b/docs/de/user/admin/settings.md index fe75784fc0..18a3f59062 100644 --- a/docs/de/user/admin/settings.md +++ b/docs/de/user/admin/settings.md @@ -25,18 +25,6 @@ Um Angriffe auf den SCM-Manager mit Cross Site Scripting (XSS / XSRF) zu erschwe #### Plugin-Settings Der SCM-Manager kann ein Plugin-Center anbinden, um schnell und bequem Plugins verwalten zu können. Um ein anderes SCM-Plugin-Center als das vorkonfigurierte zu verwenden, reicht es aus diese URL zu ändern. Läuft der SCM-Manager im Cloudogu EcoSystem, kann die Plugin-Center URL über einen Eintrag im etcd gesetzt werden. -Wenn das vorkonfigurierte Plugin-Center verwendet wird, kann der SCM-Manager mit der cloudogu platform verbunden werden. - -Nach der initialen Einrichtung sind folgende Werte standardgemäß hinterlegt: -```markdown -Plugin Center URL: https://plugin-center-api.scm-manager.org/api/v1/plugins/{version}?os={os}&arch={arch}&jre={jre} -Plugin Center Authentication URL: https://plugin-center-api.scm-manager.org/api/v1/auth/oidc -``` - -![Einstellungen, Plugin-Center nicht mit der cloudogu platform verbunden](assets/administration-settings-not-connected.png) -So können über das Plugin-Center besondere cloudogu platform-Plugins bezogen werden. Details sind in der Dokumentation des Plugin-Centers aufgeführt. -Eine bestehende Verbindung zwischen dem SCM-Manager und der cloudogu platform kann hier aufgehoben werden. -![Einstellungen, Plugin-Center mit der cloudogu platform verbunden, Button zum Lösen der Verbindung](assets/administration-settings-connected.png) ### JWT Einstellungen Benutzer erhalten einen JWT als Authentifizierungstoken nach einem erfolgreichen login. diff --git a/docs/en/user/admin/assets/administration-cloudogu-platform-confirmation.png b/docs/en/user/admin/assets/administration-cloudogu-platform-confirmation.png deleted file mode 100644 index c447517010..0000000000 Binary files a/docs/en/user/admin/assets/administration-cloudogu-platform-confirmation.png and /dev/null differ diff --git a/docs/en/user/admin/assets/administration-plugin-center-connected.png b/docs/en/user/admin/assets/administration-plugin-center-connected.png deleted file mode 100644 index d813b2ec69..0000000000 Binary files a/docs/en/user/admin/assets/administration-plugin-center-connected.png and /dev/null differ diff --git a/docs/en/user/admin/assets/administration-plugin-center-not-connected.png b/docs/en/user/admin/assets/administration-plugin-center-not-connected.png deleted file mode 100644 index 50bdee7f41..0000000000 Binary files a/docs/en/user/admin/assets/administration-plugin-center-not-connected.png and /dev/null differ diff --git a/docs/en/user/admin/assets/administration-setings-connected.png b/docs/en/user/admin/assets/administration-setings-connected.png deleted file mode 100644 index c104240b2f..0000000000 Binary files a/docs/en/user/admin/assets/administration-setings-connected.png and /dev/null differ diff --git a/docs/en/user/admin/assets/administration-setings-not-connected.png b/docs/en/user/admin/assets/administration-setings-not-connected.png deleted file mode 100644 index ef227f5b98..0000000000 Binary files a/docs/en/user/admin/assets/administration-setings-not-connected.png and /dev/null differ diff --git a/docs/en/user/admin/assets/cloudogu-platform-login.png b/docs/en/user/admin/assets/cloudogu-platform-login.png deleted file mode 100644 index 6fb578c816..0000000000 Binary files a/docs/en/user/admin/assets/cloudogu-platform-login.png and /dev/null differ diff --git a/docs/en/user/admin/plugins.md b/docs/en/user/admin/plugins.md index 54fbe73acd..0cf2caed03 100644 --- a/docs/en/user/admin/plugins.md +++ b/docs/en/user/admin/plugins.md @@ -8,26 +8,6 @@ Plugins can be managed by action icons on the tiles. System relevant plugins tha In order for changes to plugins to become effective, the SCM-Manager server needs to be restarted. That can be done after every single action. It is also possible to queue several actions like the installation of a new plugin, updates or the deletion of a plugin and to perform all actions with one restart. If an action (installation, uninstallation, update) for a plugin was performed, the buttons "Execute changes" and "Abort changes" appear. If you choose to execute the changes, a popup window that shows the current queue (all actions without a restart) appears. Now the user can decide whether to execute the changes by restarting the server. If there are actions in the queue that are no longer desired, the queue can be emptied with the abort changes button. -### cloudogu platform plugins -Some special plugins are only available to instances of SCM-Manager that are connected to the cloudogu platform. You may connect your instance by clicking the button “Connect to cloudogu platform”. -[More details on data processing.](https://scm-manager.org/data-processing) - -![Plugin-center not connected](assets/administration-plugin-center-not-connected.png) -You will be redirected to a cloudogu platform login form. -![cloudogu platform-Login-Form](assets/cloudogu-platform-login.png) -If you already have an account you simply log in. Otherwise you can create an account either by using a confederate identity provider (Google or github) or with your email. -After a successful login you will return to the SCM-Manager. Here you can review the instance and account to connect. By clicking the button “Connect” you approve the connection and return to the plugin center. -![Confirmation of connection](assets/administration-cloudogu-platform-confirmation.png) -Now you can install cloudogu platform plugins like basic plugins. -![Plugin-center connected with the cloudogu platform](assets/administration-plugin-center-connected.png) -Only one user with sufficient permissions needs to connect the instance with the cloudogu platform. The cloudogu platform plugins can than be installed by every user with suitable permissions. -You can always sever the connection in the plugin center settings in global settings of your instance. - -#### What is the cloudogu platform and why should you create an account? -The cloudogu platform is not only the home of the SCM-Manager community. You can connect to other users, get help and express feature requests in the forum. -The cloudogu platform also serves special plugins to provide more value for our community. In the future the cloudogu platform will offer exiting plugins developed in cooperation with our partners. -To unlock the full power of SCM-Manager and to hang out with our developers, join the [cloudogu platform](https://platform.cloudogu.com/) for free! - ### Installed The overview for installed plugins shows all plugins that are currently installed on the SCM-Manager instance. Optional plugins can be uninstalled or updated here. @@ -35,7 +15,6 @@ The overview for installed plugins shows all plugins that are currently installe ### Available The overview of all available plugins shows all plugins that are compatible with the current version of the SCM-Manager instance that are available through the SCM-plugin-center. The plugins can be downloaded by clicking on the icon and will be installed after a restart of the SCM-Manager server. -Special cloudogu platform-plugins can be installed the same way if your instance of SCM-Manager is connected to the cloudogu platform as described above. ![Administration-Plugins-Available](assets/administration-plugins-available.png) diff --git a/docs/en/user/admin/settings.md b/docs/en/user/admin/settings.md index 7207d904c1..6c80840d51 100644 --- a/docs/en/user/admin/settings.md +++ b/docs/en/user/admin/settings.md @@ -25,17 +25,6 @@ Activate this option to make attacks using cross site scripting (XSS / XSRF) on #### Plugin-Settings A plugin center can be used to conveniently manage plugins. If you want to use a plugin center that is not the default one, you only have to change this URL. If SCM-Manager is operated as part of a Cloudogu EcoSystem, the plugin center URL can be changed in the etcd. -If the default plugin center is used, the SCM-Manager may be connected to the cloudogu platform to receive special cloudogu platform-Plugins. Details can be found in the plugin-center documentation. - -After the initial setup, the following values are set by default: -```markdown -Plugin Center URL: https://plugin-center-api.scm-manager.org/api/v1/plugins/{version}?os={os}&arch={arch}&jre={jre} -Plugin Center Authentication URL: https://plugin-center-api.scm-manager.org/api/v1/auth/oidc -``` - -![Plugin center settings, not connected to the cloudogu platform](assets/administration-setings-not-connected.png) -An existing connection between a SCM-Manager and the cloudogu platform may be severed here. -![Plugin center settings, button sever connection to the cloudogu platform](assets/administration-settings-connected.png) #### JWT settings Users receive a JWT as an authentication token, after a successful login. diff --git a/gradle/changelog/remove_plugin_center_auth.yaml b/gradle/changelog/remove_plugin_center_auth.yaml new file mode 100644 index 0000000000..f2e18716c3 --- /dev/null +++ b/gradle/changelog/remove_plugin_center_auth.yaml @@ -0,0 +1,2 @@ +- type: changed + description: Remove plugin center authentication in order to grant access to former premium plugins 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 aa9d28698b..31b3c6a925 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -58,13 +58,6 @@ public class ScmConfiguration implements Configuration { public static final String DEFAULT_PLUGIN_URL = "https://plugin-center-api.scm-manager.org/api/v1/plugins/{version}?os={os}&arch={arch}&jre={jre}"; - /** - * Default url for plugin center authentication. - * - * @since 2.28.0 - */ - public static final String DEFAULT_PLUGIN_AUTH_URL = "https://plugin-center-api.scm-manager.org/api/v1/auth/oidc"; - /** * SCM Manager alerts url. * @@ -150,9 +143,6 @@ public class ScmConfiguration implements Configuration { @XmlElement(name = "plugin-url") private String pluginUrl = DEFAULT_PLUGIN_URL; - @XmlElement(name = "plugin-auth-url") - private String pluginAuthUrl = DEFAULT_PLUGIN_AUTH_URL; - /** * Url of the alerts api. * @@ -271,7 +261,6 @@ public class ScmConfiguration implements Configuration { this.realmDescription = other.realmDescription; this.dateFormat = other.dateFormat; this.pluginUrl = other.pluginUrl; - this.pluginAuthUrl = other.pluginAuthUrl; this.anonymousMode = other.anonymousMode; this.enableProxy = other.enableProxy; this.proxyPort = other.proxyPort; @@ -367,26 +356,6 @@ public class ScmConfiguration implements Configuration { return pluginUrl; } - /** - * Returns the url which is used for plugin center authentication. - * - * @return authentication url - * @since 2.28.0 - */ - public String getPluginAuthUrl() { - return pluginAuthUrl; - } - - /** - * Returns {@code true} if the default plugin auth url is used. - * - * @return {@code true} if the default plugin auth url is used - * @since 2.28.0 - */ - public boolean isDefaultPluginAuthUrl() { - return DEFAULT_PLUGIN_AUTH_URL.equals(pluginAuthUrl); - } - /** * Returns the url of the alerts api. * @@ -649,16 +618,6 @@ public class ScmConfiguration implements Configuration { this.pluginUrl = pluginUrl; } - /** - * Set the url for plugin center authentication. - * - * @param pluginAuthUrl authentication url - * @since 2.28.0 - */ - public void setPluginAuthUrl(String pluginAuthUrl) { - this.pluginAuthUrl = pluginAuthUrl; - } - /** * Set the url for the alerts api. * diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java index 57565399d2..5ce952296b 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java @@ -108,18 +108,6 @@ public abstract class BaseHttpRequest return self(); } - /** - * Enable authentication with a bearer token. - * @param bearerToken bearer token - * @return http request instance - * @since 2.28.0 - */ - public T bearerAuth(String bearerToken) { - headers.put("Authorization", "Bearer ".concat(bearerToken)); - - return self(); - } - /** * Enable or disabled gzip decoding. The default value is false. * diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java index a001aa0a11..80f19fe9df 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePluginDescriptor.java @@ -32,32 +32,22 @@ public class AvailablePluginDescriptor implements PluginDescriptor { private final Set optionalDependencies; private final String url; private final String checksum; - private final String installLink; /** - * @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String, String)} instead + * @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String)} instead */ @Deprecated public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set dependencies, String url, String checksum) { this(information, condition, dependencies, emptySet(), url, checksum); } - /** - * @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String, String)} instead - */ - @Deprecated public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set dependencies, Set optionalDependencies, String url, String checksum) { - this(information, condition, dependencies, optionalDependencies, url, checksum, null); - } - - public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set dependencies, Set optionalDependencies, String url, String checksum, String installLink) { this.information = information; this.condition = condition; this.dependencies = dependencies; this.optionalDependencies = optionalDependencies; this.url = url; this.checksum = checksum; - this.installLink = installLink; } public String getUrl() { @@ -87,8 +77,4 @@ public class AvailablePluginDescriptor implements PluginDescriptor { public Set getOptionalDependencies() { return optionalDependencies; } - - public Optional getInstallLink() { - return Optional.ofNullable(installLink); - } } diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java index f17feee6f5..77cf375d8c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java @@ -48,7 +48,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea private String author; private String category; private String avatarUrl; - private PluginType type = PluginType.SCM; @Override public PluginInformation clone() { @@ -60,7 +59,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea clone.setAuthor(author); clone.setCategory(category); clone.setAvatarUrl(avatarUrl); - clone.setType(type); return clone; } @@ -82,9 +80,4 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea public boolean isValid() { return Util.isNotEmpty(name) && Util.isNotEmpty(version); } - - public enum PluginType { - SCM, - CLOUDOGU - } } diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 9d807626a1..59751d4ec3 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -62,7 +62,6 @@ public class VndMediaType { public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; public static final String PLUGIN = PREFIX + "plugin" + SUFFIX; public static final String PLUGIN_COLLECTION = PREFIX + "pluginCollection" + SUFFIX; - public static final String PLUGIN_CENTER_AUTH_INFO = PREFIX + "pluginCenterAuthInfo" + SUFFIX; public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX; public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX; @SuppressWarnings("squid:S2068") diff --git a/scm-core/src/test/java/sonia/scm/config/ScmConfigurationTest.java b/scm-core/src/test/java/sonia/scm/config/ScmConfigurationTest.java index b7eb2bb195..81b3f8ef4c 100644 --- a/scm-core/src/test/java/sonia/scm/config/ScmConfigurationTest.java +++ b/scm-core/src/test/java/sonia/scm/config/ScmConfigurationTest.java @@ -16,7 +16,6 @@ package sonia.scm.config; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -26,17 +25,6 @@ class ScmConfigurationTest { private final ScmConfiguration scmConfiguration = new ScmConfiguration(); - @Test - void shouldReturnTrueForInitialPluginAuthUrl() { - assertThat(scmConfiguration.isDefaultPluginAuthUrl()).isTrue(); - } - - @Test - void shouldReturnFalseIfPluginAuthUrlHasChanged() { - scmConfiguration.setPluginAuthUrl("https://plug.ins/oidc"); - assertThat(scmConfiguration.isDefaultPluginAuthUrl()).isFalse(); - } - @ParameterizedTest @CsvSource({"https://hog.hitchiker.com/scm,scm", "https://hog.hitchiker.com/scm/,scm", "https://hog.hitchiker.com/,", "https://hog.hitchiker.com,"}) void shouldReturnContextPath(String input, String expected) { diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java index 43526fd986..3517ed8ed7 100644 --- a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java +++ b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java @@ -51,13 +51,6 @@ class BaseHttpRequestTest { assertThat(headers.get("Authorization").iterator().next()).isEqualTo("Basic dHJpY2lhOm1jbWlsbGlhbjEyMw=="); } - @Test - void shouldAddAuthorizationHeaderWithBearerScheme() { - request.bearerAuth("awesome-access-token"); - Multimap headers = request.getHeaders(); - assertThat(headers.get("Authorization").iterator().next()).isEqualTo("Bearer awesome-access-token"); - } - @Test void shouldAppendQueryString(){ request.queryString("a", "b"); diff --git a/scm-ui/ui-api/src/config.test.ts b/scm-ui/ui-api/src/config.test.ts index bb38793a85..83a06470fb 100644 --- a/scm-ui/ui-api/src/config.test.ts +++ b/scm-ui/ui-api/src/config.test.ts @@ -43,7 +43,6 @@ describe("Test config hooks", () => { namespaceStrategy: "", emergencyContacts: [], pluginUrl: "", - pluginAuthUrl: "", proxyExcludes: [], proxyPassword: null, proxyPort: 0, diff --git a/scm-ui/ui-api/src/index.ts b/scm-ui/ui-api/src/index.ts index 1acf4784b5..fbd30e8e02 100644 --- a/scm-ui/ui-api/src/index.ts +++ b/scm-ui/ui-api/src/index.ts @@ -55,7 +55,6 @@ export * from "./annotations"; export * from "./search"; export * from "./loginInfo"; export * from "./useInvalidation"; -export * from "./usePluginCenterAuthInfo"; export * from "./compare"; export * from "./utils"; export * from "./links"; diff --git a/scm-ui/ui-api/src/plugins.test.ts b/scm-ui/ui-api/src/plugins.test.ts index 607b1350c8..cdf0ceffbb 100644 --- a/scm-ui/ui-api/src/plugins.test.ts +++ b/scm-ui/ui-api/src/plugins.test.ts @@ -40,7 +40,6 @@ describe("Test plugin hooks", () => { pending: false, dependencies: [], optionalDependencies: [], - type: "SCM", _links: { install: { href: "/plugins/available/heart-of-gold-plugin/install" }, installWithRestart: { @@ -59,7 +58,6 @@ describe("Test plugin hooks", () => { markedForUninstall: false, dependencies: [], optionalDependencies: [], - type: "SCM", _links: { self: { href: "/plugins/installed/heart-of-gold-plugin", @@ -87,7 +85,6 @@ describe("Test plugin hooks", () => { name: "heart-of-gold-core-plugin", pending: false, markedForUninstall: false, - type: "SCM", dependencies: [], optionalDependencies: [], _links: { diff --git a/scm-ui/ui-api/src/usePluginCenterAuthInfo.ts b/scm-ui/ui-api/src/usePluginCenterAuthInfo.ts deleted file mode 100644 index 94fe3cd20b..0000000000 --- a/scm-ui/ui-api/src/usePluginCenterAuthInfo.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -import { ApiResult, useIndexLink } from "./base"; -import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types"; -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { apiClient } from "./apiclient"; -import { useLocation } from "react-router-dom"; - -const appendQueryParam = (link: Link, name: string, value: string) => { - let href = link.href; - if (href.includes("?")) { - href += "&"; - } else { - href += "?"; - } - link.href = href + name + "=" + value; -}; - -export const usePluginCenterAuthInfo = (): ApiResult => { - const link = useIndexLink("pluginCenterAuth"); - const location = useLocation(); - return useQuery( - ["pluginCenterAuth"], - () => { - if (!link) { - throw new Error("no such plugin center auth link"); - } - return apiClient - .get(link) - .then(response => response.json()) - .then((result: PluginCenterAuthenticationInfo) => { - if (result._links?.login) { - appendQueryParam(result._links.login as Link, "source", location.pathname); - } - if (result._links?.reconnect) { - appendQueryParam(result._links.reconnect as Link, "source", location.pathname); - } - return result; - }); - }, - { - enabled: !!link - } - ); -}; - -export const usePluginCenterLogout = (authenticationInfo: PluginCenterAuthenticationInfo) => { - const queryClient = useQueryClient(); - const { mutate, isLoading, error } = useMutation( - () => { - if (!authenticationInfo._links.logout) { - throw new Error("authenticationInfo has no logout link"); - } - const logout = authenticationInfo._links.logout as Link; - return apiClient.delete(logout.href); - }, - { - onSuccess: () => queryClient.invalidateQueries("pluginCenterAuth") - } - ); - - return { - logout: () => { - mutate(); - }, - isLoading, - error - }; -}; diff --git a/scm-ui/ui-styles/src/components/_main.scss b/scm-ui/ui-styles/src/components/_main.scss index 8248d37a56..2324b86d77 100644 --- a/scm-ui/ui-styles/src/components/_main.scss +++ b/scm-ui/ui-styles/src/components/_main.scss @@ -531,12 +531,12 @@ ul.is-separated { // card columns for repo and plugins overview .card-columns { .column { - height: 160px; + height: 120px; margin-bottom: 1.5rem; .overlay-column { position: absolute; - height: calc(160px - 1.5rem); + height: calc(120px - 1.5rem); } } diff --git a/scm-ui/ui-types/src/Config.ts b/scm-ui/ui-types/src/Config.ts index 7d8ded89a1..ec7d1b08f1 100644 --- a/scm-ui/ui-types/src/Config.ts +++ b/scm-ui/ui-types/src/Config.ts @@ -35,7 +35,6 @@ export type Config = HalRepresentation & { proxyExcludes: string[]; skipFailedAuthenticators: boolean; pluginUrl: string; - pluginAuthUrl: string; loginAttemptLimitTimeout: number; enabledXsrfProtection: boolean; enabledUserConverter: boolean; diff --git a/scm-ui/ui-types/src/Plugin.ts b/scm-ui/ui-types/src/Plugin.ts index a66871705a..6560b701ac 100644 --- a/scm-ui/ui-types/src/Plugin.ts +++ b/scm-ui/ui-types/src/Plugin.ts @@ -16,8 +16,6 @@ import { HalRepresentation, HalRepresentationWithEmbedded } from "./hal"; -type PluginType = "SCM" | "CLOUDOGU"; - export type PluginSet = HalRepresentation & { id: string; name: string; @@ -37,7 +35,6 @@ export type Plugin = HalRepresentation & { category: string; avatarUrl?: string; pending: boolean; - type: PluginType; markedForUninstall?: boolean; dependencies: string[]; optionalDependencies: string[]; @@ -62,11 +59,3 @@ export type PendingPlugins = HalRepresentationWithEmbedded<{ update: Plugin[]; uninstall: Plugin[]; }>; - -export type PluginCenterAuthenticationInfo = HalRepresentation & { - principal?: string; - pluginCenterSubject?: string; - date?: string; - default: boolean; - failed: boolean; -}; diff --git a/scm-ui/ui-webapp/public/locales/de/admin.json b/scm-ui/ui-webapp/public/locales/de/admin.json index 6bee58dcf6..8ef435edba 100644 --- a/scm-ui/ui-webapp/public/locales/de/admin.json +++ b/scm-ui/ui-webapp/public/locales/de/admin.json @@ -50,8 +50,7 @@ "title": { "install": "{{name}} Plugin installieren", "update": "{{name}} Plugin aktualisieren", - "uninstall": "{{name}} Plugin deinstallieren", - "cloudoguInstall": "{{name}} Plugin installieren" + "uninstall": "{{name}} Plugin deinstallieren" }, "restart": "Neustarten, um Plugin-Änderungen wirksam zu machen", "install": "Installieren", @@ -71,8 +70,6 @@ "version": "Version", "currentVersion": "Installierte Version", "newVersion": "Neue Version", - "cloudoguInstallInfo": "Verbinden Sie Ihre SCM-Manager-Instanz mit ihrem cloudogu platform-Account um Zugriff auf cloudogu platform-Plugins zu erhalten. Falls Sie noch über keinen Account verfügen, können Sie sich einfach kostenlos registrieren.", - "cloudoguInstall": "Mit cloudogu platform verbinden und installieren", "dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!", "optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!", "dependencies": "Abhängigkeiten", @@ -87,26 +84,6 @@ "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam.", "manualRestartRequired": "Nachdem die Plugin-Änderung durchgeführt wurde, muss SCM-Manager neu gestartet werden.", "showPending": "Um die folgenden Plugin-Änderungen auszuführen, muss SCM-Manager neu gestartet werden." - }, - "cloudoguPlatform": { - "connectionInfo": "Instanz ist mit der cloudogu platform verbunden.\nAccount: {{pluginCenterSubject}}", - "error": { - "info": "cloudogu platform Authentifizierungsinformationen konnten nicht abgerufen werden. Klicken Sie, um Details zu sehen.", - "title": "Fehler" - }, - "failed": { - "info": "Verbindung zur cloudogu platform mit Account {{pluginCenterSubject}} is fehlgeschlagen", - "message": "Die Verbindung der SCM-Manager Instanz mit der <0>cloudogu platform is fehlgeschlagen. Der Benutzer <1>{{subject}} konnte nicht authentifiziert werden.", - "button": { - "label": "Erneut mit der <0>cloudogu platform verbinden" - } - }, - "login": { - "button": { - "label": "Mit der <0>cloudogu platform verbinden" - }, - "description": "Verbinden Sie Ihren SCM-Manager mit der <0>cloudogu platform, um besondere Plugins zu installieren. Die cloudogu platform ist die Heimat der SCM-Manager Community, getragen von Maintainern des SCM-Managers. Sie haben noch kein Konto? Erstellen Sie während der Verbindung der SCM-Manager-Instanz kostenfrei ein cloudogu platform-Konto. <1>Mehr Details zur Datenverarbeitung." - } } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/public/locales/de/config.json b/scm-ui/ui-webapp/public/locales/de/config.json index ba5bb5739b..5e4e5b5d42 100644 --- a/scm-ui/ui-webapp/public/locales/de/config.json +++ b/scm-ui/ui-webapp/public/locales/de/config.json @@ -11,20 +11,6 @@ "no-write-permission-notification": "Hinweis: Es fehlen Berechtigungen zum Bearbeiten der Einstellungen!" } }, - "pluginSettings": { - "subtitle": "Plugin Einstellungen", - "pluginUrl": "Plugin Center URL", - "pluginAuthUrl": "Plugin Center Authentifizierungs URL", - "auth": { - "loading": "Lade Authentifizierungs Informationen ...", - "notAuthenticated": "Das Plugin Center ist nicht authentifiziert", - "authenticate": "Authentifizieren", - "authenticated": "Das Plugin Center ist als <0 /> authentifiziert", - "subjectTooltip": "Authentifiziert als {{ principal }} {{ ago }}", - "logout": "Abmelden", - "notConfiguredHint": "Authentifizierungs URL ist nicht gesetzt" - } - }, "jwtSettings": { "subtitle": "JWT Einstellungen", "label": "Ablaufzeit", @@ -87,6 +73,7 @@ "enabled-file-search": "Dateisuche aktivieren", "namespace-strategy": "Namespace Strategie", "login-info-url": "Login Info URL", + "pluginUrl": "Plugin Center URL", "emergencyContacts": { "label": "Notfallkontakte", "helpText": "Liste der Benutzer, die über administrative Vorfälle informiert werden.", @@ -116,7 +103,6 @@ "realmDescriptionHelpText": "Beschreibung des Authentication Realm.", "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", "pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", - "pluginAuthUrlHelpText": "Die URL der Plugin Center Authentifizierungs API.", "alertsUrlHelpText": "Die URL der Alerts API. Darüber wird über Alerts die Ihr System betreffen informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.", "releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.", "mailDomainNameHelpText": "Dieser Domain Name wird genutzt, wenn für einen User eine E-Mail-Adresse benötigt wird, für den keine hinterlegt ist. Diese Domain wird nicht zum Versenden von E-Mails genutzt und auch keine anderweitige Verbindung aufgebaut.", diff --git a/scm-ui/ui-webapp/public/locales/en/admin.json b/scm-ui/ui-webapp/public/locales/en/admin.json index 5d27143974..065e32b873 100644 --- a/scm-ui/ui-webapp/public/locales/en/admin.json +++ b/scm-ui/ui-webapp/public/locales/en/admin.json @@ -50,8 +50,7 @@ "title": { "install": "Install {{name}} Plugin", "update": "Update {{name}} Plugin", - "uninstall": "Uninstall {{name}} Plugin", - "cloudoguInstall": "Install {{name}} Plugin" + "uninstall": "Uninstall {{name}} Plugin" }, "restart": "Restart to make plugin changes effective", "install": "Install", @@ -71,8 +70,6 @@ "version": "Version", "currentVersion": "Installed version", "newVersion": "New version", - "cloudoguInstallInfo": "Connect your SCM-Manager instance with your cloudogu platform account to access cloudogu platform plugins. If you do not already have an account you can easily register for free.", - "cloudoguInstall": "Connect cloudogu platform and install", "dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!", "optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!", "dependencies": "Dependencies", @@ -87,26 +84,6 @@ "updateAllInfo": "The following plugin changes will be executed. You need to restart the SCM-Manager to make these changes effective.", "manualRestartRequired": "After the plugin change has been made, SCM-Manager must be restarted.", "showPending": "To execute the following plugin changes, SCM-Manager must be restarted." - }, - "cloudoguPlatform": { - "connectionInfo": "Instance is connected to the cloudogu platform.\nAccount: {{pluginCenterSubject}}", - "error": { - "info": "Failed to retrieve cloudogu platform authentication information. Click for more details.", - "title": "Error" - }, - "failed": { - "info": "Connection to the cloudogu platform failed for account {{pluginCenterSubject}}", - "message": "The connection of the SCM-Manager instance with the <0>cloudogu platform failed. The user <1>{{subject}} could not be authenticated. Click Reconnect to restore the connection.", - "button": { - "label": "Reconnect to the <0>cloudogu platform" - } - }, - "login": { - "button": { - "label": "Connect to the <0>cloudogu platform" - }, - "description": "Connect your SCM-Manager with the <0>cloudogu platform to install special plugins. The cloudogu platform is the home of the SCM-Manager Community, sustained by the maintainers of the SCM-Manager. You don't have an account yet? Create a free cloudogu platform account while connecting your SCM-Manager instance. <1>More details on data processing." - } } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/public/locales/en/config.json b/scm-ui/ui-webapp/public/locales/en/config.json index 1a7af32ec6..66467e0ce3 100644 --- a/scm-ui/ui-webapp/public/locales/en/config.json +++ b/scm-ui/ui-webapp/public/locales/en/config.json @@ -11,20 +11,6 @@ "no-write-permission-notification": "Please note: You do not have the permission to edit the config!" } }, - "pluginSettings": { - "subtitle": "Plugin Settings", - "pluginUrl": "Plugin Center URL", - "pluginAuthUrl": "Plugin Center Authentication URL", - "auth": { - "loading": "Loading authentication info ...", - "notAuthenticated": "Plugin Center is not authenticated", - "authenticate": "Authenticate", - "authenticated": "Plugin Center is authenticated as <0 />", - "subjectTooltip": "Authenticated by {{ principal }} {{ ago }}", - "logout": "Logout", - "notConfiguredHint": "Authentication URL is not configured" - } - }, "jwtSettings": { "subtitle": "JWT Settings", "label": "Expiration time", @@ -87,6 +73,7 @@ "enabled-file-search": "Enabled File Search", "namespace-strategy": "Namespace Strategy", "login-info-url": "Login Info URL", + "pluginUrl": "Plugin Center URL", "emergencyContacts": { "label": "Emergency Contacts", "helpText": "List of users notified of administrative incidents.", @@ -116,7 +103,6 @@ "realmDescriptionHelpText": "Enter authentication realm description.", "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.", "pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture", - "pluginAuthUrlHelpText": "The url of the Plugin Center authentication API.", "alertsUrlHelpText": "The url of the alerts api. This provides up-to-date alerts regarding your system. To disable this feature just leave the url blank.", "releaseFeedUrlHelpText": "The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.", "mailDomainNameHelpText": "This domain name will be used to create email addresses for users without one when needed. It will not be used to send mails nor will be accessed otherwise.", diff --git a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx index 045e6e0acf..743544dbc5 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx @@ -68,7 +68,6 @@ const ConfigForm: FC = ({ proxyExcludes: [], skipFailedAuthenticators: false, pluginUrl: "", - pluginAuthUrl: "", loginAttemptLimitTimeout: 0, enabledXsrfProtection: true, enabledUserConverter: false, @@ -149,6 +148,7 @@ const ConfigForm: FC = ({ dateFormat={innerConfig.dateFormat} anonymousMode={innerConfig.anonymousMode} skipFailedAuthenticators={innerConfig.skipFailedAuthenticators} + pluginUrl={innerConfig.pluginUrl} alertsUrl={innerConfig.alertsUrl} releaseFeedUrl={innerConfig.releaseFeedUrl} mailDomainName={innerConfig.mailDomainName} @@ -181,13 +181,6 @@ const ConfigForm: FC = ({ hasUpdatePermission={configUpdatePermission} />
- onChange(isValid, changedValue, name)} - hasUpdatePermission={configUpdatePermission} - /> -
= ({ realmDescription, loginInfoUrl, + pluginUrl, anonymousMode, alertsUrl, releaseFeedUrl, @@ -62,6 +64,9 @@ const GeneralSettings: FC = ({ const handleLoginInfoUrlChange = (value: string) => { onChange(true, value, "loginInfoUrl"); }; + const handlePluginCenterUrlChange = (value: string) => { + onChange(true, value, "pluginUrl"); + }; const handleRealmDescriptionChange = (value: string) => { onChange(true, value, "realmDescription"); }; @@ -127,6 +132,17 @@ const GeneralSettings: FC = ({ /> +
+
+ +
+
= ({ disabled={!hasUpdatePermission} className="is-fullwidth" options={[ - { label: t("general-settings.anonymousMode.full"), value: "FULL" }, - { label: t("general-settings.anonymousMode.protocolOnly"), value: "PROTOCOL_ONLY" }, - { label: t("general-settings.anonymousMode.off"), value: "OFF" }, + {label: t("general-settings.anonymousMode.full"), value: "FULL"}, + {label: t("general-settings.anonymousMode.protocolOnly"), value: "PROTOCOL_ONLY"}, + {label: t("general-settings.anonymousMode.off"), value: "OFF"}, ]} helpText={t("help.allowAnonymousAccessHelpText")} testId={"anonymous-mode-select"} @@ -197,12 +213,12 @@ const GeneralSettings: FC = ({ helpText={t("general-settings.emergencyContacts.helpText")} placeholder={t("general-settings.emergencyContacts.autocompletePlaceholder")} aria-label="general-settings.emergencyContacts.ariaLabel" - value={emergencyContacts.map((m) => ({ label: m, value: { id: m, displayName: m } }))} + value={emergencyContacts.map((m) => ({label: m, value: {id: m, displayName: m}}))} onChange={handleEmergencyContactsChange} > options={userOptions || []} - className={classNames({ "is-loading": userOptionsLoading })} + className={classNames({"is-loading": userOptionsLoading})} onQueryChange={setQuery} /> diff --git a/scm-ui/ui-webapp/src/admin/components/form/PluginCenterAuthentication.tsx b/scm-ui/ui-webapp/src/admin/components/form/PluginCenterAuthentication.tsx deleted file mode 100644 index 7d02280cba..0000000000 --- a/scm-ui/ui-webapp/src/admin/components/form/PluginCenterAuthentication.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -import React, { FC } from "react"; -import { usePluginCenterAuthInfo, usePluginCenterLogout } from "@scm-manager/ui-api"; -import { Button, ErrorNotification, Notification, Tooltip, useDateFormatter } from "@scm-manager/ui-components"; -import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types"; -import styled from "styled-components"; -import { Trans, useTranslation } from "react-i18next"; - -const Message = styled.p` - line-height: 2.5rem; -`; - -type Props = { - authenticationInfo: PluginCenterAuthenticationInfo; -}; - -const PluginCenterSubject: FC = ({ authenticationInfo }) => { - const formatter = useDateFormatter({ date: authenticationInfo.date }); - const [t] = useTranslation("config"); - return ( - <> - - {authenticationInfo.pluginCenterSubject} - - - ); -}; - -const AuthenticatedInfo: FC = ({ authenticationInfo }) => { - const { logout, isLoading, error } = usePluginCenterLogout(authenticationInfo); - const [t] = useTranslation("config"); - - const subject = ; - - return ( - -
- - - - {authenticationInfo._links.logout ? ( - - ) : null} -
- {error ? ( -
- -
- ) : null} -
- ); -}; - -const LoginButton: FC<{ link: Link }> = ({ link }) => { - const [t] = useTranslation("config"); - return ( - - ); -}; - -const PluginCenterAuthentication: FC = () => { - const { data, isLoading, error } = usePluginCenterAuthInfo(); - const [t] = useTranslation("config"); - - if (isLoading) { - return ( -
- -

{t("pluginSettings.auth.loading")}

-
- ); - } - - if (error) { - return ; - } - - if (!data) { - return null; - } - - if (data.principal) { - return ; - } - - if (data._links.login) { - return ( - - {t("pluginSettings.auth.notAuthenticated")} - - - ); - } else { - return null; - } -}; - -export default PluginCenterAuthentication; diff --git a/scm-ui/ui-webapp/src/admin/components/form/PluginSettings.tsx b/scm-ui/ui-webapp/src/admin/components/form/PluginSettings.tsx deleted file mode 100644 index e4ae9b0600..0000000000 --- a/scm-ui/ui-webapp/src/admin/components/form/PluginSettings.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -import React, { FC } from "react"; -import { useTranslation } from "react-i18next"; -import { InputField, Subtitle } from "@scm-manager/ui-components"; -import PluginCenterAuthentication from "./PluginCenterAuthentication"; - -type Props = { - pluginUrl: string; - pluginAuthUrl: string; - onChange: (isValid: boolean, changedValue: string, name: string) => void; - hasUpdatePermission: boolean; -}; - -const PluginSettings: FC = ({ pluginUrl, pluginAuthUrl, onChange, hasUpdatePermission }) => { - const { t } = useTranslation("config"); - - const handlePluginCenterUrlChange = (value: string) => { - onChange(true, value, "pluginUrl"); - }; - - const handlePluginCenterAuthUrlChange = (value: string) => { - onChange(true, value, "pluginAuthUrl"); - }; - - return ( -
- -
-
- -
-
-
-
- -
-
- -
- ); -}; - -export default PluginSettings; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformBanner.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformBanner.tsx deleted file mode 100644 index f658129d76..0000000000 --- a/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformBanner.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -import React, { FC } from "react"; -import { Trans, useTranslation } from "react-i18next"; -import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types"; -import classNames from "classnames"; -import styled from "styled-components"; -import { ExternalLinkButton, Icon } from "@scm-manager/ui-core"; - -type Props = { - info: PluginCenterAuthenticationInfo; -}; - -const CloudoguPlatformBanner: FC = ({ info }) => { - const loginLink = (info._links.login as Link)?.href; - if (loginLink) { - return ; - } - - if (info.failed) { - const reconnectLink = (info._links.reconnect as Link)?.href; - if (reconnectLink) { - return ; - } - } - - return null; -}; - -type PropsWithLink = Props & { - link: string; -}; - -const FailedAuthentication: FC = ({ info, link }) => { - const [t] = useTranslation("admin"); - return ( - -

- cloudogu platform, ]} - /> -

- - cloudogu platform]} - /> - -
- ); -}; - -type ContainerProps = { - className?: string; -}; - -const Container: FC = ({ className, children }) => ( - - {children} - -); - -const DivWithSolidBorder = styled.div` - border: 2px solid; -`; - -const Unauthenticated: FC = ({ link, info }) => { - const [t] = useTranslation("admin"); - return ( - - - cloudogu platform]} - /> - -

- - cloudogu platform, - Data Processing, - ]} - /> - -

-
- ); -}; - -export default CloudoguPlatformBanner; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformTag.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformTag.tsx deleted file mode 100644 index 840e82a58b..0000000000 --- a/scm-ui/ui-webapp/src/admin/plugins/components/CloudoguPlatformTag.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -import React from "react"; -import styled from "styled-components"; - -const CloudoguPlatformTagWrapper = styled.span` - border: solid 1px; -`; - -const CloudoguPlatformTag = () => ( - - cloudogu platform - -); - -export default CloudoguPlatformTag; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx index 58e9a085a3..1338f6c28c 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.tsx @@ -16,19 +16,16 @@ import React, { FC } from "react"; import styled from "styled-components"; -import { Link, Plugin, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types"; +import { Link, Plugin } from "@scm-manager/ui-types"; import { CardColumn, Icon } from "@scm-manager/ui-components"; import { PluginAction, PluginModalContent } from "../containers/PluginsOverview"; import { useTranslation } from "react-i18next"; import PluginAvatar from "./PluginAvatar"; -import classNames from "classnames"; -import CloudoguPlatformTag from "./CloudoguPlatformTag"; import { useKeyboardIteratorTarget } from "@scm-manager/ui-shortcuts"; type Props = { plugin: Plugin; openModal: (content: PluginModalContent) => void; - pluginCenterAuthInfo?: PluginCenterAuthenticationInfo; }; const ActionbarWrapper = styled.div` @@ -37,7 +34,7 @@ const ActionbarWrapper = styled.div` } `; -const IconWrapperStyle = styled.span.attrs((props) => ({ +const IconWrapperStyle = styled.span.attrs(() => ({ className: "level-item mb-0 p-2 is-clickable", }))` border: 1px solid #cdcdcd; // $dark-25 @@ -56,13 +53,11 @@ const IconWrapper: FC<{ action: () => void }> = ({ action, children }) => { ); }; -const PluginEntry: FC = ({ plugin, openModal, pluginCenterAuthInfo }) => { +const PluginEntry: FC = ({ plugin, openModal }) => { const [t] = useTranslation("admin"); const isInstallable = plugin._links.install && (plugin._links.install as Link).href; const isUpdatable = plugin._links.update && (plugin._links.update as Link).href; const isUninstallable = plugin._links.uninstall && (plugin._links.uninstall as Link).href; - const isCloudoguPlugin = plugin.type === "CLOUDOGU"; - const isDefaultPluginCenterLoginAvailable = pluginCenterAuthInfo?.default && !!pluginCenterAuthInfo?._links?.login; const ref = useKeyboardIteratorTarget(); const evaluateAction = () => { @@ -70,29 +65,16 @@ const PluginEntry: FC = ({ plugin, openModal, pluginCenterAuthInfo }) => return () => openModal({ plugin, action: PluginAction.INSTALL }); } - if (isCloudoguPlugin && isDefaultPluginCenterLoginAvailable) { - return () => openModal({ plugin, action: PluginAction.CLOUDOGU }); - } - return undefined; }; const pendingInfo = () => ( <> - + + ); const actionBar = () => ( - {isCloudoguPlugin && isDefaultPluginCenterLoginAvailable && ( - openModal({ plugin, action: PluginAction.CLOUDOGU })}> - - - )} {isInstallable && ( openModal({ plugin, action: PluginAction.INSTALL })}> @@ -121,17 +103,8 @@ const PluginEntry: FC = ({ plugin, openModal, pluginCenterAuthInfo }) => description={plugin.description} contentRight={plugin.pending || plugin.markedForUninstall ? pendingInfo() : actionBar()} footerLeft={{plugin.version}} - footerRight={null} + footerRight={{plugin.author}} /> -
- {isCloudoguPlugin ? : null} - {plugin.author} -
); }; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx index a3e44a2753..6fe517b6ae 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginGroupEntry.tsx @@ -16,26 +16,18 @@ import React, { FC } from "react"; import { CardColumnGroup } from "@scm-manager/ui-components"; -import { PluginCenterAuthenticationInfo, PluginGroup } from "@scm-manager/ui-types"; +import { PluginGroup } from "@scm-manager/ui-types"; import PluginEntry from "./PluginEntry"; import { PluginModalContent } from "../containers/PluginsOverview"; type Props = { group: PluginGroup; openModal: (content: PluginModalContent) => void; - pluginCenterAuthInfo?: PluginCenterAuthenticationInfo; }; -const PluginGroupEntry: FC = ({ openModal, group, pluginCenterAuthInfo }) => { - const entries = group.plugins.map(plugin => { - return ( - - ); +const PluginGroupEntry: FC = ({ openModal, group }) => { + const entries = group.plugins.map((plugin) => { + return ; }); return ; }; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx index f05cd33bfd..fe62723a20 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginList.tsx @@ -15,7 +15,7 @@ */ import React, { FC } from "react"; -import { Plugin, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types"; +import { Plugin } from "@scm-manager/ui-types"; import PluginGroupEntry from "../components/PluginGroupEntry"; import groupByCategory from "./groupByCategory"; import { PluginModalContent } from "../containers/PluginsOverview"; @@ -24,23 +24,15 @@ import { KeyboardIterator } from "@scm-manager/ui-shortcuts"; type Props = { plugins: Plugin[]; openModal: (content: PluginModalContent) => void; - pluginCenterAuthInfo?: PluginCenterAuthenticationInfo; }; -const PluginList: FC = ({ plugins, openModal, pluginCenterAuthInfo }) => { +const PluginList: FC = ({ plugins, openModal }) => { const groups = groupByCategory(plugins); return (
{groups.map((group) => { - return ( - - ); + return ; })}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index f487f63850..e82c2891e0 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -18,12 +18,11 @@ import React, { FC, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; -import { Link, Plugin } from "@scm-manager/ui-types"; +import { Plugin } from "@scm-manager/ui-types"; import { Button, ButtonGroup, Checkbox, ErrorNotification, Modal, Notification } from "@scm-manager/ui-components"; import SuccessNotification from "./SuccessNotification"; -import { useInstallPlugin, usePluginCenterAuthInfo, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api"; +import { useInstallPlugin, useUninstallPlugin, useUpdatePlugins } from "@scm-manager/ui-api"; import { PluginAction } from "../containers/PluginsOverview"; -import CloudoguPlatformTag from "./CloudoguPlatformTag"; type Props = { plugin: Plugin; @@ -48,16 +47,11 @@ const ListChild = styled.div` const PluginModal: FC = ({ onClose, pluginAction, plugin }) => { const [t] = useTranslation("admin"); const [shouldRestart, setShouldRestart] = useState(false); - const { - data: pluginCenterAuthInfo, - isLoading: isLoadingPluginCenterAuthInfo, - error: pluginCenterAuthInfoError - } = usePluginCenterAuthInfo(); const { isLoading: isInstalling, error: installError, install, isInstalled } = useInstallPlugin(); const { isLoading: isUninstalling, error: uninstallError, uninstall, isUninstalled } = useUninstallPlugin(); const { isLoading: isUpdating, error: updateError, update, isUpdated } = useUpdatePlugins(); - const error = installError || uninstallError || updateError || pluginCenterAuthInfoError; - const loading = isInstalling || isUninstalling || isUpdating || isLoadingPluginCenterAuthInfo; + const error = installError || uninstallError || updateError; + const loading = isInstalling || isUninstalling || isUpdating; const isDone = isInstalled || isUninstalled || isUpdated; const initialFocusRef = useRef(null); @@ -70,9 +64,6 @@ const PluginModal: FC = ({ onClose, pluginAction, plugin }) => { const handlePluginAction = (e: React.MouseEvent) => { e.preventDefault(); switch (pluginAction) { - case PluginAction.CLOUDOGU: - window.open((pluginCenterAuthInfo?._links?.login as Link).href, "_self"); - break; case PluginAction.INSTALL: install(plugin, { restart: shouldRestart }); break; @@ -195,7 +186,7 @@ const PluginModal: FC = ({ onClose, pluginAction, plugin }) => { disabled={false} /> ); - } else if (pluginAction !== PluginAction.CLOUDOGU) { + } else { return {t("plugins.modal.manualRestartRequired")}; } }; @@ -213,18 +204,6 @@ const PluginModal: FC = ({ onClose, pluginAction, plugin }) => { {t("plugins.modal.author")}: {plugin.author}
- {pluginAction === PluginAction.CLOUDOGU && ( - <> -
- -
-
- - {t("plugins.modal.cloudoguInstallInfo")} - -
- - )} {pluginAction === PluginAction.INSTALL && (
{t("plugins.modal.version")}: diff --git a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx index 40bc04660e..b0c852d491 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx @@ -29,11 +29,8 @@ import { useAvailablePlugins, useInstalledPlugins, usePendingPlugins, - usePluginCenterAuthInfo, } from "@scm-manager/ui-api"; import PluginModal from "../components/PluginModal"; -import CloudoguPlatformBanner from "../components/CloudoguPlatformBanner"; -import PluginCenterAuthInfo from "../components/PluginCenterAuthInfo"; import styled from "styled-components"; import { Button } from "@scm-manager/ui-buttons"; import { useDocumentTitle } from "@scm-manager/ui-core"; @@ -42,7 +39,6 @@ export enum PluginAction { INSTALL = "install", UPDATE = "update", UNINSTALL = "uninstall", - CLOUDOGU = "cloudoguInstall", } export type PluginModalContent = { @@ -83,7 +79,6 @@ const PluginsOverview: FC = ({ installed }) => { error: installedPluginsError, } = useInstalledPlugins({ enabled: installed }); const { data: pendingPlugins, isLoading: isLoadingPendingPlugins, error: pendingPluginsError } = usePendingPlugins(); - const pluginCenterAuthInfo = usePluginCenterAuthInfo(); const [showPendingModal, setShowPendingModal] = useState(false); const [showExecutePendingModal, setShowExecutePendingModal] = useState(false); const [showUpdateAllModal, setShowUpdateAllModal] = useState(false); @@ -97,9 +92,7 @@ const PluginsOverview: FC = ({ installed }) => { return (
- - {t("plugins.title")} <PluginCenterAuthInfo {...pluginCenterAuthInfo} /> - + {t("plugins.title")}
{actions} @@ -166,11 +159,7 @@ const PluginsOverview: FC = ({ installed }) => { return ( <> {pluginCenterStatusNotification} - + ); } @@ -211,7 +200,6 @@ const PluginsOverview: FC = ({ installed }) => { return ( <> {renderHeader(actions)} - {pluginCenterAuthInfo.data?.default ? : null} {renderPluginsList()} {renderModals()} 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 ad429cdb6d..24d14da58b 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 @@ -46,7 +46,6 @@ public class ConfigDto extends HalRepresentation implements UpdateConfigDto { private Set proxyExcludes; private boolean skipFailedAuthenticators; private String pluginUrl; - private String pluginAuthUrl; private long loginAttemptLimitTimeout; private boolean enabledXsrfProtection; private boolean enabledUserConverter; 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 cc55a210b6..b05ad3a971 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 @@ -108,7 +108,6 @@ public class IndexDtoGenerator extends HalAppenderMapper { } if (PluginPermissions.read().isPermitted()) { - builder.single(link("pluginCenterAuth", resourceLinks.pluginCenterAuth().auth())); builder.single(link("installedPlugins", resourceLinks.installedPluginCollection().self())); builder.single(link("availablePlugins", resourceLinks.availablePluginCollection().self())); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthResource.java deleted file mode 100644 index 82b900f324..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthResource.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.api.v2.resources; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; -import de.otto.edison.hal.Link; -import de.otto.edison.hal.Links; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import jakarta.annotation.Nullable; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.FormParam; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriBuilder; -import jakarta.ws.rs.core.UriInfo; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.SimplePrincipalCollection; -import sonia.scm.ExceptionWithContext; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.plugin.AuthenticationInfo; -import sonia.scm.plugin.PluginCenterAuthenticator; -import sonia.scm.plugin.PluginPermissions; -import sonia.scm.security.AllowAnonymousAccess; -import sonia.scm.security.Impersonator; -import sonia.scm.security.SecureParameterSerializer; -import sonia.scm.security.XsrfExcludes; -import sonia.scm.user.DisplayUser; -import sonia.scm.user.User; -import sonia.scm.user.UserDisplayManager; -import sonia.scm.util.HttpUtil; -import sonia.scm.web.VndMediaType; - -import java.io.IOException; -import java.net.URI; -import java.util.Optional; -import java.util.UUID; - -@Singleton -public class PluginCenterAuthResource { - - @VisibleForTesting - static final String ERROR_SOURCE_MISSING = "5DSqG6Mcg1"; - @VisibleForTesting - static final String ERROR_AUTHENTICATION_DISABLED = "8tSqFDot11"; - @VisibleForTesting - static final String ERROR_ALREADY_AUTHENTICATED = "8XSqFEBd41"; - @VisibleForTesting - static final String ERROR_PARAMS_MISSING = "52SqQBdpO1"; - @VisibleForTesting - static final String ERROR_CHALLENGE_MISSING = "FNSqFKQIR1"; - @VisibleForTesting - static final String ERROR_CHALLENGE_DOES_NOT_MATCH = "8ESqFElpI1"; - - private static final String METHOD_LOGIN = "login"; - private static final String METHOD_LOGOUT = "logout"; - - private final ScmPathInfoStore pathInfoStore; - private final PluginCenterAuthenticator authenticator; - private final ScmConfiguration configuration; - private final UserDisplayManager userDisplayManager; - private final XsrfExcludes excludes; - private final ChallengeGenerator challengeGenerator; - private final SecureParameterSerializer parameterSerializer; - private final Impersonator impersonator; - - private String challenge; - - @Inject - public PluginCenterAuthResource( - ScmPathInfoStore pathInfoStore, - PluginCenterAuthenticator authenticator, - UserDisplayManager userDisplayManager, - ScmConfiguration scmConfiguration, - XsrfExcludes excludes, - SecureParameterSerializer parameterSerializer, - Impersonator impersonator) { - this( - pathInfoStore, authenticator, userDisplayManager, scmConfiguration, excludes, () -> UUID.randomUUID().toString(), - parameterSerializer, impersonator); - } - - @VisibleForTesting - @SuppressWarnings("java:S107") // parameter count is ok for testing - PluginCenterAuthResource( - ScmPathInfoStore pathInfoStore, - PluginCenterAuthenticator authenticator, - UserDisplayManager userDisplayManager, - ScmConfiguration configuration, - XsrfExcludes excludes, - ChallengeGenerator challengeGenerator, - SecureParameterSerializer parameterSerializer, - Impersonator impersonator) { - this.pathInfoStore = pathInfoStore; - this.authenticator = authenticator; - this.configuration = configuration; - this.userDisplayManager = userDisplayManager; - this.excludes = excludes; - this.challengeGenerator = challengeGenerator; - this.parameterSerializer = parameterSerializer; - this.impersonator = impersonator; - } - - @GET - @Path("") - @Operation( - summary = "Return plugin center auth info", - description = "Return authentication information of plugin center connection", - tags = "Plugin Management", - operationId = "plugin_center_auth_information" - ) - @ApiResponse( - responseCode = "200", - description = "success", - content = @Content( - mediaType = VndMediaType.PLUGIN_COLLECTION, - schema = @Schema(implementation = PluginCenterAuthenticationInfoDto.class) - ) - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - ) - ) - @Produces(VndMediaType.PLUGIN_CENTER_AUTH_INFO) - public Response authenticationInfo(@Context UriInfo uriInfo) { - Optional authentication = authenticator.getAuthenticationInfo(); - if (authentication.isPresent()) { - return Response.ok(createAuthenticatedDto(uriInfo, authentication.get())).build(); - } - PluginCenterAuthenticationInfoDto dto = new PluginCenterAuthenticationInfoDto(createLinks(uriInfo, null)); - dto.setDefault(configuration.isDefaultPluginAuthUrl()); - return Response.ok(dto).build(); - } - - private PluginCenterAuthenticationInfoDto createAuthenticatedDto(UriInfo uriInfo, AuthenticationInfo info) { - PluginCenterAuthenticationInfoDto dto = new PluginCenterAuthenticationInfoDto( - createLinks(uriInfo, info) - ); - - dto.setPrincipal(getPrincipalDisplayName(info.getPrincipal())); - dto.setPluginCenterSubject(info.getPluginCenterSubject()); - dto.setDate(info.getDate()); - dto.setDefault(configuration.isDefaultPluginAuthUrl()); - dto.setFailed(info.isFailed()); - return dto; - } - - @GET - @Path("login") - @Operation( - summary = "Login", - description = "Start the authentication flow to connect the plugin center with an account", - tags = "Plugin Management", - operationId = "plugin_center_auth_login" - ) - @ApiResponse( - responseCode = "303", - description = "See other" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:write\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - ) - ) - public Response login( - @Context UriInfo uriInfo, @QueryParam("source") String source, @QueryParam("reconnect") boolean reconnect - ) throws IOException { - String pluginAuthUrl = configuration.getPluginAuthUrl(); - - if (Strings.isNullOrEmpty(source)) { - return error(ERROR_SOURCE_MISSING); - } - - if (Strings.isNullOrEmpty(pluginAuthUrl)) { - return error(ERROR_AUTHENTICATION_DISABLED); - } - - if (!reconnect && authenticator.isAuthenticated()) { - return error(ERROR_ALREADY_AUTHENTICATED); - } - - challenge = challengeGenerator.create(); - - URI selfUri = uriInfo.getAbsolutePath(); - selfUri = selfUri.resolve(selfUri.getPath().replace("/login", "/callback")); - - String principal = SecurityUtils.getSubject().getPrincipal().toString(); - - AuthParameter parameter = new AuthParameter( - principal, - challenge, - source - ); - - URI callbackUri = UriBuilder.fromUri(selfUri) - .queryParam("params", parameterSerializer.serialize(parameter)) - .build(); - - excludes.add(callbackUri.getPath()); - - URI authUri = UriBuilder.fromUri(pluginAuthUrl).queryParam("instance", callbackUri.toASCIIString()).build(); - return Response.seeOther(authUri).build(); - } - - private Links createLinks(UriInfo uriInfo, @Nullable AuthenticationInfo info) { - String self = uriInfo.getAbsolutePath().toASCIIString(); - Links.Builder builder = Links.linkingTo().self(self); - if (PluginPermissions.write().isPermitted()) { - if (info != null) { - builder.single(Link.link(METHOD_LOGOUT, self)); - if (info.isFailed()) { - String reconnectLink = uriInfo.getAbsolutePathBuilder() - .path(METHOD_LOGIN) - .queryParam("reconnect", "true") - .build() - .toASCIIString(); - builder.single(Link.link("reconnect", reconnectLink)); - } - } else if (!Strings.isNullOrEmpty(configuration.getPluginAuthUrl())) { - builder.single(Link.link(METHOD_LOGIN, uriInfo.getAbsolutePathBuilder().path(METHOD_LOGIN).build().toASCIIString())); - } - } - return builder.build(); - } - - private String getPrincipalDisplayName(String principal) { - return userDisplayManager.get(principal).map(DisplayUser::getDisplayName).orElse(principal); - } - - @DELETE - @Path("") - @Operation( - summary = "Logout", - description = "Start the authentication flow to connect the plugin center with an account", - tags = "Plugin Management", - operationId = "plugin_center_auth_logout" - ) - @ApiResponse( - responseCode = "204", - description = "No content" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:write\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - ) - ) - public Response logout() { - authenticator.logout(); - return Response.noContent().build(); - } - - @POST - @Path("callback") - @Operation( - summary = "Finalize authentication", - description = "Callback endpoint for the authentication flow to finalize the authentication", - tags = "Plugin Management", - operationId = "plugin_center_auth_callback" - ) - @ApiResponse( - responseCode = "303", - description = "See other" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:write\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - ) - ) - @AllowAnonymousAccess - public Response callback( - @Context UriInfo uriInfo, - @QueryParam("params") String encryptedParams, - @FormParam("subject") String subject, - @FormParam("refresh_token") String refreshToken - ) throws IOException { - if (Strings.isNullOrEmpty(encryptedParams)) { - return error(ERROR_PARAMS_MISSING); - } - - AuthParameter params = parameterSerializer.deserialize(encryptedParams, AuthParameter.class); - - Optional error = checkChallenge(params.getChallenge()); - if (error.isPresent()) { - return error(error.get()); - } - - challenge = null; - excludes.remove(uriInfo.getPath()); - - PrincipalCollection principal = createPrincipalCollection(params); - try (Impersonator.Session session = impersonator.impersonate(principal)) { - authenticator.authenticate(subject, refreshToken); - } catch (ExceptionWithContext ex) { - return error(ex.getCode()); - } - - return redirect(params.getSource()); - } - - private PrincipalCollection createPrincipalCollection(AuthParameter params) { - SimplePrincipalCollection principal = new SimplePrincipalCollection( - params.getPrincipal(), "pluginCenterAuth" - ); - User user = new User(params.getPrincipal()); - principal.add(user, "pluginCenterAuth"); - return principal; - } - - @GET - @Path("callback") - @Operation( - summary = "Abort authentication", - description = "Callback endpoint for the authentication flow to abort the authentication", - tags = "Plugin Management", - operationId = "plugin_center_auth_callback_abort" - ) - @ApiResponse( - responseCode = "303", - description = "See other" - ) - @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") - @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:write\" privilege") - @ApiResponse( - responseCode = "500", - description = "internal server error", - content = @Content( - mediaType = VndMediaType.ERROR_TYPE, - schema = @Schema(implementation = ErrorDto.class) - ) - ) - public Response callbackAbort(@Context UriInfo uriInfo, @QueryParam("params") String encryptedParams) throws IOException { - if (Strings.isNullOrEmpty(encryptedParams)) { - return error(ERROR_PARAMS_MISSING); - } - - AuthParameter params = parameterSerializer.deserialize(encryptedParams, AuthParameter.class); - - Optional error = checkChallenge(params.getChallenge()); - if (error.isPresent()) { - return error(error.get()); - } - - challenge = null; - - excludes.remove(uriInfo.getPath()); - - return redirect(params.getSource()); - } - - private Response error(String code) { - return redirect("error/" + code); - } - - private Response redirect(String location) { - URI rootUri = pathInfoStore.get().getRootUri(); - String path = rootUri.getPath(); - if (!Strings.isNullOrEmpty(location)) { - path = HttpUtil.concatenate(path, location); - } - return redirect(rootUri.resolve(path)); - } - - private Response redirect(URI location) { - return Response.status(Response.Status.SEE_OTHER).location(location).build(); - } - - private Optional checkChallenge(String challengeFromRequest) { - if (Strings.isNullOrEmpty(challenge)) { - return Optional.of(ERROR_CHALLENGE_MISSING); - } - if (!challenge.equals(challengeFromRequest)) { - return Optional.of(ERROR_CHALLENGE_DOES_NOT_MATCH); - } - return Optional.empty(); - } - - @FunctionalInterface - interface ChallengeGenerator { - String create(); - } - - @Data - @NoArgsConstructor - @AllArgsConstructor - static class AuthParameter { - - private String principal; - private String challenge; - private String source; - - } - -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthenticationInfoDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthenticationInfoDto.java deleted file mode 100644 index b9634ab66f..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginCenterAuthenticationInfoDto.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.api.v2.resources; - -import com.fasterxml.jackson.annotation.JsonInclude; -import de.otto.edison.hal.HalRepresentation; -import de.otto.edison.hal.Links; -import lombok.Getter; -import lombok.Setter; - -import java.time.Instant; - -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; - -@Getter -@Setter -@SuppressWarnings("java:S2160") // we need no equals here -public class PluginCenterAuthenticationInfoDto extends HalRepresentation { - - @JsonInclude(NON_NULL) - private String principal; - @JsonInclude(NON_NULL) - private String pluginCenterSubject; - @JsonInclude(NON_NULL) - private Instant date; - private boolean isDefault; - private boolean failed; - - public PluginCenterAuthenticationInfoDto(Links links) { - super(links); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java index 085b4f80fa..a729f2ab0f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java @@ -22,7 +22,6 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import sonia.scm.plugin.PluginInformation; import java.util.Set; @@ -41,7 +40,6 @@ public class PluginDto extends HalRepresentation { private String author; private String category; private String avatarUrl; - private PluginInformation.PluginType type = PluginInformation.PluginType.SCM; private boolean pending; @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean core; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java index e13e4b6794..b624967c0c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java @@ -16,7 +16,6 @@ package sonia.scm.api.v2.resources; -import com.google.common.base.Strings; import de.otto.edison.hal.Links; import jakarta.inject.Inject; import org.mapstruct.Mapper; @@ -63,9 +62,6 @@ public abstract class PluginDtoMapper { PluginDto dto = createDtoForAvailable(plugin); map(dto, plugin); dto.setPending(plugin.isPending()); - if (dto.getType() == null) { - dto.setType(PluginInformation.PluginType.SCM); - } return dto; } @@ -86,16 +82,8 @@ public abstract class PluginDtoMapper { .self(information.getName())); if (!plugin.isPending() && PluginPermissions.write().isPermitted()) { - boolean isCloudoguPlugin = plugin.getDescriptor().getInformation().getType() == PluginInformation.PluginType.CLOUDOGU; - if (isCloudoguPlugin) { - Optional cloudoguInstallLink = plugin.getDescriptor().getInstallLink(); - cloudoguInstallLink.ifPresent(link -> links.single(link("cloudoguInstall", link))); - } - - if (!Strings.isNullOrEmpty(plugin.getDescriptor().getUrl())) { - String href = resourceLinks.availablePlugin().install(information.getName()); - appendLink(links, "install", href); - } + String href = resourceLinks.availablePlugin().install(information.getName()); + appendLink(links, "install", href); } return new PluginDto(links.build()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java index fa3ac37a9a..0220af340c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java @@ -31,19 +31,16 @@ public class PluginRootResource { private final Provider installedPluginResourceProvider; private final Provider availablePluginResourceProvider; private final Provider pendingPluginResourceProvider; - private final Provider pluginCenterAuthResourceProvider; @Inject public PluginRootResource( Provider installedPluginResourceProvider, Provider availablePluginResourceProvider, - Provider pendingPluginResourceProvider, - Provider pluginCenterAuthResourceProvider + Provider pendingPluginResourceProvider ) { this.installedPluginResourceProvider = installedPluginResourceProvider; this.availablePluginResourceProvider = availablePluginResourceProvider; this.pendingPluginResourceProvider = pendingPluginResourceProvider; - this.pluginCenterAuthResourceProvider = pluginCenterAuthResourceProvider; } @Path("/installed") @@ -52,13 +49,12 @@ public class PluginRootResource { } @Path("/available") - public AvailablePluginResource availablePlugins() { return availablePluginResourceProvider.get(); } + public AvailablePluginResource availablePlugins() { + return availablePluginResourceProvider.get(); + } @Path("/pending") - public PendingPluginResource pendingPlugins() { return pendingPluginResourceProvider.get(); } - - @Path("/auth") - public PluginCenterAuthResource authResource() { - return pluginCenterAuthResourceProvider.get(); + public PendingPluginResource pendingPlugins() { + return pendingPluginResourceProvider.get(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 0569ed474a..2e266f3f78 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -1290,22 +1290,6 @@ class ResourceLinks { } } - public PluginCenterAuthLinks pluginCenterAuth() { - return new PluginCenterAuthLinks(scmPathInfoStore.get().get()); - } - - static class PluginCenterAuthLinks { - private final LinkBuilder indexLinkBuilder; - - PluginCenterAuthLinks(ScmPathInfo pathInfo) { - indexLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginCenterAuthResource.class); - } - - String auth() { - return indexLinkBuilder.method("authResource").parameters().method("authenticationInfo").parameters().href(); - } - } - public AlertsLinks alerts() { return new AlertsLinks(scmPathInfoStore.get().get()); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/AuthenticationInfo.java b/scm-webapp/src/main/java/sonia/scm/plugin/AuthenticationInfo.java deleted file mode 100644 index 5626fe793d..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/AuthenticationInfo.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import java.time.Instant; - -/** - * Information about the plugin center authentication. - * @since 2.28.0 - */ -public interface AuthenticationInfo { - - /** - * Returns the username of the SCM-Manager user which has authenticated the plugin center. - * @return SCM-Manager username - */ - String getPrincipal(); - - /** - * Returns the subject of the plugin center user. - * @return plugin center subject - */ - String getPluginCenterSubject(); - - /** - * Returns the date on which the authentication was performed. - * @return authentication date - */ - Instant getDate(); - - /** - * Returns {@code true} if the last authentication has failed. - * @return {@code true} if the last authentication has failed. - * @since 2.31.0 - */ - default boolean isFailed() { - return false; - } - -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/FetchAccessTokenFailedException.java b/scm-webapp/src/main/java/sonia/scm/plugin/FetchAccessTokenFailedException.java deleted file mode 100644 index 431971bd41..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/FetchAccessTokenFailedException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import sonia.scm.ExceptionWithContext; - -import java.util.Collections; - -/** - * Exception is thrown if the exchange of a refresh token to an access token fails. - * - * @since 2.28.0 - */ -public class FetchAccessTokenFailedException extends ExceptionWithContext { - - public FetchAccessTokenFailedException(String message) { - super(Collections.emptyList(), message); - } - - public FetchAccessTokenFailedException(String message, Exception cause) { - super(Collections.emptyList(), message, cause); - } - - @Override - public String getCode() { - return "AHSqALeEv1"; - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java index 09194d3c25..e62d294c71 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java @@ -52,12 +52,6 @@ public class PluginCenter { this.pluginCenterResultCache = cacheManager.getCache(PLUGIN_CENTER_RESULT_CACHE_NAME); } - @Subscribe - public void handle(PluginCenterAuthenticationEvent event) { - LOG.debug("clear plugin center cache, because of {}", event); - pluginCenterResultCache.clear(); - } - @Subscribe public void handle(ScmConfigurationChangedEvent event) { LOG.debug("clear plugin center cache, because of {}", event); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationEvent.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationEvent.java deleted file mode 100644 index 3f4583b7c6..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - - -/** - * Marker interface for plugin center authentication events such as login or logout. - * @since 2.28.0 - */ -public interface PluginCenterAuthenticationEvent { -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationFailedEvent.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationFailedEvent.java deleted file mode 100644 index 4fe13d80f2..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticationFailedEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import lombok.Value; -import sonia.scm.event.Event; - -/** - * Event is thrown if the authentication to the plugin center fails. - * @since 2.30.0 - */ -@Event -@Value -public class PluginCenterAuthenticationFailedEvent implements PluginCenterAuthenticationEvent { - AuthenticationInfo authenticationInfo; -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticator.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticator.java deleted file mode 100644 index 5a69eef620..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterAuthenticator.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlRootElement; -import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.Value; -import org.apache.shiro.SecurityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.event.ScmEventBus; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.net.ahc.AdvancedHttpResponse; -import sonia.scm.store.ConfigurationStore; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.util.HttpUtil; -import sonia.scm.xml.XmlEncryptionAdapter; -import sonia.scm.xml.XmlInstantAdapter; - -import java.io.IOException; -import java.time.Instant; -import java.util.Optional; - -import static sonia.scm.plugin.Tracing.SPAN_KIND; - -@Singleton -public class PluginCenterAuthenticator { - - private static final Logger LOG = LoggerFactory.getLogger(PluginCenterAuthenticator.class); - - @VisibleForTesting - static final String STORE_NAME = "plugin-center-auth"; - - private final ConfigurationStore configurationStore; - private final ScmConfiguration scmConfiguration; - private final AdvancedHttpClient advancedHttpClient; - private final ScmEventBus eventBus; - - @Inject - public PluginCenterAuthenticator( - ConfigurationStoreFactory configurationStore, ScmConfiguration scmConfiguration, - AdvancedHttpClient advancedHttpClient, ScmEventBus eventBus - ) { - this.configurationStore = configurationStore.withType(Authentication.class).withName(STORE_NAME).build(); - this.scmConfiguration = scmConfiguration; - this.advancedHttpClient = advancedHttpClient; - this.eventBus = eventBus; - } - - public void authenticate(String pluginCenterSubject, String refreshToken) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(pluginCenterSubject), "pluginCenterSubject is required"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(refreshToken), "refresh token is required"); - - // only a user which is able to manage plugins, can authenticate the plugin center - PluginPermissions.write().check(); - - // check if refresh token is valid - Authentication authentication = new Authentication( - principal(), pluginCenterSubject, refreshToken, Instant.now(), false - ); - fetchAccessToken(authentication); - eventBus.post(new PluginCenterLoginEvent(authentication)); - } - - public void logout() { - PluginPermissions.write().check(); - - getAuthenticationInfo().ifPresent(authenticationInfo -> { - eventBus.post(new PluginCenterLogoutEvent(authenticationInfo)); - configurationStore.delete(); - }); - } - - public boolean isAuthenticated() { - return getAuthentication().isPresent(); - } - - public Optional getAuthenticationInfo() { - PluginPermissions.read().check(); - return getAuthentication().map(a -> a); - } - - public Optional fetchAccessToken() { - PluginPermissions.read().check(); - Authentication authentication = getAuthentication() - .orElseThrow(() -> new IllegalStateException("An access token can only be obtained, after a prior authentication")); - try { - return Optional.of(fetchAccessToken(authentication)); - } catch (FetchAccessTokenFailedException ex) { - LOG.warn("failed to fetch access token", ex); - return Optional.empty(); - } - } - - @CanIgnoreReturnValue - private String fetchAccessToken(Authentication authentication) { - String pluginAuthUrl = scmConfiguration.getPluginAuthUrl(); - Preconditions.checkState(!Strings.isNullOrEmpty(pluginAuthUrl), "plugin auth url is not configured"); - - try { - AdvancedHttpResponse response = advancedHttpClient.post(HttpUtil.concatenate(pluginAuthUrl, "refresh")) - .spanKind(SPAN_KIND) - .jsonContent(new RefreshRequest(authentication.getRefreshToken())) - .request(); - - if (!response.isSuccessful()) { - authenticationFailed(authentication); - throw new FetchAccessTokenFailedException("failed to obtain access token, server returned status code " + response.getStatus()); - } - - RefreshResponse refresh = response.contentFromJson(RefreshResponse.class); - - authentication.setRefreshToken(refresh.getRefreshToken()); - authentication.setFailed(false); - configurationStore.set(authentication); - - return refresh.getAccessToken(); - } catch (IOException ex) { - authenticationFailed(authentication); - throw new FetchAccessTokenFailedException("failed to obtain an access token", ex); - } - } - - private void authenticationFailed(Authentication authentication) { - authentication.setFailed(true); - configurationStore.set(authentication); - eventBus.post(new PluginCenterAuthenticationFailedEvent(authentication)); - } - - private String principal() { - return SecurityUtils.getSubject().getPrincipal().toString(); - } - - private Optional getAuthentication() { - return configurationStore.getOptional(); - } - - @Data - @XmlRootElement - @AllArgsConstructor - @NoArgsConstructor - @XmlAccessorType(XmlAccessType.FIELD) - public static class Authentication implements AuthenticationInfo { - private String principal; - private String pluginCenterSubject; - @XmlJavaTypeAdapter(XmlEncryptionAdapter.class) - private String refreshToken; - @XmlJavaTypeAdapter(XmlInstantAdapter.class) - private Instant date; - private boolean failed; - } - - @Value - public static class RefreshRequest { - @JsonProperty("refresh_token") - String refreshToken; - } - - @Data - public static class RefreshResponse { - @JsonProperty("access_token") - private String accessToken; - @JsonProperty("refresh_token") - private String refreshToken; - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java index 9dae43ec1e..b03a7d38cb 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java @@ -79,7 +79,6 @@ public final class PluginCenterDto implements Serializable { private final String author; private final String avatarUrl; private final String sha256sum; - private PluginInformation.PluginType type; @XmlElement(name = "conditions") private final Condition conditions; diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java index 20222aa5f2..05652b1376 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java @@ -47,23 +47,12 @@ public abstract class PluginCenterDtoMapper { .collect(Collectors.toSet()); for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) { - // plugin center api returns always a download link, - // but for cloudogu plugin without authentication the href is an empty string String url = plugin.getLinks().get("download").getHref(); - String installLink = getInstallLink(plugin); AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor( - map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum(), installLink + map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum() ); plugins.add(new AvailablePlugin(descriptor)); } return new PluginCenterResult(plugins, pluginSets); } - - private String getInstallLink(PluginCenterDto.Plugin plugin) { - PluginCenterDto.Link link = plugin.getLinks().get("install"); - if (link != null) { - return link.getHref(); - } - return null; - } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java index ef23f49661..da1603dd00 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoader.java @@ -32,24 +32,21 @@ class PluginCenterLoader { private static final Logger LOG = LoggerFactory.getLogger(PluginCenterLoader.class); private final AdvancedHttpClient client; - private final PluginCenterAuthenticator authenticator; private final PluginCenterDtoMapper mapper; private final ScmEventBus eventBus; @Inject - public PluginCenterLoader(AdvancedHttpClient client, ScmEventBus eventBus, PluginCenterAuthenticator authenticator) { - this(client, authenticator, PluginCenterDtoMapper.INSTANCE, eventBus); + public PluginCenterLoader(AdvancedHttpClient client, ScmEventBus eventBus) { + this(client, PluginCenterDtoMapper.INSTANCE, eventBus); } @VisibleForTesting PluginCenterLoader( AdvancedHttpClient client, - PluginCenterAuthenticator authenticator, PluginCenterDtoMapper mapper, ScmEventBus eventBus ) { this.client = client; - this.authenticator = authenticator; this.mapper = mapper; this.eventBus = eventBus; } @@ -61,11 +58,8 @@ class PluginCenterLoader { return new PluginCenterResult(PluginCenterStatus.DEACTIVATED); } LOG.info("fetch plugins from {}", url); - AdvancedHttpRequest request = client.get(url).spanKind(SPAN_KIND); - if (authenticator.isAuthenticated()) { - authenticator.fetchAccessToken().ifPresent(request::bearerAuth); - } - PluginCenterDto pluginCenterDto = request.request().contentFromJson(PluginCenterDto.class); + PluginCenterDto pluginCenterDto = client.get(url).spanKind(SPAN_KIND).request() + .contentFromJson(PluginCenterDto.class); return mapper.map(pluginCenterDto); } catch (Exception ex) { LOG.error("failed to load plugins from plugin center, returning empty list", ex); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoginEvent.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoginEvent.java deleted file mode 100644 index bfe18efc68..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLoginEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import lombok.Value; -import sonia.scm.event.Event; - -/** - * Event is fired after a successful login to the plugin center. - * @since 2.28.0 - */ -@Event -@Value -public class PluginCenterLoginEvent implements PluginCenterAuthenticationEvent { - AuthenticationInfo authenticationInfo; -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLogoutEvent.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLogoutEvent.java deleted file mode 100644 index 2b09a3cf84..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterLogoutEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import lombok.Value; -import sonia.scm.event.Event; - -/** - * Event is fired after a successful logout from plugin center. - * @since 2.28.0 - */ -@Event -@Value -public class PluginCenterLogoutEvent implements PluginCenterAuthenticationEvent { - AuthenticationInfo priorAuthenticationInfo; -} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java index 47bd05ede9..d28cc9f753 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java @@ -22,7 +22,6 @@ import com.google.common.hash.HashingInputStream; import jakarta.inject.Inject; import sonia.scm.SCMContextProvider; import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.net.ahc.AdvancedHttpRequest; import java.io.IOException; import java.io.InputStream; @@ -38,14 +37,12 @@ class PluginInstaller { private final SCMContextProvider scmContext; private final AdvancedHttpClient client; - private final PluginCenterAuthenticator authenticator; private final SmpDescriptorExtractor smpDescriptorExtractor; @Inject - public PluginInstaller(SCMContextProvider scmContext, AdvancedHttpClient client, PluginCenterAuthenticator authenticator, SmpDescriptorExtractor smpDescriptorExtractor) { + public PluginInstaller(SCMContextProvider scmContext, AdvancedHttpClient client, SmpDescriptorExtractor smpDescriptorExtractor) { this.scmContext = scmContext; this.client = client; - this.authenticator = authenticator; this.smpDescriptorExtractor = smpDescriptorExtractor; } @@ -122,11 +119,10 @@ class PluginInstaller { } private InputStream download(AvailablePlugin plugin) throws IOException { - AdvancedHttpRequest request = client.get(plugin.getDescriptor().getUrl()).spanKind(SPAN_KIND); - if (authenticator.isAuthenticated()) { - authenticator.fetchAccessToken().ifPresent(request::bearerAuth); - } - return request.request().contentAsStream(); + return client.get(plugin.getDescriptor().getUrl()) + .spanKind(SPAN_KIND) + .request() + .contentAsStream(); } private Path createFile(AvailablePlugin plugin) throws IOException { diff --git a/scm-webapp/src/main/java/sonia/scm/security/SecureParameterSerializer.java b/scm-webapp/src/main/java/sonia/scm/security/SecureParameterSerializer.java deleted file mode 100644 index 18811eb458..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/security/SecureParameterSerializer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.inject.Inject; - -import java.io.IOException; - -public final class SecureParameterSerializer { - - private final ObjectMapper mapper; - - @Inject - public SecureParameterSerializer(ObjectMapper mapper) { - this.mapper = mapper; - } - - public String serialize(Object object) throws IOException { - String json = mapper.writeValueAsString(object); - return CipherUtil.getInstance().encode(json); - } - - public T deserialize(String serialized, Class type) throws IOException { - String decoded = CipherUtil.getInstance().decode(serialized); - return mapper.readValue(decoded, type); - } - -} diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java index d0784d34ff..d9da7f288a 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfAccessTokenValidator.java @@ -41,12 +41,10 @@ public class XsrfAccessTokenValidator implements AccessTokenValidator { ); private final Provider requestProvider; - private final XsrfExcludes excludes; - + @Inject - public XsrfAccessTokenValidator(Provider requestProvider, XsrfExcludes excludes) { + public XsrfAccessTokenValidator(Provider requestProvider) { this.requestProvider = requestProvider; - this.excludes = excludes; } @Override @@ -55,10 +53,6 @@ public class XsrfAccessTokenValidator implements AccessTokenValidator { if (xsrfClaim.isPresent()) { HttpServletRequest request = requestProvider.get(); - if (excludes.contains(request.getRequestURI())) { - return true; - } - String xsrfHeaderValue = request.getHeader(Xsrf.HEADER_KEY); return ALLOWED_METHOD.contains(request.getMethod().toUpperCase(Locale.ENGLISH)) || xsrfClaim.get().equals(xsrfHeaderValue); diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfExcludes.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfExcludes.java deleted file mode 100644 index 882e44093e..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfExcludes.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.security; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import jakarta.inject.Singleton; - -import java.util.HashSet; -import java.util.Set; - -/** - * XsrfExcludes can be used to define request uris which are excluded from xsrf validation. - * @since 2.28.0 - */ -@Singleton -public class XsrfExcludes { - - private final Set excludes = new HashSet<>(); - - /** - * Exclude the given request uri from xsrf validation. - * @param requestUri request uri - */ - public void add(String requestUri) { - excludes.add(requestUri); - } - - /** - * Include prior excluded request uri to xsrf validation. - * @param requestUri request uri - * @return {@code true} is uri was excluded - */ - @CanIgnoreReturnValue - public boolean remove(String requestUri) { - return excludes.remove(requestUri); - } - - /** - * Returns {@code true} if the request uri is excluded from xsrf validation. - * @param requestUri request uri - * @return {@code true} if uri is excluded - */ - public boolean contains(String requestUri) { - return excludes.contains(requestUri); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/update/plugin/PluginCenterAuthenticationUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/plugin/PluginCenterAuthenticationUpdateStep.java deleted file mode 100644 index af948e75cc..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/update/plugin/PluginCenterAuthenticationUpdateStep.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.update.plugin; - -import com.google.common.base.Strings; -import jakarta.inject.Inject; -import sonia.scm.migration.UpdateStep; -import sonia.scm.plugin.Extension; -import sonia.scm.plugin.PluginCenterAuthenticator; -import sonia.scm.security.CipherUtil; -import sonia.scm.store.ConfigurationStore; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.version.Version; - -import static sonia.scm.version.Version.parse; - -@Extension -public class PluginCenterAuthenticationUpdateStep implements UpdateStep { - - private final ConfigurationStoreFactory configurationStoreFactory; - - @Inject - public PluginCenterAuthenticationUpdateStep(ConfigurationStoreFactory configurationStoreFactory) { - this.configurationStoreFactory = configurationStoreFactory; - } - - @Override - public void doUpdate() throws Exception { - ConfigurationStore configurationStore = configurationStoreFactory - .withType(PluginCenterAuthenticator.Authentication.class) - .withName("plugin-center-auth") - .build(); - configurationStore.getOptional() - .ifPresent(config -> { - String token = config.getRefreshToken(); - CipherUtil cipher = CipherUtil.getInstance(); - if (Strings.isNullOrEmpty(token) || !token.startsWith("{enc}")) { - token = "{enc}".concat(cipher.encode(token)); - config.setRefreshToken(token); - configurationStore.set(config); - } - }); - } - - - @Override - public Version getTargetVersion() { - return parse("2.30.0"); - } - - @Override - public String getAffectedDataType() { - return "sonia.scm.plugin-center.authentication"; - } -} diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index bb4cb8ff58..39fe234bdc 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -393,34 +393,6 @@ "displayName": "Änderung fehlgeschlagen", "description": "Die Änderung konnte nicht durchgeführt werden. Dieses kann mehrere Ursachen haben, z. B. eine Datei die nicht verschoben werden kann, ungültige Dateinamen o. ä." }, - "5DSqG6Mcg1": { - "displayName": "Fehlender Source Parameter", - "description": "Der Source Parameter wird für die Authentifizierung benötigt." - }, - "8tSqFDot11": { - "displayName": "Authentifizierung ist deaktiviert", - "description": "Die Plugin Center Authentifizierung ist deaktiviert." - }, - "8XSqFEBd41": { - "displayName": "Authentifizierung bereits durchgeführt", - "description": "Das Plugin Center wurde bereits authentifiziert." - }, - "FNSqFKQIR1": { - "displayName": "Fehlender challenge Parameter", - "description": "Die Antwort der Authentifizierung enthält keinen Challenge Parameter." - }, - "8ESqFElpI1": { - "displayName": "Falscher challenge Parameter", - "description": "Die Antwort der Authentifizierung enthält einen falschen Challenge Parameter." - }, - "AHSqALeEv1": { - "displayName": "Authentifizierung fehlgeschlagen", - "description": "Die Authentifizierung des Plugin Centers ist fehlgeschlagen." - }, - "52SqQBdpO1": { - "displayName": "Fehlender params Parameter", - "description": "Die Antwort der Authentifizierung enthält keinen params Parameter." - }, "8OT4gBVvp1": { "displayName": "Repository Typ ungültig", "description": "Dieser Repository Typ wird nicht unterstützt." diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 968db35cdd..aeee6fa6ab 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -393,34 +393,6 @@ "displayName": "Modification failed", "description": "The modification could not be applied. This can have many reasons, for example a file that could not be moved, invalid file names, etc." }, - "5DSqG6Mcg1": { - "displayName": "Source missing", - "description": "The source parameter is missing." - }, - "8tSqFDot11": { - "displayName": "Authentication disabled", - "description": "Plugin center authentication is disabled." - }, - "8XSqFEBd41": { - "displayName": "Already authenticated", - "description": "The plugin center is already authenticated." - }, - "FNSqFKQIR1": { - "displayName": "Challenge missing", - "description": "The callback for the plugin center authentication is missing the challenge parameter." - }, - "8ESqFElpI1": { - "displayName": "Challenge mismatch", - "description": "The provided challenge does not match." - }, - "AHSqALeEv1": { - "displayName": "Authentication failed", - "description": "Plugin center authentication failed." - }, - "52SqQBdpO1": { - "displayName": "Params missing", - "description": "The parameter params is missing." - }, "8OT4gBVvp1": { "displayName": "Repository type invalid", "description": "The repository type is not supported." diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java index 0c9504867e..2121f4eaed 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java @@ -85,7 +85,7 @@ class AvailablePluginResourceTest { @BeforeEach void prepareEnvironment() { - pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null, null); + pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null); when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource); dispatcher.addSingletonResource(pluginRootResource); } 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 dd4f9b74c3..9199f25950 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 @@ -55,7 +55,6 @@ class ConfigDtoToScmConfigurationMapperTest { assertThat(config.getProxyExcludes()).contains(expectedExcludes); assertThat(config.isSkipFailedAuthenticators()).isTrue(); assertThat(config.getPluginUrl()).isEqualTo("https://plug.ins"); - assertThat(config.getPluginAuthUrl()).isEqualTo("https://plug.ins/oidc"); assertThat(config.getLoginAttemptLimitTimeout()).isEqualTo(40); assertThat(config.isEnabledXsrfProtection()).isTrue(); assertThat(config.isEnabledUserConverter()).isFalse(); @@ -99,7 +98,6 @@ class ConfigDtoToScmConfigurationMapperTest { configDto.setProxyExcludes(Sets.newSet(expectedExcludes)); configDto.setSkipFailedAuthenticators(true); configDto.setPluginUrl("https://plug.ins"); - configDto.setPluginAuthUrl("https://plug.ins/oidc"); configDto.setLoginAttemptLimitTimeout(40); configDto.setEnabledXsrfProtection(true); configDto.setNamespaceStrategy("username"); 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 223626f830..28957ba556 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 @@ -71,22 +71,6 @@ public class IndexResourceTest { this.indexResource = new IndexResource(generator); } - @Test - @SubjectAware(username = "dent", password = "secret") - public void shouldRenderPluginCenterAuthLink() { - IndexDto index = indexResource.getIndex(httpServletRequest); - - Assertions.assertThat(index.getLinks().getLinkBy("pluginCenterAuth")).isPresent(); - } - - @Test - @SubjectAware(username = "trillian", password = "secret") - public void shouldNotRenderPluginCenterLoginLinkIfPermissionsAreMissing() { - IndexDto index = indexResource.getIndex(httpServletRequest); - - Assertions.assertThat(index.getLinks().getLinkBy("pluginCenterAuth")).isNotPresent(); - } - @Test public void shouldRenderLoginUrlsForUnauthenticatedRequest() { IndexDto index = indexResource.getIndex(httpServletRequest); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java index a15b27aa8c..cf5f48c520 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java @@ -80,7 +80,7 @@ class InstalledPluginResourceTest { @BeforeEach void prepareEnvironment() { - pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null, null); + pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null); when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource); dispatcher.addSingletonResource(pluginRootResource); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java index b6f56866c2..eac7e94048 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java @@ -82,7 +82,7 @@ class PendingPluginResourceTest { @BeforeEach void prepareEnvironment() { dispatcher.registerException(ShiroException.class, Response.Status.UNAUTHORIZED); - PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource), null); + PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource)); dispatcher.addSingletonResource(pluginRootResource); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginCenterAuthResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginCenterAuthResourceTest.java deleted file mode 100644 index 5e530c909b..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginCenterAuthResourceTest.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.api.v2.resources; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.inject.util.Providers; -import jakarta.servlet.http.HttpServletResponse; -import lombok.Value; -import org.github.sdorra.jse.ShiroExtension; -import org.github.sdorra.jse.SubjectAware; -import org.jboss.resteasy.mock.MockHttpRequest; -import org.jboss.resteasy.mock.MockHttpResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.plugin.AuthenticationInfo; -import sonia.scm.plugin.FetchAccessTokenFailedException; -import sonia.scm.plugin.PluginCenterAuthenticator; -import sonia.scm.security.Impersonator; -import sonia.scm.security.SecureParameterSerializer; -import sonia.scm.security.XsrfExcludes; -import sonia.scm.user.DisplayUser; -import sonia.scm.user.UserDisplayManager; -import sonia.scm.user.UserTestData; -import sonia.scm.web.RestDispatcher; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Instant; -import java.util.Optional; -import java.util.function.Consumer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.AuthParameter; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ChallengeGenerator; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_ALREADY_AUTHENTICATED; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_AUTHENTICATION_DISABLED; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_CHALLENGE_DOES_NOT_MATCH; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_CHALLENGE_MISSING; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_PARAMS_MISSING; -import static sonia.scm.api.v2.resources.PluginCenterAuthResource.ERROR_SOURCE_MISSING; - -@ExtendWith({MockitoExtension.class, ShiroExtension.class}) -class PluginCenterAuthResourceTest { - - private final RestDispatcher dispatcher = new RestDispatcher(); - - private final ScmConfiguration scmConfiguration = new ScmConfiguration(); - - @Mock - private PluginCenterAuthenticator authenticator; - - @Mock - private XsrfExcludes excludes; - - @Mock - private ChallengeGenerator challengeGenerator; - - @Mock - private UserDisplayManager userDisplayManager; - - @Mock - private SecureParameterSerializer parameterSerializer; - - @Mock - private Impersonator impersonator; - - @BeforeEach - void setUpDispatcher() { - ScmPathInfoStore pathInfoStore = new ScmPathInfoStore(); - pathInfoStore.set(rootPathInfo); - - PluginCenterAuthResource resource = new PluginCenterAuthResource( - pathInfoStore, authenticator, userDisplayManager, - scmConfiguration, excludes, challengeGenerator, - parameterSerializer, impersonator - ); - - dispatcher.addSingletonResource( - new PluginRootResource( - null, - null, - null, - Providers.of(resource) - ) - ); - } - - @Nested - class GetAuthenticationInfo { - - @Test - void shouldReturnEmptyAuthenticationInfo() throws URISyntaxException, IOException { - JsonNode root = getJson("/v2/plugins/auth"); - - assertThat(root.has("principal")).isFalse(); - assertThat(root.has("pluginCenterSubject")).isFalse(); - assertThat(root.has("date")).isFalse(); - assertThat(root.get("_links").get("self").get("href").asText()).isEqualTo("/v2/plugins/auth"); - } - - @Test - void shouldReturnTrueForIsDefault() throws URISyntaxException, IOException { - JsonNode root = getJson("/v2/plugins/auth"); - - assertThat(root.get("default").asBoolean()).isTrue(); - } - - @Test - void shouldReturnFalseIfTheAuthUrlIsNotDefault() throws URISyntaxException, IOException { - scmConfiguration.setPluginAuthUrl("https://plug.ins"); - - JsonNode root = getJson("/v2/plugins/auth"); - - assertThat(root.get("default").asBoolean()).isFalse(); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:write") - void shouldReturnLoginLinkIfPermitted() throws URISyntaxException, IOException { - JsonNode root = getJson("/v2/plugins/auth"); - - assertThat(root.get("_links").get("login").get("href").asText()).isEqualTo("/v2/plugins/auth/login"); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:write") - void shouldNotReturnLoginLinkIfPermittedButNotConfigured() throws URISyntaxException, IOException { - scmConfiguration.setPluginAuthUrl(null); - - JsonNode root = getJson("/v2/plugins/auth"); - - assertThat(root.get("_links").get("login")).isNull(); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:write") - void shouldReturnReconnectAndLogoutLinkForFailedAuthentication() throws URISyntaxException, IOException { - JsonNode root = requestAuthInfo(true); - - assertThat(root.get("_links").get("reconnect").get("href").asText()).isEqualTo("/v2/plugins/auth/login?reconnect=true"); - } - - @Test - void shouldReturnAuthenticationInfo() throws IOException, URISyntaxException { - JsonNode root = requestAuthInfo(); - - assertThat(root.get("principal").asText()).isEqualTo("Tricia McMillan"); - assertThat(root.get("pluginCenterSubject").asText()).isEqualTo("tricia.mcmillan@hitchhiker.com"); - assertThat(root.get("date").asText()).isNotEmpty(); - assertThat(root.get("_links").get("self").get("href").asText()).isEqualTo("/v2/plugins/auth"); - } - - @Test - void shouldNotReturnLogoutLinkWithoutWritePermission() throws IOException, URISyntaxException { - JsonNode root = requestAuthInfo(); - - assertThat(root.get("_links").has("logout")).isFalse(); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:write") - void shouldReturnLogoutLinkIfPermitted() throws IOException, URISyntaxException { - JsonNode root = requestAuthInfo(); - - assertThat(root.get("_links").get("logout").get("href").asText()).isEqualTo("/v2/plugins/auth"); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:write") - void shouldNotReturnLogoutLinkIfPermitted() throws IOException, URISyntaxException { - JsonNode root = requestAuthInfo(); - - assertThat(root.get("_links").get("logout").get("href").asText()).isEqualTo("/v2/plugins/auth"); - } - - private JsonNode requestAuthInfo() throws IOException, URISyntaxException { - return requestAuthInfo(false); - } - - private JsonNode requestAuthInfo(boolean failed) throws IOException, URISyntaxException { - AuthenticationInfo info = new SimpleAuthenticationInfo( - "trillian", "tricia.mcmillan@hitchhiker.com", Instant.now(), failed - ); - when(authenticator.getAuthenticationInfo()).thenReturn(Optional.of(info)); - - DisplayUser user = DisplayUser.from(UserTestData.createTrillian()); - when(userDisplayManager.get("trillian")).thenReturn(Optional.of(user)); - - return getJson("/v2/plugins/auth"); - } - - } - - @Nested - class Logout { - - @Test - void shouldLogout() throws URISyntaxException { - MockHttpResponse response = request(MockHttpRequest.delete("/v2/plugins/auth")); - - verify(authenticator).logout(); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT); - } - - } - - @Nested - class AuthRequest { - - @Test - void shouldReturnErrorRedirectWithoutSourceParameter() throws URISyntaxException { - MockHttpResponse response = get("/v2/plugins/auth/login"); - assertError(response, ERROR_SOURCE_MISSING); - } - - @Test - void shouldReturnErrorRedirectWithoutPluginAuthUrlParameter() throws URISyntaxException { - scmConfiguration.setPluginAuthUrl(""); - - MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins"); - assertError(response, ERROR_AUTHENTICATION_DISABLED); - } - - @Test - void shouldReturnErrorRedirectIfAlreadyAuthenticated() throws URISyntaxException { - when(authenticator.isAuthenticated()).thenReturn(true); - - MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins"); - assertError(response, ERROR_ALREADY_AUTHENTICATED); - } - - @Test - @SubjectAware("trillian") - void shouldIgnorePreviousAuthenticationOnReconnection() throws URISyntaxException, IOException { - lenient().when(authenticator.isAuthenticated()).thenReturn(true); - when(challengeGenerator.create()).thenReturn("abcd"); - when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("def"); - - scmConfiguration.setPluginAuthUrl("https://plug.ins"); - - MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins&reconnect=true"); - assertRedirect(response, "https://plug.ins?instance=%2Fv2%2Fplugins%2Fauth%2Fcallback?params%3Ddef"); - } - - @Test - @SubjectAware("trillian") - void shouldReturnRedirectToPluginAuthUrl() throws URISyntaxException, IOException { - when(challengeGenerator.create()).thenReturn("abcd"); - when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("def"); - - scmConfiguration.setPluginAuthUrl("https://plug.ins"); - - MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins"); - assertRedirect(response, "https://plug.ins?instance=%2Fv2%2Fplugins%2Fauth%2Fcallback?params%3Ddef"); - } - - @Test - @SubjectAware("trillian") - void shouldExcludeCallbackFromXsrf() throws URISyntaxException, IOException { - when(challengeGenerator.create()).thenReturn("1234"); - when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("def"); - scmConfiguration.setPluginAuthUrl("https://plug.ins"); - - get("/v2/plugins/auth/login?source=/admin/plugins"); - - verify(excludes).add("/v2/plugins/auth/callback"); - } - - @Test - @SubjectAware("trillian") - void shouldSendAuthParameters() throws URISyntaxException, IOException { - when(challengeGenerator.create()).thenReturn("abc123def"); - when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("xyz"); - - get("/v2/plugins/auth/login?source=/admin/plugins"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(AuthParameter.class); - verify(parameterSerializer).serialize(captor.capture()); - - AuthParameter parameter = captor.getValue(); - assertThat(parameter.getChallenge()).isEqualTo("abc123def"); - assertThat(parameter.getSource()).isEqualTo("/admin/plugins"); - assertThat(parameter.getPrincipal()).isEqualTo("trillian"); - } - - } - - @Nested - @SubjectAware("marvin") - class AbortAuthentication { - - @BeforeEach - void setUp() throws IOException { - lenient().when(challengeGenerator.create()).thenReturn("xyz"); - lenient().when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("secureParams"); - } - - @Test - void shouldReturnErrorRedirectWithoutParams() throws URISyntaxException { - MockHttpResponse response = get("/v2/plugins/auth/callback"); - assertError(response, ERROR_PARAMS_MISSING); - } - - - @Test - void shouldReturnErrorRedirectWithoutChallenge() throws URISyntaxException, IOException { - mockParams("marvin", null, "/"); - MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams"); - assertError(response, ERROR_CHALLENGE_MISSING); - } - - @Test - void shouldReturnErrorRedirectWithChallengeMismatch() throws URISyntaxException, IOException { - mockParams("marvin", "abc", "/repos"); - get("/v2/plugins/auth/login?source=/repos"); - MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams"); - assertError(response, ERROR_CHALLENGE_DOES_NOT_MATCH); - } - - @Test - void shouldRedirectToRoot() throws URISyntaxException, IOException { - mockParams("marvin", "xyz", null); - get("/v2/plugins/auth/login?source=/repos"); - MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams"); - assertRedirect(response, "/"); - } - - @Test - void shouldRedirectToSource() throws URISyntaxException, IOException { - mockParams("marvin", "xyz", "/repos"); - get("/v2/plugins/auth/login?source=/repos"); - MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams"); - assertRedirect(response, "/repos"); - } - - @Test - void shouldRemoveCallbackFromXsrf() throws URISyntaxException, IOException { - mockParams("marvin", "xyz", "/repos"); - get("/v2/plugins/auth/login?source=/repos"); - get("/v2/plugins/auth/callback?params=secureParams"); - verify(excludes).remove("/v2/plugins/auth/callback"); - } - - } - - @Nested - @SubjectAware("slarti") - class AuthenticationCallback { - - @BeforeEach - void setUp() throws IOException { - lenient().when(challengeGenerator.create()).thenReturn("abc"); - lenient().when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("secureParams"); - } - - @Test - void shouldReturnErrorRedirectWithoutParameters() throws URISyntaxException { - MockHttpResponse response = post("/v2/plugins/auth/callback", "trillian", "rf"); - assertError(response, ERROR_PARAMS_MISSING); - } - - @Test - void shouldReturnErrorRedirectWithoutChallengeParameter() throws URISyntaxException, IOException { - mockParams("slarti", null, "/"); - MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf"); - assertError(response, ERROR_CHALLENGE_MISSING); - } - - @Test - void shouldReturnErrorRedirectWithChallengeMismatch() throws URISyntaxException, IOException { - mockParams("slarti", "xyz", "/"); - get("/v2/plugins/auth/login?source=/repos"); - MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "trillian", "rf"); - assertError(response, ERROR_CHALLENGE_DOES_NOT_MATCH); - } - - @Test - void shouldReturnErrorRedirectFromFailedAuthentication() throws URISyntaxException, IOException { - mockParams("slarti", "abc", "/"); - FetchAccessTokenFailedException exception = new FetchAccessTokenFailedException("failed ..."); - doThrow(exception).when(authenticator).authenticate("slarti", "rf"); - get("/v2/plugins/auth/login?source=/repos"); - MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf"); - assertError(response, exception.getCode()); - } - - @Test - void shouldAuthenticate() throws URISyntaxException, IOException { - mockParams("slarti", "abc", "/"); - get("/v2/plugins/auth/login?source=/repos"); - post("/v2/plugins/auth/callback?params=secureParams", "slarti", "refresh_token"); - verify(authenticator).authenticate("slarti", "refresh_token"); - } - - @Test - void shouldRedirectToSource() throws URISyntaxException, IOException { - mockParams("slarti", "abc", "/users"); - get("/v2/plugins/auth/login?source=/users"); - MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rrrrf"); - assertRedirect(response, "/users"); - } - - @Test - void shouldRemoveCallbackFromXsrf() throws URISyntaxException, IOException { - mockParams("slarti", "abc", "/users"); - get("/v2/plugins/auth/login?source=/repos"); - post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf"); - verify(excludes).remove("/v2/plugins/auth/callback"); - } - - } - - private void mockParams(String principal, String challenge, String source) throws IOException { - AuthParameter params = new AuthParameter(principal, challenge, source); - when(parameterSerializer.deserialize("secureParams", AuthParameter.class)).thenReturn(params); - } - - @CanIgnoreReturnValue - private MockHttpResponse post(String uri, String subject, String refreshToken) throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post(uri); - request.addFormHeader("subject", subject); - request.addFormHeader("refresh_token", refreshToken); - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - return response; - } - - @CanIgnoreReturnValue - private MockHttpResponse get(String uri) throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get(uri); - return request(request); - } - - private final ObjectMapper mapper = new ObjectMapper(); - - private JsonNode getJson(String uri) throws URISyntaxException, IOException { - MockHttpResponse response = get(uri); - return mapper.readTree(response.getContentAsString()); - } - - private MockHttpResponse request(MockHttpRequest request) { - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - return response; - } - - private void assertError(MockHttpResponse response, String code) { - assertRedirect(response, "/error/" + code); - } - - private void assertRedirect(MockHttpResponse response, String location) { - assertRedirect(response, (locationHeader) -> assertThat(locationHeader).isEqualTo(location)); - } - - private void assertRedirect(MockHttpResponse response, Consumer location) { - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SEE_OTHER); - location.accept(response.getOutputHeaders().getFirst("Location").toString()); - } - - @Value - private static class SimpleAuthenticationInfo implements AuthenticationInfo { - String principal; - String pluginCenterSubject; - Instant date; - boolean failed; - } - - private static final ScmPathInfo rootPathInfo = new ScmPathInfo() { - @Override - public URI getApiRestUri() { - return URI.create("/api"); - } - - @Override - public URI getRootUri() { - return URI.create("/"); - } - }; -} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java index 4f4661d7e8..038f14e314 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java @@ -36,7 +36,6 @@ import java.net.URI; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; -import static sonia.scm.plugin.PluginInformation.PluginType.*; import static sonia.scm.plugin.PluginTestHelper.createAvailable; import static sonia.scm.plugin.PluginTestHelper.createInstalled; @@ -78,14 +77,9 @@ class PluginDtoMapperTest { assertThat(dto.getAuthor()).isEqualTo("Sebastian Sdorra"); assertThat(dto.getCategory()).isEqualTo("Authentication"); assertThat(dto.getAvatarUrl()).isEqualTo("https://avatar.scm-manager.org/plugins/cas.png"); - assertThat(dto.getType()).isEqualTo(SCM); } private PluginInformation createPluginInformation() { - return createPluginInformation(SCM); - } - - private PluginInformation createPluginInformation(PluginInformation.PluginType type) { PluginInformation information = new PluginInformation(); information.setName("scm-cas-plugin"); information.setVersion("1.0.0"); @@ -93,7 +87,6 @@ class PluginDtoMapperTest { information.setAuthor("Sebastian Sdorra"); information.setCategory("Authentication"); information.setAvatarUrl("https://avatar.scm-manager.org/plugins/cas.png"); - information.setType(type); return information; } @@ -134,18 +127,6 @@ class PluginDtoMapperTest { .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install"); } - @Test - void shouldAppendCloudoguInstallLink() { - when(subject.isPermitted("plugin:write")).thenReturn(true); - AvailablePlugin plugin = createAvailable(createPluginInformation(CLOUDOGU)); - - PluginDto dto = mapper.mapAvailable(plugin); - - assertThat(dto.getType()).isEqualTo(CLOUDOGU); - assertThat(dto.getLinks().getLinkBy("cloudoguInstall").get().getHref()) - .isEqualTo("mycloudogu.com/install/my_plugin"); - } - @Test void shouldAppendInstallWithRestartLink() { when(restarter.isSupported()).thenReturn(true); @@ -157,16 +138,6 @@ class PluginDtoMapperTest { .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install?restart=true"); } - @Test - void shouldNotAppendInstallLinkWithEmptyDownloadUrl() { - when(subject.isPermitted("plugin:write")).thenReturn(true); - AvailablePlugin plugin = createAvailable(createPluginInformation(), ""); - - PluginDto dto = mapper.mapAvailable(plugin); - assertThat(dto.getLinks().hasLink("install")).isFalse(); - assertThat(dto.getLinks().hasLink("installWithRestart")).isFalse(); - } - @Test void shouldReturnMiscellaneousIfCategoryIsNull() { PluginInformation information = createPluginInformation(); 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 51525be0f2..fdccd21f67 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 @@ -89,7 +89,6 @@ class ScmConfigurationToConfigDtoMapperTest { assertThat(dto.getProxyExcludes()).contains(expectedExcludes); assertThat(dto.isSkipFailedAuthenticators()).isTrue(); assertThat(dto.getPluginUrl()).isEqualTo("https://plug.ins"); - assertThat(dto.getPluginAuthUrl()).isEqualTo("https://plug.ins/oidc"); assertThat(dto.getLoginAttemptLimitTimeout()).isEqualTo(2); assertThat(dto.isEnabledXsrfProtection()).isTrue(); assertThat(dto.getNamespaceStrategy()).isEqualTo("username"); @@ -155,7 +154,6 @@ class ScmConfigurationToConfigDtoMapperTest { config.setProxyExcludes(Sets.newSet(expectedExcludes)); config.setSkipFailedAuthenticators(true); config.setPluginUrl("https://plug.ins"); - config.setPluginAuthUrl("https://plug.ins/oidc"); config.setLoginAttemptLimitTimeout(2); config.setEnabledXsrfProtection(true); config.setNamespaceStrategy("username"); diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterAuthenticatorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterAuthenticatorTest.java deleted file mode 100644 index b20e10e6ee..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterAuthenticatorTest.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.plugin; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.apache.shiro.authz.AuthorizationException; -import org.github.sdorra.jse.ShiroExtension; -import org.github.sdorra.jse.SubjectAware; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.event.ScmEventBus; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.net.ahc.AdvancedHttpRequestWithBody; -import sonia.scm.net.ahc.AdvancedHttpResponse; -import sonia.scm.plugin.PluginCenterAuthenticator.RefreshResponse; -import sonia.scm.store.InMemoryConfigurationStoreFactory; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; -import static sonia.scm.plugin.PluginCenterAuthenticator.*; -import static sonia.scm.plugin.PluginCenterAuthenticator.RefreshRequest; - -@ExtendWith({MockitoExtension.class, ShiroExtension.class}) -class PluginCenterAuthenticatorTest { - - private PluginCenterAuthenticator authenticator; - - @Mock - private AdvancedHttpClient advancedHttpClient; - - @Mock(answer = Answers.RETURNS_SELF) - private AdvancedHttpRequestWithBody request; - - private ScmConfiguration scmConfiguration; - - @Mock - private ScmEventBus eventBus; - - private final InMemoryConfigurationStoreFactory factory = InMemoryConfigurationStoreFactory.create(); - - @BeforeEach - void setUpObjectUnderTest() { - scmConfiguration = new ScmConfiguration(); - authenticator = new PluginCenterAuthenticator(factory, scmConfiguration, advancedHttpClient, eventBus); - } - - @Test - @SubjectAware("marvin") - void shouldFailAuthenticationWithoutPermissions() { - assertThrows(AuthorizationException.class, () -> authenticator.authenticate("marvin@hitchhiker.com", "refresh-token")); - } - - @Test - @SubjectAware(value = "marvin", permissions = "plugin:read") - void shouldFailAuthenticationWithReadPermissions() { - assertThrows(AuthorizationException.class, () -> authenticator.authenticate("marvin@hitchhiker.com", "refresh-token")); - } - - @Test - @SubjectAware("marvin") - void shouldFailToFetchAccessTokenWithoutPermission() { - assertThrows(AuthorizationException.class, () -> authenticator.fetchAccessToken()); - } - - @Test - @SubjectAware("marvin") - void shouldFailGetAuthenticationInfoWithoutPermission() { - assertThrows(AuthorizationException.class, () -> authenticator.getAuthenticationInfo()); - } - - @Test - @SubjectAware("marvin") - void shouldFailLogoutWithoutPermission() { - assertThrows(AuthorizationException.class, () -> authenticator.logout()); - } - - @Nested - @SubjectAware(value = "trillian", permissions = {"plugin:read", "plugin:write"}) - class WithPermissions { - - @Test - void shouldReturnFalseWithoutRefreshToken() { - assertThat(authenticator.isAuthenticated()).isFalse(); - } - - @Test - void shouldFailWithoutRefreshToken() { - assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", null)); - } - - @Test - void shouldFailWithEmptyRefreshToken() { - assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "")); - } - - @Test - void shouldFailWithoutSubject() { - assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate(null, "rf")); - } - - @Test - void shouldFailWithEmptySubject() { - assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("", "rf")); - } - - @Test - void shouldFailWithoutPluginAuthUrl() { - scmConfiguration.setPluginAuthUrl(null); - assertThrows(IllegalStateException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token")); - } - - @Test - void shouldAuthenticate() throws IOException { - mockSuccessfulAuth("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh"); - - authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token"); - assertThat(authenticator.isAuthenticated()).isTrue(); - } - - @Test - void shouldFireLoginEvent() throws IOException { - mockSuccessfulAuth("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh"); - - authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(PluginCenterLoginEvent.class); - verify(eventBus).post(captor.capture()); - - AuthenticationInfo info = captor.getValue().getAuthenticationInfo(); - assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com"); - } - - @Test - void shouldFailFetchWithoutPriorAuthentication() { - assertThrows(IllegalStateException.class, () -> authenticator.fetchAccessToken()); - } - - @Test - void shouldUseUrlFromScmConfiguration() throws IOException { - preAuth("cool-refresh-token"); - scmConfiguration.setPluginAuthUrl("https://pca.org/oidc/"); - mockSuccessfulAuth("https://pca.org/oidc/refresh", "access", "refresh"); - - Optional accessToken = authenticator.fetchAccessToken(); - assertThat(accessToken).contains("access"); - } - - @Test - void shouldFetchAccessToken() throws IOException { - preAuth("cool-refresh-token"); - mockSuccessfulAuth("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh"); - - Optional accessToken = authenticator.fetchAccessToken(); - assertThat(accessToken).contains("access"); - } - - @Test - void shouldReturnEmptyAccessTokenOnFailedRequest() throws IOException { - preAuth("cool-refresh-token"); - - AdvancedHttpResponse response = mockAuthResponse("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh"); - when(response.isSuccessful()).thenReturn(false); - - Optional accessToken = authenticator.fetchAccessToken(); - assertThat(accessToken).isEmpty(); - } - - @Test - void shouldReturnEmptyAccessTokenOnException() throws IOException { - preAuth("cool-refresh-token"); - - when(advancedHttpClient.post("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh")) - .thenReturn(request); - when(request.request()).thenThrow(new IOException("failed")); - - Optional accessToken = authenticator.fetchAccessToken(); - assertThat(accessToken).isEmpty(); - } - - @Test - void shouldMarkAuthenticationAsFailed() throws IOException { - preAuth("cool-refresh-token"); - - AdvancedHttpResponse response = mockAuthResponse("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh"); - when(response.isSuccessful()).thenReturn(false); - - authenticator.fetchAccessToken(); - assertThat(authenticator.getAuthenticationInfo()).hasValueSatisfying( - auth -> assertThat(auth.isFailed()).isTrue() - ); - } - - @Test - void shouldUnmarkAfterSuccessfulAuthentication() throws IOException { - preAuth("cool-refresh-token", true); - mockSuccessfulAuth("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh"); - - authenticator.fetchAccessToken(); - - assertThat(authenticator.getAuthenticationInfo()).hasValueSatisfying( - auth -> assertThat(auth.isFailed()).isFalse() - ); - } - - @Test - void shouldFireAuthenticationFailedEvent() throws IOException { - preAuth("cool-refresh-token"); - - AdvancedHttpResponse response = mockAuthResponse("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh"); - when(response.isSuccessful()).thenReturn(false); - - authenticator.fetchAccessToken(); - - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(PluginCenterAuthenticationFailedEvent.class); - verify(eventBus).post(eventCaptor.capture()); - PluginCenterAuthenticationFailedEvent event = eventCaptor.getValue(); - - assertThat(event.getAuthenticationInfo().isFailed()).isTrue(); - } - - @Test - void shouldStoreRefreshTokenAfterFetch() throws IOException { - preAuth("refreshOne"); - mockSuccessfulAuth("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "accessTwo", "refreshTwo"); - - authenticator.fetchAccessToken(); - authenticator.fetchAccessToken(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(RefreshRequest.class); - verify(request, times(2)).jsonContent(captor.capture()); - - List refreshTokens = captor.getAllValues() - .stream() - .map(RefreshRequest::getRefreshToken) - .collect(Collectors.toList()); - - assertThat(refreshTokens).containsExactlyInAnyOrder("refreshOne", "refreshTwo"); - } - - @Test - void shouldReturnEmptyWithoutPriorAuthentication() { - assertThat(authenticator.getAuthenticationInfo()).isEmpty(); - } - - @Test - void shouldReturnAuthenticationInfo() { - preAuth("refresh_token"); - assertThat(authenticator.getAuthenticationInfo()).hasValueSatisfying(info -> { - assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com"); - assertThat(info.getPrincipal()).isEqualTo("trillian"); - assertThat(info.getDate()).isNotNull(); - }); - } - - @Test - void shouldLogout() { - preAuth("refresh_token"); - - authenticator.logout(); - - assertThat(authenticator.isAuthenticated()).isFalse(); - assertThat(authenticator.getAuthenticationInfo()).isEmpty(); - } - - @Test - void shouldFireLogoutEventAfterLogout() { - preAuth("refresh_token"); - - authenticator.logout(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(PluginCenterLogoutEvent.class); - verify(eventBus).post(captor.capture()); - - AuthenticationInfo info = captor.getValue().getPriorAuthenticationInfo(); - assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com"); - } - - private void preAuth(String refreshToken) { - preAuth(refreshToken, false); - } - - @SuppressWarnings("unchecked") - private void preAuth(String refreshToken, boolean failed) { - Authentication authentication = new Authentication(); - authentication.setPluginCenterSubject("tricia.mcmillan@hitchhiker.com"); - authentication.setPrincipal("trillian"); - authentication.setRefreshToken(refreshToken); - authentication.setDate(Instant.now()); - authentication.setFailed(failed); - factory.get(STORE_NAME, null).set(authentication); - } - - @CanIgnoreReturnValue - private void mockSuccessfulAuth(String url, String accessToken, String refreshToken) throws IOException { - AdvancedHttpResponse response = mockAuthResponse(url); - - RefreshResponse refreshResponse = new RefreshResponse(); - refreshResponse.setAccessToken(accessToken); - refreshResponse.setRefreshToken(refreshToken); - when(response.contentFromJson(RefreshResponse.class)).thenReturn(refreshResponse); - - when(response.isSuccessful()).thenReturn(true); - } - - private AdvancedHttpResponse mockAuthResponse(String url) throws IOException { - when(advancedHttpClient.post(url)).thenReturn(request); - - AdvancedHttpResponse response = mock(AdvancedHttpResponse.class); - when(request.request()).thenReturn(response); - - return response; - } - - } - -} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java index 21a9e35ea7..98d83681c5 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java @@ -56,7 +56,6 @@ class PluginCenterDtoMapperTest { "trillian", "http://avatar.url", "555000444", - PluginInformation.PluginType.SCM, new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), ImmutableSet.of(), @@ -111,7 +110,6 @@ class PluginCenterDtoMapperTest { "trillian", "https://avatar.url", "12345678aa", - PluginInformation.PluginType.SCM, new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), ImmutableSet.of(), @@ -127,7 +125,6 @@ class PluginCenterDtoMapperTest { "dent", "http://avatar.url", "555000444", - PluginInformation.PluginType.CLOUDOGU, new Condition(Collections.singletonList("linux"), "amd64","2.0.0"), ImmutableSet.of("scm-review-plugin"), ImmutableSet.of(), @@ -146,8 +143,6 @@ class PluginCenterDtoMapperTest { assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion()); assertThat(pluginInformation2.getAuthor()).isEqualTo(plugin2.getAuthor()); assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion()); - assertThat(pluginInformation1.getType()).isEqualTo(PluginInformation.PluginType.SCM); - assertThat(pluginInformation2.getType()).isEqualTo(PluginInformation.PluginType.CLOUDOGU); assertThat(resultSet.size()).isEqualTo(2); } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java index c8b96b9601..b628226a12 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterLoaderTest.java @@ -29,7 +29,6 @@ import sonia.scm.net.ahc.AdvancedHttpResponse; import java.io.IOException; import java.util.Collections; -import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -50,9 +49,6 @@ class PluginCenterLoaderTest { @Mock private ScmEventBus eventBus; - @Mock - private PluginCenterAuthenticator authenticator; - @InjectMocks private PluginCenterLoader loader; @@ -78,8 +74,7 @@ class PluginCenterLoaderTest { when(client.get(PLUGIN_URL)).thenReturn(request); AdvancedHttpResponse response = mock(AdvancedHttpResponse.class); when(request.request()).thenReturn(response); - return response; - } + return response; } @Test void shouldReturnEmptySetIfPluginCenterIsDeactivated() { @@ -109,25 +104,4 @@ class PluginCenterLoaderTest { verify(eventBus).post(any(PluginCenterErrorEvent.class)); } - - @Test - void shouldAppendAccessToken() throws IOException { - when(authenticator.isAuthenticated()).thenReturn(true); - when(authenticator.fetchAccessToken()).thenReturn(Optional.of("mega-cool-at")); - - mockResponse(); - loader.load(PLUGIN_URL); - - verify(request).bearerAuth("mega-cool-at"); - } - - private Set mockResponse() throws IOException { - PluginCenterDto dto = new PluginCenterDto(); - Set plugins = Collections.emptySet(); - Set pluginSets = Collections.emptySet(); - when(request().contentFromJson(PluginCenterDto.class)).thenReturn(dto); - when(mapper.map(dto)).thenReturn(new PluginCenterResult(plugins, pluginSets)); - return plugins; - } - } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterTest.java index 1fd13397db..44b8e0caf1 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterTest.java @@ -91,22 +91,6 @@ class PluginCenterTest { assertThat(pluginCenter.getAvailablePluginSets()).isSameAs(pluginSets); } - @Test - @SuppressWarnings("unchecked") - void shouldClearCacheOnPluginCenterLogin() { - Set plugins = new HashSet<>(); - Set pluginSets = new HashSet<>(); - - PluginCenterResult first = new PluginCenterResult(plugins, pluginSets); - when(loader.load(anyString())).thenReturn(first, new PluginCenterResult()); - - assertThat(pluginCenter.getAvailablePlugins()).isSameAs(plugins); - assertThat(pluginCenter.getAvailablePluginSets()).isSameAs(pluginSets); - pluginCenter.handle(new PluginCenterLoginEvent(null)); - assertThat(pluginCenter.getAvailablePlugins()).isNotSameAs(plugins); - assertThat(pluginCenter.getAvailablePluginSets()).isNotSameAs(pluginSets); - } - @Test @SuppressWarnings("unchecked") void shouldClearCacheOnConfigChange() { diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java index b6bec37512..2ab618b90d 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java @@ -37,7 +37,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Collections; import java.util.List; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,7 +45,6 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -61,9 +59,6 @@ class PluginInstallerTest { @Mock private SmpDescriptorExtractor extractor; - @Mock - private PluginCenterAuthenticator authenticator; - @InjectMocks private PluginInstaller installer; @@ -204,17 +199,6 @@ class PluginInstallerTest { assertThat(exception.getDownloaded().getVersion()).isEqualTo("1.1.0"); } - @Test - void shouldAppendBearerAuth() throws IOException { - when(authenticator.isAuthenticated()).thenReturn(true); - when(authenticator.fetchAccessToken()).thenReturn(Optional.of("atat")); - mockContent("42"); - - installer.install(PluginInstallationContext.empty(), createGitPlugin()); - - verify(request).bearerAuth("atat"); - } - private AvailablePlugin createPlugin(String name, String url, String checksum) { PluginInformation information = new PluginInformation(); information.setName(name); diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTestHelper.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTestHelper.java index c4ea2ffb6b..a7a4b8f466 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTestHelper.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTestHelper.java @@ -18,8 +18,6 @@ package sonia.scm.plugin; import org.mockito.Answers; -import java.util.Optional; - import static org.mockito.Mockito.*; public class PluginTestHelper { @@ -52,14 +50,8 @@ public class PluginTestHelper { } public static AvailablePlugin createAvailable(PluginInformation information) { - return createAvailable(information, "https://scm-manager.org/download"); - } - - public static AvailablePlugin createAvailable(PluginInformation information, String url) { AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class); lenient().when(descriptor.getInformation()).thenReturn(information); - lenient().when(descriptor.getInstallLink()).thenReturn(Optional.of("mycloudogu.com/install/my_plugin")); - lenient().when(descriptor.getUrl()).thenReturn(url); return new AvailablePlugin(descriptor); } diff --git a/scm-webapp/src/test/java/sonia/scm/security/SecureParameterSerializerTest.java b/scm-webapp/src/test/java/sonia/scm/security/SecureParameterSerializerTest.java deleted file mode 100644 index f421e38ddd..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/security/SecureParameterSerializerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -class SecureParameterSerializerTest { - - private final SecureParameterSerializer serializer = new SecureParameterSerializer(new ObjectMapper()); - - @Test - void shouldSerializeAndDeserialize() throws IOException { - TestObject object = new TestObject("1", 2); - String serialized = serializer.serialize(object); - - assertThat(serialized).isNotEmpty(); - - object = serializer.deserialize(serialized, TestObject.class); - assertThat(object).isNotNull(); - assertThat(object.getOne()).isEqualTo("1"); - assertThat(object.getTwo()).isEqualTo(2); - } - - @Data - @NoArgsConstructor - @AllArgsConstructor - public static class TestObject { - - private String one; - private int two; - - } - - -} diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java index faf1b088cd..eeaa0cc13f 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/XsrfAccessTokenValidatorTest.java @@ -16,7 +16,6 @@ package sonia.scm.security; - import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -46,8 +45,6 @@ class XsrfAccessTokenValidatorTest { @Mock private AccessToken accessToken; - private final XsrfExcludes excludes = new XsrfExcludes(); - private XsrfAccessTokenValidator validator; /** @@ -55,7 +52,7 @@ class XsrfAccessTokenValidatorTest { */ @BeforeEach void prepareObjectUnderTest() { - validator = new XsrfAccessTokenValidator(() -> request, excludes); + validator = new XsrfAccessTokenValidator(() -> request); } @Nested @@ -115,20 +112,6 @@ class XsrfAccessTokenValidatorTest { // execute and assert assertThat(validator.validate(accessToken)).isTrue(); } - - @Test - void shouldNotValidateExcludedRequest() { - excludes.add("/excluded"); - - // prepare - when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc")); - when(request.getRequestURI()).thenReturn("/excluded"); - - // execute and assert - assertThat(validator.validate(accessToken)).isTrue(); - } - - } @ParameterizedTest diff --git a/scm-webapp/src/test/java/sonia/scm/update/plugin/PluginCenterAuthentiationUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/plugin/PluginCenterAuthentiationUpdateStepTest.java deleted file mode 100644 index 8215851eff..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/update/plugin/PluginCenterAuthentiationUpdateStepTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2020 - present Cloudogu GmbH - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -package sonia.scm.update.plugin; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import sonia.scm.plugin.PluginCenterAuthenticator; -import sonia.scm.store.ConfigurationStore; -import sonia.scm.store.ConfigurationStoreFactory; - -import java.time.Instant; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PluginCenterAuthenticationUpdateStepTest { - - private PluginCenterAuthenticationUpdateStep updateStep; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private ConfigurationStoreFactory configurationStoreFactory; - @Mock - private ConfigurationStore configurationStore; - - @BeforeEach - void initUpdateStep() { - when(configurationStoreFactory.withType(PluginCenterAuthenticator.Authentication.class).withName("plugin-center-auth").build()) - .thenReturn(configurationStore); - updateStep = new PluginCenterAuthenticationUpdateStep(configurationStoreFactory); - } - - @Test - void shouldNotUpdateIfConfigFileNotAvailable() throws Exception { - when(configurationStore.getOptional()).thenReturn(Optional.empty()); - - updateStep.doUpdate(); - - verify(configurationStore, never()).set(any()); - } - - @Test - void shouldUpdateIfRefreshTokenNotEncrypted() throws Exception { - when(configurationStore.getOptional()) - .thenReturn(Optional.of(new PluginCenterAuthenticator.Authentication("trillian", "trillian", "some_not_encrypted_token", Instant.now(), false))); - - updateStep.doUpdate(); - - verify(configurationStore).set(argThat(config -> { - assertThat(config.getRefreshToken()).startsWith("{enc}"); - return true; - })); - } - - @Test - void shouldNotUpdateIfRefreshTokenIsAlreadyEncrypted() throws Exception { - when(configurationStore.getOptional()) - .thenReturn(Optional.of(new PluginCenterAuthenticator.Authentication("trillian", "trillian", "{enc}my_encrypted_token", Instant.now(), false))); - - updateStep.doUpdate(); - - verify(configurationStore, never()).set(any()); - } -}