mod_http_roster_admin/mod_http_roster_admin.lua
author Kim Alvefur <zash@zash.se>
Sun, 03 Mar 2024 11:23:40 +0100
changeset 5857 97c9b76867ca
parent 3342 7d2400710d65
permissions -rw-r--r--
mod_log_ringbuffer: Detach event handlers on logging reload (thanks Menel) Otherwise the global event handlers accumulate, one added each time logging is reoladed, and each invocation of the signal or event triggers one dump of each created ringbuffer.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     1
-- mod_http_roster_admin
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     2
-- Description: Allow user rosters to be sourced from a remote HTTP API
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     3
--
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     4
-- Version: 1.0
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     5
-- Date: 2015-03-06
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     6
-- Author: Matthew Wild <matthew@prosody.im>
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     7
-- License: MPLv2
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     8
--
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
     9
-- Requirements:
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    10
--   Prosody config:
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    11
--     storage = { roster = "memory" }
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    12
--     modules_disabled = { "roster" }
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    13
--   Dependencies:
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    14
--     Prosody 0.9
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    15
--     lua-cjson (Debian/Ubuntu/LuaRocks: lua-cjson)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    16
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    17
local http = require "net.http";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    18
local json = require "cjson";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    19
local it = require "util.iterators";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    20
local set = require "util.set";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    21
local rm = require "core.rostermanager";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    22
local st = require "util.stanza";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    23
local array = require "util.array";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    24
3320
9d8098f4b652 mod_http_roster_admin: Explicitly set 'id' attribute on roster pushes
Matthew Wild <mwild1@gmail.com>
parents: 2635
diff changeset
    25
local new_id = require "util.id".short;
9d8098f4b652 mod_http_roster_admin: Explicitly set 'id' attribute on roster pushes
Matthew Wild <mwild1@gmail.com>
parents: 2635
diff changeset
    26
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    27
local host = module.host;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    28
local sessions = hosts[host].sessions;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    29
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    30
local roster_url = module:get_option_string("http_roster_url", "http://localhost/%s");
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    31
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    32
-- Send a roster push to the named user, with the given roster, for the specified
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    33
-- contact's roster entry. Used to notify clients of changes/removals.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    34
local function roster_push(username, roster, contact_jid)
3320
9d8098f4b652 mod_http_roster_admin: Explicitly set 'id' attribute on roster pushes
Matthew Wild <mwild1@gmail.com>
parents: 2635
diff changeset
    35
	local stanza = st.iq({type="set", id=new_id()})
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    36
		:tag("query", {xmlns = "jabber:iq:roster" });
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    37
	local item = roster[contact_jid];
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    38
	if item then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    39
		stanza:tag("item", {jid = contact_jid, subscription = item.subscription, name = item.name, ask = item.ask});
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    40
		for group in pairs(item.groups) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    41
			stanza:tag("group"):text(group):up();
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    42
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    43
	else
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    44
		stanza:tag("item", {jid = contact_jid, subscription = "remove"});
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    45
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    46
	stanza:up():up(); -- move out from item
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    47
	for _, session in pairs(hosts[host].sessions[username].sessions) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    48
		if session.interested then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    49
			session.send(stanza);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    50
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    51
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    52
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    53
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    54
-- Send latest presence from the named local user to a contact.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    55
local function send_presence(username, contact_jid, available)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    56
	module:log("debug", "Sending %savailable presence from %s to contact %s", (available and "" or "un"), username, contact_jid);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    57
	for resource, session in pairs(sessions[username].sessions) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    58
		local pres;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    59
		if available then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    60
			pres = st.clone(session.presence);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    61
			pres.attr.to = contact_jid;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    62
		else
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    63
			pres = st.presence({ to = contact_jid, from = session.full_jid, type = "unavailable" });
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    64
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    65
		module:send(pres);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    66
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    67
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    68
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    69
-- Converts a 'friend' object from the API to a Prosody roster item object
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    70
local function friend_to_roster_item(friend)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    71
	return {
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    72
		name = friend.name;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    73
		subscription = "both";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    74
		groups = friend.groups or {};
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    75
	};
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    76
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    77
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    78
-- Returns a handler function to consume the data returned from
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    79
-- the API, compare it to the user's current roster, and perform
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    80
-- any actions necessary (roster pushes, presence probes) to
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    81
-- synchronize them.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    82
local function updated_friends_handler(username, cb)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    83
	return (function (ok, code, friends)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    84
		if not ok then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    85
			cb(false, code);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    86
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    87
		local user = sessions[username];
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    88
		local roster = user.roster;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    89
		local old_contacts = set.new(array.collect(it.keys(roster)));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    90
		local new_contacts = set.new(array.collect(it.keys(friends)));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    91
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    92
		-- These two entries are not real contacts, ignore them
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    93
		old_contacts:remove(false);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    94
		old_contacts:remove("pending");
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    95
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    96
		module:log("debug", "New friends list of %s: %s", username, json.encode(friends));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    97
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    98
		-- Calculate which contacts have been added/removed since
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
    99
		-- the last time we fetched the roster
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   100
		local added_contacts = new_contacts - old_contacts;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   101
		local removed_contacts = old_contacts - new_contacts;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   102
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   103
		local added, removed = 0, 0;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   104
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   105
		-- Add new contacts and notify connected clients
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   106
		for contact_jid in added_contacts do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   107
			module:log("debug", "Processing new friend of %s: %s", username, contact_jid);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   108
			roster[contact_jid] = friend_to_roster_item(friends[contact_jid]);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   109
			roster_push(username, roster, contact_jid);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   110
			send_presence(username, contact_jid, true);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   111
			added = added + 1;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   112
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   113
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   114
		-- Remove contacts and notify connected clients
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   115
		for contact_jid in removed_contacts do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   116
			module:log("debug", "Processing removed friend of %s: %s", username, contact_jid);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   117
			roster[contact_jid] = nil;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   118
			roster_push(username, roster, contact_jid);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   119
			send_presence(username, contact_jid, false);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   120
			removed = removed + 1;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   121
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   122
		module:log("debug", "User %s: added %d new contacts, removed %d contacts", username, added, removed);
2635
2bfa7d476092 mod_http_roster_admin: Don't call callback if it's nil
JC Brand <jc@opkode.com>
parents: 2626
diff changeset
   123
		if cb ~= nil then
2bfa7d476092 mod_http_roster_admin: Don't call callback if it's nil
JC Brand <jc@opkode.com>
parents: 2626
diff changeset
   124
			cb(true);
2bfa7d476092 mod_http_roster_admin: Don't call callback if it's nil
JC Brand <jc@opkode.com>
parents: 2626
diff changeset
   125
		end
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   126
	end);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   127
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   128
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   129
-- Fetch the named user's roster from the API, call callback (cb)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   130
-- with status and result (friends list) when received.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   131
function fetch_roster(username, cb)
2214
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   132
	local x = {headers = {}};
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   133
	x["headers"]["ACCEPT"] = "application/json, text/plain, */*";
2445
68ebc52222dc Log URL called by http_roster_admin
JC Brand <jc@opkode.com>
parents: 2214
diff changeset
   134
	module:log("debug", "Fetching roster at URL: %s", roster_url:format(username));
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   135
	local ok, err = http.request(
2214
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   136
		roster_url:format(username),
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   137
		x,
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   138
		function (roster_data, code)
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   139
			if code ~= 200 then
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   140
				module:log("error", "Error fetching roster from %s (code %d): %s", roster_url:format(username), code, tostring(roster_data):sub(1, 40):match("^[^\r\n]+"));
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   141
				if code ~= 0 then
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   142
					cb(nil, code, roster_data);
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   143
				end
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   144
				return;
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   145
			end
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   146
			module:log("debug", "Successfully fetched roster for %s", username);
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   147
			module:log("debug", "The roster data is %s", roster_data);
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   148
			cb(true, code, json.decode(roster_data));
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   149
		end
126d79bf079b mod_http_roster_admin: Also log if the status error is 0 (Connection refused)
JC Brand <jcbrand@minddistrict.com>
parents: 2165
diff changeset
   150
	);
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   151
	if not ok then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   152
		module:log("error", "Failed to connect to roster API at %s: %s", roster_url:format(username), err);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   153
		cb(false, 0, err);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   154
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   155
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   156
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   157
-- Fetch the named user's roster from the API, synchronize it with
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   158
-- the user's current roster. Notify callback (cb) with true/false
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   159
-- depending on success or failure.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   160
function refresh_roster(username, cb)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   161
	local user = sessions[username];
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   162
	if not (user and user.roster) then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   163
		module:log("debug", "User's (%q) roster updated, but they are not online - ignoring", username);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   164
		cb(true);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   165
		return;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   166
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   167
	fetch_roster(username, updated_friends_handler(username, cb));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   168
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   169
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   170
--- Roster protocol handling ---
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   171
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   172
-- Build a reply to a "roster get" request
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   173
local function build_roster_reply(stanza, roster_data)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   174
	local roster = st.reply(stanza)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   175
		:tag("query", { xmlns = "jabber:iq:roster" });
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   176
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   177
	for jid, item in pairs(roster_data) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   178
		if jid and jid ~= "pending" then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   179
			roster:tag("item", {
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   180
				jid = jid,
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   181
				subscription = item.subscription,
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   182
				ask = item.ask,
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   183
				name = item.name,
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   184
			});
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   185
			for group in pairs(item.groups) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   186
				roster:tag("group"):text(group):up();
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   187
			end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   188
			roster:up(); -- move out from item
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   189
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   190
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   191
	return roster;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   192
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   193
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   194
-- Handle clients requesting their roster (generally at login)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   195
-- This will not work if mod_roster is loaded (in 0.9).
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   196
module:hook("iq-get/self/jabber:iq:roster:query", function(event)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   197
	local session, stanza = event.origin, event.stanza;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   198
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   199
	session.interested = true; -- resource is interested in roster updates
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   200
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   201
	local roster = session.roster;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   202
	if roster[false].downloaded then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   203
		return session.send(build_roster_reply(stanza, roster));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   204
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   205
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   206
	-- It's possible that we can call this more than once for a new roster
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   207
	-- Should happen rarely (multiple clients of the same user request the
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   208
	-- roster in the time it takes the API to respond). Currently we just
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   209
	-- issue multiple requests, as it's harmless apart from the wasted
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   210
	-- requests.
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   211
	fetch_roster(session.username, function (ok, code, friends)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   212
		if not ok then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   213
			session.send(st.error_reply(stanza, "cancel", "internal-server-error"));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   214
			session:close("internal-server-error");
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   215
			return;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   216
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   217
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   218
		-- Are we the first callback to handle the downloaded roster?
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   219
		local first = roster[false].downloaded == nil;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   220
		if first then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   221
			-- Fill out new roster
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   222
			for jid, friend in pairs(friends) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   223
				roster[jid] = friend_to_roster_item(friend);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   224
			end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   225
		end
2621
7c3a1688e385 Purge the roster from RAM when the user logs off.
JC Brand <jc@opkode.com>
parents: 2445
diff changeset
   226
7c3a1688e385 Purge the roster from RAM when the user logs off.
JC Brand <jc@opkode.com>
parents: 2445
diff changeset
   227
		roster[false].downloaded = true;
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   228
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   229
		-- Send full roster to client
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   230
		session.send(build_roster_reply(stanza, roster));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   231
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   232
		if not first then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   233
			-- We already had a roster, make sure to handle any changes...
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   234
			updated_friends_handler(session.username, nil)(ok, code, friends);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   235
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   236
	end);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   237
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   238
	return true;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   239
end);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   240
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   241
-- Prevent client from making changes to the roster. This will not
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   242
-- work if mod_roster is loaded (in 0.9).
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   243
module:hook("iq-set/self/jabber:iq:roster:query", function(event)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   244
	local session, stanza = event.origin, event.stanza;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   245
	return session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   246
end);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   247
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   248
--- HTTP endpoint to trigger roster refresh ---
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   249
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   250
-- Handles updating for a single user: GET /roster_admin/refresh/USERNAME
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   251
function handle_refresh_single(event, username)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   252
	refresh_roster(username, function (ok, code, err)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   253
		event.response.headers["Content-Type"] = "application/json";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   254
		event.response:send(json.encode({
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   255
			status = ok and "ok" or "error";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   256
			message = err or "roster update complete";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   257
		}));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   258
	end);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   259
	return true;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   260
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   261
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   262
-- Handles updating for multiple users: POST /roster_admin/refresh
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   263
-- Payload should be a JSON array of usernames, e.g. ["user1", "user2", "user3"]
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   264
function handle_refresh_multi(event)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   265
	local users = json.decode(event.request.body);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   266
	if not users then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   267
		module:log("warn", "Multi-user refresh attempted with missing/invalid payload");
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   268
		event.response:send(400);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   269
		return true;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   270
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   271
	
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   272
	local count, count_err = 0, 0;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   273
	
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   274
	local function cb(ok)
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   275
		count = count + 1;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   276
		if not ok then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   277
			count_err = count_err + 1;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   278
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   279
		
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   280
		if count == #users then
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   281
			event.response.headers["Content-Type"] = "application/json";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   282
			event.response:send(json.encode({
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   283
				status = "ok";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   284
				message = "roster update complete";
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   285
				updated = count - count_err;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   286
				errors = count_err;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   287
			}));
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   288
		end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   289
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   290
	
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   291
	for _, username in ipairs(users) do
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   292
		refresh_roster(username, cb);
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   293
	end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   294
	
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   295
	return true;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   296
end
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   297
3342
7d2400710d65 mod_http_roster_admin: Add explicit dependency on mod_http
Kim Alvefur <zash@zash.se>
parents: 3320
diff changeset
   298
module:depends("http");
2165
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   299
module:provides("http", {
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   300
	route = {
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   301
		["POST /refresh"] = handle_refresh_multi;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   302
		["GET /refresh/*"] = handle_refresh_single;
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   303
	};
95a9f2d234da Add mod_http_roster_admin
JC Brand <jc@opkode.com>
parents:
diff changeset
   304
});