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.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, Principal principal, String permission) {
124        String username = principal.getName();
125
126        // system bypass
127        // :FIXME: temporary workaround
128        if (SecurityConstants.SYSTEM_USERNAME.equals(username)) {
129            return true;
130        }
131
132        if (principal instanceof NuxeoPrincipal && ((NuxeoPrincipal) principal).isAdministrator()) {
133            return true;
134        }
135
136        // fully check each ACE in turn
137        String[] resolvedPermissions = getPermissionsToCheck(permission);
138        String[] additionalPrincipals = getPrincipalsToCheck(principal);
139
140        // get the ordered list of ACE
141        ACP acp = doc.getSession().getMergedACP(doc);
142
143        // check pluggable policies
144        Access access = securityPolicyService.checkPermission(doc, acp, principal, permission, resolvedPermissions,
145                additionalPrincipals);
146        if (access != null && !Access.UNKNOWN.equals(access)) {
147            return access.toBoolean();
148        }
149
150        if (acp == null) {
151            return false; // no ACP on that doc - by default deny
152        }
153        access = acp.getAccess(additionalPrincipals, resolvedPermissions);
154
155        return access.toBoolean();
156    }
157
158    /**
159     * Provides the full list of all permissions or groups of permissions that contain the given one (inclusive).
160     * <p>
161     * It is exposed remotely through {@link CoreSession#getPermissionsToCheck}.
162     *
163     * @param permission
164     * @return the list, as an array of strings.
165     */
166    public String[] getPermissionsToCheck(String permission) {
167        String[] groups = permissionProvider.getPermissionGroups(permission);
168        if (groups == null) {
169            return new String[] { permission, SecurityConstants.EVERYTHING };
170        } else {
171            String[] perms = new String[groups.length + 2];
172            perms[0] = permission;
173            System.arraycopy(groups, 0, perms, 1, groups.length);
174            perms[groups.length + 1] = SecurityConstants.EVERYTHING;
175            return perms;
176        }
177    }
178
179    public static String[] getPrincipalsToCheck(Principal principal) {
180        List<String> userGroups = null;
181        if (principal instanceof NuxeoPrincipal) {
182            userGroups = ((NuxeoPrincipal) principal).getAllGroups();
183        }
184        if (userGroups == null) {
185            return new String[] { principal.getName(), SecurityConstants.EVERYONE };
186        } else {
187            int size = userGroups.size();
188            String[] groups = new String[size + 2];
189            userGroups.toArray(groups);
190            groups[size] = principal.getName();
191            groups[size + 1] = SecurityConstants.EVERYONE;
192            return groups;
193        }
194    }
195
196    @SuppressWarnings("unchecked")
197    @Override
198    public <T> T getAdapter(Class<T> adapter) {
199        if (adapter.isAssignableFrom(PermissionProvider.class)) {
200            return (T) permissionProvider;
201        } else if (adapter.isAssignableFrom(SecurityPolicyService.class)) {
202            return (T) securityPolicyService;
203        } else {
204            return adapter.cast(this);
205        }
206    }
207
208}