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);