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