PrivacyList.java 10.1 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5 6 7 8 9 10 11
/**
 * $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.
 */

12
package org.jivesoftware.openfire.privacy;
Gaston Dombiak's avatar
Gaston Dombiak committed
13 14 15

import org.dom4j.DocumentFactory;
import org.dom4j.Element;
16
import org.dom4j.io.XMPPPacketReader;
17
import org.jivesoftware.openfire.XMPPServer;
18
import org.jivesoftware.openfire.net.MXParser;
19 20
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.user.UserNotFoundException;
21 22 23 24 25 26
import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
Gaston Dombiak's avatar
Gaston Dombiak committed
27 28 29
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;

30
import java.io.*;
Gaston Dombiak's avatar
Gaston Dombiak committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A privacy list contains a set of rules that define if communication with the list owner
 * is allowed or denied. Users may have zero, one or more privacy lists. When a list is the
 * default list then that list is going to be used by default for all user sessions or analyze,
 * when user is offline, if communication may proceed (e.g. define if a message should be stored
 * offline). A user may configure is he wants to have a default list or not. When no default list
 * is defined then communication will not be blocked. However, users may define an active list
 * for a particular session. Active lists override default list (if there is one) and will be used
 * only for the duration of the session.
 *
 * @author Gaston Dombiak
 */
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
public class PrivacyList implements Cacheable, Externalizable {

    /**
     * Reuse the same factory for all the connections.
     */
    private static XmlPullParserFactory factory = null;
    private static ThreadLocal<XMPPPacketReader> localParser = null;

    static {
        try {
            factory = XmlPullParserFactory.newInstance(MXParser.class.getName(), null);
            factory.setNamespaceAware(true);
        }
        catch (XmlPullParserException e) {
            Log.error("Error creating a parser factory", e);
        }
        // Create xmpp parser to keep in each thread
        localParser = new ThreadLocal<XMPPPacketReader>() {
            protected XMPPPacketReader initialValue() {
                XMPPPacketReader parser = new XMPPPacketReader();
                factory.setNamespaceAware(true);
                parser.setXPPFactory(factory);
                return parser;
            }
        };
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
73 74 75 76 77 78

    private JID userJID;
    private String name;
    private boolean isDefault;
    private List<PrivacyItem> items = new ArrayList<PrivacyItem>();

79 80 81 82 83 84
    /**
     * Constructor added for Externalizable. Do not use this constructor.
     */
    public PrivacyList() {
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
85
    public PrivacyList(String username, String name, boolean isDefault, Element listElement) {
86
        this.userJID = XMPPServer.getInstance().createJID(username, null, true);
Gaston Dombiak's avatar
Gaston Dombiak committed
87 88 89 90 91 92
        this.name = name;
        this.isDefault = isDefault;
        // Set the new list items
        updateList(listElement);
    }

93 94 95 96 97 98 99 100 101
    /**
     * Returns the JID of the user that owns this privacy list.
     *
     * @return the JID of the user that owns this privacy list.
     */
    public JID getUserJID() {
        return userJID;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
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
    /**
     * Returns the name that uniquely identifies this list among the users lists.
     *
     * @return the name that uniquely identifies this list among the users lists.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns true if this privacy list is the default list to apply for the user. Default
     * privacy lists can be overriden per session by setting an active privacy list.
     *
     * @return true if this privacy list is the default list to apply for the user.
     */
    public boolean isDefault() {
        return isDefault;
    }

    /**
     * Sets if this privacy list is the default list to apply for the user. Default
     * privacy lists can be overriden per session by setting an active privacy list.
     *
     * @param isDefault true if this privacy list is the default list to apply for the user.
     */
    public void setDefaultList(boolean isDefault) {
        this.isDefault = isDefault;
129 130
        // Trigger event that this list has been modified
        PrivacyListManager.getInstance().dispatchModifiedEvent(this);
Gaston Dombiak's avatar
Gaston Dombiak committed
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    }

    /**
     * Returns true if the specified packet must be blocked based on this privacy list rules.
     * Rules are going to be analyzed based on their order (in ascending order). When a rule
     * is matched then communication will be blocked or allowed based on that rule. No more
     * further analysis is going to be made.
     *
     * @param packet the packet to analyze if it must be blocked.
     * @return true if the specified packet must be blocked based on this privacy list rules.
     */
    public boolean shouldBlockPacket(Packet packet) {
        if (packet.getFrom() == null) {
            // Sender is the server so it's not denied
            return false;
        }
        // Iterate over the rules and check each rule condition
148
        Roster roster = getRoster();
Gaston Dombiak's avatar
Gaston Dombiak committed
149 150 151 152 153 154
        for (PrivacyItem item : items) {
            if (item.matchesCondition(packet, roster, userJID)) {
                if (item.isAllow()) {
                    return false;
                }
                if (Log.isDebugEnabled()) {
155
                    Log.debug("PrivacyList: Packet was blocked: " + packet);
Gaston Dombiak's avatar
Gaston Dombiak committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169
                }
                return true;
            }
        }
        // If no rule blocked the communication then allow the packet to flow
        return false;
    }

    /**
     * Returns an Element with the privacy list XML representation.
     *
     * @return an Element with the privacy list XML representation.
     */
    public Element asElement() {
170 171 172
        //Element listElement = DocumentFactory.getInstance().createDocument().addElement("list");
        Element listElement = DocumentFactory.getInstance().createDocument()
                .addElement("list", "jabber:iq:privacy");
Gaston Dombiak's avatar
Gaston Dombiak committed
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        listElement.addAttribute("name", getName());
        // Add the list items to the result
        for (PrivacyItem item : items) {
            listElement.add(item.asElement());
        }
        return listElement;
    }

    /**
     * Sets the new list items based on the specified Element. The Element must contain
     * a list of item elements.
     *
     * @param listElement the element containing a list of items.
     */
    public void updateList(Element listElement) {
188 189 190 191 192 193 194 195 196 197 198
        updateList(listElement, true);
    }

    /**
     * Sets the new list items based on the specified Element. The Element must contain
     * a list of item elements.
     *
     * @param listElement the element containing a list of items.
     * @param notify true if a provicy list modified event will be triggered.
     */
    private void updateList(Element listElement, boolean notify) {
Gaston Dombiak's avatar
Gaston Dombiak committed
199 200 201 202 203 204 205 206 207
        // Reset the list of items of this list
        items = new ArrayList<PrivacyItem>();

        List<Element> itemsElements = listElement.elements("item");
        for (Element itemElement : itemsElements) {
            PrivacyItem newItem = new PrivacyItem(itemElement);
            items.add(newItem);
            // If the user's roster is required to evaluation whether a packet must be blocked
            // then ensure that the roster is available
208 209 210 211
            if (newItem.isRosterRequired()) {
                Roster roster = getRoster();
                if (roster == null) {
                    Log.warn("Privacy item removed since roster of user was not found: " + userJID.getNode());
Gaston Dombiak's avatar
Gaston Dombiak committed
212 213 214 215 216 217
                    items.remove(newItem);
                }
            }
        }
        // Sort items collections
        Collections.sort(items);
218 219 220 221
        if (notify) {
            // Trigger event that this list has been modified
            PrivacyListManager.getInstance().dispatchModifiedEvent(this);
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
222 223
    }

224 225 226 227 228 229 230 231 232
    private Roster getRoster() {
        try {
            return XMPPServer.getInstance().getRosterManager().getRoster(userJID.getNode());
        } catch (UserNotFoundException e) {
            Log.warn("Roster not found for user: " + userJID);
        }
        return null;
    }

233 234 235 236 237 238 239 240 241 242 243 244
    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.sizeOfString(userJID.toString());    // userJID
        size += CacheSizes.sizeOfString(name);                  // name
        size += CacheSizes.sizeOfBoolean();                     // isDefault
        size += CacheSizes.sizeOfCollection(items);             // items of the list
        return size;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    public int hashCode() {
        return name.hashCode();
    }

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object != null && object instanceof PrivacyList) {
            return name.equals(((PrivacyList)object).getName());
        }
        else {
            return false;
        }
    }
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277

    public void writeExternal(ObjectOutput out) throws IOException {
        ExternalizableUtil.getInstance().writeSafeUTF(out, userJID.toString());
        ExternalizableUtil.getInstance().writeBoolean(out, isDefault);
        ExternalizableUtil.getInstance().writeSafeUTF(out, asElement().asXML());
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userJID = new JID(ExternalizableUtil.getInstance().readSafeUTF(in));
        isDefault = ExternalizableUtil.getInstance().readBoolean(in);
        String xml = ExternalizableUtil.getInstance().readSafeUTF(in);
        try {
            Element element = localParser.get().read(new StringReader(xml)).getRootElement();
            updateList(element, false);
        } catch (Exception e) {
            Log.error("Error while parsing Privacy Property", e);
        }
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
278
}