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}