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