/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.jcr.security.authorization;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.Privilege;

import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.junit.Before;
import org.junit.Test;

/**
 * Permission evaluation tests for move operations.
 */
public abstract class AbstractMoveTest extends AbstractEvaluationTest {

    protected String nodePath3;

    protected String destPath;
    protected String siblingDestPath;

    protected Privilege[] modifyChildCollection;

    @Override
    @Before
    protected void setUp() throws Exception {
        super.setUp();

        Node node3 = superuser.getNode(childNPath).addNode(nodeName3);
        node3.setProperty("movedProp", "val");
        nodePath3 = node3.getPath();
        superuser.save();
        testSession.refresh(false);

        destPath = path + "/destination";
        siblingDestPath = siblingPath + "/destination";

        modifyChildCollection = privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_REMOVE_CHILD_NODES});
    }

    abstract protected void move(String source, String dest) throws RepositoryException;
    abstract protected void move(String source, String dest, Session session) throws RepositoryException;

    @Test
    public void testMove() throws Exception {
        // give 'add_child_nodes' and 'nt-management' privilege
        // -> not sufficient privileges for a move
        allow(path, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_NODE_TYPE_MANAGEMENT}));
        try {
            move(childNPath, destPath);
            fail("Move requires addChildNodes and removeChildNodes privilege.");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testMove2() throws Exception {
        // grant 'add_child_nodes', 'remove_child_nodes' at 'path'
        // -> not sufficient for a move since 'remove_node' privilege is missing
        //    on the move-target
        allow(path, modifyChildCollection);
        try {
            move(childNPath, destPath);
            fail("Move requires addChildNodes and removeChildNodes privilege.");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testMove2b() throws Exception {
        // grant 'add_child_nodes', 'remove_child_nodes' and 'nt_mgt' at 'path'
        // -> not sufficient for a move since 'remove_node' privilege is missing
        //    on the move-target
        allow(path, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_REMOVE_CHILD_NODES,
                Privilege.JCR_NODE_TYPE_MANAGEMENT}));
        try {
            move(childNPath, destPath);
            fail("Move requires addChildNodes and removeChildNodes privilege.");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testMove3() throws Exception {
        allow(path, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_REMOVE_CHILD_NODES,
                Privilege.JCR_NODE_TYPE_MANAGEMENT}));

        // allow 'remove_node' at childNPath
        // -> now move must succeed
        allow(childNPath, privilegesFromName(Privilege.JCR_REMOVE_NODE));

        move(childNPath, destPath);
    }

    @Test
    public void testMove4() throws Exception {
        allow(path, privilegesFromName(PrivilegeConstants.REP_WRITE));
        move(childNPath, destPath);

        // withdraw  'add_child_nodes' privilege on former src-parent
        // -> moving child-node back must fail
        deny(path, privilegesFromName(Privilege.JCR_ADD_CHILD_NODES));

        try {
            move(destPath, childNPath);
            fail("Move requires addChildNodes and removeChildNodes privilege.");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testMoveMissingNtManagement() throws Exception {
        // not granting jcr:nodeTypeManagement
        allow(path, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_REMOVE_CHILD_NODES,
                Privilege.JCR_REMOVE_NODE}));
        try {
            move(childNPath, destPath);
            fail("Move requires jcr:nodeTypeManagement privilege at destination.");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testMoveMissingPrivilegesInSubtree() throws Exception {
        // grant privileges required to move 'childNPath' to 'destPath'
        allow(path, privilegesFromName(PrivilegeConstants.REP_WRITE));
        // revoke privileges such that only 'childNPath' can be removed and added
        deny(childNPath, modifyChildCollection);
        deny(nodePath3, privilegesFromNames(new String[] {Privilege.JCR_REMOVE_NODE}));
        move(childNPath, destPath);
    }

    @Test
    public void testMissingJcrAddChildNodesAtDestParent() throws Exception {
        allow(path, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES,
                Privilege.JCR_REMOVE_CHILD_NODES}));
        try {
            move(childNPath, siblingDestPath);
            fail("Move requires addChildNodes privilege at dest parent");
        } catch (AccessDeniedException e) {
            // success.
        }
    }

    @Test
    public void testDifferentDestParent() throws Exception {
        allow(path, privilegesFromName(Privilege.JCR_REMOVE_CHILD_NODES));
        allow(siblingPath, privilegesFromNames(new String[] {
                Privilege.JCR_ADD_CHILD_NODES, Privilege.JCR_NODE_TYPE_MANAGEMENT
        }));
        allow(childNPath, privilegesFromName(Privilege.JCR_REMOVE_NODE));

        move(childNPath, siblingDestPath);
    }

    @Test
    public void testMoveAccessControlledNode() throws Exception {
        // permissions defined @ childNode
        // -> revoke read permission
        deny(childNPath, readPrivileges);

        assertFalse(testSession.nodeExists(childNPath));
        assertHasPrivileges(childNPath, readPrivileges, false);
        assertFalse(testSession.nodeExists(nodePath3));
        assertHasPrivileges(nodePath3, readPrivileges, false);

        // move the ancestor node
        String movedChildNPath = path + "/movedNode";
        String movedNode3Path = movedChildNPath + '/' + nodeName3;

        move(childNPath, movedChildNPath, superuser);

        // expected behavior:
        // the AC-content present on childNode is still enforced both on
        // the node itself and on the subtree.
        assertFalse(testSession.nodeExists(movedChildNPath));
        assertHasPrivileges(movedChildNPath, readPrivileges, false);
        assertFalse(testSession.nodeExists(movedNode3Path));
        assertHasPrivileges(movedNode3Path, readPrivileges, false);
    }

    @Test
    public void testMoveAccessControlledNodeInSubtree() throws Exception {
        // permissions defined @ node3Path
        // -> revoke read permission
        deny(nodePath3, readPrivileges);

        assertFalse(testSession.nodeExists(nodePath3));
        assertHasPrivileges(nodePath3, readPrivileges, false);

        // move the ancestor node
        String movedChildNPath = path + "/movedNode";
        String movedNode3Path = movedChildNPath + '/' + nodeName3;

        move(childNPath, movedChildNPath, superuser);

        // expected behavior:
        // the AC-content present on node3 is still enforced
        assertFalse(testSession.nodeExists(movedNode3Path));
        assertHasPrivileges(movedNode3Path, readPrivileges, false);
    }

    @Test
    public void testMoveWithDifferentEffectiveAc() throws Exception {
        // @path read is denied, @childNode its allowed again
        deny(path, readPrivileges);
        allow(childNPath, readPrivileges);

        assertTrue(testSession.nodeExists(nodePath3));
        assertTrue(testAcMgr.hasPrivileges(nodePath3, readPrivileges));

        // move the ancestor node
        String movedPath = path + "/movedNode";

        move(nodePath3, movedPath, superuser);

        // expected behavior:
        // due to move node3 should not e visible any more
        assertFalse(testSession.nodeExists(movedPath));
        assertHasPrivileges(movedPath, readPrivileges, false);
    }

    @Test
    public void testMoveNodeWithGlobRestriction() throws Exception {
        // permissions defined @ path
        // restriction: remove read priv to nodeName3 node
        deny(childNPath, readPrivileges, createGlobRestriction('/' +nodeName3));

        assertFalse(testSession.nodeExists(nodePath3));
        assertHasPrivileges(nodePath3, readPrivileges, false);

        String movedChildNPath = path + "/movedNode";
        String movedNode3Path = movedChildNPath + '/' + nodeName3;

        move(childNPath, movedChildNPath, superuser);

        assertFalse(testSession.nodeExists(movedNode3Path));
        assertHasPrivileges(movedNode3Path, readPrivileges, false);
    }

    @Test
    public void testMoveNodeWithGlobRestriction2() throws Exception {
        // permissions defined @ path
        // restriction: remove read priv to nodeName3 node
        deny(childNPath, readPrivileges, createGlobRestriction('/' + nodeName3));

        assertHasPrivileges(nodePath3, readPrivileges, false);

        String movedChildNPath = path + "/movedNode";
        String movedNode3Path = movedChildNPath + '/' + nodeName3;

        move(childNPath, movedChildNPath, superuser);

        assertFalse(testSession.nodeExists(movedNode3Path));
        assertHasPrivileges(movedNode3Path, readPrivileges, false);
    }
}
