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