/* * Copyright (C) 2005-2008 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.privacy; import org.dom4j.Element; import org.jivesoftware.util.cache.CacheSizes; import org.jivesoftware.util.cache.Cacheable; import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.RosterItem; import org.jivesoftware.openfire.user.UserNotFoundException; import org.xmpp.packet.*; import java.util.Collection; import java.util.Collections; /** * A privacy item acts a rule that when matched defines if a packet should be blocked or not. * * @author Gaston Dombiak */ class PrivacyItem implements Cacheable, Comparable { private int order; private boolean allow; private Type type; private JID jidValue; private RosterItem.SubType subscriptionValue; private String groupValue; private boolean filterEverything; private boolean filterIQ; private boolean filterMessage; private boolean filterPresence_in; private boolean filterPresence_out; /** * Copy of the element that defined this item. */ private Element itemElement; PrivacyItem(Element itemElement) { this.allow = "allow".equals(itemElement.attributeValue("action")); this.order = Integer.parseInt(itemElement.attributeValue("order")); String typeAttribute = itemElement.attributeValue("type"); if (typeAttribute != null) { this.type = Type.valueOf(typeAttribute); // Decode the proper value based on the rule type String value = itemElement.attributeValue("value"); if (type == Type.jid) { // Decode the specified JID this.jidValue = new JID(value); } else if (type == Type.subscription) { // Decode the specified subscription type if ("both".equals(value)) { this.subscriptionValue = RosterItem.SUB_BOTH; } else if ("to".equals(value)) { this.subscriptionValue = RosterItem.SUB_TO; } else if ("from".equals(value)) { this.subscriptionValue = RosterItem.SUB_FROM; } else { this.subscriptionValue = RosterItem.SUB_NONE; } } else { // Decode the specified group name this.groupValue = value; } } // Set what type of stanzas should be filters (i.e. blocked or allowed) this.filterIQ = itemElement.element("iq") != null; this.filterMessage = itemElement.element("message") != null; this.filterPresence_in = itemElement.element("presence-in") != null; this.filterPresence_out = itemElement.element("presence-out") != null; if (!filterIQ && !filterMessage && !filterPresence_in && !filterPresence_out) { // If none was defined then block all stanzas filterEverything = true; } // Keep a copy of the item element that defines this item this.itemElement = itemElement.createCopy(); } Element asElement() { return itemElement.createCopy(); } /** * Returns true if this privacy item needs the user roster to figure out * if a packet must be blocked. * * @return true if this privacy item needs the user roster to figure out * if a packet must be blocked. */ boolean isRosterRequired() { return type == Type.group || type == Type.subscription; } @Override public int compareTo(Object object) { if (object instanceof PrivacyItem) { return this.order - ((PrivacyItem) object).order; } return getClass().getName().compareTo(object.getClass().getName()); } /** * Returns true if the packet to analyze matches the condition defined by this rule. * Variables involved in the analysis are: type (e.g. jid, group, etc.), value (based * on the type) and granular control that defines which type of packets should be * considered. * * @param packet the packet to analyze if matches the rule's condition. * @param roster the roster of the owner of the privacy list. * @param userJID the JID of the owner of the privacy list. * @return true if the packet to analyze matches the condition defined by this rule. */ boolean matchesCondition(Packet packet, Roster roster, JID userJID) { return matchesPacketSenderCondition(packet, roster, userJID) && matchesPacketTypeCondition(packet, userJID); } boolean isAllow() { return allow; } private boolean matchesPacketSenderCondition(Packet packet, Roster roster, JID userJID) { if (type == null) { // This is the "fall-through" case return true; } boolean isPresence = packet.getClass().equals(Presence.class); boolean incoming = true; if (packet.getFrom() != null) { incoming = !userJID.toBareJID().equals(packet.getFrom().toBareJID()); } boolean matches = false; if (isPresence && !incoming && (filterEverything || filterPresence_out)) { // If this is an outgoing presence and we are filtering by outgoing presence // notification then use the receipient of the packet in the analysis matches = verifyJID(packet.getTo(), roster); } if (!matches && incoming && (filterEverything || filterPresence_in || filterIQ || filterMessage)) { matches = verifyJID(packet.getFrom(), roster); } return matches; } private boolean verifyJID(JID jid, Roster roster) { if (jid == null) { return false; } if (type == Type.jid) { if (jidValue.getResource() != null) { // Rule is filtering by exact resource match // (e.g. <user@domain/resource> or <domain/resource>) return jid.equals(jidValue); } else if (jidValue.getNode() != null) { // Rule is filtering by any resource matches (e.g. <user@domain>) return jid.toBareJID().equals(jidValue.toBareJID()); } else { // Rule is filtering by domain (e.g. <domain>) return jid.getDomain().equals(jidValue.getDomain()); } } else if (type == Type.group) { Collection<String> contactGroups; try { // Get the groups where the contact belongs RosterItem item = roster.getRosterItem(jid); contactGroups = item.getGroups(); } catch (UserNotFoundException e) { // Sender is not in the user's roster contactGroups = Collections.emptyList(); } // Check if the contact belongs to the specified group return contactGroups.contains(groupValue); } else { RosterItem.SubType contactSubscription = RosterItem.SUB_NONE; try { // Get the groups where the contact belongs RosterItem item = roster.getRosterItem(jid); contactSubscription = item.getSubStatus(); } catch (UserNotFoundException e) { // Sender is not in the user's roster } // Check if the contact has the specified subscription status return contactSubscription == subscriptionValue; } } private boolean matchesPacketTypeCondition(Packet packet, JID userJID) { if (filterEverything) { // This includes all type of packets (including subscription-related presences) return true; } Class packetClass = packet.getClass(); if (Message.class.equals(packetClass)) { return filterMessage; } else if (Presence.class.equals(packetClass)) { Presence.Type presenceType = ((Presence) packet).getType(); // Only filter presences of type available or unavailable // (ignore subscription-related presences) if (presenceType == null || presenceType == Presence.Type.unavailable) { // Calculate if packet is being received by the user JID to = packet.getTo(); boolean incoming = to != null && to.toBareJID().equals(userJID.toBareJID()); if (incoming) { return filterPresence_in; } else { return filterPresence_out; } } } else if (IQ.class.equals(packetClass)) { return filterIQ; } return false; } @Override public int getCachedSize() { // Approximate the size of the object in bytes by calculating the size // of each field. int size = 0; size += CacheSizes.sizeOfObject(); // overhead of object size += CacheSizes.sizeOfInt(); // order size += CacheSizes.sizeOfBoolean(); // allow //size += CacheSizes.sizeOfString(jidValue.toString()); // type if (jidValue != null ) { size += CacheSizes.sizeOfString(jidValue.toString()); // jidValue } //size += CacheSizes.sizeOfString(name); // subscriptionValue if (groupValue != null) { size += CacheSizes.sizeOfString(groupValue); // groupValue } size += CacheSizes.sizeOfBoolean(); // filterEverything size += CacheSizes.sizeOfBoolean(); // filterIQ size += CacheSizes.sizeOfBoolean(); // filterMessage size += CacheSizes.sizeOfBoolean(); // filterPresence_in size += CacheSizes.sizeOfBoolean(); // filterPresence_out return size; } /** * Type defines if the rule is based on JIDs, roster groups or presence subscription types. */ private static enum Type { /** * JID being analyzed should belong to a roster group of the list's owner. */ group, /** * JID being analyzed should have a resource match, domain match or bare JID match. */ jid, /** * JID being analyzed should belong to a contact present in the owner's roster with * the specified subscription status. */ subscription } }