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 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.core.security;
023
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.List;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.ecm.core.api.CoreSession;
031import org.nuxeo.ecm.core.api.NuxeoPrincipal;
032import org.nuxeo.ecm.core.api.security.ACP;
033import org.nuxeo.ecm.core.api.security.Access;
034import org.nuxeo.ecm.core.api.security.PermissionProvider;
035import org.nuxeo.ecm.core.api.security.SecurityConstants;
036import org.nuxeo.ecm.core.model.Document;
037import org.nuxeo.ecm.core.query.sql.model.SQLQuery;
038import org.nuxeo.runtime.model.ComponentContext;
039import org.nuxeo.runtime.model.ComponentInstance;
040import org.nuxeo.runtime.model.ComponentName;
041import org.nuxeo.runtime.model.DefaultComponent;
042
043/**
044 * @author Bogdan Stefanescu
045 * @author Olivier Grisel
046 * @author Anahide Tchertchian
047 */
048// TODO: improve caching invalidation
049// TODO: remove "implements SecurityConstants" and check that it doesn't break
050// anything
051public class SecurityService extends DefaultComponent {
052
053    public static final ComponentName NAME = new ComponentName("org.nuxeo.ecm.core.security.SecurityService");
054
055    public static final String PERMISSIONS_EXTENSION_POINT = "permissions";
056
057    private static final String PERMISSIONS_VISIBILITY_EXTENSION_POINT = "permissionsVisibility";
058
059    private static final String POLICIES_EXTENSION_POINT = "policies";
060
061    private static final Log log = LogFactory.getLog(SecurityService.class);
062
063    private PermissionProviderLocal permissionProvider;
064
065    private SecurityPolicyService securityPolicyService;
066
067    // private SecurityManager securityManager;
068
069    @Override
070    public void activate(ComponentContext context) {
071        super.activate(context);
072        permissionProvider = new DefaultPermissionProvider();
073        securityPolicyService = new SecurityPolicyServiceImpl();
074    }
075
076    @Override
077    public void deactivate(ComponentContext context) {
078        super.deactivate(context);
079        permissionProvider = null;
080        securityPolicyService = null;
081    }
082
083    @Override
084    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
085        if (PERMISSIONS_EXTENSION_POINT.equals(extensionPoint) && contribution instanceof PermissionDescriptor) {
086            permissionProvider.registerDescriptor((PermissionDescriptor) contribution);
087        } else if (PERMISSIONS_VISIBILITY_EXTENSION_POINT.equals(extensionPoint)
088                && contribution instanceof PermissionVisibilityDescriptor) {
089            permissionProvider.registerDescriptor((PermissionVisibilityDescriptor) contribution);
090        } else if (POLICIES_EXTENSION_POINT.equals(extensionPoint) && contribution instanceof SecurityPolicyDescriptor) {
091            securityPolicyService.registerDescriptor((SecurityPolicyDescriptor) contribution);
092        }
093    }
094
095    @Override
096    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
097        if (PERMISSIONS_EXTENSION_POINT.equals(extensionPoint) && contribution instanceof PermissionDescriptor) {
098            permissionProvider.unregisterDescriptor((PermissionDescriptor) contribution);
099        } else if (PERMISSIONS_VISIBILITY_EXTENSION_POINT.equals(extensionPoint)
100                && contribution instanceof PermissionVisibilityDescriptor) {
101            permissionProvider.unregisterDescriptor((PermissionVisibilityDescriptor) contribution);
102        } else if (POLICIES_EXTENSION_POINT.equals(extensionPoint) && contribution instanceof SecurityPolicyDescriptor) {
103            securityPolicyService.unregisterDescriptor((SecurityPolicyDescriptor) contribution);
104        }
105    }
106
107    public PermissionProvider getPermissionProvider() {
108        return permissionProvider;
109    }
110
111    public boolean arePoliciesRestrictingPermission(String permission) {
112        return securityPolicyService.arePoliciesRestrictingPermission(permission);
113    }
114
115    public boolean arePoliciesExpressibleInQuery(String repositoryName) {
116        return securityPolicyService.arePoliciesExpressibleInQuery(repositoryName);
117    }
118
119    public Collection<SQLQuery.Transformer> getPoliciesQueryTransformers(String repositoryName) {
120        return securityPolicyService.getPoliciesQueryTransformers(repositoryName);
121    }
122
123    public boolean checkPermission(Document doc, NuxeoPrincipal principal, String permission) {
124        if (principal.isAdministrator()) {
125            return true;
126        }
127        // fully check each ACE in turn
128        String[] resolvedPermissions = getPermissionsToCheck(permission);
129        String[] additionalPrincipals = getPrincipalsToCheck(principal);
130
131        // get the ordered list of ACE
132        ACP acp = doc.getSession().getMergedACP(doc);
133
134        // check pluggable policies
135        Access access = securityPolicyService.checkPermission(doc, acp, principal, permission, resolvedPermissions,
136                additionalPrincipals);
137        if (access != null && !Access.UNKNOWN.equals(access)) {
138            return access.toBoolean();
139        }
140
141        if (acp == null) {
142            return false; // no ACP on that doc - by default deny
143        }
144        access = acp.getAccess(additionalPrincipals, resolvedPermissions);
145
146        return access.toBoolean();
147    }
148
149    /**
150     * Filters the supplied permissions based on whether they are granted to a given principal for a given document.
151     *
152     * @since 9.1
153     */
154    public Collection<String> filterGrantedPermissions(Document doc, NuxeoPrincipal principal,
155            Collection<String> permissions) {
156        if (principal.isAdministrator()) {
157            return permissions;
158        }
159
160        String[] additionalPrincipals = getPrincipalsToCheck(principal);
161        ACP acp = doc.getSession().getMergedACP(doc);
162
163        List<String> result = new ArrayList<>();
164        for(String permission : permissions) {
165            String[] resolvedPermissions = getPermissionsToCheck(permission);
166            Access access = securityPolicyService.checkPermission(doc, acp, principal, permission, resolvedPermissions,
167                additionalPrincipals);
168            if (access == null || Access.UNKNOWN.equals(access)) {
169                access = acp == null ? null : acp.getAccess(additionalPrincipals, resolvedPermissions);
170            }
171            if (access != null && access.toBoolean()) {
172                result.add(permission);
173            }
174        }
175        return result;
176    }
177
178    /**
179     * Provides the full list of all permissions or groups of permissions that contain the given one (inclusive).
180     * <p>
181     * It is exposed remotely through {@link CoreSession#getPermissionsToCheck}.
182     *
183     * @return the list, as an array of strings.
184     */
185    public String[] getPermissionsToCheck(String permission) {
186        String[] groups = permissionProvider.getPermissionGroups(permission);
187        if (groups == null) {
188            return new String[] { permission, SecurityConstants.EVERYTHING };
189        } else {
190            String[] perms = new String[groups.length + 2];
191            perms[0] = permission;
192            System.arraycopy(groups, 0, perms, 1, groups.length);
193            perms[groups.length + 1] = SecurityConstants.EVERYTHING;
194            return perms;
195        }
196    }
197
198    public static String[] getPrincipalsToCheck(NuxeoPrincipal principal) {
199        List<String> userGroups = principal.getAllGroups();
200        if (userGroups == null) {
201            return new String[] { principal.getName(), SecurityConstants.EVERYONE };
202        } else {
203            int size = userGroups.size();
204            String[] groups = new String[size + 2];
205            userGroups.toArray(groups);
206            groups[size] = principal.getName();
207            groups[size + 1] = SecurityConstants.EVERYONE;
208            return groups;
209        }
210    }
211
212    @SuppressWarnings("unchecked")
213    @Override
214    public <T> T getAdapter(Class<T> adapter) {
215        if (adapter.isAssignableFrom(PermissionProvider.class)) {
216            return (T) permissionProvider;
217        } else if (adapter.isAssignableFrom(SecurityPolicyService.class)) {
218            return (T) securityPolicyService;
219        } else {
220            return adapter.cast(this);
221        }
222    }
223
224}