All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License
 *
 * Copyright (c) 2010, Manufacture Française des Pneumatiques Michelin, Thomas Maurel
 *
 * 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.michelin.cio.hudson.plugins.rolestrategy;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import hudson.Extension;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Run;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.SidACL;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

/**
 * Role-based authorization strategy.
 * @author Thomas Maurel
 */
public class RoleBasedAuthorizationStrategy extends AuthorizationStrategy {

  public final static String GLOBAL    = "globalRoles";
  public final static String PROJECT   = "projectRoles";

  /** {@link RoleMap}s associated to each {@link AccessControlled} class */
  private final Map  grantedRoles = new HashMap < String, RoleMap >();

  /**
   * Get the root ACL.
   * @return The global ACL
   */
  @Override
  public SidACL getRootACL() {
    RoleMap root = getRoleMap(GLOBAL);
    return root.getACL();
  }

  /**
   * Get the specific ACL for projects.
   * @param project The access-controlled project
   * @return The project specific ACL
   */
  @Override
  public ACL getACL(Job project) {
    SidACL acl;
    RoleMap roleMap = grantedRoles.get(PROJECT);
    if(roleMap == null) {
      acl = getRootACL();
    }
    else {
      // Create a sub-RoleMap matching the project name, and create an inheriting from root ACL
      acl = roleMap.newMatchingRoleMap(project.getName()).getACL().newInheritingACL(getRootACL());
    }
    return acl;
  }

  /**
   * Used by the container realm.
   * @return All the sids referenced by the strategy
   */
  @Override
  public Collection getGroups() {
    Set sids = new HashSet();
    for(Map.Entry entry : this.grantedRoles.entrySet()) {
      RoleMap roleMap = (RoleMap) entry.getValue();
      sids.addAll(roleMap.getSids(true));
    }
    return sids;
  }

  /**
   * Get the roles from the global {@link RoleMap}.
   * 

The returned sorted map is unmodifiable.

* @param type The object type controlled by the {@link RoleMap} * @return All roles from the global {@link RoleMap} */ public SortedMap> getGrantedRoles(String type) { RoleMap roleMap = this.getRoleMap(type); if(roleMap != null) { return roleMap.getGrantedRoles(); } return null; } /** * Get all the SIDs referenced by specified {@link RoleMap} type. * @param type The object type controlled by the {@link RoleMap} * @return All SIDs from the specified {@link RoleMap}. */ public Set getSIDs(String type) { RoleMap roleMap = this.getRoleMap(type); if(roleMap != null) { return roleMap.getSids(); } return null; } /** * Get the {@link RoleMap} associated to the given class. * @param type The object type controlled by the {@link RoleMap} * @return The associated {@link RoleMap} */ private RoleMap getRoleMap(String type) { RoleMap map; if(grantedRoles.containsKey(type)) { map = grantedRoles.get(type); } else { // Create it if it doesn't exist map = new RoleMap(); grantedRoles.put(type, map); } return map; } /** * Returns a map associating a string representation with each {@link RoleMap}. *

This method is intended to be used for XML serialization purposes (take * a look at the {@link ConverterImpl}) and, as such, must remain private * since it exposes all the security config.

*/ private Map getRoleMaps() { return grantedRoles; } /** * Add the given {@link Role} to the {@link RoleMap} associated to the provided class. * @param type The {@link AccessControlled} class referencing the {@link RoleMap} * @param role The {@link Role} to add */ private void addRole(String type, Role role) { RoleMap roleMap = this.grantedRoles.get(type); if(roleMap != null) { roleMap.addRole(role); } else { // Create the RoleMap if it doesnt exist roleMap = new RoleMap(); roleMap.addRole(role); grantedRoles.put(type, roleMap); } } /** * Assign a role to a sid * @param type The type of role * @param role The role to assign * @param sid The sid to assign to */ private void assignRole(String type, Role role, String sid) { RoleMap roleMap = this.grantedRoles.get(type); if(roleMap != null && roleMap.hasRole(role)) { roleMap.assignRole(role, sid); } } @Extension public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); /** * Converter used to persist and retrieve the strategy from disk. * *

This converter is there to manually handle the marshalling/unmarshalling * of this strategy: Doing so is a little bit dirty but allows to easily update * the plugin when new access controlled object (for the moment: Job and * Project) will be introduced. If it's the case, there's only the need to * update the getRoleMaps() method.

*/ public static class ConverterImpl implements Converter { public boolean canConvert(Class type) { return type==RoleBasedAuthorizationStrategy.class; } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { RoleBasedAuthorizationStrategy strategy = (RoleBasedAuthorizationStrategy)source; Map maps = strategy.getRoleMaps(); for(Map.Entry map : maps.entrySet()) { RoleMap roleMap = map.getValue(); writer.startNode("roleMap"); writer.addAttribute("type", map.getKey()); for(Map.Entry> grantedRole : roleMap.getGrantedRoles().entrySet()) { Role role = grantedRole.getKey(); if(role != null) { writer.startNode("role"); writer.addAttribute("name", role.getName()); writer.addAttribute("pattern", role.getPattern().pattern()); writer.startNode("permissions"); for(Permission permission : role.getPermissions()) { writer.startNode("permission"); writer.setValue(permission.getId()); writer.endNode(); } writer.endNode(); writer.startNode("assignedSIDs"); for(String sid : grantedRole.getValue()) { writer.startNode("sid"); writer.setValue(sid); writer.endNode(); } writer.endNode(); writer.endNode(); } } writer.endNode(); } } public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) { RoleBasedAuthorizationStrategy strategy = create(); while(reader.hasMoreChildren()) { reader.moveDown(); if(reader.getNodeName().equals("roleMap")) { String type = reader.getAttribute("type"); RoleMap map = new RoleMap(); while(reader.hasMoreChildren()) { reader.moveDown(); String name = reader.getAttribute("name"); String pattern = reader.getAttribute("pattern"); Set permissions = new HashSet(); String next = reader.peekNextChild(); if(next != null && next.equals("permissions")) { reader.moveDown(); while(reader.hasMoreChildren()) { reader.moveDown(); permissions.add(Permission.fromId(reader.getValue())); reader.moveUp(); } reader.moveUp(); } Role role = new Role(name, pattern, permissions); map.addRole(role); next = reader.peekNextChild(); if(next != null && next.equals("assignedSIDs")) { reader.moveDown(); while(reader.hasMoreChildren()) { reader.moveDown(); map.assignRole(role, reader.getValue()); reader.moveUp(); } reader.moveUp(); } reader.moveUp(); } strategy.grantedRoles.put(type, map); } reader.moveUp(); } return strategy; } protected RoleBasedAuthorizationStrategy create() { return new RoleBasedAuthorizationStrategy(); } } /** * Descriptor used to bind the strategy to the Web forms. */ public static final class DescriptorImpl extends GlobalMatrixAuthorizationStrategy.DescriptorImpl { @Override public String getDisplayName() { return Messages.RoleBasedAuthorizationStrategy_DisplayName(); } /** * Called on role management form's submission. */ public void doRolesSubmit(StaplerRequest req, StaplerResponse rsp) throws UnsupportedEncodingException, ServletException, FormException, IOException { Hudson.getInstance().checkPermission(Hudson.ADMINISTER); req.setCharacterEncoding("UTF-8"); JSONObject json = req.getSubmittedForm(); AuthorizationStrategy strategy = this.newInstance(req, json); Hudson.getInstance().setAuthorizationStrategy(strategy); // Persist the data Hudson.getInstance().save(); } /** * Called on role assignment form's submission. */ public void doAssignSubmit(StaplerRequest req, StaplerResponse rsp) throws UnsupportedEncodingException, ServletException, FormException, IOException { Hudson.getInstance().checkPermission(Hudson.ADMINISTER); req.setCharacterEncoding("UTF-8"); JSONObject json = req.getSubmittedForm(); AuthorizationStrategy oldStrategy = Hudson.getInstance().getAuthorizationStrategy(); if (json.has(GLOBAL) && json.has(PROJECT) && oldStrategy instanceof RoleBasedAuthorizationStrategy) { RoleBasedAuthorizationStrategy strategy = (RoleBasedAuthorizationStrategy) oldStrategy; Map maps = strategy.getRoleMaps(); for(Map.Entry map : maps.entrySet()) { RoleMap roleMap = map.getValue(); roleMap.clearSids(); JSONObject roles = json.getJSONObject(map.getKey()); for(Map.Entry r : (Set>)roles.getJSONObject("data").entrySet()) { String sid = r.getKey(); for(Map.Entry e : (Set>)r.getValue().entrySet()) { if(e.getValue()) { Role role = roleMap.getRole(e.getKey()); if(role != null && sid != null && !sid.equals("")) { roleMap.assignRole(role, sid); } } } } } // Persist the data Hudson.getInstance().save(); } } /** * Method called on Hudson Manage panel submission, and plugin specific forms * to create the {@link AuthorizationStrategy} object. */ @Override public AuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData) throws FormException { AuthorizationStrategy oldStrategy = Hudson.getInstance().getAuthorizationStrategy(); RoleBasedAuthorizationStrategy strategy; // If the form contains data, it means the method has been called by plugin // specifics forms, and we need to handle it. if (formData.has(GLOBAL) && formData.has(PROJECT) && oldStrategy instanceof RoleBasedAuthorizationStrategy) { strategy = new RoleBasedAuthorizationStrategy(); JSONObject globalRoles = formData.getJSONObject(GLOBAL); for(Map.Entry r : (Set>)globalRoles.getJSONObject("data").entrySet()) { String roleName = r.getKey(); Set permissions = new HashSet(); for(Map.Entry e : (Set>)r.getValue().entrySet()) { if(e.getValue()) { Permission p = Permission.fromId(e.getKey()); permissions.add(p); } } Role role = new Role(roleName, permissions); strategy.addRole(GLOBAL, role); RoleMap roleMap = ((RoleBasedAuthorizationStrategy) oldStrategy).getRoleMap(GLOBAL); if(roleMap != null) { Set sids = roleMap.getSidsForRole(roleName); if(sids != null) { for(String sid : sids) { strategy.assignRole(GLOBAL, role, sid); } } } } JSONObject projectRoles = formData.getJSONObject(PROJECT); for(Map.Entry r : (Set>)projectRoles.getJSONObject("data").entrySet()) { String roleName = r.getKey(); Set permissions = new HashSet(); String pattern = r.getValue().getString("pattern"); if(pattern != null) { r.getValue().remove("pattern"); } else { pattern = ".*"; } for(Map.Entry e : (Set>)r.getValue().entrySet()) { if(e.getValue()) { Permission p = Permission.fromId(e.getKey()); permissions.add(p); } } Role role = new Role(roleName, pattern, permissions); strategy.addRole(PROJECT, role); RoleMap roleMap = ((RoleBasedAuthorizationStrategy) oldStrategy).getRoleMap(PROJECT); if(roleMap != null) { Set sids = roleMap.getSidsForRole(roleName); if(sids != null) { for(String sid : sids) { strategy.assignRole(PROJECT,role, sid); } } } } } // When called from Hudson Manage panel, but was already on a role-based strategy else if(oldStrategy instanceof RoleBasedAuthorizationStrategy) { // Do nothing, keep the same strategy strategy = (RoleBasedAuthorizationStrategy) oldStrategy; } // When called from Hudson Manage panel, but when the previous strategy wasn't // role-based, it means we need to create an admin role, and assign it to the // current user to not throw him out of the webapp else { strategy = new RoleBasedAuthorizationStrategy(); Role adminRole = createAdminRole(); strategy.addRole(GLOBAL, adminRole); strategy.assignRole(GLOBAL, adminRole, getCurrentUser()); } return strategy; } /** * Create an admin role. */ private Role createAdminRole() { Set permissions = new HashSet(); for(PermissionGroup group : getGroups(GLOBAL)) { for(Permission permission : group) { permissions.add(permission); } } Role role = new Role("admin", permissions); return role; } /** * Get the current user ({@code Anonymous} if not logged-in). * @return Sid of the current user */ private String getCurrentUser() { PrincipalSid currentUser = new PrincipalSid(Hudson.getAuthentication()); return currentUser.getPrincipal(); } /** * Get the needed permissions groups. */ public List getGroups(String type) { List groups; if(type.equals(GLOBAL)) { groups = new ArrayList(PermissionGroup.getAll()); groups.remove(PermissionGroup.get(Permission.class)); } else if(type.equals(PROJECT)) { groups = Arrays.asList(PermissionGroup.get(Item.class),PermissionGroup.get(Run.class)); } else { groups = null; } return groups; } /** * Check if the permission should be shown. */ public boolean showPermission(String type, Permission p) { if(type.equals(GLOBAL)) { return showPermission(p); } else if(type.equals(PROJECT)) { return p!=Item.CREATE && p.getEnabled(); } else { return false; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy