changeset 318:84caab2bc02c

mod_data_access: New plugin providing a HTTP interface to Prosodys datamanager
author Kim Alvefur <zash@zash.se>
date Wed, 19 Jan 2011 20:18:38 +0100
parents 4f78f5020aa9
children ba2e78661ea8
files mod_data_access/mod_data_access.lua
diffstat 1 files changed, 115 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_data_access/mod_data_access.lua	Wed Jan 19 20:18:38 2011 +0100
@@ -0,0 +1,115 @@
+-- HTTP Access to datamanager
+-- By Kim Alvefur <zash@zash.se>
+
+local jid_prep = require "util.jid".prep;
+local jid_split = require "util.jid".split;
+local um_test_pw = require "core.usermanager".test_password;
+local is_admin = require "core.usermanager".is_admin
+local dm_load = require "util.datamanager".load;
+local dm_list_load = require "util.datamanager".list_load;
+local b64_decode = require "util.encodings".base64.decode;
+--local urldecode = require "net.http".urldecode;
+--[[local urlparams = --require "net.http".getQueryParams or whatever MattJ names it
+function(s)
+	if not s:match("=") then return urldecode(s); end
+	local r = {}
+	s:gsub("([^=&]*)=([^&]*)", function(k,v)
+		r[ urldecode(k) ] = urldecode(v);
+		return nil
+	end)
+	return r
+end;
+--]]
+
+local function http_response(code, message, extra_headers)
+	local response = {
+		status = code .. " " .. message;
+		body = message .. "\n"; }
+	if extra_headers then response.headers = extra_headers; end
+	return response
+end
+
+local encoders = {
+	lua = require "util.serialization".serialize,
+	json = require "util.json".encode
+};
+--[[
+encoders.xml = function(data)
+	return "<?xml version='1.0' encoding='utf-8'?><todo:write-this-serializer/>";
+end --]]
+
+local function handle_request(method, body, request)
+	if request.method ~= "GET" then
+		return http_response(405, "Method Not Allowed", {["Allow"] = "GET"});
+	end -- TODO Maybe PUT?
+
+	if not request.headers["authorization"] then
+		return http_response(401, "Unauthorized",
+		{["WWW-Authenticate"]='Basic realm="WallyWorld"'})
+	end
+	local user, password = b64_decode(request.headers.authorization
+		:match("[^ ]*$") or ""):match("([^:]*):(.*)");
+	user = jid_prep(user);
+	if not user or not password then return http_response(400, "Bad Request"); end
+	local user_node, user_host = jid_split(user)
+	if not hosts[user_host] then return http_response(401, "Unauthorized"); end
+
+	module:log("debug", "authz %s", user)
+	if not um_test_pw(user_node, user_host, password) then
+		return http_response(401, "Unauthorized");
+	end
+
+	module:log("debug", "spliting path");
+	local path = {};
+	for i in string.gmatch(request.url.path, "[^/]+") do
+		table.insert(path, i);
+	end
+	table.remove(path, 1); -- the first /data
+	module:log("debug", "split path, got %d parts: %s", #path, table.concat(path, ", "));
+
+	if #path < 3 then
+		module:log("debug", "since we need at least 3 parts, adding %s/%s", user_host, user_node);
+		table.insert(path, 1, user_node);
+		table.insert(path, 1, user_host);
+		--return http_response(400, "Bad Request");
+	end
+
+	if #path < 3 then
+		return http_response(404, "Not Found");
+	end
+
+	if user_host ~= path[1] or user_node ~= path[2] then
+		-- To only give admins acces to anything, move the inside of this block after authz
+		module:log("debug", "%s wants access to %s@%s[%s], is admin?", user, path[2], path[1], path[3])
+		if not is_admin(user, path[1]) then
+			return http_response(403, "Forbidden");
+		end
+	end
+
+	local data = dm_load(path[2], path[1], path[3]);
+	
+	data = data or dm_list_load(path[2], path[1], path[3]);
+
+	if data and encoders[path[4] or "json"] then 
+		return {
+			status = "200 OK",
+			body = encoders[path[4] or "json"](data) .. "\n",
+			headers = {["content-type"] = "text/plain; charset=utf-8"}
+			--headers = {["content-type"] = encoders[data[4] or "json"].mime .. "; charset=utf-8"}
+			-- FIXME a little nicer that the above
+			-- Also, would be cooler to use the Accept header, but parsing it ...
+		};
+	else
+		return http_response(404, "Not Found");
+	end
+end
+
+local function setup()
+	local ports = module:get_option("data_access_ports") or { 5280 };
+	require "net.httpserver".new_from_config(ports, handle_request, { base = "data" });
+end
+if prosody.start_time then -- already started
+	setup();
+else
+	prosody.events.add_handler("server-started", setup);
+end