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}