Mercurial > prosody-modules
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 }; |
