comparison mod_http_oauth2/mod_http_oauth2.lua @ 5337:8d8e85d6dc91

mod_http_oauth2: Support OpenID UserInfo claims Actually filling in those details is left to another module because I don't really wanna mix in a dependency on PEP or mod_vcard here, those implementation details can be in a second module. Some might want to fill this from LDAP or something as well.
author Kim Alvefur <zash@zash.se>
date Mon, 10 Apr 2023 10:49:02 +0200
parents 77ac04bd2f65
children cd195283127f
comparison
equal deleted inserted replaced
5336:77ac04bd2f65 5337:8d8e85d6dc91
79 79
80 local function parse_scopes(scope_string) 80 local function parse_scopes(scope_string)
81 return array(scope_string:gmatch("%S+")); 81 return array(scope_string:gmatch("%S+"));
82 end 82 end
83 83
84 local openid_claims = set.new({ "profile"; "email"; "address"; "phone" });
85
84 local function filter_scopes(username, requested_scope_string) 86 local function filter_scopes(username, requested_scope_string)
85 local selected_role, granted_scopes = nil, array(); 87 local selected_role, granted_scopes = nil, array();
86 88
87 if requested_scope_string then -- Specific role(s) requested 89 if requested_scope_string then -- Specific role(s) requested
88 local requested_scopes = parse_scopes(requested_scope_string); 90 local requested_scopes = parse_scopes(requested_scope_string);
89 for _, scope in ipairs(requested_scopes) do 91 for _, scope in ipairs(requested_scopes) do
90 if scope == "openid" then 92 if scope == "openid" or openid_claims:contains(scope) then
91 granted_scopes:push(scope); 93 granted_scopes:push(scope);
92 end 94 end
93 if selected_role == nil and usermanager.user_can_assume_role(username, module.host, scope) then 95 if selected_role == nil and usermanager.user_can_assume_role(username, module.host, scope) then
94 selected_role = scope; 96 selected_role = scope;
95 end 97 end
756 local token_info,err = tokens.get_token_info(credentials.bearer_token); 758 local token_info,err = tokens.get_token_info(credentials.bearer_token);
757 if not token_info then 759 if not token_info then
758 module:log("debug", "UserInfo query failed token validation: %s", err) 760 module:log("debug", "UserInfo query failed token validation: %s", err)
759 return 403; 761 return 403;
760 end 762 end
761 -- TODO check that they actually have access to the userinfo endpoint, aka 763 local scopes = set.new()
762 -- the 'openid' scope. Tokens currently contain the JID in plain text so 764 if type(token_info.grant.data) == "table" and type(token_info.grant.data.oauth2_scopes) == "string" then
763 -- we're not really returning anything they did not know already. 765 scopes:add_list(parse_scopes(token_info.grant.data.oauth2_scopes));
766 else
767 module:log("debug", "token_info = %q", token_info)
768 end
769
770 if not scopes:contains("openid") then
771 module:log("debug", "Missing the 'openid' scope in %q", scopes)
772 -- The 'openid' scope is required for access to this endpoint.
773 return 403;
774 end
764 775
765 local user_info = { 776 local user_info = {
766 iss = get_issuer(); 777 iss = get_issuer();
767 sub = url.build({ scheme = "xmpp"; path = token_info.jid }); 778 sub = url.build({ scheme = "xmpp"; path = token_info.jid });
768 -- Additional UserInfo fields could be pulled from vcard4, depending on
769 -- permissions and scopes granted.
770 } 779 }
780
781 local token_claims = set.intersection(openid_claims, scopes);
782 if not token_claims:empty() then
783 -- Another module can do that
784 module:fire_event("token/userinfo", {
785 token = token_info;
786 claims = token_claims;
787 username = jid.split(token_info.jid);
788 userinfo = user_info;
789 });
790 end
791
771 return { 792 return {
772 status_code = 200; 793 status_code = 200;
773 headers = { content_type = "application/json" }; 794 headers = { content_type = "application/json" };
774 body = json.encode(user_info); 795 body = json.encode(user_info);
775 }; 796 };