1 /*
2  * Copyright (C) 2004-2008 Jive Software. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.jivesoftware.openfire.roster;
18 
19 import org.jivesoftware.openfire.JMXManager;
20 import org.jivesoftware.openfire.RoutingTable;
21 import org.jivesoftware.openfire.SharedGroupException;
22 import org.jivesoftware.openfire.XMPPServer;
23 import org.jivesoftware.openfire.container.BasicModule;
24 import org.jivesoftware.openfire.event.GroupEventDispatcher;
25 import org.jivesoftware.openfire.event.GroupEventListener;
26 import org.jivesoftware.openfire.event.UserEventDispatcher;
27 import org.jivesoftware.openfire.event.UserEventListener;
28 import org.jivesoftware.openfire.group.Group;
29 import org.jivesoftware.openfire.group.GroupManager;
30 import org.jivesoftware.openfire.group.GroupNotFoundException;
31 import org.jivesoftware.openfire.mbean.ThreadPoolExecutorDelegate;
32 import org.jivesoftware.openfire.mbean.ThreadPoolExecutorDelegateMBean;
33 import org.jivesoftware.openfire.user.User;
34 import org.jivesoftware.openfire.user.UserManager;
35 import org.jivesoftware.openfire.user.UserNotFoundException;
36 import org.jivesoftware.util.*;
37 import org.jivesoftware.util.cache.Cache;
38 import org.jivesoftware.util.cache.CacheFactory;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.xmpp.packet.JID;
42 import org.xmpp.packet.Presence;
43 
44 import javax.management.ObjectName;
45 import java.time.Duration;
46 import java.time.temporal.ChronoUnit;
47 import java.util.*;
48 import java.util.concurrent.*;
49 
50 /**
51  * A simple service that allows components to retrieve a roster based solely on the ID
52  * of the owner. Users have convenience methods for obtaining a roster associated with
53  * the owner. However there are many components that need to retrieve the roster
54  * based solely on the generic ID owner key. This interface defines a service that can
55  * do that. This allows classes that generically manage resource for resource owners
56  * (such as presence updates) to generically offer their services without knowing or
57  * caring if the roster owner is a user, chatbot, etc.
58  *
59  * @author Iain Shigeoka
60  */
61 public class RosterManager extends BasicModule implements GroupEventListener, UserEventListener {
62 
63     private static final Logger Log = LoggerFactory.getLogger(RosterManager.class);
64 
65     /**
66      * The number of threads to keep in the thread pool that is used to invoke roster event listeners, even if they are idle.
67      */
68     public static final SystemProperty<Integer> EXECUTOR_CORE_POOL_SIZE = SystemProperty.Builder.ofType(Integer.class)
69         .setKey("xmpp.client.roster.threadpool.size.core")
70         .setMinValue(0)
71         .setDefaultValue(0)
72         .setDynamic(false)
73         .build();
74 
75     /**
76      * The maximum number of threads to allow in the thread pool that is used to invoke roster event listeners.
77      */
78     public static final SystemProperty<Integer> EXECUTOR_MAX_POOL_SIZE = SystemProperty.Builder.ofType(Integer.class)
79         .setKey("xmpp.client.roster.threadpool.size.max")
80         .setMinValue(1)
81         .setDefaultValue(Integer.MAX_VALUE)
82         .setDynamic(false)
83         .build();
84 
85     /**
86      * The number of threads in the thread pool that is used to invoke roster event listeners is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
87      */
88     public static final SystemProperty<Duration> EXECUTOR_POOL_KEEP_ALIVE = SystemProperty.Builder.ofType(Duration.class)
89         .setKey("xmpp.client.roster.threadpool.keepalive")
90         .setChronoUnit(ChronoUnit.SECONDS)
91         .setDefaultValue(Duration.ofSeconds(60))
92         .setDynamic(false)
93         .build();
94 
95     private static final String MUTEX_SUFFIX = " ro";
96 
97     private Cache<String, Roster> rosterCache = null;
98     private XMPPServer server;
99     private RoutingTable routingTable;
100     private RosterItemProvider provider;
101     private ThreadPoolExecutor executor;
102 
103     /**
104      * Object name used to register delegate MBean (JMX) for the thread pool executor.
105      */
106     private ObjectName objectName;
107 
108     /**
109      * Returns true if the roster service is enabled. When disabled it is not possible to
110      * retrieve users rosters or broadcast presence packets to roster contacts.
111      *
112      * @return true if the roster service is enabled.
113      */
isRosterServiceEnabled()114     public static boolean isRosterServiceEnabled() {
115         return JiveGlobals.getBooleanProperty("xmpp.client.roster.active", true);
116     }
117 
118     /**
119      * Returns true if the roster versioning is enabled.
120      *
121      * @return true if the roster versioning is enabled.
122      */
isRosterVersioningEnabled()123     public static boolean isRosterVersioningEnabled() {
124         return JiveGlobals.getBooleanProperty("xmpp.client.roster.versioning.active", true);
125     }
126 
RosterManager()127     public RosterManager() {
128         super("Roster Manager");
129         rosterCache = CacheFactory.createCache("Roster");
130 
131         initProvider();
132 
133         PropertyEventDispatcher.addListener(new PropertyEventListener() {
134             @Override
135             public void propertySet(String property, Map params) {
136                 if (property.equals("provider.roster.className")) {
137                     initProvider();
138                 }
139             }
140             @Override
141             public void propertyDeleted(String property, Map params) {}
142             @Override
143             public void xmlPropertySet(String property, Map params) {}
144             @Override
145             public void xmlPropertyDeleted(String property, Map params) {}
146         });
147 
148     }
149 
150     /**
151      * Returns the roster for the given username.
152      *
153      * @param username the username to search for.
154      * @return the roster associated with the ID.
155      * @throws org.jivesoftware.openfire.user.UserNotFoundException if the ID does not correspond
156      *         to a known entity on the server.
157      */
getRoster(String username)158     public Roster getRoster(String username) throws UserNotFoundException {
159         Roster roster = rosterCache.get(username);
160         if (roster == null) {
161             // Synchronize using a unique key so that other threads loading the User
162             // and not the Roster cannot produce a deadlock
163            synchronized ((username  + MUTEX_SUFFIX).intern()) {
164                 roster = rosterCache.get(username);
165                 if (roster == null) {
166                     // Not in cache so load a new one:
167                     roster = new Roster(username);
168                     rosterCache.put(username, roster);
169                 }
170             }
171         }
172         return roster;
173     }
174 
175     /**
176      * Removes the entire roster of a given user. This is necessary when a user
177      * account is being deleted from the server.
178      *
179      * @param user the user.
180      */
deleteRoster(JID user)181     public void deleteRoster(JID user) {
182         if (!server.isLocal(user)) {
183             // Ignore request if user is not a local user
184             return;
185         }
186         try {
187             String username = user.getNode();
188             // Get the roster of the deleted user
189             Roster roster = getRoster(username);
190             // Remove each roster item from the user's roster
191             for (RosterItem item : roster.getRosterItems()) {
192                 try {
193                     roster.deleteRosterItem(item.getJid(), false);
194                 }
195                 catch (SharedGroupException e) {
196                     // Do nothing. We shouldn't have this exception since we disabled the checkings
197                     Log.warn( "Unexpected exception while deleting roster of user '{}' .", user, e );
198                 }
199             }
200             // Remove the cached roster from memory
201             rosterCache.remove(username);
202 
203             // Get the rosters that have a reference to the deleted user
204             Iterator<String> usernames = provider.getUsernames(user.toBareJID());
205             while (usernames.hasNext()) {
206                 username = usernames.next();
207                 try {
208                     // Get the roster that has a reference to the deleted user
209                     roster = getRoster(username);
210                     // Remove the deleted user reference from this roster
211                     roster.deleteRosterItem(user, false);
212                 }
213                 catch (SharedGroupException e) {
214                     // Do nothing. We shouldn't have this exception since we disabled the checkings
215                     Log.warn( "Unexpected exception while deleting roster of user '{}' .", user, e );
216                 }
217                 catch (UserNotFoundException e) {
218                     // Deleted user had user that no longer exists on their roster. Ignore and move on.
219                 }
220             }
221         }
222         catch (UnsupportedOperationException | UserNotFoundException e) {
223             // Do nothing
224         }
225     }
226 
227     /**
228      * Returns a collection with all the groups that the user may include in his roster. The
229      * following criteria will be used to select the groups: 1) Groups that are configured so that
230      * everybody can include in his roster, 2) Groups that are configured so that its users may
231      * include the group in their rosters and the user is a group user of the group and 3) User
232      * belongs to a Group that may see a Group that whose members may include the Group in their
233      * rosters.
234      *
235      * @param username the username of the user to return his shared groups.
236      * @return a collection with all the groups that the user may include in his roster.
237      */
getSharedGroups(String username)238     public Collection<Group> getSharedGroups(String username) {
239         Collection<Group> answer = new HashSet<>();
240         Collection<Group> groups = GroupManager.getInstance().getSharedGroups(username);
241         for (Group group : groups) {
242             String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
243             if ("onlyGroup".equals(showInRoster)) {
244                 if (group.isUser(username)) {
245                     // The user belongs to the group so add the group to the answer
246                     answer.add(group);
247                 }
248                 else {
249                     // Check if the user belongs to a group that may see this group
250                     Collection<Group> groupList = parseGroups(group.getProperties().get("sharedRoster.groupList"));
251                     for (Group groupInList : groupList) {
252                         if (groupInList.isUser(username)) {
253                             answer.add(group);
254                         }
255                     }
256                 }
257             }
258             else if ("everybody".equals(showInRoster)) {
259                 // Anyone can see this group so add the group to the answer
260                 answer.add(group);
261             }
262         }
263         return answer;
264     }
265 
266     /**
267      * Returns the list of shared groups whose visibility is public.
268      *
269      * @return the list of shared groups whose visibility is public.
270      */
getPublicSharedGroups()271     public Collection<Group> getPublicSharedGroups() {
272         return GroupManager.getInstance().getPublicSharedGroups();
273     }
274 
275     /**
276      * Returns a collection of Groups obtained by parsing a comma delimited String with the name
277      * of groups.
278      *
279      * @param groupNames a comma delimited string with group names.
280      * @return a collection of Groups obtained by parsing a comma delimited String with the name
281      *         of groups.
282      */
parseGroups(String groupNames)283     private Collection<Group> parseGroups(String groupNames) {
284         Collection<Group> answer = new HashSet<>();
285         for (String groupName : parseGroupNames(groupNames)) {
286             try {
287                 answer.add(GroupManager.getInstance().getGroup(groupName));
288             }
289             catch (GroupNotFoundException e) {
290                 // Do nothing. Silently ignore the invalid reference to the group
291             }
292         }
293         return answer;
294     }
295 
296     /**
297      * Returns a collection of Groups obtained by parsing a comma delimited String with the name
298      * of groups.
299      *
300      * @param groupNames a comma delimited string with group names.
301      * @return a collection of Groups obtained by parsing a comma delimited String with the name
302      *         of groups.
303      */
parseGroupNames(String groupNames)304     private static Collection<String> parseGroupNames(String groupNames) {
305         Collection<String> answer = new HashSet<>();
306         if (groupNames != null) {
307             StringTokenizer tokenizer = new StringTokenizer(groupNames, ",");
308             while (tokenizer.hasMoreTokens()) {
309                 answer.add(tokenizer.nextToken());
310             }
311         }
312         return answer;
313     }
314 
315     @Override
groupCreated(Group group, Map params)316     public void groupCreated(Group group, Map params) {
317         //Do nothing
318     }
319 
320     @Override
groupDeleting(Group group, Map params)321     public void groupDeleting(Group group, Map params) {
322         // Get group members
323         Collection<JID> users = new HashSet<>(group.getMembers());
324         users.addAll(group.getAdmins());
325         // Get users whose roster will be updated
326         Collection<JID> affectedUsers = getAffectedUsers(group);
327         // Iterate on group members and update rosters of affected users
328         for (JID deletedUser : users) {
329             groupUserDeleted(group, affectedUsers, deletedUser);
330         }
331     }
332 
333     @Override
groupModified(final Group group, Map params)334     public void groupModified(final Group group, Map params) {
335         // Do nothing if no group property has been modified
336         if ("propertyDeleted".equals(params.get("type"))) {
337              return;
338         }
339         String keyChanged = (String) params.get("propertyKey");
340         String originalValue = (String) params.get("originalValue");
341 
342 
343         if ("sharedRoster.showInRoster".equals(keyChanged)) {
344             String currentValue = group.getProperties().get("sharedRoster.showInRoster");
345             // Nothing has changed so do nothing.
346             if (currentValue.equals(originalValue)) {
347                 return;
348             }
349             // Get the users of the group
350             final Collection<JID> users = new HashSet<>(group.getMembers());
351             users.addAll(group.getAdmins());
352             // Get the users whose roster will be affected
353             final Collection<JID> affectedUsers = getAffectedUsers(group, originalValue,
354                     group.getProperties().get("sharedRoster.groupList"));
355 
356             // Simulate that the group users has been added to the group. This will cause to push
357             // roster items to the "affected" users for the group users
358 
359             executor.submit(new Callable<Boolean>()
360             {
361                 public Boolean call() throws Exception
362                 {
363                     // Remove the group members from the affected rosters
364                     for (JID deletedUser : users) {
365                         groupUserDeleted(group, affectedUsers, deletedUser);
366                     }
367 
368                     // Simulate that the group users has been added to the group. This will cause to push
369                     // roster items to the "affected" users for the group users
370 
371                     for (JID user : users) {
372                         groupUserAdded(group, user);
373                     }
374                     return true;
375                 }
376             });
377         }
378         else if ("sharedRoster.groupList".equals(keyChanged)) {
379             String currentValue = group.getProperties().get("sharedRoster.groupList");
380             // Nothing has changed so do nothing.
381             if (currentValue.equals(originalValue)) {
382                 return;
383             }
384             // Get the users of the group
385             final Collection<JID> users = new HashSet<>(group.getMembers());
386             users.addAll(group.getAdmins());
387             // Get the users whose roster will be affected
388             final Collection<JID> affectedUsers = getAffectedUsers(group,
389                     group.getProperties().get("sharedRoster.showInRoster"), originalValue);
390 
391             executor.submit(new Callable<Boolean>()
392             {
393                 public Boolean call() throws Exception
394                 {
395                     // Remove the group members from the affected rosters
396 
397                     for (JID deletedUser : users) {
398                         groupUserDeleted(group, affectedUsers, deletedUser);
399                     }
400 
401                     // Simulate that the group users has been added to the group. This will cause to push
402                     // roster items to the "affected" users for the group users
403 
404                     for (JID user : users) {
405                         groupUserAdded(group, user);
406                     }
407                     return true;
408                 }
409             });
410         }
411         else if ("sharedRoster.displayName".equals(keyChanged)) {
412             String currentValue = group.getProperties().get("sharedRoster.displayName");
413             // Nothing has changed so do nothing.
414             if (currentValue.equals(originalValue)) {
415                 return;
416             }
417             // Do nothing if the group is not being shown in users' rosters
418             if (!isSharedGroup(group)) {
419                 return;
420             }
421             // Get all the affected users
422             Collection<JID> users = getAffectedUsers(group);
423             // Iterate on all the affected users and update their rosters
424             for (JID updatedUser : users) {
425                 // Get the roster to update.
426                 Roster roster = null;
427                 if (server.isLocal(updatedUser)) {
428                     roster = rosterCache.get(updatedUser.getNode());
429                 }
430                 if (roster != null) {
431                     // Update the roster with the new group display name
432                     roster.shareGroupRenamed(users);
433                 }
434             }
435         }
436     }
437 
438     @Override
initialize(XMPPServer server)439     public void initialize(XMPPServer server) {
440         super.initialize(server);
441         this.server = server;
442         this.routingTable = server.getRoutingTable();
443 
444         RosterEventDispatcher.addListener(new RosterEventListener() {
445             @Override
446             public void rosterLoaded(Roster roster) {
447                 // Do nothing
448             }
449 
450             @Override
451             public boolean addingContact(Roster roster, RosterItem item, boolean persistent) {
452                 // Do nothing
453                 return true;
454             }
455 
456             @Override
457             public void contactAdded(Roster roster, RosterItem item) {
458                 // Set object again in cache. This is done so that other cluster nodes
459                 // get refreshed with latest version of the object
460                 rosterCache.put(roster.getUsername(), roster);
461             }
462 
463             @Override
464             public void contactUpdated(Roster roster, RosterItem item) {
465                 // Set object again in cache. This is done so that other cluster nodes
466                 // get refreshed with latest version of the object
467                 rosterCache.put(roster.getUsername(), roster);
468             }
469 
470             @Override
471             public void contactDeleted(Roster roster, RosterItem item) {
472                 // Set object again in cache. This is done so that other cluster nodes
473                 // get refreshed with latest version of the object
474                 rosterCache.put(roster.getUsername(), roster);
475             }
476         });
477     }
478 
479     /**
480      * Returns true if the specified Group may be included in a user roster. The decision is made
481      * based on the group properties that are configurable through the Admin Console.
482      *
483      * @param group the group to check if it may be considered a shared group.
484      * @return true if the specified Group may be included in a user roster.
485      */
isSharedGroup(Group group)486     public static boolean isSharedGroup(Group group) {
487         String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
488         if ("onlyGroup".equals(showInRoster) || "everybody".equals(showInRoster)) {
489             return true;
490         }
491         return false;
492     }
493 
494     /**
495      * Returns true if the specified Group may be seen by all users in the system. The decision
496      * is made based on the group properties that are configurable through the Admin Console.
497      *
498      * @param group the group to check if it may be seen by all users in the system.
499      * @return true if the specified Group may be seen by all users in the system.
500      */
isPublicSharedGroup(Group group)501     public static boolean isPublicSharedGroup(Group group) {
502         String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
503         if ("everybody".equals(showInRoster)) {
504             return true;
505         }
506         return false;
507     }
508 
509     @Override
memberAdded(Group group, Map params)510     public void memberAdded(Group group, Map params) {
511         JID addedUser = new JID((String) params.get("member"));
512         // Do nothing if the user was an admin that became a member
513         if (group.getAdmins().contains(addedUser)) {
514             return;
515         }
516         if (!isSharedGroup(group)) {
517             for (Group visibleGroup : getVisibleGroups(group)) {
518                 // Get the list of affected users
519                 Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
520                 users.addAll(visibleGroup.getAdmins());
521                 groupUserAdded(visibleGroup, users, addedUser);
522             }
523         }
524         else {
525             groupUserAdded(group, addedUser);
526         }
527     }
528 
529     @Override
memberRemoved(Group group, Map params)530     public void memberRemoved(Group group, Map params) {
531         String member = (String) params.get("member");
532         if (member == null) {
533             return;
534         }
535         JID deletedUser = new JID(member);
536         // Do nothing if the user is still an admin
537         if (group.getAdmins().contains(deletedUser)) {
538             return;
539         }
540         if (!isSharedGroup(group)) {
541             for (Group visibleGroup : getVisibleGroups(group)) {
542                 // Get the list of affected users
543                 Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
544                 users.addAll(visibleGroup.getAdmins());
545                 groupUserDeleted(visibleGroup, users, deletedUser);
546             }
547         }
548         else {
549             groupUserDeleted(group, deletedUser);
550         }
551     }
552 
553     @Override
adminAdded(Group group, Map params)554     public void adminAdded(Group group, Map params) {
555         JID addedUser = new JID((String) params.get("admin"));
556         // Do nothing if the user was a member that became an admin
557         if (group.getMembers().contains(addedUser)) {
558             return;
559         }
560         if (!isSharedGroup(group)) {
561             for (Group visibleGroup : getVisibleGroups(group)) {
562                 // Get the list of affected users
563                 Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
564                 users.addAll(visibleGroup.getAdmins());
565                 groupUserAdded(visibleGroup, users, addedUser);
566             }
567         }
568         else {
569             groupUserAdded(group, addedUser);
570         }
571     }
572 
573     @Override
adminRemoved(Group group, Map params)574     public void adminRemoved(Group group, Map params) {
575         JID deletedUser = new JID((String) params.get("admin"));
576         // Do nothing if the user is still a member
577         if (group.getMembers().contains(deletedUser)) {
578             return;
579         }
580         // Do nothing if the group is not being shown in group members' rosters
581         if (!isSharedGroup(group)) {
582             for (Group visibleGroup : getVisibleGroups(group)) {
583                 // Get the list of affected users
584                 Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
585                 users.addAll(visibleGroup.getAdmins());
586                 groupUserDeleted(visibleGroup, users, deletedUser);
587             }
588         }
589         else {
590             groupUserDeleted(group, deletedUser);
591         }
592     }
593 
594     /**
595      * A new user has been created so members of public shared groups need to have
596      * their rosters updated. Members of public shared groups need to have a roster
597      * item with subscription FROM for the new user since the new user can see them.
598      *
599      * @param newUser the newly created user.
600      * @param params event parameters.
601      */
602     @Override
userCreated(User newUser, Map<String,Object> params)603     public void userCreated(User newUser, Map<String,Object> params) {
604         JID newUserJID = server.createJID(newUser.getUsername(), null);
605         // Shared public groups that are public should have a presence subscription
606         // of type FROM for the new user
607         for (Group group : getPublicSharedGroups()) {
608             // Get group members of public group
609             Collection<JID> users = new HashSet<>(group.getMembers());
610             users.addAll(group.getAdmins());
611             // Update the roster of each group member to include a subscription of type FROM
612             for (JID userToUpdate : users) {
613                 // Get the roster to update
614                 Roster roster = null;
615                 if (server.isLocal(userToUpdate)) {
616                     // Check that the user exists, if not then continue with the next user
617                     try {
618                         UserManager.getInstance().getUser(userToUpdate.getNode());
619                     }
620                     catch (UserNotFoundException e) {
621                         continue;
622                     }
623                     roster = rosterCache.get(userToUpdate.getNode());
624                 }
625                 // Only update rosters in memory
626                 if (roster != null) {
627                     roster.addSharedUser(group, newUserJID);
628                 }
629                 if (!server.isLocal(userToUpdate)) {
630                     // Susbcribe to the presence of the remote user. This is only necessary for
631                     // remote users and may only work with remote users that **automatically**
632                     // accept presence subscription requests
633                     sendSubscribeRequest(newUserJID, userToUpdate, true);
634                 }
635             }
636         }
637     }
638 
639     @Override
userDeleting(User user, Map<String,Object> params)640     public void userDeleting(User user, Map<String,Object> params) {
641         // Shared public groups that have a presence subscription of type FROM
642         // for the deleted user should no longer have a reference to the deleted user
643         JID userJID = server.createJID(user.getUsername(), null);
644         // Shared public groups that are public should have a presence subscription
645         // of type FROM for the new user
646         for (Group group : getPublicSharedGroups()) {
647             // Get group members of public group
648             Collection<JID> users = new HashSet<>(group.getMembers());
649             users.addAll(group.getAdmins());
650             // Update the roster of each group member to include a subscription of type FROM
651             for (JID userToUpdate : users) {
652                 // Get the roster to update
653                 Roster roster = null;
654                 if (server.isLocal(userToUpdate)) {
655                     // Check that the user exists, if not then continue with the next user
656                     try {
657                         UserManager.getInstance().getUser(userToUpdate.getNode());
658                     }
659                     catch (UserNotFoundException e) {
660                         continue;
661                     }
662                     roster = rosterCache.get(userToUpdate.getNode());
663                 }
664                 // Only update rosters in memory
665                 if (roster != null) {
666                     roster.deleteSharedUser(group, userJID);
667                 }
668                 if (!server.isLocal(userToUpdate)) {
669                     // Unsusbcribe from the presence of the remote user. This is only necessary for
670                     // remote users and may only work with remote users that **automatically**
671                     // accept presence subscription requests
672                     sendSubscribeRequest(userJID, userToUpdate, false);
673                 }
674             }
675         }
676 
677         deleteRoster(userJID);
678     }
679 
680     @Override
userModified(User user, Map<String,Object> params)681     public void userModified(User user, Map<String,Object> params) {
682         if ("nameModified".equals(params.get("type"))) {
683 
684             for (Group group : getSharedGroups(user.getUsername())) {
685                 ArrayList<JID> groupUsers = new ArrayList<>();
686                 groupUsers.addAll(group.getAdmins());
687                 groupUsers.addAll(group.getMembers());
688 
689                 for (JID groupUser : groupUsers) {
690                     rosterCache.remove(groupUser.getNode());
691                 }
692             }
693         }
694     }
695 
696     /**
697      * Notification that a Group user has been added. Update the group users' roster accordingly.
698      *
699      * @param group the group where the user was added.
700      * @param addedUser the username of the user that has been added to the group.
701      */
groupUserAdded(Group group, JID addedUser)702     private void groupUserAdded(Group group, JID addedUser) {
703         groupUserAdded(group, getAffectedUsers(group), addedUser);
704     }
705 
706     /**
707      * Notification that a Group user has been added. Update the group users' roster accordingly.
708      *
709      * @param group the group where the user was added.
710      * @param users the users to update their rosters
711      * @param addedUser the username of the user that has been added to the group.
712      */
groupUserAdded(Group group, Collection<JID> users, JID addedUser)713     private void groupUserAdded(Group group, Collection<JID> users, JID addedUser) {
714         // Get the roster of the added user.
715         Roster addedUserRoster = null;
716         if (server.isLocal(addedUser)) {
717             addedUserRoster = rosterCache.get(addedUser.getNode());
718         }
719 
720         // Iterate on all the affected users and update their rosters
721         for (JID userToUpdate : users) {
722             if (!addedUser.equals(userToUpdate)) {
723                 // Get the roster to update
724                 Roster roster = null;
725                 if (server.isLocal(userToUpdate)) {
726                     // Check that the user exists, if not then continue with the next user
727                     try {
728                         UserManager.getInstance().getUser(userToUpdate.getNode());
729                     }
730                     catch (UserNotFoundException e) {
731                         continue;
732                     }
733                     roster = rosterCache.get(userToUpdate.getNode());
734                 }
735                 // Only update rosters in memory
736                 if (roster != null) {
737                     roster.addSharedUser(group, addedUser);
738                 }
739                 // Check if the roster is still not in memory
740                 if (addedUserRoster == null && server.isLocal(addedUser)) {
741                     addedUserRoster =
742                             rosterCache.get(addedUser.getNode());
743                 }
744                 // Update the roster of the newly added group user.
745                 if (addedUserRoster != null) {
746                     Collection<Group> groups = GroupManager.getInstance().getGroups(userToUpdate);
747                     addedUserRoster.addSharedUser(userToUpdate, groups, group);
748                 }
749                 if (!server.isLocal(addedUser)) {
750                     // Susbcribe to the presence of the remote user. This is only necessary for
751                     // remote users and may only work with remote users that **automatically**
752                     // accept presence subscription requests
753                     sendSubscribeRequest(userToUpdate, addedUser, true);
754                 }
755                 if (!server.isLocal(userToUpdate)) {
756                     // Susbcribe to the presence of the remote user. This is only necessary for
757                     // remote users and may only work with remote users that **automatically**
758                     // accept presence subscription requests
759                     sendSubscribeRequest(addedUser, userToUpdate, true);
760                 }
761             }
762         }
763     }
764 
765     /**
766      * Notification that a Group user has been deleted. Update the group users' roster accordingly.
767      *
768      * @param group the group from where the user was deleted.
769      * @param deletedUser the username of the user that has been deleted from the group.
770      */
groupUserDeleted(Group group, JID deletedUser)771     private void groupUserDeleted(Group group, JID deletedUser) {
772         groupUserDeleted(group, getAffectedUsers(group), deletedUser);
773     }
774 
775     /**
776      * Notification that a Group user has been deleted. Update the group users' roster accordingly.
777      *
778      * @param group the group from where the user was deleted.
779      * @param users the users to update their rosters
780      * @param deletedUser the username of the user that has been deleted from the group.
781      */
groupUserDeleted(Group group, Collection<JID> users, JID deletedUser)782     private void groupUserDeleted(Group group, Collection<JID> users, JID deletedUser) {
783         // Get the roster of the deleted user.
784         Roster deletedUserRoster = null;
785         if (server.isLocal(deletedUser)) {
786             deletedUserRoster = rosterCache.get(deletedUser.getNode());
787         }
788 
789         // Iterate on all the affected users and update their rosters
790         for (JID userToUpdate : users) {
791             // Get the roster to update
792             Roster roster = null;
793             if (server.isLocal(userToUpdate)) {
794                 // Check that the user exists, if not then continue with the next user
795                 try {
796                     UserManager.getInstance().getUser(userToUpdate.getNode());
797                 }
798                 catch (UserNotFoundException e) {
799                     continue;
800                 }
801                 roster = rosterCache.get(userToUpdate.getNode());
802             }
803             // Only update rosters in memory
804             if (roster != null) {
805                 roster.deleteSharedUser(group, deletedUser);
806             }
807             // Check if the roster is still not in memory
808             if (deletedUserRoster == null && server.isLocal(deletedUser)) {
809                 deletedUserRoster =
810                         rosterCache.get(deletedUser.getNode());
811             }
812             // Update the roster of the newly deleted group user.
813             if (deletedUserRoster != null) {
814                 deletedUserRoster.deleteSharedUser(userToUpdate, group);
815             }
816             if (!server.isLocal(deletedUser)) {
817                 // Unsusbcribe from the presence of the remote user. This is only necessary for
818                 // remote users and may only work with remote users that **automatically**
819                 // accept presence subscription requests
820                 sendSubscribeRequest(userToUpdate, deletedUser, false);
821             }
822             if (!server.isLocal(userToUpdate)) {
823                 // Unsusbcribe from the presence of the remote user. This is only necessary for
824                 // remote users and may only work with remote users that **automatically**
825                 // accept presence subscription requests
826                 sendSubscribeRequest(deletedUser, userToUpdate, false);
827             }
828         }
829     }
830 
sendSubscribeRequest(JID sender, JID recipient, boolean isSubscribe)831     private void sendSubscribeRequest(JID sender, JID recipient, boolean isSubscribe) {
832         Presence presence = new Presence();
833         presence.setFrom(sender);
834         presence.setTo(recipient);
835         if (isSubscribe) {
836             presence.setType(Presence.Type.subscribe);
837         }
838         else {
839             presence.setType(Presence.Type.unsubscribe);
840         }
841         routingTable.routePacket(recipient, presence, false);
842     }
843 
getVisibleGroups(Group groupToCheck)844     private Collection<Group> getVisibleGroups(Group groupToCheck) {
845         return GroupManager.getInstance().getVisibleGroups(groupToCheck);
846     }
847 
848     /**
849      * Returns true if a given group is visible to a given user. That means, if the user can
850      * see the group in his roster.
851      *
852      * @param group the group to check if the user can see.
853      * @param user the JID of the user to check if he may see the group.
854      * @return true if a given group is visible to a given user.
855      */
isGroupVisible(Group group, JID user)856     public boolean isGroupVisible(Group group, JID user) {
857         String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
858         if ("everybody".equals(showInRoster)) {
859             return true;
860         }
861         else if ("onlyGroup".equals(showInRoster)) {
862             if (group.isUser(user)) {
863                  return true;
864             }
865             // Check if the user belongs to a group that may see this group
866             Collection<Group> groupList = parseGroups(group.getProperties().get(
867                     "sharedRoster.groupList"));
868             for (Group groupInList : groupList) {
869                 if (groupInList.isUser(user)) {
870                     return true;
871                 }
872             }
873         }
874         return false;
875     }
876 
877     /**
878      * Returns all the users that are related to a shared group. This is the logic that we are
879      * using: 1) If the group visiblity is configured as "Everybody" then all users in the system or
880      * all logged users in the system will be returned (configurable thorugh the "filterOffline"
881      * flag), 2) if the group visiblity is configured as "onlyGroup" then all the group users will
882      * be included in the answer and 3) if the group visiblity is configured as "onlyGroup" and
883      * the group allows other groups to include the group in the groups users' roster then all
884      * the users of the allowed groups will be included in the answer.
885      */
getAffectedUsers(Group group)886     private Collection<JID> getAffectedUsers(Group group) {
887         return getAffectedUsers(group, group.getProperties().get("sharedRoster.showInRoster"),
888                 group.getProperties().get("sharedRoster.groupList"));
889     }
890 
891     /**
892      * This method is similar to {@link #getAffectedUsers(Group)} except that it receives
893      * some group properties. The group properties are passed as parameters since the called of this
894      * method may want to obtain the related users of the group based in some properties values.
895      *
896      * This is useful when the group is being edited and some properties has changed and we need to
897      * obtain the related users of the group based on the previous group state.
898      */
getAffectedUsers(Group group, String showInRoster, String groupNames)899     private Collection<JID> getAffectedUsers(Group group, String showInRoster, String groupNames) {
900         // Answer an empty collection if the group is not being shown in users' rosters
901         if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) {
902             return new ArrayList<>();
903         }
904         // Add the users of the group
905         Collection<JID> users = new HashSet<>(group.getMembers());
906         users.addAll(group.getAdmins());
907         // Check if anyone can see this shared group
908         if ("everybody".equals(showInRoster)) {
909             // Add all users in the system
910             for (String username : UserManager.getInstance().getUsernames()) {
911                 users.add(server.createJID(username, null, true));
912             }
913             // Add all logged users. We don't need to add all users in the system since only the
914             // logged ones will be affected.
915             //users.addAll(SessionManager.getInstance().getSessionUsers());
916         }
917         else {
918             // Add the users that may see the group
919             Collection<Group> groupList = parseGroups(groupNames);
920             for (Group groupInList : groupList) {
921                 users.addAll(groupInList.getMembers());
922                 users.addAll(groupInList.getAdmins());
923             }
924         }
925         return users;
926     }
927 
getSharedUsersForRoster(Group group, Roster roster)928     Collection<JID> getSharedUsersForRoster(Group group, Roster roster) {
929         String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
930         String groupNames = group.getProperties().get("sharedRoster.groupList");
931 
932         // Answer an empty collection if the group is not being shown in users' rosters
933         if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) {
934             return new ArrayList<>();
935         }
936 
937         // Add the users of the group
938         Collection<JID> users = new HashSet<>(group.getMembers());
939         users.addAll(group.getAdmins());
940 
941         // If the user of the roster belongs to the shared group then we should return
942         // users that need to be in the roster with subscription "from"
943         if (group.isUser(roster.getUsername())) {
944             // Check if anyone can see this shared group
945             if ("everybody".equals(showInRoster)) {
946                 // Add all users in the system
947                 for (String username : UserManager.getInstance().getUsernames()) {
948                     users.add(server.createJID(username, null, true));
949                 }
950             }
951             else {
952                 // Add the users that may see the group
953                 Collection<Group> groupList = parseGroups(groupNames);
954                 for (Group groupInList : groupList) {
955                     users.addAll(groupInList.getMembers());
956                     users.addAll(groupInList.getAdmins());
957                 }
958             }
959         }
960         return users;
961     }
962 
963     /**
964      * Returns true if a group in the first collection may mutually see a group of the
965      * second collection. More precisely, return true if both collections contain a public
966      * group (i.e. anybody can see the group) or if both collection have a group that may see
967      * each other and the users are members of those groups or if one group is public and the
968      * other group allowed the public group to see it.
969      *
970      * @param user the name of the user associated to the first collection of groups. This is always a local user.
971      * @param groups a collection of groups to check against the other collection of groups.
972      * @param otherUser the JID of the user associated to the second collection of groups.
973      * @param otherGroups the other collection of groups to check against the first collection.
974      * @return true if a group in the first collection may mutually see a group of the
975      *         second collection.
976      */
hasMutualVisibility(String user, Collection<Group> groups, JID otherUser, Collection<Group> otherGroups)977     boolean hasMutualVisibility(String user, Collection<Group> groups, JID otherUser,
978             Collection<Group> otherGroups) {
979         for (Group group : groups) {
980             for (Group otherGroup : otherGroups) {
981                 // Skip this groups if the users are not group users of the groups
982                 if (!group.isUser(user) || !otherGroup.isUser(otherUser)) {
983                     continue;
984                 }
985                 if (group.equals(otherGroup)) {
986                      return true;
987                 }
988                 String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
989                 String otherShowInRoster = otherGroup.getProperties().get("sharedRoster.showInRoster");
990                 // Return true if both groups are public groups (i.e. anybody can see them)
991                 if ("everybody".equals(showInRoster) && "everybody".equals(otherShowInRoster)) {
992                     return true;
993                 }
994                 else if ("onlyGroup".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) {
995                     String groupNames = group.getProperties().get("sharedRoster.groupList");
996                     String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList");
997                     // Return true if each group may see the other group
998                     if (groupNames != null && otherGroupNames != null) {
999                         if (groupNames.contains(otherGroup.getName()) &&
1000                                 otherGroupNames.contains(group.getName())) {
1001                             return true;
1002                         }
1003                         // Check if each shared group can be seen by a group where each user belongs
1004                         Collection<Group> groupList = parseGroups(groupNames);
1005                         Collection<Group> otherGroupList = parseGroups(otherGroupNames);
1006                         for (Group groupName : groupList) {
1007                             if (groupName.isUser(otherUser)) {
1008                                 for (Group otherGroupName : otherGroupList) {
1009                                     if (otherGroupName.isUser(user)) {
1010                                         return true;
1011                                     }
1012                                 }
1013                             }
1014                         }
1015                     }
1016                 }
1017                 else if ("everybody".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) {
1018                     // Return true if one group is public and the other group allowed the public
1019                     // group to see him
1020                     String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList");
1021                     if (otherGroupNames != null && otherGroupNames.contains(group.getName())) {
1022                             return true;
1023                     }
1024                 }
1025                 else if ("onlyGroup".equals(showInRoster) && "everybody".equals(otherShowInRoster)) {
1026                     // Return true if one group is public and the other group allowed the public
1027                     // group to see him
1028                     String groupNames = group.getProperties().get("sharedRoster.groupList");
1029                     // Return true if each group may see the other group
1030                     if (groupNames != null && groupNames.contains(otherGroup.getName())) {
1031                             return true;
1032                     }
1033                 }
1034             }
1035         }
1036         return false;
1037     }
1038 
1039     @Override
start()1040     public void start() throws IllegalStateException {
1041         super.start();
1042 
1043         // Make the GroupManager listeners be registered first
1044         GroupManager.getInstance();
1045 
1046         // Add this module as a user event listener so we can update
1047         // rosters when users are created or deleted
1048         UserEventDispatcher.addListener(this);
1049         // Add the new instance as a listener of group events
1050         GroupEventDispatcher.addListener(this);
1051 
1052         executor = new ThreadPoolExecutor(
1053             EXECUTOR_CORE_POOL_SIZE.getValue(),
1054             EXECUTOR_MAX_POOL_SIZE.getValue(),
1055             EXECUTOR_POOL_KEEP_ALIVE.getValue().getSeconds(), // TODO: replace with 'toSeconds()' when no longer supporting Java 8.
1056             TimeUnit.SECONDS,
1057             new SynchronousQueue<>(),
1058             new NamedThreadFactory( "roster-worker-", null, null, null ) );
1059 
1060         if (JMXManager.isEnabled()) {
1061             final ThreadPoolExecutorDelegateMBean mBean = new ThreadPoolExecutorDelegate(executor);
1062             objectName = JMXManager.tryRegister(mBean, ThreadPoolExecutorDelegateMBean.BASE_OBJECT_NAME + "roster");
1063         }
1064     }
1065 
1066     @Override
stop()1067     public void stop() {
1068         super.stop();
1069         // Remove this module as a user event listener
1070         UserEventDispatcher.removeListener(this);
1071         // Remove this module as a listener of group events
1072         GroupEventDispatcher.removeListener(this);
1073         if (objectName != null) {
1074             JMXManager.tryUnregister(objectName);
1075             objectName = null;
1076         }
1077         executor.shutdown();
1078     }
1079 
getRosterItemProvider()1080     public static RosterItemProvider getRosterItemProvider() {
1081         return XMPPServer.getInstance().getRosterManager().provider;
1082     }
1083 
initProvider()1084     private void initProvider() {
1085         JiveGlobals.migrateProperty("provider.roster.className");
1086         String className = JiveGlobals.getProperty("provider.roster.className",
1087                 "org.jivesoftware.openfire.roster.DefaultRosterItemProvider");
1088 
1089         if (provider == null || !className.equals(provider.getClass().getName())) {
1090             try {
1091                 Class c = ClassUtils.forName(className);
1092                 provider = (RosterItemProvider) c.newInstance();
1093             }
1094             catch (Exception e) {
1095                 Log.error("Error loading roster provider: " + className, e);
1096                 provider = new DefaultRosterItemProvider();
1097             }
1098         }
1099 
1100     }
1101 
1102 }
1103