/* * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Anahide Tchertchian * */ package org.nuxeo.ecm.directory; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.DataModel; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.api.PropertyException; import org.nuxeo.ecm.core.api.impl.DataModelImpl; import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; import org.nuxeo.ecm.core.api.local.ClientLoginModule; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.directory.api.DirectoryDeleteConstraint; import org.nuxeo.ecm.directory.api.DirectoryService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.api.login.LoginComponent; /** * Base session class with helper methods common to all kinds of directory sessions. * * @author Anahide Tchertchian * @since 5.2M4 */ public abstract class BaseSession implements Session { protected static final String POWER_USERS_GROUP = "powerusers"; protected static final String READONLY_ENTRY_FLAG = "READONLY_ENTRY"; protected static final String MULTI_TENANT_ID_FORMAT = "tenant_%s_%s"; private final static Log log = LogFactory.getLog(BaseSession.class); protected final Directory directory; protected PermissionDescriptor[] permissions = null; // needed for test framework to be able to do a full backup of a directory including password protected boolean readAllColumns; protected BaseSession(Directory directory) { this.directory = directory; } /** To be implemented with a more specific return type. */ public abstract Directory getDirectory(); @Override public void setReadAllColumns(boolean readAllColumns) { this.readAllColumns = readAllColumns; } @Override public String getIdField() { return directory.getIdField(); } @Override public String getPasswordField() { return directory.getPasswordField(); } @Override public boolean isAuthenticating() { return directory.getPasswordField() != null; } @Override public boolean isReadOnly() { return directory.isReadOnly(); } /** * Checks the current user rights for the given permission against the read-only flag and the permission descriptor. * <p> * Throws {@link DirectorySecurityException} if the user does not have adequate privileges. * * @throws DirectorySecurityException if access is denied * @since 8.3 */ public void checkPermission(String permission) { if (hasPermission(permission)) { return; } if (permission.equals(SecurityConstants.WRITE) && isReadOnly()) { throw new DirectorySecurityException("Directory is read-only"); } else { NuxeoPrincipal user = ClientLoginModule.getCurrentPrincipal(); throw new DirectorySecurityException("User " + user + " does not have " + permission + " permission"); } } /** * Checks that there are no constraints for deleting the given entry id. * * @since 8.4 */ public void checkDeleteConstraints(String entryId) { List<DirectoryDeleteConstraint> deleteConstraints = directory.getDirectoryDeleteConstraints(); DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); if (deleteConstraints != null && !deleteConstraints.isEmpty()) { for (DirectoryDeleteConstraint deleteConstraint : deleteConstraints) { if (!deleteConstraint.canDelete(directoryService, entryId)) { throw new DirectoryDeleteConstraintException("This entry is referenced in another vocabulary."); } } } } /** * Checks the current user rights for the given permission against the read-only flag and the permission descriptor. * <p> * Returns {@code false} if the user does not have adequate privileges. * * @return {@code false} if access is denied * @since 8.3 */ public boolean hasPermission(String permission) { if (permission.equals(SecurityConstants.WRITE) && isReadOnly()) { if (log.isTraceEnabled()) { log.trace("Directory is read-only"); } return false; } NuxeoPrincipal user = ClientLoginModule.getCurrentPrincipal(); if (user == null) { return true; } String username = user.getName(); if (username.equals(LoginComponent.SYSTEM_USERNAME)) { return true; } if (permissions == null || permissions.length == 0) { if (user.isAdministrator()) { return true; } if (user.isMemberOf(POWER_USERS_GROUP)) { return true; } // Return true for read access to anyone when nothing defined if (permission.equals(SecurityConstants.READ)) { return true; } // Deny in all other cases if (log.isTraceEnabled()) { log.trace("User " + user + " does not have " + permission + " permission"); } return false; } List<String> groups = new ArrayList<>(user.getAllGroups()); groups.add(SecurityConstants.EVERYONE); boolean allowed = hasPermission(permission, username, groups); if (!allowed) { // if the permission Read is not explicitly granted, check Write which includes it if (permission.equals(SecurityConstants.READ)) { allowed = hasPermission(SecurityConstants.WRITE, username, groups); } } if (!allowed && log.isTraceEnabled()) { log.trace("User " + user + " does not have " + permission + " permission"); } return allowed; } protected boolean hasPermission(String permission, String username, List<String> groups) { for (PermissionDescriptor desc : permissions) { if (!desc.name.equals(permission)) { continue; } if (desc.groups != null) { for (String group : desc.groups) { if (groups.contains(group)) { return true; } } } if (desc.users != null) { for (String user : desc.users) { if (user.equals(username)) { return true; } } } } return false; } /** * Returns a bare document model suitable for directory implementations. * <p> * Can be used for creation screen. * * @since 5.2M4 */ public static DocumentModel createEntryModel(String sessionId, String schema, String id, Map<String, Object> values) throws PropertyException { DocumentModelImpl entry = new DocumentModelImpl(sessionId, schema, id, null, null, null, null, new String[] { schema }, new HashSet<String>(), null, null); DataModel dataModel; if (values == null) { values = Collections.emptyMap(); } dataModel = new DataModelImpl(schema, values); entry.addDataModel(dataModel); return entry; } /** * Returns a bare document model suitable for directory implementations. * <p> * Allow setting the readonly entry flag to {@code Boolean.TRUE}. See {@code Session#isReadOnlyEntry(DocumentModel)} * * @since 5.3.1 */ public static DocumentModel createEntryModel(String sessionId, String schema, String id, Map<String, Object> values, boolean readOnly) throws PropertyException { DocumentModel entry = createEntryModel(sessionId, schema, id, values); if (readOnly) { setReadOnlyEntry(entry); } return entry; } protected static Map<String, Serializable> mkSerializableMap(Map<String, Object> map) { Map<String, Serializable> serializableMap = null; if (map != null) { serializableMap = new HashMap<String, Serializable>(); for (String key : map.keySet()) { serializableMap.put(key, (Serializable) map.get(key)); } } return serializableMap; } protected static Map<String, Object> mkObjectMap(Map<String, Serializable> map) { Map<String, Object> objectMap = null; if (map != null) { objectMap = new HashMap<String, Object>(); for (String key : map.keySet()) { objectMap.put(key, map.get(key)); } } return objectMap; } /** * Test whether entry comes from a read-only back-end directory. * * @since 5.3.1 */ public static boolean isReadOnlyEntry(DocumentModel entry) { return entry.getContextData(READONLY_ENTRY_FLAG) == Boolean.TRUE; } /** * Set the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. * * @since 5.3.2 */ public static void setReadOnlyEntry(DocumentModel entry) { entry.putContextData(READONLY_ENTRY_FLAG, Boolean.TRUE); } /** * Unset the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance. * * @since 5.3.2 */ public static void setReadWriteEntry(DocumentModel entry) { entry.putContextData(READONLY_ENTRY_FLAG, Boolean.FALSE); } /** * Compute a multi tenant directory id based on the given {@code tenantId}. * * @return the computed directory id * @since 5.6 */ public static String computeMultiTenantDirectoryId(String tenantId, String id) { return String.format(MULTI_TENANT_ID_FORMAT, tenantId, id); } @Override public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, boolean fetchReferences, int limit, int offset) throws DirectoryException { log.info("Call an unoverrided query with offset and limit."); DocumentModelList entries = query(filter, fulltext, orderBy, fetchReferences); int toIndex = offset + limit; if (toIndex > entries.size()) { toIndex = entries.size(); } return new DocumentModelListImpl(entries.subList(offset, toIndex)); } }