001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
011 *
012 * $Id$
013 */
014
015package org.nuxeo.ecm.core.security;
016
017import java.security.Principal;
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Hashtable;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.ecm.core.api.security.ACP;
029import org.nuxeo.ecm.core.api.security.Access;
030import org.nuxeo.ecm.core.model.Document;
031import org.nuxeo.ecm.core.query.sql.model.SQLQuery;
032
033/**
034 * Security policy service implementation.
035 * <p>
036 * Iterates over ordered policies. First policy to give a known access (grant or deny) applies.
037 *
038 * @author Anahide Tchertchian
039 */
040public class SecurityPolicyServiceImpl implements SecurityPolicyService {
041
042    private static final long serialVersionUID = 482814921906794786L;
043
044    private static final Log log = LogFactory.getLog(SecurityPolicyServiceImpl.class);
045
046    private final Map<String, SecurityPolicyDescriptor> policyDescriptors;
047
048    private List<SecurityPolicy> policies;
049
050    public SecurityPolicyServiceImpl() {
051        policyDescriptors = new Hashtable<String, SecurityPolicyDescriptor>();
052    }
053
054    private void computePolicies() {
055        policies = new ArrayList<SecurityPolicy>();
056        List<SecurityPolicyDescriptor> orderedDescriptors = new ArrayList<SecurityPolicyDescriptor>();
057        for (SecurityPolicyDescriptor descriptor : policyDescriptors.values()) {
058            if (descriptor.isEnabled()) {
059                orderedDescriptors.add(descriptor);
060            }
061        }
062        Collections.sort(orderedDescriptors);
063        List<String> policyNames = new ArrayList<String>();
064        for (SecurityPolicyDescriptor descriptor : orderedDescriptors) {
065            if (descriptor.isEnabled()) {
066                try {
067                    Object policy = descriptor.getPolicy().newInstance();
068                    if (policy instanceof SecurityPolicy) {
069                        policies.add((SecurityPolicy) policy);
070                        policyNames.add(descriptor.getName());
071                    } else {
072                        log.error(String.format("Invalid contribution to security policy service %s:"
073                                + " must implement SecurityPolicy interface", descriptor.getName()));
074                    }
075                } catch (ReflectiveOperationException e) {
076                    log.error(e, e);
077                }
078            }
079        }
080        log.debug("Ordered security policies: " + policyNames.toString());
081    }
082
083    @Override
084    public synchronized List<SecurityPolicy> getPolicies() {
085        if (policies == null) {
086            computePolicies();
087        }
088        return policies;
089    }
090
091    private void resetPolicies() {
092        policies = null;
093    }
094
095    @Override
096    public boolean arePoliciesRestrictingPermission(String permission) {
097        for (SecurityPolicy policy : getPolicies()) {
098            if (policy.isRestrictingPermission(permission)) {
099                return true;
100            }
101        }
102        return false;
103    }
104
105    @Override
106    public boolean arePoliciesExpressibleInQuery(String repositoryName) {
107        for (SecurityPolicy policy : getPolicies()) {
108            if (!policy.isExpressibleInQuery(repositoryName)) {
109                return false;
110            }
111        }
112        return true;
113    }
114
115    @Override
116    public Collection<SQLQuery.Transformer> getPoliciesQueryTransformers(String repositoryName) {
117        List<SQLQuery.Transformer> transformers = new LinkedList<SQLQuery.Transformer>();
118        for (SecurityPolicy policy : getPolicies()) {
119            if (policy.isExpressibleInQuery(repositoryName)) {
120                transformers.add(policy.getQueryTransformer(repositoryName));
121            } else {
122                log.warn(String.format("Security policy '%s' for repository '%s'"
123                        + " cannot be expressed in SQL query.", policy.getClass().getName(), repositoryName));
124            }
125        }
126        return transformers;
127    }
128
129    @Override
130    public void registerDescriptor(SecurityPolicyDescriptor descriptor) {
131        String id = descriptor.getName();
132        if (policyDescriptors.containsKey(id)) {
133            log.info("Overriding security policy " + id);
134        }
135        policyDescriptors.put(id, descriptor);
136        resetPolicies();
137    }
138
139    @Override
140    public void unregisterDescriptor(SecurityPolicyDescriptor descriptor) {
141        String id = descriptor.getName();
142        if (policyDescriptors.containsKey(id)) {
143            policyDescriptors.remove(id);
144            resetPolicies();
145        }
146    }
147
148    @Override
149    public Access checkPermission(Document doc, ACP mergedAcp, Principal principal, String permission,
150            String[] resolvedPermissions, String[] additionalPrincipals) {
151        Access access = Access.UNKNOWN;
152        List<SecurityPolicy> policies = getPolicies();
153        for (SecurityPolicy policy : policies) {
154            Access policyAccess = policy.checkPermission(doc, mergedAcp, principal, permission, resolvedPermissions,
155                    additionalPrincipals);
156            if (policyAccess != null && !Access.UNKNOWN.equals(policyAccess)) {
157                access = policyAccess;
158                break;
159            }
160        }
161        return access;
162    }
163
164}