mod_auth_oauth_external/mod_auth_oauth_external.lua
author Kim Alvefur <zash@zash.se>
Thu, 16 Mar 2023 13:04:13 +0100
changeset 5350 d9bc8712a745
parent 5349 3390bb2f9f6c
child 5437 b40299bbdf14
permissions -rw-r--r--
mod_auth_oauth_external: Allow setting identity instead of discovery URL Shorter and the .well-known part is, well, well-known.
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";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     3
local json = require "util.json";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     4
local sasl = require "util.sasl";
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
     5
5350
d9bc8712a745 mod_auth_oauth_external: Allow setting identity instead of discovery URL
Kim Alvefur <zash@zash.se>
parents: 5349
diff changeset
     6
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
     7
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
     8
	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
     9
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
    10
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
    11
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    12
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
    13
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
    14
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    15
-- 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
    16
-- 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
    17
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
    18
-- 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
    19
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    20
--[[ More or less required endpoints
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    21
digraph "oauth endpoints" {
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    22
issuer -> discovery -> { registration validation }
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    23
registration -> { client_id client_secret }
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    24
{ 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
    25
}
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
local host = module.host;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    29
local provider = {};
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    30
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    31
function provider.get_sasl_handler()
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    32
	local profile = {};
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    33
	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
    34
	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
    35
	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
    36
		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
    37
		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
    38
			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
    39
				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
    40
				body = http.formencode({
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    41
					grant_type = "password";
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    42
					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
    43
					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
    44
					password = password;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    45
					scope = "openid";
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    46
				});
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
			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
    49
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    50
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    51
			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
    52
			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
    53
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    54
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    55
			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
    56
				{ 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
    57
			if err then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    58
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    59
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    60
			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
    61
				return false, nil;
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    62
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    63
			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
    64
			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
    65
				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
    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 response.jid then
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    68
				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
    69
			end
3390bb2f9f6c mod_auth_oauth_external: Support PLAIN via resource owner password grant
Kim Alvefur <zash@zash.se>
parents: 5348
diff changeset
    70
			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
    71
			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
    72
			return true, true;
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
	end
5348
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    75
	function profile:oauthbearer(token)
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    76
		if token == "" then
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    77
			return false, nil, extra;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    78
		end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    79
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    80
		local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint,
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    81
			{ headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" } }));
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    82
		if err then
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    83
			return false, nil, extra;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    84
		end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    85
		local response = ret and json.decode(ret.body);
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    86
		if not (ret.code >= 200 and ret.code < 300) then
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    87
			return false, nil, response or extra;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    88
		end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    89
		if type(response) ~= "table" or type(response[username_field]) ~= "string" then
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    90
			return false, nil, nil;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    91
		end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    92
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    93
		return response[username_field], true, response;
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    94
	end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    95
	return sasl.new(host, profile);
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    96
end
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    97
0a6d2b79a8bf mod_auth_oauth_external: Authenticate against an OAuth 2 provider
Kim Alvefur <zash@zash.se>
parents:
diff changeset
    98
module:provides("auth", provider);