001/*
002 * (C) Copyright 2006-2016 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 *     George Lefter
018 *     Florent Guillaume
019 *     Anahide Tchertchian
020 *     Gagnavarslan ehf
021 */
022package org.nuxeo.ecm.platform.usermanager;
023
024import java.io.Serializable;
025import java.security.Principal;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034import java.util.Map.Entry;
035import java.util.Set;
036import java.util.regex.Matcher;
037import java.util.regex.Pattern;
038
039import org.apache.commons.codec.digest.DigestUtils;
040import org.apache.commons.lang.StringUtils;
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.DocumentModelComparator;
045import org.nuxeo.ecm.core.api.DocumentModelList;
046import org.nuxeo.ecm.core.api.NuxeoException;
047import org.nuxeo.ecm.core.api.NuxeoGroup;
048import org.nuxeo.ecm.core.api.NuxeoPrincipal;
049import org.nuxeo.ecm.core.api.PropertyException;
050import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
051import org.nuxeo.ecm.core.api.impl.NuxeoGroupImpl;
052import org.nuxeo.ecm.core.api.local.ClientLoginModule;
053import org.nuxeo.ecm.core.api.model.Property;
054import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
055import org.nuxeo.ecm.core.api.security.ACE;
056import org.nuxeo.ecm.core.api.security.ACL;
057import org.nuxeo.ecm.core.api.security.ACP;
058import org.nuxeo.ecm.core.api.security.AdministratorGroupsProvider;
059import org.nuxeo.ecm.core.api.security.PermissionProvider;
060import org.nuxeo.ecm.core.api.security.SecurityConstants;
061import org.nuxeo.ecm.core.cache.Cache;
062import org.nuxeo.ecm.core.cache.CacheService;
063import org.nuxeo.ecm.core.event.EventProducer;
064import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
065import org.nuxeo.ecm.core.event.impl.UnboundEventContext;
066import org.nuxeo.ecm.directory.BaseSession;
067import org.nuxeo.ecm.directory.DirectoryException;
068import org.nuxeo.ecm.directory.Session;
069import org.nuxeo.ecm.directory.api.DirectoryService;
070import org.nuxeo.ecm.platform.usermanager.exceptions.GroupAlreadyExistsException;
071import org.nuxeo.ecm.platform.usermanager.exceptions.InvalidPasswordException;
072import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException;
073import org.nuxeo.runtime.api.Framework;
074import org.nuxeo.runtime.services.config.ConfigurationService;
075import org.nuxeo.runtime.services.event.Event;
076import org.nuxeo.runtime.services.event.EventService;
077
078/**
079 * Standard implementation of the Nuxeo UserManager.
080 */
081public class UserManagerImpl implements UserManager, MultiTenantUserManager, AdministratorGroupsProvider {
082
083    private static final String VALIDATE_PASSWORD_PARAM = "nuxeo.usermanager.check.password";
084
085    private static final long serialVersionUID = 1L;
086
087    private static final Log log = LogFactory.getLog(UserManagerImpl.class);
088
089    public static final String USERMANAGER_TOPIC = "usermanager";
090
091    /** Used by JaasCacheFlusher. */
092    public static final String USERCHANGED_EVENT_ID = "user_changed";
093
094    public static final String USERCREATED_EVENT_ID = "user_created";
095
096    public static final String USERDELETED_EVENT_ID = "user_deleted";
097
098    public static final String USERMODIFIED_EVENT_ID = "user_modified";
099
100    /** Used by JaasCacheFlusher. */
101    public static final String GROUPCHANGED_EVENT_ID = "group_changed";
102
103    public static final String GROUPCREATED_EVENT_ID = "group_created";
104
105    public static final String GROUPDELETED_EVENT_ID = "group_deleted";
106
107    public static final String GROUPMODIFIED_EVENT_ID = "group_modified";
108
109    public static final String DEFAULT_ANONYMOUS_USER_ID = "Anonymous";
110
111    public static final String VIRTUAL_FIELD_FILTER_PREFIX = "__";
112
113    public static final String INVALIDATE_PRINCIPAL_EVENT_ID = "invalidatePrincipal";
114
115    public static final String INVALIDATE_ALL_PRINCIPALS_EVENT_ID = "invalidateAllPrincipals";
116
117    /**
118     * Possible value for the {@link DocumentEventContext#CATEGORY_PROPERTY_KEY} key of a core event context.
119     *
120     * @since 9.2
121     */
122    public static final String USER_GROUP_CATEGORY = "userGroup";
123
124    /**
125     * Key for the id of a user or a group in a core event context.
126     *
127     * @since 9.2
128     */
129    public static final String ID_PROPERTY_KEY = "id";
130
131    /**
132     * Key for the ancestor group names of a group in a core event context.
133     *
134     * @since 9.2
135     */
136    public static final String ANCESTOR_GROUPS_PROPERTY_KEY = "ancestorGroups";
137
138    protected final DirectoryService dirService;
139
140    protected final CacheService cacheService;
141
142    protected Cache principalCache = null;
143
144    public UserMultiTenantManagement multiTenantManagement = new DefaultUserMultiTenantManagement();
145
146    /**
147     * A structure used to inject field name configuration of users schema into a NuxeoPrincipalImpl instance. TODO not
148     * all fields inside are configurable for now - they will use default values
149     */
150    protected UserConfig userConfig;
151
152    protected String userDirectoryName;
153
154    protected String userSchemaName;
155
156    protected String userIdField;
157
158    protected String userEmailField;
159
160    protected Map<String, MatchType> userSearchFields;
161
162    protected String groupDirectoryName;
163
164    protected String groupSchemaName;
165
166    protected String groupIdField;
167
168    protected String groupLabelField;
169
170    protected String groupMembersField;
171
172    protected String groupSubGroupsField;
173
174    protected String groupParentGroupsField;
175
176    protected String groupSortField;
177
178    protected Map<String, MatchType> groupSearchFields;
179
180    protected String defaultGroup;
181
182    protected List<String> administratorIds;
183
184    protected List<String> administratorGroups;
185
186    protected Boolean disableDefaultAdministratorsGroup;
187
188    protected String userSortField;
189
190    protected String userListingMode;
191
192    protected String groupListingMode;
193
194    protected Pattern userPasswordPattern;
195
196    protected VirtualUser anonymousUser;
197
198    protected String digestAuthDirectory;
199
200    protected String digestAuthRealm;
201
202    protected final Map<String, VirtualUserDescriptor> virtualUsers;
203
204    public UserManagerImpl() {
205        dirService = Framework.getLocalService(DirectoryService.class);
206        cacheService = Framework.getLocalService(CacheService.class);
207        virtualUsers = new HashMap<>();
208        userConfig = new UserConfig();
209    }
210
211    @Override
212    public void setConfiguration(UserManagerDescriptor descriptor) {
213        defaultGroup = descriptor.defaultGroup;
214        administratorIds = descriptor.defaultAdministratorIds;
215        disableDefaultAdministratorsGroup = false;
216        if (descriptor.disableDefaultAdministratorsGroup != null) {
217            disableDefaultAdministratorsGroup = descriptor.disableDefaultAdministratorsGroup;
218        }
219        administratorGroups = new ArrayList<>();
220        if (!disableDefaultAdministratorsGroup) {
221            administratorGroups.add(SecurityConstants.ADMINISTRATORS);
222        }
223        if (descriptor.administratorsGroups != null) {
224            administratorGroups.addAll(descriptor.administratorsGroups);
225        }
226        if (administratorGroups.isEmpty()) {
227            log.warn("No administrators group has been defined: at least one should be set"
228                    + " to avoid lockups when blocking rights for instance");
229        }
230        userSortField = descriptor.userSortField;
231        groupSortField = descriptor.groupSortField;
232        userListingMode = descriptor.userListingMode;
233        groupListingMode = descriptor.groupListingMode;
234        userEmailField = descriptor.userEmailField;
235        userSearchFields = descriptor.userSearchFields;
236        userPasswordPattern = descriptor.userPasswordPattern;
237        groupLabelField = descriptor.groupLabelField;
238        groupMembersField = descriptor.groupMembersField;
239        groupSubGroupsField = descriptor.groupSubGroupsField;
240        groupParentGroupsField = descriptor.groupParentGroupsField;
241        groupSearchFields = descriptor.groupSearchFields;
242        anonymousUser = descriptor.anonymousUser;
243
244        setUserDirectoryName(descriptor.userDirectoryName);
245        setGroupDirectoryName(descriptor.groupDirectoryName);
246        setVirtualUsers(descriptor.virtualUsers);
247
248        digestAuthDirectory = descriptor.digestAuthDirectory;
249        digestAuthRealm = descriptor.digestAuthRealm;
250
251        userConfig = new UserConfig();
252        userConfig.emailKey = userEmailField;
253        userConfig.schemaName = userSchemaName;
254        userConfig.nameKey = userIdField;
255
256        if (cacheService != null && descriptor.userCacheName != null) {
257            principalCache = cacheService.getCache(descriptor.userCacheName);
258            invalidateAllPrincipals();
259        }
260
261    }
262
263    protected void setUserDirectoryName(String userDirectoryName) {
264        this.userDirectoryName = userDirectoryName;
265        userSchemaName = dirService.getDirectorySchema(userDirectoryName);
266        userIdField = dirService.getDirectoryIdField(userDirectoryName);
267    }
268
269    @Override
270    public String getUserDirectoryName() {
271        return userDirectoryName;
272    }
273
274    @Override
275    public String getUserIdField() {
276        return userIdField;
277    }
278
279    @Override
280    public String getUserSchemaName() {
281        return userSchemaName;
282    }
283
284    @Override
285    public String getUserEmailField() {
286        return userEmailField;
287    }
288
289    @Override
290    public Set<String> getUserSearchFields() {
291        return Collections.unmodifiableSet(userSearchFields.keySet());
292    }
293
294    @Override
295    public Set<String> getGroupSearchFields() {
296        return Collections.unmodifiableSet(groupSearchFields.keySet());
297    }
298
299    protected void setGroupDirectoryName(String groupDirectoryName) {
300        this.groupDirectoryName = groupDirectoryName;
301        groupSchemaName = dirService.getDirectorySchema(groupDirectoryName);
302        groupIdField = dirService.getDirectoryIdField(groupDirectoryName);
303    }
304
305    @Override
306    public String getGroupDirectoryName() {
307        return groupDirectoryName;
308    }
309
310    @Override
311    public String getGroupIdField() {
312        return groupIdField;
313    }
314
315    @Override
316    public String getGroupLabelField() {
317        return groupLabelField;
318    }
319
320    @Override
321    public String getGroupSchemaName() {
322        return groupSchemaName;
323    }
324
325    @Override
326    public String getGroupMembersField() {
327        return groupMembersField;
328    }
329
330    @Override
331    public String getGroupSubGroupsField() {
332        return groupSubGroupsField;
333    }
334
335    @Override
336    public String getGroupParentGroupsField() {
337        return groupParentGroupsField;
338    }
339
340    @Override
341    public String getUserListingMode() {
342        return userListingMode;
343    }
344
345    @Override
346    public String getGroupListingMode() {
347        return groupListingMode;
348    }
349
350    @Override
351    public String getDefaultGroup() {
352        return defaultGroup;
353    }
354
355    @Override
356    public Pattern getUserPasswordPattern() {
357        return userPasswordPattern;
358    }
359
360    @Override
361    public String getAnonymousUserId() {
362        if (anonymousUser == null) {
363            return null;
364        }
365        String anonymousUserId = anonymousUser.getId();
366        if (anonymousUserId == null) {
367            return DEFAULT_ANONYMOUS_USER_ID;
368        }
369        return anonymousUserId;
370    }
371
372    protected void setVirtualUsers(Map<String, VirtualUserDescriptor> virtualUsers) {
373        this.virtualUsers.clear();
374        if (virtualUsers != null) {
375            this.virtualUsers.putAll(virtualUsers);
376        }
377    }
378
379    @Override
380    public boolean checkUsernamePassword(String username, String password) {
381
382        if (username == null || password == null) {
383            log.warn("Trying to authenticate against null username or password");
384            return false;
385        }
386
387        // deal with anonymous user
388        String anonymousUserId = getAnonymousUserId();
389        if (username.equals(anonymousUserId)) {
390            log.warn(String.format("Trying to authenticate anonymous user (%s)", anonymousUserId));
391            return false;
392        }
393
394        // deal with virtual users
395        if (virtualUsers.containsKey(username)) {
396            VirtualUser user = virtualUsers.get(username);
397            String expected = user.getPassword();
398            if (expected == null) {
399                return false;
400            }
401            return expected.equals(password);
402        }
403
404        String userDirName;
405        // BBB backward compat for userDirectory + userAuthentication
406        if ("userDirectory".equals(userDirectoryName) && dirService.getDirectory("userAuthentication") != null) {
407            userDirName = "userAuthentication";
408        } else {
409            userDirName = userDirectoryName;
410        }
411        try (Session userDir = dirService.open(userDirName)) {
412            if (!userDir.isAuthenticating()) {
413                log.error("Trying to authenticate against a non authenticating " + "directory: " + userDirName);
414                return false;
415            }
416
417            boolean authenticated = userDir.authenticate(username, password);
418            if (authenticated) {
419                syncDigestAuthPassword(username, password);
420            }
421            return authenticated;
422        }
423    }
424
425    protected void syncDigestAuthPassword(String username, String password) {
426        if (StringUtils.isEmpty(digestAuthDirectory) || StringUtils.isEmpty(digestAuthRealm) || username == null
427                || password == null) {
428            return;
429        }
430
431        String ha1 = encodeDigestAuthPassword(username, digestAuthRealm, password);
432        try (Session dir = dirService.open(digestAuthDirectory)) {
433            dir.setReadAllColumns(true); // needed to read digest password
434            String schema = dirService.getDirectorySchema(digestAuthDirectory);
435            DocumentModel entry = dir.getEntry(username, true);
436            if (entry == null) {
437                entry = getDigestAuthModel();
438                entry.setProperty(schema, dir.getIdField(), username);
439                entry.setProperty(schema, dir.getPasswordField(), ha1);
440                dir.createEntry(entry);
441                log.debug("Created digest auth password for user:" + username);
442            } else {
443                String storedHa1 = (String) entry.getProperty(schema, dir.getPasswordField());
444                if (!ha1.equals(storedHa1)) {
445                    entry.setProperty(schema, dir.getPasswordField(), ha1);
446                    dir.updateEntry(entry);
447                    log.debug("Updated digest auth password for user:" + username);
448                }
449            }
450        } catch (DirectoryException e) {
451            log.warn("Digest auth password not synchronized, check your configuration", e);
452        }
453    }
454
455    protected DocumentModel getDigestAuthModel() {
456        String schema = dirService.getDirectorySchema(digestAuthDirectory);
457        return BaseSession.createEntryModel(null, schema, null, null);
458    }
459
460    public static String encodeDigestAuthPassword(String username, String realm, String password) {
461        String a1 = username + ":" + realm + ":" + password;
462        return DigestUtils.md5Hex(a1);
463    }
464
465    @Override
466    public String getDigestAuthDirectory() {
467        return digestAuthDirectory;
468    }
469
470    @Override
471    public String getDigestAuthRealm() {
472        return digestAuthRealm;
473    }
474
475    @Override
476    public boolean validatePassword(String password) {
477        if (userPasswordPattern == null) {
478            return true;
479        } else {
480            Matcher userPasswordMatcher = userPasswordPattern.matcher(password);
481            return userPasswordMatcher.find();
482        }
483    }
484
485    protected NuxeoPrincipal makeAnonymousPrincipal() {
486        DocumentModel userEntry = makeVirtualUserEntry(getAnonymousUserId(), anonymousUser);
487        // XXX: pass anonymous user groups, but they will be ignored
488        return makePrincipal(userEntry, true, anonymousUser.getGroups());
489    }
490
491    protected NuxeoPrincipal makeVirtualPrincipal(VirtualUser user) {
492        DocumentModel userEntry = makeVirtualUserEntry(user.getId(), user);
493        return makePrincipal(userEntry, false, user.getGroups());
494    }
495
496    protected NuxeoPrincipal makeTransientPrincipal(String username) {
497        DocumentModel userEntry = BaseSession.createEntryModel(null, userSchemaName, username, null);
498        userEntry.setProperty(userSchemaName, userIdField, username);
499        NuxeoPrincipal principal = makePrincipal(userEntry, false, true, null);
500        String[] parts = username.split("/");
501        String email = parts[1];
502        principal.setFirstName(email);
503        principal.setEmail(email);
504        return principal;
505    }
506
507    protected DocumentModel makeVirtualUserEntry(String id, VirtualUser user) {
508        final DocumentModel userEntry = BaseSession.createEntryModel(null, userSchemaName, id, null);
509        // at least fill id field
510        userEntry.setProperty(userSchemaName, userIdField, id);
511        for (Entry<String, Serializable> prop : user.getProperties().entrySet()) {
512            try {
513                userEntry.setProperty(userSchemaName, prop.getKey(), prop.getValue());
514            } catch (PropertyNotFoundException ce) {
515                log.error("Property: " + prop.getKey() + " does not exists. Check your " + "UserService configuration.",
516                        ce);
517            }
518        }
519        return userEntry;
520    }
521
522    protected NuxeoPrincipal makePrincipal(DocumentModel userEntry) {
523        return makePrincipal(userEntry, false, null);
524    }
525
526    protected NuxeoPrincipal makePrincipal(DocumentModel userEntry, boolean anonymous, List<String> groups) {
527        return makePrincipal(userEntry, anonymous, false, groups);
528    }
529
530    protected NuxeoPrincipal makePrincipal(DocumentModel userEntry, boolean anonymous, boolean isTransient,
531            List<String> groups) {
532        boolean admin = false;
533        String username = userEntry.getId();
534
535        List<String> virtualGroups = new LinkedList<>();
536        // Add preconfigured groups: useful for LDAP, not for anonymous users
537        if (defaultGroup != null && !anonymous && !isTransient) {
538            virtualGroups.add(defaultGroup);
539        }
540        // Add additional groups: useful for virtual users
541        if (groups != null && !isTransient) {
542            virtualGroups.addAll(groups);
543        }
544        // Create a default admin if needed
545        if (administratorIds != null && administratorIds.contains(username)) {
546            admin = true;
547            if (administratorGroups != null) {
548                virtualGroups.addAll(administratorGroups);
549            }
550        }
551
552        NuxeoPrincipalImpl principal = new NuxeoPrincipalImpl(username, anonymous, admin, false);
553        principal.setConfig(userConfig);
554
555        principal.setModel(userEntry, false);
556        principal.setVirtualGroups(virtualGroups, true);
557
558        // TODO: reenable roles initialization once we have a use case for
559        // a role directory. In the mean time we only set the JBOSS role
560        // that is required to login
561        List<String> roles = Collections.singletonList("regular");
562        principal.setRoles(roles);
563
564        return principal;
565    }
566
567    protected boolean useCache() {
568        return principalCache != null;
569    }
570
571    @Override
572    public NuxeoPrincipal getPrincipal(String username) {
573        if (useCache()) {
574            return getPrincipalUsingCache(username);
575        }
576        return getPrincipal(username, null);
577    }
578
579    protected NuxeoPrincipal getPrincipalUsingCache(String username) {
580        NuxeoPrincipal ret = (NuxeoPrincipal) principalCache.get(username);
581        if (ret == null) {
582            ret = getPrincipal(username, null);
583            if (ret == null) {
584                return ret;
585            }
586            principalCache.put(username, ret);
587        }
588        return ((NuxeoPrincipalImpl) ret).cloneTransferable(); // should not return cached principal
589    }
590
591    @Override
592    public DocumentModel getUserModel(String userName) {
593        return getUserModel(userName, null);
594    }
595
596    @Override
597    public DocumentModel getBareUserModel() {
598        String schema = dirService.getDirectorySchema(userDirectoryName);
599        return BaseSession.createEntryModel(null, schema, null, null);
600    }
601
602    @Override
603    public NuxeoGroup getGroup(String groupName) {
604        return getGroup(groupName, null);
605    }
606
607    protected NuxeoGroup getGroup(String groupName, DocumentModel context) {
608        DocumentModel groupEntry = getGroupModel(groupName, context);
609        if (groupEntry != null) {
610            return makeGroup(groupEntry);
611        }
612        return null;
613
614    }
615
616    @Override
617    public DocumentModel getGroupModel(String groupName) {
618        return getGroupModel(groupName, null);
619    }
620
621    @SuppressWarnings("unchecked")
622    protected NuxeoGroup makeGroup(DocumentModel groupEntry) {
623        NuxeoGroup group = new NuxeoGroupImpl(groupEntry.getId());
624        List<String> list;
625        try {
626            list = (List<String>) groupEntry.getProperty(groupSchemaName, groupMembersField);
627        } catch (PropertyException e) {
628            list = null;
629        }
630        if (list != null) {
631            group.setMemberUsers(list);
632        }
633        try {
634            list = (List<String>) groupEntry.getProperty(groupSchemaName, groupSubGroupsField);
635        } catch (PropertyException e) {
636            list = null;
637        }
638        if (list != null) {
639            group.setMemberGroups(list);
640        }
641        try {
642            list = (List<String>) groupEntry.getProperty(groupSchemaName, groupParentGroupsField);
643        } catch (PropertyException e) {
644            list = null;
645        }
646        if (list != null) {
647            group.setParentGroups(list);
648        }
649        try {
650            String label = (String) groupEntry.getProperty(groupSchemaName, groupLabelField);
651            if (label != null) {
652                group.setLabel(label);
653            }
654        } catch (PropertyException e) {
655            // Nothing to do.
656        }
657        return group;
658    }
659
660    @Override
661    public List<String> getTopLevelGroups() {
662        return getTopLevelGroups(null);
663    }
664
665    @Override
666    public List<String> getGroupsInGroup(String parentId) {
667        NuxeoGroup group = getGroup(parentId, null);
668        if (group != null) {
669            return group.getMemberGroups();
670        } else {
671            return Collections.emptyList();
672        }
673    }
674
675    @Override
676    public List<String> getUsersInGroup(String groupId) {
677        return getGroup(groupId).getMemberUsers();
678    }
679
680    @Override
681    public List<String> getUsersInGroupAndSubGroups(String groupId) {
682        return getUsersInGroupAndSubGroups(groupId, null);
683    }
684
685    protected void appendSubgroups(String groupId, Set<String> groups, DocumentModel context) {
686        List<String> groupsToAppend = getGroupsInGroup(groupId, context);
687        groups.addAll(groupsToAppend);
688        for (String subgroupId : groupsToAppend) {
689            groups.add(subgroupId);
690            // avoiding infinite loop
691            if (!groups.contains(subgroupId)) {
692                appendSubgroups(subgroupId, groups, context);
693            }
694        }
695
696    }
697
698    protected boolean isAnonymousMatching(Map<String, Serializable> filter, Set<String> fulltext) {
699        String anonymousUserId = getAnonymousUserId();
700        if (anonymousUserId == null) {
701            return false;
702        }
703        if (filter == null || filter.isEmpty()) {
704            return true;
705        }
706        Map<String, Serializable> anonymousUserMap = anonymousUser.getProperties();
707        anonymousUserMap.put(userIdField, anonymousUserId);
708        for (Entry<String, Serializable> e : filter.entrySet()) {
709            String fieldName = e.getKey();
710            Object expected = e.getValue();
711            Object value = anonymousUserMap.get(fieldName);
712            if (value == null) {
713                if (expected != null) {
714                    return false;
715                }
716            } else {
717                if (fulltext != null && fulltext.contains(fieldName)) {
718                    if (!value.toString().toLowerCase().startsWith(expected.toString().toLowerCase())) {
719                        return false;
720                    }
721                } else {
722                    if (!value.equals(expected)) {
723                        return false;
724                    }
725                }
726            }
727        }
728        return true;
729    }
730
731    @Override
732    public List<NuxeoPrincipal> searchPrincipals(String pattern) {
733        DocumentModelList entries = searchUsers(pattern);
734        List<NuxeoPrincipal> principals = new ArrayList<>(entries.size());
735        for (DocumentModel entry : entries) {
736            principals.add(makePrincipal(entry));
737        }
738        return principals;
739    }
740
741    @Override
742    public DocumentModelList searchGroups(String pattern) {
743        return searchGroups(pattern, null);
744    }
745
746    @Override
747    public String getUserSortField() {
748        return userSortField;
749    }
750
751    protected Map<String, String> getUserSortMap() {
752        return getDirectorySortMap(userSortField, userIdField);
753    }
754
755    protected Map<String, String> getGroupSortMap() {
756        return getDirectorySortMap(groupSortField, groupIdField);
757    }
758
759    protected Map<String, String> getDirectorySortMap(String descriptorSortField, String fallBackField) {
760        String sortField = descriptorSortField != null ? descriptorSortField : fallBackField;
761        Map<String, String> orderBy = new HashMap<>();
762        orderBy.put(sortField, DocumentModelComparator.ORDER_ASC);
763        return orderBy;
764    }
765
766    /**
767     * @since 8.2
768     */
769    protected void notifyCore(String userOrGroupId, String eventId) {
770        notifyCore(userOrGroupId, eventId, null);
771    }
772
773    /**
774     * @since 9.2
775     */
776    protected void notifyCore(String userOrGroupId, String eventId, List<String> ancestorGroupIds) {
777        Map<String, Serializable> eventProperties = new HashMap<>();
778        eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, USER_GROUP_CATEGORY);
779        eventProperties.put(ID_PROPERTY_KEY, userOrGroupId);
780        if (ancestorGroupIds != null) {
781            eventProperties.put(ANCESTOR_GROUPS_PROPERTY_KEY, (Serializable) ancestorGroupIds);
782        }
783        NuxeoPrincipal principal = ClientLoginModule.getCurrentPrincipal();
784        UnboundEventContext envContext = new UnboundEventContext(principal, eventProperties);
785        envContext.setProperties(eventProperties);
786        EventProducer eventProducer = Framework.getService(EventProducer.class);
787        eventProducer.fireEvent(envContext.newEvent(eventId));
788    }
789
790    protected void notifyRuntime(String userOrGroupName, String eventId) {
791        EventService eventService = Framework.getService(EventService.class);
792        eventService.sendEvent(new Event(USERMANAGER_TOPIC, eventId, this, userOrGroupName));
793    }
794
795    @Override
796    public void notifyUserChanged(String userName, String eventId) {
797        invalidatePrincipal(userName);
798        notifyRuntime(userName, USERCHANGED_EVENT_ID);
799        if (eventId != null) {
800            notifyRuntime(userName, eventId);
801            notifyCore(userName, eventId);
802        }
803    }
804
805    protected void invalidatePrincipal(String userName) {
806        if (useCache()) {
807            principalCache.invalidate(userName);
808        }
809    }
810
811    @Override
812    public void notifyGroupChanged(String groupName, String eventId, List<String> ancestorGroupNames) {
813        invalidateAllPrincipals();
814        notifyRuntime(groupName, GROUPCHANGED_EVENT_ID);
815        if (eventId != null) {
816            notifyRuntime(groupName, eventId);
817            notifyCore(groupName, eventId, ancestorGroupNames);
818        }
819    }
820
821    protected void invalidateAllPrincipals() {
822        if (useCache()) {
823            principalCache.invalidateAll();
824        }
825    }
826
827    @Override
828    public Boolean areGroupsReadOnly() {
829        try (Session groupDir = dirService.open(groupDirectoryName)) {
830            return groupDir.isReadOnly();
831        } catch (DirectoryException e) {
832            log.error(e);
833            return false;
834        }
835    }
836
837    @Override
838    public Boolean areUsersReadOnly() {
839        try (Session userDir = dirService.open(userDirectoryName)) {
840            return userDir.isReadOnly();
841        } catch (DirectoryException e) {
842            log.error(e);
843            return false;
844        }
845    }
846
847    protected void checkGrouId(DocumentModel groupModel) {
848        // be sure the name does not contains trailing spaces
849        Object groupIdValue = groupModel.getProperty(groupSchemaName, groupIdField);
850        if (groupIdValue != null) {
851            groupModel.setProperty(groupSchemaName, groupIdField, groupIdValue.toString().trim());
852        }
853    }
854
855    protected String getGroupId(DocumentModel groupModel) {
856        Object groupIdValue = groupModel.getProperty(groupSchemaName, groupIdField);
857        if (groupIdValue != null && !(groupIdValue instanceof String)) {
858            throw new NuxeoException("Invalid group id " + groupIdValue);
859        }
860        return (String) groupIdValue;
861    }
862
863    protected void checkUserId(DocumentModel userModel) {
864        Object userIdValue = userModel.getProperty(userSchemaName, userIdField);
865        if (userIdValue != null) {
866            userModel.setProperty(userSchemaName, userIdField, userIdValue.toString().trim());
867        }
868    }
869
870    protected String getUserId(DocumentModel userModel) {
871        Object userIdValue = userModel.getProperty(userSchemaName, userIdField);
872        if (userIdValue != null && !(userIdValue instanceof String)) {
873            throw new NuxeoException("Invalid user id " + userIdValue);
874        }
875        return (String) userIdValue;
876    }
877
878    @Override
879    public DocumentModel createGroup(DocumentModel groupModel) {
880        return createGroup(groupModel, null);
881    }
882
883    @Override
884    public DocumentModel createUser(DocumentModel userModel) {
885        return createUser(userModel, null);
886    }
887
888    @Override
889    public void deleteGroup(String groupId) {
890        deleteGroup(groupId, null);
891    }
892
893    @Override
894    public void deleteGroup(DocumentModel groupModel) {
895        deleteGroup(groupModel, null);
896    }
897
898    @Override
899    public void deleteUser(String userId) {
900        deleteUser(userId, null);
901    }
902
903    @Override
904    public void deleteUser(DocumentModel userModel) {
905        String userId = getUserId(userModel);
906        deleteUser(userId);
907    }
908
909    @Override
910    public List<String> getGroupIds() {
911        try (Session groupDir = dirService.open(groupDirectoryName)) {
912            List<String> groupIds = groupDir.getProjection(Collections.<String, Serializable> emptyMap(),
913                    groupDir.getIdField());
914            Collections.sort(groupIds);
915            return groupIds;
916        }
917    }
918
919    @Override
920    public List<String> getUserIds() {
921        return getUserIds(null);
922    }
923
924    protected void removeVirtualFilters(Map<String, Serializable> filter) {
925        if (filter == null) {
926            return;
927        }
928        List<String> keys = new ArrayList<>(filter.keySet());
929        for (String key : keys) {
930            if (key.startsWith(VIRTUAL_FIELD_FILTER_PREFIX)) {
931                filter.remove(key);
932            }
933        }
934    }
935
936    @Override
937    public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext) {
938        return searchGroups(filter, fulltext, null);
939    }
940
941    @Override
942    public DocumentModelList searchUsers(String pattern) {
943        return searchUsers(pattern, null);
944    }
945
946    @Override
947    public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext) {
948        return searchUsers(filter, fulltext, getUserSortMap(), null);
949    }
950
951    @Override
952    public void updateGroup(DocumentModel groupModel) {
953        updateGroup(groupModel, null);
954    }
955
956    @Override
957    public void updateUser(DocumentModel userModel) {
958        updateUser(userModel, null);
959    }
960
961    @Override
962    public DocumentModel getBareGroupModel() {
963        String schema = dirService.getDirectorySchema(groupDirectoryName);
964        return BaseSession.createEntryModel(null, schema, null, null);
965    }
966
967    @Override
968    public List<String> getAdministratorsGroups() {
969        return administratorGroups;
970    }
971
972    protected List<String> getLeafPermissions(String perm) {
973        ArrayList<String> permissions = new ArrayList<>();
974        PermissionProvider permissionProvider = Framework.getService(PermissionProvider.class);
975        String[] subpermissions = permissionProvider.getSubPermissions(perm);
976        if (subpermissions == null || subpermissions.length <= 0) {
977            // it's a leaf
978            permissions.add(perm);
979            return permissions;
980        }
981        for (String subperm : subpermissions) {
982            permissions.addAll(getLeafPermissions(subperm));
983        }
984        return permissions;
985    }
986
987    @Override
988    public String[] getUsersForPermission(String perm, ACP acp) {
989        return getUsersForPermission(perm, acp, null);
990    }
991
992    @Override
993    public Principal authenticate(String name, String password) {
994        return checkUsernamePassword(name, password) ? getPrincipal(name) : null;
995    }
996
997    /*************** MULTI-TENANT-IMPLEMENTATION ************************/
998
999    public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext,
1000            Map<String, String> orderBy, DocumentModel context) {
1001        try (Session userDir = dirService.open(userDirectoryName, context)) {
1002            removeVirtualFilters(filter);
1003
1004            // XXX: do not fetch references, can be costly
1005            DocumentModelList entries = userDir.query(filter, fulltext, null, false);
1006            if (isAnonymousMatching(filter, fulltext)) {
1007                entries.add(makeVirtualUserEntry(getAnonymousUserId(), anonymousUser));
1008            }
1009
1010            // TODO: match searchable virtual users
1011
1012            if (orderBy != null && !orderBy.isEmpty()) {
1013                // sort: cannot sort before virtual users are added
1014                Collections.sort(entries, new DocumentModelComparator(userSchemaName, orderBy));
1015            }
1016
1017            return entries;
1018        }
1019    }
1020
1021    @Override
1022    public List<String> getUsersInGroup(String groupId, DocumentModel context) {
1023        String storeGroupId = multiTenantManagement.groupnameTranformer(this, groupId, context);
1024        return getGroup(storeGroupId).getMemberUsers();
1025    }
1026
1027    @Override
1028    public DocumentModelList searchUsers(String pattern, DocumentModel context) {
1029        DocumentModelList entries = new DocumentModelListImpl();
1030        if (pattern == null || pattern.length() == 0) {
1031            entries = searchUsers(Collections.<String, Serializable> emptyMap(), null);
1032        } else {
1033            pattern = pattern.trim();
1034            Map<String, DocumentModel> uniqueEntries = new HashMap<>();
1035
1036            for (Entry<String, MatchType> fieldEntry : userSearchFields.entrySet()) {
1037                Map<String, Serializable> filter = new HashMap<>();
1038                filter.put(fieldEntry.getKey(), pattern);
1039                DocumentModelList fetchedEntries;
1040                if (fieldEntry.getValue() == MatchType.SUBSTRING) {
1041                    fetchedEntries = searchUsers(filter, filter.keySet(), null, context);
1042                } else {
1043                    fetchedEntries = searchUsers(filter, null, null, context);
1044                }
1045                for (DocumentModel entry : fetchedEntries) {
1046                    uniqueEntries.put(entry.getId(), entry);
1047                }
1048            }
1049            log.debug(String.format("found %d unique entries", uniqueEntries.size()));
1050            entries.addAll(uniqueEntries.values());
1051        }
1052        // sort
1053        Collections.sort(entries, new DocumentModelComparator(userSchemaName, getUserSortMap()));
1054
1055        return entries;
1056    }
1057
1058    @Override
1059    public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext,
1060            DocumentModel context) {
1061        throw new UnsupportedOperationException();
1062    }
1063
1064    @Override
1065    public List<String> getGroupIds(DocumentModel context) {
1066        throw new UnsupportedOperationException();
1067    }
1068
1069    @Override
1070    public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext,
1071            DocumentModel context) {
1072        filter = filter != null ? cloneMap(filter) : new HashMap<>();
1073        HashSet<String> fulltextClone = fulltext != null ? cloneSet(fulltext) : new HashSet<>();
1074        multiTenantManagement.queryTransformer(this, filter, fulltextClone, context);
1075
1076        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1077            removeVirtualFilters(filter);
1078            return groupDir.query(filter, fulltextClone, getGroupSortMap(), false);
1079        }
1080    }
1081
1082    @Override
1083    public DocumentModel createGroup(DocumentModel groupModel, DocumentModel context)
1084            throws GroupAlreadyExistsException {
1085        groupModel = multiTenantManagement.groupTransformer(this, groupModel, context);
1086
1087        // be sure the name does not contains trailing spaces
1088        checkGrouId(groupModel);
1089
1090        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1091            String groupId = getGroupId(groupModel);
1092
1093            // check the group does not exist
1094            if (groupDir.hasEntry(groupId)) {
1095                throw new GroupAlreadyExistsException();
1096            }
1097            groupModel = groupDir.createEntry(groupModel);
1098            notifyGroupChanged(groupId, GROUPCREATED_EVENT_ID);
1099            return groupModel;
1100
1101        }
1102    }
1103
1104    @Override
1105    public DocumentModel getGroupModel(String groupIdValue, DocumentModel context) {
1106        String groupName = multiTenantManagement.groupnameTranformer(this, groupIdValue, context);
1107        if (groupName != null) {
1108            groupName = groupName.trim();
1109        }
1110
1111        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1112            return groupDir.getEntry(groupName);
1113        }
1114    }
1115
1116    @Override
1117    public DocumentModel getUserModel(String userName, DocumentModel context) {
1118        if (userName == null) {
1119            return null;
1120        }
1121
1122        userName = userName.trim();
1123        // return anonymous model
1124        if (anonymousUser != null && userName.equals(anonymousUser.getId())) {
1125            return makeVirtualUserEntry(getAnonymousUserId(), anonymousUser);
1126        }
1127
1128        try (Session userDir = dirService.open(userDirectoryName, context)) {
1129            return userDir.getEntry(userName);
1130        }
1131    }
1132
1133    protected Map<String, Serializable> cloneMap(Map<String, Serializable> map) {
1134        Map<String, Serializable> result = new HashMap<>();
1135        for (String key : map.keySet()) {
1136            result.put(key, map.get(key));
1137        }
1138        return result;
1139    }
1140
1141    protected HashSet<String> cloneSet(Set<String> set) {
1142        HashSet<String> result = new HashSet<>();
1143        for (String key : set) {
1144            result.add(key);
1145        }
1146        return result;
1147    }
1148
1149    @Override
1150    public NuxeoPrincipal getPrincipal(String username, DocumentModel context) {
1151        if (username == null) {
1152            return null;
1153        }
1154        String anonymousUserId = getAnonymousUserId();
1155        if (username.equals(anonymousUserId)) {
1156            return makeAnonymousPrincipal();
1157        }
1158        if (virtualUsers.containsKey(username)) {
1159            return makeVirtualPrincipal(virtualUsers.get(username));
1160        }
1161        if (NuxeoPrincipal.isTransientUsername(username)) {
1162            return makeTransientPrincipal(username);
1163        }
1164        DocumentModel userModel = getUserModel(username, context);
1165        if (userModel != null) {
1166            return makePrincipal(userModel);
1167        }
1168        return null;
1169    }
1170
1171    @Override
1172    public DocumentModelList searchGroups(String pattern, DocumentModel context) {
1173        DocumentModelList entries = new DocumentModelListImpl();
1174        if (pattern == null || pattern.length() == 0) {
1175            entries = searchGroups(Collections.<String, Serializable> emptyMap(), null);
1176        } else {
1177            pattern = pattern.trim();
1178            Map<String, DocumentModel> uniqueEntries = new HashMap<>();
1179
1180            for (Entry<String, MatchType> fieldEntry : groupSearchFields.entrySet()) {
1181                Map<String, Serializable> filter = new HashMap<>();
1182                filter.put(fieldEntry.getKey(), pattern);
1183                DocumentModelList fetchedEntries;
1184                if (fieldEntry.getValue() == MatchType.SUBSTRING) {
1185                    fetchedEntries = searchGroups(filter, filter.keySet(), context);
1186                } else {
1187                    fetchedEntries = searchGroups(filter, null, context);
1188                }
1189                for (DocumentModel entry : fetchedEntries) {
1190                    uniqueEntries.put(entry.getId(), entry);
1191                }
1192            }
1193            log.debug(String.format("found %d unique group entries", uniqueEntries.size()));
1194            entries.addAll(uniqueEntries.values());
1195        }
1196        // sort
1197        Collections.sort(entries, new DocumentModelComparator(groupSchemaName, getGroupSortMap()));
1198
1199        return entries;
1200    }
1201
1202    @Override
1203    public List<String> getUserIds(DocumentModel context) {
1204        try (Session userDir = dirService.open(userDirectoryName, context)) {
1205            List<String> userIds = userDir.getProjection(Collections.<String, Serializable> emptyMap(),
1206                    userDir.getIdField());
1207            Collections.sort(userIds);
1208            return userIds;
1209        }
1210    }
1211
1212    @Override
1213    public DocumentModel createUser(DocumentModel userModel, DocumentModel context) throws UserAlreadyExistsException {
1214        // be sure UserId does not contains any trailing spaces
1215        checkUserId(userModel);
1216
1217        try (Session userDir = dirService.open(userDirectoryName, context)) {
1218            String userId = getUserId(userModel);
1219
1220            // check the user does not exist
1221            if (userDir.hasEntry(userId)) {
1222                throw new UserAlreadyExistsException();
1223            }
1224
1225            checkPasswordValidity(userModel);
1226
1227            String schema = dirService.getDirectorySchema(userDirectoryName);
1228            String clearUsername = (String) userModel.getProperty(schema, userDir.getIdField());
1229            String clearPassword = (String) userModel.getProperty(schema, userDir.getPasswordField());
1230
1231            userModel = userDir.createEntry(userModel);
1232
1233            syncDigestAuthPassword(clearUsername, clearPassword);
1234
1235            notifyUserChanged(userId, USERCREATED_EVENT_ID);
1236            return userModel;
1237
1238        }
1239    }
1240
1241    protected void checkPasswordValidity(DocumentModel userModel) throws InvalidPasswordException {
1242        if (!mustCheckPasswordValidity()) {
1243            return;
1244        }
1245        String schema = dirService.getDirectorySchema(userDirectoryName);
1246        String passwordField = dirService.getDirectory(userDirectoryName).getPasswordField();
1247
1248        Property passwordProperty = userModel.getPropertyObject(schema, passwordField);
1249
1250        if (passwordProperty.isDirty()) {
1251            String clearPassword = (String) passwordProperty.getValue();
1252            if (StringUtils.isNotBlank(clearPassword) && !validatePassword(clearPassword)) {
1253                throw new InvalidPasswordException();
1254            }
1255        }
1256    }
1257
1258    @Override
1259    public void updateUser(DocumentModel userModel, DocumentModel context) {
1260        try (Session userDir = dirService.open(userDirectoryName, context)) {
1261            String userId = getUserId(userModel);
1262
1263            if (!userDir.hasEntry(userId)) {
1264                throw new DirectoryException("user does not exist: " + userId);
1265            }
1266
1267            String schema = dirService.getDirectorySchema(userDirectoryName);
1268
1269            checkPasswordValidity(userModel);
1270
1271            String clearUsername = (String) userModel.getProperty(schema, userDir.getIdField());
1272            String clearPassword = (String) userModel.getProperty(schema, userDir.getPasswordField());
1273
1274            userDir.updateEntry(userModel);
1275
1276            syncDigestAuthPassword(clearUsername, clearPassword);
1277
1278            notifyUserChanged(userId, USERMODIFIED_EVENT_ID);
1279        }
1280    }
1281
1282    private boolean mustCheckPasswordValidity() {
1283        return Framework.getService(ConfigurationService.class).isBooleanPropertyTrue(VALIDATE_PASSWORD_PARAM);
1284    }
1285
1286    @Override
1287    public void deleteUser(DocumentModel userModel, DocumentModel context) {
1288        String userId = getUserId(userModel);
1289        deleteUser(userId, context);
1290    }
1291
1292    @Override
1293    public void deleteUser(String userId, DocumentModel context) {
1294        try (Session userDir = dirService.open(userDirectoryName, context)) {
1295            if (!userDir.hasEntry(userId)) {
1296                throw new DirectoryException("User does not exist: " + userId);
1297            }
1298            userDir.deleteEntry(userId);
1299            notifyUserChanged(userId, USERDELETED_EVENT_ID);
1300
1301        } finally {
1302            notifyUserChanged(userId, null);
1303        }
1304    }
1305
1306    @Override
1307    public void updateGroup(DocumentModel groupModel, DocumentModel context) {
1308        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1309            String groupId = getGroupId(groupModel);
1310
1311            if (!groupDir.hasEntry(groupId)) {
1312                throw new DirectoryException("group does not exist: " + groupId);
1313            }
1314            groupDir.updateEntry(groupModel);
1315            notifyGroupChanged(groupId, GROUPMODIFIED_EVENT_ID);
1316        }
1317    }
1318
1319    @Override
1320    public void deleteGroup(DocumentModel groupModel, DocumentModel context) {
1321        String groupId = getGroupId(groupModel);
1322        deleteGroup(groupId, context);
1323    }
1324
1325    @Override
1326    public void deleteGroup(String groupId, DocumentModel context) {
1327        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1328            if (!groupDir.hasEntry(groupId)) {
1329                throw new DirectoryException("Group does not exist: " + groupId);
1330            }
1331            // Get ancestor group names before deletion to pass them as a property of the core event
1332            List<String> ancestorGroupNames = getAncestorGroups(groupId);
1333            groupDir.deleteEntry(groupId);
1334            notifyGroupChanged(groupId, GROUPDELETED_EVENT_ID, ancestorGroupNames);
1335        }
1336    }
1337
1338    @Override
1339    public List<String> getGroupsInGroup(String parentId, DocumentModel context) {
1340        return getGroup(parentId, null).getMemberGroups();
1341    }
1342
1343    @Override
1344    public List<String> getTopLevelGroups(DocumentModel context) {
1345        try (Session groupDir = dirService.open(groupDirectoryName, context)) {
1346            List<String> topLevelGroups = new LinkedList<>();
1347            // XXX retrieve all entries with references, can be costly.
1348            DocumentModelList groups = groupDir.query(Collections.<String, Serializable> emptyMap(), null, null, true);
1349            for (DocumentModel group : groups) {
1350                @SuppressWarnings("unchecked")
1351                List<String> parents = (List<String>) group.getProperty(groupSchemaName, groupParentGroupsField);
1352
1353                if (parents == null || parents.isEmpty()) {
1354                    topLevelGroups.add(group.getId());
1355                }
1356            }
1357            return topLevelGroups;
1358        }
1359    }
1360
1361    @Override
1362    public List<String> getUsersInGroupAndSubGroups(String groupId, DocumentModel context) {
1363        Set<String> groups = new HashSet<>();
1364        groups.add(groupId);
1365        appendSubgroups(groupId, groups, context);
1366
1367        Set<String> users = new HashSet<>();
1368        for (String groupid : groups) {
1369            users.addAll(getGroup(groupid, context).getMemberUsers());
1370        }
1371
1372        return new ArrayList<>(users);
1373    }
1374
1375    @Override
1376    public String[] getUsersForPermission(String perm, ACP acp, DocumentModel context) {
1377        PermissionProvider permissionProvider = Framework.getService(PermissionProvider.class);
1378        // using a hashset to avoid duplicates
1379        HashSet<String> usernames = new HashSet<>();
1380
1381        ACL merged = acp.getMergedACLs("merged");
1382        // The list of permission that is has "perm" as its (compound)
1383        // permission
1384        ArrayList<ACE> filteredACEbyPerm = new ArrayList<>();
1385
1386        List<String> currentPermissions = getLeafPermissions(perm);
1387
1388        for (ACE ace : merged.getACEs()) {
1389            // Checking if the permission contains the permission we want to
1390            // check (we use the security service method for coumpound
1391            // permissions)
1392            List<String> acePermissions = getLeafPermissions(ace.getPermission());
1393
1394            // Everything is a special permission (not compound)
1395            if (SecurityConstants.EVERYTHING.equals(ace.getPermission())) {
1396                acePermissions = Arrays.asList(permissionProvider.getPermissions());
1397            }
1398
1399            if (acePermissions.containsAll(currentPermissions)) {
1400                // special case: everybody perm grant false, don't take in
1401                // account the previous ace
1402                if (SecurityConstants.EVERYONE.equals(ace.getUsername()) && !ace.isGranted()) {
1403                    break;
1404                }
1405                filteredACEbyPerm.add(ace);
1406            }
1407        }
1408
1409        for (ACE ace : filteredACEbyPerm) {
1410            String aceUsername = ace.getUsername();
1411            List<String> users = null;
1412            // If everyone, add/remove all the users
1413            if (SecurityConstants.EVERYONE.equals(aceUsername)) {
1414                users = getUserIds();
1415            }
1416            // if a group, add/remove all the user from the group (and
1417            // subgroups)
1418            if (users == null) {
1419                NuxeoGroup group;
1420                group = getGroup(aceUsername, context);
1421                if (group != null) {
1422                    users = getUsersInGroupAndSubGroups(aceUsername, context);
1423                }
1424
1425            }
1426            // otherwise, add the user
1427            if (users == null) {
1428                users = new ArrayList<>();
1429                users.add(aceUsername);
1430            }
1431            if (ace.isGranted()) {
1432                usernames.addAll(users);
1433            } else {
1434                usernames.removeAll(users);
1435            }
1436        }
1437        return usernames.toArray(new String[usernames.size()]);
1438    }
1439
1440    @Override
1441    public List<String> getAncestorGroups(String groupId) {
1442        List<String> ancestorGroups = new ArrayList<>();
1443        populateAncestorGroups(groupId, ancestorGroups);
1444        return ancestorGroups;
1445    }
1446
1447    protected void populateAncestorGroups(String groupId, List<String> ancestorGroups) {
1448        NuxeoGroup group = getGroup(groupId);
1449        if (group != null) {
1450            List<String> parentGroups = group.getParentGroups();
1451            // Avoid infinite loop in case a group has one of its parents as a subgroup
1452            parentGroups.stream().filter(parentGroup -> !ancestorGroups.contains(parentGroup)).forEach(parentGroup -> {
1453                ancestorGroups.add(parentGroup);
1454                populateAncestorGroups(parentGroup, ancestorGroups);
1455            });
1456        }
1457    }
1458
1459    @Override
1460    public void handleEvent(Event event) {
1461        String id = event.getId();
1462        if (INVALIDATE_PRINCIPAL_EVENT_ID.equals(id)) {
1463            invalidatePrincipal((String) event.getData());
1464        } else if (INVALIDATE_ALL_PRINCIPALS_EVENT_ID.equals(id)) {
1465            invalidateAllPrincipals();
1466        }
1467    }
1468
1469}