--- a/mod_data_access/mod_data_access.lua Mon May 21 21:30:51 2012 +0200
+++ b/mod_data_access/mod_data_access.lua Mon May 21 22:10:28 2012 +0200
@@ -2,9 +2,10 @@
-- By Kim Alvefur <zash@zash.se>
local t_concat = table.concat;
+local t_insert = table.insert;
local jid_prep = require "util.jid".prep;
local jid_split = require "util.jid".split;
-local um_test_pw = require "core.usermanager".test_password;
+local test_password = require "core.usermanager".test_password;
local is_admin = require "core.usermanager".is_admin
local dm_load = require "util.datamanager".load;
local dm_store = require "util.datamanager".store;
@@ -12,16 +13,9 @@
local dm_list_store = require "util.datamanager".list_store;
local dm_list_append = require "util.datamanager".list_append;
local b64_decode = require "util.encodings".base64.decode;
-local http = require "net.http";
-local urldecode = http.urldecode;
-local urlencode = http.urlencode;
-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 saslprep = require "util.encodings".stringprep.saslprep;
+local realm = module:get_host() .. "/" .. module:get_name();
+module:depends"http";
local encoders = {
lua = require "util.serialization".serialize,
@@ -35,122 +29,124 @@
["text/x-lua"] = "lua"; lua = "text/x-lua";
["application/json"] = "json"; json = "application/json";
}
---[[
-encoders.xml = function(data)
- return "<?xml version='1.0' encoding='utf-8'?><todo:write-this-serializer/>";
-end --]]
-local allowed_methods = {
- GET = true, "GET",
- PUT = true, "PUT",
- POST = true, "POST",
-}
-
-local function handle_request(method, body, request)
- if not allowed_methods[method] then
- return http_response(405, "Method Not Allowed", {["Allow"] = t_concat(allowed_methods, ", ")});
- end
-
- if not request.headers["authorization"] then
- return http_response(401, "Unauthorized",
- {["WWW-Authenticate"]='Basic realm="WallyWorld"'})
+local function require_valid_user(f)
+ return function(event, path)
+ local request = event.request;
+ local response = event.response;
+ local headers = request.headers;
+ if not headers.authorization then
+ response.headers.www_authenticate = ("Basic realm=%q"):format(realm);
+ return 401
+ end
+ local from_jid, password = b64_decode(headers.authorization:match"[^ ]*$"):match"([^:]*):(.*)";
+ from_jid = jid_prep(from_jid);
+ password = saslprep(password);
+ if from_jid and password then
+ local user, host = jid_split(from_jid);
+ local ok, err = test_password(user, host, password);
+ if ok and user and host then
+ return f(event, path, from_jid);
+ elseif err then
+ module:log("debug", "User failed authentication: %s", err);
+ end
+ end
+ return 401
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
+end
- module:log("debug", "authz %s", user)
- if not um_test_pw(user_node, user_host, password) then
- return http_response(401, "Unauthorized");
- end
+local function handle_request(event, path, authed_user)
+ local request, response = event.request, event.response;
- module:log("debug", "spliting path");
- local path = {};
- for i in string.gmatch(request.url.path, "[^/]+") do
- table.insert(path, i);
+ --module:log("debug", "spliting path");
+ local path_items = {};
+ for i in string.gmatch(path, "[^/]+") do
+ t_insert(path_items, i);
end
- table.remove(path, 1); -- the first /data
- module:log("debug", "split path, got %d parts: %s", #path, table.concat(path, ", "));
+ --module:log("debug", "split path, got %d parts: %s", #path_items, table.concat(path_items, ", "));
- 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);
+ local user_node, user_host = jid_split(authed_user);
+ if #path_items < 3 then
+ --module:log("debug", "since we need at least 3 parts, adding %s/%s", user_host, user_node);
+ t_insert(path_items, 1, user_node);
+ t_insert(path_items, 1, user_host);
--return http_response(400, "Bad Request");
end
- if #path < 3 then
- return http_response(404, "Not Found");
+ if #path_items < 3 then
+ return 404;
end
- local p_host, p_user, p_store, p_type = unpack(path);
+ local p_host, p_user, p_store, p_type = unpack(path_items);
if not p_store or not p_store:match("^[%a_]+$") then
- return http_response(404, "Not Found");
+ return 404;
end
- if user_host ~= path[1] or user_node ~= path[2] then
+ if user_host ~= path_items[1] or user_node ~= path_items[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, p_user, p_host, p_store)
- if not is_admin(user, p_host) then
- return http_response(403, "Forbidden");
+ --module:log("debug", "%s wants access to %s@%s[%s], is admin?", authed_user, p_user, p_host, p_store)
+ if not is_admin(user_node, p_host) then
+ return 403;
end
end
+ local method = request.method;
if method == "GET" then
local data = dm_load(p_user, p_host, p_store);
- data = data or dm_load_list(p_user, p_host, p_store);
+ data = data or dm_list_load(p_user, p_host, p_store);
--TODO Use the Accept header
- content_type = p_type or "json";
+ local content_type = p_type or "json";
if data and encoders[content_type] then
- return {
- status = "200 OK",
- body = encoders[content_type](data) .. "\n",
- headers = {["content-type"] = content_type_map[content_type].."; charset=utf-8"}
- };
+ response.headers.content_type = content_type_map[content_type].."; charset=utf-8";
+ return encoders[content_type](data);
else
- return http_response(404, "Not Found");
+ return 404;
end
- else -- POST or PUT
+ elseif method == "POST" or method == "PUT" then
+ local body = request.body;
if not body then
- return http_response(400, "Bad Request")
+
+ return 400;
end
- local content_type, content = request.headers["content-type"], body;
+ local content_type, content = request.headers.content_type, body;
content_type = content_type and content_type_map[content_type]
- module:log("debug", "%s: %s", content_type, tostring(content));
+ --module:log("debug", "%s: %s", content_type, tostring(content));
content = content_type and decoders[content_type] and decoders[content_type](content);
- module:log("debug", "%s: %s", type(content), tostring(content));
+ --module:log("debug", "%s: %s", type(content), tostring(content));
if not content then
- return http_response(400, "Bad Request")
+ return 400;
end
local ok, err
if method == "PUT" then
ok, err = dm_store(p_user, p_host, p_store, content);
elseif method == "POST" then
ok, err = dm_list_append(p_user, p_host, p_store, content);
- elseif method == "DELETE" then
- dm_store(p_user, p_host, p_store, nil);
- dm_list_store(p_user, p_host, p_store, nil);
end
if ok then
- return http_response(201, "Created", { Location = t_concat({"/data",p_host,p_user,p_store}, "/") });
+ response.headers.location = t_concat({module:http_url(nil,"/data"),p_host,p_user,p_store}, "/");
+ return 201;
else
- return { status = "500 Internal Server Error", body = err }
+ response.headers.debug = err;
+ return 500;
end
+ elseif method == "DELETE" then
+ dm_store(p_user, p_host, p_store, nil);
+ dm_list_store(p_user, p_host, p_store, nil);
+ return 204;
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
+local handle_request_with_auth = require_valid_user(handle_request);
+
+module:provides("http", {
+ default_path = "/data";
+ route = {
+ ["GET /*"] = handle_request_with_auth,
+ ["PUT /*"] = handle_request_with_auth,
+ ["POST /*"] = handle_request_with_auth,
+ ["DELETE /*"] = handle_request_with_auth,
+ };
+});