diff --git a/pom.xml b/pom.xml index b46b0836..bab53b4c 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ 999999-SNAPSHOT 2.414.3 jenkinsci/${project.artifactId}-plugin + true @@ -135,6 +136,13 @@ + + + io.jenkins.plugins + manage-permission + 1.0.1 + test + org.jenkins-ci.plugins.workflow workflow-support diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java new file mode 100644 index 00000000..1aa9619e --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java @@ -0,0 +1,100 @@ +/* + * The MIT License + * + * Copyright 2024 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 org.jenkinsci.plugins.workflow.libs; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.ItemGroup; +import hudson.model.Job; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import jenkins.model.GlobalConfiguration; +import jenkins.model.Jenkins; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.StaplerRequest; + +/** + * Common code between {@link GlobalLibraries} and {@link GlobalUntrustedLibraries}. + */ +public abstract class AbstractGlobalLibraries extends GlobalConfiguration { + private List libraries = new ArrayList<>(); + + protected AbstractGlobalLibraries() { + load(); + } + + public abstract String getDescription(); + + public List getLibraries() { + return libraries; + } + + public void setLibraries(List libraries) { + this.libraries = libraries; + save(); + } + + @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + if (Jenkins.get().hasPermission(getRequiredGlobalConfigPagePermission())) { + setLibraries(Collections.emptyList()); // allow last library to be deleted + return super.configure(req, json); + } else { + return true; + } + } + + abstract static class AbstractForJob extends LibraryResolver { + @NonNull + protected abstract AbstractGlobalLibraries getConfiguration(); + + @NonNull @Override public final Collection forJob(@NonNull Job job, @NonNull Map libraryVersions) { + return getLibraries(); + } + + @NonNull @Override public final Collection fromConfiguration(@NonNull StaplerRequest request) { + if (Jenkins.get().hasPermission(getConfiguration().getRequiredGlobalConfigPagePermission())) { + return getLibraries(); + } + return Collections.emptySet(); + } + + @NonNull @Override public final Collection suggestedConfigurations(@NonNull ItemGroup group) { + return getLibraries(); + } + + private List getLibraries() { + return getConfiguration() + .getLibraries() + .stream() + .map(this::mayWrapLibrary) + .collect(Collectors.toList()); + } + + @NonNull + protected abstract LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalLibraries.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalLibraries.java index 48b1b025..7d04261c 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalLibraries.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalLibraries.java @@ -24,78 +24,49 @@ package org.jenkinsci.plugins.workflow.libs; -import hudson.Extension; -import hudson.model.ItemGroup; -import hudson.model.Job; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; import edu.umd.cs.findbugs.annotations.NonNull; -import jenkins.model.GlobalConfiguration; -import jenkins.model.Jenkins; -import net.sf.json.JSONObject; -import org.kohsuke.stapler.StaplerRequest; +import hudson.Extension; +import hudson.ExtensionList; /** * Manages libraries available to any job in the system. */ -@Extension public class GlobalLibraries extends GlobalConfiguration { - - public static @NonNull GlobalLibraries get() { - GlobalLibraries instance = GlobalConfiguration.all().get(GlobalLibraries.class); - if (instance == null) { // TODO would be useful to have an ExtensionList.getOrFail - throw new IllegalStateException(); - } - return instance; - } - - private List libraries = new ArrayList<>(); +@Extension public class GlobalLibraries extends AbstractGlobalLibraries { public GlobalLibraries() { - load(); + super(); } - public List getLibraries() { - return libraries; + @Override + public String getDescription() { + return Messages.GlobalLibraries_Description(); } - public void setLibraries(List libraries) { - this.libraries = libraries; - save(); + @NonNull + @Override + public String getDisplayName() { + return Messages.GlobalLibraries_DisplayName(); } - @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { - if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - setLibraries(Collections.emptyList()); // allow last library to be deleted - return super.configure(req, json); - } else { - return true; - } + public static @NonNull GlobalLibraries get() { + return ExtensionList.lookupSingleton(GlobalLibraries.class); } - @Extension(ordinal=0) public static class ForJob extends LibraryResolver { - - @Override public boolean isTrusted() { - return true; - } - - @NonNull @Override public Collection forJob(@NonNull Job job, @NonNull Map libraryVersions) { - return GlobalLibraries.get().getLibraries(); + @Extension(ordinal=0) public static class ForJob extends AbstractForJob { + @NonNull + protected GlobalLibraries getConfiguration() { + return get(); } - @NonNull @Override public Collection fromConfiguration(@NonNull StaplerRequest request) { - if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return GlobalLibraries.get().getLibraries(); - } - return Collections.emptySet(); + @Override + public boolean isTrusted() { + return true; } - @NonNull @Override public Collection suggestedConfigurations(@NonNull ItemGroup group) { - return GlobalLibraries.get().getLibraries(); + @NonNull + @Override + protected LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library) { + return library; } - } - } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibraries.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibraries.java new file mode 100644 index 00000000..12229489 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibraries.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * + * Copyright 2024 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 org.jenkinsci.plugins.workflow.libs; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.ExtensionList; +import hudson.security.Permission; +import jenkins.model.Jenkins; + +/** + * Manages untrusted libraries available to any job in the system. + */ +@Extension public class GlobalUntrustedLibraries extends AbstractGlobalLibraries { + + public GlobalUntrustedLibraries() { + super(); + } + + @Override + public String getDescription() { + return Messages.GlobalUntrustedLibraries_Description(); + } + + @NonNull + @Override + public String getDisplayName() { + return Messages.GlobalUntrustedLibraries_DisplayName(); + } + + public static @NonNull GlobalUntrustedLibraries get() { + return ExtensionList.lookupSingleton(GlobalUntrustedLibraries.class); + } + + @NonNull + @Override + public Permission getRequiredGlobalConfigPagePermission() { + return Jenkins.MANAGE; + } + + @Extension(ordinal=1) public static class ForJob extends AbstractForJob { + @NonNull + protected GlobalUntrustedLibraries getConfiguration() { + return get(); + } + + @Override + public boolean isTrusted() { + return false; + } + + @NonNull + @Override + protected LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library) { + return new ResolvedLibraryConfiguration(library, getClass().getName()); + } + } +} diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.jelly b/src/main/resources/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries/config.jelly similarity index 89% rename from src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.jelly rename to src/main/resources/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries/config.jelly index 72532152..918f429d 100644 --- a/src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries/config.jelly @@ -25,10 +25,10 @@ THE SOFTWARE. - - + + - + diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.properties b/src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.properties deleted file mode 100644 index 6235426b..00000000 --- a/src/main/resources/org/jenkinsci/plugins/workflow/libs/GlobalLibraries/config.properties +++ /dev/null @@ -1,25 +0,0 @@ -# The MIT License -# -# Copyright 2016 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. - -blurb=\ - Sharable libraries available to any Pipeline jobs running on this system. \ - These libraries will be trusted, meaning they run without \u201csandbox\u201d restrictions and may use @Grab. diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/libs/Messages.properties b/src/main/resources/org/jenkinsci/plugins/workflow/libs/Messages.properties index c5ad5a4b..67921dcb 100644 --- a/src/main/resources/org/jenkinsci/plugins/workflow/libs/Messages.properties +++ b/src/main/resources/org/jenkinsci/plugins/workflow/libs/Messages.properties @@ -24,3 +24,9 @@ LibraryDecorator.could_not_find_any_definition_of_librari=Could not find any def ResourceStep.library_resource_ambiguous_among_librari=Library resource {0} ambiguous among libraries {1} ResourceStep.no_such_library_resource_could_be_found_=No such library resource {0} could be found. SCMSourceRetriever.library_path_no_double_dot=Library path may not contain ".." +GlobalLibraries.DisplayName=Global Trusted Pipeline Libraries +GlobalLibraries.Description=Sharable libraries available to any Pipeline jobs running on this system. \ + These libraries will be trusted, meaning they run without \u201csandbox\u201d restrictions and may use @Grab. +GlobalUntrustedLibraries.Description=Sharable libraries available to any Pipeline jobs running on this system. \ + These libraries will be untrusted, meaning they run with \u201csandbox\u201d restrictions and cannot use @Grab. +GlobalUntrustedLibraries.DisplayName=Global Untrusted Pipeline Libraries diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/FolderLibrariesTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/FolderLibrariesTest.java index f8f537a1..449e2633 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/FolderLibrariesTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/FolderLibrariesTest.java @@ -172,19 +172,10 @@ public class FolderLibrariesTest { /** @see GrapeTest#outsideLibrarySandbox */ @Test public void noGrape() throws Exception { - sampleRepo1.init(); - sampleRepo1.write("src/pkg/Wrapper.groovy", - "package pkg\n" + - "@Grab('commons-primitives:commons-primitives:1.0')\n" + - "import org.apache.commons.collections.primitives.ArrayIntList\n" + - "class Wrapper {static def list() {new ArrayIntList()}}"); - sampleRepo1.git("add", "src"); - sampleRepo1.git("commit", "--message=init"); Folder d = r.jenkins.createProject(Folder.class, "d"); - d.getProperties().add(new FolderLibraries(Collections.singletonList(new LibraryConfiguration("grape", new SCMSourceRetriever(new GitSCMSource(null, sampleRepo1.toString(), "", "*", "", true)))))); + d.getProperties().add(new FolderLibraries(List.of(LibraryTestUtils.defineLibraryUsingGrab("grape", sampleRepo1)))); WorkflowJob p = d.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('grape@master') import pkg.Wrapper; echo(/should not have been able to run ${pkg.Wrapper.list()}/)", true)); - ScriptApproval.get().approveSignature("new org.apache.commons.collections.primitives.ArrayIntList"); r.assertLogContains("Annotation Grab cannot be used in the sandbox", r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0))); } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalLibrariesTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalLibrariesTest.java index efadbe5d..2c6f4adb 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalLibrariesTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalLibrariesTest.java @@ -24,12 +24,12 @@ package org.jenkinsci.plugins.workflow.libs; -import org.htmlunit.HttpMethod; -import org.htmlunit.WebRequest; -import org.htmlunit.html.HtmlPage; -import org.htmlunit.util.NameValuePair; -import hudson.model.Item; -import hudson.model.View; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import hudson.security.Permission; import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -38,10 +38,16 @@ import jenkins.plugins.git.GitSCMSource; import jenkins.plugins.git.GitSampleRepoRule; import jenkins.scm.impl.subversion.SubversionSCMSource; -import static org.hamcrest.Matchers.*; -import org.junit.Test; -import static org.junit.Assert.*; +import org.htmlunit.HttpMethod; +import org.htmlunit.WebRequest; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.util.NameValuePair; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.junit.ClassRule; import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockAuthorizationStrategy; @@ -50,42 +56,26 @@ public class GlobalLibrariesTest { @Rule public JenkinsRule r = new JenkinsRule(); @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); + @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); @Test public void configRoundtrip() throws Exception { r.configRoundtrip(); - GlobalLibraries gl = GlobalLibraries.get(); - assertEquals(Collections.emptyList(), gl.getLibraries()); - LibraryConfiguration foo = new LibraryConfiguration("foo", new SCMSourceRetriever(new SubversionSCMSource("foo", "https://phony.jenkins.io/foo/"))); - LibraryConfiguration bar = new LibraryConfiguration("bar", new SCMSourceRetriever(new GitSCMSource(null, "https://phony.jenkins.io/bar.git", "", "origin", "+refs/heads/*:refs/remotes/origin/*", "*", "", true))); - LibraryCachingConfiguration cachingConfiguration = new LibraryCachingConfiguration(120, "develop", "master stable"); - foo.setCachingConfiguration(cachingConfiguration); - bar.setDefaultVersion("master"); - bar.setImplicit(true); - bar.setAllowVersionOverride(false); - gl.setLibraries(Arrays.asList(foo, bar)); - r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). - grant(Jenkins.ADMINISTER).everywhere().to("alice") - ); - HtmlPage configurePage = r.createWebClient().login("alice").goTo("configure"); - assertThat(configurePage.getWebResponse().getContentAsString(), containsString("https://phony.jenkins.io/bar.git")); - r.submit(configurePage.getFormByName("config")); // JenkinsRule.configRoundtrip expanded to include login - List libs = gl.getLibraries(); - r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs); - libs = gl.getLibraries(); - r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs); - boolean noFoo = true; - for (LibraryConfiguration lib : libs) { - if ("foo".equals(lib.getName())) { - noFoo = false; - r.assertEqualDataBoundBeans(lib.getCachingConfiguration(), cachingConfiguration); - } - } - assertFalse("Missing a library called foo (should not happen)", noFoo); + configRoundtrip(r, GlobalLibraries.get(), Jenkins.ADMINISTER); } @Issue("SECURITY-1422") @Test public void checkDefaultVersionRestricted() throws Exception { + checkDefaultVersionRestricted(r, sampleRepo, GlobalLibraries.get()); + } + + @Test public void allowedGrape() throws Exception { + GlobalLibraries.get().setLibraries(List.of(LibraryTestUtils.defineLibraryUsingGrab("grape", sampleRepo))); + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("@Library('grape@master') import pkg.Wrapper; echo(/should be able to run ${pkg.Wrapper.list()}/)", true)); + r.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + static void checkDefaultVersionRestricted(JenkinsRule r, GitSampleRepoRule sampleRepo, AbstractGlobalLibraries gl) throws Exception { sampleRepo.init(); sampleRepo.write("vars/myecho.groovy", "def call() {echo 'something special'}"); sampleRepo.git("add", "vars"); @@ -96,7 +86,7 @@ public class GlobalLibrariesTest { .grant(Jenkins.ADMINISTER).everywhere().to("admin"); r.jenkins.setAuthorizationStrategy(s); LibraryConfiguration foo = new LibraryConfiguration("foo", new SCMSourceRetriever(new GitSCMSource(sampleRepo.toString()))); - GlobalLibraries.get().setLibraries(Arrays.asList(foo)); + gl.setLibraries(Arrays.asList(foo)); JenkinsRule.WebClient wc = r.createWebClient(); wc.setThrowExceptionOnFailingStatusCode(false); WebRequest req = new WebRequest(new URL(wc.getContextPath() + "/descriptorByName/" + @@ -116,4 +106,34 @@ public class GlobalLibrariesTest { containsString("Currently maps to revision")); } + static void configRoundtrip(JenkinsRule r, AbstractGlobalLibraries gl, Permission... alicePrivileges) throws Exception { + assertEquals(Collections.emptyList(), gl.getLibraries()); + LibraryConfiguration foo = new LibraryConfiguration("foo", new SCMSourceRetriever(new SubversionSCMSource("foo", "https://phony.jenkins.io/foo/"))); + LibraryConfiguration bar = new LibraryConfiguration("bar", new SCMSourceRetriever(new GitSCMSource(null, "https://phony.jenkins.io/bar.git", "", "origin", "+refs/heads/*:refs/remotes/origin/*", "*", "", true))); + LibraryCachingConfiguration cachingConfiguration = new LibraryCachingConfiguration(120, "develop", "master stable"); + foo.setCachingConfiguration(cachingConfiguration); + bar.setDefaultVersion("master"); + bar.setImplicit(true); + bar.setAllowVersionOverride(false); + gl.setLibraries(Arrays.asList(foo, bar)); + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). + grant(alicePrivileges).everywhere().to("alice") + ); + HtmlPage configurePage = r.createWebClient().login("alice").goTo("configure"); + assertThat(configurePage.getWebResponse().getContentAsString(), containsString("https://phony.jenkins.io/bar.git")); + r.submit(configurePage.getFormByName("config")); // JenkinsRule.configRoundtrip expanded to include login + List libs = gl.getLibraries(); + r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs); + libs = gl.getLibraries(); + r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs); + boolean noFoo = true; + for (LibraryConfiguration lib : libs) { + if ("foo".equals(lib.getName())) { + noFoo = false; + r.assertEqualDataBoundBeans(lib.getCachingConfiguration(), cachingConfiguration); + } + } + assertFalse("Missing a library called foo (should not happen)", noFoo); + } } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibrariesTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibrariesTest.java new file mode 100644 index 00000000..5ea5c832 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/GlobalUntrustedLibrariesTest.java @@ -0,0 +1,63 @@ +/* + * The MIT License + * + * Copyright 2016 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 org.jenkinsci.plugins.workflow.libs; + +import hudson.model.Result; +import java.util.List; +import jenkins.model.Jenkins; +import jenkins.plugins.git.GitSampleRepoRule; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class GlobalUntrustedLibrariesTest { + + @Rule public JenkinsRule r = new JenkinsRule(); + @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); + @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); + + @Test public void configRoundtrip() throws Exception { + r.configRoundtrip(); + GlobalLibrariesTest.configRoundtrip(r, GlobalUntrustedLibraries.get(), Jenkins.READ, Jenkins.MANAGE); + } + + @Issue("SECURITY-1422") + @Test public void checkDefaultVersionRestricted() throws Exception { + GlobalLibrariesTest.checkDefaultVersionRestricted(r, sampleRepo, GlobalUntrustedLibraries.get()); + } + + @Test public void noGrape() throws Exception { + GlobalUntrustedLibraries.get().setLibraries(List.of(LibraryTestUtils.defineLibraryUsingGrab("grape", sampleRepo))); + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("@Library('grape@master') import pkg.Wrapper; echo(/should not have been able to run ${pkg.Wrapper.list()}/)", true)); + r.assertLogContains("Annotation Grab cannot be used in the sandbox", r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0))); + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryTestUtils.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryTestUtils.java new file mode 100644 index 00000000..aac1c676 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryTestUtils.java @@ -0,0 +1,20 @@ +package org.jenkinsci.plugins.workflow.libs; + +import jenkins.plugins.git.GitSCMSource; +import jenkins.plugins.git.GitSampleRepoRule; + +final class LibraryTestUtils { + private LibraryTestUtils(){} + + static LibraryConfiguration defineLibraryUsingGrab(String libraryName, GitSampleRepoRule sampleRepo) throws Exception { + sampleRepo.init(); + sampleRepo.write("src/pkg/Wrapper.groovy", + "package pkg\n" + + "@Grab('commons-primitives:commons-primitives:1.0')\n" + + "import org.apache.commons.collections.primitives.ArrayIntList\n" + + "class Wrapper {static def list() {new ArrayIntList()}}"); + sampleRepo.git("add", "src"); + sampleRepo.git("commit", "--message=init"); + return new LibraryConfiguration(libraryName, new SCMSourceRetriever(new GitSCMSource(sampleRepo.toString()))); + } +}