001: /**
002: * $RCSfile: IQRosterHandler.java,v $
003: * $Revision: 3163 $
004: * $Date: 2005-12-05 17:54:23 -0300 (Mon, 05 Dec 2005) $
005: *
006: * Copyright (C) 2004 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.openfire.handler;
011:
012: import org.jivesoftware.stringprep.IDNAException;
013: import org.jivesoftware.util.LocaleUtils;
014: import org.jivesoftware.util.Log;
015: import org.jivesoftware.openfire.*;
016: import org.jivesoftware.openfire.auth.UnauthorizedException;
017: import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
018: import org.jivesoftware.openfire.roster.Roster;
019: import org.jivesoftware.openfire.roster.RosterItem;
020: import org.jivesoftware.openfire.roster.RosterManager;
021: import org.jivesoftware.openfire.user.UserAlreadyExistsException;
022: import org.jivesoftware.openfire.user.UserManager;
023: import org.jivesoftware.openfire.user.UserNotFoundException;
024: import org.xmpp.packet.IQ;
025: import org.xmpp.packet.JID;
026: import org.xmpp.packet.Packet;
027: import org.xmpp.packet.PacketError;
028:
029: import java.util.ArrayList;
030: import java.util.Iterator;
031:
032: /**
033: * Implements the TYPE_IQ jabber:iq:roster protocol. Clients
034: * use this protocol to retrieve, update, and rosterMonitor roster
035: * entries (buddy lists). The server manages the basics of
036: * roster subscriptions and roster updates based on presence
037: * and iq:roster packets, while the client maintains the user
038: * interface aspects of rosters such as organizing roster
039: * entries into groups.
040: * <p/>
041: * A 'get' query retrieves a snapshot of the roster.
042: * A 'set' query updates the roster (typically with new group info).
043: * The server sends 'set' updates asynchronously when roster
044: * entries change status.
045: * <p/>
046: * Currently an empty implementation to allow usage with normal
047: * clients. Future implementation needed.
048: * <p/>
049: * <h2>Assumptions</h2>
050: * This handler assumes that the request is addressed to the server.
051: * An appropriate TYPE_IQ tag matcher should be placed in front of this
052: * one to route TYPE_IQ requests not addressed to the server to
053: * another channel (probably for direct delivery to the recipient).
054: * <p/>
055: * <h2>Warning</h2>
056: * There should be a way of determining whether a session has
057: * authorization to access this feature. I'm not sure it is a good
058: * idea to do authorization in each handler. It would be nice if
059: * the framework could assert authorization policies across channels.
060: *
061: * @author Iain Shigeoka
062: */
063: public class IQRosterHandler extends IQHandler implements
064: ServerFeaturesProvider {
065:
066: private IQHandlerInfo info;
067:
068: private UserManager userManager;
069: private XMPPServer localServer;
070: private SessionManager sessionManager;
071: private PacketRouter router;
072:
073: public IQRosterHandler() {
074: super ("XMPP Roster Handler");
075: info = new IQHandlerInfo("query", "jabber:iq:roster");
076: }
077:
078: /**
079: * Handles all roster queries. There are two major types of queries:
080: *
081: * <ul>
082: * <li>Roster remove - A forced removal of items from a roster. Roster
083: * removals are the only roster queries allowed to
084: * directly affect the roster from another user.
085: * </li>
086: * <li>Roster management - A local user looking up or updating their
087: * roster.
088: * </li>
089: * </ul>
090: *
091: * @param packet The update packet
092: * @return The reply or null if no reply
093: */
094: public IQ handleIQ(IQ packet) throws UnauthorizedException,
095: PacketException {
096: try {
097: IQ returnPacket = null;
098: org.xmpp.packet.Roster roster = (org.xmpp.packet.Roster) packet;
099:
100: JID recipientJID = packet.getTo();
101:
102: // The packet is bound for the server and must be roster management
103: if (recipientJID == null
104: || recipientJID.getNode() == null
105: || !UserManager.getInstance().isRegisteredUser(
106: recipientJID.getNode())) {
107: returnPacket = manageRoster(roster);
108: }
109: // The packet must be a roster removal from a foreign domain user.
110: else {
111: removeRosterItem(roster);
112: }
113: return returnPacket;
114: } catch (SharedGroupException e) {
115: IQ result = IQ.createResultIQ(packet);
116: result.setChildElement(packet.getChildElement()
117: .createCopy());
118: result.setError(PacketError.Condition.not_acceptable);
119: return result;
120: } catch (Exception e) {
121: if (e.getCause() instanceof IDNAException) {
122: Log.warn(LocaleUtils.getLocalizedString("admin.error"),
123: e);
124: IQ result = IQ.createResultIQ(packet);
125: result.setChildElement(packet.getChildElement()
126: .createCopy());
127: result.setError(PacketError.Condition.jid_malformed);
128: return result;
129: } else {
130: Log.error(
131: LocaleUtils.getLocalizedString("admin.error"),
132: e);
133: IQ result = IQ.createResultIQ(packet);
134: result.setChildElement(packet.getChildElement()
135: .createCopy());
136: result
137: .setError(PacketError.Condition.internal_server_error);
138: return result;
139: }
140: }
141: }
142:
143: /**
144: * Remove a roster item. At this stage, this is recipient who has received
145: * a roster update. We must check that it is a removal, and if so, remove
146: * the roster item based on the sender's id rather than what is in the item
147: * listing itself.
148: *
149: * @param packet The packet suspected of containing a roster removal
150: */
151: private void removeRosterItem(org.xmpp.packet.Roster packet)
152: throws UnauthorizedException, SharedGroupException {
153: JID recipientJID = packet.getTo();
154: JID senderJID = packet.getFrom();
155: try {
156: for (org.xmpp.packet.Roster.Item packetItem : packet
157: .getItems()) {
158: if (packetItem.getSubscription() == org.xmpp.packet.Roster.Subscription.remove) {
159: Roster roster = userManager.getUser(
160: recipientJID.getNode()).getRoster();
161: RosterItem item = roster.getRosterItem(senderJID);
162: roster.deleteRosterItem(senderJID, true);
163: item.setSubStatus(RosterItem.SUB_REMOVE);
164: item.setSubStatus(RosterItem.SUB_NONE);
165:
166: Packet itemPacket = packet.createCopy();
167: sessionManager.userBroadcast(
168: recipientJID.getNode(), itemPacket);
169: }
170: }
171: } catch (UserNotFoundException e) {
172: throw new UnauthorizedException(e);
173: }
174: }
175:
176: /**
177: * The packet is a typical 'set' or 'get' update targeted at the server.
178: * Notice that the set could be a roster removal in which case we have to
179: * generate a local roster removal update as well as a new roster removal
180: * to send to the the roster item's owner.
181: *
182: * @param packet The packet that triggered this update
183: * @return Either a response to the roster update or null if the packet is corrupt and the session was closed down
184: */
185: private IQ manageRoster(org.xmpp.packet.Roster packet)
186: throws UnauthorizedException, UserAlreadyExistsException,
187: SharedGroupException {
188:
189: IQ returnPacket = null;
190: JID sender = packet.getFrom();
191: IQ.Type type = packet.getType();
192:
193: try {
194: if ((sender.getNode() == null
195: || !RosterManager.isRosterServiceEnabled() || !userManager
196: .isRegisteredUser(sender.getNode()))
197: && IQ.Type.get == type) {
198: // If anonymous user asks for his roster or roster service is disabled then
199: // return an empty roster
200: IQ reply = IQ.createResultIQ(packet);
201: reply.setChildElement("query", "jabber:iq:roster");
202: return reply;
203: }
204: if (!localServer.isLocal(sender)) {
205: // Sender belongs to a remote server so discard this IQ request
206: Log.warn("Discarding IQ roster packet of remote user: "
207: + packet);
208: return null;
209: }
210:
211: Roster cachedRoster = userManager.getUser(sender.getNode())
212: .getRoster();
213: if (IQ.Type.get == type) {
214: returnPacket = cachedRoster.getReset();
215: returnPacket.setType(IQ.Type.result);
216: returnPacket.setTo(sender);
217: returnPacket.setID(packet.getID());
218: // Force delivery of the response because we need to trigger
219: // a presence probe from all contacts
220: deliverer.deliver(returnPacket);
221: returnPacket = null;
222: } else if (IQ.Type.set == type) {
223:
224: for (org.xmpp.packet.Roster.Item item : packet
225: .getItems()) {
226: if (item.getSubscription() == org.xmpp.packet.Roster.Subscription.remove) {
227: removeItem(cachedRoster, packet.getFrom(), item);
228: } else {
229: if (cachedRoster.isRosterItem(item.getJID())) {
230: // existing item
231: RosterItem cachedItem = cachedRoster
232: .getRosterItem(item.getJID());
233: cachedItem.setAsCopyOf(item);
234: cachedRoster.updateRosterItem(cachedItem);
235: } else {
236: // new item
237: cachedRoster.createRosterItem(item);
238: }
239: }
240: }
241: returnPacket = IQ.createResultIQ(packet);
242: }
243: } catch (UserNotFoundException e) {
244: throw new UnauthorizedException(e);
245: }
246:
247: return returnPacket;
248:
249: }
250:
251: /**
252: * Remove the roster item from the sender's roster (and possibly the recipient's).
253: * Actual roster removal is done in the removeItem(Roster,RosterItem) method.
254: *
255: * @param roster The sender's roster.
256: * @param sender The JID of the sender of the removal request
257: * @param item The removal item element
258: */
259: private void removeItem(
260: org.jivesoftware.openfire.roster.Roster roster, JID sender,
261: org.xmpp.packet.Roster.Item item)
262: throws SharedGroupException {
263: JID recipient = item.getJID();
264: // Remove recipient from the sender's roster
265: roster.deleteRosterItem(item.getJID(), true);
266: // Forward set packet to the subscriber
267: if (localServer.isLocal(recipient)) { // Recipient is local so let's handle it here
268: try {
269: Roster recipientRoster = userManager.getUser(
270: recipient.getNode()).getRoster();
271: recipientRoster.deleteRosterItem(sender, true);
272: } catch (UserNotFoundException e) {
273: // Do nothing
274: }
275: } else {
276: // Recipient is remote so we just forward the packet to them
277: String serverDomain = localServer.getServerInfo().getName();
278: // Check if the recipient may be hosted by this server
279: if (!recipient.getDomain().contains(serverDomain)) {
280: // TODO Implete when s2s is implemented
281: } else {
282: Packet removePacket = createRemoveForward(sender,
283: recipient);
284: router.route(removePacket);
285: }
286: }
287: }
288:
289: /**
290: * Creates a forwarded removal packet.
291: *
292: * @param from The sender address to use
293: * @param to The recipient address to use
294: * @return The forwarded packet generated
295: */
296: private Packet createRemoveForward(JID from, JID to) {
297: org.xmpp.packet.Roster response = new org.xmpp.packet.Roster(
298: IQ.Type.set);
299: response.setFrom(from);
300: response.setTo(to);
301: response.addItem(from,
302: org.xmpp.packet.Roster.Subscription.remove);
303:
304: return response;
305: }
306:
307: public void initialize(XMPPServer server) {
308: super .initialize(server);
309: localServer = server;
310: userManager = server.getUserManager();
311: router = server.getPacketRouter();
312: sessionManager = server.getSessionManager();
313: }
314:
315: public IQHandlerInfo getInfo() {
316: return info;
317: }
318:
319: public Iterator<String> getFeatures() {
320: ArrayList<String> features = new ArrayList<String>();
321: features.add("jabber:iq:roster");
322: return features.iterator();
323: }
324: }
|