/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jaspersoft.jasperserver.api.metadata.view.service.impl;

import java.util.Iterator;
import java.util.List;

import junit.textui.TestRunner;

import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.acl.AclEntry;
import org.springframework.security.acl.AclProvider;
import org.springframework.security.acl.basic.BasicAclEntry;
import org.springframework.security.acl.basic.SimpleAclEntry;

import com.jaspersoft.jasperserver.api.metadata.common.domain.Folder;
import com.jaspersoft.jasperserver.api.metadata.common.domain.Resource;
import com.jaspersoft.jasperserver.api.metadata.common.domain.ResourceLookup;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.ReportUnit;
import com.jaspersoft.jasperserver.api.metadata.user.domain.ObjectPermission;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Role;
import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import com.jaspersoft.jasperserver.api.metadata.user.service.impl.AclService;
import com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria;
import com.jaspersoft.jasperserver.util.test.BaseJasperServerTest;

/**
 * @author swood
 *
 */
public class ObjectPermissionServiceTest extends BaseJasperServerTest {

    public static String reportsFolderPath = Folder.SEPARATOR + "reports";
    public static String reportUnitFolder = reportsFolderPath + Folder.SEPARATOR + "samples";
    public static String reportUnitName = "AllAccounts";
    public static String reportUnitPath = reportUnitFolder + Folder.SEPARATOR + reportUnitName;

    public static String testUserName = "TestUser";
    public static String testRoleName = "ROLE_TEST";
    public static String portletRoleName = "ROLE_PORTLET";
    public static int readPermissionMask = SimpleAclEntry.READ;
    public static int adminPermissionMask = SimpleAclEntry.ADMINISTRATION;

    ReportUnit unit;
    Role userRole, adminRole, testRole, portletRole;
    User adminUser, testUser;

    Folder root, reportsFolder;

	public ObjectPermissionServiceTest(String name) {
        super(name);
        setAutowireMode(AUTOWIRE_BY_NAME);
    }

    public static void main(String[] args) {
        TestRunner.run(UserAuthorityServiceTest.class);
    }

    protected void onSetUp() throws Exception {
        System.out.println("ObjectPermissionServiceTest Setup");

        setUpRoles();
        setUpUsers();

        setAuthenticatedUser(adminUserName);

        createObjectPermission("/", adminRole, SimpleAclEntry.ADMINISTRATION);

        root = getRepositoryService().getFolder(null, Folder.SEPARATOR);

       	assertTrue("null root", root != null);

        reportsFolder = getRepositoryService().getFolder(null, reportsFolderPath);

        assertTrue("null reportsFolder", reportsFolder != null);

        // THESE ARE DEFAULTS FOR THE SYSTEM: not deleted

        // Create default permission of ADMINISTRATION at the root level


        /*
         * Just for testing: make sure that userRole does not have access to root
         */
        deleteObjectPermission(root, userRole);

        // Set up some data

        unit = (ReportUnit) getRepositoryService().getResource(null, reportUnitPath);
        assertNotNull("Null ReportUnit", unit);
        assertEquals("Unmatched ReportUnit name", reportUnitName, unit.getName());

    }

    /**
     * ROLE_USER
     * ROLE_ADMINISTRATOR
     *
     */
    private void setUpRoles() {

        userRole = getOrCreateRole(userRoleName);

        adminRole = getOrCreateRole(administratorRoleName);

        testRole = getOrCreateRole(testRoleName);

        portletRole = getOrCreateRole(portletRoleName);
    }

    /**
     * testUserName: TestUser has role: ROLE_TEST
     * adminUserName: admin has role: ROLE_ADMINISTRATOR
     *
     */
    private void setUpUsers() {
        testUser = findOrCreateUser(testUserName);

        getUserAuthorityService().addRole(null, testUser, testRole);
        adminUser = findOrCreateUser(adminUserName);

        getUserAuthorityService().addRole(null, adminUser, adminRole);

    }

     public void onTearDown() {
        System.out.println("ObjectPermissionServiceTest Tear down");

        // Remove read permissions for the user at the report unit level
        User u = getUserAuthorityService().getUser(null, testUserName);
        Resource r = getRepositoryService().getResource(null, reportUnitPath);
        deleteObjectPermission(r, u);

        // Remove block permissions at the folder level
        System.out.println("Deleting permission for " + userRoleName + " and " + administratorRoleName + " for " + reportUnitFolder);

        Folder f = getRepositoryService().getFolder(null, reportUnitFolder);
        deleteObjectPermission(f, userRole);
        deleteObjectPermission(f, adminRole);

        // Remove block permissions at the folder level
        System.out.println("Deleting permission for " + userRoleName + " and " + Folder.SEPARATOR);
        f = getRepositoryService().getFolder(null, Folder.SEPARATOR);
        deleteObjectPermission(f, userRole);

        System.out.println("Removing role: " + adminRole.getRoleName() + " from user: " + testUser.getUsername());
        getUserAuthorityService().removeRole(null, testUser, adminRole);

        u = getUserAuthorityService().getUser(null, testUser.getUsername());

        System.out.println("test user assigned roles are: ");
        for (Iterator it = u.getRoles().iterator(); it.hasNext(); ) {
            Role role = (Role) it.next();
            System.out.println("\t" + role.getRoleName());
        }

        // remove anything else still hanging around we don't want
        getUserAuthorityService().deleteUser(null, testUserName);
        getUserAuthorityService().deleteRole(null, testRoleName);


        /*
         *  Leave entries in the database:
         *          root level ADMINISTRATION permissions for ROLE_ADMINISTRATOR
         */

        /*
         * Set read permissions at the root level for ROLE_USER
         */

        createObjectPermission(root, userRole, SimpleAclEntry.READ);
    }

    /**
     * For our report unit without a parent:
     *      ROLE_ADMINISTRATOR: SimpleAclEntry.ADMINISTRATION
     *
     *   and
     *
     * testUserName: TestUser has role: ROLE_TEST
     * adminUserName: admin has role: ROLE_ADMINISTRATOR
     */
    public void testObjectPermissionSetup() {

        setAuthenticatedUser(adminUserName);

        List l = getObjectPermissionService().getObjectPermissionsForObjectAndRecipient(null, unit, adminRole);

        assertTrue("getObjectPermissionsForObjectAndRecipient size not 0: " +
                    (l == null ? "null" : (new Integer(l.size())).toString()), l == null || l.size() == 0);
/*
        ObjectPermission op = (ObjectPermission) l.get(0);

        System.out.println("ObjectPermission for unit and adminRole: " + op);
*/
        ReportUnit ru = (ReportUnit) getRepositoryService().getResource(null, unit.getURI());

        assertTrue("Null report unit for " + unit.getURI(), ru != null);

        assertTrue(ru.getName().equals(reportUnitName));
/*
        assertTrue("op has a non-null recipient: " + op.getPermissionRecipient(), op.getPermissionRecipient() == null);
*/
        String p = ru.getParentFolder();

        Folder f = getRepositoryService().getFolder(null, p);

        assertTrue("Invalid folder: " + p, f != null);

        l = getObjectPermissionService().getObjectPermissionsForObject(null, f);

        System.out.println("ObjectPermission for unit folder: " + l);

        assertTrue("getObjectPermissionsForObject size not 0: " +
                    (l == null ? "null" : (new Integer(l.size())).toString()), l == null || l.size() == 0);
    }

    public void testAclAccess() {

        Authentication aUser = setAuthenticatedUser(adminUserName);

        BasicAclEntry[] aclEntries = ((AclService) getObjectPermissionService()).getAcls(unit);

        assertTrue("aclEntries = null", aclEntries != null);

        printAclEntries("getAcls(unit)", aclEntries);

        assertTrue("aclEntries.length = " + aclEntries.length + " not 2", aclEntries.length == 3);

        AclEntry[] userEntries = ((AclProvider) getObjectPermissionService()).getAcls(unit, aUser);
        assertTrue("userEntries = null", userEntries != null);

        printAclEntries("getAcls(unit, aUser)", (BasicAclEntry[]) userEntries);

        // 1 entry from the root for ROLE_ADMINISTRATOR
        assertTrue("userEntries.length = " + userEntries.length + " not 1", userEntries.length == 2);

        AclEntry found = null;
        for (int i = 0; i < userEntries.length && found == null; i++ ) {
            Object entry = ((BasicAclEntry) userEntries[i]).getRecipient();
            found = (entry instanceof Role && ((Role) entry).getRoleName().equals(administratorRoleName)) ?
                    userEntries[i] : null;
        }

        assertTrue("Role recipient not found", found != null);
        //assertTrue("Wrong role: " + ((BasicAclEntry) found).getRecipient(), ((Role) ((BasicAclEntry) found).getRecipient()).getRoleName().equals(administratorRoleName));
    }

    public void testMethodAndObjectAccess() {

        setAuthenticatedUser(adminUserName);

        /*
         * Now test access:
         *      admin is the user, that has a ROLE_ADMINISTRATOR

                HibernateRepositoryService.loadResourcesList=ROLE_ADMINISTRATOR,AFTER_ACL_COLLECTION_READ

         */
        assertNotNull("null testUserName", testUserName);

        Resource r = getRepositoryService().getResource(null, reportUnitPath); // should be OK - admin has access

        assertNotNull("null Resource", r);

        exerciseResourcesListAccess(adminUserName, true,
                "ERROR: Administrator should have access to report unit via role at root",
                "Administrator has access to report unit");

        //System.out.println("Administrator has access to report unit");

        /*
         * The TestUser has ROLE_TEST role, but
         * does not have ROLE_ADMINISTRATOR access to the report unit, so it will not appear in the list
         */

        exerciseResourcesListAccess(testUserName, false,
                "ERROR: ROLE_TEST should not have access to report unit because there is no permissions for ROLE_TEST or this user",
                "ROLE_TEST does not have access to report unit");

        //System.out.println("ROLE_TEST does not have access to report unit");

        /*
         * Now add permissions for the testUserName to the report unit
         */
        setAuthenticatedUser(adminUserName);
        ObjectPermission op = createObjectPermission(r, testUser, SimpleAclEntry.READ);

        /*
         * Should be able to access now
         */

        exerciseResourcesListAccess(testUserName, true,
                "ERROR: Test user should have specific access to report unit",
                "Test user has access to report unit");

        //System.out.println("Test user has access to report unit");

        setAuthenticatedUser(adminUserName);
        getObjectPermissionService().deleteObjectPermission(null, op);

        // Give the testUserName a ROLE_ADMINISTRATOR role

        getUserAuthorityService().addRole(null, testUser, adminRole);

        /*
         * Should still be able to access
         */
        exerciseResourcesListAccess(testUserName, true,
                "ERROR: ROLE_ADMINISTRATOR should have access to root level",
                "ROLE_ADMINISTRATOR given access to root level");

        //System.out.println("ROLE_ADMINISTRATOR given access to root level");

        Folder f = getRepositoryService().getFolder(null, reportUnitFolder);

// TODO: Next test should be refactored. The reason is bug #22094: ROLE_USER should have write access to the /reports folder. This fail the Block Permission test.

/*
        // Block permissions at the folder level

        setAuthenticatedUser(adminUserName);
        ObjectPermission opTmp = createObjectPermission(f, testUser, SimpleAclEntry.ADMINISTRATION);   // temporary seting administrable rights to testUser
        op = createObjectPermission(f, adminRole, SimpleAclEntry.NOTHING);

        */
/*
         * Should not be able to access
         *//*

        exerciseResourcesListAccess(adminUserName, false,
                "ERROR: ROLE_ADMINISTRATOR should have no access to report unit folder level",
                "ROLE_ADMINISTRATOR given no access to report unit folder level");

        //System.out.println("ROLE_USER given no access to report unit folder level");

        // Should not be able to access: throw exception

        try {
        	getRepositoryService().getResource(null, reportUnitFolder);

            */
/*TODO STAS fail after upgrade to spring security 2*//*

            //fail("ERROR: had access to " + reportUnitFolder);
        } catch (Exception e) {
            System.out.println("OK: had no access to " + reportUnitFolder + ": " + e.getMessage());
        }

        // Allow permissions at the folder level

        setAuthenticatedUser(testUserName);
        op = createObjectPermission(f, adminRole, SimpleAclEntry.ADMINISTRATION);
        getObjectPermissionService().deleteObjectPermission(null, opTmp);
*/

        // Check again

        try {
        	getRepositoryService().getResource(null, reportUnitFolder);
            System.out.println("OK: had access to " + reportUnitFolder);
        } catch (Exception e) {
            fail("ERROR: had no access to " + reportUnitFolder + ": " + e.getMessage());
            e.printStackTrace();
        }
    }

    private void printAclEntries(String name, BasicAclEntry[] aclEntries) {

        System.out.println("AclEntry set: " + name);

        for (int i = 0; aclEntries != null && aclEntries.length > 0 && i < aclEntries.length; i++) {
            System.out.println("aclEntries[" + i + "]: " + aclEntries[i].getAclObjectIdentity() +
                    "\n\tparent: " + aclEntries[i].getAclObjectParentIdentity() +
                    "\n\trecipient: " + aclEntries[i].getRecipient() +
                    "\n\tmask: " + aclEntries[i].getMask());
        }

    }

    private void exerciseResourcesListAccess(String userName, boolean expectToFind,
            String testDescription, String successMessage) {
        FilterCriteria criteria = FilterCriteria.createFilter(ReportUnit.class);
        List l;

        setAuthenticatedUser(userName);

        Iterator it;
        boolean found = false;

        try {
            l = getRepositoryService().loadResourcesList(criteria);
            System.out.println(testDescription +  "\nloadResourcesList size: " + l.size());
            it = l.iterator();
            found = false;
            while (it.hasNext() && !found) {
                ResourceLookup rlu = (ResourceLookup) it.next();
                found = rlu.getURIString().equals(reportUnitPath);
            }

            // If we found and did not expect to, or we did not find and we expected to

            if (found != expectToFind) {
                fail(userName + ": loadResourcesList " + (expectToFind ? "did not contain " : "contained un") + "expected report unit for " + reportUnitName);
            }
        } catch (AccessDeniedException ex) {
            fail(userName + ": loadResourcesList AccessDeniedException unexpected");
        }

        System.out.println(successMessage);

    }

}
