view mod_password_policy/mod_password_policy.lua @ 5264:d3ebaef1ea7a

mod_http_oauth2: Correctly verify OAuth client credentials on revocation Makes no sense to validate against username and password here, or using a token to revoke another token, or itself? In fact, upon further discussion, why do you need credentials to revoke a token? If you are not supposed to have the token, revoking it seems the most responsible thing to do with it, so it should be allowed, while if you are supposed to have it, you should be allowed to revoke it.
author Kim Alvefur <zash@zash.se>
date Tue, 21 Mar 2023 21:57:18 +0100
parents bfd4af4caddc
children d3b69859553a
line wrap: on
line source

-- Password policy enforcement for Prosody
--
-- Copyright (C) 2012 Waqas Hussain
--
--
-- Configuration:
--    password_policy = {
--        length = 8;
--    }


local it = require "util.iterators";
local set = require "util.set";
local st = require "util.stanza";

local options = module:get_option("password_policy");

options = options or {};
options.length = options.length or 8;
if options.exclude_username == nil then
	options.exclude_username = true;
end

local builtin_policies = set.new({ "length", "exclude_username" });
local extra_policies = set.new(it.to_array(it.keys(options))) - builtin_policies;

local extra_policy_handlers = {};

module:handle_items("password-policy-provider", function (event)
	-- Password policy handler added
	local item = event.item;
	module:log("error", "Adding password policy handler '%s'", item.name);
	extra_policy_handlers[item.name] = item.check_password;
end, function (event)
	-- Password policy handler removed
	local item = event.item;
	extra_policy_handlers[item.name] = nil;
end);

function check_password(password, additional_info)
	if not password or password == "" then
		return nil, "No password provided", "no-password";
	end

	if #password < options.length then
		return nil, ("Password is too short (minimum %d characters)"):format(options.length), "length";
	end

	if additional_info then
		local username = additional_info.username;
		if username and password:lower():find(username:lower(), 1, true) then
			return nil, "Password must not include your username", "username";
		end
	end

	for policy in extra_policies do
		local handler = extra_policy_handlers[policy];
		if not handler then
			module:log("error", "No policy handler found for '%s' (typo, or module not loaded?)", policy);
			return nil, "Internal error while verifying password", "internal";
		end
		local ok, reason_text, reason_name = handler(password, options[policy], additional_info);
		if ok ~= true then
			return nil, reason_text or ("Password failed %s check"):format(policy), reason_name or policy;
		end
	end

	return true;
end

function get_policy() --luacheck: ignore 131/get_policy
	return options;
end

function handler(event)
	local origin, stanza = event.origin, event.stanza;

	if stanza.attr.type == "set" then
		local query = stanza.tags[1];

		local passwords = {};

		local dataform = query:get_child("x", "jabber:x:data");
		if dataform then
			for _,tag in ipairs(dataform.tags) do
				if tag.attr.var == "password" then
					table.insert(passwords, tag:get_child_text("value"));
				end
			end
		end

		table.insert(passwords, query:get_child_text("password"));

		local additional_info = {
			username = origin.username;
		};

		for _,password in ipairs(passwords) do
			if password then
				local pw_ok, pw_err, pw_failed_policy = check_password(password, additional_info);
				if not pw_ok then
					module:log("debug", "Password failed check against '%s' policy", pw_failed_policy);
					origin.send(st.error_reply(stanza, "cancel", "not-acceptable", pw_err));
					return true;
				end
			end
		end
	end
end

module:hook("iq/self/jabber:iq:register:query", handler, 10);
module:hook("iq/host/jabber:iq:register:query", handler, 10);
module:hook("stanza/iq/jabber:iq:register:query", handler, 10);