Mercurial > prosody-modules
diff mod_auth_oauth_external/mod_auth_oauth_external.lua @ 5653:62c6e17a5e9d
Merge
author | Stephen Paul Weber <singpolyma@singpolyma.net> |
---|---|
date | Mon, 18 Sep 2023 08:24:19 -0500 |
parents | 4e79f344ae2f |
children | 0207fd248480 |
line wrap: on
line diff
--- a/mod_auth_oauth_external/mod_auth_oauth_external.lua Mon Sep 18 08:22:07 2023 -0500 +++ b/mod_auth_oauth_external/mod_auth_oauth_external.lua Mon Sep 18 08:24:19 2023 -0500 @@ -1,5 +1,6 @@ local http = require "net.http"; local async = require "util.async"; +local jid = require "util.jid"; local json = require "util.json"; local sasl = require "util.sasl"; @@ -15,7 +16,8 @@ -- XXX Hold up, does whatever done here even need any of these things? Are we -- the OAuth client? Is the XMPP client the OAuth client? What are we??? local client_id = module:get_option_string("oauth_external_client_id"); --- TODO -- local client_secret = module:get_option_string("oauth_external_client_secret"); +local client_secret = module:get_option_string("oauth_external_client_secret"); +local scope = module:get_option_string("oauth_external_scope", "openid"); --[[ More or less required endpoints digraph "oauth endpoints" { @@ -28,6 +30,32 @@ local host = module.host; local provider = {}; +local function not_implemented() + return nil, "method not implemented" +end + +-- With proper OAuth 2, most of these should be handled at the atuhorization +-- server, no there. +provider.test_password = not_implemented; +provider.get_password = not_implemented; +provider.set_password = not_implemented; +provider.create_user = not_implemented; +provider.delete_user = not_implemented; + +function provider.user_exists(_username) + -- Can this even be done in a generic way in OAuth 2? + -- OIDC and WebFinger perhaps? + return true; +end + +function provider.users() + -- TODO this could be done by recording known users locally + return function () + module:log("debug", "User iteration not supported"); + return nil; + end +end + function provider.get_sasl_handler() local profile = {}; profile.http_client = http.default; -- TODO configurable @@ -35,14 +63,16 @@ if token_endpoint and allow_plain then local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable function profile:plain_test(username, password, realm) + username = jid.unescape(username); -- COMPAT Mastodon local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, { headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" }; body = http.formencode({ grant_type = "password"; client_id = client_id; + client_secret = client_secret; username = map_username(username, realm); password = password; - scope = "openid"; + scope = scope; }); })) if err or not (tok.code >= 200 and tok.code < 300) then @@ -52,6 +82,12 @@ if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then return false, nil; end + if not validation_endpoint then + -- We're not going to get more info, only the username + self.username = jid.escape(username); + self.token_info = token_resp; + return true, true; + end local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, { headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } })); if err then @@ -61,36 +97,38 @@ return false, nil; end local response = json.decode(ret.body); - if type(response) ~= "table" or (response[username_field]) ~= username then + if type(response) ~= "table" then + return false, nil, nil; + elseif type(response[username_field]) ~= "string" then return false, nil, nil; end - if response.jid then - self.username, self.realm, self.resource = jid.prepped_split(response.jid, true); - end - self.role = response.role; + self.username = jid.escape(response[username_field]); self.token_info = response; return true, true; end end - function profile:oauthbearer(token) - if token == "" then - return false, nil, extra; - end + if validation_endpoint then + function profile:oauthbearer(token) + if token == "" then + return false, nil, extra; + end - local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, - { headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" } })); - if err then - return false, nil, extra; + local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, { + headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" }; + })); + if err then + return false, nil, extra; + end + local response = ret and json.decode(ret.body); + if not (ret.code >= 200 and ret.code < 300) then + return false, nil, response or extra; + end + if type(response) ~= "table" or type(response[username_field]) ~= "string" then + return false, nil, nil; + end + + return jid.escape(response[username_field]), true, response; end - local response = ret and json.decode(ret.body); - if not (ret.code >= 200 and ret.code < 300) then - return false, nil, response or extra; - end - if type(response) ~= "table" or type(response[username_field]) ~= "string" then - return false, nil, nil; - end - - return response[username_field], true, response; end return sasl.new(host, profile); end