001/* 002 * (C) Copyright 2006-2014 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 * Anahide Tchertchian 018 * 019 */ 020 021package org.nuxeo.ecm.directory; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.collections.ScopeType; 035import org.nuxeo.common.collections.ScopedMap; 036import org.nuxeo.ecm.core.api.DataModel; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.DocumentModelList; 039import org.nuxeo.ecm.core.api.NuxeoPrincipal; 040import org.nuxeo.ecm.core.api.PropertyException; 041import org.nuxeo.ecm.core.api.impl.DataModelImpl; 042import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 043import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 044import org.nuxeo.ecm.core.api.local.ClientLoginModule; 045import org.nuxeo.ecm.core.api.security.SecurityConstants; 046import org.nuxeo.runtime.api.login.LoginComponent; 047 048/** 049 * Base session class with helper methods common to all kinds of directory sessions. 050 * 051 * @author Anahide Tchertchian 052 * @since 5.2M4 053 */ 054public abstract class BaseSession implements Session { 055 056 protected static final String POWER_USERS_GROUP = "powerusers"; 057 058 protected static final String READONLY_ENTRY_FLAG = "READONLY_ENTRY"; 059 060 protected static final String MULTI_TENANT_ID_FORMAT = "tenant_%s_%s"; 061 062 private final static Log log = LogFactory.getLog(BaseSession.class); 063 064 protected PermissionDescriptor[] permissions = null; 065 066 /** 067 * Check the current user rights for the given permission against the permission descriptor 068 * 069 * @return true if the user 070 * @since 6.0 071 */ 072 public boolean isCurrentUserAllowed(String permissionTocheck) { 073 PermissionDescriptor[] permDescriptors = permissions; 074 NuxeoPrincipal currentUser = ClientLoginModule.getCurrentPrincipal(); 075 076 if (currentUser == null) { 077 if (log.isDebugEnabled()) { 078 log.debug("Can't get current user to check directory permission. EVERYTHING is allowed by default"); 079 } 080 return true; 081 } 082 String username = currentUser.getName(); 083 List<String> userGroups = currentUser.getAllGroups(); 084 085 if (username.equalsIgnoreCase(LoginComponent.SYSTEM_USERNAME)) { 086 return true; 087 } 088 089 if (permDescriptors == null || permDescriptors.length == 0) { 090 if (currentUser.isAdministrator()) { 091 // By default if nothing is specified, admin is allowed 092 return true; 093 } 094 if (currentUser.isMemberOf(POWER_USERS_GROUP)) { 095 return true; 096 } 097 098 // Return true for read access to anyone when nothing defined 099 if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) { 100 return true; 101 } 102 103 if (log.isDebugEnabled()) { 104 log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser, 105 permissionTocheck)); 106 } 107 // Deny in all other case 108 return false; 109 } 110 boolean allowed = checkPermission(permDescriptors, permissionTocheck, username, userGroups); 111 if (allowed != true) { 112 // If the permission has not been found and if the permission to 113 // check is read 114 // Then try to check if the current user is allowed, because having 115 // write access include read 116 if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) { 117 allowed = checkPermission(permDescriptors, SecurityConstants.WRITE, username, userGroups); 118 } 119 } 120 if (log.isDebugEnabled() && !allowed) { 121 log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser, 122 permissionTocheck)); 123 } 124 return allowed; 125 126 } 127 128 private boolean checkPermission(PermissionDescriptor permDescriptors[], String permToChek, String username, 129 List<String> userGroups) { 130 List<String> groups = new ArrayList<String>(userGroups); 131 groups.add(SecurityConstants.EVERYONE); 132 for (int i = 0; i < permDescriptors.length; i++) { 133 PermissionDescriptor currentDesc = permDescriptors[i]; 134 if (currentDesc.name.equalsIgnoreCase(permToChek)) { 135 if (currentDesc.groups != null) { 136 for (int j = 0; j < currentDesc.groups.length; j++) { 137 String groupName = currentDesc.groups[j]; 138 if (groups.contains(groupName)) { 139 return true; 140 } 141 } 142 } 143 144 if (currentDesc.users != null) { 145 for (int j = 0; j < currentDesc.users.length; j++) { 146 String currentUsername = currentDesc.users[j]; 147 if (currentUsername.equals(username)) { 148 return true; 149 } 150 } 151 } 152 } 153 } 154 return false; 155 } 156 157 /** 158 * Returns a bare document model suitable for directory implementations. 159 * <p> 160 * Can be used for creation screen. 161 * 162 * @since 5.2M4 163 */ 164 public static DocumentModel createEntryModel(String sessionId, String schema, String id, Map<String, Object> values) 165 throws PropertyException { 166 DocumentModelImpl entry = new DocumentModelImpl(sessionId, schema, id, null, null, null, null, 167 new String[] { schema }, new HashSet<String>(), null, null); 168 DataModel dataModel; 169 if (values == null) { 170 values = Collections.emptyMap(); 171 } 172 dataModel = new DataModelImpl(schema, values); 173 entry.addDataModel(dataModel); 174 return entry; 175 } 176 177 /** 178 * Returns a bare document model suitable for directory implementations. 179 * <p> 180 * Allow setting the readonly entry flag to {@code Boolean.TRUE}. See {@code Session#isReadOnlyEntry(DocumentModel)} 181 * 182 * @since 5.3.1 183 */ 184 public static DocumentModel createEntryModel(String sessionId, String schema, String id, 185 Map<String, Object> values, boolean readOnly) throws PropertyException { 186 DocumentModel entry = createEntryModel(sessionId, schema, id, values); 187 if (readOnly) { 188 setReadOnlyEntry(entry); 189 } 190 return entry; 191 } 192 193 protected static Map<String, Serializable> mkSerializableMap(Map<String, Object> map) { 194 Map<String, Serializable> serializableMap = null; 195 if (map != null) { 196 serializableMap = new HashMap<String, Serializable>(); 197 for (String key : map.keySet()) { 198 serializableMap.put(key, (Serializable) map.get(key)); 199 } 200 } 201 return serializableMap; 202 } 203 204 protected static Map<String, Object> mkObjectMap(Map<String, Serializable> map) { 205 Map<String, Object> objectMap = null; 206 if (map != null) { 207 objectMap = new HashMap<String, Object>(); 208 for (String key : map.keySet()) { 209 objectMap.put(key, map.get(key)); 210 } 211 } 212 return objectMap; 213 } 214 215 /** 216 * Test whether entry comes from a read-only back-end directory. 217 * 218 * @since 5.3.1 219 */ 220 public static boolean isReadOnlyEntry(DocumentModel entry) { 221 ScopedMap contextData = entry.getContextData(); 222 return contextData.getScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG) == Boolean.TRUE; 223 } 224 225 /** 226 * Set the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. 227 * 228 * @since 5.3.2 229 */ 230 public static void setReadOnlyEntry(DocumentModel entry) { 231 ScopedMap contextData = entry.getContextData(); 232 contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.TRUE); 233 } 234 235 /** 236 * Unset the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. 237 * 238 * @since 5.3.2 239 */ 240 public static void setReadWriteEntry(DocumentModel entry) { 241 ScopedMap contextData = entry.getContextData(); 242 contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.FALSE); 243 } 244 245 /** 246 * Compute a multi tenant directory id based on the given {@code tenantId}. 247 * 248 * @return the computed directory id 249 * @since 5.6 250 */ 251 public static String computeMultiTenantDirectoryId(String tenantId, String id) { 252 return String.format(MULTI_TENANT_ID_FORMAT, tenantId, id); 253 } 254 255 @Override 256 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 257 boolean fetchReferences, int limit, int offset) throws DirectoryException { 258 log.info("Call an unoverrided query with offset and limit."); 259 DocumentModelList entries = query(filter, fulltext, orderBy, fetchReferences); 260 int toIndex = offset + limit; 261 if (toIndex > entries.size()) { 262 toIndex = entries.size(); 263 } 264 265 return new DocumentModelListImpl(entries.subList(offset, toIndex)); 266 } 267 268}