Mercurial > prosody-modules
comparison mod_http_oauth2/mod_http_oauth2.lua @ 5513:0005d4201030
mod_http_oauth2: Reject duplicate form-urlencoded parameters
Per RFC 6749 section 3.1
> Request and response parameters MUST NOT be included more than once.
Thanks to OAuch for pointing out
Also cleans up some of the icky behavior of formdecode(), like returning
a string if no '=' is included.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 02 Jun 2023 11:03:57 +0200 |
parents | 1fbc8718bed6 |
children | 61b8d3eb91a4 |
comparison
equal
deleted
inserted
replaced
5512:1fbc8718bed6 | 5513:0005d4201030 |
---|---|
26 return function(k) | 26 return function(k) |
27 return t[k]; | 27 return t[k]; |
28 end | 28 end |
29 end | 29 end |
30 | 30 |
31 local function strict_formdecode(query) | |
32 if not query then | |
33 return nil; | |
34 end | |
35 local params = http.formdecode(query); | |
36 if type(params) ~= "table" then | |
37 return nil, "no-pairs"; | |
38 end | |
39 local dups = {}; | |
40 for _, pair in ipairs(params) do | |
41 if dups[pair.name] then | |
42 return nil, "duplicate"; | |
43 end | |
44 dups[pair.name] = true; | |
45 end | |
46 return params; | |
47 end | |
48 | |
31 local function read_file(base_path, fn, required) | 49 local function read_file(base_path, fn, required) |
32 local f, err = io.open(base_path .. "/" .. fn); | 50 local f, err = io.open(base_path .. "/" .. fn); |
33 if not f then | 51 if not f then |
34 module:log(required and "error" or "debug", "Unable to load template file: %s", err); | 52 module:log(required and "error" or "debug", "Unable to load template file: %s", err); |
35 if required then | 53 if required then |
368 return oauth_error("invalid_redirect_uri"); | 386 return oauth_error("invalid_redirect_uri"); |
369 end | 387 end |
370 | 388 |
371 local redirect = url.parse(redirect_uri); | 389 local redirect = url.parse(redirect_uri); |
372 | 390 |
373 local query = http.formdecode(redirect.query or ""); | 391 local query = strict_formdecode(redirect.query); |
374 if type(query) ~= "table" then query = {}; end | 392 if type(query) ~= "table" then query = {}; end |
375 table.insert(query, { name = "code", value = code }); | 393 table.insert(query, { name = "code", value = code }); |
376 table.insert(query, { name = "iss", value = get_issuer() }); | 394 table.insert(query, { name = "iss", value = get_issuer() }); |
377 if params.state then | 395 if params.state then |
378 table.insert(query, { name = "state", value = params.state }); | 396 table.insert(query, { name = "state", value = params.state }); |
531 local function get_auth_state(request) | 549 local function get_auth_state(request) |
532 local form = request.method == "POST" | 550 local form = request.method == "POST" |
533 and request.body | 551 and request.body |
534 and request.body ~= "" | 552 and request.body ~= "" |
535 and request.headers.content_type == "application/x-www-form-urlencoded" | 553 and request.headers.content_type == "application/x-www-form-urlencoded" |
536 and http.formdecode(request.body); | 554 and strict_formdecode(request.body); |
537 | 555 |
538 if type(form) ~= "table" then return {}; end | 556 if type(form) ~= "table" then return {}; end |
539 | 557 |
540 if not form.user_token then | 558 if not form.user_token then |
541 -- First step: login | 559 -- First step: login |
641 -- error directly to the user-agent. | 659 -- error directly to the user-agent. |
642 local function error_response(request, redirect_uri, err) | 660 local function error_response(request, redirect_uri, err) |
643 if not redirect_uri or redirect_uri == oob_uri then | 661 if not redirect_uri or redirect_uri == oob_uri then |
644 return render_error(err); | 662 return render_error(err); |
645 end | 663 end |
646 local q = request.url.query and http.formdecode(request.url.query); | 664 local q = strict_formdecode(request.url.query); |
647 local redirect_query = url.parse(redirect_uri); | 665 local redirect_query = url.parse(redirect_uri); |
648 local sep = redirect_query.query and "&" or "?"; | 666 local sep = redirect_query.query and "&" or "?"; |
649 redirect_uri = redirect_uri | 667 redirect_uri = redirect_uri |
650 .. sep .. http.formencode(err.extra.oauth2_response) | 668 .. sep .. http.formencode(err.extra.oauth2_response) |
651 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() }); | 669 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() }); |
695 local credentials = get_request_credentials(event.request); | 713 local credentials = get_request_credentials(event.request); |
696 | 714 |
697 event.response.headers.content_type = "application/json"; | 715 event.response.headers.content_type = "application/json"; |
698 event.response.headers.cache_control = "no-store"; | 716 event.response.headers.cache_control = "no-store"; |
699 event.response.headers.pragma = "no-cache"; | 717 event.response.headers.pragma = "no-cache"; |
700 local params = http.formdecode(event.request.body); | 718 local params = strict_formdecode(event.request.body); |
701 if not params then | 719 if not params then |
702 return oauth_error("invalid_request"); | 720 return oauth_error("invalid_request"); |
703 end | 721 end |
704 | 722 |
705 if credentials and credentials.type == "basic" then | 723 if credentials and credentials.type == "basic" then |
721 | 739 |
722 -- Directly returning errors to the user before we have a validated client object | 740 -- Directly returning errors to the user before we have a validated client object |
723 if not request.url.query then | 741 if not request.url.query then |
724 return render_error(oauth_error("invalid_request", "Missing query parameters")); | 742 return render_error(oauth_error("invalid_request", "Missing query parameters")); |
725 end | 743 end |
726 local params = http.formdecode(request.url.query); | 744 local params = strict_formdecode(request.url.query); |
727 if not params then | 745 if not params then |
728 return render_error(oauth_error("invalid_request", "Invalid query parameters")); | 746 return render_error(oauth_error("invalid_request", "Invalid query parameters")); |
729 end | 747 end |
730 | 748 |
731 if not params.client_id then | 749 if not params.client_id then |
823 if not verify_client_secret(credentials.username, credentials.password) then | 841 if not verify_client_secret(credentials.username, credentials.password) then |
824 return 401; | 842 return 401; |
825 end | 843 end |
826 end | 844 end |
827 | 845 |
828 local form_data = http.formdecode(event.request.body or ""); | 846 local form_data = strict_formdecode(event.request.body); |
829 if not form_data or not form_data.token then | 847 if not form_data or not form_data.token then |
830 response.headers.accept = "application/x-www-form-urlencoded"; | 848 response.headers.accept = "application/x-www-form-urlencoded"; |
831 return 415; | 849 return 415; |
832 end | 850 end |
833 local ok, err = tokens.revoke_token(form_data.token); | 851 local ok, err = tokens.revoke_token(form_data.token); |