diff --git a/postgres.sql b/postgres.sql index 9495950..e2d01a0 100644 --- a/postgres.sql +++ b/postgres.sql @@ -15,6 +15,7 @@ CREATE TABLE subscriptions (node TEXT REFERENCES nodes (node), listener TEXT, subscription TEXT, updated TIMESTAMP, + temporary BOOLEAN DEFAULT FALSE, PRIMARY KEY (node, "user")); CREATE INDEX subscriptions_updated ON subscriptions (updated); CREATE TABLE affiliations (node TEXT REFERENCES nodes (node), diff --git a/src/local/model_postgres.coffee b/src/local/model_postgres.coffee index 8cd67e9..e36c96e 100644 --- a/src/local/model_postgres.coffee +++ b/src/local/model_postgres.coffee @@ -261,7 +261,7 @@ class Transaction , (err, res) -> cb err, res?.rows?[0]?.listener or "none" - setSubscription: (node, user, listener, subscription, cb) -> + setSubscription: (node, user, listener, subscription, temporary, cb) -> unless node return cb(new Error("No node")) unless user @@ -277,22 +277,22 @@ class Transaction logger.debug "setSubscription #{node} #{user} isSet=#{isSet} toDelete=#{toDelete}" if isSet and not toDelete if listener - db.query "UPDATE subscriptions SET listener=$1, subscription=$2, updated=CURRENT_TIMESTAMP WHERE node=$3 AND \"user\"=$4" - , [ listener, subscription, node, user ] + db.query "UPDATE subscriptions SET listener=$1, subscription=$2, updated=CURRENT_TIMESTAMP, temporary=$3 WHERE node=$4 AND \"user\"=$5" + , [ listener, subscription, temporary, node, user ] , cb2 else - db.query "UPDATE subscriptions SET subscription=$1, updated=CURRENT_TIMESTAMP WHERE node=$2 AND \"user\"=$3" - , [ subscription, node, user ] + db.query "UPDATE subscriptions SET subscription=$1, updated=CURRENT_TIMESTAMP, temporary=$2 WHERE node=$3 AND \"user\"=$4" + , [ subscription, temporary, node, user ] , cb2 else if not isSet and not toDelete # listener=null is allowed for 3rd-party inboxes if listener - db.query "INSERT INTO subscriptions (node, \"user\", listener, subscription, updated) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)" - , [ node, user, listener, subscription ] + db.query "INSERT INTO subscriptions (node, \"user\", listener, subscription, updated, temporary) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, $5)" + , [ node, user, listener, subscription, temporary ] , cb2 else - db.query "INSERT INTO subscriptions (node, \"user\", subscription, updated) VALUES ($1, $2, $3, CURRENT_TIMESTAMP)" - , [ node, user, subscription ] + db.query "INSERT INTO subscriptions (node, \"user\", subscription, updated, temporary) VALUES ($1, $2, $3, CURRENT_TIMESTAMP, $4)" + , [ node, user, subscription, temporary ] , cb2 else if isSet and toDelete db.query "DELETE FROM subscriptions WHERE node=$1 AND \"user\"=$2" @@ -311,7 +311,7 @@ class Transaction db = @db async.waterfall [(cb2) -> - db.query "SELECT \"user\", subscription FROM subscriptions WHERE node=$1 ORDER BY updated DESC", [ node ], cb2 + db.query "SELECT \"user\", subscription FROM subscriptions WHERE node=$1 AND temporary=FALSE ORDER BY updated DESC", [ node ], cb2 , (res, cb2) -> subscribers = for row in res.rows { user: row.user, subscription: row.subscription } @@ -323,7 +323,7 @@ class Transaction # Not only by users but also by listeners. # @param cb {Function} cb(Error, { user, node, subscription }) getSubscriptions: (actor, cb) -> - @db.query "SELECT \"user\", node, subscription FROM subscriptions WHERE \"user\"=$1 OR listener=$1 ORDER BY updated DESC", [ actor ], (err, res) -> + @db.query "SELECT \"user\", node, subscription FROM subscriptions WHERE temporary=FALSE AND (\"user\"=$1 OR listener=$1) ORDER BY updated DESC", [ actor ], (err, res) -> cb err, res?.rows getPending: (node, cb) -> @@ -382,6 +382,11 @@ class Transaction @db.query "DELETE FROM subscriptions WHERE \"user\"=$1", [user], (err) -> cb err + clearTemporarySubscriptions: (user, cb) -> + logger.debug "clearTemporarySubscriptions #{user}" + @db.query "DELETE FROM subscriptions WHERE \"user\"=$1 AND temporary=TRUE", [user], (err) -> + cb err + ## # Affiliation management ## @@ -448,7 +453,7 @@ class Transaction getAffiliated: (node, cb) -> db = @db async.waterfall [(cb2) -> - db.query "SELECT \"user\", affiliation FROM affiliations WHERE node=$1 ORDER BY updated DESC", [ node ], cb2 + db.query "SELECT affiliations.user, affiliation FROM (affiliations JOIN subscriptions ON affiliations.user = subscriptions.user AND affiliations.node = subscriptions.node AND temporary=FALSE) WHERE affiliations.node=$1 ORDER BY affiliations.updated DESC", [ node ], cb2 , (res, cb2) -> affiliations = for row in res.rows { user: row.user, affiliation: row.affiliation } @@ -748,4 +753,3 @@ parseEl = (xml) -> catch e logger.error "Parsing " + xml + ": " + e.stack return undefined - diff --git a/src/local/operations.coffee b/src/local/operations.coffee index 2d1913c..886b129 100644 --- a/src/local/operations.coffee +++ b/src/local/operations.coffee @@ -411,7 +411,7 @@ class Register extends ModelOperation created = created_ t.setAffiliation node, user, 'owner', cb2 , (cb2) => - t.setSubscription node, user, @req.sender, 'subscribed', cb2 + t.setSubscription node, user, @req.sender, 'subscribed', false, cb2 , (cb2) -> # if already present, don't overwrite config if created @@ -479,7 +479,7 @@ class CreateNode extends ModelOperation # Set t.setConfig @req.node, config, cb2 , (cb2) => - t.setSubscription @req.node, @req.actor, @req.sender, 'subscribed', cb2 + t.setSubscription @req.node, @req.actor, @req.sender, 'subscribed', false, cb2 , (cb2) => t.setAffiliation @req.node, @req.actor, 'owner', cb2 ], cb @@ -533,9 +533,12 @@ class Subscribe extends PrivilegedOperation @fetchNodeConfig t, cb2 , (cb2) => if @nodeConfig.accessModel is 'authorize' - @subscription = 'pending' - # Immediately return: - return cb2() + if @req.temporary + return cb new errors.NotAllowed('Cannot subscribe temporarily to private node') + else + @subscription = 'pending' + # Immediately return: + return cb2() @subscription = 'subscribed' defaultAffiliation = @nodeConfig.defaultAffiliation or 'none' @@ -552,7 +555,7 @@ class Subscribe extends PrivilegedOperation privilegedTransaction: (t, cb) -> async.waterfall [ (cb2) => - t.setSubscription @req.node, @req.actor, @req.sender, @subscription, cb2 + t.setSubscription @req.node, @req.actor, @req.sender, @subscription, @req.temporary, cb2 , (cb2) => if @affiliation t.setAffiliation @req.node, @req.actor, @affiliation, cb2 @@ -569,6 +572,7 @@ class Subscribe extends PrivilegedOperation node: @req.node user: @req.actor subscription: @subscription + temporary: @req.temporary }] if @affiliation ns.push @@ -592,7 +596,7 @@ class Unsubscribe extends PrivilegedOperation return cb new errors.Forbidden("You may not unsubscribe from your own nodes") async.waterfall [ (cb2) => - t.setSubscription @req.node, @req.actor, @req.sender, 'none', cb2 + t.setSubscription @req.node, @req.actor, @req.sender, 'none', false, cb2 , (cb2) => @fetchActorAffiliation t, cb2 , (cb2) => @@ -620,7 +624,6 @@ class Unsubscribe extends PrivilegedOperation affiliation: @actorAffiliation }] - class RetrieveItems extends PrivilegedOperation run: (cb) -> if @subscriptionsNodeOwner? @@ -883,7 +886,7 @@ class ManageNodeSubscriptions extends PrivilegedOperation subscription isnt 'subscribed' cb4 new errors.Forbidden("You may not unsubscribe the owner") else - t.setSubscription @req.node, user, null, subscription, cb4 + t.setSubscription @req.node, user, null, subscription, false, cb4 , (cb4) => t.getAffiliation @req.node, user, cb4 , (affiliation, cb4) => @@ -1006,6 +1009,10 @@ class RemoveUser extends ModelOperation t.clearUserSubscriptions @req.actor, (err) => cb err, subscriptions +class RemoveTemporarySubscriptions extends ModelOperation + transaction: (t, cb) -> + t.clearTemporarySubscriptions @req.actor, (err) => + cb err class AuthorizeSubscriber extends PrivilegedOperation requiredAffiliation: => @@ -1024,7 +1031,7 @@ class AuthorizeSubscriber extends PrivilegedOperation @subscription = 'none' async.waterfall [ (cb2) => - t.setSubscription @req.node, @req.user, @req.sender, @subscription, cb2 + t.setSubscription @req.node, @req.user, @req.sender, @subscription, false, cb2 , (cb2) => if @affiliation t.setAffiliation @req.node, @req.user, @affiliation, cb2 @@ -1127,10 +1134,10 @@ class PushInbox extends ModelOperation when 'subscription' notification.push update - {node, user, listener, subscription} = update + {node, user, listener, subscription, temporary} = update if subscription isnt 'subscribed' unsubscribedNodes[node] = yes - t.setSubscription node, user, listener, subscription, cb3 + t.setSubscription node, user, listener, subscription, temporary, cb3 when 'affiliation' notification.push update @@ -1359,6 +1366,7 @@ OPERATIONS = 'manage-node-affiliations': ManageNodeAffiliations 'manage-node-configuration': ManageNodeConfiguration 'remove-user': RemoveUser + 'remove-temporary-subscriptions': RemoveTemporarySubscriptions 'confirm-subscriber-authorization': AuthorizeSubscriber 'replay-archive': ReplayArchive 'push-inbox': PushInbox diff --git a/src/router.coffee b/src/router.coffee index 833610d..2dfc26e 100644 --- a/src/router.coffee +++ b/src/router.coffee @@ -292,3 +292,8 @@ class exports.Router actor: user sender: user runLocally req, -> + else + req = + operation: 'remove-temporary-subscriptions' + actor: user + @runLocally req, -> diff --git a/src/xmpp/backend_pubsub.coffee b/src/xmpp/backend_pubsub.coffee index b089fc3..5db51a4 100644 --- a/src/xmpp/backend_pubsub.coffee +++ b/src/xmpp/backend_pubsub.coffee @@ -145,6 +145,7 @@ class exports.PubsubBackend extends EventEmitter node: node user: child.attrs.jid subscription: child.attrs.subscription + temporary: child.attrs.temporary? and child.attrs.temporary == '1' if child.is('affiliation') updates.push diff --git a/src/xmpp/pubsub_notifications.coffee b/src/xmpp/pubsub_notifications.coffee index 1692d29..18b969e 100644 --- a/src/xmpp/pubsub_notifications.coffee +++ b/src/xmpp/pubsub_notifications.coffee @@ -49,6 +49,7 @@ class EventNotification extends Notification jid: update.user node: update.node subscription: update.subscription + temporary: if update.temporary then '1' else '0' ) when 'affiliation' eventEl. diff --git a/src/xmpp/pubsub_server.coffee b/src/xmpp/pubsub_server.coffee index 5cb1ece..6977cda 100644 --- a/src/xmpp/pubsub_server.coffee +++ b/src/xmpp/pubsub_server.coffee @@ -318,6 +318,14 @@ class PubsubSubscribeRequest extends PubsubRequest @subscribeEl = @pubsubEl?.getChild("subscribe") @node = @subscribeEl?.attrs.node + @temporary = false + + @optionsEl = @pubsubEl?.getChild("options") + @formEl = @optionsEl?.getChild("x") + if @formEl + for field in @formEl.getChildren("field") + if field.attrs.var == 'pubsub#expire' + @temporary = field.getChild("value")?.getText() is 'presence' matches: () -> super &&