/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 3. * * This program 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 General Public License for more details, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package org.flowerplatform.web.security.sandbox; import java.io.File; import java.security.Permission; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.flowerplatform.web.security.permission.AbstractTreePermission; import org.flowerplatform.web.security.permission.FlowerWebFilePermission; import org.flowerplatform.web.entity.ISecurityEntity; /** * Contains "tree-style" permissions (or hierarchical permission). * The collection is homogeneous (i.e. all the permissions are of same * type). * * <p> * A tree-style permission has a path on which that permission makes sense. * Examples of tree-style permissions: file system permission, modify permissions * permission. * * <p> * {@link AbstractTreePermission#AbstractTreePermission()}'s doc specifies conventions * for the path and wildcard. * * <p> * This class has logic to determine if a permission is given or not, based on * path verifications (i.e. verify the checked permission against the list of * permissions of this collection). If the algorithm matches some records (i.e. * {@link AbstractTreePermission}) from this collection, it will delegate to the corresponding * permission, to continue the evaluation. * * @see AbstractTreePermission * * @author Cristi * @author Florin * * */ public class TreePermissionCollection { /** * The class of the tree permissions in this {@link TreePermissionCollection}. * * <p> * Used for sanity check (i.e. ensure that all the permissions are of this type). * */ private Class<? extends AbstractTreePermission> treePermissionClass; /** * Flag indicating if the list is sorted or not. * */ private boolean sorted = false; /** * */ private File root; /** * This is a list of entries ordered by path. The paths (names) of * permissions encapsulated by {@link TreePermissionCollectionEntry} are * relative to {@link #root}. * * */ public List<TreePermissionCollectionEntry> entries = new ArrayList<TreePermissionCollectionEntry>(); /** * @param treePermissionClass - The class of the tree permissions in this {@link TreePermissionCollection}. * */ public TreePermissionCollection(Class<? extends AbstractTreePermission> treePermissionClass, File root) { if (treePermissionClass == null) { throw new IllegalArgumentException("treePermissionClass cannot be null"); } this.treePermissionClass = treePermissionClass; this.root = root; } /** * Adds a permission to the collection, for the given principal. * Throws an error if the type of <code>permission</code> differs from * the permission type of this {@link TreePermissionCollection}. * * */ public void addPermission(ISecurityEntity securityEntity, AbstractTreePermission permission) { if (securityEntity == null || permission == null) { throw new IllegalArgumentException(String.format("securityEntity and permission cannot be null. securityEntity = %s, permission = %s", securityEntity, permission)); } if (permission.getClass() != treePermissionClass) { throw new IllegalArgumentException(String.format("Attempted to add a permission of type %s in a TreePermissionCollection of type %s", permission.getClass(), treePermissionClass)); } entries.add(new TreePermissionCollectionEntry(securityEntity, permission)); sorted = false; } /** * Checks if the <code>permission</code> is granted to the <code>principal</code>. * * <p> * Contains the path check algorithm, described in the spec/mind map file. * * @param permission - Can be an {@link AbstractTreePermission} or normal {@link Permission}, * depending on the permission type of this {@link TreePermissionCollection}. E.g. for a * {@link FlowerWebFilePermission} the parameter would be of type {@link java.io.FilePermission}. * * */ public boolean implies(FlowerWebPrincipal principal, Permission permission) { if (!sorted) { // sort entries by permission path Collections.sort(entries, new Comparator<TreePermissionCollectionEntry>() { @Override public int compare(TreePermissionCollectionEntry e1, TreePermissionCollectionEntry e2) { return e1.getPermission().getName().compareTo(e2.getPermission().getName()); } }); sorted = true; } boolean refused = false; String pathRefused = null; for (int i = entries.size() - 1; i >= 0; i--) { TreePermissionCollectionEntry entry = entries.get(i); AbstractTreePermission availablePermission = entry.getPermission(); ISecurityEntity securityEntity = entry.getSecurityEntity(); // if we found a permission that denies access // we continue searching for permissions that allow access // as long as the path is the same. If path changes we do not // continue searching. if (refused && ! pathRefused.equals(availablePermission.getName())) { return false; } // if permission is java.io.FilePermission, we do not need to worry about * and - inconsistency // because we will be asked for a permission without wildcards if (pathMatch(permission.getName(), availablePermission.getName())) { // if path matches if (securityEntity.contains(principal.getUser())) { if (availablePermission.impliesWithoutTreePathCheck(permission)) { // and permission is allowed return true; } else { refused = true; pathRefused = availablePermission.getName(); } } } } return false; } /** * @param path * @param referencePath - The path of AbtractTreePermission (that supports * wildcard). * @return If <code>path</code> matches <code>referencePath</code>. * * */ public boolean pathMatch(String path, String referencePath) { boolean match = false; // referencePath is a path relative to root (the path from a AbstractTreePermission) referencePath = new File(root.getPath() + "/" + referencePath).getAbsolutePath().replace("\\", "/"); // path can be a relative path to user dir, not to root. For example new // File("a.txt").canRead will trigger a check for FilePermission("a.txt", "read"). path = new File(path).getAbsolutePath().replace("\\", "/"); if (referencePath.endsWith(AbstractTreePermission.STAR_WILDCARD)){ // path=D:/temp/a/file.txt // referencePath=D:/temp/* referencePath = referencePath.substring(0, referencePath.length() - 2); if (path.startsWith(referencePath)){ match = true; } } else if (referencePath.equals(path)) { // path=D:/temp/a // referencePath=D:/temp/a match = true; } return match; } /** * Returns true if the path is a child for the root of this permission * collection. * * @param path * a path to be checked if belongs to root. If it a relative path * it will be considered relative to user directory, not to root. * * */ public boolean belongsToRoot(String path) { String rootPath = root.getAbsolutePath().replace("\\", "/"); // A call like new File("some_relative_path").canRead will determine a security check for this path. // But this path is relative to user directory, not to this <code>root</root>. path = new File(path).getAbsolutePath().replace("\\", "/"); // path can be relative to user.dir. See getAbsolutePath() return path.startsWith(rootPath); } }