/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.controller.access.management; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.access.Authorizer; import org.jboss.as.controller.access.AuthorizerConfiguration; import org.jboss.as.controller.access.Caller; import org.jboss.as.controller.access.CombinationPolicy; import org.jboss.as.controller.access.rbac.StandardRBACAuthorizer; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; /** * Standard {@link AuthorizerConfiguration} implementation that also exposes mutator APIs for use by * the WildFly management layer. * * @author Brian Stansberry (c) 2013 Red Hat Inc. */ public class WritableAuthorizerConfiguration implements AuthorizerConfiguration, AccessConstraintUtilizationRegistry { private volatile Map<String, RoleMappingImpl> roleMappings = new HashMap<String, RoleMappingImpl>(); private final Map<Object, RoleMappingImpl> removedRoles = new WeakHashMap<Object, RoleMappingImpl>(); private final Map<AccessConstraintKey, Map<PathAddress, AccessConstraintUtilization>> accessConstraintUtilization = new HashMap<AccessConstraintKey, Map<PathAddress, AccessConstraintUtilization>>(); private volatile CombinationPolicy combinationPolicy = CombinationPolicy.PERMISSIVE; private volatile boolean useIdentityRoles; private volatile boolean nonFacadeMBeansSensitive; private volatile Authorizer.AuthorizerDescription authorizerDescription; private volatile RoleMaps roleMaps; private final Set<ScopedRoleListener> scopedRoleListeners = new LinkedHashSet<ScopedRoleListener>(); public WritableAuthorizerConfiguration(Authorizer.AuthorizerDescription authorizerDescription) { this.authorizerDescription = authorizerDescription; this.roleMaps = new RoleMaps(authorizerDescription.getStandardRoles(), Collections.<String, ScopedRole>emptyMap()); } /** * Reset the internal state of this object back to what it originally was. * * Used then reloading a server or in a slave host controller following a post-boot reconnect * to the master. */ public synchronized void reset() { this.authorizerDescription = StandardRBACAuthorizer.AUTHORIZER_DESCRIPTION; this.useIdentityRoles = this.nonFacadeMBeansSensitive = false; this.roleMappings = new HashMap<String, RoleMappingImpl>(); RoleMaps oldRoleMaps = this.roleMaps; this.roleMaps = new RoleMaps(authorizerDescription.getStandardRoles(), Collections.<String, ScopedRole>emptyMap()); for (ScopedRole role : oldRoleMaps.scopedRoles.values()) { for (ScopedRoleListener listener : scopedRoleListeners) { try { listener.scopedRoleRemoved(role); } catch (Exception ignored) { // TODO log an ERROR } } } } public synchronized void registerScopedRoleListener(ScopedRoleListener listener) { scopedRoleListeners.add(listener); } public synchronized void unregisterScopedRoleListener(ScopedRoleListener listener) { scopedRoleListeners.remove(listener); } @Override public CombinationPolicy getPermissionCombinationPolicy() { return combinationPolicy; } @Override public boolean isRoleBased() { return authorizerDescription.isRoleBased(); } @Override public boolean isMapUsingIdentityRoles() { return useIdentityRoles; } @Override public Set<String> getStandardRoles() { return roleMaps.standardRoles; } @Override public Map<String, ScopedRole> getScopedRoles() { return roleMaps.scopedRoles; } @Override public Set<String> getAllRoles() { return roleMaps.allRoles; } @Override public boolean hasRole(String roleName) { final Set<String> canonicalRoles = roleMaps.canonicalRoles; return canonicalRoles.contains(getOfficialForm(roleName)); } @Override public Map<String, RoleMapping> getRoleMappings() { return Collections.<String, RoleMapping>unmodifiableMap(roleMappings); } public void setUseIdentityRoles(boolean useIdentityRoles) { this.useIdentityRoles = useIdentityRoles; } public synchronized void addScopedRole(ScopedRole toAdd) { for (ScopedRoleListener listener : scopedRoleListeners) { listener.scopedRoleAdded(toAdd); } Map<String, ScopedRole> newScopedRoles = new HashMap<String, ScopedRole>(roleMaps.scopedRoles); newScopedRoles.put(toAdd.getName(), toAdd); roleMaps = new RoleMaps(roleMaps.standardRoles, newScopedRoles); } public synchronized void removeScopedRole(String toRemove) { Map<String, ScopedRole> newScopedRoles = new HashMap<String, ScopedRole>(roleMaps.scopedRoles); ScopedRole removed = newScopedRoles.remove(toRemove); roleMaps = new RoleMaps(roleMaps.standardRoles, newScopedRoles); if (removed != null) { for (ScopedRoleListener listener : scopedRoleListeners) { listener.scopedRoleRemoved(removed); } } } @Override public boolean isNonFacadeMBeansSensitive() { return nonFacadeMBeansSensitive; } public void addRoleMappingImmediate(final String roleName) { roleMappings.put(roleName, new RoleMappingImpl(roleName)); } /** * Adds a new role to the list of defined roles. * * @param roleName - The name of the role being added. */ public synchronized void addRoleMapping(final String roleName) { HashMap<String, RoleMappingImpl> newRoles = new HashMap<String, RoleMappingImpl>(roleMappings); if (newRoles.containsKey(roleName) == false) { newRoles.put(roleName, new RoleMappingImpl(roleName)); roleMappings = Collections.unmodifiableMap(newRoles); } } /** * Remove a role from the list of defined roles. * * @param roleName - The name of the role to be removed. * @return A key that can be used to undo the removal. */ public synchronized Object removeRoleMapping(final String roleName) { /* * Would not expect this to happen during boot so don't offer the 'immediate' optimisation. */ HashMap<String, RoleMappingImpl> newRoles = new HashMap<String, RoleMappingImpl>(roleMappings); if (newRoles.containsKey(roleName)) { RoleMappingImpl removed = newRoles.remove(roleName); Object removalKey = new Object(); removedRoles.put(removalKey, removed); roleMappings = Collections.unmodifiableMap(newRoles); return removalKey; } return null; } /** * Undo a prior removal using the supplied undo key. * * @param removalKey - The key returned from the call to removeRoleMapping. * @return true if the undo was successful, false otherwise. */ public synchronized boolean undoRoleMappingRemove(final Object removalKey) { HashMap<String, RoleMappingImpl> newRoles = new HashMap<String, RoleMappingImpl>(roleMappings); RoleMappingImpl toRestore = removedRoles.remove(removalKey); if (toRestore != null && newRoles.containsKey(toRestore.getName()) == false) { newRoles.put(toRestore.getName(), toRestore); roleMappings = Collections.unmodifiableMap(newRoles); return true; } return false; } public void setRoleMappingIncludeAll(final String roleName, final boolean includeAll) { RoleMappingImpl role = roleMappings.get(roleName); role.setIncludeAll(includeAll); } public boolean addRoleMappingPrincipal(final String roleName, final PrincipalType principalType, final MatchType matchType, final String name, final String realm, final boolean immediate) { RoleMappingImpl role = roleMappings.get(roleName); if (role != null) { if (immediate) { return role.addPrincipalImmediate(createPrincipal(principalType, name, realm), matchType); } else { return role.addPrincipal(createPrincipal(principalType, name, realm), matchType); } } return false; } public boolean removeRoleMappingPrincipal(final String roleName, final PrincipalType principalType, final MatchType matchType, final String name, final String realm) { RoleMappingImpl role = roleMappings.get(roleName); if (role != null) { return role.removePrincipal(createPrincipal(principalType, name, realm), matchType); } return false; } public MappingPrincipal createPrincipal(final PrincipalType principalType, final String name, final String realm) { return new MappingPrincipalImpl(principalType, name, realm); } public void setPermissionCombinationPolicy(CombinationPolicy combinationPolicy) { assert combinationPolicy != null : "combinationPolicy is null"; this.combinationPolicy = combinationPolicy; } void setNonFacadeMBeansSensitive(boolean nonFacadeMBeansSensitive) { this.nonFacadeMBeansSensitive = nonFacadeMBeansSensitive; } synchronized void setAuthorizerDescription(Authorizer.AuthorizerDescription authorizerDescription) { this.authorizerDescription = authorizerDescription; this.roleMaps = new RoleMaps(authorizerDescription.getStandardRoles(), roleMaps.scopedRoles); } private static String getOfficialForm(String roleName) { return roleName == null ? null : roleName.toUpperCase(Locale.ENGLISH); } @Override public synchronized Map<PathAddress, AccessConstraintUtilization> getAccessConstraintUtilizations(AccessConstraintKey accessConstraintKey) { Map<PathAddress, AccessConstraintUtilization> result = getAccessConstraintUtilizations(accessConstraintKey, false); return result == null ? Collections.<PathAddress, AccessConstraintUtilization>emptyMap() : Collections.unmodifiableMap(result); } private Map<PathAddress, AccessConstraintUtilization> getAccessConstraintUtilizations(AccessConstraintKey constraintKey, boolean addIfNull) { Map<PathAddress, AccessConstraintUtilization> result = accessConstraintUtilization.get(constraintKey); if (result == null && addIfNull) { result = new HashMap<PathAddress, AccessConstraintUtilization>(); accessConstraintUtilization.put(constraintKey, result); } return result; } @Override public synchronized void registerAccessConstraintResourceUtilization(AccessConstraintKey key, PathAddress address) { AccessConstraintUtilizationImpl acu = getAccessConstraintUtilizationImpl(key, address); acu.setResourceConstrained(true); } @Override public synchronized void registerAccessConstraintAttributeUtilization(AccessConstraintKey key, PathAddress address, String attribute) { AccessConstraintUtilizationImpl acu = getAccessConstraintUtilizationImpl(key, address); acu.addAttribute(attribute); } @Override public synchronized void registerAccessConstraintOperationUtilization(AccessConstraintKey key, PathAddress address, String operation) { AccessConstraintUtilizationImpl acu = getAccessConstraintUtilizationImpl(key, address); acu.addOperation(operation); } @Override public synchronized void unregisterAccessConstraintUtilizations(PathAddress address) { for (Map<PathAddress, AccessConstraintUtilization> map : accessConstraintUtilization.values()) { map.remove(address); } } private AccessConstraintUtilizationImpl getAccessConstraintUtilizationImpl(AccessConstraintKey key, PathAddress address) { Map<PathAddress, AccessConstraintUtilization> map = getAccessConstraintUtilizations(key, true); AccessConstraintUtilizationImpl acu = (AccessConstraintUtilizationImpl) map.get(address); if (acu == null) { // WFLY-1819. Validate that ApplicationTypeConfig.DEPLOYMENT isn't misused // A bit hacky, but this is as good a control point for this as any. if (key.isCore() && ApplicationTypeAccessConstraintDefinition.DEPLOYMENT.getName().equals(key.getName()) && ApplicationTypeAccessConstraintDefinition.DEPLOYMENT.getType().equals(key.getType())) { assert !address.toString().contains(ModelDescriptionConstants.SUBSYSTEM) : "Invalid use of " + key + " in a subsystem; reserved for core use"; } acu = new AccessConstraintUtilizationImpl(key, address); map.put(address, acu); } return acu; } /** * Types of matching strategies used in {@link org.jboss.as.controller.access.Caller} to {@link org.jboss.as.controller.access.AuthorizerConfiguration.RoleMapping} mapping. */ public static enum MatchType { /** Any exclusive match, where a match precludes mapping a {@link org.jboss.as.controller.access.Caller} to a {@link org.jboss.as.controller.access.AuthorizerConfiguration.RoleMapping} */ EXCLUDE, /** Any inclusive match, where a match allows mapping a {@link org.jboss.as.controller.access.Caller} to a {@link org.jboss.as.controller.access.AuthorizerConfiguration.RoleMapping} */ INCLUDE } private static final class RoleMappingImpl implements RoleMapping { private final String name; private boolean includeAll; private volatile Set<MappingPrincipal> includes = new HashSet<MappingPrincipal>(); private volatile Set<MappingPrincipal> excludes = new HashSet<MappingPrincipal>(); private RoleMappingImpl(final String name) { this.name = name; } @Override public String getName() { return name; } @Override public String toString() { StringBuilder sb = new StringBuilder("[Role name='" + name + "' "); sb.append("{Includes = "); for (MappingPrincipal current : includes) { sb.append(current.toString()); } sb.append("}"); sb.append("{Excludes = "); for (MappingPrincipal current : excludes) { sb.append(current.toString()); } sb.append("}"); sb.append("]"); return sb.toString(); } private boolean addPrincipalImmediate(final MappingPrincipal principal, final MatchType matchType) { Set<MappingPrincipal> set = getSet(matchType, true); try { return set.add(principal); } finally { setSet(set, matchType, true); } } private synchronized boolean addPrincipal(final MappingPrincipal principal, final MatchType matchType) { Set<MappingPrincipal> set = getSet(matchType, false); try { return set.add(principal); } finally { setSet(set, matchType, false); } } private void setIncludeAll(final boolean includeAll) { this.includeAll = includeAll; } public boolean includeAllAuthedUsers() { return includeAll; } private synchronized boolean removePrincipal(final MappingPrincipal principal, final MatchType matchType) { Set<MappingPrincipal> set = getSet(matchType, false); try { return set.remove(principal); } finally { setSet(set, matchType, false); } } @Override public MappingPrincipal isIncluded(Caller caller) { return isInSet(caller, includes); } @Override public MappingPrincipal isExcluded(Caller caller) { return isInSet(caller, excludes); } private MappingPrincipal isInSet(Caller caller, Set<MappingPrincipal> theSet) { // One match is all it takes - return true on first match found. String accountName = null; String realm = null; Set<String> groups = null; for (MappingPrincipal current : theSet) { String expectedRealm = current.getRealm(); switch (current.getType()) { case USER: if (expectedRealm != null) { if (current.getName().equals((accountName = getAccountName(caller, accountName))) && expectedRealm.equals((realm = getRealmName(caller, realm)))) { return current; } } else { if (current.getName().equals((accountName = getAccountName(caller, accountName)))) { return current; } } break; case GROUP: if (expectedRealm != null) { if ((groups = getGroups(caller, groups)).contains(current.getName()) && expectedRealm.equals((realm = getRealmName(caller, realm)))) { return current; } } else { if ((groups = getGroups(caller, groups)).contains(current.getName())) { return current; } } break; } } return null; } private String getAccountName(final Caller caller, final String currentValue) { return currentValue != null ? currentValue : caller.getName(); } private String getRealmName(final Caller caller, final String currentValue) { return currentValue != null ? currentValue : caller.getRealm(); } private Set<String> getGroups(final Caller caller, final Set<String> currentValue) { return currentValue != null ? currentValue : caller.getAssociatedGroups(); } private Set<MappingPrincipal> getSet(final MatchType matchType, final boolean immediate) { Set<MappingPrincipal> set; switch (matchType) { case INCLUDE: set = includes; break; default: set = excludes; } return immediate ? set : new HashSet<MappingPrincipal>(set); } private void setSet(final Set<MappingPrincipal> set, final MatchType matchType, final boolean immediate) { if (immediate == false) { switch (matchType) { case INCLUDE: includes = Collections.<MappingPrincipal>unmodifiableSet(set); break; case EXCLUDE: excludes = Collections.<MappingPrincipal>unmodifiableSet(set); } } } } private static final class MappingPrincipalImpl implements MappingPrincipal { private final PrincipalType type; private final String realm; private final String name; private final int hashCode; // Doesn't change and we know it is needed. private MappingPrincipalImpl(final PrincipalType type, final String name, final String realm) { this.type = type; this.name = name; this.realm = realm; hashCode = type.ordinal() * name.hashCode() * (realm == null ? 31 : realm.hashCode()); } @Override public PrincipalType getType() { return type; } @Override public String getRealm() { return realm; } @Override public String getName() { return name; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object obj) { return obj instanceof MappingPrincipalImpl && this.equals((MappingPrincipalImpl) obj); } private boolean equals(MappingPrincipalImpl obj) { return type == obj.type && name.equals(obj.name) && (realm == null ? obj.realm == null : realm.equals(obj.realm)); } @Override public String toString() { return "Principal [type=" + type + ", realm=" + realm + ", name=" + name + "]"; } } private static class RoleMaps { private final Set<String> standardRoles; private final Map<String, ScopedRole> scopedRoles; private final Set<String> allRoles; private final Set<String> canonicalRoles; private RoleMaps(Set<String> standardRoles, Map<String, ScopedRole> scopedRoles) { this.standardRoles = standardRoles; this.scopedRoles = scopedRoles; Set<String> allRoles = new HashSet<String>(); allRoles.addAll(standardRoles); allRoles.addAll(scopedRoles.keySet()); this.allRoles = Collections.unmodifiableSet(allRoles); Set<String> canonicalRoles = new HashSet<String>(); for (String r : allRoles) { canonicalRoles.add(getOfficialForm(r)); } this.canonicalRoles = Collections.unmodifiableSet(canonicalRoles); } } }