001/*
002 * (C) Copyright 2006-2007 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 *     Thierry Delprat
018 *
019 */
020
021package org.nuxeo.ecm.platform.computedgroups;
022
023import java.io.Serializable;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.DocumentModelList;
031import org.nuxeo.ecm.core.api.NuxeoException;
032import org.nuxeo.ecm.core.api.NuxeoGroup;
033import org.nuxeo.ecm.core.api.NuxeoPrincipal;
034import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
035import org.nuxeo.ecm.core.query.sql.model.QueryBuilder;
036import org.nuxeo.ecm.directory.BaseSession;
037import org.nuxeo.ecm.directory.Session;
038import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
039import org.nuxeo.ecm.platform.usermanager.UserManager;
040import org.nuxeo.ecm.platform.usermanager.UserManagerImpl;
041import org.nuxeo.runtime.api.Framework;
042
043/**
044 * {@link UserManager} implementation that is aware of {@link ComputedGroup}.
045 *
046 * @author Thierry Delprat
047 */
048public class UserManagerWithComputedGroups extends UserManagerImpl {
049
050    private static final long serialVersionUID = 1L;
051
052    protected static Boolean useComputedGroup;
053
054    public static final String VIRTUAL_GROUP_MARKER = "__virtualGroup";
055
056    protected ComputedGroupsService getService() {
057        return Framework.getService(ComputedGroupsService.class);
058    }
059
060    protected boolean activateComputedGroup() {
061        // NXP-8133: recompute during tests, need to find a cleaner fix
062        if (useComputedGroup == null || Framework.isTestModeSet()) {
063            useComputedGroup = getService().activateComputedGroups();
064        }
065        return useComputedGroup;
066    }
067
068    @Override
069    protected NuxeoPrincipal makePrincipal(DocumentModel userEntry, boolean anonymous, List<String> groups) {
070
071        NuxeoPrincipal principal = super.makePrincipal(userEntry, anonymous, groups);
072        if (activateComputedGroup() && principal instanceof NuxeoPrincipalImpl) {
073            NuxeoPrincipalImpl nuxPrincipal = (NuxeoPrincipalImpl) principal;
074
075            List<String> vGroups = getService().computeGroupsForUser(nuxPrincipal);
076
077            if (vGroups == null) {
078                vGroups = new ArrayList<>();
079            }
080
081            List<String> origVGroups = nuxPrincipal.getVirtualGroups();
082            if (origVGroups == null) {
083                origVGroups = new ArrayList<>();
084            }
085
086            // MERGE!
087            origVGroups.addAll(vGroups);
088
089            nuxPrincipal.setVirtualGroups(origVGroups);
090
091            // This a hack to work around the problem of running tests
092            if (!Framework.isTestModeSet()) {
093                nuxPrincipal.updateAllGroups();
094            } else {
095                List<String> allGroups = nuxPrincipal.getGroups();
096                for (String vGroup : vGroups) {
097                    if (!allGroups.contains(vGroup)) {
098                        allGroups.add(vGroup);
099                    }
100                }
101                nuxPrincipal.setGroups(allGroups);
102            }
103        }
104        return principal;
105    }
106
107    @Override
108    public NuxeoGroup getGroup(String groupName) {
109        NuxeoGroup grp = super.getGroup(groupName);
110        if (activateComputedGroup() && (grp == null || getService().allowGroupOverride())) {
111            NuxeoGroup computed = getService().getComputedGroup(groupName, groupConfig);
112            if (computed != null) {
113                grp = computed;
114            }
115        }
116        return grp;
117    }
118
119    @Override
120    public NuxeoGroup getGroup(String groupName, DocumentModel context) {
121        NuxeoGroup grp = super.getGroup(groupName, context);
122        if (activateComputedGroup() && (grp == null || getService().allowGroupOverride())) {
123            NuxeoGroup computed = getService().getComputedGroup(groupName, groupConfig);
124            if (computed != null) {
125                grp = computed;
126            }
127        }
128        return grp;
129    }
130
131    @Override
132    public List<String> getGroupIds() {
133        List<String> ids = super.getGroupIds();
134        if (activateComputedGroup()) {
135            List<String> vGroups = getService().computeGroupIds();
136            for (String vGroup : vGroups) {
137                if (!ids.contains(vGroup)) {
138                    ids.add(vGroup);
139                }
140            }
141        }
142        return ids;
143    }
144
145    @Override
146    public DocumentModel getGroupModel(String groupName) {
147        DocumentModel grpDoc = super.getGroupModel(groupName);
148        if (activateComputedGroup() && grpDoc == null) {
149            return getComputedGroupAsDocumentModel(groupName);
150        }
151        return grpDoc;
152    }
153
154    @Override
155    public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext) {
156        return searchGroups(filter, fulltext, null);
157    }
158
159    @Override
160    public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext,
161            DocumentModel context) {
162
163        boolean searchInVirtualGroups = activateComputedGroup();
164        if (Boolean.FALSE.equals(filter.get(VIRTUAL_GROUP_MARKER))) {
165            searchInVirtualGroups = false;
166        }
167
168        removeVirtualFilters(filter);
169        DocumentModelList groups = super.searchGroups(filter, fulltext, context);
170
171        if (searchInVirtualGroups) {
172            for (String vGroupName : getService().searchComputedGroups(filter, fulltext)) {
173                DocumentModel vGroup = getComputedGroupAsDocumentModel(vGroupName);
174                if (vGroup != null) {
175                    if (!groups.contains(vGroup)) {
176                        groups.add(vGroup);
177                    }
178                }
179            }
180        }
181        return groups;
182    }
183
184    @Override
185    public DocumentModelList searchGroups(QueryBuilder queryBuilder) {
186        return searchGroups(queryBuilder, null);
187    }
188
189    @Override
190    public DocumentModelList searchGroups(QueryBuilder queryBuilder, DocumentModel context) {
191        // TODO find a way to pass a flag to allow callers do disable searching in virtual groups
192        DocumentModelList virtualEntries = new DocumentModelListImpl();
193        for (String groupName : getService().searchComputedGroups(queryBuilder)) {
194            DocumentModel entry = getComputedGroupAsDocumentModel(groupName);
195            if (entry != null && !virtualEntries.contains(entry)) {
196                virtualEntries.add(entry);
197            }
198        }
199        if (virtualEntries.isEmpty()) {
200            // no computed groups match
201            return super.searchGroups(queryBuilder, context);
202        } else {
203            // we have some computed groups to add, we'll need to do limit/offset/order by post-filtering
204            queryBuilder = multiTenantManagement.groupQueryTransformer(this, queryBuilder, context);
205            try (Session session = dirService.open(groupDirectoryName, context)) {
206                return queryWithVirtualEntries(session, queryBuilder, virtualEntries);
207            }
208        }
209    }
210
211    protected DocumentModel getComputedGroupAsDocumentModel(String grpName) {
212        NuxeoGroup grp = getService().getComputedGroup(grpName, groupConfig);
213        if (grp == null) {
214            return null;
215        }
216
217        String schemaName = getGroupSchemaName();
218        String id = getGroupIdField();
219        DocumentModel groupDoc = BaseSession.createEntryModel(null, schemaName, grpName, null);
220
221        groupDoc.setProperty(schemaName, getGroupMembersField(), grp.getMemberUsers());
222        groupDoc.setProperty(schemaName, id, grp.getName());
223        groupDoc.setProperty(schemaName, getGroupIdField(), grp.getName());
224
225        groupDoc.putContextData("virtual", Boolean.TRUE);
226
227        return groupDoc;
228    }
229
230    @Override
231    public DocumentModel createGroup(DocumentModel groupModel, DocumentModel context) {
232        if (activateComputedGroup()) {
233            String groupName = (String) groupModel.getProperty(groupConfig.schemaName, groupConfig.idField);
234            NuxeoGroup computed = getService().getComputedGroup(groupName, groupConfig);
235            if (computed != null) {
236                throw new NuxeoException("Cannot create a computed group");
237            }
238        }
239        return super.createGroup(groupModel, context);
240    }
241
242    @Override
243    public void updateGroup(DocumentModel groupModel, DocumentModel context) {
244        if (activateComputedGroup()) {
245            String groupName = (String) groupModel.getProperty(groupConfig.schemaName, groupConfig.idField);
246            NuxeoGroup computed = getService().getComputedGroup(groupName, groupConfig);
247            if (computed != null) {
248                throw new NuxeoException("Cannot update a computed group");
249            }
250        }
251        super.updateGroup(groupModel, context);
252    }
253
254    @Override
255    public void deleteGroup(String groupId, DocumentModel context) {
256        if (activateComputedGroup()) {
257            NuxeoGroup computed = getService().getComputedGroup(groupId, groupConfig);
258            if (computed != null) {
259                throw new NuxeoException("Cannot delete a computed group");
260            }
261        }
262        super.deleteGroup(groupId, context);
263    }
264}