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