/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.security.AccessMode; import org.geoserver.security.GeoServerSecurityFilterChainProxy; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * Represents a hierarchical security tree node. The tree as a whole is * represented by its root * * @author Andrea Aime - TOPP * */ public class SecureTreeNode { /** * Special role set used to mean every possible role in the system */ static final Set<String> EVERYBODY = Collections.singleton("*"); /** * The role given to the administrators */ static final String ROOT_ROLE = GeoServerRole.ADMIN_ROLE.getAuthority(); /** * Depth or the security tree root */ static int ROOT_DEPTH = 0; /** * Depth of a workspace/global group rule */ static int WS_LG_DEPTH = 1; /** * Depth of a resource specific rule */ static int RESOURCE_DEPTH = 2; Map<String, SecureTreeNode> children = new HashMap<String, SecureTreeNode>(); SecureTreeNode parent; /** * A map from access mode to set of roles that can perform that kind of * access. Given a certain access mode, the intepretation of the associated * set of roles is: * <ul> * <li>roles set null: no local rule, fall back on the parent of this node</li> * <li>roles set empty: nobody can perform this kind of access</li> * <li>roles list equals to {@link #EVERYBODY}: everybody can perform * accesses in that mode</li> * <li>otherwise: only users having at least one granted authority matching * one of the roles in the set can access</li> * </ul> */ Map<AccessMode, Set<String>> authorizedRoles = new HashMap<AccessMode, Set<String>>(); /** * Builds a child of the specified parent node * * @param parent */ private SecureTreeNode(SecureTreeNode parent) { this.parent = parent; // no rule specified, full fall back on the node's parent } /** * Builds the root of a security tree */ public SecureTreeNode() { // by default we allow access for everybody in all modes for the root // node, since we have no parent to fall back onto // -> except for admin access, default is administrator for (AccessMode mode : AccessMode.values()) { switch(mode) { case ADMIN: authorizedRoles.put(mode, Collections.singleton(ROOT_ROLE)); break; default: authorizedRoles.put(mode, EVERYBODY); } } } /** * Returns a child with the specified name, or null * * @param name * */ public SecureTreeNode getChild(String name) { return children.get(name); } /** * Adds a child to this path element * * @param name * */ public SecureTreeNode addChild(String name) { if (getChild(name) != null) throw new IllegalArgumentException("This pathElement " + name + " is already among my children"); SecureTreeNode child = new SecureTreeNode(this); children.put(name, child); return child; } /** * Tells if the user is allowed to access the PathElement with the specified * access mode. If no information can be found in the current node, the * decision is delegated to the parent. If the root is reached and it has no * security definition, access will be granted. Otherwise, the first path * element with a role list for the specified access mode will return true * if the user has a {@link GrantedAuthority} matching one of the specified * roles, false otherwise * * @param user * @param mode * */ public boolean canAccess(Authentication user, AccessMode mode) { Set<String> roles = getAuthorizedRoles(mode); if (GeoServerSecurityFilterChainProxy.isSecurityEnabledForCurrentRequest()==false) return true; // if we don't know, we ask the parent, otherwise we assume // the object is unsecured if (roles == null) { return parent.canAccess(user, mode); } // if the roles is just "*" any granted authority will match if (roles.equals(EVERYBODY)) return true; // let's scan thru the the authorities granted to the user and // see if he matches any of the write roles if (user == null || user.getAuthorities() == null) return false; // look for a match on the roles, using the "root" rules as well (root can do everything) for (GrantedAuthority authority : user.getAuthorities()) { final String userRole = authority.getAuthority(); if (roles.contains(userRole) || ROOT_ROLE.equals(userRole)) return true; } return false; } /** * Returns the authorized roles for the specified access mode. The * collection can be null if we don't have a rule, meaning the rule will * have to searched in the parent node */ public Set<String> getAuthorizedRoles(AccessMode mode) { return authorizedRoles.get(mode); } /** * Sets the authorized roles for the specified access mode */ public void setAuthorizedRoles(AccessMode mode, Set<String> roles) { authorizedRoles.put(mode, roles); } /** * Utility method that drills down from the current node using the specified * list of child names, and returns the latest element found along that path * (might not be correspondent to the full path specified, security paths * can be incomplete, the definition of the parent applies to the missing * children as well) * * @param pathElements * */ public SecureTreeNode getDeepestNode(String... pathElements) { SecureTreeNode curr = this; SecureTreeNode result = this; for (int i = 0; i < pathElements.length; i++) { final SecureTreeNode next = curr.getChild(pathElements[i]); if (next == null) { return result; } else { curr = next; // don't return info about a node that has no explicit // rule associated, the parent will do if(curr.authorizedRoles != null && !curr.authorizedRoles.isEmpty()) { result = curr; } } } return curr; } /** * Utility method that drills down from the current node using the specified * list of child names, and returns an element only if it fully matches the provided path * * @param pathElements * */ public SecureTreeNode getNode(String... pathElements) { SecureTreeNode curr = this; for (int i = 0; i < pathElements.length; i++) { final SecureTreeNode next = curr.getChild(pathElements[i]); if (next == null) { return null; } else { curr = next; } } return curr; } /** * The children of this secure tree node * * */ Map<String, SecureTreeNode> getChildren() { return children; } @Override public String toString() { // customized toString to avoid printing the whole tree recursively, this one prints only // the info in the current level return "SecureTreeNode [childrenCount=" + children.size() + ", hasParent=" + (parent != null) + ", authorizedRoles=" + authorizedRoles + "]"; } /** * Returns the node depth, 0 is the root, 1 is a workspace/global layer one, 2 is layer specific * @return */ int getDepth() { int depth = 0; Set<SecureTreeNode> visited = new HashSet<>(); SecureTreeNode n = this; while(n.parent != null && !visited.contains(n.parent)) { depth++; visited.add(n); n = n.parent; } return depth; } }