Software /
code /
prosody-modules
Comparison
mod_auth_oauth_external/mod_auth_oauth_external.lua @ 5673:0eb2d5ea2428
merge
author | Stephen Paul Weber <singpolyma@singpolyma.net> |
---|---|
date | Sat, 06 May 2023 19:40:23 -0500 |
parent | 5346:d9bc8712a745 |
child | 5433:b40299bbdf14 |
comparison
equal
deleted
inserted
replaced
5672:2c69577b28c2 | 5673:0eb2d5ea2428 |
---|---|
1 local http = require "net.http"; | |
2 local async = require "util.async"; | |
3 local json = require "util.json"; | |
4 local sasl = require "util.sasl"; | |
5 | |
6 local issuer_identity = module:get_option_string("oauth_external_issuer"); | |
7 local oidc_discovery_url = module:get_option_string("oauth_external_discovery_url", | |
8 issuer_identity and issuer_identity .. "/.well-known/oauth-authorization-server" or nil); | |
9 local validation_endpoint = module:get_option_string("oauth_external_validation_endpoint"); | |
10 local token_endpoint = module:get_option_string("oauth_external_token_endpoint"); | |
11 | |
12 local username_field = module:get_option_string("oauth_external_username_field", "preferred_username"); | |
13 local allow_plain = module:get_option_boolean("oauth_external_resource_owner_password", true); | |
14 | |
15 -- XXX Hold up, does whatever done here even need any of these things? Are we | |
16 -- the OAuth client? Is the XMPP client the OAuth client? What are we??? | |
17 local client_id = module:get_option_string("oauth_external_client_id"); | |
18 -- TODO -- local client_secret = module:get_option_string("oauth_external_client_secret"); | |
19 | |
20 --[[ More or less required endpoints | |
21 digraph "oauth endpoints" { | |
22 issuer -> discovery -> { registration validation } | |
23 registration -> { client_id client_secret } | |
24 { client_id client_secret validation } -> required | |
25 } | |
26 --]] | |
27 | |
28 local host = module.host; | |
29 local provider = {}; | |
30 | |
31 function provider.get_sasl_handler() | |
32 local profile = {}; | |
33 profile.http_client = http.default; -- TODO configurable | |
34 local extra = { oidc_discovery_url = oidc_discovery_url }; | |
35 if token_endpoint and allow_plain then | |
36 local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable | |
37 function profile:plain_test(username, password, realm) | |
38 local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, { | |
39 headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" }; | |
40 body = http.formencode({ | |
41 grant_type = "password"; | |
42 client_id = client_id; | |
43 username = map_username(username, realm); | |
44 password = password; | |
45 scope = "openid"; | |
46 }); | |
47 })) | |
48 if err or not (tok.code >= 200 and tok.code < 300) then | |
49 return false, nil; | |
50 end | |
51 local token_resp = json.decode(tok.body); | |
52 if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then | |
53 return false, nil; | |
54 end | |
55 local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, | |
56 { headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } })); | |
57 if err then | |
58 return false, nil; | |
59 end | |
60 if not (ret.code >= 200 and ret.code < 300) then | |
61 return false, nil; | |
62 end | |
63 local response = json.decode(ret.body); | |
64 if type(response) ~= "table" or (response[username_field]) ~= username then | |
65 return false, nil, nil; | |
66 end | |
67 if response.jid then | |
68 self.username, self.realm, self.resource = jid.prepped_split(response.jid, true); | |
69 end | |
70 self.role = response.role; | |
71 self.token_info = response; | |
72 return true, true; | |
73 end | |
74 end | |
75 function profile:oauthbearer(token) | |
76 if token == "" then | |
77 return false, nil, extra; | |
78 end | |
79 | |
80 local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, | |
81 { headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" } })); | |
82 if err then | |
83 return false, nil, extra; | |
84 end | |
85 local response = ret and json.decode(ret.body); | |
86 if not (ret.code >= 200 and ret.code < 300) then | |
87 return false, nil, response or extra; | |
88 end | |
89 if type(response) ~= "table" or type(response[username_field]) ~= "string" then | |
90 return false, nil, nil; | |
91 end | |
92 | |
93 return response[username_field], true, response; | |
94 end | |
95 return sasl.new(host, profile); | |
96 end | |
97 | |
98 module:provides("auth", provider); |