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 final Directory directory;
065
066    protected PermissionDescriptor[] permissions = null;
067
068    protected BaseSession(Directory directory) {
069        this.directory = directory;
070    }
071
072    /** To be implemented with a more specific return type. */
073    public abstract Directory getDirectory();
074
075    @Override
076    public String getIdField() {
077        return directory.getIdField();
078    }
079
080    @Override
081    public String getPasswordField() {
082        return directory.getPasswordField();
083    }
084
085    @Override
086    public boolean isAuthenticating() {
087        return directory.getPasswordField() != null;
088    }
089
090    @Override
091    public boolean isReadOnly() {
092        return directory.isReadOnly();
093    }
094
095    /**
096     * Check the current user rights for the given permission against the permission descriptor
097     *
098     * @return true if the user
099     * @since 6.0
100     */
101    public boolean isCurrentUserAllowed(String permissionTocheck) {
102        PermissionDescriptor[] permDescriptors = permissions;
103        NuxeoPrincipal currentUser = ClientLoginModule.getCurrentPrincipal();
104
105        if (currentUser == null) {
106            if (log.isDebugEnabled()) {
107                log.debug("Can't get current user to check directory permission. EVERYTHING is allowed by default");
108            }
109            return true;
110        }
111        String username = currentUser.getName();
112        List<String> userGroups = currentUser.getAllGroups();
113
114        if (username.equalsIgnoreCase(LoginComponent.SYSTEM_USERNAME)) {
115            return true;
116        }
117
118        if (permDescriptors == null || permDescriptors.length == 0) {
119            if (currentUser.isAdministrator()) {
120                // By default if nothing is specified, admin is allowed
121                return true;
122            }
123            if (currentUser.isMemberOf(POWER_USERS_GROUP)) {
124                return true;
125            }
126
127            // Return true for read access to anyone when nothing defined
128            if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) {
129                return true;
130            }
131
132            if (log.isDebugEnabled()) {
133                log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser,
134                        permissionTocheck));
135            }
136            // Deny in all other case
137            return false;
138        }
139        boolean allowed = checkPermission(permDescriptors, permissionTocheck, username, userGroups);
140        if (allowed != true) {
141            // If the permission has not been found and if the permission to
142            // check is read
143            // Then try to check if the current user is allowed, because having
144            // write access include read
145            if (permissionTocheck.equalsIgnoreCase(SecurityConstants.READ)) {
146                allowed = checkPermission(permDescriptors, SecurityConstants.WRITE, username, userGroups);
147            }
148        }
149        if (log.isDebugEnabled() && !allowed) {
150            log.debug(String.format("User '%s', is not allowed for permission '%s' on the directory", currentUser,
151                    permissionTocheck));
152        }
153        return allowed;
154
155    }
156
157    private boolean checkPermission(PermissionDescriptor permDescriptors[], String permToChek, String username,
158            List<String> userGroups) {
159        List<String> groups = new ArrayList<String>(userGroups);
160        groups.add(SecurityConstants.EVERYONE);
161        for (int i = 0; i < permDescriptors.length; i++) {
162            PermissionDescriptor currentDesc = permDescriptors[i];
163            if (currentDesc.name.equalsIgnoreCase(permToChek)) {
164                if (currentDesc.groups != null) {
165                    for (int j = 0; j < currentDesc.groups.length; j++) {
166                        String groupName = currentDesc.groups[j];
167                        if (groups.contains(groupName)) {
168                            return true;
169                        }
170                    }
171                }
172
173                if (currentDesc.users != null) {
174                    for (int j = 0; j < currentDesc.users.length; j++) {
175                        String currentUsername = currentDesc.users[j];
176                        if (currentUsername.equals(username)) {
177                            return true;
178                        }
179                    }
180                }
181            }
182        }
183        return false;
184    }
185
186    /**
187     * Returns a bare document model suitable for directory implementations.
188     * <p>
189     * Can be used for creation screen.
190     *
191     * @since 5.2M4
192     */
193    public static DocumentModel createEntryModel(String sessionId, String schema, String id, Map<String, Object> values)
194            throws PropertyException {
195        DocumentModelImpl entry = new DocumentModelImpl(sessionId, schema, id, null, null, null, null,
196                new String[] { schema }, new HashSet<String>(), null, null);
197        DataModel dataModel;
198        if (values == null) {
199            values = Collections.emptyMap();
200        }
201        dataModel = new DataModelImpl(schema, values);
202        entry.addDataModel(dataModel);
203        return entry;
204    }
205
206    /**
207     * Returns a bare document model suitable for directory implementations.
208     * <p>
209     * Allow setting the readonly entry flag to {@code Boolean.TRUE}. See {@code Session#isReadOnlyEntry(DocumentModel)}
210     *
211     * @since 5.3.1
212     */
213    public static DocumentModel createEntryModel(String sessionId, String schema, String id,
214            Map<String, Object> values, boolean readOnly) throws PropertyException {
215        DocumentModel entry = createEntryModel(sessionId, schema, id, values);
216        if (readOnly) {
217            setReadOnlyEntry(entry);
218        }
219        return entry;
220    }
221
222    protected static Map<String, Serializable> mkSerializableMap(Map<String, Object> map) {
223        Map<String, Serializable> serializableMap = null;
224        if (map != null) {
225            serializableMap = new HashMap<String, Serializable>();
226            for (String key : map.keySet()) {
227                serializableMap.put(key, (Serializable) map.get(key));
228            }
229        }
230        return serializableMap;
231    }
232
233    protected static Map<String, Object> mkObjectMap(Map<String, Serializable> map) {
234        Map<String, Object> objectMap = null;
235        if (map != null) {
236            objectMap = new HashMap<String, Object>();
237            for (String key : map.keySet()) {
238                objectMap.put(key, map.get(key));
239            }
240        }
241        return objectMap;
242    }
243
244    /**
245     * Test whether entry comes from a read-only back-end directory.
246     *
247     * @since 5.3.1
248     */
249    public static boolean isReadOnlyEntry(DocumentModel entry) {
250        ScopedMap contextData = entry.getContextData();
251        return contextData.getScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG) == Boolean.TRUE;
252    }
253
254    /**
255     * Set the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance.
256     *
257     * @since 5.3.2
258     */
259    public static void setReadOnlyEntry(DocumentModel entry) {
260        ScopedMap contextData = entry.getContextData();
261        contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.TRUE);
262    }
263
264    /**
265     * Unset the read-only flag of a directory entry. To be used by EntryAdaptor implementations for instance.
266     *
267     * @since 5.3.2
268     */
269    public static void setReadWriteEntry(DocumentModel entry) {
270        ScopedMap contextData = entry.getContextData();
271        contextData.putScopedValue(ScopeType.REQUEST, READONLY_ENTRY_FLAG, Boolean.FALSE);
272    }
273
274    /**
275     * Compute a multi tenant directory id based on the given {@code tenantId}.
276     *
277     * @return the computed directory id
278     * @since 5.6
279     */
280    public static String computeMultiTenantDirectoryId(String tenantId, String id) {
281        return String.format(MULTI_TENANT_ID_FORMAT, tenantId, id);
282    }
283
284    @Override
285    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy,
286            boolean fetchReferences, int limit, int offset) throws DirectoryException {
287        log.info("Call an unoverrided query with offset and limit.");
288        DocumentModelList entries = query(filter, fulltext, orderBy, fetchReferences);
289        int toIndex = offset + limit;
290        if (toIndex > entries.size()) {
291            toIndex = entries.size();
292        }
293
294        return new DocumentModelListImpl(entries.subList(offset, toIndex));
295    }
296
297}