mod_data_access/mod_data_access.lua
changeset 461 bbea8081c865
parent 455 52f2188ec47d
child 486 b84493ef1d1d
equal deleted inserted replaced
460:9bb9343f3c7a 461:bbea8081c865
     1 -- HTTP Access to datamanager
     1 -- HTTP Access to datamanager
     2 -- By Kim Alvefur <zash@zash.se>
     2 -- By Kim Alvefur <zash@zash.se>
     3 
     3 
     4 local t_concat = table.concat;
       
     5 local jid_prep = require "util.jid".prep;
     4 local jid_prep = require "util.jid".prep;
     6 local jid_split = require "util.jid".split;
     5 local jid_split = require "util.jid".split;
     7 local um_test_pw = require "core.usermanager".test_password;
     6 local um_test_pw = require "core.usermanager".test_password;
     8 local is_admin = require "core.usermanager".is_admin
     7 local is_admin = require "core.usermanager".is_admin
     9 local dm_load = require "util.datamanager".load;
     8 local dm_load = require "util.datamanager".load;
    10 local dm_store = require "util.datamanager".store;
       
    11 local dm_list_load = require "util.datamanager".list_load;
     9 local dm_list_load = require "util.datamanager".list_load;
    12 local dm_list_append = require "util.datamanager".list_append;
       
    13 local b64_decode = require "util.encodings".base64.decode;
    10 local b64_decode = require "util.encodings".base64.decode;
    14 local http = require "net.http";
    11 --local urldecode = require "net.http".urldecode;
    15 local urldecode  = http.urldecode;
    12 --[[local urlparams = --require "net.http".getQueryParams or whatever MattJ names it
    16 local urlencode  = http.urlencode;
    13 function(s)
       
    14 	if not s:match("=") then return urldecode(s); end
       
    15 	local r = {}
       
    16 	s:gsub("([^=&]*)=([^&]*)", function(k,v)
       
    17 		r[ urldecode(k) ] = urldecode(v);
       
    18 		return nil
       
    19 	end)
       
    20 	return r
       
    21 end;
       
    22 --]]
       
    23 
    17 local function http_response(code, message, extra_headers)
    24 local function http_response(code, message, extra_headers)
    18 	local response = {
    25 	local response = {
    19 		status = code .. " " .. message;
    26 		status = code .. " " .. message;
    20 		body = message .. "\n"; }
    27 		body = message .. "\n"; }
    21 	if extra_headers then response.headers = extra_headers; end
    28 	if extra_headers then response.headers = extra_headers; end
    24 
    31 
    25 local encoders = {
    32 local encoders = {
    26 	lua = require "util.serialization".serialize,
    33 	lua = require "util.serialization".serialize,
    27 	json = require "util.json".encode
    34 	json = require "util.json".encode
    28 };
    35 };
    29 local decoders = {
       
    30 	lua = require "util.serialization".deserialize,
       
    31 	json = require "util.json".decode,
       
    32 };
       
    33 local content_type_map = {
       
    34 	["text/x-lua"] = "lua"; lua = "text/x-lua";
       
    35 	["application/json"] = "json"; json = "application/json";
       
    36 }
       
    37 --[[
    36 --[[
    38 encoders.xml = function(data)
    37 encoders.xml = function(data)
    39 	return "<?xml version='1.0' encoding='utf-8'?><todo:write-this-serializer/>";
    38 	return "<?xml version='1.0' encoding='utf-8'?><todo:write-this-serializer/>";
    40 end --]]
    39 end --]]
    41 
    40 
    42 local allowed_methods = {
       
    43 	GET = true, "GET",
       
    44 	PUT = true, "PUT",
       
    45 	POST = true, "POST",
       
    46 }
       
    47 
       
    48 local function handle_request(method, body, request)
    41 local function handle_request(method, body, request)
    49 	if not allowed_methods[method] then
    42 	if request.method ~= "GET" then
    50 		return http_response(405, "Method Not Allowed", {["Allow"] = t_concat(allowed_methods, ", ")});
    43 		return http_response(405, "Method Not Allowed", {["Allow"] = "GET"});
    51 	end
    44 	end -- TODO Maybe PUT?
    52 
    45 
    53 	if not request.headers["authorization"] then
    46 	if not request.headers["authorization"] then
    54 		return http_response(401, "Unauthorized",
    47 		return http_response(401, "Unauthorized",
    55 		{["WWW-Authenticate"]='Basic realm="WallyWorld"'})
    48 		{["WWW-Authenticate"]='Basic realm="WallyWorld"'})
    56 	end
    49 	end
    83 
    76 
    84 	if #path < 3 then
    77 	if #path < 3 then
    85 		return http_response(404, "Not Found");
    78 		return http_response(404, "Not Found");
    86 	end
    79 	end
    87 
    80 
    88 	local p_host, p_user, p_store, p_type = unpack(path);
       
    89 	
       
    90 	if not p_store or not p_store:match("^[%a_]+$") then
       
    91 		return http_response(404, "Not Found");
       
    92 	end
       
    93 
       
    94 	if user_host ~= path[1] or user_node ~= path[2] then
    81 	if user_host ~= path[1] or user_node ~= path[2] then
    95 		-- To only give admins acces to anything, move the inside of this block after authz
    82 		-- To only give admins acces to anything, move the inside of this block after authz
    96 		module:log("debug", "%s wants access to %s@%s[%s], is admin?", user, p_user, p_host, p_store)
    83 		module:log("debug", "%s wants access to %s@%s[%s], is admin?", user, path[2], path[1], path[3])
    97 		if not is_admin(user, p_host) then
    84 		if not is_admin(user, path[1]) then
    98 			return http_response(403, "Forbidden");
    85 			return http_response(403, "Forbidden");
    99 		end
    86 		end
   100 	end
    87 	end
   101 
    88 
   102 	if method == "GET" then
    89 	local data = dm_load(path[2], path[1], path[3]);
   103 		local data = dm_load(p_user, p_host, p_store);
    90 	
       
    91 	data = data or dm_list_load(path[2], path[1], path[3]);
   104 
    92 
   105 		data = data or dm_load_list(p_user, p_host, p_store);
    93 	if data and encoders[path[4] or "json"] then 
   106 
    94 		return {
   107 		--TODO Use the Accept header
    95 			status = "200 OK",
   108 		content_type = p_type or "json";
    96 			body = encoders[path[4] or "json"](data) .. "\n",
   109 		if data and encoders[content_type] then 
    97 			headers = {["content-type"] = "text/plain; charset=utf-8"}
   110 			return {
    98 			--headers = {["content-type"] = encoders[data[4] or "json"].mime .. "; charset=utf-8"}
   111 				status = "200 OK",
    99 			-- FIXME a little nicer that the above
   112 				body = encoders[content_type](data) .. "\n",
   100 			-- Also, would be cooler to use the Accept header, but parsing it ...
   113 				headers = {["content-type"] = content_type_map[content_type].."; charset=utf-8"}
   101 		};
   114 			};
   102 	else
   115 		else
   103 		return http_response(404, "Not Found");
   116 			return http_response(404, "Not Found");
       
   117 		end
       
   118 	else -- POST or PUT
       
   119 		if not body then
       
   120 			return http_response(400, "Bad Request")
       
   121 		end
       
   122 		local content_type, content = request.headers["content-type"], body;
       
   123 		content_type = content_type and content_type_map[content_type]
       
   124 		module:log("debug", "%s: %s", content_type, tostring(content));
       
   125 		content = content_type and decoders[content_type] and decoders[content_type](content);
       
   126 		module:log("debug", "%s: %s", type(content), tostring(content));
       
   127 		if not content then
       
   128 			return http_response(400, "Bad Request")
       
   129 		end
       
   130 		local ok, err
       
   131 		if method == "PUT" then
       
   132 			ok, err = dm_store(p_user, p_host, p_store, content);
       
   133 		elseif method == "POST" then
       
   134 			ok, err = dm_list_append(p_user, p_host, p_store, content);
       
   135 		end
       
   136 		if ok then
       
   137 			return http_response(201, "Created", { Location = t_concat({"/data",p_host,p_user,p_store}, "/") });
       
   138 		else
       
   139 			return { status = "500 Internal Server Error", body = err }
       
   140 		end
       
   141 	end
   104 	end
   142 end
   105 end
   143 
   106 
   144 local function setup()
   107 local function setup()
   145 	local ports = module:get_option("data_access_ports") or { 5280 };
   108 	local ports = module:get_option("data_access_ports") or { 5280 };