comparison mod_rest/mod_rest.lua @ 3876:75b330d4fa6f

mod_rest: Add support for HTTP Basic username and password authentication
author Kim Alvefur <zash@zash.se>
date Sat, 01 Feb 2020 13:03:18 +0100
parents 505ae524b635
children 3b31ff7b4c7c
comparison
equal deleted inserted replaced
3875:93f71ab6cb00 3876:75b330d4fa6f
14 14
15 local allow_any_source = module:get_host_type() == "component"; 15 local allow_any_source = module:get_host_type() == "component";
16 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true); 16 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true);
17 local secret = assert(module:get_option_string("rest_credentials"), "rest_credentials is a required setting"); 17 local secret = assert(module:get_option_string("rest_credentials"), "rest_credentials is a required setting");
18 local auth_type = assert(secret:match("^%S+"), "Format of rest_credentials MUST be like 'Bearer secret'"); 18 local auth_type = assert(secret:match("^%S+"), "Format of rest_credentials MUST be like 'Bearer secret'");
19 assert(auth_type == "Bearer", "Only 'Bearer' is supported in rest_credentials"); 19 assert(auth_type == "Bearer" or auth_type == "Basic", "Only 'Bearer' and 'Basic' are supported in rest_credentials");
20 20
21 local jsonmap = module:require"jsonmap"; 21 local jsonmap = module:require"jsonmap";
22 -- Bearer token 22 -- Bearer token
23 local function check_credentials(request) 23 local function check_credentials(request)
24 return request.headers.authorization == secret; 24 return request.headers.authorization == secret;
25 end
26 if secret == "Basic" and module:get_host_type() == "local" then
27 local um = require "core.usermanager";
28 local encodings = require "util.encodings";
29 local base64 = encodings.base64;
30
31 function check_credentials(request)
32 local creds = string.match(request.headers.authorization, "^Basic%s+([A-Za-z0-9+/]+=?=?)%s*$");
33 if not creds then return false; end
34 creds = base64.decode(creds);
35 if not creds then return false; end
36 local username, password = string.match(creds, "^([^:]+):(.*)$");
37 if not username then return false; end
38 username, password = encodings.stringprep.nodeprep(username), encodings.stringprep.saslprep(password);
39 if not username then return false; end
40 module:log("debug", "usermanager.test_password(%q, %q, %q)", username, module.host, string.rep("*", #password))
41 if not um.test_password(username, module.host, password) then
42 return false;
43 end
44 return jid.join(username, module.host);
45 end
25 end 46 end
26 47
27 local function parse(mimetype, data) 48 local function parse(mimetype, data)
28 mimetype = mimetype and mimetype:match("^[^; ]*"); 49 mimetype = mimetype and mimetype:match("^[^; ]*");
29 if mimetype == "application/xmpp+xml" then 50 if mimetype == "application/xmpp+xml" then
62 return tostring(s); 83 return tostring(s);
63 end 84 end
64 85
65 local function handle_post(event) 86 local function handle_post(event)
66 local request, response = event.request, event.response; 87 local request, response = event.request, event.response;
88 local from = module.host;
67 if not request.headers.authorization then 89 if not request.headers.authorization then
68 response.headers.www_authenticate = ("%s realm=%q"):format(auth_type, module.host.."/"..module.name); 90 response.headers.www_authenticate = ("%s realm=%q"):format(auth_type, module.host.."/"..module.name);
69 return 401; 91 return 401;
70 elseif not check_credentials(request) then 92 else
71 return 401; 93 local authz = check_credentials(request);
94 if not authz then
95 return 401;
96 end
97 if type(authz) == "string" then
98 from = authz;
99 end
72 end 100 end
73 local payload, err = parse(request.headers.content_type, request.body); 101 local payload, err = parse(request.headers.content_type, request.body);
74 if not payload then 102 if not payload then
75 -- parse fail 103 -- parse fail
76 return errors.new({ code = 400, text = "Failed to parse payload" }, { error = err, type = request.headers.content_type, data = request.body }); 104 return errors.new({ code = 400, text = "Failed to parse payload" }, { error = err, type = request.headers.content_type, data = request.body });
82 end 110 end
83 local to = jid.prep(payload.attr.to); 111 local to = jid.prep(payload.attr.to);
84 if not to then 112 if not to then
85 return errors.new({ code = 422, text = "Invalid destination JID" }); 113 return errors.new({ code = 422, text = "Invalid destination JID" });
86 end 114 end
87 local from = module.host;
88 if allow_any_source and payload.attr.from then 115 if allow_any_source and payload.attr.from then
89 from = jid.prep(payload.attr.from); 116 from = jid.prep(payload.attr.from);
90 if not from then 117 if not from then
91 return errors.new({ code = 422, text = "Invalid source JID" }); 118 return errors.new({ code = 422, text = "Invalid source JID" });
92 end 119 end