mod_auth_oauth_external/mod_auth_oauth_external.lua
author Kim Alvefur <zash@zash.se>
Mon, 08 May 2023 20:01:34 +0200
changeset 5438 92ad8f03f225
parent 5437 b40299bbdf14
child 5439 b3e7886fea6a
permissions -rw-r--r--
mod_auth_oauth_external: Work without token validation endpoint In this mode, only PLAIN is possible and the provided username is assumed to be the XMPP localpart.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     1
local http = require "net.http";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     2
local async = require "util.async";
5437
b40299bbdf14 mod_auth_oauth_external: Fix missing import of util.jid
Kim Alvefur <zash@zash.se>
parents: 5350
diff changeset
     3
local jid = require "util.jid";
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     4
local json = require "util.json";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     5
local sasl = require "util.sasl";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     6
5350
d9bc8712a745 mod_auth_oauth_external: Allow setting identity instead of discovery URL
Kim Alvefur <zash@zash.se>
parents: 5349
diff changeset
     7
local issuer_identity = module:get_option_string("oauth_external_issuer");
d9bc8712a745 mod_auth_oauth_external: Allow setting identity instead of discovery URL
Kim Alvefur <zash@zash.se>
parents: 5349
diff changeset
     8
local oidc_discovery_url = module:get_option_string("oauth_external_discovery_url",
d9bc8712a745 mod_auth_oauth_external: Allow setting identity instead of discovery URL
Kim Alvefur <zash@zash.se>
parents: 5349
diff changeset
     9
	issuer_identity and issuer_identity .. "/.well-known/oauth-authorization-server" or nil);
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    10
local validation_endpoint = module:get_option_string("oauth_external_validation_endpoint");
5349
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    11
local token_endpoint = module:get_option_string("oauth_external_token_endpoint");
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    12
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    13
local username_field = module:get_option_string("oauth_external_username_field", "preferred_username");
5349
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    14
local allow_plain = module:get_option_boolean("oauth_external_resource_owner_password", true);
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    15
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    16
-- XXX Hold up, does whatever done here even need any of these things? Are we
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    17
-- the OAuth client? Is the XMPP client the OAuth client? What are we???
5349
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    18
local client_id = module:get_option_string("oauth_external_client_id");
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    19
-- TODO -- local client_secret = module:get_option_string("oauth_external_client_secret");
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    20
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    21
--[[ More or less required endpoints
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    22
digraph "oauth endpoints" {
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    23
issuer -> discovery -> { registration validation }
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    24
registration -> { client_id client_secret }
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    25
{ client_id client_secret validation } -> required
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    26
}
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    27
--]]
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    28
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    29
local host = module.host;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    30
local provider = {};
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    31
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    32
function provider.get_sasl_handler()
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    33
	local profile = {};
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    34
	profile.http_client = http.default; -- TODO configurable
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    35
	local extra = { oidc_discovery_url = oidc_discovery_url };
5349
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    36
	if token_endpoint and allow_plain then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    37
		local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    38
		function profile:plain_test(username, password, realm)
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    39
			local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, {
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    40
				headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" };
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    41
				body = http.formencode({
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    42
					grant_type = "password";
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    43
					client_id = client_id;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    44
					username = map_username(username, realm);
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    45
					password = password;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    46
					scope = "openid";
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    47
				});
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    48
			}))
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    49
			if err or not (tok.code >= 200 and tok.code < 300) then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    50
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    51
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    52
			local token_resp = json.decode(tok.body);
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    53
			if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    54
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    55
			end
5438
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    56
			if not validation_endpoint then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    57
				-- We're not going to get more info, only the username
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    58
				self.username = jid.escape(username);
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    59
				self.token_info = token_resp;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    60
				return true, true;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    61
			end
5349
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    62
			local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint,
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    63
				{ headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } }));
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    64
			if err then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    65
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    66
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    67
			if not (ret.code >= 200 and ret.code < 300) then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    68
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    69
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    70
			local response = json.decode(ret.body);
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    71
			if type(response) ~= "table" or (response[username_field]) ~= username then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    72
				return false, nil, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    73
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    74
			if response.jid then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    75
				self.username, self.realm, self.resource = jid.prepped_split(response.jid, true);
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    76
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    77
			self.role = response.role;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    78
			self.token_info = response;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    79
			return true, true;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    80
		end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    81
	end
5438
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    82
	if validation_endpoint then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    83
		function profile:oauthbearer(token)
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    84
			if token == "" then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    85
				return false, nil, extra;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    86
			end
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    87
5438
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    88
			local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, {
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    89
				headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" };
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    90
			}));
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    91
			if err then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    92
				return false, nil, extra;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    93
			end
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    94
			local response = ret and json.decode(ret.body);
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    95
			if not (ret.code >= 200 and ret.code < 300) then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    96
				return false, nil, response or extra;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    97
			end
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    98
			if type(response) ~= "table" or type(response[username_field]) ~= "string" then
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
    99
				return false, nil, nil;
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
   100
			end
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
   101
92ad8f03f225 mod_auth_oauth_external: Work without token validation endpoint
Kim Alvefur <zash@zash.se>
parents: 5437
diff changeset
   102
			return response[username_field], true, response;
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   103
		end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   104
	end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   105
	return sasl.new(host, profile);
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   106
end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   107
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
   108
module:provides("auth", provider);