提交 47f38d71 编写于 作者: D Daniel Beck

[SECURITY-595]

Co-Authored-By: NWadeck Follonier <wadeck.follonier@gmail.com>
上级 73afa0ca
......@@ -39,7 +39,7 @@ THE SOFTWARE.
<properties>
<staplerFork>true</staplerFork>
<stapler.version>1.254.2</stapler.version>
<stapler.version>1.254.3</stapler.version>
<spring.version>2.5.6.SEC03</spring.version>
<groovy.version>2.4.11</groovy.version>
<!-- TODO: Actually many issues are being filtered by src/findbugs/findbugs-excludes.xml -->
......@@ -179,6 +179,11 @@ THE SOFTWARE.
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jenkins.stapler</groupId>
<artifactId>jenkins-stapler-support</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
......
......@@ -50,6 +50,7 @@ import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.JenkinsJVM;
import jenkins.util.SystemProperties;
import org.apache.commons.httpclient.Credentials;
......@@ -78,6 +79,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
*
* @see jenkins.model.Jenkins#proxy
*/
@StaplerAccessibleType
public final class ProxyConfiguration extends AbstractDescribableImpl<ProxyConfiguration> implements Saveable, Serializable {
/**
* Holds a default TCP connect timeout set on all connections returned from this class,
......
......@@ -34,6 +34,7 @@ import javax.annotation.Nullable;
import hudson.model.AperiodicWork;
import jenkins.model.Jenkins;
import jenkins.model.identity.InstanceIdentityProvider;
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.slaves.RemotingVersionInfo;
import jenkins.util.SystemProperties;
import hudson.slaves.OfflineCause;
......@@ -82,6 +83,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
* @author Kohsuke Kawaguchi
* @see AgentProtocol
*/
@StaplerAccessibleType
public final class TcpSlaveAgentListener extends Thread {
private final ServerSocketChannel serverSocket;
......
......@@ -26,6 +26,7 @@ package hudson.diagnosis;
import hudson.Extension;
import hudson.Util;
import hudson.model.AdministrativeMonitor;
import jenkins.security.stapler.StaplerDispatchable;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
......@@ -70,6 +71,7 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
return new HttpRedirect(redirect);
}
@StaplerDispatchable
public void getTestForReverseProxySetup(String rest) {
Jenkins j = Jenkins.getInstance();
String inferred = j.getRootUrlFromRequest() + "manage";
......
......@@ -30,6 +30,7 @@ import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher.ProcStarter;
import hudson.slaves.Cloud;
import jenkins.security.stapler.StaplerDispatchable;
import jenkins.util.SystemProperties;
import hudson.Util;
import hudson.cli.declarative.CLIResolver;
......@@ -958,6 +959,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* Gets the read-only snapshot view of all {@link Executor}s.
*/
@Exported
@StaplerDispatchable
public List<Executor> getExecutors() {
return new ArrayList<Executor>(executors);
}
......@@ -966,6 +968,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* Gets the read-only snapshot view of all {@link OneOffExecutor}s.
*/
@Exported
@StaplerDispatchable
public List<OneOffExecutor> getOneOffExecutors() {
return new ArrayList<OneOffExecutor>(oneOffExecutors);
}
......
......@@ -23,6 +23,8 @@
*/
package hudson.model;
import jenkins.security.stapler.StaplerAccessibleType;
/**
* A model object has a human readable name.
*
......@@ -32,6 +34,7 @@ package hudson.model;
*
* @author Kohsuke Kawaguchi
*/
@StaplerAccessibleType
public interface ModelObject {
String getDisplayName();
}
......@@ -39,6 +39,7 @@ import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerAccessibleType;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
......@@ -75,6 +76,7 @@ import org.kohsuke.stapler.export.ExportedBean;
* @see ParametersAction
*/
@ExportedBean(defaultVisibility=3)
@StaplerAccessibleType
public abstract class ParameterValue implements Serializable {
private static final Logger LOGGER = Logger.getLogger(ParameterValue.class.getName());
......
......@@ -69,6 +69,7 @@ import java.nio.file.Files;
import hudson.util.Futures;
import jenkins.security.QueueItemAuthenticatorProvider;
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.SystemProperties;
import jenkins.util.Timer;
import hudson.triggers.SafeTimerTask;
......@@ -1993,6 +1994,7 @@ public class Queue extends ResourceController implements Saveable {
* Implementation must have <tt>executorCell.jelly</tt>, which is
* used to render the HTML that indicates this executable is executing.
*/
@StaplerAccessibleType
public interface Executable extends Runnable {
/**
* Task from which this executable was created.
......
......@@ -33,6 +33,8 @@ import hudson.ProxyConfiguration;
import hudson.security.ACLContext;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import jenkins.security.stapler.StaplerDispatchable;
import jenkins.util.SystemProperties;
import hudson.Util;
import hudson.XmlFile;
......@@ -317,6 +319,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
* can be empty but never null. Oldest entries first.
*/
@Exported
@StaplerDispatchable
public List<UpdateCenterJob> getJobs() {
synchronized (jobs) {
return new ArrayList<UpdateCenterJob>(jobs);
......@@ -517,6 +520,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
* @return
* can be empty but never null.
*/
@StaplerDispatchable // referenced by _api.jelly
public PersistedList<UpdateSite> getSites() {
return sites;
}
......
......@@ -63,6 +63,7 @@ import jenkins.model.item_category.Categories;
import jenkins.model.item_category.Category;
import jenkins.model.item_category.ItemCategory;
import jenkins.scm.RunWithSCM;
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.ProgressiveRendering;
import jenkins.util.xml.XMLUtils;
......@@ -700,6 +701,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
}
@ExportedBean
@StaplerAccessibleType
public static final class People {
@Exported
public final List<UserInfo> users;
......
......@@ -36,6 +36,7 @@ import java.util.Collections;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerAccessibleType;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
......@@ -62,6 +63,7 @@ import org.kohsuke.stapler.StaplerRequest;
* @author Kohsuke Kawaguchi
* @see SecurityRealm
*/
@StaplerAccessibleType
public abstract class AuthorizationStrategy extends AbstractDescribableImpl<AuthorizationStrategy> implements ExtensionPoint {
/**
* Returns the instance of {@link ACL} where all the other {@link ACL} instances
......
......@@ -9,6 +9,7 @@ import javax.servlet.ServletRequest;
import hudson.init.Initializer;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerAccessibleType;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebApp;
......@@ -40,6 +41,7 @@ import org.kohsuke.stapler.StaplerResponse;
* @see <a href="http://en.wikipedia.org/wiki/XSRF">Wikipedia: Cross site request forgery</a>
*/
@ExportedBean
@StaplerAccessibleType
public abstract class CrumbIssuer implements Describable<CrumbIssuer>, ExtensionPoint {
private static final String CRUMB_ATTRIBUTE = CrumbIssuer.class.getName() + "_crumb";
......
......@@ -11,6 +11,7 @@ import java.nio.file.InvalidPathException;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerDispatchable;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
......@@ -94,6 +95,7 @@ public class HsErrPidList extends AdministrativeMonitor {
/**
* Expose files to the URL.
*/
@StaplerDispatchable
public List<HsErrPidFile> getFiles() {
return files;
}
......
......@@ -32,6 +32,7 @@ import hudson.ExtensionPoint;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.security.stapler.StaplerAccessibleType;
import org.apache.commons.lang.StringUtils;
/**
* Jenkins install state.
......@@ -44,6 +45,7 @@ import org.apache.commons.lang.StringUtils;
*
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
@StaplerAccessibleType
public class InstallState implements ExtensionPoint {
/**
* Need InstallState != NEW for tests by default
......
......@@ -37,7 +37,11 @@ import hudson.*;
import hudson.Launcher.LocalLauncher;
import jenkins.AgentProtocol;
import jenkins.diagnostics.URICheckEncodingMonitor;
import jenkins.security.stapler.DoActionFilter;
import jenkins.security.stapler.StaplerFilteredActionListener;
import jenkins.security.stapler.StaplerDispatchable;
import jenkins.security.RedactSecretJsonInErrorMessageSanitizer;
import jenkins.security.stapler.TypedFilter;
import jenkins.util.SystemProperties;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
......@@ -895,6 +899,16 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
webApp.setClassLoader(pluginManager.uberClassLoader);
webApp.setJsonInErrorMessageSanitizer(RedactSecretJsonInErrorMessageSanitizer.INSTANCE);
TypedFilter typedFilter = new TypedFilter();
webApp.setFilterForGetMethods(typedFilter);
webApp.setFilterForFields(typedFilter);
webApp.setFilterForDoActions(new DoActionFilter());
StaplerFilteredActionListener actionListener = new StaplerFilteredActionListener();
webApp.setFilteredGetterTriggerListener(actionListener);
webApp.setFilteredDoActionTriggerListener(actionListener);
webApp.setFilteredFieldTriggerListener(actionListener);
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit.DAYS.toMillis(365));
ClassFilterImpl.register();
......@@ -1643,6 +1657,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
save();
}
@StaplerDispatchable
public FederatedLoginService getFederatedLoginService(String name) {
for (FederatedLoginService fls : FederatedLoginService.all()) {
if (fls.getUrlName().equals(name))
......@@ -2601,6 +2616,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*
* @since 1.349
*/
@StaplerDispatchable
public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
}
......@@ -2970,6 +2986,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
// if no finger print matches, display "not found page".
@StaplerDispatchable
public Object getFingerprint( String md5sum ) throws IOException {
Fingerprint r = fingerprintMap.get(md5sum);
if(r==null) return new NoFingerprintMatch(md5sum);
......@@ -4040,6 +4057,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* End point that intentionally throws an exception to test the error behaviour.
* @since 1.467
*/
@StaplerDispatchable
public void doException() {
throw new RuntimeException();
}
......@@ -4588,6 +4606,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* Plugins who wish to contribute boxes on the side panel can add widgets
* by {@code getWidgets().add(new MyWidget())} from {@link Plugin#start()}.
*/
@StaplerDispatchable // some plugins use this to add views to widgets
public List<Widget> getWidgets() {
return widgets;
}
......
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import hudson.ExtensionList;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.FunctionList;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@Restricted(NoExternalUse.class)
public class DoActionFilter implements FunctionList.Filter {
private static final Logger LOGGER = Logger.getLogger(DoActionFilter.class.getName());
/**
* if a method has "do" as name (not possible in pure Java but doable in Groovy or other JVM languages)
* the new system does not consider it as a web method.
* <p>
* Use <code>@WebMethod(name="")</code> or <code>doIndex</code> in such case.
*/
private static final Pattern DO_METHOD_REGEX = Pattern.compile("^do[^a-z].*");
public boolean keep(@Nonnull Function m) {
if (m.getAnnotation(StaplerNotDispatchable.class) != null) {
return false;
}
if (m.getAnnotation(StaplerDispatchable.class) != null) {
return true;
}
String methodName = m.getName();
String signature = m.getSignature();
// check whitelist
ExtensionList<RoutingDecisionProvider> whitelistProviders = ExtensionList.lookup(RoutingDecisionProvider.class);
if (whitelistProviders.size() > 0) {
for (RoutingDecisionProvider provider : whitelistProviders) {
RoutingDecisionProvider.Decision methodDecision = provider.decide(signature);
if (methodDecision == RoutingDecisionProvider.Decision.ACCEPTED) {
LOGGER.log(Level.CONFIG, "Action " + signature + " is acceptable because it is whitelisted by " + provider);
return true;
}
if (methodDecision == RoutingDecisionProvider.Decision.REJECTED) {
LOGGER.log(Level.CONFIG, "Action " + signature + " is not acceptable because it is blacklisted by " + provider);
return false;
}
}
}
if (methodName.equals("doDynamic")) {
// reject doDynamic because it's treated separately by Stapler.
return false;
}
for (Annotation a : m.getAnnotations()) {
if (WebMethodConstants.WEB_METHOD_ANNOTATION_NAMES.contains(a.annotationType().getName())) {
return true;
}
if (a.annotationType().getAnnotation(InterceptorAnnotation.class) != null) {
// This is a Stapler interceptor annotation like RequirePOST or JsonResponse
return true;
}
}
// there is rarely more than two annotations in a method signature
for (Annotation[] perParameterAnnotation : m.getParameterAnnotations()) {
for (Annotation annotation : perParameterAnnotation) {
if (WebMethodConstants.WEB_METHOD_PARAMETER_ANNOTATION_NAMES.contains(annotation.annotationType().getName())) {
return true;
}
}
}
if (!DO_METHOD_REGEX.matcher(methodName).matches()) {
return false;
}
// after the method name check to avoid allowing methods that are meant to be used by routable ones
// normally they should be private in such case
for (Class<?> parameterType : m.getParameterTypes()) {
if (WebMethodConstants.WEB_METHOD_PARAMETERS_NAMES.contains(parameterType.getName())) {
return true;
}
}
Class<?> returnType = m.getReturnType();
if (HttpResponse.class.isAssignableFrom(returnType)) {
return true;
}
// as HttpResponseException inherits from RuntimeException,
// there is no requirement for the developer to explicitly checks it.
Class<?>[] checkedExceptionTypes = m.getCheckedExceptionTypes();
for (Class<?> checkedExceptionType : checkedExceptionTypes) {
if (HttpResponse.class.isAssignableFrom(checkedExceptionType)) {
return true;
}
}
return false;
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import hudson.ExtensionPoint;
import javax.annotation.Nonnull;
public abstract class RoutingDecisionProvider implements ExtensionPoint {
enum Decision {
ACCEPTED,
REJECTED,
UNKNOWN
}
@Nonnull public abstract Decision decide(@Nonnull String signature);
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.event.FilteredDoActionTriggerListener;
import org.kohsuke.stapler.event.FilteredFieldTriggerListener;
import org.kohsuke.stapler.event.FilteredGetterTriggerListener;
import org.kohsuke.stapler.lang.FieldRef;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Log a warning message when a "getter" or "doAction" function that was filtered out by SECURITY-400 new rules
*/
@Restricted(NoExternalUse.class)
public class StaplerFilteredActionListener implements FilteredDoActionTriggerListener, FilteredGetterTriggerListener, FilteredFieldTriggerListener {
private static final Logger LOGGER = Logger.getLogger(StaplerFilteredActionListener.class.getName());
private static final String LOG_MESSAGE = "New Stapler routing rules result in the URL \"{0}\" no longer being allowed. " +
"If you consider it safe to use, add the following to the whitelist: \"{1}\". " +
"Learn more: https://jenkins.io/redirect/stapler-routing";
@Override
public boolean onDoActionTrigger(Function f, StaplerRequest req, StaplerResponse rsp, Object node) {
LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature()
});
return false;
}
@Override
public boolean onGetterTrigger(Function f, StaplerRequest req, StaplerResponse rsp, Object node, String expression) {
LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature()
});
return false;
}
@Override
public boolean onFieldTrigger(FieldRef f, StaplerRequest req, StaplerResponse staplerResponse, Object node, String expression) {
LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature()
});
return false;
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import com.google.common.annotations.VisibleForTesting;
import hudson.BulkChange;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.model.Saveable;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.lang.FieldRef;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Fill the list of getter methods that are whitelisted for Stapler
* Each item in the set are formatted to correspond exactly to what {@link Function#getDisplayName()} returns
*/
@Restricted(NoExternalUse.class)
@Extension
public class StaticRoutingDecisionProvider extends RoutingDecisionProvider implements Saveable {
private static final Logger LOGGER = Logger.getLogger(StaticRoutingDecisionProvider.class.getName());
private Set<String> whitelistSignaturesFromFixedList;
private Set<String> whitelistSignaturesFromUserControlledList;
private Set<String> blacklistSignaturesFromFixedList;
private Set<String> blacklistSignaturesFromUserControlledList;
public StaticRoutingDecisionProvider() {
reload();
}
/**
* Return the singleton instance of this class, typically for script console use
*/
public static StaticRoutingDecisionProvider get() {
return ExtensionList.lookupSingleton(StaticRoutingDecisionProvider.class);
}
/**
* @see Function#getSignature()
* @see FieldRef#getSignature()
*/
@Nonnull
public synchronized Decision decide(@Nonnull String signature) {
if (whitelistSignaturesFromFixedList == null || whitelistSignaturesFromUserControlledList == null ||
blacklistSignaturesFromFixedList == null || blacklistSignaturesFromUserControlledList == null) {
reload();
}
LOGGER.log(Level.CONFIG, "Checking whitelist for " + signature);
// priority to blacklist
if (blacklistSignaturesFromFixedList.contains(signature) || blacklistSignaturesFromUserControlledList.contains(signature)) {
return Decision.REJECTED;
}
if (whitelistSignaturesFromFixedList.contains(signature) || whitelistSignaturesFromUserControlledList.contains(signature)) {
return Decision.ACCEPTED;
}
return Decision.UNKNOWN;
}
public synchronized void reload() {
reloadFromDefault();
reloadFromUserControlledList();
resetMetaClassCache();
}
@VisibleForTesting
synchronized void resetAndSave(){
this.whitelistSignaturesFromFixedList = new HashSet<>();
this.whitelistSignaturesFromUserControlledList = new HashSet<>();
this.blacklistSignaturesFromFixedList = new HashSet<>();
this.blacklistSignaturesFromUserControlledList = new HashSet<>();
this.save();
}
private void resetMetaClassCache() {
// to allow the change to be effective, i.e. rebuild the MetaClass using the new whitelist
WebApp.get(Jenkins.get().servletContext).clearMetaClassCache();
}
private synchronized void reloadFromDefault() {
try (InputStream is = StaticRoutingDecisionProvider.class.getResourceAsStream("default-whitelist.txt")) {
whitelistSignaturesFromFixedList = new HashSet<>();
blacklistSignaturesFromFixedList = new HashSet<>();
parseFileIntoList(
IOUtils.readLines(is, StandardCharsets.UTF_8),
whitelistSignaturesFromFixedList,
blacklistSignaturesFromFixedList
);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
LOGGER.log(Level.FINE, "Found {0} getter in the standard whitelist", whitelistSignaturesFromFixedList.size());
}
public synchronized StaticRoutingDecisionProvider add(@Nonnull String signature) {
if (this.whitelistSignaturesFromUserControlledList.add(signature)) {
LOGGER.log(Level.INFO, "Signature [{0}] added to the whitelist", signature);
save();
resetMetaClassCache();
} else {
LOGGER.log(Level.INFO, "Signature [{0}] was already present in the whitelist", signature);
}
return this;
}
public synchronized StaticRoutingDecisionProvider addBlacklistSignature(@Nonnull String signature) {
if (this.blacklistSignaturesFromUserControlledList.add(signature)) {
LOGGER.log(Level.INFO, "Signature [{0}] added to the blacklist", signature);
save();
resetMetaClassCache();
} else {
LOGGER.log(Level.INFO, "Signature [{0}] was already present in the blacklist", signature);
}
return this;
}
public synchronized StaticRoutingDecisionProvider remove(@Nonnull String signature) {
if (this.whitelistSignaturesFromUserControlledList.remove(signature)) {
LOGGER.log(Level.INFO, "Signature [{0}] removed from the whitelist", signature);
save();
resetMetaClassCache();
} else {
LOGGER.log(Level.INFO, "Signature [{0}] was not present in the whitelist", signature);
}
return this;
}
public synchronized StaticRoutingDecisionProvider removeBlacklistSignature(@Nonnull String signature) {
if (this.blacklistSignaturesFromUserControlledList.remove(signature)) {
LOGGER.log(Level.INFO, "Signature [{0}] removed from the blacklist", signature);
save();
resetMetaClassCache();
} else {
LOGGER.log(Level.INFO, "Signature [{0}] was not present in the blacklist", signature);
}
return this;
}
/**
* Saves the configuration info to the disk.
*/
public synchronized void save() {
if (BulkChange.contains(this)) {
return;
}
File file = getConfigFile();
try {
List<String> allSignatures = new ArrayList<>(whitelistSignaturesFromUserControlledList);
blacklistSignaturesFromUserControlledList.stream()
.map(signature -> "!" + signature)
.forEach(allSignatures::add);
FileUtils.writeLines(file, allSignatures);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to save " + file.getAbsolutePath(), e);
}
}
/**
* Loads the data from the disk into this object.
*
* <p>
* The constructor of the derived class must call this method.
* (If we do that in the base class, the derived class won't
* get a chance to set default values.)
*/
private synchronized void reloadFromUserControlledList() {
File file = getConfigFile();
if (!file.exists()) {
if ((whitelistSignaturesFromUserControlledList != null && whitelistSignaturesFromUserControlledList.isEmpty()) ||
(blacklistSignaturesFromUserControlledList != null && blacklistSignaturesFromUserControlledList.isEmpty())) {
LOGGER.log(Level.INFO, "No whitelist source file found at " + file + " so resetting user-controlled whitelist");
}
whitelistSignaturesFromUserControlledList = new HashSet<>();
blacklistSignaturesFromUserControlledList = new HashSet<>();
return;
}
LOGGER.log(Level.INFO, "Whitelist source file found at " + file);
try {
whitelistSignaturesFromUserControlledList = new HashSet<>();
blacklistSignaturesFromUserControlledList = new HashSet<>();
parseFileIntoList(
FileUtils.readLines(file, StandardCharsets.UTF_8),
whitelistSignaturesFromUserControlledList,
blacklistSignaturesFromUserControlledList
);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to load " + file.getAbsolutePath(), e);
}
}
private File getConfigFile() {
return new File(WHITELIST_PATH == null ? new File(Jenkins.get().getRootDir(), "stapler-whitelist.txt").toString() : WHITELIST_PATH);
}
private void parseFileIntoList(List<String> lines, Set<String> whitelist, Set<String> blacklist){
lines.stream()
.filter(line -> !line.matches("#.*|\\s*"))
.forEach(line -> {
if (line.startsWith("!")) {
String withoutExclamation = line.substring(1);
if (!withoutExclamation.isEmpty()) {
blacklist.add(withoutExclamation);
}
} else {
whitelist.add(line);
}
});
}
/** Allow script console access */
public static String WHITELIST_PATH = SystemProperties.getString(StaticRoutingDecisionProvider.class.getName() + ".whitelist");
}
package jenkins.security.stapler;
import hudson.ExtensionList;
import jenkins.util.SystemProperties;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.FunctionList;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerOverridable;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
import org.kohsuke.stapler.lang.FieldRef;
import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@Restricted(NoExternalUse.class)
public class TypedFilter implements FieldRef.Filter, FunctionList.Filter {
private static final Logger LOGGER = Logger.getLogger(TypedFilter.class.getName());
private static final Map<Class<?>, Boolean> staplerCache = new HashMap<>();
private boolean isClassAcceptable(Class<?> clazz) {
if (clazz.isArray()) {
// special case to allow klass.isArray() dispatcher
Class<?> elementClazz = clazz.getComponentType();
// does not seem possible to fall in an infinite loop since array cannot be recursively defined
if (isClassAcceptable(elementClazz)) {
LOGGER.log(Level.FINE,
"Class {0} is acceptable because it is an Array of acceptable elements {1}",
new Object[]{clazz.getName(), elementClazz.getName()}
);
return true;
} else {
LOGGER.log(Level.FINE,
"Class {0} is not acceptable because it is an Array of non-acceptable elements {1}",
new Object[]{clazz.getName(), elementClazz.getName()}
);
return false;
}
}
return SKIP_TYPE_CHECK || isStaplerRelevantCached(clazz);
}
private static boolean isStaplerRelevantCached(@Nonnull Class<?> clazz) {
if (staplerCache.containsKey(clazz)) {
return staplerCache.get(clazz);
}
boolean ret = isStaplerRelevant(clazz);
staplerCache.put(clazz, ret);
return ret;
}
@Restricted(NoExternalUse.class)
public static boolean isStaplerRelevant(@Nonnull Class<?> clazz) {
return isSpecificClassStaplerRelevant(clazz) || isSuperTypesStaplerRelevant(clazz);
}
private static boolean isSuperTypesStaplerRelevant(@Nonnull Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && isStaplerRelevantCached(superclass)) {
return true;
}
for (Class<?> interfaceClass : clazz.getInterfaces()) {
if (isStaplerRelevantCached(interfaceClass)) {
return true;
}
}
return false;
}
private static boolean isSpecificClassStaplerRelevant(@Nonnull Class<?> clazz) {
if (clazz.isAnnotationPresent(StaplerAccessibleType.class)) {
return true;
}
// Classes implementing these Stapler types can be considered routable
if (StaplerProxy.class.isAssignableFrom(clazz)) {
return true;
}
if (StaplerFallback.class.isAssignableFrom(clazz)) {
return true;
}
if (StaplerOverridable.class.isAssignableFrom(clazz)) {
return true;
}
for (Method m : clazz.getMethods()) {
if (isRoutableMethod(m)) {
return true;
}
}
return false;
}
private static boolean isRoutableMethod(@Nonnull Method m) {
for (Annotation a : m.getDeclaredAnnotations()) {
if (WebMethodConstants.WEB_METHOD_ANNOTATION_NAMES.contains(a.annotationType().getName())) {
return true;
}
if (a.annotationType().isAnnotationPresent(InterceptorAnnotation.class)) {
// This is a Stapler interceptor annotation like RequirePOST or JsonResponse
return true;
}
}
for (Annotation[] set : m.getParameterAnnotations()) {
for (Annotation a : set) {
if (WebMethodConstants.WEB_METHOD_PARAMETER_ANNOTATION_NAMES.contains(a.annotationType().getName())) {
return true;
}
}
}
for (Class<?> parameterType : m.getParameterTypes()) {
if (WebMethodConstants.WEB_METHOD_PARAMETERS_NAMES.contains(parameterType.getName())) {
return true;
}
}
return WebApp.getCurrent().getFilterForDoActions().keep(new Function.InstanceFunction(m));
}
@Override
public boolean keep(@Nonnull FieldRef fieldRef) {
if (fieldRef.getAnnotation(StaplerNotDispatchable.class) != null) {
// explicitly marked as an invalid field
return false;
}
if (fieldRef.getAnnotation(StaplerDispatchable.class) != null) {
// explicitly marked as a valid field
return true;
}
String signature = fieldRef.getSignature();
// check whitelist
ExtensionList<RoutingDecisionProvider> decisionProviders = ExtensionList.lookup(RoutingDecisionProvider.class);
if (decisionProviders.size() > 0) {
for (RoutingDecisionProvider provider : decisionProviders) {
RoutingDecisionProvider.Decision fieldDecision = provider.decide(signature);
if (fieldDecision == RoutingDecisionProvider.Decision.ACCEPTED) {
LOGGER.log(Level.CONFIG, "Field {0} is acceptable because it is whitelisted by {1}", new Object[]{signature, provider});
return true;
}
if (fieldDecision == RoutingDecisionProvider.Decision.REJECTED) {
LOGGER.log(Level.CONFIG, "Field {0} is not acceptable because it is blacklisted by {1}", new Object[]{signature, provider});
return false;
}
Class<?> type = fieldRef.getReturnType();
if (type != null) {
String typeSignature = "class " + type.getCanonicalName();
RoutingDecisionProvider.Decision fieldTypeDecision = provider.decide(typeSignature);
if (fieldTypeDecision == RoutingDecisionProvider.Decision.ACCEPTED) {
LOGGER.log(Level.CONFIG, "Field {0} is acceptable because its type is whitelisted by {1}", new Object[]{signature, provider});
return true;
}
if (fieldTypeDecision == RoutingDecisionProvider.Decision.REJECTED) {
LOGGER.log(Level.CONFIG, "Field {0} is not acceptable because its type is blacklisted by {1}", new Object[]{signature, provider});
return false;
}
}
}
}
if (PROHIBIT_STATIC_ACCESS && fieldRef.isStatic()) {
// unless whitelisted or marked as routable, reject static fields
return false;
}
Class<?> returnType = fieldRef.getReturnType();
boolean isOk = isClassAcceptable(returnType);
LOGGER.log(Level.FINE, "Field analyzed: {0} => {1}", new Object[]{fieldRef.getName(), isOk});
return isOk;
}
@Override
public boolean keep(@Nonnull Function function) {
if (function.getAnnotation(StaplerNotDispatchable.class) != null) {
// explicitly marked as an invalid getter
return false;
}
if (function.getAnnotation(StaplerDispatchable.class) != null) {
// explicitly marked as a valid getter
return true;
}
String signature = function.getSignature();
// check whitelist
ExtensionList<RoutingDecisionProvider> decision = ExtensionList.lookup(RoutingDecisionProvider.class);
if (decision.size() > 0) {
for (RoutingDecisionProvider provider : decision) {
RoutingDecisionProvider.Decision methodDecision = provider.decide(signature);
if (methodDecision == RoutingDecisionProvider.Decision.ACCEPTED) {
LOGGER.log(Level.CONFIG, "Function {0} is acceptable because it is whitelisted by {1}", new Object[]{signature, provider});
return true;
}
if (methodDecision == RoutingDecisionProvider.Decision.REJECTED) {
LOGGER.log(Level.CONFIG, "Function {0} is not acceptable because it is blacklisted by {1}", new Object[]{signature, provider});
return false;
}
Class<?> type = function.getReturnType();
if (type != null) {
String typeSignature = "class " + type.getCanonicalName();
RoutingDecisionProvider.Decision returnTypeDecision = provider.decide(typeSignature);
if (returnTypeDecision == RoutingDecisionProvider.Decision.ACCEPTED) {
LOGGER.log(Level.CONFIG, "Function {0} is acceptable because its type is whitelisted by {1}", new Object[]{signature, provider});
return true;
}
if (returnTypeDecision == RoutingDecisionProvider.Decision.REJECTED) {
LOGGER.log(Level.CONFIG, "Function {0} is not acceptable because its type is blacklisted by {1}", new Object[]{signature, provider});
return false;
}
}
}
}
if (PROHIBIT_STATIC_ACCESS && function.isStatic()) {
// unless whitelisted or marked as routable, reject static methods
return false;
}
if (function.getName().equals("getDynamic")) {
Class[] parameterTypes = function.getParameterTypes();
if (parameterTypes.length > 0 && parameterTypes[0] == String.class) {
// While this is more general than what Stapler can invoke on these types,
// The above is the only criterion for Stapler to attempt dispatch.
// Therefore prohibit this as a regular getter.
return false;
}
}
if (function.getName().equals("getStaplerFallback") && function.getParameterTypes().length == 0) {
// A parameter-less #getStaplerFallback() implements special fallback behavior for the
// StaplerFallback interface. We do not check for the presence of the interface on the current
// class, or the return type, as that could change since the implementing component was last built.
return false;
}
if (function.getName().equals("getTarget") && function.getParameterTypes().length == 0) {
// A parameter-less #getTarget() implements special redirection behavior for the
// StaplerProxy interface. We do not check for the presence of the interface on the current
// class, or the return type, as that could change since the implementing component was last built.
return false;
}
Class<?> returnType = function.getReturnType();
boolean isOk = isClassAcceptable(returnType);
LOGGER.log(Level.FINE, "Function analyzed: {0} => {1}", new Object[]{signature, isOk});
return isOk;
}
@Restricted(NoExternalUse.class)
public static boolean SKIP_TYPE_CHECK = SystemProperties.getBoolean(TypedFilter.class.getName() + ".skipTypeCheck");
@Restricted(NoExternalUse.class)
public static boolean PROHIBIT_STATIC_ACCESS = SystemProperties.getBoolean(TypedFilter.class.getName() + ".prohibitStaticAccess", true);
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.Header;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;
import org.kohsuke.stapler.bind.JavaScriptMethod;
import org.kohsuke.stapler.json.JsonBody;
import org.kohsuke.stapler.json.SubmittedForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Restricted(NoExternalUse.class)
final class WebMethodConstants {
/**
* If a method has at least one of those parameters, it is considered as an implicit web method
*/
private static final List<Class<?>> WEB_METHOD_PARAMETERS = Collections.unmodifiableList(Arrays.asList(
StaplerRequest.class,
HttpServletRequest.class,
StaplerResponse.class,
HttpServletResponse.class
));
static final Set<String> WEB_METHOD_PARAMETERS_NAMES = Collections.unmodifiableSet(
WEB_METHOD_PARAMETERS.stream()
.map(Class::getName)
.collect(Collectors.toSet())
);
/**
* If a method is annotated with one of those annotations,
* the method is considered as an explicit web method
*/
static final List<Class<? extends Annotation>> WEB_METHOD_ANNOTATIONS = Collections.singletonList(
WebMethod.class
// plus every annotation that's annotated with InterceptorAnnotation
// JavaScriptMethod.class not taken here because it's a special case
);
static final Set<String> WEB_METHOD_ANNOTATION_NAMES;
static {
Set<String> webMethodAnnotationNames = WEB_METHOD_ANNOTATIONS.stream()
.map(Class::getName)
.collect(Collectors.toSet());
webMethodAnnotationNames.add(JavaScriptMethod.class.getName());
WEB_METHOD_ANNOTATION_NAMES = Collections.unmodifiableSet(webMethodAnnotationNames);
}
/**
* If at least one parameter of the method is annotated with one of those annotations,
* the method is considered as an implicit web method
*/
private static final List<Class<? extends Annotation>> WEB_METHOD_PARAMETER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(
QueryParameter.class,
AncestorInPath.class,
Header.class,
JsonBody.class,
SubmittedForm.class
));
static final Set<String> WEB_METHOD_PARAMETER_ANNOTATION_NAMES = Collections.unmodifiableSet(
WEB_METHOD_PARAMETER_ANNOTATIONS.stream()
.map(Class::getName)
.collect(Collectors.toSet())
);
}
# This file contains the built-in whitelist for Stapler request dispatching.
# It's a tool for retaining compatibility with unusual plugin behavior after introducing the SECURITY-595 security fix.
# To provide your own custom whitelist, create/edit $JENKINS_HOME/stapler-whitelist.txt
# Determine the whitelist entries for methods in a known class from the script console:
# com.acme.package.ClassName.class.methods.each {
# println new org.kohsuke.stapler.Function.InstanceFunction(it).signature
# }
# com.acme.package.ClassName.class.fields.each {
# println org.kohsuke.stapler.lang.FieldRef.wrap(it).signature
# }
# return
#######################################################################################################################
###################################################### Whitelist ######################################################
#######################################################################################################################
######################
# Credentials Plugin #
######################
# Used where credentials are used (e.g. SCM config), without this, 'Add' button will break as its dialog is at:
# /descriptor/….CredentialsSelectHelper/resolver/….CredentialsSelectHelper$SystemContextResolver/provider/….SystemCredentialsProvider$ProviderImpl/context/jenkins/dialog
method com.cloudbees.plugins.credentials.CredentialsSelectHelper getResolver java.lang.String
method com.cloudbees.plugins.credentials.CredentialsSelectHelper$WrappedContextResolver getProvider java.lang.String
# Used by Credentials Plugin's FingerprintTest and Git Plugin's GitSCMTest, as well as others:
method com.cloudbees.plugins.credentials.CredentialsStoreAction$DomainWrapper getCredentials
################
# JUnit Plugin #
################
# Allow various #getHistory() as these only have resources and #getGraph()
class hudson.tasks.junit.History
##################
# Metrics Plugin #
##################
# Method returns Object for no clear reason
method jenkins.metrics.api.MetricsRootAction getCurrentUser
#########################
# Pipeline Plugin Suite #
#########################
# Used in the 'Pipeline Steps' UI, the Execution has Nodes but no UI of its own
method org.jenkinsci.plugins.workflow.job.WorkflowRun getExecution
# FlowGraphTable only has a Jelly view, and nothing else that would indicate Stapler-routability
method org.jenkinsci.plugins.workflow.job.views.FlowGraphTableAction getFlowGraph
############################
# Maven Integration Plugin #
############################
# Advertised in https://github.com/jenkinsci/maven-plugin/blob/7ac83fa85fda0c4d1d02663059644f0655823879/src/main/resources/hudson/maven/reporters/MavenArtifactRecord/_api.jelly#L31
field hudson.maven.reporters.MavenArtifactRecord attachedArtifacts
###########################################
# Static Analysis Plugins (analysis-core) #
###########################################
# Methods return Object for no clear reason
method hudson.plugins.analysis.core.AbstractProjectAction getTrendGraph
method hudson.plugins.analysis.core.AbstractProjectAction getTrendDetails
method hudson.plugins.analysis.core.AbstractProjectAction getTrendDetails org.kohsuke.stapler.StaplerRequest org.kohsuke.stapler.StaplerResponse
###########################
# Blue Ocean Plugin Suite #
###########################
# Methods return Object for no clear reason
method io.jenkins.blueocean.service.embedded.rest.AbstractRunImpl getLog
method io.jenkins.blueocean.service.embedded.rest.QueuedBlueRun getLog
method io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeImpl getLog
method io.jenkins.blueocean.rest.impl.pipeline.PipelineStepImpl getLog
##########################
# Promoted Builds Plugin #
##########################
# Only subtypes look Stapler-relevant
method hudson.plugins.promoted_builds.PromotionProcess getPromotionCondition java.lang.String
##########################
# Robot Framework Plugin #
##########################
# Unsure whether this is needed, but RobotSuiteResult is supposed to be reachable.
method hudson.plugins.robot.model.RobotResult getSuites
########################
# Maven Invoker Plugin #
########################
# MavenInvokerResult only has an index view, and nothing else that would indicate Stapler-routability
method org.jenkinsci.plugins.maveninvoker.MavenInvokerBuildAction getResult java.lang.String
###########################
# Cloud Statistics Plugin #
###########################
# Used via CloudStatistics#getUrl(ProvisioningActivity, PhaseExecution, PhaseExecutionAttachment) for attempts.groovy
method org.jenkinsci.plugins.cloudstats.CloudStatistics getActivity java.lang.String
method org.jenkinsci.plugins.cloudstats.ProvisioningActivity getPhase java.lang.String
####################
# SLOCCount Plugin #
####################
# Support various getters in the same type
class hudson.plugins.sloccount.SloccountResult
##############################
# Project Inheritance Plugin #
##############################
# do* methods with no indication they're supposed to be routable (return String, no args, no annotations)
method hudson.plugins.project_inheritance.projects.InheritanceProject doGetParamDefaultsAsXML
method hudson.plugins.project_inheritance.projects.InheritanceProject doGetParamExpansionsAsXML
method hudson.plugins.project_inheritance.projects.InheritanceProject doGetVersionsAsCompressedXML
method hudson.plugins.project_inheritance.projects.InheritanceProject doGetVersionsAsXML
method hudson.plugins.project_inheritance.projects.InheritanceProject doRenderSVGRelationGraph
###############################################
# Changes since last successfull build Plugin # (sic)
###############################################
# Only has an index.jelly view, so needs to be explicit
class com.cloudbees.jenkins.plugins.changelog.Changes
###################
# Fitnesse Plugin #
###################
# return hudson.plugins.fitnesse.History with only getters for hudson.util.Graph, like analysis-core
method hudson.plugins.fitnesse.FitnesseProjectAction getTrend
###########################
# Build Time Blame Plugin #
###########################
# Only has resources and a getter for a Graph, so needs whitelisting when not scanning
class org.jenkins.ci.plugins.buildtimeblame.analysis.BlameReport
##########################
# Azure VM Agents Plugin #
##########################
# Declared to return String, no args, and always returns null…?
method com.microsoft.azure.vmagent.AzureVMAgentTemplate$DescriptorImpl doFillImageReferenceTypeItems
################################
# TestComplete support plug-in #
################################
method com.smartbear.jenkins.plugins.testcomplete.TcSummaryAction getReports
#####################
# Job Cacher Plugin #
#####################
# UI linking to this is ArbitraryFileCache/cache-entry.jelly, used from CacheProjectAction/index.jelly, and ultimately handled by ArbitraryFileCache#doDynamic
method jenkins.plugins.jobcacher.CacheProjectAction getCaches
#########################
# Dimensions SCM Plugin #
#########################
# Needs whitelisting due to not following the usual naming scheme
method hudson.plugins.dimensionsscm.DimensionsSCM$DescriptorImpl domanadatoryFieldCheck org.kohsuke.stapler.StaplerRequest org.kohsuke.stapler.StaplerResponse
##############################
# Google Health Check Plugin #
##############################
method com.google.jenkins.plugins.health.lib.DerivedPageAction getZone java.lang.String
package jenkins.security.stapler;
import org.junit.Assert;
import org.junit.Test;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.json.JsonResponse;
import org.kohsuke.stapler.lang.FieldRef;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class StaplerSignaturesTest {
@Test
public void testSignaturesSimple() throws Exception {
Set<String> methodSignatures = Arrays.stream(SomeClass.class.getMethods()).map(it -> new Function.InstanceFunction(it).getSignature()).collect(Collectors.toSet());
Assert.assertEquals(SomeClass.METHOD_SIGNATURES, methodSignatures);
Set<String> fieldSignatures = Arrays.stream(SomeClass.class.getFields()).map(it -> FieldRef.wrap(it).getSignature()).collect(Collectors.toSet());
Assert.assertEquals(SomeClass.FIELD_SIGNATURES, fieldSignatures);
}
@Test
public void testSignaturesInheritance() throws Exception {
Set<String> methodSignatures = Arrays.stream(SomeSubclass.class.getMethods()).map(it -> new Function.InstanceFunction(it).getSignature()).collect(Collectors.toSet());
Assert.assertEquals(SomeSubclass.METHOD_SIGNATURES, methodSignatures);
Set<String> fieldSignatures = Arrays.stream(SomeSubclass.class.getFields()).map(it -> FieldRef.wrap(it).getSignature()).collect(Collectors.toSet());
Assert.assertEquals(SomeSubclass.FIELD_SIGNATURES, fieldSignatures);
}
public static class SomeClass {
static Set<String> METHOD_SIGNATURES = new HashSet<>(Arrays.asList(
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo java.lang.String",
"staticMethod jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo int",
"staticMethod jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo long",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo jenkins.security.stapler.StaplerSignaturesTest$SomeClass",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass doFoo org.kohsuke.stapler.StaplerRequest org.kohsuke.stapler.StaplerResponse",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass doWhatever java.lang.String",
"method java.lang.Object getClass",
"method java.lang.Object equals java.lang.Object",
"method java.lang.Object hashCode",
"method java.lang.Object notify",
"method java.lang.Object notifyAll",
"method java.lang.Object toString",
"method java.lang.Object wait long int",
"method java.lang.Object wait long",
"method java.lang.Object wait"
));
public void getFoo() {}
public void getFoo(String arg) {}
public static void getFoo(int arg) {}
public static void getFoo(long arg) {}
public void getFoo(SomeClass arg) {}
public void doFoo(StaplerRequest req, StaplerResponse rsp) {}
@StaplerDispatchable @JsonResponse
public void doWhatever(@QueryParameter String arg) {}
static Set<String> FIELD_SIGNATURES = new HashSet<>(Arrays.asList(
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass whatever",
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass thing",
"staticField jenkins.security.stapler.StaplerSignaturesTest$SomeClass staticField",
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass stringList"
));
public String whatever;
public Object thing;
public static Object staticField;
public List<String> stringList;
}
public static class SomeSubclass extends SomeClass {
static Set<String> METHOD_SIGNATURES = new HashSet<>(Arrays.asList(
"method jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass getFoo",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass subtypeExclusive",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass subtypeExclusive java.lang.String",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass varargMethod [Ljava.lang.String;",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo java.lang.String",
"staticMethod jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo int",
"staticMethod jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo long",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass getFoo jenkins.security.stapler.StaplerSignaturesTest$SomeClass",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass doFoo org.kohsuke.stapler.StaplerRequest org.kohsuke.stapler.StaplerResponse",
"method jenkins.security.stapler.StaplerSignaturesTest$SomeClass doWhatever java.lang.String",
"method java.lang.Object getClass",
"method java.lang.Object equals java.lang.Object",
"method java.lang.Object hashCode",
"method java.lang.Object notify",
"method java.lang.Object notifyAll",
"method java.lang.Object toString",
"method java.lang.Object wait long int",
"method java.lang.Object wait long",
"method java.lang.Object wait"
));
public void getFoo() {}
public void subtypeExclusive(){}
public void subtypeExclusive(String arg){}
public void varargMethod(String... args){}
static Set<String> FIELD_SIGNATURES = new HashSet<>(Arrays.asList(
"field jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass whatever",
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass whatever",
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass thing",
"staticField jenkins.security.stapler.StaplerSignaturesTest$SomeSubclass staticField",
"staticField jenkins.security.stapler.StaplerSignaturesTest$SomeClass staticField",
"field jenkins.security.stapler.StaplerSignaturesTest$SomeClass stringList"
));
public String whatever;
public static Object staticField;
}
}
......@@ -68,6 +68,12 @@ THE SOFTWARE.
<version>2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>cloudbees-folder</artifactId>
<version>6.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>maven-plugin</artifactId>
......
......@@ -23,10 +23,13 @@
*/
package hudson.model;
import com.cloudbees.hudson.plugins.folder.Folder;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.DomNodeUtil;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import jenkins.model.Jenkins;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.jvnet.hudson.test.Issue;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
......@@ -194,6 +197,18 @@ public class ViewTest {
@Issue("JENKINS-9367")
@Test public void allImagesCanBeLoaded() throws Exception {
User.get("user", true);
// as long as the cloudbees-folder is included as test dependency, its Folder will load icon
boolean folderPluginActive = (j.jenkins.getPlugin("cloudbees-folder") != null);
// link to Folder class is done here to ensure if we remove the dependency, this code will fail and so will be removed
boolean folderPluginClassesLoaded = (j.jenkins.getDescriptor(Folder.class) != null);
// this could be written like this to avoid the hard dependency:
// boolean folderPluginClassesLoaded = (j.jenkins.getDescriptor("com.cloudbees.hudson.plugins.folder.Folder") != null);
if (!folderPluginActive && folderPluginClassesLoaded) {
// reset the icon added by Folder because the plugin resources are not reachable
IconSet.icons.addIcon(new Icon("icon-folder icon-md", "24x24/folder.gif", "width: 24px; height: 24px;"));
}
WebClient webClient = j.createWebClient();
webClient.getOptions().setJavaScriptEnabled(false);
j.assertAllImageLoadSuccessfully(webClient.goTo("asynchPeople"));
......
......@@ -59,7 +59,7 @@ public class FormFieldValidatorTest {
return true;
}
public void doCheckXyz() {
public FormValidation doCheckXyz() {
throw new Error("doCheckXyz is broken");
}
}
......
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import com.gargoylesoftware.htmlunit.Page;
import hudson.model.UnprotectedRootAction;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@Issue("SECURITY-400")
@For(RoutingDecisionProvider.class)
public class CustomRoutingDecisionProviderTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@TestExtension("customRoutingWhitelistProvider")
public static class XxxBlacklister extends RoutingDecisionProvider {
@Override
public Decision decide(@Nonnull String signature) {
if (signature.contains("xxx")) {
return Decision.REJECTED;
}
return Decision.UNKNOWN;
}
}
@TestExtension
public static class OneMethodIsBlacklisted implements UnprotectedRootAction {
@Override
public @CheckForNull String getUrlName() {
return "custom";
}
@Override
public String getDisplayName() {
return null;
}
@Override
public String getIconFileName() {
return null;
}
public StaplerAbstractTest.Renderable getLegitGetter() {
return new StaplerAbstractTest.Renderable();
}
public StaplerAbstractTest.Renderable getLegitxxxGetter() {
return new StaplerAbstractTest.Renderable();
}
}
private static class Renderable {
public void doIndex() {replyOk();}
@WebMethod(name = "valid")
public void valid() {replyOk();}
}
private static void replyOk() {
StaplerResponse resp = Stapler.getCurrentResponse();
try {
resp.getWriter().write("ok");
resp.flushBuffer();
} catch (IOException e) {}
}
@Test
public void customRoutingWhitelistProvider() throws Exception {
Page okPage = j.createWebClient().goTo("custom/legitGetter", null);
assertThat(okPage.getWebResponse().getStatusCode(), is(200));
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
Page errorPage = wc.goTo("custom/legitxxxGetter", null);
assertThat(errorPage.getWebResponse().getStatusCode(), is(404));
}
}
package jenkins.security.stapler;
import hudson.model.UnprotectedRootAction;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.annotation.CheckForNull;
import java.util.Arrays;
import java.util.stream.Stream;
@Issue("SECURITY-400")
public class DynamicTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
public void testRequestsDispatchedToEligibleDynamic() throws Exception {
JenkinsRule.WebClient wc = j.createWebClient();
Stream.of("whatever", "displayName", "iconFileName", "urlName", "response1", "response2").forEach(url ->
{
try {
assertThat(wc.goTo("root/" + url).getWebResponse().getContentAsString(), containsString(url));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@TestExtension
public static class Root implements UnprotectedRootAction {
@CheckForNull
@Override
public String getIconFileName() {
return null;
}
@CheckForNull
@Override
public String getDisplayName() {
return null;
}
@StaplerNotDispatchable
public HttpResponse getResponse1() {
return null;
}
@StaplerNotDispatchable
public HttpResponse doResponse2() {
return null;
}
public void doDynamic(StaplerRequest req) {
throw HttpResponses.errorWithoutStack(200, req.getRestOfPath());
}
@CheckForNull
@Override
public String getUrlName() {
return "root";
}
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import com.cloudbees.hudson.plugins.folder.Folder;
import hudson.model.TopLevelItem;
import hudson.model.View;
import jenkins.model.Jenkins;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.TestExtension;
import java.awt.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertFalse;
/**
* To check the previous behavior you can use:
* <pre>
* {@link org.kohsuke.stapler.MetaClass#LEGACY_GETTER_MODE} = true;
* </pre>
* It will disable the usage of {@link TypedFilter}
*/
@Issue("SECURITY-400")
@For(TypedFilter.class)
public class GetterMethodFilterTest extends StaplerAbstractTest {
@TestExtension
public static class TestWithReturnJavaPlatformObject extends AbstractUnprotectedRootAction {
public static boolean called = false;
public String getString() { return "a";}
// cannot provide side-effect since the String has no side-effect methods
public Object getObjectString() { return "a";}
// but it opens wide range of potentially dangerous classes
public Object getObjectCustom() {
return new Object() {
// in order to provide a web entry-point
public void doIndex() {
replyOk();
}
};
}
public Point getPoint() { return new Point(1, 2);}
public Point getPointCustomChild() {
return new Point() {
// in order to provide a web entry-point
public void doIndex() {
replyOk();
}
};
}
public Point getPointWithListener() {
return new Point() {
@Override
public double getX() {
// just to demonstrate the potential side-effect
called = true;
return super.getX();
}
};
}
}
@Test
public void testWithReturnJavaPlatformObject_string() throws Exception {
assertNotReachable("testWithReturnJavaPlatformObject/string/");
}
@Test
public void testWithReturnJavaPlatformObject_objectString() throws Exception {
assertNotReachable("testWithReturnJavaPlatformObject/objectString/");
}
@Test
public void testWithReturnJavaPlatformObject_objectCustom() throws Exception {
assertNotReachable("testWithReturnJavaPlatformObject/objectCustom/");
}
@Test
public void testWithReturnJavaPlatformObject_point() throws Exception {
assertNotReachable("testWithReturnJavaPlatformObject/point/");
}
// previously reachable and so potentially open to future security vulnerability
@Test
public void testWithReturnJavaPlatformObject_pointCustomChild() throws Exception {
assertNotReachable("testWithReturnJavaPlatformObject/pointCustomChild/");
}
@Test
public void testWithReturnJavaPlatformObject_pointWithListener() throws Exception {
TestWithReturnJavaPlatformObject.called = false;
assertFalse(TestWithReturnJavaPlatformObject.called);
// could potentially trigger some side-effects
assertNotReachable("testWithReturnJavaPlatformObject/pointWithListener/x/");
assertFalse(TestWithReturnJavaPlatformObject.called);
}
@TestExtension
public static class TestWithReturnMultiple extends AbstractUnprotectedRootAction {
public List<Renderable> getList() {
return Arrays.asList(new Renderable(), new Renderable());
}
// as we cannot determine the element class due to type erasure, this is reachable
public List<? extends Point> getListOfPoint() {
return Collections.singletonList(new RenderablePoint());
}
public List<List<Renderable>> getListOfList() {
return Collections.singletonList(Arrays.asList(new Renderable(), new Renderable()));
}
public Renderable[] getArray() { return new Renderable[]{new Renderable(), new Renderable()};}
// will not be accepted since the componentType is from JVM
public Point[] getArrayOfPoint() {
return new Point[]{new Point() {
public void doIndex() {replyOk();}
}};
}
public Renderable[][] getArrayOfArray() {
return new Renderable[][]{
new Renderable[]{new Renderable(), new Renderable()}
};
}
@SuppressWarnings("unchecked")
public List<Renderable>[] getArrayOfList() {
List<Renderable> list = Arrays.asList(new Renderable(), new Renderable());
return (List<Renderable>[]) Collections.singletonList(list).toArray(new List[0]);
}
public List<Renderable[]> getListOfArray() {
return Collections.singletonList(
new Renderable[]{new Renderable(), new Renderable()}
);
}
public Map<String, Renderable> getMap() {
return new HashMap<String, Renderable>() {{
put("a", new Renderable());
}};
}
}
@Test
public void testWithReturnMultiple_list() throws Exception {
assertNotReachable("testWithReturnMultiple/list/");
assertNotReachable("testWithReturnMultiple/list/0/");
assertNotReachable("testWithReturnMultiple/list/1/");
assertNotReachable("testWithReturnMultiple/list/2/");
}
@Test
public void testWithReturnMultiple_listOfPoint() throws Exception {
assertNotReachable("testWithReturnMultiple/listOfPoint/");
assertNotReachable("testWithReturnMultiple/listOfPoint/0/");
assertNotReachable("testWithReturnMultiple/listOfPoint/1/");
}
@Test
public void testWithReturnMultiple_listOfList() throws Exception {
assertNotReachable("testWithReturnMultiple/listOfList/");
assertNotReachable("testWithReturnMultiple/listOfList/0/");
assertNotReachable("testWithReturnMultiple/listOfList/1/");
assertNotReachable("testWithReturnMultiple/listOfList/0/0/");
assertNotReachable("testWithReturnMultiple/listOfList/0/1/");
assertNotReachable("testWithReturnMultiple/listOfList/0/2/");
}
@Test
public void testWithReturnMultiple_array() throws Exception {
assertNotReachable("testWithReturnMultiple/array/");
assertReachable("testWithReturnMultiple/array/0/");
assertReachable("testWithReturnMultiple/array/1/");
assertNotReachable("testWithReturnMultiple/array/2/");
}
@Test
public void testWithReturnMultiple_arrayOfPoint() throws Exception {
assertNotReachable("testWithReturnMultiple/arrayOfPoint/");
assertNotReachable("testWithReturnMultiple/arrayOfPoint/0/");
assertNotReachable("testWithReturnMultiple/arrayOfPoint/1/");
}
@Test
public void testWithReturnMultiple_arrayOfArray() throws Exception {
assertNotReachable("testWithReturnMultiple/arrayOfArray/");
assertNotReachable("testWithReturnMultiple/arrayOfArray/0/");
assertNotReachable("testWithReturnMultiple/arrayOfArray/1/");
assertReachable("testWithReturnMultiple/arrayOfArray/0/0/");
assertReachable("testWithReturnMultiple/arrayOfArray/0/1/");
assertNotReachable("testWithReturnMultiple/arrayOfArray/0/2/");
}
@Test
public void testWithReturnMultiple_arrayOfList() throws Exception {
assertNotReachable("testWithReturnMultiple/arrayOfList/");
assertNotReachable("testWithReturnMultiple/arrayOfList/0/");
assertNotReachable("testWithReturnMultiple/arrayOfList/1/");
assertNotReachable("testWithReturnMultiple/arrayOfList/0/0/");
assertNotReachable("testWithReturnMultiple/arrayOfList/0/1/");
assertNotReachable("testWithReturnMultiple/arrayOfList/0/2/");
}
@Test
public void testWithReturnMultiple_listOfArray() throws Exception {
assertNotReachable("testWithReturnMultiple/listOfArray/");
assertNotReachable("testWithReturnMultiple/listOfArray/0/");
assertNotReachable("testWithReturnMultiple/listOfArray/1/");
assertNotReachable("testWithReturnMultiple/listOfArray/0/0/");
assertNotReachable("testWithReturnMultiple/listOfArray/0/1/");
assertNotReachable("testWithReturnMultiple/listOfArray/0/2/");
}
@Test
public void testWithReturnMultiple_map() throws Exception {
assertNotReachable("testWithReturnMultiple/map/");
assertNotReachable("testWithReturnMultiple/map/a/");
assertNotReachable("testWithReturnMultiple/map/b/");
}
@TestExtension
public static class TestWithReturnCoreObject extends AbstractUnprotectedRootAction {
public View.People getPeople() {
// provide an index jelly view
return new View.People(Jenkins.getInstance());
}
}
@Test
public void testWithReturnCoreObject_people() throws Exception {
assertReachableWithoutOk("testWithReturnCoreObject/people/");
}
@Test
public void testTopLevelItemIsLegal() throws Exception {
TopLevelItem item = j.createFreeStyleProject();
assertReachableWithoutOk("job/" + item.getName());
}
@TestExtension
public static class TestWithReturnPluginObject extends AbstractUnprotectedRootAction {
public Folder getFolder() {
return new Folder(Jenkins.getInstance(), "testFolder");
}
}
@Test
public void testWithReturnPluginObject_folder() throws Exception {
// the search part is just to get something from the call
assertReachableWithoutOk("testWithReturnPluginObject/folder/search/suggest/?query=xxx");
}
// full package name just to be explicit
@TestExtension
public static class TestWithReturnThirdPartyObject extends AbstractUnprotectedRootAction {
public org.apache.commons.codec.binary.Base64 getBase64() {
return new org.apache.commons.codec.binary.Base64();
}
public org.apache.commons.codec.Encoder getEncoder() {
return new org.apache.commons.codec.binary.Base64();
}
public org.apache.commons.codec.Encoder getEncoderCustomChild() {
return new org.apache.commons.codec.Encoder() {
@Override
public Object encode(Object source) throws org.apache.commons.codec.EncoderException {
// it's not about implementation...
return null;
}
public void doIndex() {
// it's about sending a message
replyOk();
}
};
}
}
// the class itself was reachable but no more interaction are available and so return 404
@Test
public void testWithReturnThirdPartyObject_base32() throws Exception {
assertNotReachable("testWithReturnThirdPartyObject/base32/");
}
// the class itself was reachable but no more interaction are available and so return 404,
// in case there is some callable methods, we could create some side-effect even we got 404
@Test
public void testWithReturnThirdPartyObject_encoder() throws Exception {
assertNotReachable("testWithReturnThirdPartyObject/encoder/");
}
// as we add a entry-point in the class, now it can propose some interaction,
// dangerous behavior that is not prohibited
@Test
public void testWithReturnThirdPartyObject_encoderCustomChild() throws Exception {
assertNotReachable("testWithReturnThirdPartyObject/encoderCustomChild/");
}
//================================= getter methods with primitives =================================
@TestExtension
public static class TestWithReturnPrimitives extends AbstractUnprotectedRootAction {
public int getInteger() { return 1;}
public Integer getIntegerObject() { return 1;}
public long getLong() { return 1L;}
public Long getLongObject() { return 1L;}
public short getShort() { return (short) 1;}
public Short getShortObject() { return 1;}
public byte getByte() { return (byte) 1;}
public Byte getByteObject() { return (byte) 1;}
public boolean getBoolean() { return true;}
public Boolean getBooleanObject() { return Boolean.TRUE;}
public char getChar() { return 'a';}
public Character getCharObject() { return 'a';}
public float getFloat() { return 1.0f;}
public Float getFloatObject() { return 1.0f;}
public double getDouble() { return 1.0;}
public Double getDoubleObject() { return 1.0;}
public void getVoid() { }
public Void getVoidObject() { return null; }
}
@Test
public void testTestWithReturnPrimitives_integer() throws Exception {
assertNotReachable("testWithReturnPrimitives/integer/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_integerObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/integerObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_long() throws Exception {
assertNotReachable("testWithReturnPrimitives/long/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_longObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/longObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_short() throws Exception {
assertNotReachable("testWithReturnPrimitives/short/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_shortObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/shortObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_byte() throws Exception {
assertNotReachable("testWithReturnPrimitives/byte/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_byteObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/byteObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_boolean() throws Exception {
assertNotReachable("testWithReturnPrimitives/boolean/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_booleanObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/booleanObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_char() throws Exception {
assertNotReachable("testWithReturnPrimitives/char/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_charObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/charObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_float() throws Exception {
assertNotReachable("testWithReturnPrimitives/float/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_floatObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/floatObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_double() throws Exception {
assertNotReachable("testWithReturnPrimitives/double/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_doubleObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/doubleObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_void() throws Exception {
assertNotReachable("testWithReturnPrimitives/void/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
@Test
public void testTestWithReturnPrimitives_voidObject() throws Exception {
assertNotReachable("testWithReturnPrimitives/voidObject/");
assertGetMethodRequestWasBlockedAndResetFlag();
}
//================================= getter methods =================================
@TestExtension
public static class TestWithReturnWithinStaplerScope extends DoActionFilterTest.AbstractUnprotectedRootAction {
public Renderable getRenderable() { return new Renderable();}
}
@Test
public void testWithReturnWithinStaplerScope_renderable() throws Exception {
assertReachable("testWithReturnWithinStaplerScope/renderable/");
assertReachable("testWithReturnWithinStaplerScope/renderable/valid/");
}
}
package jenkins.security.stapler;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.WithPlugin;
@Issue("SECURITY-400")
@For({StaplerDispatchable.class, StaplerAccessibleType.class})
public class JenkinsSupportAnnotationsTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
@WithPlugin("annotations-test.hpi")
public void testPluginWithAnnotations() throws Exception {
// test fails if TypedFilter ignores @StaplerDispatchable
j.createWebClient().goTo("annotationsTest/whatever", "");
// test fails if TypedFilter ignores @StaplerAccessibleType
j.createWebClient().goTo("annotationsTest/transit/response", "");
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import org.junit.Ignore;
import org.junit.Test;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.List;
public class PreventRoutingTest extends StaplerAbstractTest {
@TestExtension
public static class TargetNull extends AbstractUnprotectedRootAction implements StaplerProxy {
@Override
public @CheckForNull String getUrlName() {
return "target-null";
}
@Override
public Object getTarget() {
// in case of null, it's "this" that is considered
return null;
}
public Renderable getLegitRoutable(){
return new Renderable();
}
}
@Test
// TODO un-ignore once we use a Stapler release with the fix for this
@Ignore("Does not behave as intended before https://github.com/stapler/stapler/pull/149")
public void getTargetNull_isNotRoutable() throws Exception {
assertNotReachable("target-null/legitRoutable");
}
@TestExtension
public static class TargetNewObject extends AbstractUnprotectedRootAction implements StaplerProxy {
@Override
public @CheckForNull String getUrlName() {
return "target-new-object";
}
@Override
public Object getTarget() {
// Object is not routable
return new Object();
}
public Renderable getLegitRoutable(){
return new Renderable();
}
}
@Test
public void getTargetNewObject_isNotRoutable() throws Exception {
assertNotReachable("target-new-object/legitRoutable");
}
@TestExtension
public static class NotARequest extends AbstractUnprotectedRootAction {
@Override
public @CheckForNull String getUrlName() {
return "not-a-request";
}
public Renderable getLegitRoutable(){
notStaplerGetter(this);
return new Renderable();
}
// just to validate it's ok
public Renderable getLegitRoutable2(){
return new Renderable();
}
}
private static void notStaplerGetter(@Nonnull Object o){
StaplerRequest req = Stapler.getCurrentRequest();
if (req != null) {
List<Ancestor> ancestors = req.getAncestors();
if (!ancestors.isEmpty() && ancestors.get(ancestors.size() - 1).getObject() == o) {
throw HttpResponses.notFound();
}
}
}
@Test
public void regularGetter_notARequest() throws Exception {
assertReachable("not-a-request/legitRoutable2");
assertNotReachable("not-a-request/legitRoutable");
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import hudson.model.UnprotectedRootAction;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.ClassRule;
import org.jvnet.hudson.test.JenkinsRule;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.WebMethod;
import javax.annotation.CheckForNull;
import java.awt.*;
import java.io.IOException;
import java.net.URL;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class StaplerAbstractTest {
@ClassRule
public static JenkinsRule rule = new JenkinsRule();
protected JenkinsRule j;
protected JenkinsRule.WebClient wc;
protected WebApp webApp;
protected static boolean filteredGetMethodTriggered = false;
protected static boolean filteredDoActionTriggered = false;
protected static boolean filteredFieldTriggered = false;
@Before
public void setUp() throws Exception {
j = rule;
j.jenkins.setCrumbIssuer(null);
wc = j.createWebClient();
this.webApp = (WebApp) j.jenkins.servletContext.getAttribute(WebApp.class.getName());
webApp.setFilteredGetterTriggerListener((f, req, rst, node, expression) -> {
filteredGetMethodTriggered = true;
return false;
});
webApp.setFilteredDoActionTriggerListener((f, req, rsp, node) -> {
filteredDoActionTriggered = true;
return false;
});
webApp.setFilteredFieldTriggerListener((f, req, rsp, node, expression) -> {
filteredFieldTriggered = true;
return false;
});
filteredGetMethodTriggered = false;
filteredDoActionTriggered = false;
filteredFieldTriggered = false;
}
//================================= utility class =================================
protected static class AbstractUnprotectedRootAction implements UnprotectedRootAction {
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public @CheckForNull String getUrlName() {
return StringUtils.uncapitalize(this.getClass().getSimpleName());
}
}
public static final String RENDERABLE_CLASS_SIGNATURE = "class jenkins.security.stapler.StaplerAbstractTest.Renderable";
protected static class Renderable {
public void doIndex() {replyOk();}
@WebMethod(name = "valid")
public void valid() {replyOk();}
}
protected static class ParentRenderable {
public Renderable getRenderable(){
return new Renderable();
}
}
protected static class RenderablePoint extends Point {
public void doIndex() {replyOk();}
}
//================================= utility methods =================================
protected static void replyOk() {
StaplerResponse resp = Stapler.getCurrentResponse();
try {
resp.getWriter().write("ok");
resp.flushBuffer();
} catch (IOException e) {}
}
//================================= testing methods =================================
protected void assertGetMethodRequestWasBlockedAndResetFlag() {
assertTrue("No get method request was blocked", filteredGetMethodTriggered);
filteredGetMethodTriggered = false;
}
protected void assertDoActionRequestWasBlockedAndResetFlag() {
assertTrue("No do action request was blocked", filteredDoActionTriggered);
filteredDoActionTriggered = false;
}
protected void assertFieldRequestWasBlockedAndResetFlag() {
assertTrue("No field request was blocked", filteredFieldTriggered);
filteredFieldTriggered = false;
}
protected void assertGetMethodActionRequestWasNotBlocked() {
assertFalse("There was at least one get method request that was blocked", filteredGetMethodTriggered);
}
protected void assertDoActionRequestWasNotBlocked() {
assertFalse("There was at least one do action request that was blocked", filteredDoActionTriggered);
}
protected void assertFieldRequestWasNotBlocked() {
assertFalse("There was at least one field request that was blocked", filteredFieldTriggered);
}
protected void assertReachable(String url, HttpMethod method) throws IOException {
try {
Page page = wc.getPage(new WebRequest(new URL(j.getURL(), url), method));
assertEquals(200, page.getWebResponse().getStatusCode());
assertThat(page.getWebResponse().getContentAsString(), startsWith("ok"));
assertDoActionRequestWasNotBlocked();
assertGetMethodActionRequestWasNotBlocked();
assertFieldRequestWasNotBlocked();
} catch (FailingHttpStatusCodeException e) {
fail("Url " + url + " should be reachable, received " + e.getMessage() + " (" + e.getStatusCode() + ") instead.");
}
}
protected void assertReachable(String url) throws IOException {
assertReachable(url, HttpMethod.GET);
}
protected void assertReachableWithSettings(WebRequest request) throws IOException {
Page page = wc.getPage(request);
assertEquals(200, page.getWebResponse().getStatusCode());
assertEquals("ok", page.getWebResponse().getContentAsString());
assertDoActionRequestWasNotBlocked();
}
protected void assertReachableWithoutOk(String url) throws IOException {
try {
Page page = wc.getPage(new URL(j.getURL(), url));
assertEquals(200, page.getWebResponse().getStatusCode());
} catch (FailingHttpStatusCodeException e) {
fail("Url " + url + " should be reachable, received " + e.getMessage() + " (" + e.getStatusCode() + ") instead.");
}
}
protected void assertNotReachable(String url) throws IOException {
try {
wc.getPage(new URL(j.getURL(), url));
fail("Url " + url + " is reachable but should not be, an not-found error is expected");
} catch (FailingHttpStatusCodeException e) {
assertEquals("Url " + url + " returns an error different from 404", 404, e.getResponse().getStatusCode());
}
}
}
package jenkins.security.stapler;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebMethod;
@Issue("SECURITY-400")
@For({StaplerDispatchable.class, StaplerNotDispatchable.class, DoActionFilter.class})
public class StaplerRoutableActionTest extends StaplerAbstractTest {
@TestExtension
public static class TestNewRulesRoutableAction extends AbstractUnprotectedRootAction {
// StaplerDispatchable is not enough, the method needs to have at least either a name starting with do* or a WebMethod annotation
@StaplerDispatchable
public void notDoName() { replyOk(); }
@StaplerDispatchable // could be used to indicate that's a web method, without having to use @WebMethod
public void doWebMethod1() { replyOk(); }
// without annotation, returnType, parameter, exception => not a web method
public void doWebMethod2() { replyOk(); }
public void doWebMethod3() throws HttpResponses.HttpResponseException {
replyOk();
}
public void doWebMethod4(StaplerRequest request) {
replyOk();
}
public void doWebMethod5(@QueryParameter String foo) {
replyOk();
}
}
@Test
public void testNewRulesRoutableAction_notDoName() throws Exception {
assertNotReachable("testNewRulesRoutableAction/notDoName/");
// not even considered as a blocked action because the filter is not even called, they are lacking do* or @WebMethod
// assertDoActionRequestWasBlockedAndResetFlag();
assertNotReachable("testNewRulesRoutableAction/tDoName/");
// assertDoActionRequestWasBlockedAndResetFlag();
}
@Test
public void testNewRulesRoutableAction_webMethod1() throws Exception {
assertReachable("testNewRulesRoutableAction/webMethod1/");
}
@Test
public void testNewRulesRoutableAction_webMethod3Through5() throws Exception {
assertReachable("testNewRulesRoutableAction/webMethod3/");
assertReachable("testNewRulesRoutableAction/webMethod4/");
assertReachable("testNewRulesRoutableAction/webMethod5/");
}
@Test
public void testNewRulesRoutableAction_webMethod2() throws Exception {
assertNotReachable("testNewRulesRoutableAction/webMethod2/");
assertDoActionRequestWasBlockedAndResetFlag();
}
@TestExtension
public static class TestNewRulesNonroutableAction extends AbstractUnprotectedRootAction {
@StaplerNotDispatchable
public void doWebMethod1() { replyOk(); }
@StaplerNotDispatchable
@WebMethod(name = "webMethod2")
public void doWebMethod2() { replyOk(); }
}
@Test
public void testNewRulesNonroutableAction_webMethod1() throws Exception {
assertNotReachable("testNewRulesNonroutableAction/webMethod1/");
assertDoActionRequestWasBlockedAndResetFlag();
}
@Test
public void testNewRulesNonroutableAction_webMethod2() throws Exception {
// priority of negative over positive
assertNotReachable("testNewRulesNonroutableAction/webMethod2/");
assertDoActionRequestWasBlockedAndResetFlag();
}
}
package jenkins.security.stapler;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.TestExtension;
@Issue("SECURITY-595")
@For({StaplerDispatchable.class, StaplerNotDispatchable.class, TypedFilter.class})
public class StaplerRoutableFieldTest extends StaplerAbstractTest {
@TestExtension
public static class TestRootAction extends AbstractUnprotectedRootAction {
@Override
public String getUrlName() {
return "test";
}
public Renderable renderableNotAnnotated = new Renderable();
public ParentRenderable parentRenderableNotAnnotated = new ParentRenderable();
public Object objectNotAnnotated = new Renderable();
@StaplerDispatchable
public Renderable renderableAnnotatedOk = new Renderable();
@StaplerDispatchable
public ParentRenderable parentRenderableAnnotatedOk = new ParentRenderable();
@StaplerDispatchable
public Object objectAnnotatedOk = new Renderable();
@StaplerNotDispatchable
public Renderable renderableAnnotatedKo = new Renderable();
@StaplerNotDispatchable
public Object objectAnnotatedKo = new Renderable();
@StaplerDispatchable
@StaplerNotDispatchable
public Renderable renderableDoubleAnnotated = new Renderable();
@StaplerDispatchable
@StaplerNotDispatchable
public Object objectDoubleAnnotated = new Renderable();
public static Renderable staticRenderableNotAnnotated = new Renderable();
public static Object staticObjectNotAnnotated = new Renderable();
@StaplerDispatchable
public static Renderable staticRenderableAnnotatedOk = new Renderable();
@StaplerDispatchable
public static Object staticObjectAnnotatedOk = new Renderable();
}
@Test
public void testFieldNotAnnotated() throws Exception {
assertReachable("test/renderableNotAnnotated/");
assertReachable("test/renderableNotAnnotated/valid/");
assertNotReachable("test/parentRenderableNotAnnotated/");
assertNotReachable("test/parentRenderableNotAnnotated/renderable/");
assertNotReachable("test/parentRenderableNotAnnotated/renderable/valid/");
assertNotReachable("test/objectNotAnnotated/");
assertNotReachable("test/objectNotAnnotated/valid/");
}
@Test
public void testFieldNotAnnotated_escapeHatch() throws Exception {
boolean currentValue = TypedFilter.SKIP_TYPE_CHECK;
try {
TypedFilter.SKIP_TYPE_CHECK = true;
// to apply the new configuration
webApp.clearMetaClassCache();
assertReachable("test/renderableNotAnnotated/");
assertReachable("test/renderableNotAnnotated/valid/");
assertNotReachable("test/parentRenderableNotAnnotated/");
assertReachable("test/parentRenderableNotAnnotated/renderable/");
assertReachable("test/parentRenderableNotAnnotated/renderable/valid/");
} finally {
TypedFilter.SKIP_TYPE_CHECK = currentValue;
// to reset the configuration
webApp.clearMetaClassCache();
}
}
@Test
public void testFieldAnnotatedOk() throws Exception {
assertReachable("test/renderableAnnotatedOk/");
assertReachable("test/renderableAnnotatedOk/valid/");
assertReachable("test/objectAnnotatedOk/");
assertReachable("test/objectAnnotatedOk/valid/");
}
@Test
public void testFieldAnnotatedKo() throws Exception {
assertNotReachable("test/renderableAnnotatedKo/");
assertNotReachable("test/renderableAnnotatedKo/valid/");
assertNotReachable("test/objectAnnotatedKo/");
assertNotReachable("test/objectAnnotatedKo/valid/");
}
@Test
public void testFieldDoubleAnnotated() throws Exception {
assertNotReachable("test/renderableDoubleAnnotated/");
assertNotReachable("test/renderableDoubleAnnotated/valid/");
assertNotReachable("test/objectDoubleAnnotated/");
assertNotReachable("test/objectDoubleAnnotated/valid/");
}
@Test
public void testStaticFieldNotAnnotated() throws Exception {
assertNotReachable("test/staticRenderableNotAnnotated/");
assertNotReachable("test/staticRenderableNotAnnotated/valid/");
assertNotReachable("test/staticObjectNotAnnotated/");
assertNotReachable("test/staticObjectNotAnnotated/valid/");
}
@Test
public void testStaticFieldNotAnnotated_escapeHatch() throws Exception {
boolean currentValue = TypedFilter.PROHIBIT_STATIC_ACCESS;
try {
TypedFilter.PROHIBIT_STATIC_ACCESS = false;
// to apply the new configuration
webApp.clearMetaClassCache();
assertReachable("test/staticRenderableNotAnnotated/");
assertReachable("test/staticRenderableNotAnnotated/valid/");
assertNotReachable("test/staticObjectNotAnnotated/");
assertNotReachable("test/staticObjectNotAnnotated/valid/");
} finally {
TypedFilter.PROHIBIT_STATIC_ACCESS = currentValue;
// to reset the configuration
webApp.clearMetaClassCache();
}
}
@Test
public void testStaticFieldAnnotatedOk() throws Exception {
assertReachable("test/staticRenderableAnnotatedOk/");
assertReachable("test/staticRenderableAnnotatedOk/valid/");
assertReachable("test/staticObjectAnnotatedOk/");
assertReachable("test/staticObjectAnnotatedOk/valid/");
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.TestExtension;
@Issue("SECURITY-400")
@For({StaplerDispatchable.class, StaplerNotDispatchable.class, TypedFilter.class})
public class StaplerRoutableGetterTest extends StaplerAbstractTest {
@TestExtension
public static class TestRootAction extends AbstractUnprotectedRootAction {
@Override
public String getUrlName() {
return "test";
}
public Object getFalseWithoutAnnotation() {
return new Renderable();
}
@StaplerDispatchable
public Object getFalseWithAnnotation() {
return new Renderable();
}
public Renderable getTrueWithoutAnnotation() {
return new Renderable();
}
@StaplerNotDispatchable
public Renderable getTrueWithAnnotation() {
return new Renderable();
}
@StaplerDispatchable
@StaplerNotDispatchable
public Renderable getPriorityToNegative() {
return new Renderable();
}
}
@Test
public void testForceGetterMethod() throws Exception {
assertNotReachable("test/falseWithoutAnnotation/");
assertNotReachable("test/falseWithoutAnnotation/valid/");
filteredGetMethodTriggered = false;
assertReachable("test/falseWithAnnotation/");
assertReachable("test/falseWithAnnotation/valid/");
}
@Test
public void testForceNotGetterMethod() throws Exception {
assertReachable("test/trueWithoutAnnotation/");
assertReachable("test/trueWithoutAnnotation/valid/");
assertNotReachable("test/trueWithAnnotation/");
assertNotReachable("test/trueWithAnnotation/valid/");
}
@Test
public void testPriorityIsNegative() throws Exception {
assertNotReachable("test/priorityToNegative/");
}
public static class TestRootActionParent extends AbstractUnprotectedRootAction {
@StaplerNotDispatchable
public Renderable getParentKoButChildOk() {
return new Renderable();
}
@StaplerNotDispatchable
public Renderable getParentKoButChildNone() {
return new Renderable();
}
public Renderable getParentNoneButChildOk() {
return new Renderable();
}
public Renderable getParentNoneButChildKo() {
return new Renderable();
}
@StaplerDispatchable
public Renderable getParentOkButChildKo() {
return new Renderable();
}
@StaplerDispatchable
public Renderable getParentOkButChildNone() {
return new Renderable();
}
}
@TestExtension
public static class TestRootActionChild extends TestRootActionParent {
@Override
public String getUrlName() {
return "test-child";
}
@StaplerDispatchable
public Renderable getParentKoButChildOk() {
return new Renderable();
}
public Renderable getParentKoButChildNone() {
return new Renderable();
}
@StaplerDispatchable
public Renderable getParentNoneButChildOk() {
return new Renderable();
}
@StaplerNotDispatchable
public Renderable getParentNoneButChildKo() {
return new Renderable();
}
@StaplerNotDispatchable
public Renderable getParentOkButChildKo() {
return new Renderable();
}
public Renderable getParentOkButChildNone() {
return new Renderable();
}
}
@Test
public void testInheritanceOfAnnotation_childHasLastWord() throws Exception {
assertNotReachable("test-child/parentKoButChildOk/");
assertNotReachable("test-child/parentKoButChildNone/");
filteredGetMethodTriggered = false;
assertReachable("test-child/parentNoneButChildOk/");
assertNotReachable("test-child/parentNoneButChildKo/");
assertNotReachable("test-child/parentOkButChildKo/");
filteredGetMethodTriggered = false;
assertReachable("test-child/parentOkButChildNone/");
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;
import java.io.File;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Due to the fact we are using a @ClassRule for the other tests to improve performance,
* we cannot use @LocalData to test the loading of the whitelist as that annotation seem to not work with @ClassRule.
*/
@Issue("SECURITY-400")
@For(StaticRoutingDecisionProvider.class)
public class StaticRoutingDecisionProvider2Test {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
@LocalData("whitelist_empty")
public void userControlledWhitelist_empty_Loading() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(
wl.decide("public java.lang.Object jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider.getObjectCustom()"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
assertThat(
wl.decide("blabla"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
}
@Test
@LocalData("whitelist_monoline")
public void userControlledWhitelist_monoline_Loading() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(
wl.decide("method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(
wl.decide("blabla"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
}
@Test
@LocalData("whitelist_multiline")
public void userControlledWhitelist_multiline_Loading() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(
wl.decide("method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(
wl.decide("method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom2"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(
wl.decide("blabla"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
}
@Test
@LocalData("comment_ignored")
public void userControlledWhitelist_commentsAreIgnored() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(wl.decide("this line is not read"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(wl.decide("not-this-one"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(wl.decide("neither"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(wl.decide("finally-not"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(wl.decide("this-one-is"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("this-one-also"), is(RoutingDecisionProvider.Decision.ACCEPTED));
}
@Test
@LocalData("whitelist_emptyline")
public void userControlledWhitelist_emptyLinesAreIgnored() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(wl.decide("signature-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("signature-2"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("signature-3"), is(RoutingDecisionProvider.Decision.ACCEPTED));
// neither the empty line or an exclamation mark followed by nothing or spaces are not considered
assertThat(wl.decide(""), is(RoutingDecisionProvider.Decision.UNKNOWN));
}
@Test
@LocalData("greylist_multiline")
public void userControlledWhitelist_whiteAndBlack() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(wl.decide("signature-1-ok"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("signature-3-ok"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("signature-2-not-ok"), is(RoutingDecisionProvider.Decision.REJECTED));
assertThat(wl.decide("signature-4-not-ok"), is(RoutingDecisionProvider.Decision.REJECTED));
// the exclamation mark is not used
assertThat(wl.decide("!signature-2-not-ok"), is(RoutingDecisionProvider.Decision.UNKNOWN));
}
@Test
public void defaultList() throws Exception {
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertThat(
wl.decide("method io.jenkins.blueocean.service.embedded.rest.AbstractRunImpl getLog"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(
wl.decide("method io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeImpl getLog"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(
wl.decide("method io.jenkins.blueocean.rest.impl.pipeline.PipelineStepImpl getLog"),
is(RoutingDecisionProvider.Decision.ACCEPTED)
);
assertThat(wl.decide("method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
assertThat(wl.decide("blabla"),
is(RoutingDecisionProvider.Decision.UNKNOWN)
);
}
@Test
public void userControlledWhitelist_savedCorrectly() throws Exception {
File whitelistUserControlledList = new File(j.jenkins.getRootDir(), "stapler-whitelist.txt");
assertFalse(whitelistUserControlledList.exists());
StaticRoutingDecisionProvider wl = new StaticRoutingDecisionProvider();
assertFalse(whitelistUserControlledList.exists());
assertThat(wl.decide("nothing"), is(RoutingDecisionProvider.Decision.UNKNOWN));
wl.save();
assertTrue(whitelistUserControlledList.exists());
assertThat(FileUtils.readFileToString(whitelistUserControlledList), is(""));
wl.add("white-1");
assertThat(wl.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertTrue(whitelistUserControlledList.exists());
assertThat(FileUtils.readFileToString(whitelistUserControlledList), containsString("white-1"));
{
StaticRoutingDecisionProvider temp = new StaticRoutingDecisionProvider();
assertThat(temp.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
}
wl.addBlacklistSignature("black-2");
assertThat(wl.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("black-2"), is(RoutingDecisionProvider.Decision.REJECTED));
assertThat(FileUtils.readFileToString(whitelistUserControlledList), allOf(
containsString("white-1"),
containsString("!black-2")
));
{
StaticRoutingDecisionProvider temp = new StaticRoutingDecisionProvider();
assertThat(temp.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(temp.decide("black-2"), is(RoutingDecisionProvider.Decision.REJECTED));
}
wl.removeBlacklistSignature("black-2");
assertThat(wl.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(wl.decide("black-2"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(FileUtils.readFileToString(whitelistUserControlledList), allOf(
containsString("white-1"),
not(containsString("black-2"))
));
{
StaticRoutingDecisionProvider temp = new StaticRoutingDecisionProvider();
assertThat(temp.decide("white-1"), is(RoutingDecisionProvider.Decision.ACCEPTED));
assertThat(temp.decide("black-2"), is(RoutingDecisionProvider.Decision.UNKNOWN));
}
wl.remove("white-1");
assertThat(wl.decide("white-1"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(wl.decide("black-2"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(FileUtils.readFileToString(whitelistUserControlledList), allOf(
not(containsString("white-1")),
not(containsString("black-2"))
));
{
StaticRoutingDecisionProvider temp = new StaticRoutingDecisionProvider();
assertThat(temp.decide("white-1"), is(RoutingDecisionProvider.Decision.UNKNOWN));
assertThat(temp.decide("black-2"), is(RoutingDecisionProvider.Decision.UNKNOWN));
}
}
}
package jenkins.security.stapler;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
@Issue("SECURITY-400")
public class TypedFilterTest extends StaplerAbstractTest {
@TestExtension
public static class GetTarget1 extends AbstractUnprotectedRootAction {
public Renderable getTarget(){
return new Renderable();
}
}
@Test
public void getTarget_withoutArg_isNotRoutableDirectly() throws Exception {
assertNotReachable("getTarget1/target/");
}
@TestExtension
public static class GetTarget2 extends AbstractUnprotectedRootAction {
@StaplerDispatchable
public Renderable getTarget(){
return new Renderable();
}
}
@Test
public void getTarget_withoutArg_isRoutableWithAnnotation() throws Exception {
assertReachable("getTarget2/target/");
}
@TestExtension
public static class GetTarget3 extends AbstractUnprotectedRootAction {
@StaplerNotDispatchable
public Renderable getTarget(){
return new Renderable();
}
}
@Test
public void getTarget_withArg_isNotRoutableWithStaplerNotDispatchable() throws Exception {
assertNotReachable("getTarget3/target/");
}
@TestExtension
public static class GetTarget4 extends AbstractUnprotectedRootAction {
public Renderable getTarget(StaplerRequest req){
return new Renderable();
}
}
@Test
public void getTarget_withArg_isRoutable() throws Exception {
assertReachable("getTarget4/target/");
}
@TestExtension
public static class GetStaplerFallback1 extends AbstractUnprotectedRootAction {
public Renderable getStaplerFallback(){
return new Renderable();
}
}
@Test
public void getStaplerFallback_withoutArg_isNotRoutableDirectly() throws Exception {
assertNotReachable("getStaplerFallback1/staplerFallback/");
}
@TestExtension
public static class GetStaplerFallback2 extends AbstractUnprotectedRootAction {
@StaplerDispatchable
public Renderable getStaplerFallback(){
return new Renderable();
}
}
@Test
public void getStaplerFallback_withoutArg_isRoutableWithAnnotation() throws Exception {
assertReachable("getStaplerFallback2/staplerFallback/");
}
@TestExtension
public static class GetStaplerFallback3 extends AbstractUnprotectedRootAction {
@StaplerNotDispatchable
public Renderable getStaplerFallback(){
return new Renderable();
}
}
@Test
public void getStaplerFallback_withArg_isNotRoutableWithStaplerNotDispatchable() throws Exception {
assertNotReachable("getStaplerFallback3/staplerFallback/");
}
@TestExtension
public static class GetStaplerFallback4 extends AbstractUnprotectedRootAction {
public Renderable getStaplerFallback(StaplerRequest req){
return new Renderable();
}
}
@Test
public void getStaplerFallback_withArg_isRoutable() throws Exception {
assertReachable("getStaplerFallback4/staplerFallback/");
}
public static class TypeImplementingStaplerProxy implements StaplerProxy {
@Override
public Object getTarget() {
return new Renderable();
}
}
public static class TypeExtendingTypeImplementingStaplerProxy extends TypeImplementingStaplerProxy {
}
// FIXME @StaplerNotDispatchable
public static class TypeImplementingStaplerProxy2 implements StaplerProxy {
@Override
public Object getTarget() {
return new Renderable();
}
}
public static class TypeExtendingTypeImplementingStaplerProxy2 extends TypeImplementingStaplerProxy2 {
}
@TestExtension
public static class GetTypeImplementingStaplerProxy extends AbstractUnprotectedRootAction {
public TypeImplementingStaplerProxy getTypeImplementingStaplerProxy(){
return new TypeImplementingStaplerProxy();
}
public TypeExtendingTypeImplementingStaplerProxy getTypeExtendingTypeImplementingStaplerProxy(){
return new TypeExtendingTypeImplementingStaplerProxy();
}
public TypeImplementingStaplerProxy2 getTypeImplementingStaplerProxy2(){
return new TypeImplementingStaplerProxy2();
}
public TypeExtendingTypeImplementingStaplerProxy2 getTypeExtendingTypeImplementingStaplerProxy2(){
return new TypeExtendingTypeImplementingStaplerProxy2();
}
}
@Test
public void typeImplementingStaplerProxy_isRoutableByDefault() throws Exception {
assertReachable("getTypeImplementingStaplerProxy/typeImplementingStaplerProxy/");
assertReachable("getTypeImplementingStaplerProxy/typeImplementingStaplerProxy/valid");
}
@Test
public void typeExtendingParentImplementingStaplerProxy_isRoutableByDefault() throws Exception {
assertReachable("getTypeImplementingStaplerProxy/typeExtendingTypeImplementingStaplerProxy/");
assertReachable("getTypeImplementingStaplerProxy/typeExtendingTypeImplementingStaplerProxy/valid/");
}
@Test
public void typeImplementingStaplerProxy_isNotRoutableWithNonroutable() throws Exception {
//TODO no way to avoid routability if implementing StaplerProxy
// assertNotReachable("getTypeImplementingStaplerProxy/typeImplementingStaplerProxy2/");
// assertNotReachable("getTypeImplementingStaplerProxy/typeImplementingStaplerProxy2/valid/");
}
@Test
public void typeExtendingParentImplementingStaplerProxy_isNotRoutableWithNonroutable() throws Exception {
//TODO no way to avoid routability if super type implementing StaplerProxy
// assertNotReachable("getTypeImplementingStaplerProxy/typeExtendingTypeImplementingStaplerProxy2/");
// assertNotReachable("getTypeImplementingStaplerProxy/typeExtendingTypeImplementingStaplerProxy2/valid/");
}
@TestExtension
public static class GetDynamic1 extends AbstractUnprotectedRootAction {
public Renderable getDynamic(){
return new Renderable();
}
}
@Test
public void getDynamic_withoutArg_isRoutable() throws Exception {
assertReachable("getDynamic1/dynamic/");
assertNotReachable("getDynamic1/<anyString>/");
}
@TestExtension
public static class GetDynamic2 extends AbstractUnprotectedRootAction {
public Renderable getDynamic(String someArgs){
return new Renderable();
}
}
@Test
public void getDynamic_withArgStartingWithString_isRoutable() throws Exception {
// dynamic is "just" a subcase of regular getDynamic usage
assertReachable("getDynamic2/dynamic/");
assertReachable("getDynamic2/<anyString>/");
}
@TestExtension
public static class GetDynamic3 extends AbstractUnprotectedRootAction {
public Renderable getDynamic(StaplerRequest req, String someArgs){
return new Renderable();
}
}
@Test
public void getDynamic_withArgNotStartingWithString_isNotRoutable() throws Exception {
assertNotReachable("getDynamic3/dynamic/");
assertNotReachable("getDynamic3/<anyString>/");
}
@TestExtension
public static class GetDynamic4 extends AbstractUnprotectedRootAction {
public Renderable getDynamic(StaplerRequest req){
return new Renderable();
}
}
@Test
public void getDynamic_withArgNotIncludingString_isRoutable() throws Exception {
assertReachable("getDynamic4/dynamic/");
// there is no magic here, as the string argument is missing, just a regular getter
assertNotReachable("getDynamic4/<anyString>/");
}
}
{"hudson-matrix-MatrixProject":0,"hudson-maven-MavenModuleSet":0,"hudson-model-FreeStyleProject":0,"org-jvnet-hudson-test-MockFolder":0,"org-jvnet-hudson-test-SecuredMockFolder":0}
\ No newline at end of file
{"com-cloudbees-hudson-plugins-folder-Folder":0,"hudson-matrix-MatrixProject":0,"hudson-maven-MavenModuleSet":0,"hudson-model-FreeStyleProject":0,"org-jvnet-hudson-test-MockFolder":0,"org-jvnet-hudson-test-SecuredMockFolder":0}
\ No newline at end of file
# this line is not read
this-one-is
# not-this-one
#neither
this-one-also
# finally-not
signature-1
# just an empty line
signature-2
# space after the exclamation mark
!
# no space
!
signature-3
method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom
method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom
method jenkins.security.stapler.StaticRoutingDecisionProviderTest$ContentProvider getObjectCustom2
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册