/** * Copyright (C) 2004-2009 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire.clearspace; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.dom4j.Attribute; import org.dom4j.Element; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.muc.MUCEventDelegate; import org.jivesoftware.openfire.muc.MUCRoom; import org.jivesoftware.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.IQ; import org.xmpp.packet.JID; import org.xmpp.packet.PacketError; /** * Handles checking with Clearspace regarding whether a user can join a particular MUC room (based * on their permissions with the Clearspace JiveObject (eg. Community/Space) that the room is associated with). * * In addition, this MUCEventDelegate provides a means to obtain room configuration details from Clearspace * in the event that the Clearspace MUC service needs to create a room on-demand (eg. when a user first joins the room). * * @author Armando Jagucki */ public class ClearspaceMUCEventDelegate extends MUCEventDelegate { private static final Logger Log = LoggerFactory.getLogger(ClearspaceMUCEventDelegate.class); private String csMucDomain; private String csComponentAddress; private final String GET_ROOM_CONFIG_WARNING ="Clearspace sent an unexpected reply to a get-room-config request."; public ClearspaceMUCEventDelegate() { String xmppDomain = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); csMucDomain = ClearspaceManager.MUC_SUBDOMAIN + "." + xmppDomain; csComponentAddress = ClearspaceManager.CLEARSPACE_COMPONENT + "." + xmppDomain; } @Override public InvitationResult sendingInvitation(MUCRoom room, JID invitee, JID inviter, String reason) { // Packet should look like: // <iq to="clearspace.example.org" from="clearspace-conference.example.org"> // <room-invite xmlns="http://jivesoftware.com/clearspace"> // <inviter>username@example.org</inviter> // <room>14-1234@clearspace-conference.example.org</roomjid> // <reason>Example Message</reason> // <invitee>anotheruser@example.org</invitee> // </room-invite> // </iq> IQ query = new IQ(); query.setFrom(csMucDomain); Element cmd = query.setChildElement("invite-check", "http://jivesoftware.com/clearspace"); Element inviterjidElement = cmd.addElement("inviter"); inviterjidElement.setText(inviter.toBareJID()); Element inviteejidElement = cmd.addElement("invitee"); inviteejidElement.setText(invitee.toBareJID()); Element roomjidElement = cmd.addElement("room"); roomjidElement.setText(room.getJID().toBareJID()); Element messageElement = cmd.addElement("reason"); messageElement.setText(reason); IQ result = ClearspaceManager.getInstance().query(query, 15000); if (null != result) { if (result.getType() != IQ.Type.error) { // No error, that indicates that we were successful and the user is permitted. return InvitationResult.HANDLED_BY_DELEGATE; } else if(result.getError().getType() == PacketError.Type.continue_processing) { return InvitationResult.HANDLED_BY_OPENFIRE; } } // No successful return, not allowed. return InvitationResult.REJECTED; } /** * Returns true if the user is allowed to join the room. If the userjid is an owner of the room, * we will return true immediately. * * @param room the room the user is attempting to join. * @param userjid the JID of the user attempting to join the room. * @return true if the user is allowed to join the room. */ @Override public boolean joiningRoom(MUCRoom room, JID userjid) { // Always allow an owner to join the room (especially since they need to join to configure the // room on initial creation). Collection<JID> owners = room.getOwners(); if (owners != null && owners.contains(userjid.asBareJID())) { return true; } // Packet should look like: // <iq to="clearspace.example.org" from="clearspace-conference.example.org"> // <join-check xmlns="http://jivesoftware.com/clearspace"> // <userjid>username@example.org</userjid> // <roomjid>14-1234@clearspace-conference.example.org</roomjid> // </join-check> // </iq> IQ query = new IQ(); query.setFrom(csMucDomain); Element cmd = query.setChildElement("join-check", "http://jivesoftware.com/clearspace"); Element userjidElement = cmd.addElement("userjid"); userjidElement.setText(userjid.toBareJID()); Element roomjidElement = cmd.addElement("roomjid"); roomjidElement.setText(room.getJID().toBareJID()); IQ result = ClearspaceManager.getInstance().query(query, 15000); if (result == null) { // No answer was received, assume false for security reasons. if (Log.isDebugEnabled()) { Log.debug("No answer from Clearspace on join-check in ClearspaceMUCEventDelegate. User: " + userjid.toBareJID() + " Room: " + room.getJID().toBareJID()); } return false; } if (result.getType() != IQ.Type.error) { // No error, that indicates that we were successful and the user is permitted. return true; } // No successful return, not allowed. return false; } @Override public boolean shouldRecreate(String roomName, JID userjid) { return !(roomName + "@" + csComponentAddress).equals(userjid.toBareJID()); } @Override public Map<String, String> getRoomConfig(String roomName) { Map<String, String> roomConfig = new HashMap<String, String>(); IQ iq = new IQ(IQ.Type.get); iq.setFrom(csMucDomain); iq.setID("get_room_config_" + StringUtils.randomString(3)); Element child = iq.setChildElement("get-room-config", "http://jivesoftware.com/clearspace"); Element roomjidElement = child.addElement("roomjid"); JID roomJid = new JID(roomName + "@" + csMucDomain); roomjidElement.setText(roomJid.toBareJID()); IQ result = ClearspaceManager.getInstance().query(iq, 15000); if (result == null) { // No answer was received from Clearspace, so return null. Log.warn(GET_ROOM_CONFIG_WARNING + " Room: " + roomJid.toBareJID()); return null; } else if (result.getType() != IQ.Type.result) { // The reply was not a valid result containing the room configuration, so return null. Log.warn(GET_ROOM_CONFIG_WARNING + " Room: " + roomJid.toBareJID()); return null; } // Setup room configuration based on the configuration values in the result packet. Element query = result.getChildElement(); if (query == null) { Log.warn(GET_ROOM_CONFIG_WARNING + " Room: " + roomJid.toBareJID()); return null; } Element xElement = query.element("x"); if (xElement == null) { Log.warn(GET_ROOM_CONFIG_WARNING + " Room: " + roomJid.toBareJID()); return null; } @SuppressWarnings("unchecked") Iterator<Element> fields = xElement.elementIterator("field"); while (fields.hasNext()) { Element field = fields.next(); Attribute varAttribute = field.attribute("var"); if (varAttribute != null) { Element value = field.element("value"); if (value != null) { roomConfig.put(varAttribute.getValue(), value.getText()); } } } String ownerJid = roomJid.getNode() + "@" + csComponentAddress; roomConfig.put("muc#roomconfig_roomowners", ownerJid); return roomConfig; } /** * This event will be triggered when an entity attempts to destroy a room. * <p> * Returns true if the user is allowed to destroy the room.</p> * * @param roomName the name of the MUC room being destroyed. * @param userjid the JID of the user attempting to destroy the room. * @return true if the user is allowed to destroy the room. */ @Override public boolean destroyingRoom(String roomName, JID userjid) { // We never allow destroying a room as a user, but clearspace components are permitted. return ClearspaceManager.getInstance().isFromClearspace(userjid); } }