Merge branch 'master' into suppressStack

This commit is contained in:
Daniel Beck authored and GitHub committed 2020-02-22 23:14:25 +01:00
commit 7cc0628d4c
188 files changed
+5360 -598

No files matched your search

@@ -431,6 +431,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
return null;
}
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Administrator action installing a plugin, which could do far worse.")
private static File resolve(File base, String relative) {
File rel = new File(relative);
if(rel.isAbsolute())
+1 -1
View File
@@ -2971,7 +2971,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
private static void checkPermissionForValidate() {
AccessControlled subject = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class);
if (subject == null)
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
Jenkins.get().checkPermission(Jenkins.MANAGE);
else
subject.checkPermission(Item.CONFIGURE);
}
+98 -17
View File
@@ -27,6 +27,7 @@ package hudson;
import hudson.model.Slave;
import hudson.security.*;
import java.util.function.Predicate;
import jenkins.telemetry.impl.AutoRefresh;
import jenkins.util.SystemProperties;
import hudson.cli.CLICommand;
@@ -142,6 +143,7 @@ import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
import org.acegisecurity.AccessDeniedException;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
@@ -157,8 +159,6 @@ import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgument;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import hudson.model.PasswordParameterDefinition;
import hudson.util.RunList;
@@ -186,6 +186,9 @@ public class Functions {
private static final AtomicLong iota = new AtomicLong();
private static Logger LOGGER = Logger.getLogger(Functions.class.getName());
@Restricted(NoExternalUse.class)
public static /* non-final */ boolean UI_REFRESH = SystemProperties.getBoolean("jenkins.ui.refresh");
public Functions() {
}
@@ -406,7 +409,7 @@ public class Functions {
* </pre>
*
* <p>
* The head portion is the part of the URL from the {@link jenkins.model.Jenkins}
* The head portion is the part of the URL from the {@link Jenkins}
* object to the first {@link Run} subtype. When "next/prev build"
* is chosen, this part remains intact.
*
@@ -486,13 +489,23 @@ public class Functions {
/**
* Gets the system property indicated by the specified key.
*
* Delegates to {@link SystemProperties#getString(java.lang.String)}.
* Delegates to {@link SystemProperties#getString(String)}.
*/
@Restricted(DoNotUse.class)
public static String getSystemProperty(String key) {
return SystemProperties.getString(key);
}
/**
* Returns true if and only if the UI refresh is enabled.
*
* @since TODO
*/
@Restricted(DoNotUse.class)
public static boolean isUiRefreshEnabled() {
return UI_REFRESH;
}
public static Map getEnvVars() {
return new TreeMap<>(EnvVars.masterEnvVars);
}
@@ -1051,10 +1064,13 @@ public class Functions {
* we needed this for {@link GlobalConfiguration}s are for backward compatibility.
*
* @param predicate
* Filter the descriptors based on {@link GlobalConfigurationCategory}
* Filter the descriptors based on this predicate
* @since 1.494
* @deprecated use {@link #getSortedDescriptorsForGlobalConfigByDescriptor(Predicate)}
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig(Predicate<GlobalConfigurationCategory> predicate) {
@Deprecated
@Restricted(NoExternalUse.class)
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig(com.google.common.base.Predicate<GlobalConfigurationCategory> predicate) {
ExtensionList<Descriptor> exts = ExtensionList.lookup(Descriptor.class);
List<Tag> r = new ArrayList<>(exts.size());
@@ -1062,6 +1078,10 @@ public class Functions {
Descriptor d = c.getInstance();
if (d.getGlobalConfigPage()==null) continue;
if (!Jenkins.get().hasPermission(d.getRequiredGlobalConfigPagePermission())) {
continue;
}
if (predicate.apply(d.getCategory())) {
r.add(new Tag(c.ordinal(), d));
}
@@ -1075,10 +1095,47 @@ public class Functions {
}
/**
* Like {@link #getSortedDescriptorsForGlobalConfig(Predicate)} but with a constant truth predicate, to include all descriptors.
* Gets all the descriptors sorted by their inheritance tree of {@link Describable}
* so that descriptors of similar types come nearby.
*
* <p>
* We sort them by {@link Extension#ordinal()} but only for {@link GlobalConfiguration}s,
* as the value is normally used to compare similar kinds of extensions, and we needed
* {@link GlobalConfiguration}s to be able to position themselves in a layer above.
* This however creates some asymmetry between regular {@link Descriptor}s and {@link GlobalConfiguration}s.
* Perhaps it is better to introduce another annotation element? But then,
* extensions shouldn't normally concern themselves about ordering too much, and the only reason
* we needed this for {@link GlobalConfiguration}s are for backward compatibility.
*
* @param predicate
* Filter the descriptors based on this predicate
* @since TODO
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig() {
return getSortedDescriptorsForGlobalConfig(Predicates.<GlobalConfigurationCategory>alwaysTrue());
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigByDescriptor(Predicate<Descriptor> predicate) {
ExtensionList<Descriptor> exts = ExtensionList.lookup(Descriptor.class);
List<Tag> r = new ArrayList<>(exts.size());
for (ExtensionComponent<Descriptor> c : exts.getComponents()) {
Descriptor d = c.getInstance();
if (d.getGlobalConfigPage()==null) continue;
if (predicate.test(d)) {
r.add(new Tag(c.ordinal(), d));
}
}
Collections.sort(r);
List<Descriptor> answer = new ArrayList<>(r.size());
for (Tag d : r) answer.add(d.d);
return DescriptorVisibilityFilter.apply(Jenkins.get(),answer);
}
/**
* Like {@link #getSortedDescriptorsForGlobalConfigByDescriptor(Predicate)} but with a constant truth predicate, to include all descriptors.
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigByDescriptor() {
return getSortedDescriptorsForGlobalConfigByDescriptor(descriptor -> true);
}
/**
@@ -1086,21 +1143,45 @@ public class Functions {
*/
@Deprecated
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigNoSecurity() {
return getSortedDescriptorsForGlobalConfig(Predicates.not(GlobalSecurityConfiguration.FILTER));
return getSortedDescriptorsForGlobalConfigByDescriptor(d -> GlobalSecurityConfiguration.FILTER.negate().test(d));
}
/**
* Like {@link #getSortedDescriptorsForGlobalConfig(Predicate)} but for unclassified descriptors only.
* Descriptors in the global configuration form that users with {@link Jenkins#MANAGE} permission can configure.
*
* @since 1.506
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigUnclassified() {
return getSortedDescriptorsForGlobalConfig(new Predicate<GlobalConfigurationCategory>() {
public boolean apply(GlobalConfigurationCategory cat) {
return cat instanceof GlobalConfigurationCategory.Unclassified;
}
});
return getSortedDescriptorsForGlobalConfigByDescriptor(d -> d.getCategory() instanceof GlobalConfigurationCategory.Unclassified && Jenkins.get().hasPermission(d.getRequiredGlobalConfigPagePermission()));
}
/**
* Descriptors shown in the global configuration form to users with {@link Jenkins#SYSTEM_READ} permission.
*
* @since TODO
*/
@Restricted(NoExternalUse.class)
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigUnclassifiedReadable() {
return getSortedDescriptorsForGlobalConfigByDescriptor(d -> d.getCategory() instanceof GlobalConfigurationCategory.Unclassified && (
Jenkins.get().hasPermission(d.getRequiredGlobalConfigPagePermission()) || Jenkins.get().hasPermission(Jenkins.SYSTEM_READ)));
}
/**
* Checks if the current security principal has one of the supplied permissions.
*
* @throws AccessDeniedException
* if the user doesn't have the permission.
*
* @since TODO
*/
public static void checkAnyPermission(AccessControlled ac, Permission[] permissions) {
if (permissions == null || permissions.length == 0) {
return;
}
ac.checkAnyPermission(permissions);
}
private static class Tag implements Comparable<Tag> {
double ordinal;
String hierarchy;
+10 -5
View File
@@ -1394,7 +1394,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@RequirePOST
public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
Jenkins.get().checkPermission(CONFIGURE_UPDATECENTER);
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
if (req.hasParameter("remove")) {
UpdateCenter uc = Jenkins.get().getUpdateCenter();
@@ -1600,7 +1600,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@RequirePOST
public HttpResponse doSiteConfigure(@QueryParameter String site) throws IOException {
Jenkins hudson = Jenkins.get();
hudson.checkPermission(CONFIGURE_UPDATECENTER);
hudson.checkPermission(Jenkins.ADMINISTER);
UpdateCenter uc = hudson.getUpdateCenter();
PersistedList<UpdateSite> sites = uc.getSites();
for (UpdateSite s : sites) {
@@ -1615,7 +1615,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@POST
public HttpResponse doProxyConfigure(StaplerRequest req) throws IOException, ServletException {
Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(CONFIGURE_UPDATECENTER);
jenkins.checkPermission(Jenkins.ADMINISTER);
ProxyConfiguration pc = req.bindJSON(ProxyConfiguration.class, req.getSubmittedForm());
if (pc.name==null) {
@@ -1634,7 +1634,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@RequirePOST
public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException {
try {
Jenkins.get().checkPermission(UPLOAD_PLUGINS);
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
@@ -1762,7 +1762,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
private FormValidation checkUpdatesServer() throws Exception {
for (UpdateSite site : Jenkins.get().getUpdateCenter().getSites()) {
FormValidation v = site.updateDirectlyNow(DownloadService.signatureCheck);
FormValidation v = site.updateDirectlyNow();
if (v.kind != FormValidation.Kind.OK) {
// Stop with an error
return v;
@@ -2105,7 +2105,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
public static boolean FAST_LOOKUP = !SystemProperties.getBoolean(PluginManager.class.getName()+".noFastLookup");
/** @deprecated in Jenkins 2.222 use {@link Jenkins#ADMINISTER} instead */
@Deprecated
public static final Permission UPLOAD_PLUGINS = new Permission(Jenkins.PERMISSIONS, "UploadPlugins", Messages._PluginManager_UploadPluginsPermission_Description(),Jenkins.ADMINISTER,PermissionScope.JENKINS);
/** @deprecated in Jenkins 2.222 use {@link Jenkins#ADMINISTER} instead */
@Deprecated
public static final Permission CONFIGURE_UPDATECENTER = new Permission(Jenkins.PERMISSIONS, "ConfigureUpdateCenter", Messages._PluginManager_ConfigureUpdateCenterPermission_Description(),Jenkins.ADMINISTER,PermissionScope.JENKINS);
/**
+2
View File
@@ -23,6 +23,7 @@
*/
package hudson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Launcher.ProcStarter;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
@@ -215,6 +216,7 @@ public abstract class Proc {
* @param err
* null to redirect stderr to stdout.
*/
@SuppressFBWarnings(value = "COMMAND_INJECTION", justification = "Command injection is the point of this old, barely used class.")
public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out,OutputStream err,File workDir) throws IOException {
this( calcName(cmd),
stderr(environment(new ProcessBuilder(cmd),env).directory(workDir), err==null || err== SELFPUMP_OUTPUT),
+11 -1
View File
@@ -589,6 +589,8 @@ public class Util {
/**
* Computes MD5 digest of the given input stream.
*
* This method should only be used for non-security applications where the MD5 weakness is not a problem.
*
* @param source
* The stream will be closed by this method at the end of this method.
* @return
@@ -598,7 +600,7 @@ public class Util {
@Nonnull
public static String getDigestOf(@Nonnull InputStream source) throws IOException {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest md5 = getMd5();
DigestInputStream in = new DigestInputStream(source, md5);
// Note: IOUtils.copy() buffers the input internally, so there is no
// need to use a BufferedInputStream.
@@ -618,6 +620,14 @@ public class Util {
*/
}
// TODO JENKINS-60563 remove MD5 from all usages in Jenkins
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_MD5", justification =
"This method should only be used for non-security applications where the MD5 weakness is not a problem.")
@Deprecated
private static MessageDigest getMd5() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("MD5");
}
@Nonnull
public static String getDigestOf(@Nonnull String text) {
try {
@@ -23,6 +23,7 @@
*/
package hudson.cli;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.remoting.ClassFilter;
import hudson.remoting.ObjectInputStreamEx;
import hudson.remoting.SocketChannelStream;
@@ -113,6 +114,8 @@ public class Connection {
/**
* Receives an object sent by {@link #writeObject(Object)}
*/
// TODO JENKINS-60562 remove this class
@SuppressFBWarnings(value = "OBJECT_DESERIALIZATION", justification = "Not used. We should just remove it. Class is deprecated.")
public <T> T readObject() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStreamEx(in,
getClass().getClassLoader(), ClassFilter.DEFAULT);
@@ -194,16 +197,20 @@ public class Connection {
*/
public Connection encryptConnection(SecretKey sessionKey, String algorithm) throws IOException, GeneralSecurityException {
Cipher cout = Cipher.getInstance(algorithm);
cout.init(Cipher.ENCRYPT_MODE, sessionKey, new IvParameterSpec(sessionKey.getEncoded()));
cout.init(Cipher.ENCRYPT_MODE, sessionKey, createIv(sessionKey));
CipherOutputStream o = new CipherOutputStream(out, cout);
Cipher cin = Cipher.getInstance(algorithm);
cin.init(Cipher.DECRYPT_MODE, sessionKey, new IvParameterSpec(sessionKey.getEncoded()));
cin.init(Cipher.DECRYPT_MODE, sessionKey, createIv(sessionKey));
CipherInputStream i = new CipherInputStream(in, cin);
return new Connection(i,o);
}
private IvParameterSpec createIv(SecretKey sessionKey) {
return new IvParameterSpec(sessionKey.getEncoded());
}
/**
* Given a byte array that contains arbitrary number of bytes, digests or expands those bits into the specified
* number of bytes without loss of entropy.
@@ -59,7 +59,7 @@ public class GroovyCommand extends CLICommand {
protected int run() throws Exception {
// this allows the caller to manipulate the JVM state, so require the execute script privilege.
Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
Binding binding = new Binding();
binding.setProperty("out",new PrintWriter(stdout,true));
@@ -60,7 +60,7 @@ public class GroovyshCommand extends CLICommand {
@Override
protected int run() {
// this allows the caller to manipulate the JVM state, so require the admin privilege.
Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
// this being remote means no jline capability is available
System.setProperty("jline.terminal", UnsupportedTerminal.class.getName());
@@ -83,7 +83,7 @@ public class InstallPluginCommand extends CLICommand {
@Override
protected int run() throws Exception {
Jenkins h = Jenkins.get();
h.checkPermission(PluginManager.UPLOAD_PLUGINS);
h.checkPermission(Jenkins.ADMINISTER);
PluginManager pm = h.getPluginManager();
if (name != null) {
@@ -25,6 +25,7 @@
*/
package hudson.console;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jenkins.model.Jenkins;
import hudson.remoting.ObjectInputStreamEx;
import java.util.concurrent.TimeUnit;
@@ -128,7 +129,7 @@ public class AnnotatedLargeText<T> extends LargeText {
long timestamp = ois.readLong();
if (TimeUnit.HOURS.toMillis(1) > abs(System.currentTimeMillis()-timestamp))
// don't deserialize something too old to prevent a replay attack
return (ConsoleAnnotator) ois.readObject();
return getConsoleAnnotator(ois);
} catch (RuntimeException ex) {
throw new IOException("Could not decode input", ex);
}
@@ -140,6 +141,11 @@ public class AnnotatedLargeText<T> extends LargeText {
return ConsoleAnnotator.initial(context);
}
@SuppressFBWarnings(value = "OBJECT_DESERIALIZATION", justification = "Deserialization is protected by logic.")
private ConsoleAnnotator getConsoleAnnotator(ObjectInputStream ois) throws IOException, ClassNotFoundException {
return (ConsoleAnnotator) ois.readObject();
}
@CheckReturnValue
@Override
public long writeLogTo(long start, Writer w) throws IOException {
@@ -23,6 +23,7 @@
*/
package hudson.console;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.ExtensionPoint;
import hudson.Functions;
import hudson.MarkupText;
@@ -275,7 +276,7 @@ public abstract class ConsoleNote<T> implements Serializable, Describable<Consol
try (ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream(new ByteArrayInputStream(buf)),
jenkins != null ? jenkins.pluginManager.uberClassLoader : ConsoleNote.class.getClassLoader(),
ClassFilter.DEFAULT)) {
return (ConsoleNote) ois.readObject();
return getConsoleNote(ois);
}
} catch (Error e) {
// for example, bogus 'sz' can result in OutOfMemoryError.
@@ -284,6 +285,11 @@ public abstract class ConsoleNote<T> implements Serializable, Describable<Consol
}
}
@SuppressFBWarnings(value = "OBJECT_DESERIALIZATION", justification = "Deserialization is protected by logic.")
private static ConsoleNote getConsoleNote(ObjectInputStream ois) throws IOException, ClassNotFoundException {
return (ConsoleNote) ois.readObject();
}
/**
* Skips the encoded console note.
*/
@@ -103,7 +103,7 @@ public final class MemoryUsageMonitor extends PeriodicWork {
* Generates the memory usage statistics graph.
*/
public TrendChart doGraph(@QueryParameter String type) throws IOException {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
return MultiStageTimeSeries.createTrendChart(TimeScale.parse(type),used,max);
}
}
@@ -68,7 +68,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
* If {@link #isActivated()} returns true, Jenkins will use the {@code message.jelly}
* view of this object to render the warning text. This happens in the
* {@code http://SERVER/jenkins/manage} page. This view should typically render
* a DIV box with class='alert alert-error' or class='alert alert-warning' with a human-readable text
* a DIV box with class='alert alert-danger' or class='alert alert-warning' with a human-readable text
* inside it. It often also contains a link to a page that provides more details
* about the problem.
* </dd>
@@ -778,7 +778,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
@Exported
public Set<LabelAtom> getAssignedLabels() {
Node node = getNode();
return (node != null) ? node.getAssignedLabels() : Collections.EMPTY_SET;
return (node != null) ? node.getAssignedLabels() : Collections.emptySet();
}
/**
@@ -786,7 +786,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
public List<AbstractProject> getTiedJobs() {
Node node = getNode();
return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.EMPTY_LIST;
return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.emptyList();
}
public RunList getBuilds() {
@@ -24,6 +24,7 @@
package hudson.model;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.PluginWrapper;
import hudson.RelativePath;
import hudson.XmlFile;
@@ -31,6 +32,7 @@ import hudson.BulkChange;
import hudson.ExtensionList;
import hudson.Util;
import hudson.model.listeners.SaveableListener;
import hudson.security.Permission;
import hudson.util.FormApply;
import hudson.util.FormValidation.CheckMethod;
import hudson.util.ReflectionUtils;
@@ -830,6 +832,20 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable,
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Unclassified.class);
}
/**
* Returns the permission type needed in order to access the {@link #getGlobalConfigPage()} for this descriptor.
* By default, requires {@link Jenkins#ADMINISTER} permission.
* For now this only applies to descriptors configured through the global ({@link jenkins.model.GlobalConfigurationCategory.Unclassified}) configuration.
* Override to return something different if appropriate. The only currently supported alternative return value is {@link Jenkins#MANAGE}.
*
* @return Permission required to globally configure this descriptor.
* @since TODO
*/
public @Nonnull
Permission getRequiredGlobalConfigPagePermission() {
return Jenkins.ADMINISTER;
}
private String getViewPage(Class<?> clazz, String pageName, String defaultValue) {
return getViewPage(clazz,Collections.singleton(pageName),defaultValue);
}
@@ -346,13 +346,12 @@ public final class DirectoryBrowserSupport implements HttpResponse {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Serving "+baseFile+" with lastModified=" + lastModified + ", length=" + length);
InputStream in = baseFile.open();
if (view) {
// for binary files, provide the file name for download
rsp.setHeader("Content-Disposition", "inline; filename=" + baseFile.getName());
// pseudo file name to let the Stapler set text/plain
rsp.serveFile(req, in, lastModified, -1, length, "plain.txt");
rsp.serveFile(req, baseFile.open(), lastModified, -1, length, "plain.txt");
} else {
if (resourceToken != null) {
// redirect to second domain
@@ -368,7 +367,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
}
}
rsp.serveFile(req, in, lastModified, -1, length, baseFile.getName());
rsp.serveFile(req, baseFile.open(), lastModified, -1, length, baseFile.getName());
}
}
}
@@ -1257,8 +1257,7 @@ public class Fingerprint implements ModelObject, Saveable {
file.getParentFile().mkdirs();
// JENKINS-16301: fast path for the common case.
AtomicFileWriter afw = new AtomicFileWriter(file);
try {
PrintWriter w = new PrintWriter(afw);
try (PrintWriter w = new PrintWriter((afw))) {
w.println("<?xml version='1.1' encoding='UTF-8'?>");
w.println("<fingerprint>");
w.print(" <timestamp>");
@@ -25,9 +25,13 @@ package hudson.model;
import hudson.model.listeners.ItemListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.io.File;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import org.acegisecurity.AccessDeniedException;
@@ -55,6 +59,33 @@ public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject
*/
Collection<T> getItems();
/**
* Gets all the items in this collection in a read-only view
* that matches supplied Predicate
* @since TODO
*/
default Collection<T> getItems(Predicate<T> pred) {
return getItemsStream(pred)
.collect(Collectors.toList());
}
/**
* Gets a read-only stream of all the items in this collection
* @since TODO
*/
default Stream<T> getItemsStream() {
return getItems().stream();
}
/**
* Gets a read-only stream of all the items in this collection
* that matches supplied Predicate
* @since TODO
*/
default Stream<T> getItemsStream(Predicate<T> pred) {
return getItemsStream().filter(pred);
}
/**
* Returns the path relative to the context root,
* like "foo/bar/zot/". Note no leading slash but trailing slash.
@@ -99,6 +130,14 @@ public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject
return Items.getAllItems(this, type);
}
/**
* Similar to {@link #getAllItems(Class)} with additional predicate filtering
* @since TODO
*/
default <T extends Item> List<T> getAllItems(Class<T> type, Predicate<T> pred) {
return Items.getAllItems(this, type, pred);
}
/**
* Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree
* and filter them by the given type.
@@ -108,6 +147,15 @@ public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject
return Items.allItems(this, type);
}
/**
* Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree
* and filter them by the given type and given predicate
* @since TODO
*/
default <T extends Item> Iterable<T> allItems(Class<T> type, Predicate<T> pred) {
return Items.allItems(this, type, pred);
}
/**
* Gets all the items recursively.
* @since 2.93
+72 -10
View File
@@ -47,6 +47,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.function.Predicate;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
@@ -393,26 +394,44 @@ public class Items {
* <p>
* If you do not need to iterate all items, or if the order of the items is not required, consider using
* {@link #allItems(ItemGroup, Class)} instead.
*
* @param root Root node to start searching from
* @param type Given type of of items being searched for
* @return List of items matching given criteria
*
* @since 1.512
*/
public static <T extends Item> List<T> getAllItems(final ItemGroup root, Class<T> type) {
return getAllItems(root ,type, t -> true);
}
/**
* Similar to {@link #getAllItems(ItemGroup, Class)} but with a predicate to pre-filter items to
* avoid checking ACLs unnecessarily and returning items not required by the caller
* @param root Root node to start searching from
* @param type Given type of of items being searched for
* @param pred Predicate condition to filter items
* @return List of items matching given criteria
*
* @since TODO
*/
public static <T extends Item> List<T> getAllItems(final ItemGroup root, Class<T> type, Predicate<T> pred) {
List<T> r = new ArrayList<>();
getAllItems(root, type, r);
getAllItems(root, type, r, pred);
return r;
}
private static <T extends Item> void getAllItems(final ItemGroup root, Class<T> type, List<T> r) {
List<Item> items = new ArrayList<>(((ItemGroup<?>) root).getItems());
private static <T extends Item> void getAllItems(final ItemGroup root, Class<T> type, List<T> r, Predicate<T> pred) {
List<Item> items = new ArrayList<>(((ItemGroup<?>) root).getItems(t -> t instanceof ItemGroup || (type.isInstance(t) && pred.test(type.cast(t)))));
// because we add items depth first, we can use the quicker BY_NAME comparison
items.sort(BY_NAME);
for (Item i : items) {
if (type.isInstance(i)) {
if (type.isInstance(i) && pred.test(type.cast(i))) {
if (i.hasPermission(Item.READ)) {
r.add(type.cast(i));
}
}
if (i instanceof ItemGroup) {
getAllItems((ItemGroup) i, type, r);
getAllItems((ItemGroup) i, type, r, pred);
}
}
}
@@ -434,6 +453,24 @@ public class Items {
return allItems(Jenkins.getAuthentication(), root, type);
}
/**
* Gets a read-only view of all the {@link Item}s recursively matching type and predicate
* in the {@link ItemGroup} tree visible to
* {@link Jenkins#getAuthentication()} without concern for the order in which items are returned. Each iteration
* of the view will be "live" reflecting the items available between the time the iteration was started and the
* time the iteration was completed, however if items are moved during an iteration - depending on the move - it
* may be possible for such items to escape the entire iteration.
*
* @param root the root.
* @param type the type.
* @param <T> the type.
* @param <T> the predicate.
* @return An {@link Iterable} for all items.
* @since TODO
*/
public static <T extends Item> Iterable<T> allItems(ItemGroup root, Class<T> type, Predicate<T> pred) {
return allItems(Jenkins.getAuthentication(), root, type, pred);
}
/**
* Gets a read-only view all the {@link Item}s recursively in the {@link ItemGroup} tree visible to the supplied
@@ -449,7 +486,26 @@ public class Items {
* @since 2.37
*/
public static <T extends Item> Iterable<T> allItems(Authentication authentication, ItemGroup root, Class<T> type) {
return new AllItemsIterable<>(root, authentication, type);
return allItems(authentication, root, type, t -> true);
}
/**
* Gets a read-only view all the {@link Item}s recursively matching supplied type and predicate conditions
* in the {@link ItemGroup} tree visible to the supplied
* authentication without concern for the order in which items are returned. Each iteration
* of the view will be "live" reflecting the items available between the time the iteration was started and the
* time the iteration was completed, however if items are moved during an iteration - depending on the move - it
* may be possible for such items to escape the entire iteration.
*
* @param root the root.
* @param type the type.
* @param <T> the type.
* @param pred the predicate.
* @return An {@link Iterable} for all items.
* @since TODO
*/
public static <T extends Item> Iterable<T> allItems(Authentication authentication, ItemGroup root, Class<T> type, Predicate<T> pred) {
return new AllItemsIterable<>(root, authentication, type, pred);
}
/**
@@ -518,11 +574,16 @@ public class Items {
* The type of item we want to return.
*/
private final Class<T> type;
/**
* Predicate to filter items with
*/
private final Predicate<T> pred;
private AllItemsIterable(ItemGroup root, Authentication authentication, Class<T> type) {
private AllItemsIterable(ItemGroup root, Authentication authentication, Class<T> type, Predicate<T> pred) {
this.root = root;
this.authentication = authentication;
this.type = type;
this.pred = pred;
}
@Override
@@ -561,6 +622,7 @@ public class Items {
if (next != null) {
return true;
}
Predicate<Item> search = t -> t instanceof ItemGroup || (type.isInstance(t) && pred.test(type.cast(t)));
while (true) {
if (delegate == null || !delegate.hasNext()) {
if (stack.isEmpty()) {
@@ -569,13 +631,13 @@ public class Items {
ItemGroup group = stack.pop();
// group.getItems() is responsible for performing the permission check so we will not repeat it
if (Jenkins.getAuthentication() == authentication) {
delegate = group.getItems().iterator();
delegate = group.getItems(search).iterator();
} else {
// slower path because the caller has switched authentication
// we need to keep the original authentication so that allItems() can be used
// like getAllItems() without the cost of building the entire list up front
try (ACLContext ctx = ACL.as(authentication)) {
delegate = group.getItems().iterator();
delegate = group.getItems(search).iterator();
}
}
}
@@ -584,7 +646,7 @@ public class Items {
if (item instanceof ItemGroup) {
stack.push((ItemGroup) item);
}
if (type.isInstance(item)) {
if (type.isInstance(item) && pred.test(type.cast(item))) {
next = type.cast(item);
return true;
}
+6 -7
View File
@@ -66,6 +66,8 @@ import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
@@ -388,13 +390,10 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
*/
@Exported
public List<AbstractProject> getTiedJobs() {
List<AbstractProject> r = new ArrayList<>();
for (AbstractProject<?,?> p : Jenkins.get().allItems(AbstractProject.class)) {
if(p instanceof TopLevelItem && this.equals(p.getAssignedLabel()))
r.add(p);
}
r.sort(Items.BY_FULL_NAME);
return r;
return StreamSupport.stream(Jenkins.get().allItems(AbstractProject.class,
i -> i instanceof TopLevelItem && this.equals(i.getAssignedLabel())).spliterator(),
true)
.sorted(Items.BY_FULL_NAME).collect(Collectors.toList());
}
/**
@@ -35,7 +35,7 @@ import org.jenkinsci.Symbol;
@Extension(ordinal=100) @Symbol("manageJenkins")
public class ManageJenkinsAction implements RootAction {
public String getIconFileName() {
if (Jenkins.get().hasPermission(Jenkins.ADMINISTER))
if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ))
return "gear2.png";
else
return null;
@@ -112,10 +112,14 @@ public abstract class ManagementLink implements ExtensionPoint, Action {
}
/**
* @return permission required for user to access this management link, in addition to {@link Jenkins#ADMINISTER}
* Returns the permission required for user to see this management link on the "Manage Jenkins" page ({@link ManageJenkinsAction}).
*
* Historically, this returned null, which amounted to the same behavior, as {@link Jenkins#ADMINISTER} was required to access the page.
*
* @return the permission required for the link to be shown on "Manage Jenkins".
*/
public @CheckForNull Permission getRequiredPermission() {
return null;
public @Nonnull Permission getRequiredPermission() {
return Jenkins.ADMINISTER;
}
/**
@@ -1063,7 +1063,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public List<FormValidation> updateAllSites() throws InterruptedException, ExecutionException {
List <Future<FormValidation>> futures = new ArrayList<>();
for (UpdateSite site : getSites()) {
Future<FormValidation> future = site.updateDirectly(DownloadService.signatureCheck);
Future<FormValidation> future = site.updateDirectly();
if (future != null) {
futures.add(future);
}
@@ -2516,7 +2516,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
try {
// Need to do the following because the plugin manager will attempt to access
// $JENKINS_HOME/updates/$ID_DEFAULT.json. Needs to be up to date.
site.updateDirectlyNow(true);
site.updateDirectlyNow();
} catch (Exception e) {
LOGGER.log(WARNING, "Upgrading Jenkins. Failed to update the default Update Site '" + UpdateCenter.ID_DEFAULT +
"'. Plugin upgrades may fail.", e);
@@ -160,6 +160,17 @@ public class UpdateSite {
return dataTimestamp;
}
/**
* Update the data file from the given URL if the file
* does not exist, or is otherwise due for update.
* Accepted formats are JSONP or HTML with {@code postMessage}, not raw JSON.
* @return null if no updates are necessary, or the future result
* @since TODO
*/
public @CheckForNull Future<FormValidation> updateDirectly() {
return updateDirectly(DownloadService.signatureCheck);
}
/**
* Update the data file from the given URL if the file
* does not exist, or is otherwise due for update.
@@ -167,7 +178,9 @@ public class UpdateSite {
* @param signatureCheck whether to enforce the signature (may be off only for testing!)
* @return null if no updates are necessary, or the future result
* @since 1.502
* @deprecated use {@linkplain #updateDirectly()}
*/
@Deprecated
public @CheckForNull Future<FormValidation> updateDirectly(final boolean signatureCheck) {
if (! getDataFile().exists() || isDue()) {
return Jenkins.get().getUpdateCenter().updateService.submit(new Callable<FormValidation>() {
@@ -180,6 +193,16 @@ public class UpdateSite {
}
}
/**
* Forces an update of the data file from the configured URL, irrespective of the last time the data was retrieved.
* @return A {@code FormValidation} indicating the if the update metadata was successfully downloaded from the configured update site
* @since TODO
* @throws IOException if there was an error downloading or saving the file.
*/
public @Nonnull FormValidation updateDirectlyNow() throws IOException {
return updateDirectlyNow(DownloadService.signatureCheck);
}
@Restricted(NoExternalUse.class)
public @Nonnull FormValidation updateDirectlyNow(boolean signatureCheck) throws IOException {
return updateData(DownloadService.loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), signatureCheck);
+2 -2
View File
@@ -60,9 +60,9 @@ public abstract class ViewJob<JobT extends ViewJob<JobT,RunT>, RunT extends Run<
/**
* All {@link Run}s. Copy-on-write semantics.
*/
protected transient /*almost final*/ RunMap<RunT> runs = new RunMap<>();
protected transient volatile /*almost final*/ RunMap<RunT> runs = new RunMap<>(null, null);
private transient boolean notLoaded = true;
private transient volatile boolean notLoaded = true;
/**
* If the reloading of runs are in progress (in another thread,
+10 -2
View File
@@ -23,6 +23,8 @@
*/
package hudson.scheduler;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -51,10 +53,10 @@ public abstract class Hash {
* Produces an integer in [0,n)
*/
public abstract int next(int n);
public static Hash from(String seed) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest md5 = getMd5();
md5.update(seed.getBytes(StandardCharsets.UTF_8));
byte[] digest = md5.digest();
@@ -77,6 +79,12 @@ public abstract class Hash {
}
}
// TODO JENKINS-60563 remove MD5 from all usages in Jenkins
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_MD5", justification = "Should not be used for security.")
private static MessageDigest getMd5() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("MD5");
}
/**
* Creates a hash that always return 0.
*/
+70 -1
View File
@@ -27,6 +27,8 @@ import hudson.model.User;
import hudson.model.View;
import hudson.model.ViewDescriptor;
import hudson.model.ViewGroup;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -69,8 +71,48 @@ public abstract class ACL {
if (a == SYSTEM) {
return;
}
if(!hasPermission(a,p))
if (!hasPermission(a,p)) {
while (!p.enabled && p.impliedBy != null) {
p = p.impliedBy;
}
throw new AccessDeniedException2(a,p);
}
}
/**
* Checks if the current security principal has one of the supplied permissions.
*
* This is just a convenience function.
*
* @throws AccessDeniedException
* if the user doesn't have the permission.
* @throws IllegalArgumentException
* if no permissions are provided
*
* @since TODO
*/
public final void checkAnyPermission(@Nonnull Permission... permissions) {
if (permissions.length == 0) {
throw new IllegalArgumentException("At least one permission must be provided");
}
boolean failed = !hasAnyPermission(permissions);
Authentication authentication = Jenkins.getAuthentication();
if (failed) {
String permissionsDisplayName = Arrays.stream(permissions)
.map(p -> p.group.title + "/" + p.name)
.collect(Collectors.joining(", "));
String errorMessage;
if (permissions.length == 1) {
errorMessage = Messages.AccessDeniedException2_MissingPermission(authentication.getName(), permissionsDisplayName);
} else {
errorMessage = Messages.AccessDeniedException_MissingPermissions(authentication.getName(), permissionsDisplayName);
}
throw new AccessDeniedException(errorMessage);
}
}
/**
@@ -87,6 +129,33 @@ public abstract class ACL {
return hasPermission(a, p);
}
/**
* Checks if the current security principal has any of the permissions.
*
* @return {@code false}
* if the user doesn't have one of the required permissions.
*
* @throws IllegalArgumentException
* if no permissions are provided
*/
public final boolean hasAnyPermission(@Nonnull Permission... permissions) {
if (permissions.length == 0) {
throw new IllegalArgumentException("At least one permission must be provided");
}
Authentication a = Jenkins.getAuthentication();
if (a == SYSTEM) {
return true;
}
for (Permission permission : permissions) {
if (hasPermission(permission)) {
return true;
}
}
return false;
}
/**
* Checks if the given principle has the given permission.
*
@@ -47,6 +47,16 @@ public interface AccessControlled {
getACL().checkPermission(permission);
}
/**
* Convenient short-cut for {@code getACL().checkAnyPermission(permission)}
* @see ACL#checkAnyPermission(Permission...)
*
* @since TODO
*/
default void checkAnyPermission(@Nonnull Permission... permission) throws AccessDeniedException {
getACL().checkAnyPermission(permission);
}
/**
* Convenient short-cut for {@code getACL().hasPermission(permission)}
*/
@@ -54,6 +64,16 @@ public interface AccessControlled {
return getACL().hasPermission(permission);
}
/**
* Convenient short-cut for {@code getACL().hasAnyPermission(permission)}
* @see ACL#hasAnyPermission(Permission...)
*
* @since TODO
*/
default boolean hasAnyPermission(@Nonnull Permission... permission) {
return getACL().hasAnyPermission(permission);
}
/**
* Convenient short-cut for {@code getACL().hasPermission(a, permission)}
* @since 2.92
@@ -23,6 +23,7 @@
*/
package hudson.security;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.User;
import jenkins.model.Jenkins;
import hudson.util.Scrambler;
@@ -162,8 +163,7 @@ public class BasicAuthenticationFilter implements Filter {
path += '?'+q;
// prepare a redirect
rsp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
rsp.setHeader("Location",path);
prepareRedirect(rsp, path);
// ... but first let the container authenticate this request
RequestDispatcher d = servletContext.getRequestDispatcher("/j_security_check?j_username="+
@@ -171,6 +171,12 @@ public class BasicAuthenticationFilter implements Filter {
d.include(req,rsp);
}
@SuppressFBWarnings(value = "UNVALIDATED_REDIRECT", justification = "Redirect is validated as processed.")
private void prepareRedirect(HttpServletResponse rsp, String path) {
rsp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
rsp.setHeader("Location",path);
}
//public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// HttpServletRequest req = (HttpServletRequest) request;
// String authorization = req.getHeader("Authorization");
@@ -23,10 +23,10 @@
*/
package hudson.security;
import com.google.common.base.Predicate;
import hudson.BulkChange;
import hudson.Extension;
import hudson.Functions;
import hudson.RestrictedSince;
import hudson.markup.MarkupFormatter;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
@@ -37,6 +37,7 @@ import hudson.util.FormApply;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -106,7 +107,7 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
}
}
public boolean configure(StaplerRequest req, JSONObject json) throws hudson.model.Descriptor.FormException {
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
// for compatibility reasons, the actual value is stored in Jenkins
Jenkins j = Jenkins.get();
j.checkPermission(Jenkins.ADMINISTER);
@@ -126,7 +127,7 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
try {
j.setSlaveAgentPort(new ServerTcpPort(json.getJSONObject("slaveAgentPort")).getPort());
} catch (IOException e) {
throw new hudson.model.Descriptor.FormException(e, "slaveAgentPortType");
throw new FormException(e, "slaveAgentPortType");
}
}
Set<String> agentProtocols = new TreeSet<>();
@@ -144,7 +145,7 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
// persist all the additional security configs
boolean result = true;
for(Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfig(FILTER)){
for(Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfigByDescriptor(FILTER)){
result &= configureDescriptor(req,json,d);
}
@@ -184,15 +185,13 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
return Jenkins.ADMINISTER;
}
public static Predicate<GlobalConfigurationCategory> FILTER = new Predicate<GlobalConfigurationCategory>() {
public boolean apply(GlobalConfigurationCategory input) {
return input instanceof GlobalConfigurationCategory.Security;
}
};
@Restricted(NoExternalUse.class)
@RestrictedSince("TODO")
public static Predicate<Descriptor> FILTER = input -> input.getCategory() instanceof GlobalConfigurationCategory.Security;
/**
* @return
* @see hudson.model.Describable#getDescriptor()
* @see Describable#getDescriptor()
*/
@SuppressWarnings("unchecked")
@Override
@@ -23,6 +23,7 @@
*/
package hudson.security;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Functions;
import com.google.common.base.Strings;
@@ -100,15 +101,7 @@ public class HudsonAuthenticationEntryPoint extends AuthenticationProcessingFilt
} catch (IllegalStateException e) {
out = rsp.getWriter();
}
out.printf(
"<html><head>" +
"<meta http-equiv='refresh' content='1;url=%1$s'/>" +
"<script>window.location.replace('%1$s');</script>" +
"</head>" +
"<body style='background-color:white; color:white;'>%n" +
"%n%n"+
"Authentication required%n"+
"<!--%n",loginForm);
printResponse(loginForm, out);
if (cause!=null)
cause.report(out);
@@ -123,4 +116,17 @@ public class HudsonAuthenticationEntryPoint extends AuthenticationProcessingFilt
out.close();
}
}
@SuppressFBWarnings(value = "XSS_SERVLET", justification = "Intermediate step for redirecting users to login page.")
private void printResponse(String loginForm, PrintWriter out) {
out.printf(
"<html><head>" +
"<meta http-equiv='refresh' content='1;url=%1$s'/>" +
"<script>window.location.replace('%1$s');</script>" +
"</head>" +
"<body style='background-color:white; color:white;'>%n" +
"%n%n"+
"Authentication required%n"+
"<!--%n",loginForm);
}
}
@@ -490,7 +490,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
}
}
@Restricted(NoExternalUse.class)
@Restricted(NoExternalUse.class) // _entryForm.jelly and signup.jelly
public boolean isMailerPluginPresent() {
try {
// mail support has moved to a separate plugin
@@ -27,8 +27,11 @@ import hudson.Extension;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
@@ -49,14 +52,29 @@ public class GlobalCrumbIssuerConfiguration extends GlobalConfiguration {
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
// for compatibility reasons, the actual value is stored in Jenkins
Jenkins j = Jenkins.get();
if (json.has("csrf")) {
JSONObject csrf = json.getJSONObject("csrf");
j.setCrumbIssuer(CrumbIssuer.all().newInstanceFromRadioList(csrf, "issuer"));
if (json.has("crumbIssuer")) {
j.setCrumbIssuer(req.bindJSON(CrumbIssuer.class, json.getJSONObject("crumbIssuer")));
} else {
j.setCrumbIssuer(null);
j.setCrumbIssuer(createDefaultCrumbIssuer());
}
return true;
}
}
@Restricted(NoExternalUse.class) // Jelly
public CrumbIssuer getCrumbIssuer() {
return Jenkins.get().getCrumbIssuer();
}
@Restricted(NoExternalUse.class)
public static CrumbIssuer createDefaultCrumbIssuer() {
if (DISABLE_CSRF_PROTECTION) {
return null;
}
return new DefaultCrumbIssuer(SystemProperties.getBoolean(Jenkins.class.getName() + ".crumbIssuerProxyCompatibility", false));
}
@Restricted(NoExternalUse.class)
public static /* non-final */ boolean DISABLE_CSRF_PROTECTION = SystemProperties.getBoolean(GlobalCrumbIssuerConfiguration.class.getName() + ".DISABLE_CSRF_PROTECTION");
}
@@ -46,6 +46,8 @@ import hudson.util.FormValidation;
import hudson.util.NamingThreadFactory;
import hudson.util.SequentialExecutionQueue;
import hudson.util.StreamTaskListener;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.io.File;
import java.io.IOException;
@@ -557,7 +559,7 @@ public class SCMTrigger extends Trigger<Item> {
if (actions == null) {
additionalActions = new Action[0];
} else {
additionalActions = actions;
additionalActions = Arrays.copyOf(actions, actions.length);
}
}
@@ -189,13 +189,13 @@ public class AtomicFileWriter extends Writer {
try {
// Try to make an atomic move.
Files.move(tmpPath, destPath, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException e) {
} catch (IOException moveFailed) {
// If it falls here that can mean many things. Either that the atomic move is not supported,
// or something wrong happened. Anyway, let's try to be over-diagnosing
if (e instanceof AtomicMoveNotSupportedException) {
LOGGER.log(Level.WARNING, "Atomic move not supported. falling back to non-atomic move.", e);
if (moveFailed instanceof AtomicMoveNotSupportedException) {
LOGGER.log(Level.WARNING, "Atomic move not supported. falling back to non-atomic move.", moveFailed);
} else {
LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move.", e);
LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move.", moveFailed);
}
if (destPath.toFile().exists()) {
@@ -204,19 +204,19 @@ public class AtomicFileWriter extends Writer {
try {
Files.move(tmpPath, destPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e1) {
e1.addSuppressed(e);
} catch (IOException replaceFailed) {
replaceFailed.addSuppressed(moveFailed);
LOGGER.log(Level.WARNING, "Unable to move {0} to {1}. Attempting to delete {0} and abandoning.",
new Path[]{tmpPath, destPath});
try {
Files.deleteIfExists(tmpPath);
} catch (IOException e2) {
e2.addSuppressed(e1);
} catch (IOException deleteFailed) {
replaceFailed.addSuppressed(deleteFailed);
LOGGER.log(Level.WARNING, "Unable to delete {0}, good bye then!", tmpPath);
throw e2;
throw replaceFailed;
}
throw e1;
throw replaceFailed;
}
}
}
@@ -26,6 +26,7 @@ package hudson.util;
import java.lang.RuntimeException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -33,6 +34,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.util.Iterators.DuplicateFilterIterator;
/**
@@ -290,7 +292,7 @@ public class ConsistentHash<T> {
*/
private int md5(String s) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest md5 = getMd5();
md5.update(s.getBytes());
byte[] digest = md5.digest();
@@ -303,6 +305,12 @@ public class ConsistentHash<T> {
}
}
// TODO JENKINS-60563 remove MD5 from all usages in Jenkins
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_MD5", justification = "Not used for security.")
private MessageDigest getMd5() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("MD5");
}
/**
* unsigned byte->int.
*/
@@ -1,5 +1,6 @@
package hudson.util;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.EnvVars;
import hudson.Util;
import org.kohsuke.accmod.Restricted;
@@ -17,19 +18,23 @@ class DOSToUnixPathHelper {
void validate(File fexe);
}
static private boolean checkPrefix(String prefix, Helper helper) {
File f = new File(prefix);
File f = constructFile(prefix);
if(f.exists()) {
helper.checkExecutable(f);
return true;
}
File fexe = new File(prefix+".exe");
File fexe = constructFile(prefix+".exe");
if(fexe.exists()) {
helper.checkExecutable(fexe);
return true;
}
return false;
}
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Limited use for locating shell executable by administrator.")
private static File constructFile(String prefix) {
return new File(prefix);
}
static void iteratePath(String exe, Helper helper) {
exe = fixEmpty(exe);
if(exe==null) {
@@ -23,6 +23,7 @@
*/
package hudson.util;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.FilePath;
import hudson.ProxyConfiguration;
import hudson.Util;
@@ -145,6 +146,7 @@ public abstract class FormFieldValidator {
/**
* Gets the parameter as a file.
*/
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Not used.")
protected final File getFileParameter(String paramName) {
return new File(Util.fixNull(request.getParameter(paramName)));
}
@@ -329,7 +331,7 @@ public abstract class FormFieldValidator {
try {
URL url = new URL(value);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
HttpURLConnection con = openConnection(url);
con.connect();
if(con.getResponseCode()!=200
|| con.getHeaderField("X-Hudson")==null) {
@@ -342,6 +344,11 @@ public abstract class FormFieldValidator {
handleIOException(value,e);
}
}
@SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Not used.")
private HttpURLConnection openConnection(URL url) throws IOException {
return (HttpURLConnection)url.openConnection();
}
}
/**
@@ -196,7 +196,7 @@ public final class RemotingDiagnostics {
@WebMethod(name="heapdump.hprof")
public void doHeapDump(StaplerRequest req, StaplerResponse rsp) throws IOException, InterruptedException {
owner.checkPermission(Jenkins.RUN_SCRIPTS);
owner.checkPermission(Jenkins.ADMINISTER);
rsp.setContentType("application/octet-stream");
FilePath dump = obtain();
+2 -1
View File
@@ -5,6 +5,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
@@ -144,7 +145,7 @@ public class Retrier <V>{
* @return this builder
*/
public @Nonnull Builder<V> withDuringActionExceptions(@CheckForNull Class<?>[] exceptions) {
this.duringActionExceptions = exceptions;
this.duringActionExceptions = exceptions != null ? Arrays.copyOf(exceptions, exceptions.length) : null;
return this;
}
@@ -4,10 +4,12 @@ import com.google.common.collect.Lists;
import jenkins.util.SystemProperties;
import hudson.init.InitMilestone;
import hudson.init.InitReactorListener;
import hudson.security.ACL;
import hudson.util.DaemonThreadFactory;
import hudson.util.NamingThreadFactory;
import jenkins.model.Configuration;
import jenkins.model.Jenkins;
import jenkins.security.ImpersonatingExecutorService;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorException;
@@ -45,7 +47,7 @@ public class InitReactorRunner {
else
es = Executors.newSingleThreadExecutor(new NamingThreadFactory(new DaemonThreadFactory(), "InitReactorRunner"));
try {
reactor.execute(es,buildReactorListener());
reactor.execute(new ImpersonatingExecutorService(es, ACL.SYSTEM), buildReactorListener());
} finally {
es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks
}
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import hudson.security.csrf.GlobalCrumbIssuerConfiguration;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.security.seed.UserSeedProperty;
import jenkins.util.SystemProperties;
@@ -133,7 +134,7 @@ public class SetupWizard extends PageDecorator {
jenkins.setSlaveAgentPort(SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort",-1));
// require a crumb issuer
jenkins.setCrumbIssuer(new DefaultCrumbIssuer(SystemProperties.getBoolean(Jenkins.class.getName() + ".crumbIssuerProxyCompatibility",false)));
jenkins.setCrumbIssuer(GlobalCrumbIssuerConfiguration.createDefaultCrumbIssuer());
// set master -> slave security:
jenkins.getInjector().getInstance(AdminWhitelistRule.class)
@@ -26,8 +26,12 @@ package jenkins.management;
import hudson.Extension;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import javax.annotation.Nonnull;
/**
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
@@ -48,6 +52,12 @@ public class CliLink extends ManagementLink {
return Messages.CliLink_Description();
}
@Nonnull
@Override
public Permission getRequiredPermission() {
return Jenkins.MANAGE;
}
@Override
public String getUrlName() {
return "cli";
@@ -26,8 +26,12 @@ package jenkins.management;
import hudson.Extension;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import javax.annotation.Nonnull;
/**
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
@@ -48,6 +52,12 @@ public class ConfigureLink extends ManagementLink {
return Messages.ConfigureLink_Description();
}
@Nonnull
@Override
public Permission getRequiredPermission() {
return Jenkins.READ;
}
@Override
public String getUrlName() {
return "configure";
@@ -57,6 +57,6 @@ public class ConsoleLink extends ManagementLink {
@Override
public Permission getRequiredPermission() {
return Jenkins.RUN_SCRIPTS;
return Jenkins.ADMINISTER;
}
}
@@ -26,9 +26,13 @@ package jenkins.management;
import hudson.Extension;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import jenkins.management.Messages;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import javax.annotation.Nonnull;
/**
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
@@ -49,6 +53,12 @@ public class NodesLink extends ManagementLink {
return Messages.NodesLink_Description();
}
@Nonnull
@Override
public Permission getRequiredPermission() {
return Jenkins.MANAGE;
}
@Override
public String getUrlName() {
return "computer";
@@ -26,8 +26,12 @@ package jenkins.management;
import hudson.Extension;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import javax.annotation.Nonnull;
/**
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
@@ -48,6 +52,12 @@ public class StatisticsLink extends ManagementLink {
return Messages.StatisticsLink_Description();
}
@Nonnull
@Override
public Permission getRequiredPermission() {
return Jenkins.MANAGE;
}
@Override
public String getUrlName() {
return "load-statistics";
@@ -26,8 +26,13 @@ package jenkins.management;
import hudson.Extension;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import javax.annotation.CheckForNull;
/**
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
@@ -48,6 +53,12 @@ public class SystemInfoLink extends ManagementLink {
return Messages.SystemInfoLink_Description();
}
@Nonnull
@Override
public Permission getRequiredPermission() {
return Jenkins.SYSTEM_READ;
}
@Override
public String getUrlName() {
return "systemInfo";
@@ -0,0 +1,80 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.Extension;
import hudson.model.AsyncPeriodicWork;
import hudson.model.Job;
import hudson.model.TaskListener;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Background task actually running background build discarders.
*
* @see GlobalBuildDiscarderConfiguration
* @see GlobalBuildDiscarderStrategy
*/
@Restricted(NoExternalUse.class)
@Extension
public class BackgroundGlobalBuildDiscarder extends AsyncPeriodicWork {
private static final Logger LOGGER = Logger.getLogger(BackgroundGlobalBuildDiscarder.class.getName());
public BackgroundGlobalBuildDiscarder() {
super("Periodic background build discarder"); // TODO i18n
}
@Override
protected void execute(TaskListener listener) throws IOException, InterruptedException {
for (Job job : Jenkins.get().allItems(Job.class)) {
processJob(listener, job);
}
}
public static void processJob(TaskListener listener, Job job) {
listener.getLogger().println("Processing " + job.getFullName());
GlobalBuildDiscarderConfiguration.get().getConfiguredBuildDiscarders().forEach(strategy -> {
String displayName = strategy.getDescriptor().getDisplayName();
listener.getLogger().println("Offering " + job.getFullName() + " to " + displayName);
if (strategy.isApplicable(job)) {
listener.getLogger().println(job.getFullName() + " accepted by " + displayName);
try {
strategy.apply(job);
} catch (Exception ex) {
listener.error("An exception occurred when executing " + displayName + ": " + ex.getMessage());
LOGGER.log(Level.WARNING, "An exception occurred when executing " + displayName, ex);
}
}
});
}
@Override
public long getRecurrencePeriod() {
return HOUR;
}
}
@@ -0,0 +1,72 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.util.DescribableList;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
import java.util.Collections;
/**
* Global configuration UI for background build discarders
*
* @see GlobalBuildDiscarderStrategy
* @see BackgroundGlobalBuildDiscarder
*/
@Restricted(NoExternalUse.class)
@Extension @Symbol("buildDiscarders")
public class GlobalBuildDiscarderConfiguration extends GlobalConfiguration {
public static GlobalBuildDiscarderConfiguration get() {
return ExtensionList.lookupSingleton(GlobalBuildDiscarderConfiguration.class);
}
private final DescribableList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> configuredBuildDiscarders =
new DescribableList<>(this, Collections.singletonList(new JobGlobalBuildDiscarderStrategy()));
private Object readResolve() {
configuredBuildDiscarders.setOwner(this);
return this;
}
public DescribableList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> getConfiguredBuildDiscarders() {
return configuredBuildDiscarders;
}
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
try {
configuredBuildDiscarders.rebuildHetero(req, json, GlobalBuildDiscarderStrategyDescriptor.all(), "configuredBuildDiscarders");
return true;
} catch (IOException x) {
throw new FormException(x, "configuredBuildDiscarders");
}
}
}
@@ -0,0 +1,51 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.listeners.RunListener;
import hudson.util.LogTaskListener;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Run background build discarders on an individual job once a build is finalized
*/
@Extension
@Restricted(NoExternalUse.class)
public class GlobalBuildDiscarderListener extends RunListener<Run> {
private static final Logger LOGGER = Logger.getLogger(GlobalBuildDiscarderListener.class.getName());
@Override
public void onFinalized(Run run) {
Job job = run.getParent();
BackgroundGlobalBuildDiscarder.processJob(new LogTaskListener(LOGGER, Level.FINE), job);
}
}
@@ -0,0 +1,81 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Job;
import hudson.model.Run;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Extension point for global background build discarders.
*
* @see BackgroundGlobalBuildDiscarder
* @see GlobalBuildDiscarderConfiguration
* @see JobGlobalBuildDiscarderStrategy
*/
public abstract class GlobalBuildDiscarderStrategy extends AbstractDescribableImpl<GlobalBuildDiscarderStrategy> implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(GlobalBuildDiscarderStrategy.class.getName());
/**
* Returns true if and only if this strategy applies to the given job.
* @param job
* @return true if and only if this strategy applies to the given job.
*/
public abstract boolean isApplicable(Job<?, ?> job);
/**
* Applies this build discarder strategy to the given job, i.e. delete builds based on this strategy's configuration.
*
* The default implementation calls {@link #apply(Run)} on each build.
*
* @param job
* @throws IOException
* @throws InterruptedException
*/
public void apply(Job<? extends Job, ? extends Run> job) throws IOException, InterruptedException {
for (Run<? extends Job, ? extends Run> run : job.getBuilds()) {
try {
apply(run);
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Failed to delete " + run.getFullDisplayName(), ex);
}
}
}
/**
* Applies this build discarder strategy to the given run, i.e. delete builds based on this strategy's configuration.
*
* @param run
* @throws IOException
* @throws InterruptedException
*/
public void apply(Run<?, ?> run) throws IOException, InterruptedException {
// no-op by default
}
}
@@ -0,0 +1,37 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.DescriptorExtensionList;
import hudson.model.Descriptor;
/**
* {@link Descriptor} for {@link BackgroundGlobalBuildDiscarder}.
*/
public abstract class GlobalBuildDiscarderStrategyDescriptor extends Descriptor<GlobalBuildDiscarderStrategy> {
public static DescriptorExtensionList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> all() {
return Jenkins.get().getDescriptorList(GlobalBuildDiscarderStrategy.class);
}
}
@@ -24,12 +24,15 @@
package jenkins.model;
import hudson.Extension;
import hudson.security.Permission;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
/**
* Configures the project naming strategy.
*
@@ -59,4 +62,10 @@ public class GlobalProjectNamingStrategyConfiguration extends GlobalConfiguratio
}
return true;
}
@Nonnull
@Override
public Permission getRequiredGlobalConfigPagePermission() {
return Jenkins.MANAGE;
}
}
@@ -24,10 +24,12 @@
package jenkins.model;
import hudson.Extension;
import hudson.security.Permission;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
import java.io.IOException;
/**
@@ -57,4 +59,10 @@ public class GlobalQuietPeriodConfiguration extends GlobalConfiguration {
throw new FormException(e,"quietPeriod");
}
}
@Nonnull
@Override
public Permission getRequiredGlobalConfigPagePermission() {
return Jenkins.MANAGE;
}
}
@@ -24,11 +24,13 @@
package jenkins.model;
import hudson.Extension;
import hudson.security.Permission;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
import java.io.IOException;
/**
@@ -54,4 +56,10 @@ public class GlobalSCMRetryCountConfiguration extends GlobalConfiguration {
throw new FormException(e.getMessage(), "quietPeriod");
}
}
@Nonnull
@Override
public Permission getRequiredGlobalConfigPagePermission() {
return Jenkins.MANAGE;
}
}
+68 -13
View File
@@ -37,6 +37,8 @@ import com.thoughtworks.xstream.XStream;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.*;
import hudson.Launcher.LocalLauncher;
import hudson.security.csrf.DefaultCrumbIssuer;
import hudson.security.csrf.GlobalCrumbIssuerConfiguration;
import jenkins.AgentProtocol;
import jenkins.diagnostics.URICheckEncodingMonitor;
import jenkins.security.stapler.DoActionFilter;
@@ -293,6 +295,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
@@ -653,7 +656,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* {@link hudson.security.csrf.CrumbIssuer}
*/
private volatile CrumbIssuer crumbIssuer;
private volatile CrumbIssuer crumbIssuer = GlobalCrumbIssuerConfiguration.createDefaultCrumbIssuer();
/**
* All labels known to Jenkins. This allows us to reuse the same label instances
@@ -1762,12 +1765,21 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@Exported(name="jobs")
public List<TopLevelItem> getItems() {
return getItems(t -> true);
}
/**
* Gets just the immediate children of {@link Jenkins} based on supplied predicate.
*
* @see #getAllItems(Class)
* @since TODO
*/
public List<TopLevelItem> getItems(Predicate<TopLevelItem> pred) {
List<TopLevelItem> viewableItems = new ArrayList<>();
for (TopLevelItem item : items.values()) {
if (item.hasPermission(Item.READ))
if (pred.test(item) && item.hasPermission(Item.READ))
viewableItems.add(item);
}
return viewableItems;
}
@@ -1787,9 +1799,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
public <T> List<T> getItems(Class<T> type) {
List<T> r = new ArrayList<>();
for (TopLevelItem i : getItems())
if (type.isInstance(i))
r.add(type.cast(i));
for (TopLevelItem i : getItems(type::isInstance)) {
r.add(type.cast(i));
}
return r;
}
@@ -2190,6 +2202,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* @since 2.64
*/
public List<AdministrativeMonitor> getActiveAdministrativeMonitors() {
if (!Jenkins.get().hasPermission(ADMINISTER)) {
return Collections.emptyList();
}
return administrativeMonitors.stream().filter(m -> {
try {
return m.isEnabled() && m.isActivated();
@@ -3285,9 +3300,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
}
// Initialize the filter with the crumb issuer
setCrumbIssuer(crumbIssuer);
// Allow the disabling system property to interfere here
setCrumbIssuer(getCrumbIssuer());
// auto register root actions
for (Action a : getExtensionList(RootAction.class))
@@ -3753,7 +3767,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
BulkChange bc = new BulkChange(this);
try {
checkPermission(ADMINISTER);
checkPermission(MANAGE);
JSONObject json = req.getSubmittedForm();
@@ -3781,7 +3795,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@CheckForNull
public CrumbIssuer getCrumbIssuer() {
return crumbIssuer;
return GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION ? null : crumbIssuer;
}
public void setCrumbIssuer(CrumbIssuer issuer) {
@@ -4449,7 +4463,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
public static void _doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view, VirtualChannel channel, ACL acl) throws IOException, ServletException {
// ability to run arbitrary script is dangerous
acl.checkPermission(RUN_SCRIPTS);
acl.checkPermission(ADMINISTER);
String text = req.getParameter("script");
if (text != null) {
@@ -4479,7 +4493,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@RequirePOST
public void doEval(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
checkPermission(RUN_SCRIPTS);
checkPermission(ADMINISTER);
req.getWebApp().getDispatchValidator().allowDispatch(req, rsp);
try {
MetaClass mc = req.getWebApp().getMetaClass(getClass());
@@ -5235,7 +5249,48 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public static final PermissionGroup PERMISSIONS = Permission.HUDSON_PERMISSIONS;
public static final Permission ADMINISTER = Permission.HUDSON_ADMINISTER;
/**
* This permission grants access to parts of the Jenkins system configuration.
*
* <p>Only features that won't have an impact on Jenkins' overall security and stability should have their
* permission requirement lowered from {@link #ADMINISTER} to {@code MANAGE}.
* For example, many scripting and code execution features (e.g., configuring master agents, paths to tools on master, etc.)
* are unsafe to make available to users with only this permission,
* as they could be used to bypass permission enforcement and elevate permissions.</p>
*
* <p>This permission is disabled by default and support for it considered experimental.
* Administrators can set the system property {@code jenkins.security.ManagePermission} to enable it.</p>
*
* @since TODO
*/
@Restricted(Beta.class)
public static final Permission MANAGE = new Permission(PERMISSIONS, "Manage",
Messages._Jenkins_Manage_Description(),
ADMINISTER,
SystemProperties.getBoolean("jenkins.security.ManagePermission"),
new PermissionScope[]{PermissionScope.JENKINS});
/**
* Allows read-only access to large parts of the system configuration.
*
* When combined with {@link #MANAGE}, it is expected that everything is shown as if only {@link #SYSTEM_READ} was granted,
* but that only options editable by users with {@link #MANAGE} are editable.
*/
@Restricted(Beta.class)
public static final Permission SYSTEM_READ = new Permission(PERMISSIONS, "SystemRead",
Messages._Jenkins_SystemRead_Description(),
ADMINISTER,
SystemProperties.getBoolean("jenkins.security.SystemReadPermission"),
new PermissionScope[]{PermissionScope.JENKINS});
@Restricted(NoExternalUse.class) // called by jelly
public static final Permission[] MANAGE_AND_SYSTEM_READ =
new Permission[] { MANAGE, SYSTEM_READ };
public static final Permission READ = new Permission(PERMISSIONS,"Read",Messages._Hudson_ReadPermission_Description(),Permission.READ,PermissionScope.JENKINS);
/** @deprecated in Jenkins 2.222 use {@link Jenkins#ADMINISTER} instead */
@Deprecated
public static final Permission RUN_SCRIPTS = new Permission(PERMISSIONS, "RunScripts", Messages._Hudson_RunScriptsPermission_Description(),ADMINISTER,PermissionScope.JENKINS);
/**
@@ -0,0 +1,65 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.Extension;
import hudson.model.Job;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.annotation.Nonnull;
import java.io.IOException;
/**
* Periodically call a job's configured build discarder in the background.
*/
@Restricted(NoExternalUse.class)
public class JobGlobalBuildDiscarderStrategy extends GlobalBuildDiscarderStrategy {
@DataBoundConstructor
public JobGlobalBuildDiscarderStrategy() {
// required for data binding
}
@Override
public boolean isApplicable(Job<?, ?> job) {
return job.getBuildDiscarder() != null;
}
@Override
public void apply(Job<?, ?> job) throws IOException, InterruptedException {
job.logRotate();
}
@Extension @Symbol("jobBuildDiscarder")
public static class DescriptorImpl extends GlobalBuildDiscarderStrategyDescriptor {
@Nonnull
@Override
public String getDisplayName() {
return Messages.JobGlobalBuildDiscarderStrategy_displayName();
}
}
}
@@ -24,6 +24,7 @@
package jenkins.model;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.Job;
@@ -313,13 +314,19 @@ public final class RunIdMigrator {
if (args.length != 1) {
throw new Exception("pass one parameter, $JENKINS_HOME");
}
File root = new File(args[0]);
File root = constructFile(args[0]);
File jobs = new File(root, "jobs");
if (!jobs.isDirectory()) {
throw new FileNotFoundException("no such $JENKINS_HOME " + root);
}
new RunIdMigrator().unmigrateJobsDir(jobs);
}
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Only invoked from the command line as a standalone utility")
private static File constructFile(String arg) {
return new File(arg);
}
private void unmigrateJobsDir(File jobs) throws Exception {
File[] jobDirs = jobs.listFiles();
if (jobDirs == null) {
@@ -0,0 +1,74 @@
/*
* The MIT License
*
* Copyright 2019 Daniel Beck
*
* 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 jenkins.model;
import hudson.Extension;
import hudson.model.Job;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.annotation.Nonnull;
import java.io.IOException;
/**
* Apply a user-specified build discarder periodically on all jobs.
*/
@Restricted(NoExternalUse.class)
public class SimpleGlobalBuildDiscarderStrategy extends GlobalBuildDiscarderStrategy {
private BuildDiscarder discarder;
@DataBoundConstructor
public SimpleGlobalBuildDiscarderStrategy(BuildDiscarder discarder) {
this.discarder = discarder;
}
public BuildDiscarder getDiscarder() {
return discarder;
}
@Override
public boolean isApplicable(Job<?, ?> job) {
return true;
}
@Override
public void apply(Job<?, ?> job) throws IOException, InterruptedException {
if (discarder != null) {
discarder.perform(job);
}
}
@Extension
@Symbol("simpleBuildDiscarder")
public static class DescriptorImpl extends GlobalBuildDiscarderStrategyDescriptor {
@Nonnull
@Override
public String getDisplayName() {
return Messages.SimpleGlobalBuildDiscarderStrategy_displayName();
}
}
}
@@ -1,5 +1,6 @@
package jenkins.model.identity;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.model.UnprotectedRootAction;
import java.security.MessageDigest;
@@ -61,6 +62,7 @@ public class IdentityRootAction implements UnprotectedRootAction {
*
* @return the fingerprint of the public key.
*/
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_MD5", justification = "Not used for security. ")
public String getFingerprint() {
RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey();
if (key == null) {
@@ -68,7 +70,7 @@ public class IdentityRootAction implements UnprotectedRootAction {
}
// TODO replace with org.jenkinsci.remoting.util.KeyUtils once JENKINS-36871 changes are merged
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
MessageDigest digest = getMd5();
digest.reset();
byte[] bytes = digest.digest(key.getEncoded());
StringBuilder result = new StringBuilder(Math.max(0, bytes.length * 3 - 1));
@@ -84,4 +86,10 @@ public class IdentityRootAction implements UnprotectedRootAction {
throw new IllegalStateException("JLS mandates MD5 support");
}
}
// TODO JENKINS-60563 remove MD5 from all usages in Jenkins
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_MD5", justification = "Not used for security. ")
private MessageDigest getMd5() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("MD5");
}
}
@@ -91,7 +91,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer,R> i
/**
* Used in {@link #all()} to quickly determine if we've already loaded everything.
*/
private boolean fullyLoaded;
private volatile boolean fullyLoaded;
/**
* Currently visible index.
@@ -212,7 +212,7 @@ public class AdminWhitelistRule implements StaplerProxy {
public void setMasterKillSwitch(boolean state) {
final Jenkins jenkins = Jenkins.get();
try {
jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
jenkins.checkPermission(Jenkins.ADMINISTER);
File f = getMasterKillSwitchFile(jenkins);
FileUtils.writeStringToFile(f, Boolean.toString(state), Charset.defaultCharset());
// treat the file as the canonical source of information in case write fails
@@ -227,7 +227,7 @@ public class AdminWhitelistRule implements StaplerProxy {
*/
@Override
public Object getTarget() {
Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
return this;
}
@@ -48,7 +48,7 @@ public class MasterKillSwitchConfiguration extends GlobalConfiguration {
* Unless this option is relevant, we don't let users choose this.
*/
public boolean isRelevant() {
return jenkins.hasPermission(Jenkins.RUN_SCRIPTS) && jenkins.isUseSecurity();
return jenkins.hasPermission(Jenkins.ADMINISTER) && jenkins.isUseSecurity();
}
}
@@ -111,7 +111,7 @@ public class JnlpSlaveAgentProtocol4 extends AgentProtocol {
// prepare our keyStore so we can provide our authentication
keyStore = KeyStore.getInstance("JKS");
char[] password = "password".toCharArray();
char[] password = constructPassword();
try {
keyStore.load(null, password);
} catch (IOException e) {
@@ -146,6 +146,10 @@ public class JnlpSlaveAgentProtocol4 extends AgentProtocol {
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
}
private char[] constructPassword() {
return "password".toCharArray();
}
/**
* Inject the {@link IOHubProvider}
*
@@ -182,7 +186,7 @@ public class JnlpSlaveAgentProtocol4 extends AgentProtocol {
LOGGER.log(Level.INFO, "Updating {0} TLS certificate to retain validity", getName());
X509Certificate identityCertificate = InstanceIdentityProvider.RSA.getCertificate();
RSAPrivateKey privateKey = InstanceIdentityProvider.RSA.getPrivateKey();
char[] password = "password".toCharArray();
char[] password = constructPassword();
keyStore.setKeyEntry("jenkins", privateKey, password, new X509Certificate[]{identityCertificate});
}
} catch (KeyStoreException e) {
@@ -24,6 +24,7 @@
package jenkins.telemetry.impl;
import hudson.Extension;
import hudson.util.VersionNumber;
import jenkins.model.Jenkins;
import jenkins.telemetry.Telemetry;
import jenkins.util.SystemProperties;
@@ -97,7 +98,8 @@ public class SecuritySystemProperties extends Telemetry {
putStringInfo(security, "hudson.security.HudsonPrivateSecurityRealm.ID_REGEX");
Map<String, Object> info = new TreeMap<>();
info.put("core", Jenkins.getVersion().toString());
VersionNumber jenkinsVersion = Jenkins.getVersion();
info.put("core", jenkinsVersion != null ? jenkinsVersion.toString() : "UNKNOWN");
info.put("clientDate", clientDateString());
info.put("properties", security);
@@ -26,6 +26,7 @@ package jenkins.telemetry.impl.java11;
import com.google.common.annotations.VisibleForTesting;
import hudson.Extension;
import hudson.util.VersionNumber;
import io.jenkins.lib.versionnumber.JavaSpecificationVersion;
import jenkins.model.Jenkins;
import jenkins.telemetry.Telemetry;
@@ -160,7 +161,8 @@ public class MissingClassTelemetry extends Telemetry {
}
JSONObject info = new JSONObject();
info.put("core", Jenkins.getVersion() != null ? Jenkins.getVersion().toString() : "UNKNOWN");
VersionNumber jenkinsVersion = Jenkins.getVersion();
info.put("core", jenkinsVersion != null ? jenkinsVersion.toString() : "UNKNOWN");
info.put("clientDate", clientDateString());
info.put("classMissingEvents", events);
@@ -23,14 +23,13 @@
*/
package jenkins.tools;
import com.google.common.base.Predicate;
import hudson.Extension;
import hudson.Functions;
import hudson.model.Descriptor;
import hudson.model.ManagementLink;
import hudson.security.Permission;
import hudson.util.FormApply;
import jenkins.model.GlobalConfigurationCategory;
import java.util.function.Predicate;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
@@ -81,12 +80,12 @@ public class GlobalToolConfiguration extends ManagementLink {
FormApply.success(req.getContextPath() + "/manage").generateResponse(req, rsp, null);
}
private boolean configure(StaplerRequest req, JSONObject json) throws hudson.model.Descriptor.FormException, IOException {
private boolean configure(StaplerRequest req, JSONObject json) throws Descriptor.FormException, IOException {
Jenkins j = Jenkins.get();
j.checkPermission(Jenkins.ADMINISTER);
boolean result = true;
for(Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfig(FILTER)){
for (Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfigByDescriptor(FILTER)) {
result &= configureDescriptor(req, json, d);
}
j.save();
@@ -101,11 +100,7 @@ public class GlobalToolConfiguration extends ManagementLink {
return d.configure(req, js);
}
public static Predicate<GlobalConfigurationCategory> FILTER = new Predicate<GlobalConfigurationCategory>() {
public boolean apply(GlobalConfigurationCategory input) {
return input instanceof ToolConfigurationCategory;
}
};
public static Predicate<Descriptor> FILTER = input -> input.getCategory() instanceof ToolConfigurationCategory;
private static final Logger LOGGER = Logger.getLogger(GlobalToolConfiguration.class.getName());
}