# HG changeset patch # User Emmanuel Gil Peyrot # Date 1277754930 -7200 # Node ID c2954a9e5665440dbc3548c9e11a15f374a6e267 # Parent 9ee956af41e31a45b420ff335f733a697d22419a 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. diff --git a/configuration.js b/configuration.js --- 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: { diff --git a/nodes.js b/nodes.js --- 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')) diff --git a/psgxs.js b/psgxs.js --- 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}); diff --git a/storage.js b/storage.js --- 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';