Skip to content

Commit

Permalink
Merge branch 'master' into hetero-list-button
Browse files Browse the repository at this point in the history
  • Loading branch information
mawinter69 committed Aug 23, 2023
2 parents a00e983 + ae74db6 commit 5f08c6c
Show file tree
Hide file tree
Showing 80 changed files with 650 additions and 88 deletions.
12 changes: 9 additions & 3 deletions core/src/main/java/hudson/model/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -592,10 +592,16 @@ public static Set<LabelAtom> parse(@CheckForNull String labels) {
final Set<LabelAtom> r = new TreeSet<>();
labels = fixNull(labels);
if (labels.length() > 0) {
final QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(labels);
while (tokenizer.hasMoreTokens())
r.add(Jenkins.get().getLabelAtom(tokenizer.nextToken()));
Jenkins j = Jenkins.get();
LabelAtom labelAtom = j.tryGetLabelAtom(labels);
if (labelAtom == null) {
final QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(labels);
while (tokenizer.hasMoreTokens())
r.add(j.getLabelAtom(tokenizer.nextToken()));
} else {
r.add(labelAtom);
}
}
return r;
}

Expand Down
16 changes: 14 additions & 2 deletions core/src/main/java/hudson/model/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import net.sf.json.JSONObject;
import org.jvnet.localizer.Localizable;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.accmod.restrictions.ProtectedExternally;
import org.kohsuke.stapler.BindInterceptor;
import org.kohsuke.stapler.Stapler;
Expand Down Expand Up @@ -298,20 +299,31 @@ public OfflineCause getTemporaryOfflineCause() {
public TagCloud<LabelAtom> getLabelCloud() {
return new TagCloud<>(getAssignedLabels(), Label::getTiedJobCount);
}

/**
* @return An immutable set of LabelAtom associated with the current node label.
*/
@NonNull
@Restricted(NoExternalUse.class)
protected Set<LabelAtom> getLabelAtomSet() {
// Default implementation doesn't cache, since we can't hook on label updates.
return Collections.unmodifiableSet(Label.parse(getLabelString()));
}

/**
* Returns the possibly empty set of labels that are assigned to this node,
* including the automatic {@link #getSelfLabel() self label}, manually
* assigned labels and dynamically assigned labels via the
* {@link LabelFinder} extension point.
*
* This method has a side effect of updating the hudson-wide set of labels
* and should be called after events that will change that - e.g. a agent
* and should be called after events that will change that - e.g. an agent
* connecting.
*/

@Exported
public Set<LabelAtom> getAssignedLabels() {
Set<LabelAtom> r = Label.parse(getLabelString());
Set<LabelAtom> r = new HashSet<>(getLabelAtomSet());
r.add(getSelfLabel());
r.addAll(getDynamicLabels());
return Collections.unmodifiableSet(r);
Expand Down
21 changes: 19 additions & 2 deletions core/src/main/java/hudson/model/Slave.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import hudson.Util;
import hudson.cli.CLI;
import hudson.model.Descriptor.FormException;
import hudson.model.labels.LabelAtom;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Which;
Expand All @@ -60,6 +61,7 @@
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
Expand Down Expand Up @@ -179,6 +181,7 @@ protected Slave(@NonNull String name, String remoteFS, ComputerLauncher launcher
this.name = name;
this.remoteFS = remoteFS;
this.launcher = launcher;
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
}

/**
Expand All @@ -193,7 +196,7 @@ protected Slave(@NonNull String name, String nodeDescription, String remoteFS, i
this.numExecutors = numExecutors;
this.mode = mode;
this.remoteFS = Util.fixNull(remoteFS).trim();
this.label = Util.fixNull(labelString).trim();
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(labelString));
this.launcher = launcher;
this.retentionStrategy = retentionStrategy;
getAssignedLabels(); // compute labels now
Expand Down Expand Up @@ -328,11 +331,24 @@ public String getLabelString() {
@Override
@DataBoundSetter
public void setLabelString(String labelString) throws IOException {
this.label = Util.fixNull(labelString).trim();
_setLabelString(labelString);
// Compute labels now.
getAssignedLabels();
}

private void _setLabelString(String labelString) {
this.label = Util.fixNull(labelString).trim();
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
}

@NonNull
private transient Set<LabelAtom> labelAtomSet;

@Override
protected Set<LabelAtom> getLabelAtomSet() {
return labelAtomSet;
}

@Override
public Callable<ClockDifference, IOException> getClockDifferenceCallable() {
return new GetClockDifference1();
Expand Down Expand Up @@ -574,6 +590,7 @@ public int hashCode() {
protected Object readResolve() {
if (nodeProperties == null)
nodeProperties = new DescribableList<>(this);
_setLabelString(label);
return this;
}

Expand Down
22 changes: 17 additions & 5 deletions core/src/main/java/hudson/security/SecurityRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -647,15 +647,27 @@ public static String getFrom() {
from = request.getParameter("from");
}

// On the 404 error page, use the session attribute it sets
if (request != null && request.getRequestURI().equals(request.getContextPath() + "/404")) {
final HttpSession session = request.getSession(false);
if (session != null) {
final Object attribute = session.getAttribute("from");
if (attribute != null) {
from = attribute.toString();
}
}
}

// If entry point was not found, try to deduce it from the request URI
// except pages related to login process
// except pages related to login process and the 404 error page
if (from == null
&& request != null
&& request.getRequestURI() != null
&& !request.getRequestURI().equals("/loginError")
&& !request.getRequestURI().equals("/login")) {

from = request.getRequestURI();
// The custom login page makes the next two lines obsolete, but safer to have them.
&& !request.getRequestURI().equals(request.getContextPath() + "/loginError")
&& !request.getRequestURI().equals(request.getContextPath() + "/login")
&& !request.getRequestURI().equals(request.getContextPath() + "/404")) {
from = request.getRequestURI();
}

// If deduced entry point isn't deduced yet or the content is a blank value
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/jenkins/ErrorAttributeFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package jenkins;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.springframework.security.core.Authentication;

/**
* Record the current user authentication for later impersonation if the response is 404 Not Found.
*
* @see Jenkins#generateNotFoundResponse(org.kohsuke.stapler.StaplerRequest, org.kohsuke.stapler.StaplerResponse)
*/
@Restricted(NoExternalUse.class)
public class ErrorAttributeFilter implements Filter {

public static final String USER_ATTRIBUTE = "jenkins.ErrorAttributeFilter.user";

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final Authentication authentication = Jenkins.getAuthentication2();
servletRequest.setAttribute(USER_ATTRIBUTE, authentication);
filterChain.doFilter(servletRequest, servletResponse);
}
}
67 changes: 65 additions & 2 deletions core/src/main/java/jenkins/model/Jenkins.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import jenkins.AgentProtocol;
import jenkins.ErrorAttributeFilter;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
Expand All @@ -275,6 +276,7 @@
import jenkins.security.ConfidentialStore;
import jenkins.security.MasterToSlaveCallable;
import jenkins.security.RedactSecretJsonInErrorMessageSanitizer;
import jenkins.security.ResourceDomainConfiguration;
import jenkins.security.SecurityListener;
import jenkins.security.stapler.DoActionFilter;
import jenkins.security.stapler.StaplerDispatchValidator;
Expand Down Expand Up @@ -930,7 +932,7 @@ protected Jenkins(File root, ServletContext context, PluginManager pluginManager

Trigger.timer = new java.util.Timer("Jenkins cron thread");
queue = new Queue(LoadBalancer.CONSISTENT_HASH);

labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
try {
dependencyGraph = DependencyGraph.EMPTY;
} catch (InternalError e) {
Expand Down Expand Up @@ -1088,6 +1090,7 @@ protected Object readResolve() {
/* deserializing without a value set means we need to migrate */
nodeRenameMigrationNeeded = true;
}
_setLabelString(label);

return this;
}
Expand Down Expand Up @@ -1468,6 +1471,14 @@ public boolean hasPeople() {
}

public Api getApi() {
/* Do not show "REST API" link in footer when on 404 error page */
final StaplerRequest req = Stapler.getCurrentRequest();
if (req != null) {
final Object attribute = req.getAttribute("javax.servlet.error.message");
if (attribute != null) {
return null;
}
}
return new Api(this);
}

Expand Down Expand Up @@ -2114,6 +2125,20 @@ public Label getLabel(String expr) {
}
}

/**
* Returns the label atom of the given name, only if it already exists.
* @return non-null if the label atom already exists.
*/
@Restricted(NoExternalUse.class)
public @Nullable LabelAtom tryGetLabelAtom(@NonNull String name) {
Label label = labels.get(name);
if (label instanceof LabelAtom) {
return (LabelAtom) label;
}
return null;
}


/**
* Gets all the active labels in the current system.
*/
Expand All @@ -2126,6 +2151,14 @@ public Set<Label> getLabels() {
return r;
}

@NonNull
private transient Set<LabelAtom> labelAtomSet;

@Override
protected Set<LabelAtom> getLabelAtomSet() {
return labelAtomSet;
}

public Set<LabelAtom> getLabelAtoms() {
Set<LabelAtom> r = new TreeSet<>();
for (Label l : labels.values()) {
Expand Down Expand Up @@ -3291,10 +3324,17 @@ public String getLabelString() {

@Override
public void setLabelString(String label) throws IOException {
this.label = label;
_setLabelString(label);
save();
}

private void _setLabelString(String label) {
this.label = label;
if (Jenkins.getInstanceOrNull() != null) { // avoid on unit tests
this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
}
}

@NonNull
@Override
public LabelAtom getSelfLabel() {
Expand Down Expand Up @@ -4540,6 +4580,26 @@ public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
}
}

/**
* Serve a custom 404 error page, configured in web.xml.
*/
@WebMethod(name = "404")
@Restricted(NoExternalUse.class)
public void generateNotFoundResponse(StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
if (ResourceDomainConfiguration.isResourceRequest(req)) {
rsp.forward(this, "_404_simple", req);
} else {
final Object attribute = req.getAttribute(ErrorAttributeFilter.USER_ATTRIBUTE);
if (attribute instanceof Authentication) {
try (ACLContext unused = ACL.as2((Authentication) attribute)) {
rsp.forward(this, "_404", req);
}
} else {
rsp.forward(this, "_404", req);
}
}
}

/**
* Queues up a safe restart of Jenkins.
* Builds that cannot continue while the controller is not running have to finish or pause before it can proceed.
Expand Down Expand Up @@ -5729,6 +5789,9 @@ public boolean shouldShowStackTrace() {
* <p>See also:{@link #getUnprotectedRootActions}.
*/
private static final Set<String> ALWAYS_READABLE_PATHS = new HashSet<>(Arrays.asList(
"404", // Web method
"_404", // .jelly
"_404_simple", // .jelly
"login", // .jelly
"loginError", // .jelly
"logout", // #doLogout
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/java/jenkins/security/ResourceDomainFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package jenkins.security;

import hudson.Extension;
import hudson.Functions;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
Expand All @@ -49,14 +50,14 @@ public class ResourceDomainFilter implements HttpServletFilter {

private static final Logger LOGGER = Logger.getLogger(ResourceDomainFilter.class.getName());

private static final Set<String> ALLOWED_PATHS = new HashSet<>(Arrays.asList("/" + ResourceDomainRootAction.URL, "/favicon.ico", "/favicon.svg", "/robots.txt"));
private static final Set<String> ALLOWED_PATHS = new HashSet<>(Arrays.asList("/" + ResourceDomainRootAction.URL, "/favicon.ico", "/favicon.svg", "/apple-touch-icon.png", "/mask-icon.svg", "/robots.txt", "/images/rage.svg"));
public static final String ERROR_RESPONSE = "Jenkins serves only static files on this domain.";

@Override
public boolean handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, ServletException {
if (ResourceDomainConfiguration.isResourceRequest(req)) {
String path = req.getPathInfo();
if (!path.startsWith("/" + ResourceDomainRootAction.URL + "/") && !ALLOWED_PATHS.contains(path)) {
if (!path.startsWith("/" + ResourceDomainRootAction.URL + "/") && !ALLOWED_PATHS.contains(path) && !isAllowedPathWithResourcePrefix(path)) {
LOGGER.fine(() -> "Rejecting request to " + req.getRequestURL() + " from " + req.getRemoteAddr() + " on resource domain");
rsp.sendError(404, ERROR_RESPONSE);
return true;
Expand All @@ -66,4 +67,7 @@ public boolean handle(HttpServletRequest req, HttpServletResponse rsp) throws IO
return false;
}

private static boolean isAllowedPathWithResourcePrefix(String path) {
return path.startsWith(Functions.getResourcePath()) && ALLOWED_PATHS.contains(path.substring(Functions.getResourcePath().length()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
@Restricted(NoExternalUse.class)
public class ResourceDomainRootAction implements UnprotectedRootAction {

private static final String RESOURCE_DOMAIN_ROOT_ACTION_ERROR = "jenkins.security.ResourceDomainRootAction.error";

private static final Logger LOGGER = Logger.getLogger(ResourceDomainRootAction.class.getName());

public static final String URL = "static-files";
Expand Down Expand Up @@ -104,12 +106,14 @@ public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException
if (ResourceDomainConfiguration.isResourceRequest(req)) {
rsp.sendError(404, ResourceDomainFilter.ERROR_RESPONSE);
} else {
req.setAttribute(RESOURCE_DOMAIN_ROOT_ACTION_ERROR, true);
rsp.sendError(404, "Cannot handle requests to this URL unless on Jenkins resource URL.");
}
}

public Object getDynamic(String id, StaplerRequest req, StaplerResponse rsp) throws Exception {
if (!ResourceDomainConfiguration.isResourceRequest(req)) {
req.setAttribute(RESOURCE_DOMAIN_ROOT_ACTION_ERROR, true);
rsp.sendError(404, "Cannot handle requests to this URL unless on Jenkins resource URL.");
return null;
}
Expand Down
Loading

0 comments on commit 5f08c6c

Please sign in to comment.