Skip to content
Permalink
Browse files

Merge pull request #27 from ikedam/feature/JENKINS-20398_CopyPermission

[JENKINS-20398] Job Property to define projects that can copy artifacts
  • Loading branch information
ikedam committed Feb 11, 2014
2 parents ddb5694 + 9c9dfa7 commit f81b6ac3274bfb811b7fd1bda694feff3c1b0c8a
@@ -315,6 +315,13 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene
}

private boolean canReadFrom(Job<?, ?> job, AbstractBuild<?, ?> build) {
if ((job instanceof AbstractProject) && CopyArtifactPermissionProperty.canCopyArtifact(
build.getProject().getRootProject(),
((AbstractProject<?,?>)job).getRootProject()
)) {
return true;
}

if (!ACL.SYSTEM.equals(Jenkins.getAuthentication())) {
// if the build does not run on SYSTEM authorization,
// Jenkins is configured to use QueueItemAuthenticator.
@@ -0,0 +1,260 @@
/*
* The MIT License
*
* Copyright (c) 2013 IKEDA Yasuyuki
*
* 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.plugins.copyartifact;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import hudson.Extension;
import hudson.model.AutoCompletionCandidates;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.AbstractProject;
import hudson.util.FormValidation;

/**
* Job Property to define projects that can copy artifacts of this project.
*/
public class CopyArtifactPermissionProperty extends JobProperty<AbstractProject<?,?>> {
public static final String PROPERTY_NAME = "copy-artifact-permission-property";

private final List<String> projectNameList;

/**
* @return list of project names that can copy artifacts of this project.
*/
public List<String> getProjectNameList() {
return projectNameList;
}

/**
* @return comma-separated project names that can copy artifacts of this project.
*/
public String getProjectNames() {
return StringUtils.join(projectNameList, ',');
}

/**
* Constructor
*
* @param projectNames comma-separated project names that can copy artifacts of this project.
*/
@DataBoundConstructor
public CopyArtifactPermissionProperty(String projectNames) {
List<String> rawProjectNameList = Arrays.asList((projectNames != null)?StringUtils.split(projectNames, ','):new String[0]);
projectNameList = new ArrayList<String>(rawProjectNameList.size());
for (String rawProjectName: rawProjectNameList) {
if (StringUtils.isBlank(rawProjectName)) {
continue;
}
projectNameList.add(StringUtils.trim(rawProjectName));
}
}

/**
* @param copier a project who wants to copy artifacts of this project.
* @return whether copier is allowed to copy artifacts of this project.
*/
public boolean canCopiedBy(AbstractProject<?,?> copier) {
String copierName = copier.getRelativeNameFrom(owner.getParent());
for (String projectName: getProjectNameList()) {
if (isNameMatch(copierName, projectName)) {
return true;
}
}
return false;
}

/**
* package scope for testing purpose.
*
* @param name
* @param pattern
* @return whether name matches pattern.
*/
/*package*/ static boolean isNameMatch(String name, String pattern) {
if (pattern == null || name == null) {
return false;
}
if (!pattern.contains("*")) {
// if no wild card, simply complete match.
return pattern.equals(name);
}

List<String> literals = Arrays.asList(pattern.split("\\*", -1));
String regex = StringUtils.join(Lists.transform(literals, new Function<String, String>() {
public String apply(String input) {
return Pattern.quote(input);
}
}), ".*");
return name.matches(regex);
}

/**
* Convenient wrapper for {@link CopyArtifactPermissionProperty#canCopiedBy(AbstractProject)}
*
* @param copier a project that wants to copy artifacts of copiee.
* @param copiee a owner of artifacts.
* @return whether copier can copy artifacts of copiee.
*/
public static boolean canCopyArtifact(AbstractProject<?,?> copier, AbstractProject<?,?> copiee) {
CopyArtifactPermissionProperty prop = copiee.getProperty(CopyArtifactPermissionProperty.class);
if (prop == null) {
return false;
}
return prop.canCopiedBy(copier);
}

/**
* Descriptor for {@link CopyArtifactPermissionProperty}.
*/
@Extension
public static class DescriptorImpl extends JobPropertyDescriptor {
/**
* @return name displayed in the project configuration page.
* @see hudson.model.Descriptor#getDisplayName()
*/
@Override
public String getDisplayName() {
return Messages.CopyArtifactPermissionProperty_DisplayName();
}

/**
* @return key name used in the configuration form.
*/
public String getPropertyName() {
return PROPERTY_NAME;
}

/**
* @param req
* @param formData
* @return
* @throws hudson.model.Descriptor.FormException
* @see hudson.model.JobPropertyDescriptor#newInstance(org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject)
*/
@Override
public CopyArtifactPermissionProperty newInstance(StaplerRequest req, JSONObject formData)
throws hudson.model.Descriptor.FormException {
if(formData == null || formData.isNullObject()) {
return null;
}
JSONObject form = formData.getJSONObject(getPropertyName());
if(form == null || form.isNullObject()) {
return null;
}

return (CopyArtifactPermissionProperty)super.newInstance(req, form);
}

/**
* package scope for testing purpose.
*
* @param projectNames
* @param context
* @return
*/
/*package*/ List<String> checkNotFoundProjects(String projectNames, ItemGroup<?> context) {
if (StringUtils.isBlank(projectNames)) {
return Collections.emptyList();
}
List<String> notFound = new ArrayList<String>();
for (String projectName: StringUtils.split(projectNames, ',')) {
if (StringUtils.isBlank(projectName)) {
continue;
}
projectName = StringUtils.trim(projectName);
if (projectName.contains("*")) {
// no check for pattern
continue;
}
AbstractProject<?,?> proj = Jenkins.getInstance().getItem(projectName, context, AbstractProject.class);
if (proj == null || proj.getRootProject() != proj || !proj.hasPermission(Item.READ)) {
// permission check is done only for root project.
notFound.add(projectName);
continue;
}
}
return notFound;
}

/**
* @param projectNames
* @return
*/
public FormValidation doCheckProjectNames(@QueryParameter String projectNames, @AncestorInPath ItemGroup<?> context) {
List<String> notFound = checkNotFoundProjects(projectNames, context);
if (!notFound.isEmpty()) {
return FormValidation.warning(Messages.CopyArtifactPermissionProperty_MissingProject(StringUtils.join(notFound, ",")));
}
return FormValidation.ok();
}

/**
* @param value
* @param context
* @return
*/
public AutoCompletionCandidates doAutoCompleteProjectNames(@QueryParameter String value, @AncestorInPath ItemGroup<?> context) {
AutoCompletionCandidates candidates = new AutoCompletionCandidates();
if (StringUtils.isBlank(value)) {
return candidates;
}
value = StringUtils.trim(value);
for (AbstractProject<?,?> project: Jenkins.getInstance().getAllItems(AbstractProject.class)) {
if (project.getRootProject() != project) {
// permission check is done only for root project.
continue;
}
if (!project.hasPermission(Item.READ)) {
continue;
}

String relativeName = project.getRelativeNameFrom(context);
if (relativeName.startsWith(value)) {
candidates.add(relativeName);
}
}
return candidates;
}
}
}
@@ -0,0 +1,31 @@
<!--
The MIT License
Copyright (c) 2013 IKEDA Yasuyuki
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">
<f:optionalBlock name="${descriptor.propertyName}" title="${descriptor.displayName}" checked="${instance != null}">
<f:entry title="${%Projects to allow copy artifacts}" field="projectNames">
<f:textbox autoCompleteDelimChar="," />
</f:entry>
</f:optionalBlock>
</j:jelly>
@@ -0,0 +1,24 @@
# The MIT License
#
# Copyright (c) 2013 IKEDA Yasuyuki
#
# 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.

# Projects\ to\ allow\ copy\ artifacts=成果物のコピーを許可するプロジェクト
Projects\ to\ allow\ copy\ artifacts=\u6210\u679c\u7269\u306e\u30b3\u30d4\u30fc\u3092\u8a31\u53ef\u3059\u308b\u30d7\u30ed\u30b8\u30a7\u30af\u30c8
@@ -0,0 +1,4 @@
<div>
Comma seperated list of projects that can copy artifacts of this project.
Wild card character ('*') is available.
</div>
@@ -0,0 +1,4 @@
<div>
このプロジェクトの成果物をコピーできるプロジェクトをカンマ区切りで指定します。
ワイルドカード ('*') を使用できます。
</div>
@@ -0,0 +1,3 @@
<div>
Define projects that can copy artifacts of this project without authentication.
</div>
@@ -0,0 +1,3 @@
<div>
このプロジェクトの成果物を、認証を必要とせずにコピーできるプロジェクトを指定します。
</div>
@@ -19,3 +19,5 @@ TriggeredBuildSelector.DisplayName=Upstream build that triggered this job
ParameterizedBuildSelector.DisplayName=Specified by a build parameter
BuildSelectorParameter.DisplayName=Build selector for Copy Artifact
WorkspaceSelector.DisplayName=Copy from WORKSPACE of latest completed build
CopyArtifactPermissionProperty.DisplayName=Permission to Copy Artifact
CopyArtifactPermissionProperty.MissingProject=Unable to find project: {0}
@@ -16,3 +16,5 @@ SpecificBuildSelector.DisplayName=\u7279\u5b9a\u306e\u30d3\u30eb\u30c9
ParameterizedBuildSelector.DisplayName=\u7279\u5b9a\u306e\u30d3\u30eb\u30c9\u30d1\u30e9\u30e1\u30fc\u30bf
BuildSelectorParameter.DisplayName=Copy Artifact\u7528\u30d3\u30eb\u30c9\u30bb\u30ec\u30af\u30bf\u30fc
WorkspaceSelector.DisplayName=\u6700\u65b0\u306e\u5b8c\u4e86\u3057\u305f\u30d3\u30eb\u30c9\u306e\u30ef\u30fc\u30af\u30b9\u30da\u30fc\u30b9\u304b\u3089\u30b3\u30d4\u30fc
CopyArtifactPermissionProperty.DisplayName=\u6210\u679c\u7269\u306e\u30b3\u30d4\u30fc\u306e\u8a31\u53ef
CopyArtifactPermissionProperty.MissingProject=\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0}

0 comments on commit f81b6ac

Please sign in to comment.
You can’t perform that action at this time.