PrivacyItem.java 9.13 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.privacy;

import org.dom4j.Element;
import org.jivesoftware.wildfire.roster.Roster;
import org.jivesoftware.wildfire.roster.RosterItem;
import org.jivesoftware.wildfire.user.UserNotFoundException;
import org.xmpp.packet.*;

import java.io.Serializable;
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 Serializable, 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;
    }

    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) &&
                matchesPacketTypeCondition(packet, userJID);
    }

    boolean isAllow() {
        return allow;
    }

    private boolean matchesPacketSenderCondition(Packet packet, Roster roster) {
        if (type == null) {
            // This is the "fall-through" case
            return true;
        }
        boolean isPresence = packet.getClass().equals(Presence.class);
        boolean matches = false;
        if (isPresence && (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);
        }
146
        if (!matches && (filterEverything || filterPresence_in || filterIQ || filterMessage)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
            matches = verifyJID(packet.getFrom(), roster);
        }
        return matches;
    }

    private boolean verifyJID(JID jid, Roster roster) {
        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 = null;
            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;
    }

    /**
     * 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;
    }
}