Commit bee39bd6 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Initial version. JM-102

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3413 b35dd754-fafc-0310-a699-88a17e54d16e
parent 8744af70
/**
* $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);
}
if (!matches && (filterEverything || filterPresence_in && filterIQ || filterMessage)) {
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;
}
}
/**
* $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.DocumentFactory;
import org.dom4j.Element;
import org.jivesoftware.util.Log;
import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.roster.Roster;
import org.jivesoftware.wildfire.user.UserNotFoundException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import java.io.Serializable;
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
*/
public class PrivacyList implements Serializable {
private JID userJID;
private String name;
private boolean isDefault;
private List<PrivacyItem> items = new ArrayList<PrivacyItem>();
private Roster roster;
public PrivacyList(String username, String name, boolean isDefault, Element listElement) {
this.userJID = XMPPServer.getInstance().createJID(username, null);
this.name = name;
this.isDefault = isDefault;
// Set the new list items
updateList(listElement);
}
/**
* 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;
}
/**
* 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
for (PrivacyItem item : items) {
if (item.matchesCondition(packet, roster, userJID)) {
if (item.isAllow()) {
return false;
}
if (Log.isDebugEnabled()) {
Log.debug("Packet was blocked: " + packet);
}
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() {
Element listElement = DocumentFactory.getInstance().createDocument().addElement("list");
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) {
// 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
if (roster == null && newItem.isRosterRequired()) {
try {
roster = XMPPServer.getInstance().getRosterManager().getRoster(userJID.getNode());
}
catch (UserNotFoundException e) {
Log.warn("Privacy item removed since roster of user was not found: " +
userJID.getNode());
items.remove(newItem);
}
}
}
// Sort items collections
Collections.sort(items);
}
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;
}
}
}
package org.jivesoftware.wildfire.privacy;
import org.dom4j.Element;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager;
/**
* A Privacy list manager creates, gets, updates and removes privacy lists. Loaded lists
* are kept in memory using a cache that will keep them at most for 6 hours.
*
* @author Gaston Dombiak
*/
public class PrivacyListManager {
private static final PrivacyListManager instance = new PrivacyListManager();
private PrivacyListProvider provider = new PrivacyListProvider();
private Cache listsCache;
/**
* Returns the unique instance of this class.
*
* @return the unique instance of this class.
*/
public static PrivacyListManager getInstance() {
return instance;
}
private PrivacyListManager() {
// Create the cache of privacy lists
CacheManager.initializeCache("listsCache", 512 * 1024);
listsCache = CacheManager.getCache("listsCache");
}
/**
* Creates a new privacy list for the specified user.
*
* @param username the username of the list owner.
* @param listName the name of the new privacy list.
* @param listElement the XML that specifies the list and its items.
* @return the newly created PrivacyList.
*/
public PrivacyList createPrivacyList(String username, String listName, Element listElement) {
// Create new list
PrivacyList list = new PrivacyList(username, listName, false, listElement);
// Add new list to cache
listsCache.put(getCacheKey(username, listName), list);
// Save new list to database
provider.createPrivacyList(username, list);
return list;
}
/**
* Deletes an existing privacy list of a user. If the privacy list being deleted was
* the default list then the user will end up with no default list. Therefore, the user
* will have to set a new default list.
*
* @param username the username of the list owner.
* @param listName the name of the list being deleted.
*/
public void deletePrivacyList(String username, String listName) {
// Remove the list from the cache
listsCache.remove(getCacheKey(username, listName));
// Delete the privacy list from the DB
provider.deletePrivacyList(username, listName);
// Check if deleted list was the default list
PrivacyList defaultList = (PrivacyList) listsCache.get(getDefaultCacheKey(username));
if (defaultList != null && listName.equals(defaultList.getName())) {
listsCache.remove(getDefaultCacheKey(username));
}
}
/**
* Deletes all privacy lists of a user. This may be necessary when a user is being
* deleted from the system.
*
* @param username the username of the list owner.
*/
public void deletePrivacyLists(String username) {
for (String listName : provider.getPrivacyLists(username).keySet()) {
// Remove the list from the cache
listsCache.remove(getCacheKey(username, listName));
}
// Delete user privacy lists from the DB
provider.deletePrivacyLists(username);
}
/**
* Returns the default privacy list of the specified user or <tt>null</tt> if
* none was found.
*
* @param username the name of the user to get his default list.
* @return the default privacy list of the specified user or <tt>null</tt> if
* none was found.
*/
public PrivacyList getDefaultPrivacyList(String username) {
// Check if we have the default list in the cache
String cacheKey = getDefaultCacheKey(username);
PrivacyList list = (PrivacyList) listsCache.get(cacheKey);
if (list == null) {
synchronized (username.intern()) {
list = (PrivacyList) listsCache.get(cacheKey);
if (list == null) {
// Load default list from the database
list = provider.loadDefaultPrivacyList(username);
if (list != null) {
listsCache.put(cacheKey, list);
}
}
}
}
return list;
}
/**
* Returns a specific privacy list of the specified user or <tt>null</tt> if
* none was found.
*
* @param username the name of the user to get his privacy list.
* @param listName the name of the list to get.
* @return a privacy list of the specified user or <tt>null</tt> if
* none was found.
*/
public PrivacyList getPrivacyList(String username, String listName) {
// Check if we have a list in the cache
String cacheKey = getCacheKey(username, listName);
PrivacyList list = (PrivacyList) listsCache.get(cacheKey);
if (list == null) {
// Load the list from the database
list = provider.loadPrivacyList(username, listName);
if (list != null) {
listsCache.put(cacheKey, list);
}
}
return list;
}
/**
* Sets a given privacy list as the new default list of the user.
*
* @param username the name of the user that is setting a new default list.
* @param newDefault the new default privacy list.
* @param oldDefault the previous privacy list or <tt>null</tt> if no default list existed.
*/
public void changeDefaultList(String username, PrivacyList newDefault, PrivacyList oldDefault) {
// TODO Analyze concurrency issues when other resource may log in while doing this change
if (oldDefault != null) {
// Update old default list to become just another list
oldDefault.setDefaultList(false);
provider.updatePrivacyList(username, oldDefault);
}
// Update new list to become the default
newDefault.setDefaultList(true);
// Set new default list in the cache
listsCache.put(getDefaultCacheKey(username), newDefault);
// Update both lists in the database
provider.updatePrivacyList(username, newDefault);
}
/**
* Returns the key to use to locate a privacy list in the cache.
*/
private String getCacheKey(String username, String listName) {
return username + listName;
}
/**
* Returns the key to use to locate default privacy lists in the cache.
*/
private String getDefaultCacheKey(String username) {
return getCacheKey(username, "__d_e_f_a_u_l_t__");
}
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<p>Implementation of Blocking Communication.</p>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment