changeset 1:c2954a9e5665

Add a super-owner that has power over all nodes; add support for affiliation changes; support pubsub#creator metadata; change pubsub#digest_frequency default; and use better default config values.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 28 Jun 2010 21:55:30 +0200
parents 9ee956af41e3
children d3ae2f8b685d
files configuration.js nodes.js psgxs.js storage.js
diffstat 4 files changed, 110 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/configuration.js
+++ b/configuration.js
@@ -20,7 +20,10 @@
 var config = exports;
 
 config.jid = 'pubsub.example.org';
-config.password = 'pubsub.example.org';
+config.password = 'hellohello';
+config.superOwner = ['you@example.com'];
+config.version = '0.1';
+config.os = 'GNU/Linux';
 
 config.activated = [
 	//'access-authorize',
@@ -82,7 +85,7 @@ config.service_configuration = {
 		FORM_TYPE: {type: 'hidden', value: 'http://jabber.org/protocol/pubsub#subscribe_options'},
 		'pubsub#deliver': {type: 'boolean', label: 'Whether an entity wants to receive or disable notifications', value: true},
 		'pubsub#digest': {type: 'boolean', label: 'Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually', value: false},
-		'pubsub#digest_frequency': {type: 'text-single', label: 'The minimum number of milliseconds between sending any two notification digests', value: 60*1000},
+		'pubsub#digest_frequency': {type: 'text-single', label: 'The minimum number of milliseconds between sending any two notification digests', value: 24*60*60*1000},
 //		'pubsub#expire': {type: 'text-single', label: 'The DateTime at which a leased subscription will end or has ended'},
 //		'pubsub#include_body': {type: 'boolean', label: 'Whether an entity wants to receive an XMPP message body in addition to the payload format', value: false},
 //		'pubsub#show-values': {type: 'list-multi', label: 'The presence states for which an entity wants to receive notifications', options: {
--- a/nodes.js
+++ b/nodes.js
@@ -34,7 +34,7 @@ exports.Node = function(params) {
 	this.items = {};
 	if (config.enabled('subscribe'))
 		this.subscribers = {};
-	this.owner = ['lm@slam'];
+	this.owner = [];
 	if (config.enabled('publisher-affiliation'))
 		this.publisher = [];
 	if (config.enabled('publish-only-affiliation'))
--- a/psgxs.js
+++ b/psgxs.js
@@ -59,12 +59,13 @@ function onIq(stanza) {
 		response = xmpp.iq({to: to, from: from, type: 'result'});
 
 	if (type == 'get') {
+
+		// XEP-0092
 		if (stanza.getChild('query', 'jabber:iq:version')) {
-			var os = 'GNU/Linux';
 			var query = xmpp.stanza('query', {xmlns: 'jabber:iq:version'})
-				.s('name').t('PubSubJS')
-				.s('version').t('Pas releasé')
-				.s('os').t(os);
+				.s('name').t('PSĜS')
+				.s('version').t(config.version)
+				.s('os').t(config.os);
 			response.cx(query);
 
 		// SECTION 5.1
@@ -104,6 +105,7 @@ function onIq(stanza) {
 					.s('feature', {'var': 'http://jabber.org/protocol/disco#info'})
 					.s('feature', {'var': 'http://jabber.org/protocol/disco#items'})
 					.s('feature', {'var': 'http://jabber.org/protocol/pubsub'})
+					.s('feature', {'var': 'jabber:iq:version'})
 //					.s('feature', {'var': 'http://jabber.org/protocol/commands'})
 					for (var i in config.activated)
 						if (typeof i == 'string')
@@ -266,7 +268,7 @@ function onIq(stanza) {
 					return makeError(response, errors.node_does_not_exist.n);
 
 				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'owner' && affil != 'publisher' && affil != 'member')
+				if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
 					return makeError(response, errors.pub.publish.insufficient_privileges.n);
 
 				var item = [];
@@ -317,7 +319,7 @@ function onIq(stanza) {
 		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
 			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
 
-			// SECTION
+			// SECTION 8.2
 			if (pubsub.getChild('configure')) {
 				if (!config.enabled('config-node'))
 					return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
@@ -329,7 +331,7 @@ function onIq(stanza) {
 					return makeError(response, errors.node_does_not_exist.n);
 
 				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'owner' && affil != 'publish-only')
+				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
 					return makeError(response, errors.pub.publish.insufficient_privileges.n);
 
 				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
@@ -339,7 +341,7 @@ function onIq(stanza) {
 				p.cx(s);
 				response.cx(p);
 
-			// SECTION
+			// SECTION 8.3
 			} else if (pubsub.getChild('default')) {
 				if (!config.enabled('config-node'))
 					return makeError(response, errors.owner.default_options.node_configuration_not_supported.n);
@@ -351,7 +353,7 @@ function onIq(stanza) {
 				p.cx(s);
 				response.cx(p);
 
-			// SECTION
+			// SECTION 8.8
 			} else if (pubsub.getChild('subscriptions')) {
 				if (!config.enabled('manage-subscriptions'))
 					return makeError(response, errors.owner.manage_subscriptions.not_supported.n);
@@ -364,7 +366,8 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
+				var affil = storage.getAffiliation(toBareJID(to), nodeID);
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.forbidden.n);
 
 				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
@@ -377,7 +380,7 @@ function onIq(stanza) {
 				p.cx(s);
 				response.cx(p);
 
-			// SECTION
+			// SECTION 8.9
 			} else if (pubsub.getChild('affiliations')) {
 				if (!config.enabled('modify-affiliations'))
 					return makeError(response, errors.owner.manage_affiliations.not_supported.n);
@@ -390,15 +393,16 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				var affil = storage.getAffiliationsFromNodeID(nodeID);
-				if (affil[toBareJID(to)] != 'owner')
+				var affils = storage.getAffiliationsFromNodeID(nodeID);
+				var affil = affils[toBareJID(to)];
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n);
 
 				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
 				var s = xmpp.stanza('affiliations', {node: nodeID});
 
-				for (var jid in affil)
-					s.s('affiliation', {jid: jid, affiliation: affil[jid]})
+				for (var jid in affils)
+					s.s('affiliation', {jid: jid, affiliation: affils[jid]})
 
 				p.cx(s);
 				response.cx(p);
@@ -463,7 +467,7 @@ function onIq(stanza) {
 						return makeError(response, subID);
 				} else if (configuration['pubsub#access_model'] == 'whitelist') {
 					var affil = storage.getAffiliation(jid, nodeID);
-					if (affil != 'owner' && affil != 'publisher' && affil != 'member')
+					if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
 						return makeError(response, errors.sub.subscribe.not_on_whitelist.n);
 
 					subID = storage.subscribe(nodeID, jid, conf);
@@ -481,7 +485,7 @@ function onIq(stanza) {
 					var affiliates = storage.getAffiliationsFromNodeID(nodeID);
 					var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168
 					for (var i in affiliates) {
-						if (affiliates[i] == 'owner') {
+						if (affiliates[i] == 'super-owner' || affiliates[i] == 'owner') {
 							var message = xmpp.message({to: i}).cx(form);
 							conn.send(message);
 						}
@@ -549,7 +553,7 @@ function onIq(stanza) {
 				if (typeof form == 'number')
 					return makeError(response, form);
 
-			// SECTION
+			// SECTION 7.1
 			} else if (pubsub.getChild('publish')) {
 				if (!config.enabled('publish'))
 					return makeError(response, errors.pub.publish.item_publication_not_supported.n);
@@ -562,7 +566,7 @@ function onIq(stanza) {
 				var affil = storage.getAffiliation(toBareJID(to), nodeID);
 				if (typeof affil == 'number')
 					return makeError(response, affil);
-				if (affil != 'owner' && affil != 'publisher' && affil != 'publish-only')
+				if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'publish-only')
 					return makeError(response, errors.forbidden.n);
 
 				var item = publish.getChild('item');
@@ -600,12 +604,19 @@ function onIq(stanza) {
 						return makeError(response, r);
 				}
 
-				if (autocreate)
-					storage.createNode(nodeID, form);
+				if (autocreate) {
+					if (!form)
+						form = {};
+					form['pubsub#creator'] = toBareJID(to);
+
+					var r = storage.createNode(nodeID, form);
+					if (typeof r == 'number')
+						return makeError(response, r);
+				}
 
 				var content = item.getChild();
+
 				subscribers = storage.setItem(nodeID, itemID, content);
-
 				if (typeof subscribers == 'number')
 					return makeError(response, subscribers);
 
@@ -620,7 +631,7 @@ function onIq(stanza) {
 					.c('publish', {node: nodeID})
 						.c('item', {id: itemID});
 
-			// SECTION
+			// SECTION 7.2
 			} else if (pubsub.getChild('retract')) {
 				if (!config.enabled('retract-items'))
 					return makeError(response, errors.pub.retract.item_deletion_not_supported.n);
@@ -649,7 +660,7 @@ function onIq(stanza) {
 				attrs[itemID] = {};
 				sendNotifs(subscribers, 'items', nodeID, attrs, 'retract')
 
-			// SECTION
+			// SECTION 8.1
 			} else if (pubsub.getChild('create')) {
 				if (!config.enabled('create-nodes'))
 					return makeError(response, errors.owner.create.node_creation_not_supported.n);
@@ -667,7 +678,7 @@ function onIq(stanza) {
 					return makeError(response, errors.nodeid_already_exists.n);
 
 				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'owner' && affil != 'publish-only')
+				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
 					return makeError(response, errors.forbidden.n);
 
 				var configure = pubsub.getChild('configure');
@@ -689,6 +700,9 @@ function onIq(stanza) {
 					var conf = form;
 				}
 
+				if (!conf)
+					conf = {};
+				conf['pubsub#creator'] = toBareJID(to);
 				var r = storage.createNode(nodeID, conf);
 				if (typeof r == 'number')
 					return makeError(response, r);
@@ -701,7 +715,7 @@ function onIq(stanza) {
 		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
 			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
 
-			// SECTION
+			// SECTION 8.2.4
 			if (pubsub.getChild('configure')) {
 				if (!config.enabled('config-node'))
 					return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
@@ -713,7 +727,7 @@ function onIq(stanza) {
 					return makeError(response, errors.node_does_not_exist.n);
 
 				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'owner' && affil != 'publish-only')
+				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
 					return makeError(response, errors.forbidden.n);
 
 				var x = configure.getChild('x', 'jabber:x:data');
@@ -730,7 +744,7 @@ function onIq(stanza) {
 				if (typeof set == 'number')
 					return makeError(response, set);
 
-			// SECTION
+			// SECTION 8.4
 			} else if (pubsub.getChild('delete')) {
 				if (!config.enabled('delete-nodes'))
 					return makeError(response, errors.feature_not_implemented.n); //XXX
@@ -743,7 +757,8 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
+				var affil = storage.getAffiliation(toBareJID(to), nodeID);
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.forbidden.n);
 
 				var notifs = storage.deleteNode(nodeID);
@@ -752,7 +767,7 @@ function onIq(stanza) {
 
 				sendNotifs(notifs, 'delete', nodeID);
 
-			// SECTION
+			// SECTION 8.5
 			} else if (pubsub.getChild('purge')) {
 				if (!config.enabled('purge-nodes'))
 					return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX
@@ -765,7 +780,8 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
+				var affil = storage.getAffiliation(toBareJID(to), nodeID);
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.forbidden.n);
 
 				if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node
@@ -777,7 +793,7 @@ function onIq(stanza) {
 
 				sendNotifs(notifs, 'purge', nodeID);
 
-			// SECTION
+			// SECTION 8.8.2
 			} else if (pubsub.getChild('subscriptions')) {
 				if (!config.enabled('manage-subscriptions'))
 					return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX
@@ -790,7 +806,8 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
+				var affil = storage.getAffiliation(toBareJID(to), nodeID);
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.forbidden.n);
 
 				var e = false;
@@ -802,6 +819,7 @@ function onIq(stanza) {
 					if (typeof set == 'number')
 						e = true;
 					else {
+						// SECTION 8.8.4
 						sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: subscription});
 						subscriptions.tags.splice(i, 1);
 					}
@@ -810,7 +828,7 @@ function onIq(stanza) {
 				if (e)
 					return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub);
 
-			// SECTION
+			// SECTION 8.9.2
 			} else if (pubsub.getChild('affiliations')) {
 				if (!config.enabled('modify-affiliations'))
 					return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX
@@ -823,7 +841,8 @@ function onIq(stanza) {
 				if (!storage.existsNode(nodeID))
 					return makeError(response, errors.node_does_not_exist.n);
 
-				if (storage.getAffiliation(toBareJID(to), nodeID) != 'owner')
+				var affil = storage.getAffiliation(toBareJID(to), nodeID);
+				if (affil != 'super-owner' && affil != 'owner')
 					return makeError(response, errors.forbidden.n);
 
 				var e = false;
@@ -834,8 +853,11 @@ function onIq(stanza) {
 					var set = storage.setAffiliation(nodeID, jid, affiliation);
 					if (typeof set == 'number')
 						e = true;
-					else
+					else {
+						// SECTION 8.9.4
+						sendNotifs(jid, 'affiliations', nodeID, {jid: jid, affiliation: affiliation});
 						affiliations.children.splice(i, 1);
+					}
 				}
 
 				if (e)
@@ -922,7 +944,25 @@ function makeError(response, errorNumber
 function sendNotifs(notifs, type, nodeID, a1, a2) {
 	var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'});
 
-	if (type == 'collection') {
+	if (type == 'affiliations') {
+		ev.attr.xmlns = 'http://jabber.org/protocol/pubsub';
+
+		var args = {};
+		for (i in a1) {
+			var attr = a1[i];
+			if (i == 'affiliation')
+				args.affiliation = attr;
+			else if (i == 'jid')
+				args.jid = attr;
+		}
+		if (!args.jid || args.jid == '') {
+			_('Error #2', 41)
+			return;
+		}
+		var affiliation = xmpp.stanza('affiliation', );
+		var affiliations = xmpp.stanza('affiliations', {node: nodeID});
+		ev.cx(affiliations);
+	} else if (type == 'collection') {
 		var collection = xmpp.stanza('collection', {node: nodeID});
 		if (a1 == 'associate')
 			collection.cx('associate', {node: nodeID});
--- a/storage.js
+++ b/storage.js
@@ -294,8 +294,10 @@ storage.getItem = function(nodeID, itemI
 	var items = storage.existsItem(nodeID, itemID);
 	if (typeof items == 'number')
 		return items;
+
 	if (items)
 		return items.content;
+
 	return errors.item_not_found;
 };
 
@@ -388,6 +390,9 @@ storage.getAffiliation = function(jid, n
 	} else
 		node = nodeID;
 
+	for (var affil in config.owner)
+		if (typeof affil == 'string' && node.owner[affil] == jid)
+			return 'super-owner';
 	for (var affil in node.owner)
 		if (typeof affil == 'string' && node.owner[affil] == jid)
 			return 'owner';
@@ -408,29 +413,34 @@ storage.getAffiliation = function(jid, n
 
 storage.getAffiliationsFromJID = function(jid) {
 	var affils = {};
-	for (var node in list) {
-		var n = list[node];
-		for (var affil in n.owner)
-			if (typeof affil == 'string' && n.owner[affil] == jid) {
-				affils[node] = 'owner';
+	for (var nodeID in list) {
+		var node = list[nodeID];
+		for (var affil in config.owner)
+			if (typeof affil == 'string' && config.owner[affil] == jid) {
+				affils[nodeID] = 'super-owner';
+				break;
+			}
+		for (var affil in node.owner)
+			if (typeof affil == 'string' && node.owner[affil] == jid) {
+				affils[nodeID] = 'owner';
 				break;
 			}
 		if (config.enabled('publisher-affiliation'))
-			for (var affil in n.publisher)
-				if (typeof affil == 'string' && n.publisher[affil] == jid) {
-					affils[node] = 'publisher';
+			for (var affil in node.publisher)
+				if (typeof affil == 'string' && node.publisher[affil] == jid) {
+					affils[nodeID] = 'publisher';
 					break;
 				}
 		if (config.enabled('publish-only-affiliation'))
-			for (var affil in n.publishOnly)
-				if (typeof affil == 'string' && n.publishOnly[affil] == jid) {
-					affils[node] = 'publish-only';
+			for (var affil in node.publishOnly)
+				if (typeof affil == 'string' && node.publishOnly[affil] == jid) {
+					affils[nodeID] = 'publish-only';
 					break;
 				}
 		if (config.enabled('outcast-affiliation'))
-			for (var affil in n.outcast)
-				if (typeof affil == 'string' && n.outcast[affil] == jid) {
-					affils[node] = 'outcast';
+			for (var affil in node.outcast)
+				if (typeof affil == 'string' && node.outcast[affil] == jid) {
+					affils[nodeID] = 'outcast';
 					break;
 				}
 	}
@@ -447,6 +457,9 @@ storage.getAffiliationsFromNodeID = func
 		node = nodeID;
 
 	var affils = {};
+	for (var jid in config.owner)
+		if (typeof jid == 'string')
+			affils[config.owner[jid]] = 'super-owner';
 	for (var jid in node.owner)
 		if (typeof jid == 'string')
 			affils[node.owner[jid]] = 'owner';