Skip to content

Commit

Permalink
JENKINS-25771 - Add started by a member of group JobRestriction
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher Suarez committed Jan 9, 2015
1 parent 47b292f commit f783d6b
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.509.2</version>
<version>1.588</version>
</parent>

<groupId>com.synopsys.arc.jenkinsci.plugins</groupId>
Expand Down
@@ -0,0 +1,193 @@
/*
* The MIT License
*
* Copyright 2014 Oleg Nenashev <o.v.nenashev@gmail.com>.
*
* 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 com.synopsys.arc.jenkinsci.plugins.jobrestrictions.restrictions.job;

import hudson.Extension;
import hudson.model.Action;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Queue;
import hudson.model.Run;
import hudson.security.SecurityRealm;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import jenkins.model.Jenkins;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.UserDetails;
import org.kohsuke.stapler.DataBoundConstructor;

import com.synopsys.arc.jenkinsci.plugins.jobrestrictions.Messages;
import com.synopsys.arc.jenkinsci.plugins.jobrestrictions.restrictions.JobRestriction;
import com.synopsys.arc.jenkinsci.plugins.jobrestrictions.restrictions.JobRestrictionDescriptor;
import com.synopsys.arc.jenkinsci.plugins.jobrestrictions.util.GroupSelector;

/**
*
* @author Oleg Nenashev <o.v.nenashev@gmail.com>
* @since 0.4
*/
public class StartedByMemberOfGroupRestriction extends JobRestriction {

private final List<GroupSelector> groupList;
private final boolean checkGroupsFromUpstreamProjects;

private transient Set<String> acceptedGroups = null;

@DataBoundConstructor
public StartedByMemberOfGroupRestriction(List<GroupSelector> groupList,
boolean checkGroupsFromUpstreamProjects) {
this.groupList = groupList;
this.checkGroupsFromUpstreamProjects = checkGroupsFromUpstreamProjects;
}

public List<GroupSelector> getGroupList() {
return groupList;
}

public boolean isCheckGroupsFromUpstreamProjects() {
return checkGroupsFromUpstreamProjects;
}

private synchronized @Nonnull
Set<String> getAcceptedGroups() {
if (acceptedGroups == null) {
final List<GroupSelector> selectors = getGroupList();
acceptedGroups = new HashSet<String>(selectors.size());
for (GroupSelector selector : selectors) {
acceptedGroups.add(selector.getSelectedGroupId()); // merge
// equal
// entries
}
}
return acceptedGroups;
}

private boolean acceptsUser(@CheckForNull String userId) {
if (userId == null) {
return false;
}
SecurityRealm sr = Jenkins.getInstance().getSecurityRealm();
UserDetails userDetails = sr.loadUserByUsername(userId);

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Jan 9, 2015

Member

No handling of possible exceptions

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Jan 9, 2015

Member

@csms
In any case, I want to switch the implementation to User::getAuthorities().
Seems it will have a better performance due to the internal caching.

Do you agree?

for (String groupId : getAcceptedGroups()) {
GrantedAuthority[] authorities = userDetails.getAuthorities();
for (GrantedAuthority auth : authorities) {
String authority = auth.getAuthority();
if (authority.equals(groupId)) {
return true;
}
}
}
return false;
}

/** package */
boolean canTake(@Nonnull List<Cause> causes) {
boolean userIdCause = false;
boolean rebuildCause = false;
boolean upstreamCause = false;
boolean aUserIdWasNotAccepted =false;
boolean userIdCauseExists=false;;
for (Cause cause : causes) {
if (cause.getClass().equals(Cause.UserIdCause.class) && !aUserIdWasNotAccepted) {
userIdCauseExists=true;
//if several userIdCauses exists, be defensive and don't allow if one is not accepted.
final @CheckForNull
String startedBy = ((Cause.UserIdCause) cause).getUserId();
if(acceptsUser(startedBy) )
{
userIdCause = true;
}
else
{
aUserIdWasNotAccepted=true;
userIdCause=false;
}
}
if (checkGroupsFromUpstreamProjects
&& cause.getClass().equals(Cause.UpstreamCause.class)) {

final List<Cause> upstreamCauses = ((Cause.UpstreamCause) cause)
.getUpstreamCauses();

if (canTake(upstreamCauses)) // Recursive call to iterate
// through all causes
{
upstreamCause = true;
}
}

}

//userId has preceedence
if(userIdCauseExists)
{
return userIdCause;
}
else//If no update cause exists we should also return false...
{
return upstreamCause;
}
}

@Override
public boolean canTake(Queue.BuildableItem item) {

List<Action> allActions = (List<Action>) item.getAllActions();

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Jan 9, 2015

Member

getActions() is enough IMO. It's hard to imagine a cause which comes from a transient action.
I'll modify the logic to make it compatible with 1.509.3

If you have any use-cases for transient actions, I can add their support as well

List<Cause> causes = new ArrayList<Cause>();
for (Action action : allActions) {
try {
CauseAction causeAction = (CauseAction) action;
causes.addAll(causeAction.getCauses());
} catch (ClassCastException cce) {

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Jan 9, 2015

Member

Bad practice, which strongly decreases the performance.
instanceof should be used

}

}

return canTake(causes);
}

@Override
public boolean canTake(Run run) {
return canTake(run.getCauses());
}

@Extension
public static class DescriptorImpl extends JobRestrictionDescriptor {

@Override
public String getDisplayName() {
return Messages
.restrictions_Job_StartedByMemberOfGroupRestriction_displayName();
}
}
}
@@ -0,0 +1,130 @@
/*
* The MIT License
*
* Copyright 2014 Oleg Nenashev <nenashev@synopsys.com>, Synopsys 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 com.synopsys.arc.jenkinsci.plugins.jobrestrictions.util;

import hudson.Extension;
import hudson.Functions;
import hudson.Util;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.User;
import hudson.security.SecurityRealm;
import hudson.security.GroupDetails;
import hudson.security.UserMayOrMayNotExistException;
import hudson.util.FormValidation;
import hudson.util.FormValidation.Kind;

import java.io.Serializable;

import javax.annotation.CheckForNull;

import jenkins.model.Jenkins;

import org.acegisecurity.AuthenticationException;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.springframework.dao.DataAccessException;

/**
* Describable Item, which allows to configure a user.
* @since 0.4
*/
//TODO: Autocompletion
public class GroupSelector implements Describable<GroupSelector>, Serializable {

/**ID of the user*/
@CheckForNull String selectedGroupId;

@DataBoundConstructor
public GroupSelector(@CheckForNull String selectedGroupId) {
this.selectedGroupId = hudson.Util.fixEmptyAndTrim(selectedGroupId);
}

@CheckForNull
public String getSelectedGroupId() {
return selectedGroupId;
}

@Override
public Descriptor<GroupSelector> getDescriptor() {
return DESCRIPTOR;
}

@Override
public boolean equals(Object obj) {
if (obj instanceof GroupSelector) {
GroupSelector cmp = (GroupSelector)obj;
return selectedGroupId != null ? selectedGroupId.equals(cmp.selectedGroupId) : cmp.selectedGroupId == null;
}
return false;
}

@Override
public int hashCode() {
int hash = 7;
hash = 17 * hash + (selectedGroupId != null ? selectedGroupId.hashCode() : 0);
return hash;
}

@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public static class DescriptorImpl extends Descriptor<GroupSelector> {

@Override
public String getDisplayName() {
return "N/A";
}

public FormValidation doCheckSelectedGroupId(@QueryParameter String selectedGroupId) {
selectedGroupId = Util.fixEmptyAndTrim(selectedGroupId);
SecurityRealm sr = Jenkins.getInstance().getSecurityRealm();
String eSelectedGroupId = Functions.escape(selectedGroupId);
if (selectedGroupId == null) {
return FormValidation.error("Field is empty");
}

if(selectedGroupId.equals("authenticated"))
// system reserved group
return FormValidation.ok();

try {
GroupDetails details = sr.loadGroupByGroupname(selectedGroupId);
if(details==null)
return FormValidation.warning("Group " + selectedGroupId + " is not registered in Jenkins");
return FormValidation.ok();
} catch (UserMayOrMayNotExistException e) {
// undecidable, meaning the group may exist
return FormValidation.respond(Kind.OK, eSelectedGroupId);
} catch (UsernameNotFoundException e) {
// fall through next
} catch (DataAccessException e) {
// fall through next
} catch (AuthenticationException e) {
// fall through next
}
return FormValidation.warning("Group " + selectedGroupId + " is not registered in Jenkins");
}
}
}
Expand Up @@ -11,5 +11,6 @@ restrictions.Logic.Not=Not
restrictions.Job.RegexName=Regular Expression (Job Name)
restrictions.Job.RegexName.OkMessage=Pattern is valid
restrictions.Job.RegexName.FailMessage=Pattern is invalid and will be ignored.
restrictions.Job.StartedByMemberOfGroupRestriction.displayName=Started By member of group
restirctions.Stuff.MultipleSuffix=(multiple entries)

@@ -0,0 +1,35 @@
<!--
* The MIT License
*
* Copyright 2013 Synopsys Inc., Oleg Nenashev <nenashev@synopsys.com>
*
* 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" xmlns:p="/lib/hudson/project">

<f:entry title="${%groupList}">
<f:repeatableProperty field="groupList" add="${%Add group}"/>
</f:entry>
<f:entry field="checkGroupsFromUpstreamProjects">
<f:checkbox title="${%checkGroupsFromUpstreamProjects}" checked="${it.checkGroupsFromUpstreamProjects}"/>
</f:entry>
</j:jelly>
@@ -0,0 +1,3 @@
groupList=Group list
checkGroupsFromUpstreamProjects=Accept upstream runs started by groups above

1 comment on commit f783d6b

@cubasuarez
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you I'm learning to crawl here :-).

Please sign in to comment.