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