Java tutorial
/* * The MIT License * * Copyright (c) 2010, Manufacture Franaise 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.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.MapMaker; import com.synopsys.arc.jenkins.plugins.rolestrategy.Macro; import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleMacroExtension; import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.PluginManager; import hudson.model.User; import hudson.security.AccessControlled; import hudson.security.Permission; import hudson.security.SidACL; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.acls.sid.Sid; import org.acegisecurity.userdetails.UserDetails; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.rolestrategy.Settings; import org.jenkinsci.plugins.rolestrategy.permissions.DangerousPermissionHandlingMode; import org.jenkinsci.plugins.rolestrategy.permissions.DangerousPermissionHelper; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.springframework.dao.DataAccessException; /** * Class holding a map for each kind of {@link AccessControlled} object, associating * each {@link Role} with the concerned {@link User}s/groups. * @author Thomas Maurel */ public class RoleMap { /** Map associating each {@link Role} with the concerned {@link User}s/groups. */ private final SortedMap<Role, Set<String>> grantedRoles; private static final Logger LOGGER = Logger.getLogger(RoleMap.class.getName()); private final Cache<String, UserDetails> cache = CacheBuilder.newBuilder().softValues() .maximumSize(Settings.USER_DETAILS_CACHE_MAX_SIZE) .expireAfterWrite(Settings.USER_DETAILS_CACHE_EXPIRATION_TIME_SEC, TimeUnit.SECONDS).build(); RoleMap() { this.grantedRoles = new TreeMap<Role, Set<String>>(); } RoleMap(SortedMap<Role, Set<String>> grantedRoles) { this.grantedRoles = grantedRoles; } /** * Check if the given sid has the provided {@link Permission}. * @return True if the sid's granted permission */ private boolean hasPermission(String sid, Permission p, RoleType roleType, AccessControlled controlledItem) { if (DangerousPermissionHelper.isDangerous(p)) { /* if this is a dangerous permission, fall back to Administer unless we're in compat mode */ p = Jenkins.ADMINISTER; } for (Role role : getRolesHavingPermission(p)) { if (this.grantedRoles.get(role).contains(sid)) { // Handle roles macro if (Macro.isMacro(role)) { Macro macro = RoleMacroExtension.getMacro(role.getName()); if (macro != null) { RoleMacroExtension macroExtension = RoleMacroExtension.getMacroExtension(macro.getName()); if (macroExtension.IsApplicable(roleType) && macroExtension.hasPermission(sid, p, roleType, controlledItem, macro)) { return true; } } } // Default handling else { return true; } } else if (Settings.TREAT_USER_AUTHORITIES_AS_ROLES) { try { UserDetails userDetails = cache.getIfPresent(sid); if (userDetails == null) { userDetails = Jenkins.getInstance().getSecurityRealm().loadUserByUsername(sid); cache.put(sid, userDetails); } for (GrantedAuthority grantedAuthority : userDetails.getAuthorities()) { if (grantedAuthority.getAuthority().equals(role.getName())) { return true; } } } catch (BadCredentialsException e) { LOGGER.log(Level.FINE, "Bad credentials", e); } catch (DataAccessException e) { LOGGER.log(Level.FINE, "failed to access the data", e); } catch (RuntimeException ex) { // There maybe issues in the logic, which lead to IllegalStateException in Acegi Security (JENKINS-35652) // So we want to ensure this method does not fail horribly in such case LOGGER.log(Level.WARNING, "Unhandled exception during user authorities processing", ex); } } // TODO: Handle users macro } return false; } /** * Check if the {@link RoleMap} contains the given {@link Role}. * @return True if the {@link RoleMap} contains the given role */ public boolean hasRole(Role role) { return this.grantedRoles.containsKey(role); } /** * Get the ACL for the current {@link RoleMap}. * @return ACL for the current {@link RoleMap} */ public SidACL getACL(RoleType roleType, AccessControlled controlledItem) { return new AclImpl(roleType, controlledItem); } /** * Add the given role to this {@link RoleMap}. * @param role The {@link Role} to add */ public void addRole(Role role) { if (this.getRole(role.getName()) == null) { this.grantedRoles.put(role, new HashSet<String>()); } } /** * Assign the sid to the given {@link Role}. * @param role The {@link Role} to assign the sid to * @param sid The sid to assign */ public void assignRole(Role role, String sid) { if (this.hasRole(role)) { this.grantedRoles.get(role).add(sid); } } /** * Clear all the sids associated to the given {@link Role}. * @param role The {@link Role} for which you want to clear the sids */ public void clearSidsForRole(Role role) { if (this.hasRole(role)) { this.grantedRoles.get(role).clear(); } } /** * Clear all the roles associated to the given sid * @param sid The sid for thwich you want to clear the {@link Role}s */ public void deleteSids(String sid) { for (Map.Entry<Role, Set<String>> entry : grantedRoles.entrySet()) { Role role = entry.getKey(); Set<String> sids = entry.getValue(); if (sids.contains(sid)) { sids.remove(sid); } } } /** * Clear all the sids for each {@link Role} of the {@link RoleMap}. */ public void clearSids() { for (Map.Entry<Role, Set<String>> entry : this.grantedRoles.entrySet()) { Role role = entry.getKey(); this.clearSidsForRole(role); } } /** * Get the {@link Role} object named after the given param. * @param name The name of the {@link Role} * @return The {@link Role} named after the given param */ public Role getRole(String name) { for (Role role : this.getRoles()) { if (role.getName().equals(name)) { return role; } } return null; } /** * Removes a {@link Role} * @param role The {@link Role} which shall be removed */ public void removeRole(Role role) { this.grantedRoles.remove(role); } /** * Get an unmodifiable sorted map containing {@link Role}s and their assigned sids. * @return An unmodifiable sorted map containing the {@link Role}s and their associated sids */ public SortedMap<Role, Set<String>> getGrantedRoles() { return Collections.unmodifiableSortedMap(this.grantedRoles); } /** * Get an unmodifiable set containing all the {@link Role}s of this {@link RoleMap}. * @return An unmodifiable set containing the {@link Role}s */ public Set<Role> getRoles() { return Collections.unmodifiableSet(this.grantedRoles.keySet()); } /** * Get all the sids referenced in this {@link RoleMap}, minus the {@code Anonymous} sid. * @return A sorted set containing all the sids, minus the {@code Anonymous} sid */ public SortedSet<String> getSids() { return this.getSids(false); } /** * Get all the sids referenced in this {@link RoleMap}. * @param includeAnonymous True if you want the {@code Anonymous} sid to be included in the set * @return A sorted set containing all the sids */ public SortedSet<String> getSids(Boolean includeAnonymous) { TreeSet<String> sids = new TreeSet<String>(); for (Map.Entry entry : this.grantedRoles.entrySet()) { sids.addAll((Set) entry.getValue()); } // Remove the anonymous sid if asked to if (!includeAnonymous) { sids.remove("anonymous"); } return Collections.unmodifiableSortedSet(sids); } /** * Get all the sids assigned to the {@link Role} named after the {@code roleName} param. * @param roleName The name of the role * @return A sorted set containing all the sids */ public Set<String> getSidsForRole(String roleName) { Role role = this.getRole(roleName); if (role != null) { return Collections.unmodifiableSet(this.grantedRoles.get(role)); } return null; } /** * Create a sub-map of the current {@link RoleMap} containing only the * {@link Role}s matching the given pattern. * @param namePattern The pattern to match * @return A {@link RoleMap} containing only {@link Role}s matching the given name */ public RoleMap newMatchingRoleMap(String namePattern) { Set<Role> roles = getMatchingRoles(namePattern); SortedMap<Role, Set<String>> roleMap = new TreeMap<Role, Set<String>>(); for (Role role : roles) { roleMap.put(role, this.grantedRoles.get(role)); } return new RoleMap(roleMap); } /** * Get all the roles holding the given permission. * @param permission The permission you want to check * @return A Set of Roles holding the given permission */ private Set<Role> getRolesHavingPermission(final Permission permission) { final Set<Role> roles = new HashSet<Role>(); final Set<Permission> permissions = new HashSet<Permission>(); Permission p = permission; // Get the implying permissions for (; p != null; p = p.impliedBy) { permissions.add(p); } // Walk through the roles, and only add the roles having the given permission, // or a permission implying the given permission new RoleWalker() { public void perform(Role current) { if (current.hasAnyPermission(permissions)) { roles.add(current); } } }; return roles; } /** * Get all the roles whose pattern match the given pattern. * @param namePattern The string to match * @return A Set of Roles matching the given name */ private Set<Role> getMatchingRoles(final String namePattern) { final Set<Role> roles = new HashSet<Role>(); // Walk through the roles and only add the Roles whose pattern matches the given string new RoleWalker() { public void perform(Role current) { Matcher m = current.getPattern().matcher(namePattern); if (m.matches()) { roles.add(current); } } }; return roles; } /** * The Acl class that will delegate the permission check to the {@link RoleMap} object. */ private final class AclImpl extends SidACL { AccessControlled item; RoleType roleType; public AclImpl(RoleType roleType, AccessControlled item) { this.item = item; this.roleType = roleType; } /** * Checks if the sid has the given permission. * <p>Actually only delegate the check to the {@link RoleMap} instance.</p> * @param p The sid to check * @param permission The permission to check * @return True if the sid has the given permission */ @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "As declared in Jenkins API") @Override protected Boolean hasPermission(Sid p, Permission permission) { if (RoleMap.this.hasPermission(toString(p), permission, roleType, item)) { return true; } return null; } } /** * A class to walk through all the {@link RoleMap}'s roles and perform an * action on each one. */ private abstract class RoleWalker { RoleWalker() { walk(); } /** * Walk through the roles. */ public void walk() { Set<Role> roles = RoleMap.this.getRoles(); Iterator iter = roles.iterator(); while (iter.hasNext()) { Role current = (Role) iter.next(); perform(current); } } /** * The method to implement which will be called on each {@link Role}. */ abstract public void perform(Role current); } }