# HG changeset patch # User Kim Alvefur # Date 1720286767 -7200 # Node ID 9bcc26406b4734b8a42a94ec57d0bee24a8cae80 # Parent 32d1abb89dfe93a7850e3951b6e4f7447fe8454d mod_rest: Return specific errors from credential checks This is a step towards returning details of what went wrong when checking credentials, distinguishing missing, malformed, and wrong credentials. diff -r 32d1abb89dfe -r 9bcc26406b47 mod_rest/mod_rest.lua --- a/mod_rest/mod_rest.lua Thu Jul 04 16:06:32 2024 +0200 +++ b/mod_rest/mod_rest.lua Sat Jul 06 19:26:07 2024 +0200 @@ -34,35 +34,58 @@ www_authenticate_header = table.concat(header, ", "); end -local function check_credentials(request) +local post_errors = errors.init("mod_rest", { + noauthz = { code = 401; type = "auth"; condition = "not-authorized"; text = "No credentials provided" }; + unauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials not accepted" }; + malformauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials malformed" }; + prepauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials failed stringprep" }; + parse = { code = 400; type = "modify"; condition = "not-well-formed"; text = "Failed to parse payload" }; + xmlns = { code = 422; type = "modify"; condition = "invalid-namespace"; text = "'xmlns' attribute must be empty" }; + name = { code = 422; type = "modify"; condition = "unsupported-stanza-type"; text = "Invalid stanza, must be 'message', 'presence' or 'iq'." }; + to = { code = 422; type = "modify"; condition = "improper-addressing"; text = "Invalid destination JID" }; + from = { code = 422; type = "modify"; condition = "invalid-from"; text = "Invalid source JID" }; + from_auth = { code = 403; type = "auth"; condition = "not-authorized"; text = "Not authorized to send stanza with requested 'from'" }; + iq_type = { code = 422; type = "modify"; condition = "invalid-xml"; text = "'iq' stanza must be of type 'get' or 'set'" }; + iq_tags = { code = 422; type = "modify"; condition = "bad-format"; text = "'iq' stanza must have exactly one child tag" }; + mediatype = { code = 415; type = "cancel"; condition = "bad-format"; text = "Unsupported media type" }; + size = { code = 413; type = "modify"; condition = "resource-constraint", text = "Payload too large" }; +}); + +local function check_credentials(request) -- > session | boolean, error local auth_type, auth_data = string.match(request.headers.authorization, "^(%S+)%s(.+)$"); if not (auth_type and auth_data) or not auth_mechanisms:contains(auth_type) then - return false; + return nil, post_errors.new("noauthz", { request = request }); end if auth_type == "Basic" then local creds = base64.decode(auth_data); - if not creds then return false; end + if not creds then + return nil, post_errors.new("malformauthz", { request = request }); + end local username, password = string.match(creds, "^([^:]+):(.*)$"); - if not username then return false; end + if not username then + return nil, post_errors.new("malformauthz", { request = request }); + end username, password = encodings.stringprep.nodeprep(username), encodings.stringprep.saslprep(password); - if not username or not password then return false; end + if not username or not password then + return false, post_errors.new("prepauthz", { request = request }); + end if not um.test_password(username, module.host, password) then - return false; + return false, post_errors.new("unauthz", { request = request }); end - return { username = username, host = module.host }; + return { username = username; host = module.host }; elseif auth_type == "Bearer" then if tokens.get_token_session then return tokens.get_token_session(auth_data); else -- COMPAT w/0.12 local token_info = tokens.get_token_info(auth_data); if not token_info or not token_info.session then - return false; + return false, post_errors.new("unauthz", { request = request }); end return token_info.session; end end - return nil; + return nil, post_errors.new("noauthz", { request = request }); end if module:get_option_string("authentication") == "anonymous" and module:get_option_boolean("anonymous_rest") then @@ -268,21 +291,6 @@ error "unsupported encoding"; end -local post_errors = errors.init("mod_rest", { - noauthz = { code = 401; type = "auth"; condition = "not-authorized"; text = "No credentials provided" }; - unauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials not accepted" }; - parse = { code = 400; type = "modify"; condition = "not-well-formed"; text = "Failed to parse payload" }; - xmlns = { code = 422; type = "modify"; condition = "invalid-namespace"; text = "'xmlns' attribute must be empty" }; - name = { code = 422; type = "modify"; condition = "unsupported-stanza-type"; text = "Invalid stanza, must be 'message', 'presence' or 'iq'." }; - to = { code = 422; type = "modify"; condition = "improper-addressing"; text = "Invalid destination JID" }; - from = { code = 422; type = "modify"; condition = "invalid-from"; text = "Invalid source JID" }; - from_auth = { code = 403; type = "auth"; condition = "not-authorized"; text = "Not authorized to send stanza with requested 'from'" }; - iq_type = { code = 422; type = "modify"; condition = "invalid-xml"; text = "'iq' stanza must be of type 'get' or 'set'" }; - iq_tags = { code = 422; type = "modify"; condition = "bad-format"; text = "'iq' stanza must have exactly one child tag" }; - mediatype = { code = 415; type = "cancel"; condition = "bad-format"; text = "Unsupported media type" }; - size = { code = 413; type = "modify"; condition = "resource-constraint", text = "Payload too large" }; -}); - -- GET → iq-get local function parse_request(request, path) if path and request.method == "GET" then @@ -308,9 +316,10 @@ response.headers.www_authenticate = www_authenticate_header; return post_errors.new("noauthz"); else - origin = check_credentials(request); + local err; + origin, err = check_credentials(request); if not origin then - return post_errors.new("unauthz"); + return err or post_errors.new("unauthz"); end from = jid.join(origin.username, origin.host, origin.resource); origin.full_jid = from;