/*
* 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.constraint;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.jboss.as.controller.access.HostEffect;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.JmxAction;
import org.jboss.as.controller.access.JmxTarget;
import org.jboss.as.controller.access.ServerGroupEffect;
import org.jboss.as.controller.access.TargetAttribute;
import org.jboss.as.controller.access.TargetResource;
import org.jboss.as.controller.access.rbac.StandardRole;
/**
* Constraint related to whether the target resource is associated with one or more managed domain server groups.
*
* @author Brian Stansberry (c) 2013 Red Hat Inc.
*/
public class ServerGroupEffectConstraint extends AbstractConstraint implements Constraint, ScopingConstraint {
public static final ScopingConstraintFactory FACTORY = new Factory();
private static final ServerGroupEffectConstraint GLOBAL_USER = new ServerGroupEffectConstraint(true, false);
private static final ServerGroupEffectConstraint DOMAIN_GLOBAL_REQUIRED = new ServerGroupEffectConstraint(false, false);
private static final ServerGroupEffectConstraint HOST_GLOBAL_REQUIRED = new ServerGroupEffectConstraint(false, true);
private static final ServerGroupEffectConstraint UNASSIGNED = new ServerGroupEffectConstraint();
private final boolean user;
private final boolean global;
// nonServerHost means it applies to a host resource
// but not a server or server-config child
private final boolean nonServerHost;
private final boolean unassigned;
private final GroupsHolder groupsHolder;
private final boolean readOnly;
private final boolean groupAdd;
private final boolean groupRemove;
private final ServerGroupEffectConstraint readOnlyConstraint;
// For domain unassigned resources
private ServerGroupEffectConstraint() {
this.user = false;
this.global = false;
this.nonServerHost = false;
this.unassigned = true;
this.readOnly = false;
this.readOnlyConstraint = null;
this.groupAdd = false;
this.groupRemove = false;
this.groupsHolder = new GroupsHolder();
}
// For GLOBAL cases
private ServerGroupEffectConstraint(final boolean user, boolean nonServerHost) {
this.user = user;
this.global = true;
this.nonServerHost = nonServerHost;
this.unassigned = false;
this.readOnly = false;
this.readOnlyConstraint = null;
this.groupAdd = false;
this.groupRemove = false;
this.groupsHolder = new GroupsHolder();
}
private ServerGroupEffectConstraint(Set<String> allowed, boolean nonServerHost, boolean groupAdd, boolean groupRemove) {
this.user = false;
this.global = false;
this.nonServerHost = nonServerHost;
this.unassigned = false;
this.groupsHolder = new GroupsHolder(allowed);
this.readOnly = false;
this.groupAdd = groupAdd;
this.groupRemove = groupRemove;
this.readOnlyConstraint = null;
}
// For server group scoped role creation
public ServerGroupEffectConstraint(List<String> allowed) {
this.user = true;
this.global = false;
this.nonServerHost = false;
this.unassigned = false;
this.groupsHolder = new GroupsHolder(allowed);
this.readOnly = false;
this.groupAdd = false;
this.groupRemove = false;
this.readOnlyConstraint = new ServerGroupEffectConstraint(groupsHolder);
}
/**
* Creates the constraint the standard constraint will return from {@link #getOutofScopeReadConstraint()}
* Only call from {@link ServerGroupEffectConstraint#ServerGroupEffectConstraint(java.util.List)}
*/
private ServerGroupEffectConstraint(GroupsHolder groupsHolder) {
this.user = true;
this.global = false;
this.nonServerHost = false;
this.unassigned = false;
this.groupsHolder = groupsHolder;
this.readOnly = true;
this.groupAdd = false;
this.groupRemove = false;
this.readOnlyConstraint = null;
}
public void setAllowedGroups(List<String> allowed) {
assert !global : "constraint is global";
assert readOnlyConstraint != null : "invalid cast";
this.groupsHolder.specific = new LinkedHashSet<String>(allowed);
}
@Override
public boolean violates(Constraint other, Action.ActionEffect actionEffect) {
if (other instanceof ServerGroupEffectConstraint) {
ServerGroupEffectConstraint sgec = (ServerGroupEffectConstraint) other;
Set<String> ourSpecific = groupsHolder.specific;
Set<String> sgecSpecific = sgec.groupsHolder.specific;
if (user) {
assert !sgec.user : "illegal comparison";
if (readOnly) {
// Allow global or any matching server group
// Also, since this is readOnly, allow nonServerHost
if (!sgec.global && !sgec.nonServerHost) {
boolean anyMatch = anyMatch(ourSpecific, sgecSpecific);
if (!anyMatch) ControllerLogger.ACCESS_LOGGER.tracef("read-only server-group constraint violated " +
"for action %s due to no match between groups %s and allowed groups %s",
actionEffect, sgecSpecific, ourSpecific);
return !anyMatch;
}
} else if (!global) {
if (sgec.global) {
// Only the readOnlyConstraint gets global
ControllerLogger.ACCESS_LOGGER.tracef("server-group constraint violated for action %s due to " +
"requirement for access to global resources", actionEffect);
return true;
} else if (!sgec.unassigned) {
if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
// Writes must not effect other groups
boolean containsAll = ourSpecific.containsAll(sgecSpecific);
if (!containsAll) {
ControllerLogger.ACCESS_LOGGER.tracef("server-group constraint violated for action %s due to " +
"mismatch of groups %s vs allowed %s", actionEffect, sgecSpecific, ourSpecific);
} else if (sgec.groupAdd) {
ControllerLogger.ACCESS_LOGGER.tracef("server-group constraint violated for action %s due to " +
"attempt to add the server group", actionEffect);
} else if (sgec.groupRemove) {
ControllerLogger.ACCESS_LOGGER.tracef("server-group constraint violated for action %s due to " +
"attempt to remove the server group", actionEffect);
}
return !containsAll || sgec.groupAdd || sgec.groupRemove;
} else {
// Reads ok as long as one of our groups match
boolean anyMatch = anyMatch(ourSpecific, sgecSpecific);
if (!anyMatch) {
// Allow access for server-group add so there's no bizarre "no such resource"
if (sgec.groupAdd && actionEffect == Action.ActionEffect.ADDRESS) {
return false;
}
ControllerLogger.ACCESS_LOGGER.tracef("server-group constraint violated " +
"for action %s due to no match between groups %s and allowed groups %s",
actionEffect, sgecSpecific, ourSpecific);
}
return !anyMatch;
}
} // else fall through
}
} else {
assert sgec.user : "illegal comparison";
return other.violates(this, actionEffect);
}
}
return false;
}
private boolean anyMatch(Set<String> ourSpecific, Set<String> sgecSpecific) {
boolean matched = false;
for (String ourGroup : ourSpecific) {
if (sgecSpecific.contains(ourGroup)) {
matched = true;
break;
}
}
if (!matched) {
// WFLY-2089
matched = sgecSpecific.size() == 1 && sgecSpecific.contains("*");
}
return matched;
}
@Override
public boolean replaces(Constraint other) {
return other instanceof ServerGroupEffectConstraint && (readOnly || readOnlyConstraint != null);
}
// Scoping Constraint
@Override
public ScopingConstraintFactory getFactory() {
assert readOnlyConstraint != null : "invalid cast";
return FACTORY;
}
@Override
public Constraint getStandardConstraint() {
assert readOnlyConstraint != null : "invalid cast";
return this;
}
@Override
public Constraint getOutofScopeReadConstraint() {
assert readOnlyConstraint != null : "invalid cast";
return readOnlyConstraint;
}
private static class GroupsHolder {
private volatile Set<String> specific = new LinkedHashSet<String>();
private GroupsHolder() {
// no-op
}
private GroupsHolder(Collection<String> groups) {
this.specific.addAll(groups);
}
}
private static class Factory extends AbstractConstraintFactory implements ScopingConstraintFactory {
@Override
public Constraint getStandardUserConstraint(StandardRole role, Action.ActionEffect actionEffect) {
return GLOBAL_USER;
}
@Override
public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetAttribute target) {
return getRequiredConstraint(target.getServerGroupEffect(), target.getHostEffect());
}
@Override
public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetResource target) {
return getRequiredConstraint(target.getServerGroupEffect(), target.getHostEffect());
}
@Override
public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, JmxAction action, JmxTarget target) {
return getRequiredConstraint(target.getServerGroupEffect(), target.getHostEffect());
}
private Constraint getRequiredConstraint(ServerGroupEffect serverGroupEffect, HostEffect hostEffect) {
boolean nonServerHost = hostEffect != null && !hostEffect.isHostEffectGlobal() && !hostEffect.isServerEffect();
if (serverGroupEffect == null || serverGroupEffect.isServerGroupEffectGlobal()) {
if (nonServerHost) {
return HOST_GLOBAL_REQUIRED;
}
return DOMAIN_GLOBAL_REQUIRED;
} else if (serverGroupEffect.isServerGroupEffectUnassigned()) {
return UNASSIGNED;
}
return new ServerGroupEffectConstraint(serverGroupEffect.getAffectedServerGroups(),
nonServerHost, serverGroupEffect.isServerGroupAdd(), serverGroupEffect.isServerGroupRemove());
}
@Override
protected int internalCompare(AbstractConstraintFactory other) {
// We prefer going first
return this.equals(other) ? 0 : -1;
}
}
}