view mod_auth_http_async/mod_auth_http_async.lua @ 4931:13070c6a7ce8

mod_http_muc_log: Fix exception on lack of trailing slash in room path A request to /room leads to the match call returning nil which in turn calls nodeprep(nil). In Prosody 0.11.x this does nothing and simply returns the nil, while in 0.12 it is an error. Now it redirects to the calendar view at /room/ - even for non-existant rooms. Discovered at a deployment with http_paths = { muc_log = "/" } and requests to /robots.txt and similar, which now result in a uses redirect before returning 404.
author Kim Alvefur <zash@zash.se>
date Fri, 22 Apr 2022 14:29:32 +0200
parents 39156d6f7268
children
line wrap: on
line source

-- Prosody IM
-- Copyright (C) 2008-2013 Matthew Wild
-- Copyright (C) 2008-2013 Waqas Hussain
-- Copyright (C) 2014 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--

local new_sasl = require "util.sasl".new;
local base64 = require "util.encodings".base64.encode;
local have_async, async = pcall(require, "util.async");

local log = module._log;
local host = module.host;

local api_base = module:get_option_string("http_auth_url",  ""):gsub("$host", host);
if api_base == "" then error("http_auth_url required") end

local provider = {};

-- globals required by socket.http
if rawget(_G, "PROXY") == nil then
	rawset(_G, "PROXY", false)
end
if rawget(_G, "base_parsed") == nil then
	rawset(_G, "base_parsed", false)
end
if not have_async then -- FINE! Set your globals then
	prosody.unlock_globals()
	require "ltn12"
	require "socket"
	require "socket.http"
	require "ssl.https"
	prosody.lock_globals()
end

local function async_http_auth(url, username, password)
	module:log("debug", "async_http_auth()");
	local http = require "net.http";
	local wait, done = async.waiter();
	local content, code, request, response;
	local ex = {
		headers = { Authorization = "Basic "..base64(username..":"..password); };
	}
	local function cb(content_, code_, request_, response_)
		content, code, request, response = content_, code_, request_, response_;
		done();
	end
	http.request(url, ex, cb);
	wait();
	if code >= 200 and code <= 299 then
		module:log("debug", "HTTP auth provider confirmed valid password");
		return true;
	else
		module:log("debug", "HTTP auth provider returned status code %d", code);
	end
	return nil, "Auth failed. Invalid username or password.";
end

local function sync_http_auth(url,username, password)
	module:log("debug", "sync_http_auth()");
	require "ltn12";
	local http = require "socket.http";
	local https = require "ssl.https";
	local request;
	if string.sub(url, 1, string.len('https')) == 'https' then
		request = https.request;
	else
		request = http.request;
	end
	local _, code, headers, status = request{
		url = url,
		headers = { Authorization = "Basic "..base64(username..":"..password);  }
	};
	if type(code) == "number" and code >= 200 and code <= 299 then
		module:log("debug", "HTTP auth provider confirmed valid password");
		return true;
	else
		module:log("debug", "HTTP auth provider returned status code: "..code);
	end
	return nil, "Auth failed. Invalid username or password.";
end

function provider.test_password(username, password)
	local url = api_base:gsub("$user", username):gsub("$password", password);
	log("debug", "Testing password for user %s at host %s with URL %s", username, host, url);
	if (have_async) then
		return async_http_auth(url, username, password);
	else
		return sync_http_auth(url, username, password);
	end
end

function provider.users()
	return function()
		return nil;
	end
end

function provider.set_password(username, password)
	return nil, "Changing passwords not supported";
end

function provider.user_exists(username)
	return true;
end

function provider.create_user(username, password)
	return nil, "User creation not supported";
end

function provider.delete_user(username)
	return nil , "User deletion not supported";
end

function provider.get_sasl_handler()
	return new_sasl(host, {
		plain_test = function(sasl, username, password, realm)
			return provider.test_password(username, password), true;
		end
	});
end

module:provides("auth", provider);