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 * @param permission 184 * @return the list, as an array of strings. 185 */ 186 public String[] getPermissionsToCheck(String permission) { 187 String[] groups = permissionProvider.getPermissionGroups(permission); 188 if (groups == null) { 189 return new String[] { permission, SecurityConstants.EVERYTHING }; 190 } else { 191 String[] perms = new String[groups.length + 2]; 192 perms[0] = permission; 193 System.arraycopy(groups, 0, perms, 1, groups.length); 194 perms[groups.length + 1] = SecurityConstants.EVERYTHING; 195 return perms; 196 } 197 } 198 199 public static String[] getPrincipalsToCheck(NuxeoPrincipal principal) { 200 List<String> userGroups = principal.getAllGroups(); 201 if (userGroups == null) { 202 return new String[] { principal.getName(), SecurityConstants.EVERYONE }; 203 } else { 204 int size = userGroups.size(); 205 String[] groups = new String[size + 2]; 206 userGroups.toArray(groups); 207 groups[size] = principal.getName(); 208 groups[size + 1] = SecurityConstants.EVERYONE; 209 return groups; 210 } 211 } 212 213 @SuppressWarnings("unchecked") 214 @Override 215 public <T> T getAdapter(Class<T> adapter) { 216 if (adapter.isAssignableFrom(PermissionProvider.class)) { 217 return (T) permissionProvider; 218 } else if (adapter.isAssignableFrom(SecurityPolicyService.class)) { 219 return (T) securityPolicyService; 220 } else { 221 return adapter.cast(this); 222 } 223 } 224 225}