info.magnolia.security.app.dialog.action.SaveRoleDialogAction.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.security.app.dialog.action.SaveRoleDialogAction.java

Source

/**
 * This file Copyright (c) 2012-2015 Magnolia International
 * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
 *
 *
 * This file is dual-licensed under both the Magnolia
 * Network Agreement and the GNU General Public License.
 * You may elect to use one or the other of these licenses.
 *
 * This file is distributed in the hope that it will be
 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
 * Redistribution, except as permitted by whichever of the GPL
 * or MNA you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or
 * modify this file under the terms of the GNU General
 * Public License, Version 3, as published by the Free Software
 * Foundation.  You should have received a copy of the GNU
 * General Public License, Version 3 along with this program;
 * if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 2. For the Magnolia Network Agreement (MNA), this file
 * and the accompanying materials are made available under the
 * terms of the MNA which accompanies this distribution, and
 * is available at http://www.magnolia-cms.com/mna.html
 *
 * Any modifications to this file must keep this entire header
 * intact.
 *
 */
package info.magnolia.security.app.dialog.action;

import info.magnolia.cms.security.Permission;
import info.magnolia.cms.security.PrincipalUtil;
import info.magnolia.cms.security.Role;
import info.magnolia.cms.security.RoleManager;
import info.magnolia.cms.security.SecuritySupport;
import info.magnolia.cms.security.auth.ACL;
import info.magnolia.cms.security.operations.AccessDefinition;
import info.magnolia.context.MgnlContext;
import info.magnolia.jcr.util.NodeUtil;
import info.magnolia.objectfactory.Components;
import info.magnolia.security.app.dialog.field.AccessControlList;
import info.magnolia.security.app.dialog.field.WorkspaceAccessFieldFactory;
import info.magnolia.security.app.util.UsersWorkspaceUtil;
import info.magnolia.ui.admincentral.dialog.action.SaveDialogAction;
import info.magnolia.ui.admincentral.dialog.action.SaveDialogActionDefinition;
import info.magnolia.ui.api.action.ActionExecutionException;
import info.magnolia.ui.form.EditorCallback;
import info.magnolia.ui.form.EditorValidator;
import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
import info.magnolia.ui.vaadin.integration.jcr.JcrNewNodeAdapter;
import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
import info.magnolia.ui.vaadin.integration.jcr.ModelConstants;

import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;

import org.apache.commons.lang3.StringUtils;

import com.vaadin.data.Item;
import com.vaadin.data.Property;

/**
 * Save role dialog action. Transforms nodes added by {@link info.magnolia.security.app.dialog.field.WorkspaceAccessFieldFactory} to its final representation.
 */
public class SaveRoleDialogAction extends SaveDialogAction {

    private final SecuritySupport securitySupport;

    public SaveRoleDialogAction(SaveDialogActionDefinition definition, Item item, EditorValidator validator,
            EditorCallback callback, SecuritySupport securitySupport) {
        super(definition, item, validator, callback);
        this.securitySupport = securitySupport;
    }

    /**
     * @deprecated since 5.2.1 - use {@link SaveRoleDialogAction#SaveRoleDialogAction(info.magnolia.ui.admincentral.dialog.action.SaveDialogActionDefinition, com.vaadin.data.Item, info.magnolia.ui.form.EditorValidator, info.magnolia.ui.form.EditorCallback, info.magnolia.cms.security.SecuritySupport)} instead.
     */
    public SaveRoleDialogAction(SaveDialogActionDefinition definition, Item item, EditorValidator validator,
            EditorCallback callback) {
        this(definition, item, validator, callback, Components.getComponent(SecuritySupport.class));
    }

    @Override
    public void execute() throws ActionExecutionException {

        final JcrNodeAdapter nodeAdapter = (JcrNodeAdapter) item;

        // First validate
        validator.showValidation(true);
        if (validator.isValid() && validateAccessControlLists(nodeAdapter)) {
            createOrUpdateRole(nodeAdapter);
            callback.onSuccess(getDefinition().getName());

        } else {
            // validation errors are displayed in the UI.
        }
    }

    private void createOrUpdateRole(JcrNodeAdapter roleItem) throws ActionExecutionException {
        try {

            final RoleManager roleManager = securitySupport.getRoleManager();

            final String newRoleName = (String) roleItem.getItemProperty(ModelConstants.JCR_NAME).getValue();

            Role role;
            Node roleNode;
            if (roleItem instanceof JcrNewNodeAdapter) {

                // JcrNewNodeAdapter returns the parent JCR item here
                Node parentNode = roleItem.getJcrItem();
                String parentPath = parentNode.getPath();

                role = roleManager.createRole(parentPath, newRoleName);
                roleNode = parentNode.getNode(role.getName());

                // Repackage the JcrNewNodeAdapter as a JcrNodeAdapter so we can update the node
                roleItem = convertNewNodeAdapterForUpdating((JcrNewNodeAdapter) roleItem, roleNode);
                roleNode = roleItem.applyChanges();
            } else {
                // First fetch the initial name (changes not applied yet here).
                String existingRoleName = roleItem.getJcrItem().getName();
                String pathBefore = roleItem.getJcrItem().getPath();
                // Apply changes now since the further operations on ACL's are done on nodes.
                roleNode = roleItem.applyChanges();
                if (!StringUtils.equals(existingRoleName, newRoleName)) {
                    NodeUtil.renameNode(roleNode, newRoleName);
                    roleNode.setProperty("name", newRoleName);
                    UsersWorkspaceUtil.updateAcls(roleNode, pathBefore);
                }
            }

            if (roleNode.hasNode("acl_userroles/0")) {
                Node entryNode = roleNode.getNode("acl_userroles/0");
                entryNode.setProperty(WorkspaceAccessFieldFactory.INTERMEDIARY_FORMAT_PROPERTY_NAME, "true");
                entryNode.setProperty(WorkspaceAccessFieldFactory.ACCESS_TYPE_PROPERTY_NAME,
                        AccessControlList.ACCESS_TYPE_NODE);
                entryNode.getSession().save();
            }

            for (Node aclNode : NodeUtil.getNodes(roleNode)) {

                if (aclNode.getName().startsWith("acl_") && !aclNode.getName().equals("acl_uri")) {

                    AccessControlList acl = new AccessControlList();

                    for (Node entryNode : NodeUtil.getNodes(aclNode)) {

                        if (entryNode.hasProperty(WorkspaceAccessFieldFactory.INTERMEDIARY_FORMAT_PROPERTY_NAME)) {
                            String path = entryNode.getProperty(AccessControlList.PATH_PROPERTY_NAME).getString();
                            long accessType = entryNode
                                    .getProperty(WorkspaceAccessFieldFactory.ACCESS_TYPE_PROPERTY_NAME).getLong();
                            long permissions = entryNode.getProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME)
                                    .getLong();

                            path = stripWildcardsFromPath(path);

                            if (StringUtils.isNotBlank(path)) {
                                acl.addEntry(new AccessControlList.Entry(permissions, accessType, path));
                            }
                        }
                        entryNode.remove();
                    }

                    aclNode.setProperty(WorkspaceAccessFieldFactory.INTERMEDIARY_FORMAT_PROPERTY_NAME,
                            (Value) null);
                    acl.saveEntries(aclNode);
                }
            }

            roleNode.getSession().save();
        } catch (final Exception e) {
            throw new ActionExecutionException(e);
        }
    }

    private JcrNodeAdapter convertNewNodeAdapterForUpdating(JcrNewNodeAdapter newNodeAdapter, Node node)
            throws RepositoryException {

        JcrNodeAdapter adapter = new JcrNodeAdapter(node);

        for (Object propertyId : newNodeAdapter.getItemPropertyIds()) {
            Property property = adapter.getItemProperty(propertyId);
            if (property == null) {
                adapter.addItemProperty(propertyId, newNodeAdapter.getItemProperty(propertyId));
            } else {
                property.setValue(newNodeAdapter.getItemProperty(propertyId).getValue());
            }
        }

        adapter.getChildren().clear();
        for (AbstractJcrNodeAdapter child : newNodeAdapter.getChildren().values()) {

            if (child instanceof JcrNewNodeAdapter) {
                if (node.hasNode(child.getNodeName())) {
                    if (child.getNodeName().startsWith("acl_")) {
                        child = convertNewNodeAdapterForUpdating((JcrNewNodeAdapter) child,
                                node.getNode(child.getNodeName()));
                        adapter.addChild(child);
                    } else {
                        child.setNodeName(getUniqueNodeNameForChild(child.getParent()));
                        child.setParent(adapter);
                        child.setItemId(adapter.getItemId());
                    }
                } else {
                    child.setParent(adapter);
                    child.setItemId(adapter.getItemId());
                }
            }
            adapter.addChild(child);
        }

        return adapter;
    }

    private String getUniqueNodeNameForChild(AbstractJcrNodeAdapter parentItem) throws RepositoryException {

        // The adapter cannot handle more than one unnamed child, see MGNLUI-1459, so we have to generate unique ones

        Node parentNode = null;
        if (!(parentItem instanceof JcrNewNodeAdapter)) {
            parentNode = parentItem.getJcrItem();
        }

        int newNodeName = 0;
        while (true) {
            if (parentItem.getChild(String.valueOf(newNodeName)) != null) {
                newNodeName++;
                continue;
            }
            if (parentNode != null && parentNode.hasNode(String.valueOf(newNodeName))) {
                newNodeName++;
                continue;
            }
            break;
        }

        return String.valueOf(newNodeName);
    }

    /**
     * Validates the ACLs present in the dialog. The validation is done on the JcrNodeAdapter because we have to validate
     * before calling applyChanges. applyChanges() modifies the adapter and it needs to be untouched when validation
     * fails because it is then still used in the dialog.
     */
    private boolean validateAccessControlLists(JcrNodeAdapter roleItem) throws ActionExecutionException {

        if (MgnlContext.getUser().hasRole(AccessDefinition.DEFAULT_SUPERUSER_ROLE)) {
            return true;
        }

        try {
            if (roleItem instanceof JcrNewNodeAdapter) {
                Node parentNode = roleItem.getJcrItem();

                // Make sure this user is allowed to add a role here, the role manager would happily do it and then we'd fail to read the node
                parentNode.getSession().checkPermission(parentNode.getPath(), Session.ACTION_ADD_NODE);
            }

            for (AbstractJcrNodeAdapter aclItem : roleItem.getChildren().values()) {

                String aclNodeName = aclItem.getNodeName();

                if (aclNodeName.startsWith("acl_")) {

                    if (aclItem.getItemProperty(
                            WorkspaceAccessFieldFactory.INTERMEDIARY_FORMAT_PROPERTY_NAME) != null) {

                        // This is an ACL added using WorkspaceAccessFieldFactory

                        for (AbstractJcrNodeAdapter entryItem : aclItem.getChildren().values()) {

                            String path = (String) entryItem.getItemProperty(AccessControlList.PATH_PROPERTY_NAME)
                                    .getValue();
                            long accessType = (Long) entryItem
                                    .getItemProperty(WorkspaceAccessFieldFactory.ACCESS_TYPE_PROPERTY_NAME)
                                    .getValue();
                            long permissions = (Long) entryItem
                                    .getItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME).getValue();

                            String workspaceName = StringUtils.replace(aclItem.getNodeName(), "acl_", "");

                            if (!isCurrentUserEntitledToGrantRights(workspaceName, path, accessType, permissions)) {
                                throw new ActionExecutionException(
                                        "Access violation: could not create role. Have you the necessary grants to create such a role?");
                            }
                        }
                    } else if (aclNodeName.equals("acl_uri")) {

                        // This is an ACL added using WebAccessFieldFactory

                        for (AbstractJcrNodeAdapter entryItem : aclItem.getChildren().values()) {

                            String path = (String) entryItem.getItemProperty(AccessControlList.PATH_PROPERTY_NAME)
                                    .getValue();
                            long permissions = (Long) entryItem
                                    .getItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME).getValue();

                            if (!isCurrentUserEntitledToGrantUriRights(path, permissions)) {
                                throw new ActionExecutionException(
                                        "Access violation: could not create role. Have you the necessary grants to create such a role?");
                            }
                        }
                    }
                }
            }

            return true;

        } catch (AccessControlException e) {
            throw new ActionExecutionException(e);
        } catch (RepositoryException e) {
            throw new ActionExecutionException(e);
        }
    }

    /**
     * Examines whether the current user creating/editing a role has himself the required permissions to the workspaces
     * he's specifying in the ACLs. We See MGNLUI-2357.
     */
    private boolean isCurrentUserEntitledToGrantRights(String workspaceName, String path, long accessType,
            long permissions) throws RepositoryException {

        if (MgnlContext.getUser().hasRole(AccessDefinition.DEFAULT_SUPERUSER_ROLE)) {
            return true;
        }

        // Granting DENY access is only allowed if the user has READ access to the node
        if (permissions == Permission.NONE) {
            permissions = Permission.READ;
        }

        ACL acl = PrincipalUtil.findAccessControlList(MgnlContext.getSubject(), workspaceName);
        if (acl == null) {
            return false;
        }

        Permission ownPermissions = findBestMatchingPermissions(acl.getList(), stripWildcardsFromPath(path));
        if (ownPermissions == null) {
            return false;
        }

        boolean recursive = (accessType & AccessControlList.ACCESS_TYPE_CHILDREN) != 0;

        if (recursive && !ownPermissions.getPattern().getPatternString().endsWith("/*")) {
            return false;
        }

        return granted(ownPermissions, permissions);
    }

    /**
     * Examines whether the current user creating/editing a role has himself the required permissions to the URIs
     * he's specifying in the ACLs. We See MGNLUI-2357.
     */
    private boolean isCurrentUserEntitledToGrantUriRights(String path, long permissions)
            throws RepositoryException {

        if (MgnlContext.getUser().hasRole(AccessDefinition.DEFAULT_SUPERUSER_ROLE)) {
            return true;
        }

        // Granting DENY access is only allowed if the user has READ access to the path
        if (permissions == Permission.NONE) {
            permissions = Permission.READ;
        }

        ACL acl = PrincipalUtil.findAccessControlList(MgnlContext.getSubject(), "uri");
        if (acl == null) {
            return false;
        }

        boolean recursive = path.endsWith("*");

        Permission ownPermissions = findBestMatchingPermissions(acl.getList(), stripWildcardsFromPath(path));
        if (ownPermissions == null) {
            return false;
        }

        if (recursive && !ownPermissions.getPattern().getPatternString().endsWith("*")) {
            return false;
        }

        return granted(ownPermissions, permissions);
    }

    private String stripWildcardsFromPath(String path) {
        path = StringUtils.stripEnd(path, "/*");
        if (StringUtils.isBlank(path)) {
            path = "/";
        }
        return path;
    }

    private boolean granted(Permission permissionsGranted, long permissionsNeeded) {
        return (permissionsGranted.getPermissions() & permissionsNeeded) == permissionsNeeded;
    }

    private Permission findBestMatchingPermissions(List<Permission> permissions, String path) {
        if (permissions == null) {
            return null;
        }
        Permission bestMatch = null;
        long permission = 0;
        int patternLength = 0;
        ArrayList<Permission> temp = new ArrayList<Permission>();
        temp.addAll(permissions);
        for (Permission p : temp) {
            if (p.match(path)) {
                int l = p.getPattern().getLength();
                if (patternLength == l && (permission < p.getPermissions())) {
                    permission = p.getPermissions();
                    bestMatch = p;
                } else if (patternLength < l) {
                    patternLength = l;
                    permission = p.getPermissions();
                    bestMatch = p;
                }
            }
        }
        return bestMatch;
    }
}