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