/*
* 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.rbac;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jboss.as.controller.access.Action;
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.Environment;
import org.jboss.as.controller.access.TargetAttribute;
import org.jboss.as.controller.access.TargetResource;
import org.jboss.as.controller.access.constraint.ConstraintFactory;
import org.jboss.as.controller.access.constraint.ScopingConstraint;
import org.jboss.as.controller.access.JmxAction;
import org.jboss.as.controller.access.JmxTarget;
import org.jboss.as.controller.access.constraint.ApplicationTypeConstraint;
import org.jboss.as.controller.access.constraint.AuditConstraint;
import org.jboss.as.controller.access.constraint.Constraint;
import org.jboss.as.controller.access.constraint.HostEffectConstraint;
import org.jboss.as.controller.access.constraint.NonAuditConstraint;
import org.jboss.as.controller.access.constraint.SensitiveTargetConstraint;
import org.jboss.as.controller.access.constraint.SensitiveVaultExpressionConstraint;
import org.jboss.as.controller.access.constraint.ServerGroupEffectConstraint;
import org.jboss.as.controller.access.constraint.TopRoleConstraint;
import org.jboss.as.controller.access.permission.AllPermissionsCollection;
import org.jboss.as.controller.access.permission.CombinationManagementPermission;
import org.jboss.as.controller.access.permission.ManagementPermission;
import org.jboss.as.controller.access.permission.ManagementPermissionCollection;
import org.jboss.as.controller.access.permission.PermissionFactory;
import org.jboss.as.controller.access.permission.SimpleManagementPermission;
import org.jboss.as.controller.logging.ControllerLogger;
/**
* Default {@link org.jboss.as.controller.access.permission.PermissionFactory} implementation that supports
* the WildFly default role-based access control permission scheme.
*
* @author Brian Stansberry (c) 2013 Red Hat Inc.
*/
public class DefaultPermissionFactory implements PermissionFactory, AuthorizerConfiguration.ScopedRoleListener {
private static final PermissionCollection NO_PERMISSIONS = new NoPermissionsCollection();
private final RoleMapper roleMapper;
private final SortedSet<ConstraintFactory> constraintFactories = new TreeSet<ConstraintFactory>();
private final Map<String, ManagementPermissionCollection> permissionsByRole = new HashMap<String, ManagementPermissionCollection>();
private final Map<String, ScopedBase> scopedBaseMap = new HashMap<String, ScopedBase>();
private final AuthorizerConfiguration authorizerConfiguration;
private PermsHolder permsHolder;
private boolean rolePermissionsConfigured;
/**
* Creates a new {@code DefaultPermissionFactory}
* @param roleMapper the role mapper. Cannot be {@code null}
* @param authorizerConfiguration the configuration for the {@link org.jboss.as.controller.access.Authorizer} that
* is using this factory. Cannot be {@code null}
*/
public DefaultPermissionFactory(RoleMapper roleMapper, AuthorizerConfiguration authorizerConfiguration) {
this(roleMapper, getStandardConstraintFactories(), authorizerConfiguration);
}
/** Only for testing use, other than the delegation from the primary constructor */
DefaultPermissionFactory(RoleMapper roleMapper,
Set<ConstraintFactory> constraintFactories, AuthorizerConfiguration authorizerConfiguration) {
this.roleMapper = roleMapper;
this.constraintFactories.addAll(constraintFactories);
this.authorizerConfiguration = authorizerConfiguration;
}
@Override
public PermissionCollection getUserPermissions(Caller caller, Environment callEnvironment, Action action, TargetAttribute target) {
return getUserPermissions(roleMapper.mapRoles(caller, callEnvironment, action, target));
}
@Override
public PermissionCollection getUserPermissions(Caller caller, Environment callEnvironment, Action action, TargetResource target) {
return getUserPermissions(roleMapper.mapRoles(caller, callEnvironment, action, target));
}
@Override
public PermissionCollection getUserPermissions(Caller caller, Environment callEnvironment, JmxAction action, JmxTarget target) {
return getUserPermissions(roleMapper.mapRoles(caller, callEnvironment, action, target));
}
private PermissionCollection getUserPermissions(Set<String> roles) {
PermissionCollection result = checkAllPermissions(roles);
if (result != null) {
return result;
}
PermsHolder currentPerms = configureRolePermissions();
result = currentPerms.getPermissions(roles);
if (result != null) {
return result;
}
final CombinationPolicy combinationPolicy = authorizerConfiguration.getPermissionCombinationPolicy();
ManagementPermissionCollection simple = null;
Map<Action.ActionEffect, CombinationManagementPermission> combined = null;
for (String roleName : roles) {
if (combinationPolicy == CombinationPolicy.REJECTING && simple != null) {
throw ControllerLogger.ROOT_LOGGER.illegalMultipleRoles();
}
ManagementPermissionCollection role = currentPerms.permsByRole.get(getOfficialForm(roleName));
if (role == null) {
throw ControllerLogger.ROOT_LOGGER.unknownRole(roleName);
}
if (simple == null) {
simple = role;
} else {
if (combined == null) {
combined = new HashMap<Action.ActionEffect, CombinationManagementPermission>();
Enumeration<Permission> permissionEnumeration = simple.elements();
String firstName = simple.getName();
while (permissionEnumeration.hasMoreElements()) {
ManagementPermission mperm = (ManagementPermission) permissionEnumeration.nextElement();
Action.ActionEffect actionEffect = mperm.getActionEffect();
CombinationManagementPermission cmp = new CombinationManagementPermission(combinationPolicy, actionEffect);
cmp.addUnderlyingPermission(firstName, mperm);
combined.put(actionEffect, cmp);
}
}
Enumeration<Permission> permissionEnumeration = role.elements();
String officialForm = getOfficialForm(roleName);
while (permissionEnumeration.hasMoreElements()) {
ManagementPermission mperm = (ManagementPermission) permissionEnumeration.nextElement();
Action.ActionEffect actionEffect = mperm.getActionEffect();
CombinationManagementPermission cmp = combined.get(actionEffect);
if (cmp == null) {
cmp = new CombinationManagementPermission(combinationPolicy, actionEffect);
combined.put(actionEffect, cmp);
}
cmp.addUnderlyingPermission(officialForm, mperm);
}
}
}
if (combined == null) {
result = simple != null ? simple : NO_PERMISSIONS;
} else {
result = new ManagementPermissionCollection("MULTIPLE ROLES", CombinationManagementPermission.class);
for (CombinationManagementPermission cmp : combined.values()) {
result.add(cmp);
}
}
currentPerms.storePermissions(roles, result);
return result;
}
private PermissionCollection checkAllPermissions(Set<String> roles) {
if (roles.contains(StandardRole.SUPERUSER.toString())
&& (authorizerConfiguration.getPermissionCombinationPolicy() == CombinationPolicy.PERMISSIVE
|| roles.size() == 1)) {
return AllPermissionsCollection.INSTANCE;
}
return null;
}
@Override
public PermissionCollection getRequiredPermissions(Action action, TargetAttribute target) {
PermsHolder currentPerms = configureRolePermissions();
ConstraintFactory[] currentFactories = currentPerms.constraintFactories;
ManagementPermissionCollection result = new ManagementPermissionCollection(SimpleManagementPermission.class);
for (Action.ActionEffect actionEffect : action.getActionEffects()) {
Constraint[] constraints = new Constraint[currentFactories.length];
for (int i = 0; i < constraints.length; i++) {
constraints[i] = currentFactories[i].getRequiredConstraint(actionEffect, action, target);
}
result.add(new SimpleManagementPermission(actionEffect, constraints));
}
return result;
}
@Override
public PermissionCollection getRequiredPermissions(Action action, TargetResource target) {
PermsHolder currentPerms = configureRolePermissions();
ConstraintFactory[] currentFactories = currentPerms.constraintFactories;
ManagementPermissionCollection result = new ManagementPermissionCollection(SimpleManagementPermission.class);
for (Action.ActionEffect actionEffect : action.getActionEffects()) {
Constraint[] constraints = new Constraint[currentFactories.length];
for (int i = 0; i < constraints.length; i++) {
constraints[i] = currentFactories[i].getRequiredConstraint(actionEffect, action, target);
}
result.add(new SimpleManagementPermission(actionEffect, constraints));
}
return result;
}
@Override
public PermissionCollection getRequiredPermissions(JmxAction action, JmxTarget target) {
PermsHolder currentPerms = configureRolePermissions();
ConstraintFactory[] currentFactories = currentPerms.constraintFactories;
ManagementPermissionCollection result = new ManagementPermissionCollection(SimpleManagementPermission.class);
for (Action.ActionEffect actionEffect : action.getActionEffects()) {
Constraint[] constraints = new Constraint[currentFactories.length];
for (int i = 0; i < constraints.length; i++) {
constraints[i] = currentFactories[i].getRequiredConstraint(actionEffect, action, target);
}
result.add(new SimpleManagementPermission(actionEffect, constraints));
}
return result;
}
/** Hook for the access control management layer to add a new constraint factory */
void addConstraintFactory(ConstraintFactory factory) {
synchronized (this) {
if (constraintFactories.add(factory)) {
// Throw away our permission sets
rolePermissionsConfigured = false;
}
}
}
@Override
public synchronized void scopedRoleAdded(AuthorizerConfiguration.ScopedRole added) {
String roleName = added.getName();
String officialForm = getOfficialForm(roleName);
if (permissionsByRole.containsKey(officialForm)) {
throw ControllerLogger.ROOT_LOGGER.roleIsAlreadyRegistered(roleName);
}
String baseName = added.getBaseRoleName();
String officialBase = getOfficialForm(baseName);
if (rolePermissionsConfigured && !permissionsByRole.containsKey(officialBase)) {
throw ControllerLogger.ROOT_LOGGER.unknownBaseRole(baseName);
}
ScopingConstraint constraint = added.getScopingConstraint();
addConstraintFactory(constraint.getFactory());
scopedBaseMap.put(officialForm, new ScopedBase(StandardRole.valueOf(officialBase), constraint));
rolePermissionsConfigured = false;
}
@Override
public synchronized void scopedRoleRemoved(AuthorizerConfiguration.ScopedRole removed) {
String officialForm = getOfficialForm(removed.getName());
StandardRole standard;
try {
standard = StandardRole.valueOf(officialForm);
} catch (RuntimeException ignored) {
// wasn't a standard role
standard = null;
}
if (standard != null) {
throw ControllerLogger.ROOT_LOGGER.cannotRemoveStandardRole(standard.toString());
}
synchronized (this) {
scopedBaseMap.remove(officialForm);
rolePermissionsConfigured = false;
}
}
private synchronized PermsHolder configureRolePermissions() {
if (!rolePermissionsConfigured) {
this.permissionsByRole.clear();
this.permissionsByRole.putAll(configureDefaultPermissions());
for (Map.Entry<String, ScopedBase> entry : scopedBaseMap.entrySet()) {
addScopedRoleInternal(entry.getKey(), entry.getValue().base, entry.getValue().constraint);
}
permsHolder = new PermsHolder(permissionsByRole, constraintFactories);
rolePermissionsConfigured = true;
}
return permsHolder;
}
private synchronized Map<String, ManagementPermissionCollection> configureDefaultPermissions() {
Map<String, ManagementPermissionCollection> result = new HashMap<String, ManagementPermissionCollection>();
for (StandardRole standardRole : StandardRole.values()) {
String officialForm = getOfficialForm(standardRole);
ManagementPermissionCollection rolePerms = new ManagementPermissionCollection(officialForm, SimpleManagementPermission.class);
for (Action.ActionEffect actionEffect : Action.ActionEffect.values()) {
if (standardRole.isActionEffectAllowed(actionEffect)) {
Constraint[] constraints = new Constraint[constraintFactories.size()];
int i = 0;
for (ConstraintFactory factory : this.constraintFactories) {
constraints[i] = factory.getStandardUserConstraint(standardRole, actionEffect);
i++;
}
rolePerms.add(new SimpleManagementPermission(actionEffect, constraints));
}
}
result.put(officialForm, rolePerms);
}
return result;
}
private synchronized void addScopedRoleInternal(String officialForm, StandardRole base, ScopingConstraint scopingConstraint) {
ManagementPermissionCollection baseCollection = permissionsByRole.get(getOfficialForm(base));
int constraintIndex = getConstraintIndex(scopingConstraint.getFactory());
Map<Action.ActionEffect, SimpleManagementPermission> monitorPermissions = new HashMap<Action.ActionEffect, SimpleManagementPermission>();
ManagementPermissionCollection monitorCollection = permissionsByRole.get(getOfficialForm(StandardRole.MONITOR));
Enumeration<Permission> monitorEnumeration = monitorCollection.elements();
while (monitorEnumeration.hasMoreElements()) {
SimpleManagementPermission monitorPerm = (SimpleManagementPermission) monitorEnumeration.nextElement();
monitorPermissions.put(monitorPerm.getActionEffect(), monitorPerm);
}
ManagementPermissionCollection scopedPermissions = null;
Enumeration<Permission> permissionEnumeration = baseCollection.elements();
String scopedBaseName = officialForm + " (" + getOfficialForm(base) + " permissions)";
while (permissionEnumeration.hasMoreElements()) {
SimpleManagementPermission basePerm = (SimpleManagementPermission) permissionEnumeration.nextElement();
Action.ActionEffect actionEffect = basePerm.getActionEffect();
CombinationManagementPermission combinedPermission = new CombinationManagementPermission(CombinationPolicy.PERMISSIVE, actionEffect);
if (scopedPermissions == null) {
scopedPermissions = new ManagementPermissionCollection(officialForm, CombinationManagementPermission.class);
}
ManagementPermission scopedPerm = basePerm.createScopedPermission(scopingConstraint.getStandardConstraint(), constraintIndex);
combinedPermission.addUnderlyingPermission(scopedBaseName, scopedPerm);
SimpleManagementPermission monitorPerm = monitorPermissions.get(actionEffect);
String scopedReadOnlyName = officialForm + " (READ-ONLY permissions)";
if (monitorPerm != null) {
combinedPermission.addUnderlyingPermission(scopedReadOnlyName,
monitorPerm.createScopedPermission(scopingConstraint.getOutofScopeReadConstraint(), constraintIndex));
}
scopedPermissions.add(combinedPermission);
}
permissionsByRole.put(officialForm, scopedPermissions);
}
private int getConstraintIndex(ConstraintFactory factory) {
int i = 0;
for (Iterator<ConstraintFactory> iter = constraintFactories.iterator(); iter.hasNext(); i++) {
if (factory.equals(iter.next())) {
return i;
}
}
throw new IllegalStateException();
}
private static Set<ConstraintFactory> getStandardConstraintFactories() {
final Set<ConstraintFactory> result = new HashSet<ConstraintFactory>();
result.add(ApplicationTypeConstraint.FACTORY);
result.add(AuditConstraint.FACTORY);
result.add(NonAuditConstraint.FACTORY);
result.add(HostEffectConstraint.FACTORY);
result.add(SensitiveTargetConstraint.FACTORY);
result.add(SensitiveVaultExpressionConstraint.FACTORY);
result.add(ServerGroupEffectConstraint.FACTORY);
result.add(TopRoleConstraint.FACTORY);
return result;
}
private static String getOfficialForm(StandardRole role) {
return role.getOfficialForm();
}
private static String getOfficialForm(String role) {
return role.toUpperCase(Locale.ENGLISH);
}
/** Data holder class */
private class ScopedBase {
private final StandardRole base;
private final ScopingConstraint constraint;
private ScopedBase(StandardRole base, ScopingConstraint constraint) {
this.base = base;
this.constraint = constraint;
}
}
private static class NoPermissionsCollection extends PermissionCollection {
private static final long serialVersionUID = 426277167342589940L;
private NoPermissionsCollection() {
super.setReadOnly();
}
@Override
public void add(Permission permission) {
throw new UnsupportedOperationException();
}
@Override
public boolean implies(Permission permission) {
return false;
}
@Override
public Enumeration<Permission> elements() {
return new Enumeration<Permission>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public Permission nextElement() {
throw new NoSuchElementException();
}
};
}
}
private static class PermsHolder {
private final Map<Set<String>, PermissionCollection> permsByRoleSet =
Collections.synchronizedMap(new HashMap<Set<String>, PermissionCollection>());
private final Map<String, ManagementPermissionCollection> permsByRole =
new HashMap<String, ManagementPermissionCollection>();
private final ConstraintFactory[] constraintFactories;
private PermsHolder(Map<String, ManagementPermissionCollection> permsByRole, SortedSet<ConstraintFactory> constraintFactories) {
this.permsByRole.putAll(permsByRole);
this.constraintFactories = constraintFactories.toArray(new ConstraintFactory[constraintFactories.size()]);
}
private PermissionCollection getPermissions(Set<String> roleSet) {
return permsByRoleSet.get(roleSet);
}
private void storePermissions(Set<String> roleSet, PermissionCollection perms) {
permsByRoleSet.put(roleSet, perms);
}
}
}