提交 03647c7c 编写于 作者: K Kohsuke Kawaguchi

merged back the RC branch

......@@ -66,7 +66,10 @@ Upcoming changes</a>
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.600>What's new in 1.600</a> <!--=DATE=--></h3>
<h3><a name=v1.601>What's new in 1.601</a> <!--=DATE=--></h3>
<!--=RC-CHANGES=-->
</div><!--=END=-->
<h3><a name=v1.600>What's new in 1.600</a> (2015/02/28)</h3>
<ul class=image>
<li class="rfe">
JDK auto-installer for Mac OSX
......@@ -83,7 +86,6 @@ Upcoming changes</a>
Maven build step fail to launch mvn process when special chars are present in build variables.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-26684">issue 26684</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.599>What's new in 1.599</a> (2015/02/16)</h3>
<ul class=image>
<li class=bug>
......
......@@ -66,6 +66,7 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
......@@ -303,13 +304,19 @@ public class ClassicPluginStrategy implements PluginStrategy {
// don't fix the dependency for yourself, or else we'll have a cycle
String yourName = atts.getValue("Short-Name");
if (shortName.equals(yourName)) return;
if (BREAK_CYCLES.contains(yourName + '/' + shortName)) {
LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {yourName, shortName});
return;
}
// some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
String jenkinsVersion = atts.getValue("Jenkins-Version");
if (jenkinsVersion==null)
jenkinsVersion = atts.getValue("Hudson-Version");
if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0)
optionalDependencies.add(new PluginWrapper.Dependency(shortName+':'+requireVersion));
if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0) {
optionalDependencies.add(new PluginWrapper.Dependency(shortName + ':' + requireVersion));
LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {yourName, shortName, jenkinsVersion});
}
}
}
......@@ -330,6 +337,14 @@ public class ClassicPluginStrategy implements PluginStrategy {
new DetachedPlugin("junit","1.577.*","1.0")
);
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
private static final Set<String> BREAK_CYCLES = new HashSet<String>(Arrays.asList(
"script-security/matrix-auth",
"script-security/windows-slaves",
"script-security/antisamy-markup-formatter",
"script-security/matrix-project"
));
/**
* Computes the classloader that takes the class masking into account.
*
......
......@@ -51,6 +51,8 @@ import jenkins.RestartRequiredException;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
import jenkins.util.xml.RestrictiveEntityResolver;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
......@@ -109,6 +111,7 @@ import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
......@@ -1045,6 +1048,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
requestedPlugins.put(shortName, requested);
}
}
@Override public InputSource resolveEntity(String publicId, String systemId) throws IOException,
SAXException {
return RestrictiveEntityResolver.INSTANCE.resolveEntity(publicId, systemId);
}
});
} catch (SAXException x) {
throw new IOException("Failed to parse XML",x);
......
......@@ -45,6 +45,8 @@ import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import jenkins.security.NotReallyRoleSensitiveCallable;
import org.acegisecurity.Authentication;
import jenkins.util.xml.XMLUtils;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.WebMethod;
......@@ -67,12 +69,11 @@ import org.kohsuke.stapler.HttpDeletable;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.xml.sax.SAXException;
import javax.servlet.ServletException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
......@@ -627,25 +628,24 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
}
/**
* Updates Job by its XML definition.
* Updates an Item by its XML definition.
* @param source source of the Item's new definition.
* The source should be either a <code>StreamSource</code> or a <code>SAXSource</code>, other
* sources may not be handled.
* @since 1.473
*/
public void updateByXml(Source source) throws IOException {
checkPermission(CONFIGURE);
XmlFile configXmlFile = getConfigFile();
AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
final AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
try {
try {
// this allows us to use UTF-8 for storing data,
// plus it checks any well-formedness issue in the submitted
// data
Transformer t = TransformerFactory.newInstance()
.newTransformer();
t.transform(source,
new StreamResult(out));
XMLUtils.safeTransform(source, new StreamResult(out));
out.close();
} catch (TransformerException e) {
throw new IOException("Failed to persist config.xml", e);
} catch (SAXException e) {
throw new IOException("Failed to persist config.xml", e);
}
// try to reflect the changes by reloading
......@@ -667,6 +667,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
// if everything went well, commit this new version
out.commit();
SaveableListener.fireOnChange(this, getConfigFile());
} finally {
out.abort(); // don't leave anything behind
}
......
......@@ -24,6 +24,7 @@
package hudson.model;
import hudson.ExtensionList;
import jenkins.util.xml.FilteredFunctionContext;
import jenkins.model.Jenkins;
import jenkins.security.SecureRequester;
......@@ -32,6 +33,7 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.kohsuke.stapler.QueryParameter;
......@@ -107,14 +109,16 @@ public class Api extends AbstractModelObject {
p.writeTo(bean,pruner,Flavor.XML.createDataWriter(bean,sw));
// apply XPath
FilteredFunctionContext functionContext = new FilteredFunctionContext();
Object result;
try {
Document dom = new SAXReader().read(new StringReader(sw.toString()));
// apply exclusions
if (excludes!=null) {
for (String exclude : excludes) {
List<org.dom4j.Node> list = (List<org.dom4j.Node>)dom.selectNodes(exclude);
XPath xExclude = dom.createXPath(exclude);
xExclude.setFunctionContext(functionContext);
List<org.dom4j.Node> list = (List<org.dom4j.Node>)xExclude.selectNodes(dom);
for (org.dom4j.Node n : list) {
Element parent = n.getParent();
if(parent!=null)
......@@ -126,7 +130,9 @@ public class Api extends AbstractModelObject {
if(xpath==null) {
result = dom;
} else {
List list = dom.selectNodes(xpath);
XPath comp = dom.createXPath(xpath);
comp.setFunctionContext(functionContext);
List list = comp.selectNodes(dom);
if (wrapper!=null) {
Element root = DocumentFactory.getInstance().createElement(wrapper);
for (Object o : list) {
......
......@@ -66,7 +66,7 @@ public class DownloadService extends PageDecorator {
* Builds up an HTML fragment that starts all the download jobs.
*/
public String generateFragment() {
if (!DownloadSettings.get().isUseBrowser()) {
if (!DownloadSettings.usePostBack()) {
return "";
}
if (neverUpdate) return "";
......@@ -170,6 +170,30 @@ public class DownloadService extends PageDecorator {
}
}
/**
* Loads JSON from a JSON-with-{@code postMessage} URL.
* @param src a URL to a JSON HTML file (typically including {@code id} and {@code version} query parameters)
* @return the embedded JSON text
* @throws IOException if either downloading or processing failed
*/
@Restricted(NoExternalUse.class)
public static String loadJSONHTML(URL src) throws IOException {
InputStream is = ProxyConfiguration.open(src).getInputStream();
try {
String jsonp = IOUtils.toString(is, "UTF-8");
String preamble = "window.parent.postMessage(JSON.stringify(";
int start = jsonp.indexOf(preamble);
int end = jsonp.lastIndexOf("),'*');");
if (start >= 0 && end > start) {
return jsonp.substring(start + preamble.length(), end).trim();
} else {
throw new IOException("Could not find JSON in " + src);
}
} finally {
is.close();
}
}
/**
* Represents a periodically updated JSON data file obtained from a remote URL.
*
......@@ -283,9 +307,7 @@ public class DownloadService extends PageDecorator {
* This is where the browser sends us the data.
*/
public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
if (!DownloadSettings.get().isUseBrowser()) {
throw new IOException("not allowed");
}
DownloadSettings.checkPostBackAccess();
long dataTimestamp = System.currentTimeMillis();
due = dataTimestamp+getInterval(); // success or fail, don't try too often
......@@ -317,7 +339,7 @@ public class DownloadService extends PageDecorator {
@Restricted(NoExternalUse.class)
public FormValidation updateNow() throws IOException {
return load(loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
return load(loadJSONHTML(new URL(getUrl() + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
}
/**
......
......@@ -174,9 +174,7 @@ public class UpdateSite {
* This is the endpoint that receives the update center data file from the browser.
*/
public FormValidation doPostBack(StaplerRequest req) throws IOException, GeneralSecurityException {
if (!DownloadSettings.get().isUseBrowser()) {
throw new IOException("not allowed");
}
DownloadSettings.checkPostBackAccess();
return updateData(IOUtils.toString(req.getInputStream(),"UTF-8"), true);
}
......
......@@ -35,6 +35,7 @@ import hudson.security.Permission;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException;
import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.util.XStream2;
import jenkins.model.IdStrategy;
......@@ -109,7 +110,18 @@ import javax.annotation.Nullable;
*/
@ExportedBean
public class User extends AbstractModelObject implements AccessControlled, DescriptorByNameOwner, Saveable, Comparable<User>, ModelObjectWithContextMenu {
/**
* The username of the 'unknown' user used to avoid null user references.
*/
private static final String UKNOWN_USERNAME = "unknown";
/**
* These usernames should not be used by real users logging into Jenkins. Therefore, we prevent
* users with these names from being saved.
*/
private static final String[] ILLEGAL_PERSISTED_USERNAMES = new String[]{ACL.ANONYMOUS_USERNAME,
ACL.SYSTEM_USERNAME, UKNOWN_USERNAME};
private transient final String id;
private volatile String fullName;
......@@ -320,7 +332,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
* This is used to avoid null {@link User} instance.
*/
public static @Nonnull User getUnknown() {
return get("unknown");
return get(UKNOWN_USERNAME);
}
/**
......@@ -642,10 +654,35 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
return new File(Jenkins.getInstance().getRootDir(), "users");
}
/**
* Is the ID allowed? Some are prohibited for security reasons. See SECURITY-166.
* <p/>
* Note that this is only enforced when saving. These users are often created
* via the constructor (and even listed on /asynchPeople), but our goal is to
* prevent anyone from logging in as these users. Therefore, we prevent
* saving a User with one of these ids.
*
* @return true if the username or fullname is valid
* @since 1.600
*/
public static boolean isIdOrFullnameAllowed(String id) {
for (String invalidId : ILLEGAL_PERSISTED_USERNAMES) {
if (id.equalsIgnoreCase(invalidId))
return false;
}
return true;
}
/**
* Save the settings to a file.
*/
public synchronized void save() throws IOException {
public synchronized void save() throws IOException, FormValidation {
if (! isIdOrFullnameAllowed(id)) {
throw FormValidation.error(Messages.User_IllegalUsername(id));
}
if (! isIdOrFullnameAllowed(fullName)) {
throw FormValidation.error(Messages.User_IllegalFullname(fullName));
}
if(BulkChange.contains(this)) return;
getConfigFile().write(this);
SaveableListener.fireOnChange(this, getConfigFile());
......
......@@ -58,6 +58,8 @@ import hudson.widgets.Widget;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.util.ProgressiveRendering;
import jenkins.util.xml.XMLUtils;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
......@@ -75,9 +77,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedInputStream;
......@@ -106,6 +106,7 @@ import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jenkins.model.Jenkins.*;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.xml.sax.SAXException;
/**
* Encapsulates the rendering of the list of {@link TopLevelItem}s
......@@ -1063,7 +1064,10 @@ public abstract class View extends AbstractModelObject implements AccessControll
}
/**
* Updates Job by its XML definition.
* Updates the View with the new XML definition.
* @param source source of the Item's new definition.
* The source should be either a <code>StreamSource</code> or <code>SAXSource</code>, other sources
* may not be handled.
*/
public void updateByXml(Source source) throws IOException {
checkPermission(CONFIGURE);
......@@ -1072,13 +1076,12 @@ public abstract class View extends AbstractModelObject implements AccessControll
// this allows us to use UTF-8 for storing data,
// plus it checks any well-formedness issue in the submitted
// data
Transformer t = TransformerFactory.newInstance()
.newTransformer();
t.transform(source,
new StreamResult(out));
XMLUtils.safeTransform(source, new StreamResult(out));
out.close();
} catch (TransformerException e) {
throw new IOException("Failed to persist configuration.xml", e);
} catch (SAXException e) {
throw new IOException("Failed to persist configuration.xml", e);
}
// try to reflect the changes by reloading
......
......@@ -35,6 +35,8 @@ import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Gate-keeper that controls access to Hudson's model objects.
......@@ -95,23 +97,34 @@ public abstract class ACL {
}
};
/**
* The username for the anonymous user
*/
@Restricted(NoExternalUse.class)
public static final String ANONYMOUS_USERNAME = "anonymous";
/**
* {@link Sid} that represents the anonymous unauthenticated users.
* <p>
* {@link HudsonFilter} sets this up, so this sid remains the same
* regardless of the current {@link SecurityRealm} in use.
*/
public static final Sid ANONYMOUS = new PrincipalSid("anonymous");
public static final Sid ANONYMOUS = new PrincipalSid(ANONYMOUS_USERNAME);
protected static final Sid[] AUTOMATIC_SIDS = new Sid[]{EVERYONE,ANONYMOUS};
/**
* The username for the system user
*/
@Restricted(NoExternalUse.class)
public static final String SYSTEM_USERNAME = "SYSTEM";
/**
* {@link Sid} that represents the Hudson itself.
* <p>
* This is used when Hudson is performing computation for itself, instead
* of acting on behalf of an user, such as doing builds.
*/
public static final Authentication SYSTEM = new UsernamePasswordAuthenticationToken("SYSTEM","SYSTEM");
public static final Authentication SYSTEM = new UsernamePasswordAuthenticationToken(SYSTEM_USERNAME,"SYSTEM");
/**
* Changes the {@link Authentication} associated with the current thread
......
......@@ -337,6 +337,14 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
if(si.email==null || !si.email.contains("@"))
si.errorMessage = Messages.HudsonPrivateSecurityRealm_CreateAccount_InvalidEmailAddress();
if (! User.isIdOrFullnameAllowed(si.username)) {
si.errorMessage = hudson.model.Messages.User_IllegalUsername(si.username);
}
if (! User.isIdOrFullnameAllowed(si.fullname)) {
si.errorMessage = hudson.model.Messages.User_IllegalFullname(si.fullname);
}
if(si.errorMessage!=null) {
// failed. ask the user to try again.
req.setAttribute("data",si);
......
/*
* The MIT License
*
* Copyright (c) 2015, 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.fun;
import hudson.Extension;
import hudson.model.UnprotectedRootAction;
/**
* Easter egg for Jenkins 100K certificate.
*
* @author Kohsuke Kawaguchi
* @since 1.600
*/
@Extension(ordinal=-100)
public class Jenkins100K implements UnprotectedRootAction {
@Override
public String getIconFileName() {
return "/images/headshot.png";
}
@Override
public String getDisplayName() {
return "Jenkins 100K";
}
@Override
public String getUrlName() {
return "jenkins100k";
}
}
......@@ -25,6 +25,8 @@
package jenkins.model;
import hudson.Extension;
import hudson.Main;
import hudson.model.AdministrativeMonitor;
import hudson.model.AsyncPeriodicWork;
import hudson.model.DownloadService;
import hudson.model.TaskListener;
......@@ -32,6 +34,7 @@ import hudson.model.UpdateSite;
import hudson.util.FormValidation;
import java.io.IOException;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
......@@ -49,7 +52,7 @@ import org.kohsuke.stapler.StaplerRequest;
return Jenkins.getInstance().getInjector().getInstance(DownloadSettings.class);
}
private boolean useBrowser = true; // historical default, not necessarily recommended
private boolean useBrowser = false;
public DownloadSettings() {
load();
......@@ -69,6 +72,21 @@ import org.kohsuke.stapler.StaplerRequest;
save();
}
@Override public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}
public static boolean usePostBack() {
return get().isUseBrowser() && Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER);
}
public static void checkPostBackAccess() throws AccessDeniedException {
if (!get().isUseBrowser()) {
throw new AccessDeniedException("browser-based download disabled");
}
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
}
@Extension public static final class DailyCheck extends AsyncPeriodicWork {
public DailyCheck() {
......@@ -79,10 +97,24 @@ import org.kohsuke.stapler.StaplerRequest;
return DAY;
}
@Override public long getInitialDelay() {
return Main.isUnitTest ? DAY : 0;
}
@Override protected void execute(TaskListener listener) throws IOException, InterruptedException {
if (get().isUseBrowser()) {
return;
}
boolean due = false;
for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) {
if (site.isDue()) {
due = true;
break;
}
}
if (!due) {
return;
}
HttpResponse rsp = Jenkins.getInstance().getPluginManager().doCheckUpdatesServer();
if (rsp instanceof FormValidation) {
listener.error(((FormValidation) rsp).renderHtml());
......@@ -91,4 +123,12 @@ import org.kohsuke.stapler.StaplerRequest;
}
@Extension public static final class Warning extends AdministrativeMonitor {
@Override public boolean isActivated() {
return DownloadSettings.get().isUseBrowser();
}
}
}
......@@ -135,6 +135,9 @@ public class JSONSignatureValidator {
// which isn't useful at all
Set<TrustAnchor> anchors = new HashSet<TrustAnchor>(); // CertificateUtil.getDefaultRootCAs();
Jenkins j = Jenkins.getInstance();
if (j == null) {
return anchors;
}
for (String cert : (Set<String>) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
if (cert.endsWith("/") || cert.endsWith(".txt")) {
continue; // skip directories also any text files that are meant to be documentation
......
......@@ -33,12 +33,15 @@ import hudson.util.DirScanner;
import hudson.util.FileVisitor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import jenkins.MasterToSlaveFileCallable;
......@@ -209,12 +212,14 @@ public abstract class VirtualFile implements Comparable<VirtualFile>, Serializab
* @return a wrapper
*/
public static VirtualFile forFile(final File f) {
return new FileVF(f);
return new FileVF(f, f);
}
private static final class FileVF extends VirtualFile {
private final File f;
FileVF(File f) {
private final File root;
FileVF(File f, File root) {
this.f = f;
this.root = root;
}
@Override public String getName() {
return f.getName();
......@@ -223,46 +228,85 @@ public abstract class VirtualFile implements Comparable<VirtualFile>, Serializab
return f.toURI();
}
@Override public VirtualFile getParent() {
return forFile(f.getParentFile());
return new FileVF(f.getParentFile(), root);
}
@Override public boolean isDirectory() throws IOException {
if (isIllegalSymlink()) {
return false;
}
return f.isDirectory();
}
@Override public boolean isFile() throws IOException {
if (isIllegalSymlink()) {
return false;
}
return f.isFile();
}
@Override public boolean exists() throws IOException {
if (isIllegalSymlink()) {
return false;
}
return f.exists();
}
@Override public VirtualFile[] list() throws IOException {
if (isIllegalSymlink()) {
return new VirtualFile[0];
}
File[] kids = f.listFiles();
if (kids == null) {
return new VirtualFile[0];
}
VirtualFile[] vfs = new VirtualFile[kids.length];
for (int i = 0; i < kids.length; i++) {
vfs[i] = forFile(kids[i]);
vfs[i] = new FileVF(kids[i], root);
}
return vfs;
}
@Override public String[] list(String glob) throws IOException {
if (isIllegalSymlink()) {
return new String[0];
}
return new Scanner(glob).invoke(f, null);
}
@Override public VirtualFile child(String name) {
return forFile(new File(f, name));
return new FileVF(new File(f, name), root);
}
@Override public long length() throws IOException {
if (isIllegalSymlink()) {
return 0;
}
return f.length();
}
@Override public long lastModified() throws IOException {
if (isIllegalSymlink()) {
return 0;
}
return f.lastModified();
}
@Override public boolean canRead() throws IOException {
if (isIllegalSymlink()) {
return false;
}
return f.canRead();
}
@Override public InputStream open() throws IOException {
if (isIllegalSymlink()) {
throw new FileNotFoundException(f.getPath());
}
return new FileInputStream(f);
}
private boolean isIllegalSymlink() { // TODO JENKINS-26838
try {
String myPath = f.getCanonicalPath();
String rootPath = root.getCanonicalPath();
if (!myPath.equals(rootPath) && !myPath.startsWith(rootPath + File.separatorChar)) {
return true;
}
} catch (IOException x) {
Logger.getLogger(VirtualFile.class.getName()).log(Level.FINE, "could not determine symlink status of " + f, x);
}
return false;
}
}
/**
......
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, Inc. All rights reserved.
*
* 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.util.xml;
import org.jaxen.Function;
import org.jaxen.FunctionContext;
import org.jaxen.UnresolvableException;
import org.jaxen.XPathFunctionContext;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/**
* {@link org.jaxen.FunctionContext} that removes some {@link org.dom4j.XPath}
* function names that are deemed bad as user input.
*
* @author Robert Sandell &lt;rsandell@cloudbees.com&gt;.
* @see org.jaxen.FunctionContext
* @see org.dom4j.XPath
* @see hudson.model.Api
*/
@Restricted(NoExternalUse.class)
public class FilteredFunctionContext implements FunctionContext {
/**
* Default set of "bad" function names.
*/
private static final Set<String> DEFAULT_ILLEGAL_FUNCTIONS = Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("document")
));
private final FunctionContext base;
private final Set<String> illegalFunctions;
public FilteredFunctionContext(Set<String> illegalFunctions) {
this.illegalFunctions = illegalFunctions;
base = XPathFunctionContext.getInstance();
}
public FilteredFunctionContext() {
this(DEFAULT_ILLEGAL_FUNCTIONS);
}
@Override
public Function getFunction(String namespaceURI, String prefix, String localName) throws UnresolvableException {
if (localName != null && illegalFunctions.contains(localName.toLowerCase(Locale.ENGLISH))) {
throw new UnresolvableException("Illegal function: " + localName);
}
return base.getFunction(namespaceURI, prefix, localName);
}
}
package jenkins.util.xml;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;
/**
* An EntityResolver that will fail to resolve any entities.
* Useful in preventing External XML Entity injection attacks.
*/
@Restricted(NoExternalUse.class)
public final class RestrictiveEntityResolver implements EntityResolver {
public final static RestrictiveEntityResolver INSTANCE = new RestrictiveEntityResolver();
private RestrictiveEntityResolver() {
// prevent multiple instantiation.
super();
}
/**
* Throws a SAXException if this tried to resolve any entity.
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
throw new SAXException("Refusing to resolve entity with publicId(" + publicId + ") and systemId (" + systemId + ")");
}
}
package jenkins.util.xml;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.xml.XMLConstants;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
/**
* Utilities useful when working with various XML types.
*/
@Restricted(NoExternalUse.class)
public final class XMLUtils {
private final static Logger LOGGER = LogManager.getLogManager().getLogger(XMLUtils.class.getName());
private final static String DISABLED_PROPERTY_NAME = XMLUtils.class.getName() + ".disableXXEPrevention";
/**
* Transform the source to the output in a manner that is protected against XXE attacks.
* If the transform can not be completed safely then an IOException is thrown.
* Note - to turn off safety set the system property <code>disableXXEPrevention</code> to <code>true</code>.
* @param source The XML input to transform. - This should be a <code>StreamSource</code> or a
* <code>SAXSource</code> in order to be able to prevent XXE attacks.
* @param out The Result of transforming the <code>source</code>.
*/
public static void safeTransform(@Nonnull Source source, @Nonnull Result out) throws TransformerException,
SAXException {
InputSource src = SAXSource.sourceToInputSource(source);
if (src != null) {
SAXTransformerFactory stFactory = (SAXTransformerFactory) TransformerFactory.newInstance();
stFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
try {
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
}
catch (SAXException ignored) { /* ignored */ }
try {
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
}
catch (SAXException ignored) { /* ignored */ }
// defend against XXE
// the above features should strip out entities - however the feature may not be supported depending
// on the xml implementation used and this is out of our control.
// So add a fallback plan if all else fails.
xmlReader.setEntityResolver(RestrictiveEntityResolver.INSTANCE);
SAXSource saxSource = new SAXSource(xmlReader, src);
_transform(saxSource, out);
}
else {
// for some reason we could not convert source
// this applies to DOMSource and StAXSource - and possibly 3rd party implementations...
// a DOMSource can already be compromised as it is parsed by the time it gets to us.
if (Boolean.getBoolean(DISABLED_PROPERTY_NAME)) {
LOGGER.log(Level.WARNING, "XML external entity (XXE) prevention has been disabled by the system " +
"property {0}=true Your system may be vulnerable to XXE attacks.", DISABLED_PROPERTY_NAME);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Caller stack trace: ", new Exception("XXE Prevention caller history"));
}
_transform(source, out);
}
else {
throw new TransformerException("Could not convert source of type " + source.getClass() + " and " +
"XXEPrevention is enabled.");
}
}
}
/**
* potentially unsafe XML transformation.
* @param source The XML input to transform.
* @param out The Result of transforming the <code>source</code>.
*/
private static void _transform(Source source, Result out) throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// this allows us to use UTF-8 for storing data,
// plus it checks any well-formedness issue in the submitted data.
Transformer t = factory.newTransformer();
t.transform(source, out);
}
}
......@@ -356,3 +356,6 @@ Jenkins.CheckDisplayName.DisplayNameNotUniqueWarning=The display name, "{0}", is
Jenkins.NotAllowedName="{0}" is not allowed name
Jenkins.IsRestarting=Jenkins is restarting
User.IllegalUsername="{0}" is prohibited as a username for security reasons.
User.IllegalFullname="{0}" is prohibited as a full name for security reasons.
......@@ -31,8 +31,8 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:invokeStatic var="ds" className="jenkins.model.DownloadSettings" method="get"/>
<j:if test="${ds.useBrowser}">
<j:invokeStatic var="enabled" className="jenkins.model.DownloadSettings" method="usePostBack"/>
<j:if test="${enabled}">
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.due or forcedUpdateCheck}">
<script>
......
<!--
The MIT License
Copyright (c) 2015, 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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="Jenkins">
<l:main-panel>
<div align="center" id="certificate">
<style>
#side-panel { display: none; }
#main-panel { padding-left: 0px !important; }
#certificate {
opacity: 0;
-webkit-transition: opacity 2s ease-in;
-moz-transition: opacity 2s ease-in;
-ms-transition: opacity 2s ease-in;
-o-transition: opacity 2s ease-in;
transition: opacity 2s ease-in;
}
#certificate.load {
opacity: 1;
}
</style>
<div style="margin:2em"><a href="http://jenkins-ci.org/100k">
<img src="${imagesURL}/100k.png"/>
</a></div>
<div style="margin:2em">
In Feb 2015, Jenkins project counted that the active installations it tracks around the world went over the 100,000 instances.
</div>
<a class="twitter-share-button" href="https://twitter.com/share"
data-related="jenkinsci"
data-size="large"
data-text="I helped @JenkinsCI to grow to 100K! #Jenkins100K"
data-url="http://jenkins-ci.org/content/jenkins-celebration-day-february-26"
data-count="right">
Tweet
</a>
<script>
window.twttr=(function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return;js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);t._e=[];t.ready=function(f){t._e.push(f);};return t;}(document,"script","twitter-wjs"));
Behaviour.addLoadEvent(function() {
$('certificate').addClassName('load');
});
</script>
</div>
</l:main-panel>
</l:layout>
</j:jelly>
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright 2015 Jesse Glick.
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<div class="warning">
<form method="post" action="${rootURL}/${it.url}/disable">
<j:out value="${%blurb(rootURL)}"/>
<f:submit value="${%Dismiss}"/>
</form>
</div>
</j:jelly>
# The MIT License
#
# Copyright 2015 Jesse Glick.
#
# 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.
blurb=\
You currently are using browser-based download to retrieve metadata for Jenkins plugins and tools. \
This has reliability issues and is not considered fully secure. \
Consider <a href="{0}/configureSecurity">switching to server-based download</a>.
......@@ -2,8 +2,7 @@ package jenkins.security.DownloadSettings
def f = namespace(lib.FormTagLib)
f.section(title: _("Download Preferences")) {
f.entry(title: _("Use Browser"), field: "useBrowser") {
f.checkbox()
}
// TODO avoid indentation somehow
f.entry(field: "useBrowser") {
f.checkbox(title: _("Use browser for metadata download"))
}
<div>
<p>
Check to force the user’s browser to download metadata (lists of available plugins, tools etc.)
rather than Jenkins itself doing it.
Actual file downloads (plugins, tools) will still happen from Jenkins itself,
but this can be used to at least <em>see</em> new metadata when Jenkins cannot access the Internet
(but your own browser can, perhaps using some special proxy that Jenkins is not configured to use).
</div>
</p>
<p>
Use of browser mode is discouraged.
It has robustness problems and has been limited to users with Overall/Administer.
</p>
......@@ -26,11 +26,14 @@ package hudson;
import org.apache.tools.ant.filters.StringInputStream;
import org.junit.Test;
import org.xml.sax.SAXException;
import java.io.IOException;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
public class PluginManagerTest {
......@@ -41,4 +44,28 @@ public class PluginManagerTest {
.parseRequestedPlugins(new StringInputStream("<root><stuff plugin='stuff@1.0'><more plugin='other@2.0'><things plugin='stuff@1.2'/></more></stuff></root>")).toString());
}
@Issue("SECURITY-167")
@Test
public void parseInvalidRequestedPlugins() throws Exception {
String evilXML = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[<!ENTITY foo SYSTEM \"file:///\">]>\n" +
"<root>\n" +
" <stuff plugin='stuff@1.0'>\n" +
"&foo;" +
" <more plugin='other@2.0'>\n" +
" <things plugin='stuff@1.2'/>\n" +
" </more>\n" +
" </stuff>\n" +
"</root>\n";
PluginManager pluginManager = new LocalPluginManager(Util.createTempDir());
try {
pluginManager.parseRequestedPlugins(new StringInputStream(evilXML));
fail("XML contains an external entity, but no exception was thrown.");
}
catch (IOException ex) {
assertThat(ex.getCause(), instanceOf(SAXException.class));
assertThat(ex.getCause().getMessage(), containsString("Refusing to resolve entity with publicId(null) and systemId (file:///)"));
}
}
}
/*
* The MIT License
*
* Copyright 2015 Jesse Glick.
*
* 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.util;
import hudson.Util;
import hudson.model.TaskListener;
import java.io.File;
import java.io.FileNotFoundException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
public class VirtualFileTest {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Issue("SECURITY-162")
@Test public void outsideSymlinks() throws Exception {
File ws = tmp.newFolder("ws");
FileUtils.write(new File(ws, "safe"), "safe");
Util.createSymlink(ws, "safe", "supported", TaskListener.NULL);
File other = tmp.newFolder("other");
FileUtils.write(new File(other, "secret"), "s3cr3t");
Util.createSymlink(ws, "../other/secret", "hack", TaskListener.NULL);
VirtualFile root = VirtualFile.forFile(ws);
VirtualFile supported = root.child("supported");
assertTrue(supported.isFile());
assertTrue(supported.exists());
assertEquals("safe", IOUtils.toString(supported.open(), (String) null));
VirtualFile hack = root.child("hack");
assertFalse(hack.isFile());
assertFalse(hack.exists());
try {
hack.open();
fail();
} catch (FileNotFoundException x) {
// OK
}
}
}
\ No newline at end of file
/*
* The MIT License
*
* Copyright 2015 James Nord.
*
* 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.xml;
import jenkins.util.xml.XMLUtils;
import org.junit.Test;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertThat;
import org.jvnet.hudson.test.Issue;
public class XMLUtilsTest {
@Issue("SECURITY-167")
@Test()
public void testSafeTransformDoesNotProcessForeignResources() throws Exception {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[\n" +
" <!ENTITY foo SYSTEM \"file:///\">\n" +
"]>\n" +
"<project>\n" +
" <actions/>\n" +
" <description>&foo;</description>\n" +
" <keepDependencies>false</keepDependencies>\n" +
" <properties/>\n" +
" <scm class=\"hudson.scm.NullSCM\"/>\n" +
" <canRoam>true</canRoam>\n" +
" <triggers/>\n" +
" <builders/>\n" +
" <publishers/>\n" +
" <buildWrappers/>\n" +
"</project>";
StringWriter stringWriter = new StringWriter();
try {
XMLUtils.safeTransform(new StreamSource(new StringReader(xml)), new StreamResult(stringWriter));
// if no exception then JAXP is swallowing these - so there should be no entity in the description.
assertThat(stringWriter.toString(), containsString("<description/>"));
} catch (TransformerException ex) {
assertThat(ex.getMessage(), containsString("Refusing to resolve entity"));
}
}
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlIDoesNotFail() throws Exception {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<project>\n" +
" <actions/>\n" +
" <description>&amp;</description>\n" +
" <keepDependencies>false</keepDependencies>\n" +
" <properties/>\n" +
" <scm class=\"hudson.scm.NullSCM\"/>\n" +
" <canRoam>true</canRoam>\n" +
" <triggers/>\n" +
" <builders/>\n" +
" <publishers/>\n" +
" <buildWrappers/>\n" +
"</project>";
StringWriter stringWriter = new StringWriter();
XMLUtils.safeTransform(new StreamSource(new StringReader(xml)), new StreamResult(stringWriter));
// make sure that normal entities are retained.
assertThat(stringWriter.toString(), containsString("<description>&amp;</description>"));
}
}
jenkins (1.600) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Sat, 28 Feb 2015 09:47:38 -0800
jenkins (1.599) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
......
......@@ -98,6 +98,7 @@ THE SOFTWARE.
<slf4jVersion>1.7.7</slf4jVersion> <!-- < 1.6.x version didn't specify the license (MIT) -->
<maven-plugin.version>2.7.1</maven-plugin.version>
<matrix-project.version>1.4.1</matrix-project.version>
<animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
<java.level>6</java.level>
......
......@@ -93,7 +93,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>1.0-beta-1</version>
<version>${matrix-project.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
......
/*
* The MIT License
*
* Copyright 2015 James Nord.
*
* 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 hudson.model;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.StringReader;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.text.IsEmptyString.isEmptyOrNullString;
import static org.junit.Assert.assertThat;
import org.jvnet.hudson.test.Issue;
public class AbstractItemSecurityTest {
@Rule
public JenkinsRule jenkinsRule = new JenkinsRule();
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlDoesNotProcessForeignResources() throws Exception {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[\n" +
" <!ENTITY foo SYSTEM \"file:///\">\n" +
"]>\n" +
"<project>\n" +
" <description>&foo;</description>\n" +
" <scm class=\"hudson.scm.NullSCM\"/>\n" +
"</project>";
FreeStyleProject project = jenkinsRule.createFreeStyleProject("security-167");
project.setDescription("Wibble");
try {
project.updateByXml(new StreamSource(new StringReader(xml)));
// if we didn't fail JAXP has thrown away the entity.
assertThat(project.getDescription(), isEmptyOrNullString());
} catch (IOException ex) {
assertThat(ex.getCause(), not(nullValue()));
assertThat(ex.getCause().getMessage(), containsString("Refusing to resolve entity"));
}
}
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlDoesNotFail() throws Exception {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<project>\n" +
" <description>&amp;</description>\n" +
" <scm class=\"hudson.scm.NullSCM\"/>\n" +
"</project>";
FreeStyleProject project = jenkinsRule.createFreeStyleProject("security-167");
project.updateByXml((StreamSource) new StreamSource(new StringReader(xml)));
assertThat(project.getDescription(), is("&")); // the entity is transformed
}
}
......@@ -23,9 +23,11 @@
*/
package hudson.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import com.gargoylesoftware.htmlunit.Page;
import java.io.File;
import java.net.HttpURLConnection;
import org.junit.Rule;
......@@ -54,6 +56,27 @@ public class ApiTest {
assertEquals("<root/>", page.getWebResponse().getContentAsString());
}
/**
* Test that calling the XML API with the XPath <code>document</code> function fails.
*
* @throws Exception if so
*/
@Issue("SECURITY-165")
@Test public void xPathDocumentFunction() throws Exception {
File f = new File(j.jenkins.getRootDir(), "queue.xml");
JenkinsRule.WebClient client = j.createWebClient();
try {
client.goTo("api/xml?xpath=document(\"" + f.getAbsolutePath() + "\")", "application/xml");
fail("Should become 500 error");
} catch (com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException e) {
String contentAsString = e.getResponse().getContentAsString();
j.assertStringContains(
contentAsString,
"Illegal function: document");
}
}
@Test
@Issue("JENKINS-3267")
public void wrappedOneItem() throws Exception {
......
/*
* The MIT License
*
* Copyright 2015 Jesse Glick.
*
* 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 hudson.model;
import hudson.util.FormValidation;
import java.net.URL;
import java.util.Set;
import java.util.TreeSet;
import net.sf.json.JSONObject;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.WithoutJenkins;
@Issue("SECURITY-163")
public class DownloadService2Test {
@Rule public JenkinsRule r = new JenkinsRule();
@Test public void updateNow() throws Exception {
for (DownloadService.Downloadable d : DownloadService.Downloadable.all()) {
FormValidation v = d.updateNow();
assertEquals(v.toString(), FormValidation.Kind.OK, v.kind);
}
}
@WithoutJenkins
@Test public void loadJSONHTML() throws Exception {
assertRoots("[list, signature]", "hudson.tasks.Maven.MavenInstaller.json.html"); // format used by most tools
assertRoots("[data, signature, version]", "hudson.tools.JDKInstaller.json.html"); // anomalous format
}
private static void assertRoots(String expected, String file) throws Exception {
URL resource = DownloadService2Test.class.getResource(file);
assertNotNull(file, resource);
JSONObject json = JSONObject.fromObject(DownloadService.loadJSONHTML(resource));
@SuppressWarnings("unchecked") Set<String> keySet = json.keySet();
assertEquals(expected, new TreeSet<String>(keySet).toString());
}
}
......@@ -5,6 +5,7 @@ import java.io.IOException;
import java.net.URL;
import java.util.Set;
import java.util.TreeSet;
import jenkins.model.DownloadSettings;
import net.sf.json.JSONObject;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.HudsonTestCase;
......@@ -30,6 +31,7 @@ public class DownloadServiceTest extends HudsonTestCase {
// to bypass the URL restriction, we'll trigger downloadService.download ourselves
job = new Downloadable("test", "UNUSED");
Downloadable.all().add(job);
DownloadSettings.get().setUseBrowser(true);
}
@Issue("JENKINS-5536")
......
......@@ -46,7 +46,7 @@ public class UpdateCenter2Test {
@RandomlyFails("SocketTimeoutException from goTo due to GET http://localhost:…/update-center.json?…")
@Test public void install() throws Exception {
UpdateSite.neverUpdate = false;
j.createWebClient().goTo(""); // load the metadata
j.jenkins.pluginManager.doCheckUpdatesServer(); // load the metadata
DownloadJob job = (DownloadJob) j.jenkins.getUpdateCenter().getPlugin("changelog-history").deploy().get(); // this seems like one of the smallest plugin
System.out.println(job.status);
assertTrue(job.status instanceof Success);
......
/*
* The MIT License
*
* Copyright (c) 2015, 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 hudson.security;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.User;
import hudson.security.pages.SignupPage;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import org.jvnet.hudson.test.Issue;
/**
* Tests the signup page of {@link hudson.security.HudsonPrivateSecurityRealm}
* TODO: Add to {@link hudson.security.HudsonPrivateSecurityRealmTest} going forward
*/
public class HudsonPrivateSecurityRealm2Test {
@Rule
public JenkinsRule rule = new JenkinsRule();
@Test
public void signup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("alice");
signup.enterPassword("alice");
signup.enterFullName("Alice User");
signup.enterEmail("alice@nowhere.com");
HtmlPage success = signup.submit(rule);
assertThat(success.getElementById("main-panel").getTextContent(), containsString("Success"));
assertThat(success.getAnchorByHref("/jenkins/user/alice").getTextContent(), containsString("Alice User"));
assertEquals("Alice User", securityRealm.getUser("alice").getDisplayName());
}
@Issue("SECURITY-166")
@Test
public void anonymousCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("anonymous");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("anonymous", false, Collections.emptyMap()));
}
@Issue("SECURITY-166")
@Test
public void systemCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("system");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("system",false, Collections.emptyMap()));
}
/**
* We don't allow prohibited fullnames since this may encumber auditing.
*/
@Issue("SECURITY-166")
@Test
public void fullNameOfUnknownCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("unknown2");
signup.enterPassword("unknown2");
signup.enterFullName("unknown");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a full name");
assertNull(User.get("unknown2",false, Collections.emptyMap()));
}
}
package hudson.security.pages;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.jvnet.hudson.test.JenkinsRule;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
/**
* The the signup page for {@link hudson.security.HudsonPrivateSecurityRealm}
*/
public class SignupPage {
public HtmlForm signupForm;
public final HtmlPage signupPage;
public SignupPage(HtmlPage signupPage) {
this.signupPage = signupPage;
assertNotNull("The sign up page has a username field.", this.signupPage.getElementById("username"));
for (HtmlForm signupForm : this.signupPage.getForms()) {
if (signupForm.getInputsByName("username").size() == 0)
continue;
this.signupForm = signupForm;
}
}
public void enterUsername(String username) {
signupForm.getInputByName("username").setValueAttribute(username);
}
/**
* Enters the password in password1 and password2.
* You can then call {@link #enterPassword2(String)} if you want them to be different.
* @param password
*/
public void enterPassword(String password) {
signupForm.getInputByName("password1").setValueAttribute(password);
signupForm.getInputByName("password2").setValueAttribute(password);
}
public void enterPassword2(String password2) {
signupForm.getInputByName("password2").setValueAttribute(password2);
}
public void enterFullName(String fullName) {
signupForm.getInputByName("fullname").setValueAttribute(fullName);
}
public void enterEmail(String email) {
signupForm.getInputByName("email").setValueAttribute(email);
}
public HtmlPage submit(JenkinsRule rule) throws Exception {
return rule.submit(signupForm);
}
public void assertErrorContains(String msg) {
assertThat(signupForm.getElementById("main-panel").getTextContent(),containsString(msg));
}
}
......@@ -36,6 +36,7 @@ import hudson.model.Result;
import static hudson.tasks.LogRotatorTest.build;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.List;
import jenkins.util.VirtualFile;
......@@ -126,6 +127,34 @@ public class ArtifactArchiverTest {
assertEquals("lodge", kids[0].getName());
// do not check that it .exists() since its target has not been archived
}
@Issue("SECURITY-162")
@Test public void outsideSymlinks() throws Exception {
final FreeStyleProject p = j.createFreeStyleProject();
p.getBuildersList().add(new TestBuilder() {
@Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
FilePath ws = build.getWorkspace();
if (ws == null) {
return false;
}
ws.child("hack").symlinkTo(p.getConfigFile().getFile().getAbsolutePath(), listener);
return true;
}
});
p.getPublishersList().add(new ArtifactArchiver("hack", "", false, true));
FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
List<FreeStyleBuild.Artifact> artifacts = b.getArtifacts();
assertEquals(1, artifacts.size());
FreeStyleBuild.Artifact artifact = artifacts.get(0);
assertEquals("hack", artifact.relativePath);
VirtualFile[] kids = b.getArtifactManager().root().list();
assertEquals(1, kids.length);
assertEquals("hack", kids[0].getName());
assertFalse(kids[0].isDirectory());
assertFalse(kids[0].isFile());
assertFalse(kids[0].exists());
j.createWebClient().assertFails(b.getUrl() + "artifact/hack", HttpURLConnection.HTTP_NOT_FOUND);
}
private void runNewBuildAndStartUnitlIsCreated(AbstractProject project) throws InterruptedException{
int buildNumber = project.getNextBuildNumber();
......
<!DOCTYPE html><html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><script>window.onload = function () { window.parent.postMessage(JSON.stringify(
{
"list": [
{
"id": "3.2.2",
"name": "3.2.2",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.2.2-bin.zip"
},
{
"id": "3.2.1",
"name": "3.2.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.2.1-bin.zip"
},
{
"id": "3.1.1",
"name": "3.1.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.1.1-bin.zip"
},
{
"id": "3.1.0",
"name": "3.1.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.1.0-bin.zip"
},
{
"id": "3.0.5",
"name": "3.0.5",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.5-bin.zip"
},
{
"id": "3.0.4",
"name": "3.0.4",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.4-bin.zip"
},
{
"id": "3.0.3",
"name": "3.0.3",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.3-bin.zip"
},
{
"id": "3.0.2",
"name": "3.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.2-bin.zip"
},
{
"id": "3.0.1",
"name": "3.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0.1-bin.zip"
},
{
"id": "3.0",
"name": "3.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-3.0-bin.zip"
},
{
"id": "2.2.1",
"name": "2.2.1",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.2.1-bin.zip"
},
{
"id": "2.2.0",
"name": "2.2.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.2.0-bin.zip"
},
{
"id": "2.1.0",
"name": "2.1.0",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.1.0-bin.zip"
},
{
"id": "2.0.11",
"name": "2.0.11",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.11-bin.zip"
},
{
"id": "2.0.10",
"name": "2.0.10",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.10-bin.zip"
},
{
"id": "2.0.9",
"name": "2.0.9",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.9-bin.zip"
},
{
"id": "2.0.8",
"name": "2.0.8",
"url": "http://archive.apache.org/dist/maven/binaries/apache-maven-2.0.8-bin.zip"
},
{
"id": "2.0.7",
"name": "2.0.7",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.7-bin.zip"
},
{
"id": "2.0.6",
"name": "2.0.6",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.6-bin.zip"
},
{
"id": "2.0.5",
"name": "2.0.5",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.5-bin.zip"
},
{
"id": "2.0.4",
"name": "2.0.4",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.4-bin.zip"
},
{
"id": "2.0.3",
"name": "2.0.3",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.3-bin.zip"
},
{
"id": "2.0.2",
"name": "2.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.2-bin.zip"
},
{
"id": "2.0.1",
"name": "2.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0.1-bin.zip"
},
{
"id": "2.0",
"name": "2.0",
"url": "http://archive.apache.org/dist/maven/binaries/maven-2.0-bin.zip"
},
{
"id": "1.1",
"name": "1.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.1.zip"
},
{
"id": "1.0.2",
"name": "1.0.2",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.2.zip"
},
{
"id": "1.0.1",
"name": "1.0.1",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.1.zip"
},
{
"id": "1.0",
"name": "1.0",
"url": "http://archive.apache.org/dist/maven/binaries/maven-1.0.zip"
}
],
"signature": {
"certificates": ["MIICmTCCAYECBQDerb70MA0GCSqGSIb3DQEBBAUAMIGKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxGDAWBgNVBAoTD0plbmtpbnMgUHJvamVjdDEaMBgGA1UEAxMRS29oc3VrZSBLYXdhZ3VjaGkxHTAbBgkqhkiG9w0BCQEWDmtrQGtvaHN1a2Uub3JnMB4XDTE0MDEyNzIxMDQwN1oXDTE1MDEyNzIxMDQwN1owXjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExGDAWBgNVBAoTD0plbmtpbnMgUHJvamVjdDEgMB4GA1UEAxMXQ29tbXVuaXR5IFVwZGF0ZSBDZW50ZXIwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAvAYxdnnMyREVQkfsMmGNXj2mFMgur+jjavJx5Wjc6Mfiq1x33Ps7qprhaklHmCg720Xe30E2+I/5R00XcUA+CwIDAQABMA0GCSqGSIb3DQEBBAUAA4IBAQCv9qYtq4miozdu/uzCvj2Dfj8Ms39ATivtuPiHCWagcOiR1SAjKI4DTdbz9WfNHH67TQrSSOwt7+HBjBVVh8WP1bI+OuJwgqmjcTuy14tByA3I5QZqM05SvZnVYY9ce5uXXNxe+FMwTRvnywbGPOPGG9xnfEsfN7NUjpAYbgM6fIkBMepOeL38IPUJb5pbDvM+F1zPaEJxnhGxnj3CuLu0XsI0h2wKCak1cJaLACJ+YSwPZju5/xNjQSibxAeoWh4u3JDQdgMSIOVGZWndypvr/xhLT2mVdXHSU7kpNB8Ojz9nC62g7qrC22bgUa1cGVIlljIi2Sf/xlXLu9Csksf+"],
"correct_digest": "NCZ9Z7HAwOMg7MnVrEVUsR5b+UA=",
"correct_signature": "ne6nBPs70d9Hh1gOMiKlVhj+zLbNjbMMVpn0Wn7MwF14MpVlCenkJouOw9b1Bq6Ay5L6bJ4iVvoCbvFhJyRrcg==",
"digest": "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
"signature": "PzSbGGnXzyOz2elTuaHwshl0uz/BZ50XcsuD/dyKq8fAyfqT27n2ghZETXAUKia4lFpTlqtuIm9+NySEbxYuTQ=="
}
}
),'*'); };</script></body></html>
\ No newline at end of file
......@@ -358,7 +358,13 @@ THE SOFTWARE.
<artifactItem>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>1.3</version>
<version>${matrix-project.version}</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.13</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
......
此差异由.gitattributes 抑制。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册