package io.oasp.module.security.common.base.accesscontrol;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.mmm.util.collection.base.NodeCycle;
import net.sf.mmm.util.collection.base.NodeCycleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.oasp.module.security.common.api.accesscontrol.AccessControl;
import io.oasp.module.security.common.api.accesscontrol.AccessControlGroup;
import io.oasp.module.security.common.api.accesscontrol.AccessControlPermission;
import io.oasp.module.security.common.api.accesscontrol.AccessControlProvider;
import io.oasp.module.security.common.api.accesscontrol.AccessControlSchema;
/**
* This is the abstract base implementation of {@link AccessControlProvider}.<br/>
* ATTENTION:<br/>
* You need to call {@link #initialize(AccessControlSchema)} from the derived implementation.
*
*/
public abstract class AbstractAccessControlProvider implements AccessControlProvider {
/** Logger instance. */
private static final Logger LOG = LoggerFactory.getLogger(AbstractAccessControlProvider.class);
/** @see #getAccessControl(String) */
private final Map<String, AccessControl> id2nodeMap;
/**
* The constructor.
*/
public AbstractAccessControlProvider() {
super();
this.id2nodeMap = new HashMap<>();
}
/**
* Performs the required initialization of this class.
*
* @param config is the {@link AccessControlSchema}.
*/
protected void initialize(AccessControlSchema config) {
LOG.debug("Initializing.");
List<AccessControlGroup> groups = config.getGroups();
if (groups.size() == 0) {
throw new IllegalStateException("AccessControlSchema is empty - please configure at least one group!");
}
Set<AccessControlGroup> toplevelGroups = new HashSet<>(groups);
for (AccessControlGroup group : groups) {
collectAccessControls(group, toplevelGroups);
NodeCycle<AccessControlGroup> nodeCycle = new NodeCycle<>(group);
checkForCyclicDependencies(group, nodeCycle);
}
}
/**
* Checks that the given {@link AccessControlGroup} has no cyclic {@link AccessControlGroup#getInherits() inheritance
* graph}.
*
* @param group is the {@link AccessControlGroup} to check.
* @param nodeCycle the {@link NodeCycle} used to detect cycles.
*/
protected void checkForCyclicDependencies(AccessControlGroup group, NodeCycle<AccessControlGroup> nodeCycle) {
for (AccessControlGroup inheritedGroup : group.getInherits()) {
List<AccessControlGroup> inverseCycle = nodeCycle.getInverseCycle();
if (inverseCycle.contains(inheritedGroup)) {
throw new NodeCycleException(nodeCycle);
}
inverseCycle.add(inheritedGroup);
checkForCyclicDependencies(inheritedGroup, nodeCycle);
AccessControlGroup removed = inverseCycle.remove(inverseCycle.size() - 1);
assert (removed == inheritedGroup);
}
}
/**
* Called from {@link #initialize(AccessControlSchema)} to collect all {@link AccessControl}s recursively.
*
* @param group the {@link AccessControlGroup} to traverse.
* @param toplevelGroups is the {@link Set} of all {@link AccessControlGroup}s from
* {@link AccessControlSchema#getGroups()}.
*/
protected void collectAccessControls(AccessControlGroup group, Set<AccessControlGroup> toplevelGroups) {
if (!toplevelGroups.contains(group)) {
throw new IllegalStateException("Invalid group not declared as top-level group in schema: " + group);
}
AccessControl old = this.id2nodeMap.put(group.getId(), group);
if (old != null) {
LOG.debug("Already visited access control group {}", group);
if (old != group) {
throw new IllegalStateException(
"Invalid security configuration: duplicate groups with id " + group.getId() + "!");
}
// group has already been visited, stop recursion...
return;
} else {
LOG.debug("Registered access control group {}", group);
}
for (AccessControlPermission permission : group.getPermissions()) {
old = this.id2nodeMap.put(permission.getId(), permission);
if (old != null) {
// throw new IllegalStateException("Invalid security configuration: duplicate permission with id "
// + permission.getId() + "!");
LOG.warn("Security configuration contains duplicate permission with id {}.", permission.getId());
} else {
LOG.debug("Registered access control permission {}", permission);
}
}
for (AccessControlGroup inheritedGroup : group.getInherits()) {
collectAccessControls(inheritedGroup, toplevelGroups);
}
}
@Override
public AccessControl getAccessControl(String nodeId) {
return this.id2nodeMap.get(nodeId);
}
@Override
public boolean collectAccessControlIds(String groupId, Set<String> permissions) {
AccessControl node = getAccessControl(groupId);
if (node instanceof AccessControlGroup) {
collectPermissionIds((AccessControlGroup) node, permissions);
} else {
// node does not exist or is a flat AccessControlPermission
permissions.add(groupId);
}
return (node != null);
}
/**
* Recursive implementation of {@link #collectAccessControlIds(String, Set)} for {@link AccessControlGroup}s.
*
* @param group is the {@link AccessControlGroup} to traverse.
* @param permissions is the {@link Set} used to collect.
*/
public void collectPermissionIds(AccessControlGroup group, Set<String> permissions) {
boolean added = permissions.add(group.getId());
if (!added) {
// we have already visited this node, stop recursion...
return;
}
for (AccessControlPermission permission : group.getPermissions()) {
permissions.add(permission.getId());
}
for (AccessControlGroup inheritedGroup : group.getInherits()) {
collectPermissionIds(inheritedGroup, permissions);
}
}
@Override
public boolean collectAccessControls(String groupId, Set<AccessControl> permissions) {
AccessControl node = getAccessControl(groupId);
if (node == null) {
return false;
}
if (node instanceof AccessControlGroup) {
collectPermissionNodes((AccessControlGroup) node, permissions);
} else {
// node is a flat AccessControlPermission
permissions.add(node);
}
return true;
}
/**
* Recursive implementation of {@link #collectAccessControls(String, Set)} for {@link AccessControlGroup}s.
*
* @param group is the {@link AccessControlGroup} to traverse.
* @param permissions is the {@link Set} used to collect.
*/
public void collectPermissionNodes(AccessControlGroup group, Set<AccessControl> permissions) {
boolean added = permissions.add(group);
if (!added) {
// we have already visited this node, stop recursion...
return;
}
for (AccessControlPermission permission : group.getPermissions()) {
permissions.add(permission);
}
for (AccessControlGroup inheritedGroup : group.getInherits()) {
collectPermissionNodes(inheritedGroup, permissions);
}
}
}