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