comparison mod_http_oauth2/mod_http_oauth2.lua @ 5477:5986e0edd7a3

mod_http_oauth2: Use validated redirect URI when returning errors to client Parsing it from the query again without the validation done by get_redirect_uri() may lead to open redirect issues.
author Kim Alvefur <zash@zash.se>
date Thu, 18 May 2023 14:17:58 +0200
parents 575f52b15f5a
children af105c7a24b2
comparison
equal deleted inserted replaced
5476:575f52b15f5a 5477:5986e0edd7a3
604 -- OAuth errors should be returned to the client if possible, i.e. by 604 -- OAuth errors should be returned to the client if possible, i.e. by
605 -- appending the error information to the redirect_uri and sending the 605 -- appending the error information to the redirect_uri and sending the
606 -- redirect to the user-agent. In some cases we can't do this, e.g. if 606 -- redirect to the user-agent. In some cases we can't do this, e.g. if
607 -- the redirect_uri is missing or invalid. In those cases, we render an 607 -- the redirect_uri is missing or invalid. In those cases, we render an
608 -- error directly to the user-agent. 608 -- error directly to the user-agent.
609 local function error_response(request, err) 609 local function error_response(request, redirect_uri, err)
610 local q = request.url.query and http.formdecode(request.url.query);
611 local redirect_uri = q and q.redirect_uri;
612 if not redirect_uri or not is_secure_redirect(redirect_uri) then 610 if not redirect_uri or not is_secure_redirect(redirect_uri) then
613 module:log("warn", "Missing or invalid redirect_uri %q, rendering error to user-agent", redirect_uri); 611 module:log("warn", "Missing or invalid redirect_uri %q, rendering error to user-agent", redirect_uri);
614 return render_error(err); 612 return render_error(err);
615 end 613 end
614 local q = request.url.query and http.formdecode(request.url.query);
616 local redirect_query = url.parse(redirect_uri); 615 local redirect_query = url.parse(redirect_uri);
617 local sep = redirect_query.query and "&" or "?"; 616 local sep = redirect_query.query and "&" or "?";
618 redirect_uri = redirect_uri 617 redirect_uri = redirect_uri
619 .. sep .. http.formencode(err.extra.oauth2_response) 618 .. sep .. http.formencode(err.extra.oauth2_response)
620 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() }); 619 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() });
701 700
702 if not ok then 701 if not ok then
703 return render_error(oauth_error("invalid_request", "Invalid 'client_id' parameter")); 702 return render_error(oauth_error("invalid_request", "Invalid 'client_id' parameter"));
704 end 703 end
705 704
706 if not get_redirect_uri(client, params.redirect_uri) then 705 local redirect_uri = get_redirect_uri(client, params.redirect_uri);
706 if not redirect_uri then
707 return render_error(oauth_error("invalid_request", "Invalid 'redirect_uri' parameter")); 707 return render_error(oauth_error("invalid_request", "Invalid 'redirect_uri' parameter"));
708 end 708 end
709 -- From this point we know that redirect_uri is safe to use 709 -- From this point we know that redirect_uri is safe to use
710 710
711 local client_response_types = set.new(array(client.response_types or { "code" })); 711 local client_response_types = set.new(array(client.response_types or { "code" }));
712 client_response_types = set.intersection(client_response_types, allowed_response_type_handlers); 712 client_response_types = set.intersection(client_response_types, allowed_response_type_handlers);
713 if not client_response_types:contains(params.response_type) then 713 if not client_response_types:contains(params.response_type) then
714 return error_response(request, oauth_error("invalid_client", "'response_type' not allowed")); 714 return error_response(request, redirect_uri, oauth_error("invalid_client", "'response_type' not allowed"));
715 end 715 end
716 716
717 local requested_scopes = parse_scopes(params.scope or ""); 717 local requested_scopes = parse_scopes(params.scope or "");
718 if client.scope then 718 if client.scope then
719 local client_scopes = set.new(parse_scopes(client.scope)); 719 local client_scopes = set.new(parse_scopes(client.scope));
736 local scopes, roles = split_scopes(requested_scopes); 736 local scopes, roles = split_scopes(requested_scopes);
737 roles = user_assumable_roles(auth_state.user.username, roles); 737 roles = user_assumable_roles(auth_state.user.username, roles);
738 return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes+roles }, true); 738 return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes+roles }, true);
739 elseif not auth_state.consent then 739 elseif not auth_state.consent then
740 -- Notify client of rejection 740 -- Notify client of rejection
741 return error_response(request, oauth_error("access_denied")); 741 return error_response(request, redirect_uri, oauth_error("access_denied"));
742 end 742 end
743 -- else auth_state.consent == true 743 -- else auth_state.consent == true
744 744
745 local granted_scopes = auth_state.scopes 745 local granted_scopes = auth_state.scopes
746 if client.scope then 746 if client.scope then
762 nonce = params.nonce; 762 nonce = params.nonce;
763 }); 763 });
764 local response_type = params.response_type; 764 local response_type = params.response_type;
765 local response_handler = response_type_handlers[response_type]; 765 local response_handler = response_type_handlers[response_type];
766 if not response_handler then 766 if not response_handler then
767 return error_response(request, oauth_error("unsupported_response_type")); 767 return error_response(request, redirect_uri, oauth_error("unsupported_response_type"));
768 end 768 end
769 local ret = response_handler(client, params, user_jid, id_token); 769 local ret = response_handler(client, params, user_jid, id_token);
770 if errors.is_err(ret) then 770 if errors.is_err(ret) then
771 return error_response(request, ret); 771 return error_response(request, redirect_uri, ret);
772 end 772 end
773 return ret; 773 return ret;
774 end 774 end
775 775
776 local function handle_revocation_request(event) 776 local function handle_revocation_request(event)