/*
 * 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.jcr2spi.security.authorization.jackrabbit.acl;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.Privilege;

import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QValueFactory;
import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
import org.junit.Assert;

/**
 * Tests the functionality of the JCR AccessControlList API implementation. The
 * purpose is to test the consistency of the access control list by a, adding ,
 * deleting and modifying entries in the list.
 */
public class AccessControlListImplTest extends AbstractAccessControlTest {

    private QValueFactory vFactory;

    private Principal unknownPrincipal;
    private Principal knownPrincipal;

    private NamePathResolver resolver;

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

        resolver = new DefaultNamePathResolver(superuser);
        vFactory = QValueFactoryImpl.getInstance();

        unknownPrincipal = getHelper().getUnknownPrincipal(superuser);
        knownPrincipal = new Principal() {
            @Override
            public String getName() {
                return "everyone";
            }
        };
    }

    private JackrabbitAccessControlList createAccessControList(String aclPath)
            throws RepositoryException {
        return new AccessControlListImpl(aclPath, resolver, vFactory);
    }

    private Map<String, Value> createEmptyRestriction() {
        return Collections.<String, Value> emptyMap();
    }

    public void testAddingDifferentEntries() throws Exception {
        JackrabbitAccessControlList acl = createAccessControList(testRoot);

        // allow read to unknownPrincipal
        Privilege[] p = privilegesFromName(Privilege.JCR_READ);
        acl.addAccessControlEntry(unknownPrincipal, p);

        // allow addChildNodes to secondPrincipal
        p = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
        acl.addAccessControlEntry(knownPrincipal, p);

        // deny modifyAccessControl to 'unknown' principal
        p = privilegesFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL);
        acl.addEntry(unknownPrincipal, p, false);

        // deny jcr:nodeTypeManagement to secondPrincipal
        p = privilegesFromName(Privilege.JCR_NODE_TYPE_MANAGEMENT);
        acl.addEntry(knownPrincipal, p, false);

        // four different entries
        Assert.assertEquals(4, acl.size());

        // UnknownPrincipal entries
        AccessControlEntry[] pentries = getEntries(acl, unknownPrincipal);
        Assert.assertEquals(2, pentries.length);

        // secondPrincipal entries
        AccessControlEntry[] sentries = getEntries(acl, knownPrincipal);
        Assert.assertEquals(2, sentries.length);
    }

    public void testMultipleEntryEffect() throws Exception {
        JackrabbitAccessControlList acl = createAccessControList(testRoot);
        Privilege[] privileges = privilegesFromName(Privilege.JCR_READ);

        // GRANT 'read' privilege to the Admin user -> list now contains one
        // allow entry
        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));

        // policy contains a single entry
        assertEquals(1, acl.size());

        AccessControlEntry[] entries = acl.getAccessControlEntries();

        // ... and the entry grants a single privilege
        assertEquals(1, entries[0].getPrivileges().length);
        assertEquals("jcr:read", entries[0].getPrivileges()[0].getName());

        // GRANT 'add_child_node' privilege for the admin user -> same entry but
        // with an additional 'add_child_node' privilege.
        privileges = privilegesFromNames(new String[] {Privilege.JCR_ADD_CHILD_NODES, Privilege.JCR_READ });
        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));

        // A new Entry was added -> entries count should be 2.
        assertEquals(2, acl.size());

        // The single entry should now contain both 'read' and 'add_child_nodes'
        // privileges for the same principal.
        assertEquals(1, acl.getAccessControlEntries()[0].getPrivileges().length);
        assertEquals(2, acl.getAccessControlEntries()[1].getPrivileges().length);

        // adding a privilege that's already granted for the same principal ->
        // again modified as the client doesn't care about possible compaction the
        // server may want to make.
        privileges = privilegesFromNames(new String[] { Privilege.JCR_READ });
        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));
        assertEquals(3, acl.size());

        // revoke the read privilege
        assertTrue("Fail to revoke read privilege", acl.addEntry(unknownPrincipal, privileges, false, createEmptyRestriction()));

        // should now be 3 entries -> 2 allow entry + a deny entry
        assertEquals(4, acl.size());
    }

    public void testMultipleEntryEffect2() throws Exception {
        JackrabbitAccessControlList acl = createAccessControList(testRoot);
        // GRANT a read privilege
        Privilege[] privileges = privilegesFromNames(new String[] { Privilege.JCR_READ });
        assertTrue("New Entry -> grants read privilege", acl.addAccessControlEntry(unknownPrincipal, privileges));

        assertTrue("Fail to revoke the read privilege", acl.addEntry(unknownPrincipal, privileges, false, createEmptyRestriction()));
        Assert.assertEquals(2, acl.size());
    }

    // -------------------------------------------------------< utility methods >---

    private AccessControlEntry[] getEntries(AccessControlList acl, Principal princ) throws RepositoryException {
        AccessControlEntry[] entries = acl.getAccessControlEntries();
        List<AccessControlEntry> entriesPerPrincipal = new ArrayList<AccessControlEntry>(2);
        for (AccessControlEntry entry : entries) {
            if (entry.getPrincipal().getName().equals(princ.getName())) {
                entriesPerPrincipal.add(entry);
            }
        }
        return entriesPerPrincipal.toArray(new AccessControlEntry[entriesPerPrincipal.size()]);
    }
 }
