/* * 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.logging.ControllerLogger; import org.jboss.as.controller.access.Action; import org.jboss.as.controller.access.HostEffect; import org.jboss.as.controller.access.JmxAction; import org.jboss.as.controller.access.JmxTarget; 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 hosts. * * @author Brian Stansberry (c) 2013 Red Hat Inc. */ public class HostEffectConstraint extends AbstractConstraint implements Constraint, ScopingConstraint { public static final ScopingConstraintFactory FACTORY = new Factory(); private static final HostEffectConstraint GLOBAL_USER = new HostEffectConstraint(true); private static final HostEffectConstraint GLOBAL_REQUIRED = new HostEffectConstraint(false); private final boolean user; private final boolean global; private final HostsHolder hostsHolder; private final boolean readOnly; private final HostEffectConstraint readOnlyConstraint; private HostEffectConstraint(final boolean user) { super(); this.user = user; this.global = true; this.readOnly = false; this.readOnlyConstraint = null; this.hostsHolder = new HostsHolder(); } private HostEffectConstraint(Set<String> allowed) { super(); this.user = false; this.global = false; this.hostsHolder = new HostsHolder(allowed); this.readOnly = false; this.readOnlyConstraint = null; } public HostEffectConstraint(List<String> allowed) { super(); this.user = true; this.global = false; this.hostsHolder = new HostsHolder(allowed); this.readOnly = false; this.readOnlyConstraint = new HostEffectConstraint(this.hostsHolder, true); } /** * Creates the constraint the standard constraint will return from {@link #getOutofScopeReadConstraint()} * Only call from {@link HostEffectConstraint#HostEffectConstraint(java.util.List)} */ private HostEffectConstraint(HostsHolder hostsHolder, boolean readOnly) { super(); this.user = true; this.global = false; this.hostsHolder = hostsHolder; this.readOnly = readOnly; this.readOnlyConstraint = null; } public void setAllowedHosts(List<String> allowed) { assert !global : "constraint is global"; assert readOnlyConstraint != null : "invalid cast"; this.hostsHolder.specific = new LinkedHashSet<String>(allowed); } @Override public boolean violates(Constraint other, Action.ActionEffect actionEffect) { if (other instanceof HostEffectConstraint) { HostEffectConstraint hec = (HostEffectConstraint) other; Set<String> ourSpecific = hostsHolder.specific; Set<String> hecSpecific = hec.hostsHolder.specific; if (user) { assert !hec.user : "illegal comparison"; if (readOnly) { // Allow global or any matching server group if (!hec.global) { boolean anyMatch = anyMatch(ourSpecific, hecSpecific); if (!anyMatch) ControllerLogger.ACCESS_LOGGER.tracef("read-only host constraint violated " + "for action %s due to no match between hosts %s and allowed hosts %s", actionEffect, hecSpecific, ourSpecific); return !anyMatch; } } else if (!global) { if (hec.global) { // Only the readOnlyConstraint gets global ControllerLogger.ACCESS_LOGGER.tracef("host constraint violated for action %s due to " + "requirement for access to global resources", actionEffect); return true; } else { if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) { // Writes must not effect other groups boolean containsAll = ourSpecific.containsAll(hecSpecific); if (!containsAll) { ControllerLogger.ACCESS_LOGGER.tracef("host constraint violated for action %s due to " + "mismatch of hosts %s vs hosts %s", actionEffect, hecSpecific, ourSpecific); } return !containsAll; } else { // Reads ok as long as one of our groups match boolean anyMatch = anyMatch(ourSpecific, hecSpecific); if (!anyMatch) ControllerLogger.ACCESS_LOGGER.tracef("host constraint violated " + "for action %s due to no match between hosts %s and allowed hosts %s", actionEffect, hecSpecific, ourSpecific); return !anyMatch; } } // else fall through } } else { assert hec.user : "illegal comparison"; return other.violates(this, actionEffect); } } return false; } private boolean anyMatch(Set<String> ourSpecific, Set<String> hecSpecific) { boolean matched = false; for (String ourGroup : ourSpecific) { if (hecSpecific.contains(ourGroup)) { matched = true; break; } } if (!matched) { // WFLY-2089 matched = hecSpecific.size() == 1 && hecSpecific.contains("*"); } return matched; } @Override public boolean replaces(Constraint other) { return other instanceof HostEffectConstraint && (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 HostsHolder { private volatile Set<String> specific = new LinkedHashSet<String>(); private HostsHolder() { // no-op } private HostsHolder(Collection<String> hosts) { this.specific.addAll(hosts); } } 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.getHostEffect()); } @Override public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetResource target) { return getRequiredConstraint(target.getHostEffect()); } private Constraint getRequiredConstraint(HostEffect hostEffect) { if (hostEffect == null || hostEffect.isHostEffectGlobal()) { return GLOBAL_REQUIRED; } return new HostEffectConstraint(hostEffect.getAffectedHosts()); } @Override public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, JmxAction action, JmxTarget target) { return getRequiredConstraint(target.getHostEffect()); } @Override protected int internalCompare(AbstractConstraintFactory other) { // We prefer going first return this.equals(other) ? 0 : -1; } } }