diff --git a/Jenkinsfile b/Jenkinsfile index fd76d7f129..62d7f503cc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,7 +43,7 @@ for(j = 0; j < jdks.size(); j++) { "MAVEN_OPTS=-Xmx1536m -Xms512m"], buildType, jdk) { // Actually run Maven! // -Dmaven.repo.local=… tells Maven to create a subdir in the temporary directory for the local Maven repository - def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -ntp -Dmaven.repo.local=$m2repo -s settings-azure.xml -e" + def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -ntp -Dmaven.repo.local=$m2repo -e" if(isUnix()) { sh mvnCmd @@ -87,7 +87,7 @@ builds.ath = { checkout scm withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m", "MAVEN_OPTS=-Xmx1536m -Xms512m"], 8) { - sh "mvn --batch-mode --show-version -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml" + sh "mvn --batch-mode --show-version -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo" } dir("war/target") { fileUri = "file://" + pwd() + "/jenkins.war" diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java index 1b9b4856ad..9c33baa358 100644 --- a/cli/src/main/java/hudson/cli/CLI.java +++ b/cli/src/main/java/hudson/cli/CLI.java @@ -23,6 +23,7 @@ */ package hudson.cli; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.cli.client.Messages; import java.io.DataInputStream; import javax.net.ssl.HostnameVerifier; @@ -175,6 +176,7 @@ public class CLI { HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); // bypass host name check, too. HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @SuppressFBWarnings(value = "WEAK_HOSTNAME_VERIFIER", justification = "User set parameter to skip verifier.") public boolean verify(String s, SSLSession sslSession) { return true; } @@ -188,7 +190,7 @@ public class CLI { continue; } if(head.equals("-i") && args.size()>=2) { - File f = new File(args.get(1)); + File f = getFileFromArguments(args); if (!f.exists()) { printUsage(Messages.CLI_NoSuchFileExists(f)); return -1; @@ -290,7 +292,7 @@ public class CLI { if (userInfo != null) { factory = factory.basicAuth(userInfo); } else if (auth != null) { - factory = factory.basicAuth(auth.startsWith("@") ? FileUtils.readFileToString(new File(auth.substring(1)), Charset.defaultCharset()).trim() : auth); + factory = factory.basicAuth(auth.startsWith("@") ? readAuthFromFile(auth).trim() : auth); } if (mode == Mode.HTTP) { @@ -304,6 +306,16 @@ public class CLI { throw new AssertionError(); } + @SuppressFBWarnings(value = {"PATH_TRAVERSAL_IN", "URLCONNECTION_SSRF_FD"}, justification = "User provided values for running the program.") + private static String readAuthFromFile(String auth) throws IOException { + return FileUtils.readFileToString(new File(auth.substring(1)), Charset.defaultCharset()); + } + + @SuppressFBWarnings(value = {"PATH_TRAVERSAL_IN", "URLCONNECTION_SSRF_FD"}, justification = "User provided values for running the program.") + private static File getFileFromArguments(List args) { + return new File(args.get(1)); + } + private static int webSocketConnection(String url, List args, CLIConnectionFactory factory) throws Exception { LOGGER.fine(() -> "Trying to connect to " + url + " via plain protocol over WebSocket"); class CLIEndpoint extends Endpoint { diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java index 723e97f16c..fe8db45116 100644 --- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java +++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java @@ -1,5 +1,7 @@ package hudson.cli; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -60,7 +62,7 @@ public class FullDuplexHttpStream { // server->client LOGGER.fine("establishing download side"); - HttpURLConnection con = (HttpURLConnection) target.openConnection(); + HttpURLConnection con = openHttpConnection(target); con.setDoOutput(true); // request POST to avoid caching con.setRequestMethod("POST"); con.addRequestProperty("Session", uuid.toString()); @@ -78,7 +80,7 @@ public class FullDuplexHttpStream { // client->server uses chunked encoded POST for unlimited capacity. LOGGER.fine("establishing upload side"); - con = (HttpURLConnection) target.openConnection(); + con = openHttpConnection(target); con.setDoOutput(true); // request POST con.setRequestMethod("POST"); con.setChunkedStreamingMode(0); @@ -92,10 +94,15 @@ public class FullDuplexHttpStream { LOGGER.fine("established upload side"); } - // As this transport mode is using POST, it is necessary to resolve possible redirections using GET first. + @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Client-side code doesn't involve SSRF.") + private HttpURLConnection openHttpConnection(URL target) throws IOException { + return (HttpURLConnection) target.openConnection(); + } + + // As this transport mode is using POST, it is necessary to resolve possible redirections using GET first. @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Client-side code doesn't involve SSRF.") private URL tryToResolveRedirects(URL base, String authorization) { try { - HttpURLConnection con = (HttpURLConnection) base.openConnection(); + HttpURLConnection con = openHttpConnection(base); if (authorization != null) { con.addRequestProperty("Authorization", authorization); } diff --git a/cli/src/main/java/hudson/cli/NoCheckTrustManager.java b/cli/src/main/java/hudson/cli/NoCheckTrustManager.java index c69b3330c2..cceacf2b21 100644 --- a/cli/src/main/java/hudson/cli/NoCheckTrustManager.java +++ b/cli/src/main/java/hudson/cli/NoCheckTrustManager.java @@ -1,5 +1,7 @@ package hudson.cli; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import javax.net.ssl.X509TrustManager; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -8,12 +10,15 @@ import java.security.cert.X509Certificate; * @author Kohsuke Kawaguchi */ public class NoCheckTrustManager implements X509TrustManager { + @SuppressFBWarnings(value = "WEAK_TRUST_MANAGER", justification = "User set parameter to skip verifier.") public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } + @SuppressFBWarnings(value = "WEAK_TRUST_MANAGER", justification = "User set parameter to skip verifier.") public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } + @SuppressFBWarnings(value = "WEAK_TRUST_MANAGER", justification = "User set parameter to skip verifier.") public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } diff --git a/cli/src/main/java/hudson/cli/SSHCLI.java b/cli/src/main/java/hudson/cli/SSHCLI.java index 39dc9210a3..5321536c5c 100644 --- a/cli/src/main/java/hudson/cli/SSHCLI.java +++ b/cli/src/main/java/hudson/cli/SSHCLI.java @@ -61,11 +61,10 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; */ class SSHCLI { - @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", justification = "Due to whatever reason FindBugs reports it fot try-with-resources") static int sshConnection(String jenkinsUrl, String user, List args, PrivateKeyProvider provider, final boolean strictHostKey) throws IOException { Logger.getLogger(SecurityUtils.class.getName()).setLevel(Level.WARNING); // suppress: BouncyCastle not registered, using the default JCE provider URL url = new URL(jenkinsUrl + "login"); - URLConnection conn = url.openConnection(); + URLConnection conn = openConnection(url); CLI.verifyJenkinsConnection(conn); String endpointDescription = conn.getHeaderField("X-SSH-Endpoint"); @@ -131,6 +130,11 @@ class SSHCLI { } } + @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Client-side code doesn't involve SSRF.") + private static URLConnection openConnection(URL url) throws IOException { + return url.openConnection(); + } + private SSHCLI() {} } diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index c5369614de..e3af9fc07c 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -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()) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 01a8d3cc0e..6b5cf97304 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -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); } diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 50339569b6..8dd4342138 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -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 { * * *

- * 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 getSortedDescriptorsForGlobalConfig(Predicate predicate) { + @Deprecated + @Restricted(NoExternalUse.class) + public static Collection getSortedDescriptorsForGlobalConfig(com.google.common.base.Predicate predicate) { ExtensionList exts = ExtensionList.lookup(Descriptor.class); List 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. + * + *

+ * 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 getSortedDescriptorsForGlobalConfig() { - return getSortedDescriptorsForGlobalConfig(Predicates.alwaysTrue()); + public static Collection getSortedDescriptorsForGlobalConfigByDescriptor(Predicate predicate) { + ExtensionList exts = ExtensionList.lookup(Descriptor.class); + List r = new ArrayList<>(exts.size()); + + for (ExtensionComponent 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 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 getSortedDescriptorsForGlobalConfigByDescriptor() { + return getSortedDescriptorsForGlobalConfigByDescriptor(descriptor -> true); } /** @@ -1086,21 +1143,45 @@ public class Functions { */ @Deprecated public static Collection 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 getSortedDescriptorsForGlobalConfigUnclassified() { - return getSortedDescriptorsForGlobalConfig(new Predicate() { - 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 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 { double ordinal; String hierarchy; diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index d906466683..a235146efc 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -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 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); /** diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java index 6acb1ceeba..f387d6abcc 100644 --- a/core/src/main/java/hudson/Proc.java +++ b/core/src/main/java/hudson/Proc.java @@ -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), diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 7775b4ca6a..776fea2c41 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -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 { diff --git a/core/src/main/java/hudson/cli/Connection.java b/core/src/main/java/hudson/cli/Connection.java index 1961447778..bdeb192fd3 100644 --- a/core/src/main/java/hudson/cli/Connection.java +++ b/core/src/main/java/hudson/cli/Connection.java @@ -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 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. diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java index 9a802bc5a1..8279f7ba4e 100644 --- a/core/src/main/java/hudson/cli/GroovyCommand.java +++ b/core/src/main/java/hudson/cli/GroovyCommand.java @@ -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)); diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java index dd2584ebf0..c8255f6456 100644 --- a/core/src/main/java/hudson/cli/GroovyshCommand.java +++ b/core/src/main/java/hudson/cli/GroovyshCommand.java @@ -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()); diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index 09cb54c95c..7b5985c75e 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -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) { diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java index c862d0c61b..36d2664e92 100644 --- a/core/src/main/java/hudson/console/AnnotatedLargeText.java +++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java @@ -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 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 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 { diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index 4d2e2c2530..e55f7c2629 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -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 implements Serializable, Describable implements Serializable, Describable diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index de123b9368..0fe348beda 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -778,7 +778,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Exported public Set 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 getTiedJobs() { Node node = getNode(); - return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.EMPTY_LIST; + return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.emptyList(); } public RunList getBuilds() { diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index 463cdee366..a946de2acf 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -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> 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); } diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java index 0f02f1c2a2..b31795eff6 100644 --- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java +++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java @@ -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()); } } } diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index e628c6bd54..baf5e6215e 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -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(""); w.println(""); w.print(" "); diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index 2397587a0b..f79df6bae4 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -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 extends PersistenceRoot, ModelObject */ Collection getItems(); + /** + * Gets all the items in this collection in a read-only view + * that matches supplied Predicate + * @since TODO + */ + default Collection getItems(Predicate pred) { + return getItemsStream(pred) + .collect(Collectors.toList()); + } + + /** + * Gets a read-only stream of all the items in this collection + * @since TODO + */ + default Stream getItemsStream() { + return getItems().stream(); + } + + /** + * Gets a read-only stream of all the items in this collection + * that matches supplied Predicate + * @since TODO + */ + default Stream getItemsStream(Predicate 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 extends PersistenceRoot, ModelObject return Items.getAllItems(this, type); } + /** + * Similar to {@link #getAllItems(Class)} with additional predicate filtering + * @since TODO + */ + default List getAllItems(Class type, Predicate 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 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 Iterable allItems(Class type, Predicate pred) { + return Items.allItems(this, type, pred); + } + /** * Gets all the items recursively. * @since 2.93 diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index c6b06e322c..df4af2e221 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -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 { *

* 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 List getAllItems(final ItemGroup root, Class 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 List getAllItems(final ItemGroup root, Class type, Predicate pred) { List r = new ArrayList<>(); - getAllItems(root, type, r); + getAllItems(root, type, r, pred); return r; } - private static void getAllItems(final ItemGroup root, Class type, List r) { - List items = new ArrayList<>(((ItemGroup) root).getItems()); + private static void getAllItems(final ItemGroup root, Class type, List r, Predicate pred) { + List 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 the type. + * @param the predicate. + * @return An {@link Iterable} for all items. + * @since TODO + */ + public static Iterable allItems(ItemGroup root, Class type, Predicate 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 Iterable allItems(Authentication authentication, ItemGroup root, Class 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 the type. + * @param pred the predicate. + * @return An {@link Iterable} for all items. + * @since TODO + */ + public static Iterable allItems(Authentication authentication, ItemGroup root, Class type, Predicate 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 type; + /** + * Predicate to filter items with + */ + private final Predicate pred; - private AllItemsIterable(ItemGroup root, Authentication authentication, Class type) { + private AllItemsIterable(ItemGroup root, Authentication authentication, Class type, Predicate 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 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; } diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index ddf419604a..f9ce39340d 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -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

diff --git a/core/src/main/resources/hudson/model/View/AsynchPeople/index.jelly b/core/src/main/resources/hudson/model/View/AsynchPeople/index.jelly index ffdcd6d2f3..f3da56cbcd 100644 --- a/core/src/main/resources/hudson/model/View/AsynchPeople/index.jelly +++ b/core/src/main/resources/hudson/model/View/AsynchPeople/index.jelly @@ -100,7 +100,7 @@ THE SOFTWARE. - diff --git a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy index 6e19623d43..77e18e2cc6 100644 --- a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy +++ b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy @@ -4,7 +4,6 @@ import hudson.security.SecurityRealm import hudson.markup.MarkupFormatterDescriptor import hudson.security.AuthorizationStrategy import jenkins.AgentProtocol -import jenkins.model.GlobalConfiguration import hudson.Functions import hudson.model.Descriptor @@ -94,7 +93,7 @@ l.layout(norefresh:true, permission:app.ADMINISTER, title:my.displayName, csscla } } - Functions.getSortedDescriptorsForGlobalConfig(my.FILTER).each { Descriptor descriptor -> + Functions.getSortedDescriptorsForGlobalConfigByDescriptor(my.FILTER).each { Descriptor descriptor -> set("descriptor",descriptor) set("instance",descriptor) f.rowSet(name:descriptor.jsonSafeClassName) { diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryForm.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryForm.jelly index 69509462d5..3a0bd5627b 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryForm.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryForm.jelly @@ -24,23 +24,41 @@ THE SOFTWARE. - + + + + Context where the page is loaded. + + + Title of the HTML page. Rendered in the page content inside a <h1>. + + + The method to call from within the HudsonPrivateSecurityRealm. + + + Determines if the tag will include the captcha. + + + The wrapper for the data provided by the user and the error message(s) if any. Is null on first form display. + + +

${title}

-
+
-
+
${data.errorMessage}
-
+
${error.value}
+ ${%User ID} ${%Name} ${%Last Commit Activity}
- + @@ -55,17 +73,17 @@ THE SOFTWARE. - - - - + + + + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage.jelly index 8009ad6518..cb30e27bdf 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage.jelly @@ -22,28 +22,36 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + - + + + + Corresponds to the "it" of Jelly, meaning the context where the page is loaded. + + + Title of the HTML page. + Rendered into <title> tag, in the page content inside a <h1> and as the label of the submit button. + + + The method to call from within the HudsonPrivateSecurityRealm. + + + Determines if the tag will include the captcha. + + + The wrapper for the data provided by the user and the error message(s) if any. Is null on first form display. + + - - - + - - + + - diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage/resources.css b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage/resources.css new file mode 100644 index 0000000000..18e0744642 --- /dev/null +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/_entryFormPage/resources.css @@ -0,0 +1,12 @@ +input { + /* match width with captcha image */ + width:200px; +} + +.form-content { + margin: 2em; +} + +.form-content .error { + margin-bottom: 1em; +} diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/addUser.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/addUser.jelly index daacc17bfc..b9b3f246c7 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/addUser.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/addUser.jelly @@ -26,6 +26,6 @@ THE SOFTWARE. Page for admin to create a new user --> - - - \ No newline at end of file + + + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly index 9757152168..a36f9c14f0 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly @@ -23,8 +23,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser.jelly index 6ebd369ae0..b3568d1a6e 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser.jelly @@ -26,6 +26,6 @@ THE SOFTWARE. This is used to create the first user. --> - - - \ No newline at end of file + + + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly index a7cad84726..29d3ffc4a2 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - + @@ -32,20 +32,20 @@ THE SOFTWARE.
${%Username}:
${%Password}:
${%E-mail address}:
${%E-mail address}:
${%Enter text as shown}:
- [${%captcha}] + [${%captcha}]
- - - + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/loginLink.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/loginLink.jelly index c2ef8305fa..ba75e740a8 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/loginLink.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/loginLink.jelly @@ -23,10 +23,10 @@ THE SOFTWARE. --> - + | ${%sign up} - \ No newline at end of file + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/sidepanel.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/sidepanel.jelly index 8d864d01e9..8e3c84edcc 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/sidepanel.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/sidepanel.jelly @@ -23,13 +23,13 @@ THE SOFTWARE. --> - + - - - + + + - \ No newline at end of file + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signupWithFederatedIdentity.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signupWithFederatedIdentity.jelly index 8daec8d381..83f9eef9ec 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signupWithFederatedIdentity.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/signupWithFederatedIdentity.jelly @@ -26,6 +26,6 @@ THE SOFTWARE. User self sign up page. --> - - - \ No newline at end of file + + + diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/success.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/success.jelly index 59363c6d1c..a6f137e1a2 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/success.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/success.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/hudson/security/Messages.properties b/core/src/main/resources/hudson/security/Messages.properties index 03774c8251..26f54b92ee 100644 --- a/core/src/main/resources/hudson/security/Messages.properties +++ b/core/src/main/resources/hudson/security/Messages.properties @@ -69,5 +69,6 @@ PAMSecurityRealm.Uid=uid: {0} # not in use Permission.Permissions.Title=N/A AccessDeniedException2.MissingPermission={0} is missing the {1} permission +AccessDeniedException.MissingPermissions={0} is missing a permission, one of {1} is required NoneSecurityRealm.DisplayName=None diff --git a/core/src/main/resources/hudson/security/SecurityRealm/signup.jelly b/core/src/main/resources/hudson/security/SecurityRealm/signup.jelly index 9f054b876c..3609d38405 100644 --- a/core/src/main/resources/hudson/security/SecurityRealm/signup.jelly +++ b/core/src/main/resources/hudson/security/SecurityRealm/signup.jelly @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + diff --git a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.groovy b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.groovy index 0951384d93..b999cf5229 100644 --- a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.groovy +++ b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.groovy @@ -7,12 +7,13 @@ def all = CrumbIssuer.all() if (!all.isEmpty()) { f.section(title: _("CSRF Protection")) { - f.optionalBlock(field:"csrf", title:_("Prevent Cross Site Request Forgery exploits"), checked: app.useCrumbs ) { - f.entry(title:_("Crumbs")) { - table(style:"width:100%") { - f.descriptorRadioList(title:_("Crumb Algorithm"), varName:"issuer", instance:app.crumbIssuer, descriptors:all) - } + if (hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION) { + f.entry { + p(raw(_('disabled'))) + p(_('unsupported')) } + } else { + f.dropdownDescriptorSelector(title: _("Crumb Issuer"), descriptors: all, field: 'crumbIssuer') } } } diff --git a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.properties b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.properties new file mode 100644 index 0000000000..760578ae4a --- /dev/null +++ b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/config.properties @@ -0,0 +1,2 @@ +disabled=This configuration is unavailable because the System property hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION is set to true. +unsupported=That option should be considered unsupported and its use should be limited to working around compatibility problems until they are resolved. diff --git a/core/src/main/resources/jenkins/management/AdministrativeMonitorsConfiguration/config.groovy b/core/src/main/resources/jenkins/management/AdministrativeMonitorsConfiguration/config.groovy index 18416259db..5698abc807 100644 --- a/core/src/main/resources/jenkins/management/AdministrativeMonitorsConfiguration/config.groovy +++ b/core/src/main/resources/jenkins/management/AdministrativeMonitorsConfiguration/config.groovy @@ -34,7 +34,8 @@ f.section(title: _("Administrative monitors configuration")) { f.entry(title: _("Enabled administrative monitors")) { p(_("blurb")) table(width: "100%") { - for (AdministrativeMonitor am : AdministrativeMonitor.all()) { + for (AdministrativeMonitor am : new ArrayList<>(AdministrativeMonitor.all()) + .sort({ o1, o2 -> o1.getDisplayName() <=> o2.getDisplayName() })) { f.block() { f.checkbox(name: "administrativeMonitor", title: am.displayName, diff --git a/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.jelly b/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.jelly index c4809218de..b4b1c8bb29 100644 --- a/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.jelly +++ b/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.jelly @@ -27,7 +27,11 @@ THE SOFTWARE. diff --git a/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.properties b/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.properties index 60f6fe5694..a6f9d9eb11 100644 --- a/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.properties +++ b/core/src/main/resources/jenkins/management/AdministrativeMonitorsDecorator/footer.properties @@ -1 +1,2 @@ -tooltip=There are {0} active administrative monitors. \ No newline at end of file +tooltip=There are {0} active administrative monitors. +monitors={0,choice,0#monitors|1#monitor|1 - + @@ -45,21 +45,29 @@ THE SOFTWARE. - + + + - + + + - - - - + + + + + + - + + + diff --git a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly index d9dd6de40d..ea2b6cb53e 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly @@ -27,8 +27,7 @@ THE SOFTWARE. --> - - + diff --git a/core/src/main/resources/jenkins/model/Jenkins/systemInfo.jelly b/core/src/main/resources/jenkins/model/Jenkins/systemInfo.jelly index a1224ebc48..3889e160e7 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/systemInfo.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/systemInfo.jelly @@ -27,7 +27,7 @@ THE SOFTWARE. --> - +

${%System Properties}

@@ -59,8 +59,57 @@ THE SOFTWARE.
+ ${%User ID} ${%Name} +
${user.id} ${user} - + - +
+

${%Memory Usage}

+ + + + + + + + + + +
+ ${%Timespan}: + + + ${%Short} + + + ${%Short} + + + + + + ${%Medium} + + + ${%Medium} + + + + + + ${%Long} + + + ${%Long} + + +
+ + +

${%Thread Dumps}

${%threadDump_blurb('threadDump')}

+
diff --git a/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.jelly b/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.jelly new file mode 100644 index 0000000000..c694ee8558 --- /dev/null +++ b/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.jelly @@ -0,0 +1,30 @@ + + + + + + ${%blurb} + + diff --git a/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.properties b/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.properties new file mode 100644 index 0000000000..3e19a30f35 --- /dev/null +++ b/core/src/main/resources/jenkins/model/JobGlobalBuildDiscarderStrategy/config.properties @@ -0,0 +1,3 @@ +blurb = Build discarders configured for a job are only run after a build finishes. \ + This option runs jobs'' configured build discarders periodically, applying configuration changes even when no new builds are run. \ + This option has no effect if there is no build discarder configured for a job. diff --git a/core/src/main/resources/jenkins/model/Messages.properties b/core/src/main/resources/jenkins/model/Messages.properties index eb5effcbfd..64bf784b3c 100644 --- a/core/src/main/resources/jenkins/model/Messages.properties +++ b/core/src/main/resources/jenkins/model/Messages.properties @@ -76,3 +76,6 @@ CLI.disable-job.shortDescription=Disables a job. CLI.enable-job.shortDescription=Enables a job. GlobalCloudConfiguration.DisplayName=Configure Clouds + +SimpleGlobalBuildDiscarderStrategy.displayName=Specific Build Discarder +JobGlobalBuildDiscarderStrategy.displayName=Project Build Discarder diff --git a/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.jelly b/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.jelly new file mode 100644 index 0000000000..8e9f880f9a --- /dev/null +++ b/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.jelly @@ -0,0 +1,7 @@ + + + + ${%blurb} + + + diff --git a/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.properties b/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.properties new file mode 100644 index 0000000000..621323e43a --- /dev/null +++ b/core/src/main/resources/jenkins/model/SimpleGlobalBuildDiscarderStrategy/config.properties @@ -0,0 +1 @@ +blurb = The selected build discarder with be applied after any build finishes, as well as periodically. diff --git a/core/src/main/resources/jenkins/security/s2m/AdminWhitelistRule/index.jelly b/core/src/main/resources/jenkins/security/s2m/AdminWhitelistRule/index.jelly index 19dba8c4b9..fc1dbcce30 100644 --- a/core/src/main/resources/jenkins/security/s2m/AdminWhitelistRule/index.jelly +++ b/core/src/main/resources/jenkins/security/s2m/AdminWhitelistRule/index.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - +

${%Agent → Master Access Control}

diff --git a/core/src/main/resources/jenkins/split-plugins.txt b/core/src/main/resources/jenkins/split-plugins.txt index 0547f25558..f792f1fb9c 100644 --- a/core/src/main/resources/jenkins/split-plugins.txt +++ b/core/src/main/resources/jenkins/split-plugins.txt @@ -2,7 +2,7 @@ # Columns are: plugin ID, last core release still containing the plugin's functionality, plugin version that's implied, minimum Java specification version where to install the plugin -# Note that all split plugins between and including matrix-auth and jdk-tool incorrectly use the first +# Note that all split plugins between and including matrix-auth and jaxb incorrectly use the first # core release without the plugin's functionality when they should use the immediately prior release. # Fixing these retroactively won't help, as the difference only matters to those specific versions. diff --git a/core/src/main/resources/jenkins/tools/GlobalToolConfiguration/index.groovy b/core/src/main/resources/jenkins/tools/GlobalToolConfiguration/index.groovy index a4fe9ee97a..5ee526b1a9 100644 --- a/core/src/main/resources/jenkins/tools/GlobalToolConfiguration/index.groovy +++ b/core/src/main/resources/jenkins/tools/GlobalToolConfiguration/index.groovy @@ -25,7 +25,7 @@ l.layout(norefresh:true, permission:app.ADMINISTER, title:my.displayName) { div(class:"behavior-loading", _("LOADING")) f.form(method:"post",name:"config",action:"configure") { - Functions.getSortedDescriptorsForGlobalConfig(my.FILTER).each { Descriptor descriptor -> + Functions.getSortedDescriptorsForGlobalConfigByDescriptor(my.FILTER).each { Descriptor descriptor -> set("descriptor",descriptor) set("instance",descriptor) f.rowSet(name:descriptor.jsonSafeClassName) { diff --git a/core/src/main/resources/lib/form/checkbox.jelly b/core/src/main/resources/lib/form/checkbox.jelly index 240e612c0e..ec3b9892ae 100644 --- a/core/src/main/resources/lib/form/checkbox.jelly +++ b/core/src/main/resources/lib/form/checkbox.jelly @@ -68,6 +68,7 @@ THE SOFTWARE. title="${attrs.tooltip}" onclick="${attrs.readonly=='true' ? 'return false;' : attrs.onclick}" id="${attrs.id}" class="${attrs.class} ${attrs.negative!=null ? 'negative' : null} ${attrs.checkUrl!=null?'validated':''}" checkUrl="${attrs.checkUrl}" checkDependsOn="${attrs.checkDependsOn}" json="${attrs.json}" + disabled="${readOnlyMode ? 'true' : null}" checked="${value ? 'true' : null}"/>
diff --git a/core/src/main/resources/lib/form/hetero-list/hetero-list.js b/core/src/main/resources/lib/form/hetero-list/hetero-list.js index d9ada4f45f..c2aca7c411 100644 --- a/core/src/main/resources/lib/form/hetero-list/hetero-list.js +++ b/core/src/main/resources/lib/form/hetero-list/hetero-list.js @@ -10,6 +10,9 @@ Behaviour.specify("DIV.hetero-list-container", 'hetero-list', -100, function(e) var menu = document.createElement("SELECT"); var btns = findElementsBySelector(e,"INPUT.hetero-list-add"), btn = btns[btns.length-1]; // In case nested content also uses hetero-list + if (!btn) { + return; + } YAHOO.util.Dom.insertAfter(menu,btn); var prototypes = $(e.lastChild); diff --git a/core/src/main/resources/lib/form/number.jelly b/core/src/main/resources/lib/form/number.jelly index fb5a0a6c26..70ce46324b 100644 --- a/core/src/main/resources/lib/form/number.jelly +++ b/core/src/main/resources/lib/form/number.jelly @@ -76,12 +76,15 @@ THE SOFTWARE. - + + + + ${customizedFields.add(name)} diff --git a/core/src/main/resources/lib/form/password.jelly b/core/src/main/resources/lib/form/password.jelly index caaf04e0ff..6d52b164ac 100644 --- a/core/src/main/resources/lib/form/password.jelly +++ b/core/src/main/resources/lib/form/password.jelly @@ -60,74 +60,86 @@ THE SOFTWARE. If any other value is specified then requests will use GET. - - - + - - -
- -
-
- - - - - - - - - ${%Concealed} + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + + + + + + ${%Concealed} +
+
+ +
+
-
- -
- - +
+ + + + +
- - -
- - - + ATTRIBUTES="${attrs}" EXCEPT="field clazz value"/> + + diff --git a/core/src/main/resources/lib/form/possibleReadOnlyField.jelly b/core/src/main/resources/lib/form/possibleReadOnlyField.jelly new file mode 100644 index 0000000000..4fabae5c7e --- /dev/null +++ b/core/src/main/resources/lib/form/possibleReadOnlyField.jelly @@ -0,0 +1,18 @@ + + + + + + + N/A + + +
${value}
+
+
+
+ + + +
+
diff --git a/core/src/main/resources/lib/form/radio.jelly b/core/src/main/resources/lib/form/radio.jelly index fda884cc06..c614ae733e 100644 --- a/core/src/main/resources/lib/form/radio.jelly +++ b/core/src/main/resources/lib/form/radio.jelly @@ -26,7 +26,7 @@ THE SOFTWARE. <input type="radio"> tag that takes true/false for @checked, which is more Jelly friendly. - + Note that safari doesn't support onchange. Beware that the name attribute should be uniquified among all radio blocks on the page, such as by prefixing it with "G0025." or whatever gensym. @@ -41,7 +41,7 @@ THE SOFTWARE. + checked="${attrs.checked?'true':null}" disabled="${readOnlyMode?'true':null}" /> diff --git a/core/src/main/resources/lib/form/radioBlock.jelly b/core/src/main/resources/lib/form/radioBlock.jelly index c930ee0709..c42dcbdea0 100644 --- a/core/src/main/resources/lib/form/radioBlock.jelly +++ b/core/src/main/resources/lib/form/radioBlock.jelly @@ -57,8 +57,13 @@ THE SOFTWARE. diff --git a/core/src/main/resources/lib/form/slave-mode.jelly b/core/src/main/resources/lib/form/slave-mode.jelly index 61c8edc299..515fd7e7ca 100644 --- a/core/src/main/resources/lib/form/slave-mode.jelly +++ b/core/src/main/resources/lib/form/slave-mode.jelly @@ -36,10 +36,19 @@ THE SOFTWARE. - + + + ${node.mode} + + + + + + + \ No newline at end of file diff --git a/core/src/main/resources/lib/form/textarea.jelly b/core/src/main/resources/lib/form/textarea.jelly index 08c86f4333..117e8eb230 100644 --- a/core/src/main/resources/lib/form/textarea.jelly +++ b/core/src/main/resources/lib/form/textarea.jelly @@ -86,17 +86,19 @@ THE SOFTWARE. org.kohsuke.stapler.codemirror.theme.default"/>
- + + + ${customizedFields.add(name)} diff --git a/core/src/main/resources/lib/form/textbox.jelly b/core/src/main/resources/lib/form/textbox.jelly index 0f6cf0ea21..ad40892bd7 100644 --- a/core/src/main/resources/lib/form/textbox.jelly +++ b/core/src/main/resources/lib/form/textbox.jelly @@ -65,7 +65,7 @@ THE SOFTWARE. which is the recommended approach. - Specify 'post' (must be lowercase) to change the HTTP method used for the AJAX requests to @checkUrl from a GET to a POST. + Specify 'post' (must be lowercase) to change the HTTP method used for the AJAX requests to @checkUrl from a GET to a POST. If any other value is specified then requests will use GET. @@ -82,12 +82,15 @@ THE SOFTWARE. - + + + + ${customizedFields.add(name)} diff --git a/core/src/main/resources/lib/hudson/scriptConsole.jelly b/core/src/main/resources/lib/hudson/scriptConsole.jelly index a0d24b32e0..494dc6c516 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole.jelly +++ b/core/src/main/resources/lib/hudson/scriptConsole.jelly @@ -27,7 +27,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/lib/layout/breadcrumbBar.jelly b/core/src/main/resources/lib/layout/breadcrumbBar.jelly index ef1b411328..3412b1a87c 100644 --- a/core/src/main/resources/lib/layout/breadcrumbBar.jelly +++ b/core/src/main/resources/lib/layout/breadcrumbBar.jelly @@ -39,49 +39,48 @@ THE SOFTWARE.
-
- - - + + + + - - - - -
diff --git a/core/src/main/resources/lib/layout/breadcrumbs.css b/core/src/main/resources/lib/layout/breadcrumbs.css index 26c4683a8a..30b65c2c42 100644 --- a/core/src/main/resources/lib/layout/breadcrumbs.css +++ b/core/src/main/resources/lib/layout/breadcrumbs.css @@ -1,70 +1,115 @@ #left-top-nav { - text-align: left; - color: #222; -} - -#right-top-nav { - float: right; - margin-right: 1em; - height: 2em; - line-height: 2em; + text-align: left; + color: #222; } #left-top-nav a { - color: black; + color: black; } #top-nav .a { - color: white; + color: white; +} + +.breadcrumbs__wrapper { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 2.5rem; + font-size: 1rem; + line-height: 1.5; + font-family: Roboto, Helvetica, Arial, sans-serif; + padding: 0 1.25rem; + background-color: #F8F8F8; +} + +/* + Revert the font change to the options menu. + It's used all over the application and having Roboto may make it clash with other UI elements +*/ +#breadcrumb-menu { + font-family: Helvetica, Arial, sans-serif; } #breadcrumbs { list-style-type: none; - padding: 0 0 0 8px; margin: 0; - height: 2em; - border-bottom: 1px solid #d3d7cf; - display:block; + padding: 0; + min-height: 2.5rem; + display: block; + display: flex; + align-items: center; + flex: 1; + border-bottom: none; + flex-wrap: wrap; } #breadcrumbs LI { - float:left; - line-height:2em; - height: 2em; - color:#555753; display:inline-block; + min-height: 2.5rem; } -#breadcrumbs LI A { - display: block; - padding-left: 0.75em; - line-height: 2em; +#breadcrumbs LI A, +.breadcrumbs__auto-refresh { + display: inline-block; + display: inline-flex; + align-items: center; + height: 2.5rem; + padding: 0 0.5rem; } #breadcrumbs LI:hover, A.breadcrumbBarAnchor.mouseIsOverMenuSelector { background-color: #e0e4dc; } -#breadcrumbs LI A:link, #breadcrumbs LI A:visited { - text-decoration:none; - color:#555753; - display: block; + +#breadcrumbs LI A, #breadcrumbs LI A:link, #breadcrumbs LI A:visited, +.breadcrumbs__auto-refresh:link, .breadcrumbs__auto-refresh:visited { + /* is this still needed? */ max-width: 1330px; - float: left; + text-overflow: ellipsis; + text-decoration: none; overflow: hidden; white-space: nowrap; + font-weight: bold; + + display: inline-block; + display: inline-flex; + color: #4d545d; } -#breadcrumbs LI A:hover, #crumbs LI A:focus { - color:#3465a4; +#breadcrumbs LI A:hover, #breadcrumbs LI A:focus, +.breadcrumbs__auto-refresh:hover, .breadcrumbs__auto-refresh:focus { + /* TODO: change hover state + add it in the LI element so that it applies to the menuSeparator */ + text-decoration: underline; + outline-color: #3FB3F7; +} + +#breadcrumbs LI A:active, .breadcrumbs__auto-refresh:active { + background-color: #C8CEC2; +} + +#breadcrumbs LI A { + cursor: pointer; +} + +.breadcrumbs__auto-refresh { + text-transform: lowercase; + margin-left: 1.5rem; } #breadcrumbs LI.children, #breadcrumbs LI.separator {/* '>' separator between two items */ - width: 16px; + width: 1.25rem; + height: 100%; + display: inline-block; + display: inline-flex; + align-items: center; background-image: url(menu_right_arrow2.png); background-position: center center; background-repeat: no-repeat; + } #breadcrumbs LI.children { @@ -80,21 +125,21 @@ } #menuSelector {/* used for showing 'v' on the right of the anchor */ - background-color:transparent; - background-image: url(menu_down_arrow.png); - background-position: center center; - background-repeat: no-repeat; - width: 15px; - height:16px; - position: absolute; - visibility: hidden; - cursor: pointer; - z-index: 2000; + background-color:transparent; + background-image: url(menu_down_arrow.png); + background-position: center center; + background-repeat: no-repeat; + width: 15px; + height:16px; + position: absolute; + visibility: hidden; + cursor: pointer; + z-index: 2000; } #menuSelector.inverse { - background-image: url(menu_down_arrow2.png); + background-image: url(menu_down_arrow2.png); } A.model-link.inside, #breadcrumbs A.inside {/* additional 'inside' class allows pre-allocation of the context menu space */ - padding-right: 16px; + padding-right: 16px; } diff --git a/core/src/main/resources/lib/layout/hasAdministerOrManage.jelly b/core/src/main/resources/lib/layout/hasAdministerOrManage.jelly new file mode 100644 index 0000000000..faf8f95da5 --- /dev/null +++ b/core/src/main/resources/lib/layout/hasAdministerOrManage.jelly @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/src/main/resources/lib/layout/hasPermission.jelly b/core/src/main/resources/lib/layout/hasPermission.jelly index d42a5f9689..6371acc382 100644 --- a/core/src/main/resources/lib/layout/hasPermission.jelly +++ b/core/src/main/resources/lib/layout/hasPermission.jelly @@ -26,7 +26,13 @@ THE SOFTWARE. Renders the body only if the current user has the specified permission - + + By default it will reuse the current context. + If the provided value does not inherit from hudson.security.AccessControlled, + the tag will look for the first ancestor satisfying the condition. + The hasPermission will be performed against that value. + + permission object to check. If this is null, the body will be also rendered. @@ -34,4 +40,4 @@ THE SOFTWARE. - \ No newline at end of file + diff --git a/core/src/main/resources/lib/layout/html.jelly b/core/src/main/resources/lib/layout/html.jelly index 0a56c939ad..5fa717b347 100644 --- a/core/src/main/resources/lib/layout/html.jelly +++ b/core/src/main/resources/lib/layout/html.jelly @@ -80,7 +80,7 @@ THE SOFTWARE. - + ${h.checkPermission(it,permission)} diff --git a/core/src/main/resources/lib/layout/layout.jelly b/core/src/main/resources/lib/layout/layout.jelly index cac4b0d893..c6ea3ff9ca 100644 --- a/core/src/main/resources/lib/layout/layout.jelly +++ b/core/src/main/resources/lib/layout/layout.jelly @@ -49,9 +49,13 @@ THE SOFTWARE. in <l:header/> tag. - If given, this page is only made available to users that has the specified permission. + If given, this page is only made available to users who have the specified permission. (The permission will be checked against the "it" object.) + + If given, this page is made available to users who have one or more of the specified permissions. + (The permissions will be checked against the "it" object.) + Available values: two-column (by default), one-column (full-width size) or full-screen (since 2.53). @@ -92,17 +96,18 @@ THE SOFTWARE. - + ${h.checkPermission(it,permission)} + ${h.checkAnyPermission(it, permissions)} ${h.appendIfNotNull(title, ' [Jenkins]', 'Jenkins')} - - + + @@ -156,6 +161,9 @@ THE SOFTWARE. + + + @@ -185,60 +193,12 @@ THE SOFTWARE.