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