Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-43205][JENKINS-69006] Allow admins to configure a global default URL provider and always make external links go through /display/redirect #202

Merged
merged 7 commits into from
Aug 17, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* The MIT License
*
* Copyright 2023 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.displayurlapi;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.Util;
import hudson.util.ListBoxModel;
import java.util.Objects;
import jenkins.model.GlobalConfiguration;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty;
import org.kohsuke.stapler.DataBoundSetter;

@Extension
@Symbol("defaultDisplayUrlProvider")
public class DefaultDisplayURLProviderGlobalConfiguration extends GlobalConfiguration {

public DefaultDisplayURLProviderGlobalConfiguration() {
load();
}

private @CheckForNull String providerId;

public @CheckForNull String getProviderId() {
return providerId;
}

@DataBoundSetter
public void setProviderId(@CheckForNull String providerId) {
providerId = Util.fixEmptyAndTrim(providerId);
if (PreferredProviderUserProperty.ProviderOption.DEFAULT_OPTION.getId().equals(providerId)) {
providerId = null;
}
this.providerId = providerId;
save();
}

public @CheckForNull DisplayURLProvider getConfiguredProvider() {
if (providerId == null) {
return null;
}
return DisplayURLProvider.all().stream()
.filter(provider -> provider.getClass().getName().equals(providerId))
.findFirst()
.orElse(null);
}

public static DefaultDisplayURLProviderGlobalConfiguration get() {
return ExtensionList.lookupSingleton(DefaultDisplayURLProviderGlobalConfiguration.class);
}

public ListBoxModel doFillProviderIdItems() {
ListBoxModel items = new ListBoxModel();
for (PreferredProviderUserProperty.ProviderOption providerOption : PreferredProviderUserProperty.getAll()) {
ListBoxModel.Option option = new ListBoxModel.Option(
providerOption.getName(),
providerOption.getId(),
Objects.equals(providerOption.getId(), providerId)
);
items.add(option);
}
return items;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@
* Generates URLs for well known UI locations for use in notifications (e.g. mailer, HipChat, Slack,
* IRC, etc) Extensible to allow plugins to override common URLs (e.g. Blue Ocean or another future
* secondary UI)
*
* <p>Implementations should generally extend {@link ClassicDisplayURLProvider} and delegate to it for unsupported
* builds instead of extending this class directly.
*/
/* TODO: This API is awkward. All providers must return a non-null value for all URLs, meaning that in practice they
all need to delegate to ClassicDisplayURLProvider. Ideally, the API would allow providers to return null to indicate
that they do not support a particular build/job, and we would loop over all providers in a user-defined order looking
for the first one that returns a non-null URL, which would allow providers which handle distinct job types to coexist.
*/
public abstract class DisplayURLProvider implements ExtensionPoint {

/**
* Returns the {@link DisplayURLProvider} to use for generating links to be given to users.
*
* @return DisplayURLProvider
* @see #getPreferredProvider
*/
public static DisplayURLProvider get() {
DisplayURLProvider preferredProvider = getPreferredProvider();
return preferredProvider != null ? preferredProvider : DisplayURLProviderImpl.INSTANCE;
return DisplayURLProviderImpl.INSTANCE;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As before #42. See discussion in JENKINS-69006.

}

/**
Expand All @@ -41,13 +49,12 @@ public static ExtensionList<DisplayURLProvider> all() {
return ExtensionList.lookup(DisplayURLProvider.class);
}

/**
* Returns the singleton instance of the {@link ClassicDisplayURLProvider} extension.
*/
public static DisplayURLProvider getDefault() {
DisplayURLProvider defaultProvider = getPreferredProvider();
if (defaultProvider == null) {
defaultProvider = ExtensionList.lookup(DisplayURLProvider.class)
return ExtensionList.lookup(DisplayURLProvider.class)
.get(ClassicDisplayURLProvider.class);
}
return defaultProvider;
}

/**
Expand Down Expand Up @@ -182,18 +189,39 @@ private static String findClass() {
return clazz;
}

/**
* Selects the provider that should be used to redirect users from the display URLs generated by
* the provider return by {@link #get} to an actual page.
* <p>Precedence is as follows, stopping at the first non-null value:
* <ol>
* <li>{@link PreferredProviderUserProperty}
* <li>{@link DefaultDisplayURLProviderGlobalConfiguration}
* <li>{@link #JENKINS_DISPLAYURL_PROVIDER_ENV}
* <li>{@link #JENKINS_DISPLAYURL_PROVIDER_PROP}
* <li>The provider extension with the highest ordinal value that is not an instance of {@link ClassicDisplayURLProvider}
* <li>{@link ClassicDisplayURLProvider}
* </ol>
* @see #get
*/
@Nullable
public static DisplayURLProvider getPreferredProvider() {
PreferredProviderUserProperty prefProperty = getUserPreferredProviderProperty();

if (prefProperty != null && prefProperty.getConfiguredProvider() != null) {
return prefProperty.getConfiguredProvider();
PreferredProviderUserProperty userProperty = getUserPreferredProviderProperty();
if (userProperty != null && userProperty.getConfiguredProvider() != null) {
return userProperty.getConfiguredProvider();
}
DisplayURLProvider globalGuiProvider = DefaultDisplayURLProviderGlobalConfiguration.get().getConfiguredProvider();
if (globalGuiProvider != null) {
return globalGuiProvider;
}
String clazz = findClass();
if (isNotEmpty(clazz)) {
return ExtensionList.lookup(DisplayURLProvider.class).getDynamic(clazz);
String globalProviderClass = findClass();
if (isNotEmpty(globalProviderClass)) {
return ExtensionList.lookup(DisplayURLProvider.class).getDynamic(globalProviderClass);
}
return null;
ExtensionList<DisplayURLProvider> all = DisplayURLProvider.all();
DisplayURLProvider displayURLProvider = all.stream()
.filter(p -> !(p instanceof ClassicDisplayURLProvider))
.findFirst().orElse(DisplayURLProvider.getDefault());
return displayURLProvider;
}

@Nullable
Expand All @@ -202,6 +230,6 @@ public static PreferredProviderUserProperty getUserPreferredProviderProperty() {
return (user == null) ? null : user.getProperty(PreferredProviderUserProperty.class);
}

private static final String JENKINS_DISPLAYURL_PROVIDER_ENV = "JENKINS_DISPLAYURL_PROVIDER";
private static final String JENKINS_DISPLAYURL_PROVIDER_PROP = "jenkins.displayurl.provider";
static final String JENKINS_DISPLAYURL_PROVIDER_ENV = "JENKINS_DISPLAYURL_PROVIDER";
static final String JENKINS_DISPLAYURL_PROVIDER_PROP = "jenkins.displayurl.provider";
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package org.jenkinsci.plugins.displayurlapi.actions;

import com.google.common.annotations.VisibleForTesting;
import hudson.ExtensionList;
import hudson.model.Action;
import java.util.Objects;
import java.util.function.Predicate;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.displayurlapi.ClassicDisplayURLProvider;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

Expand Down Expand Up @@ -61,30 +57,7 @@ DisplayURLProvider lookupProvider(StaplerRequest req) {
}

DisplayURLProvider lookupProvider() {
dwnusbaum marked this conversation as resolved.
Show resolved Hide resolved
PreferredProviderUserProperty prefProperty = getUserPreferredProviderProperty();

if (prefProperty != null && prefProperty.getConfiguredProvider() != null) {
return prefProperty.getConfiguredProvider();
}
DisplayURLProvider displayURLProvider = DisplayURLProvider.getPreferredProvider();
if (displayURLProvider == null) {
ExtensionList<DisplayURLProvider> all = DisplayURLProvider.all();
displayURLProvider = all.stream()
.filter(
((Predicate<DisplayURLProvider>) ClassicDisplayURLProvider.class::isInstance)
.negate())
.findFirst().orElse(DisplayURLProvider.getDefault());
}
return displayURLProvider;
}

/**
* @deprecated use {@link DisplayURLProvider#getUserPreferredProviderProperty()}
*/
@VisibleForTesting
@Deprecated
protected PreferredProviderUserProperty getUserPreferredProviderProperty() {
return DisplayURLProvider.getUserPreferredProviderProperty();
return DisplayURLProvider.getPreferredProvider();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public DisplayURLProvider getConfiguredProvider() {
.orElse(null);
}

public List<ProviderOption> getAll() {
public static List<ProviderOption> getAll() {
List<ProviderOption> options = DisplayURLProvider.all().stream()
.map(input -> new ProviderOption(input.getClass().getName(), input.getDisplayName()))
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public UserProperty newInstance(User user) {
public ListBoxModel doFillProviderIdItems() {
ListBoxModel items = new ListBoxModel();
PreferredProviderUserProperty property = PreferredProviderUserProperty.forCurrentUser();
for (ProviderOption providerOption : property.getAll()) {
for (ProviderOption providerOption : PreferredProviderUserProperty.getAll()) {
ListBoxModel.Option option = new ListBoxModel.Option(
providerOption.getName(),
providerOption.getId(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="${%Default notification URL}">
<f:entry field="providerId">
<f:select/>
</f:entry>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Select the default notification URL when clicking links to Jenkins from notifications (e.g. Email, Slack, or GitHub).
Individual users can override this value as desired in their preferences.
</div>
Loading