# HG changeset patch # User Kim Alvefur # Date 1337631028 -7200 # Node ID dd7d30c175d4c3ff5d7816686e77d66681d9fd63 # Parent 343b115ebbeae9a6de406191d289e9db90dcaa4b mod_data_access: Cleanup and update to new HTTP API diff -r 343b115ebbea -r dd7d30c175d4 mod_data_access/mod_data_access.lua --- 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 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 ""; -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, + }; +});