001/* 002 * (C) Copyright 2006-2018 Nuxeo (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 */ 020 021package org.nuxeo.ecm.core.security; 022 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.Map.Entry; 029import java.util.Set; 030import java.util.TreeSet; 031 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.ecm.core.api.security.UserVisiblePermission; 037 038/** 039 * @author Bogdan Stefanescu 040 * @author Olivier Grisel 041 */ 042public class DefaultPermissionProvider implements PermissionProviderLocal { 043 044 @SuppressWarnings("unused") 045 private static final Log log = LogFactory.getLog(DefaultPermissionProvider.class); 046 047 private final List<PermissionDescriptor> registeredPermissions = new LinkedList<>(); 048 049 // to be recomputed each time a new PermissionDescriptor is registered - 050 // null means invalidated 051 private Map<String, MergedPermissionDescriptor> mergedPermissions; 052 053 private Map<String, Set<String>> mergedGroups; 054 055 private final List<PermissionVisibilityDescriptor> registeredPermissionsVisibility = new LinkedList<>(); 056 057 private Map<String, PermissionVisibilityDescriptor> mergedPermissionsVisibility; 058 059 public DefaultPermissionProvider() { 060 mergedPermissionsVisibility = null; 061 } 062 063 @Override 064 public synchronized List<UserVisiblePermission> getUserVisiblePermissionDescriptors(String typeName) { 065 if (mergedPermissionsVisibility == null) { 066 computeMergedPermissionsVisibility(); 067 } 068 // grab the default items (type is "") 069 PermissionVisibilityDescriptor defaultVisibility = mergedPermissionsVisibility.get(typeName); 070 if (defaultVisibility == null) { 071 // fallback to default 072 defaultVisibility = mergedPermissionsVisibility.get(""); 073 } 074 if (defaultVisibility == null) { 075 throw new NuxeoException("no permission visibility configuration registered"); 076 } 077 return defaultVisibility.getSortedUIPermissionDescriptor(); 078 } 079 080 @Override 081 public List<UserVisiblePermission> getUserVisiblePermissionDescriptors() { 082 return getUserVisiblePermissionDescriptors(""); 083 } 084 085 // called synchronized 086 protected void computeMergedPermissionsVisibility() { 087 mergedPermissionsVisibility = new HashMap<>(); 088 for (PermissionVisibilityDescriptor pvd : registeredPermissionsVisibility) { 089 PermissionVisibilityDescriptor mergedPvd = mergedPermissionsVisibility.get(pvd.getTypeName()); 090 if (mergedPvd == null) { 091 mergedPvd = new PermissionVisibilityDescriptor(pvd); 092 if (!StringUtils.isEmpty(pvd.getTypeName())) { 093 PermissionVisibilityDescriptor defaultPerms = new PermissionVisibilityDescriptor( 094 mergedPermissionsVisibility.get("")); 095 defaultPerms.merge(mergedPvd); 096 mergedPvd.setPermissionUIItems( 097 defaultPerms.getPermissionUIItems().toArray(new PermissionUIItemDescriptor[] {})); 098 } 099 mergedPermissionsVisibility.put(mergedPvd.getTypeName(), mergedPvd); 100 } else { 101 mergedPvd.merge(pvd); 102 } 103 } 104 } 105 106 @Override 107 public synchronized String[] getSubPermissions(String perm) { 108 List<String> permissions = getPermission(perm).getSubPermissions(); 109 return permissions.toArray(new String[permissions.size()]); 110 } 111 112 @Override 113 public synchronized String[] getAliasPermissions(String perm) { 114 List<String> permissions = getPermission(perm).getSubPermissions(); 115 return permissions.toArray(new String[permissions.size()]); 116 } 117 118 // called synchronized 119 protected MergedPermissionDescriptor getPermission(String perm) { 120 if (mergedPermissions == null) { 121 computeMergedPermissions(); 122 } 123 MergedPermissionDescriptor mpd = mergedPermissions.get(perm); 124 if (mpd == null) { 125 throw new NuxeoException(perm + " is not a registered permission"); 126 } 127 return mpd; 128 } 129 130 // OG: this is an awkward method prototype left unchanged for BBB 131 @Override 132 public synchronized String[] getPermissionGroups(String perm) { 133 if (mergedGroups == null) { 134 computeMergedGroups(); 135 } 136 Set<String> groups = mergedGroups.get(perm); 137 if (groups != null && !groups.isEmpty()) { 138 // OG: why return null instead of an empty array 139 return groups.toArray(new String[groups.size()]); 140 } 141 return null; 142 } 143 144 // called synchronized 145 protected void computeMergedGroups() { 146 if (mergedPermissions == null) { 147 computeMergedPermissions(); 148 } 149 mergedGroups = new HashMap<>(); 150 151 // scanning sub permissions to collect direct group membership 152 for (MergedPermissionDescriptor mpd : mergedPermissions.values()) { 153 for (String subPermission : mpd.getSubPermissions()) { 154 Set<String> groups = mergedGroups.get(subPermission); 155 if (groups == null) { 156 groups = new TreeSet<>(); 157 groups.add(mpd.getName()); 158 mergedGroups.put(subPermission, groups); 159 } else { 160 if (!groups.contains(mpd.getName())) { 161 groups.add(mpd.getName()); 162 } 163 } 164 } 165 } 166 167 // building the transitive closure on groups membership with a recursive 168 // method 169 Set<String> alreadyProcessed = new HashSet<>(); 170 for (Entry<String, Set<String>> groupEntry : mergedGroups.entrySet()) { 171 String permissionName = groupEntry.getKey(); 172 Set<String> groups = groupEntry.getValue(); 173 Set<String> allGroups = computeAllGroups(permissionName, alreadyProcessed); 174 groups.addAll(allGroups); 175 } 176 } 177 178 // called synchronized 179 protected Set<String> computeAllGroups(String permissionName, Set<String> alreadyProcessed) { 180 Set<String> allGroups = mergedGroups.get(permissionName); 181 if (allGroups == null) { 182 allGroups = new TreeSet<>(); 183 } 184 if (alreadyProcessed.contains(permissionName)) { 185 return allGroups; 186 } else { 187 // marking it processed early to avoid infinite loops in case of 188 // recursive inclusion 189 alreadyProcessed.add(permissionName); 190 for (String directGroupName : new TreeSet<>(allGroups)) { 191 allGroups.addAll(computeAllGroups(directGroupName, alreadyProcessed)); 192 } 193 return allGroups; 194 } 195 } 196 197 // OG: this is an awkward method prototype left unchanged for BBB 198 @Override 199 public synchronized String[] getPermissions() { 200 if (mergedPermissions == null) { 201 computeMergedPermissions(); 202 } 203 // TODO OG: should we add aliased permissions here as well? 204 return mergedPermissions.keySet().toArray(new String[mergedPermissions.size()]); 205 } 206 207 // called synchronized 208 protected void computeMergedPermissions() { 209 mergedPermissions = new HashMap<>(); 210 for (PermissionDescriptor pd : registeredPermissions) { 211 MergedPermissionDescriptor mpd = mergedPermissions.get(pd.getName()); 212 if (mpd == null) { 213 mpd = new MergedPermissionDescriptor(pd); 214 mergedPermissions.put(mpd.getName(), mpd); 215 } else { 216 mpd.mergeDescriptor(pd); 217 } 218 } 219 } 220 221 @Override 222 public synchronized void registerDescriptor(PermissionDescriptor descriptor) { 223 // check that all included permission have previously been registered 224 Set<String> alreadyRegistered = new HashSet<>(); 225 for (PermissionDescriptor registeredPerm : registeredPermissions) { 226 alreadyRegistered.add(registeredPerm.getName()); 227 } 228 for (String includePerm : descriptor.getIncludePermissions()) { 229 if (!alreadyRegistered.contains(includePerm)) { 230 throw new NuxeoException( 231 String.format("Permission '%s' included by '%s' is not a registered permission", includePerm, 232 descriptor.getName())); 233 } 234 } 235 // invalidate merged permission 236 mergedPermissions = null; 237 mergedGroups = null; 238 // append the new descriptor 239 registeredPermissions.add(descriptor); 240 } 241 242 @Override 243 public synchronized void unregisterDescriptor(PermissionDescriptor descriptor) { 244 int lastOccurence = registeredPermissions.lastIndexOf(descriptor); 245 if (lastOccurence != -1) { 246 // invalidate merged permission 247 mergedPermissions = null; 248 mergedGroups = null; 249 // remove the last occurrence of the descriptor 250 registeredPermissions.remove(lastOccurence); 251 } 252 } 253 254 @Override 255 public synchronized void registerDescriptor(PermissionVisibilityDescriptor descriptor) { 256 // invalidate cached merged descriptors 257 mergedPermissionsVisibility = null; 258 registeredPermissionsVisibility.add(descriptor); 259 } 260 261 @Override 262 public synchronized void unregisterDescriptor(PermissionVisibilityDescriptor descriptor) { 263 int lastOccurence = registeredPermissionsVisibility.lastIndexOf(descriptor); 264 if (lastOccurence != -1) { 265 // invalidate merged descriptors 266 mergedPermissionsVisibility = null; 267 // remove the last occurrence of the descriptor 268 registeredPermissionsVisibility.remove(lastOccurence); 269 } 270 } 271 272}