/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/security/ACLSecurityImpl.java,v 1.14 2004/12/21 16:12:44 luetzkendorf Exp $
 * $Revision: 1.14 $
 * $Date: 2004/12/21 16:12:44 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed 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.slide.security;

import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import org.apache.slide.common.Namespace;
import org.apache.slide.common.NamespaceConfig;
import org.apache.slide.common.PropertyName;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.structure.ActionNode;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.XMLValue;
import org.apache.slide.util.logger.Logger;
import org.jdom.JDOMException;

/**
 * WebDAV/ACL compliant security helper implementation (draft-12)
 *
 * @version $Revision: 1.14 $
 */
public class ACLSecurityImpl extends SecurityImpl {
    
    private static final String LOG_CHANNEL = ACLSecurityImpl.class.getName();
    private static final Vector EMPTY_VECTOR = new Vector();
    
    /**
     * Be sure to call init() before using an instance created with this
     * constructor.
     */
    public ACLSecurityImpl() {
        super();
    }
    
    /**
     * Constructor.
     *
     * @param namespace Namespace
     * @param namespaceConfig Namespace configuration
     */
    public ACLSecurityImpl(Namespace namespace, NamespaceConfig namespaceConfig) {
        super(namespace, namespaceConfig);
    }
    
    /**
     * Method init
     *
     * @param    namespace           a  Namespace
     * @param    namespaceConfig     a  NamespaceConfig
     */
    public void init(Namespace namespace, NamespaceConfig namespaceConfig) {
        super.init(namespace, namespaceConfig);
    }
    
    /**
     * Check whether or not an actor can perform the specified activity
     * on a collection.
     *
     * @param object Object on which access is tested
     * @param subject Subject who seeks to perform the action
     * @param action Action which is to be performed
     * @return true if the action can be performed
     * @exception ServiceAccessException DataSource access error
     * @exception ObjectNotFoundException Specified object was not found
     * in the DataSource
     */
    public boolean hasPermission(ObjectNode object, SubjectNode subject,
                                 ActionNode action)
        throws ServiceAccessException, ObjectNotFoundException {
        
        throw new UnsupportedOperationException(
            "Please use alternate signature: "
                +"hasPermission(SlideToken token, ObjectNode object, ActionNode action)");
    }
    
    /**
     * Check whether or not an actor (principal) can perform the specified activity
     * on the specified resource.
     *
     * @param    token               a  SlideToken
     * @param    objectNode          Object on which access is tested
     * @param    actionNode          Action which is to be performed
     *
     * @return   true if the action can be performed
     *
     * @throws   ServiceAccessException
     * @throws   ObjectNotFoundException
     */
    public boolean hasPermission(SlideToken token, ObjectNode objectNode, ActionNode actionNode)
        throws ServiceAccessException, ObjectNotFoundException {
        
        // logging
        if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
            try {
                logger.log("@@@ check object="+objectNode+", subject="+getPrincipal(token)+", action="+actionNode, LOG_CHANNEL, Logger.DEBUG);
            } catch (ObjectNotFoundException onfe) {
                // ignore as this is logging only
            }
        }
        // no check for default action (server intitialization)
        if (actionNode == namespaceConfig.getDefaultAction()) {
            return true;
        }
        
        Enumeration permissions = enumeratePermissions(token, objectNode, true);
        return evaluateAcl(token, objectNode, actionNode, permissions);
    }
    
    public boolean hasRole(SlideToken token, String role) throws ServiceAccessException, ObjectNotFoundException {
        return hasRole(token, (SubjectNode)getPrincipal(token), role);
    }
    
    // overwrites super
    public boolean hasRole(SlideToken token, SubjectNode subjectNode, String role) throws ServiceAccessException, ObjectNotFoundException {
        SubjectNode roleNode = null;
        if (namespaceConfig.getRolesPath() != null && namespaceConfig.getRolesPath().length() != 0) {
            roleNode = SubjectNode.getSubjectNode(namespaceConfig.getRolesPath()+"/"+role);
        }
        if (roleNode != null && matchPrincipal(token, subjectNode, roleNode)) {
            return true;
        }
        else {
            // check groups
            SubjectNode groupNode = null;
            if (namespaceConfig.getGroupsPath() != null && namespaceConfig.getGroupsPath().length() != 0) {
                groupNode = SubjectNode.getSubjectNode(namespaceConfig.getGroupsPath()+"/"+role);
            }
            return (groupNode != null && matchPrincipal(token, subjectNode, groupNode));
        }
    }
    
    // overwrites super
    public boolean hasRole(ObjectNode object, String role) throws ServiceAccessException, ObjectNotFoundException {
        throw new UnsupportedOperationException("Please use alternate signature: hasRole(SlideToken token, String role)");
    }
    
    // overwrites super
    public Enumeration getRoles(SlideToken token) throws ServiceAccessException, ObjectNotFoundException {
        return getRoles(token, (SubjectNode)getPrincipal(token));
    }
    
    /**
     * Get the role names the specified subject has (deeply over nested roles, if there)
     *
     * @param    token               a  SlideToken
     * @param    subjectNode         a  SubjectNode
     * @return   an Enumeration of role names
     * @throws   ServiceAccessException
     * @throws   ObjectNotFoundException
     */
    public Enumeration getRoles(SlideToken token, SubjectNode subjectNode) throws ServiceAccessException, ObjectNotFoundException {
        // check the principal exists
        Uri subjectUri = namespace.getUri(token, subjectNode.getUri());
        subjectUri.getStore().retrieveObject(subjectUri);
        
        Vector result = new Vector();
        Uri rolesUri = null;
        ObjectNode rolesNode = null;
        if (namespaceConfig.getRolesPath() != null && namespaceConfig.getRolesPath().length() != 0) {
            rolesUri = namespace.getUri(token, namespaceConfig.getRolesPath());
            try {
                rolesNode = rolesUri.getStore().retrieveObject(rolesUri);
            } catch (ObjectNotFoundException e) {}
            if (rolesNode != null) {
                Enumeration rolesEnum = rolesNode.enumerateBindings();
                while (rolesEnum.hasMoreElements()) {
                    ObjectNode.Binding b = (ObjectNode.Binding)rolesEnum.nextElement();
                    String role = b.getName();
                    if (hasRole(token, subjectNode, role)) {
                        result.add(role);
                    }
                }
            }
        }
        Uri groupsUri = null;
        ObjectNode groupsNode = null;
        if (namespaceConfig.getGroupsPath() != null && namespaceConfig.getGroupsPath().length() != 0) {
            groupsUri = namespace.getUri(token, namespaceConfig.getGroupsPath());
            groupsNode = groupsUri.getStore().retrieveObject(groupsUri);
            if (groupsNode != null) {
                Enumeration groupsEnum = groupsNode.enumerateBindings();
                while (groupsEnum.hasMoreElements()) {
                    ObjectNode.Binding b = (ObjectNode.Binding)groupsEnum.nextElement();
                    String group = b.getName();
                    if (hasRole(token, subjectNode, group)) {
                        result.add(group);
                    }
                }
            }
        }
        return result.elements();
    }
    
    /**
     * Get enumeration of paths according to property DAV:group-membership
     *
     * @param    token               a  SlideToken
     * @param    subjectNode         a  SubjectNode
     * @return   an Enumeration of paths (String)
     * @throws   ServiceAccessException
     * @throws   ObjectNotFoundException
     */
    public Enumeration getGroupMembership(SlideToken token, SubjectNode subjectNode) throws ServiceAccessException, ObjectNotFoundException {
        // check the principal exists
        Uri subjectUri = namespace.getUri(token, subjectNode.getUri());
        subjectUri.getStore().retrieveObject(subjectUri);
        
        Vector result = new Vector();
        Uri rolesUri = null;
        ObjectNode rolesNode = null;
        if (namespaceConfig.getRolesPath() != null && namespaceConfig.getRolesPath().length() != 0) {
            rolesUri = namespace.getUri(token, namespaceConfig.getRolesPath());
            try {
                rolesNode = rolesUri.getStore().retrieveObject(rolesUri);
            } catch (ObjectNotFoundException e) {}
            if (rolesNode != null) {
                Enumeration rolesEnum = rolesNode.enumerateBindings();
                while (rolesEnum.hasMoreElements()) {
                    ObjectNode.Binding b = (ObjectNode.Binding)rolesEnum.nextElement();
                    String role = b.getName();
                    Uri roleUri = namespace.getUri(token, namespaceConfig.getRolesPath()+"/"+role);
                    try {
                        NodeRevisionDescriptor nrd =
                            roleUri.getStore().retrieveRevisionDescriptor(roleUri, new NodeRevisionNumber());
                        NodeProperty membersetProp = nrd.getProperty(GROUP_MEMBER_SET);
                        if (membersetProp != null && membersetProp.getValue() != null) {
                            XMLValue xmlVal = new XMLValue((String)membersetProp.getValue());
                            List memberNodes = xmlVal.getHrefNodes();
                            if (memberNodes.contains(subjectNode)) {
                                result.add(roleUri.toString());
                            }
                        }
                    } catch (RevisionDescriptorNotFoundException e) {
                    } catch (JDOMException e) {}
                }
            }
        }
        Uri groupsUri = null;
        ObjectNode groupsNode = null;
        if (namespaceConfig.getGroupsPath() != null && namespaceConfig.getGroupsPath().length() != 0) {
            groupsUri = namespace.getUri(token, namespaceConfig.getGroupsPath());
            try {
                groupsNode = groupsUri.getStore().retrieveObject(groupsUri);
            } catch (ObjectNotFoundException e) {}
            if (groupsNode != null) {
                Enumeration rolesEnum = groupsNode.enumerateBindings();
                while (rolesEnum.hasMoreElements()) {
                    ObjectNode.Binding b = (ObjectNode.Binding)rolesEnum.nextElement();
                    String group = b.getName();
                    Uri roleUri = namespace.getUri(token, namespaceConfig.getGroupsPath()+"/"+group);
                    try {
                        NodeRevisionDescriptor nrd =
                            roleUri.getStore().retrieveRevisionDescriptor(roleUri, new NodeRevisionNumber());
                        NodeProperty membersetProp = nrd.getProperty(GROUP_MEMBER_SET);
                        if (membersetProp != null && membersetProp.getValue() != null) {
                            XMLValue xmlVal = new XMLValue((String)membersetProp.getValue());
                            List memberNodes = xmlVal.getHrefNodes();
                            if (memberNodes.contains(subjectNode)) {
                                result.add(roleUri.toString());
                            }
                        }
                    } catch (RevisionDescriptorNotFoundException e) {
                    } catch (JDOMException e) {}
                }
            }
        }
        return result.elements();
    }
    
    // overwrites super
    public Enumeration getRoles(ObjectNode object) {
        return EMPTY_VECTOR.elements();
    }
    
    /**
     * Evaluates speified ACL by first-match principle
     */
    private boolean evaluateAcl(SlideToken token, ObjectNode objectNode, ActionNode actionNode, Enumeration permissions) throws ServiceAccessException, ObjectNotFoundException {
        boolean result = false;
        SubjectNode subjectNode = (SubjectNode)getPrincipal(token);
        while (permissions.hasMoreElements()) {
            NodePermission permission = (NodePermission)permissions.nextElement();
            if (match(token, objectNode, subjectNode, actionNode, permission)) {
                result = !permission.isNegative();
                break;
            }
        }
        return result;
    }
    
    private boolean match(SlideToken token, ObjectNode objectNode, SubjectNode subjectNode, ActionNode actionNode, NodePermission permission) throws ServiceAccessException {
        boolean result = true;
        result = matchAction(token, actionNode, permission.getActionNode()) && matchSubject(token, objectNode, subjectNode, permission.getSubjectNode());
        // logging
        if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
            logger.log("    permission="+permission+", match="+result, LOG_CHANNEL, Logger.DEBUG);
        }
        return result;
    }
    
    private boolean matchSubject(SlideToken token, ObjectNode objectNode, SubjectNode checkSubject, SubjectNode permSubject) throws ServiceAccessException {
        if (permSubject.equals(SubjectNode.ALL)) {
            return true;
        }
        else if (permSubject.equals(SubjectNode.AUTHENTICATED)) {
            return !checkSubject.equals(SubjectNode.UNAUTHENTICATED);
        }
        else if (permSubject.equals(SubjectNode.UNAUTHENTICATED)) {
            return checkSubject.equals(SubjectNode.UNAUTHENTICATED);
        }
        else if (permSubject.equals(SubjectNode.OWNER)) {
            return matchOwner(token, objectNode, checkSubject);
        }
        else if (permSubject.equals(SubjectNode.SELF)) {
            return matchPrincipal(token, checkSubject, (SubjectNode)objectNode);
        }
        else {
            return matchPrincipal(token, checkSubject, permSubject);
        }
    }
    
    private boolean matchOwner(SlideToken token, ObjectNode objectNode, SubjectNode checkSubject) throws ServiceAccessException {
        Uri objectUri = namespace.getUri(token, objectNode.getUri());
        try {
            NodeRevisionDescriptor nrd =
                objectUri.getStore().retrieveRevisionDescriptor(objectUri, new NodeRevisionNumber());
            NodeProperty ownerProp = nrd.getProperty(PropertyName.OWNER);
            if (ownerProp != null && ownerProp.getValue() != null) {
                String usersPath = namespace.getConfig().getUsersPath();
                SubjectNode ownerSubject = SubjectNode.getSubjectNode(usersPath+"/"+ownerProp.getValue());
                return ownerSubject.equals(checkSubject);
            }
            else {
                return false;
            }
        }
        catch (RevisionDescriptorNotFoundException e) {
            return false;
        }
        catch (ServiceAccessException e) {
            throw e;
        }
    }
}




