aboutsummaryrefslogtreecommitdiffstats
path: root/core/moduleapi.lua
diff options
context:
space:
mode:
Diffstat (limited to 'core/moduleapi.lua')
-rw-r--r--core/moduleapi.lua230
1 files changed, 204 insertions, 26 deletions
diff --git a/core/moduleapi.lua b/core/moduleapi.lua
index 870a6a50..8710b243 100644
--- a/core/moduleapi.lua
+++ b/core/moduleapi.lua
@@ -6,28 +6,30 @@
-- COPYING file in the source package for more information.
--
-local array = require "util.array";
-local set = require "util.set";
-local it = require "util.iterators";
-local logger = require "util.logger";
-local timer = require "util.timer";
-local resolve_relative_path = require"util.paths".resolve_relative_path;
-local st = require "util.stanza";
-local cache = require "util.cache";
-local errors = require "util.error";
-local promise = require "util.promise";
-local time_now = require "util.time".now;
-local format = require "util.format".format;
-local jid_node = require "util.jid".node;
-local jid_resource = require "util.jid".resource;
+local array = require "prosody.util.array";
+local set = require "prosody.util.set";
+local it = require "prosody.util.iterators";
+local logger = require "prosody.util.logger";
+local timer = require "prosody.util.timer";
+local resolve_relative_path = require"prosody.util.paths".resolve_relative_path;
+local st = require "prosody.util.stanza";
+local cache = require "prosody.util.cache";
+local errors = require "prosody.util.error";
+local promise = require "prosody.util.promise";
+local time_now = require "prosody.util.time".now;
+local format = require "prosody.util.format".format;
+local jid_node = require "prosody.util.jid".node;
+local jid_split = require "prosody.util.jid".split;
+local jid_resource = require "prosody.util.jid".resource;
+local human_io = require "prosody.util.human.io";
local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
local error, setmetatable, type = error, setmetatable, type;
local ipairs, pairs, select = ipairs, pairs, select;
local tonumber, tostring = tonumber, tostring;
local require = require;
-local pack = table.pack or require "util.table".pack; -- table.pack is only in 5.2
-local unpack = table.unpack or unpack; --luacheck: ignore 113 -- renamed in 5.2
+local pack = table.pack;
+local unpack = table.unpack;
local prosody = prosody;
local hosts = prosody.hosts;
@@ -128,14 +130,14 @@ function api:wrap_global(event, handler)
end
function api:require(lib)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
local f, n = modulemanager.loader:load_code_ext(self.name, lib, "lib.lua", self.environment);
if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
return f();
end
function api:depends(name)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
if self:get_option_inherited_set("modules_disabled", {}):contains(name) then
error("Dependency on disabled module mod_"..name);
end
@@ -168,6 +170,10 @@ function api:depends(name)
end
end
self.dependencies[name] = true;
+ if not mod.module.reverse_dependencies then
+ mod.module.reverse_dependencies = {};
+ end
+ mod.module.reverse_dependencies[self.name] = true;
return mod;
end
@@ -200,7 +206,7 @@ function api:shared(path)
end
function api:get_option(name, default_value)
- local config = require "core.configmanager";
+ local config = require "prosody.core.configmanager";
local value = config.get(self.host, name);
if value == nil then
value = default_value;
@@ -227,12 +233,83 @@ function api:get_option_string(name, default_value)
return tostring(value);
end
-function api:get_option_number(name, ...)
- local value = self:get_option_scalar(name, ...);
+function api:get_option_number(name, default_value, min, max)
+ local value = self:get_option_scalar(name, default_value);
local ret = tonumber(value);
if value ~= nil and ret == nil then
self:log("error", "Config option '%s' not understood, expecting a number", name);
end
+ if ret == default_value then
+ -- skip interval checks for default or nil
+ return ret;
+ end
+ if min and ret < min then
+ self:log("warn", "Config option '%s' out of bounds %g < %g", name, ret, min);
+ return min;
+ end
+ if max and ret > max then
+ self:log("warn", "Config option '%s' out of bounds %g > %g", name, ret, max);
+ return max;
+ end
+ return ret;
+end
+
+function api:get_option_integer(name, default_value, min, max)
+ local value = self:get_option_number(name, default_value, min or math.mininteger or -2 ^ 52, max or math.maxinteger or 2 ^ 53);
+ if value == default_value then
+ -- pass default trough unaltered, violates ranges sometimes
+ return value;
+ end
+ if math.type(value) == "float" then
+ self:log("warn", "Config option '%s' expected an integer, not a float (%g)", name, value)
+ return math.floor(value);
+ end
+ -- nil or an integer
+ return value;
+end
+
+function api:get_option_period(name, default_value, min, max)
+ local value = self:get_option_scalar(name, default_value);
+
+ local ret;
+ if value == "never" or value == false then
+ -- usually for disabling some periodic thing
+ return math.huge;
+ elseif type(value) == "number" then
+ -- assume seconds
+ ret = value;
+ elseif type(value) == "string" then
+ ret = human_io.parse_duration(value);
+ if value ~= nil and ret == nil then
+ self:log("error", "Config option '%s' not understood, expecting a period (e.g. \"2 days\")", name);
+ end
+ elseif value ~= nil then
+ self:log("error", "Config option '%s' expects a number or a period description string (e.g. \"3 hours\"), not %s", name, type(value));
+ return nil;
+ else
+ return nil;
+ end
+
+ if ret < 0 then
+ self:log("debug", "Treating negative period as infinity");
+ return math.huge;
+ end
+
+ if type(min) == "string" then
+ min = human_io.parse_duration(min);
+ end
+ if min and ret < min then
+ self:log("warn", "Config option '%s' out of bounds %g < %g", name, ret, min);
+ return min;
+ end
+ if type(max) == "string" then
+ max = human_io.parse_duration(max);
+ end
+ if max and ret > max then
+ self:log("warn", "Config option '%s' out of bounds %g > %g", name, ret, max);
+ return max;
+ end
+
return ret;
end
@@ -305,6 +382,15 @@ function api:get_option_path(name, default, parent)
return resolve_relative_path(parent, value);
end
+function api:get_option_enum(name, default, ...)
+ local value = self:get_option_scalar(name, default);
+ if value == nil then return nil; end
+ local options = set.new{default, ...};
+ if not options:contains(value) then
+ self:log("error", "Config option '%s' not in set of allowed values (one of: %s)", name, options);
+ end
+ return value;
+end
function api:context(host)
return setmetatable({ host = host or "*", global = "*" == host }, { __index = self, __newindex = self });
@@ -328,7 +414,7 @@ function api:remove_item(key, value)
end
function api:get_host_items(key)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
local result = modulemanager.get_items(key, self.host) or {};
return result;
end
@@ -537,11 +623,12 @@ function api:load_resource(path, mode)
end
function api:open_store(name, store_type)
- return require"core.storagemanager".open(self.host, name or self.name, store_type);
+ if self.host == "*" then return nil, "global-storage-not-supported"; end
+ return require"prosody.core.storagemanager".open(self.host, name or self.name, store_type);
end
function api:measure(name, stat_type, conf)
- local measure = require "core.statsmanager".measure;
+ local measure = require "prosody.core.statsmanager".measure;
local fixed_label_key, fixed_label_value
if self.host ~= "*" then
fixed_label_key = "host"
@@ -556,7 +643,7 @@ function api:measure(name, stat_type, conf)
end
function api:metric(type_, name, unit, description, label_keys, conf)
- local metric = require "core.statsmanager".metric;
+ local metric = require "prosody.core.statsmanager".metric;
local is_scoped = self.host ~= "*"
if is_scoped then
-- prepend `host` label to label keys if this is not a global module
@@ -578,7 +665,7 @@ local status_priorities = { error = 3, warn = 2, info = 1, core = 0 };
function api:set_status(status_type, status_message, override)
local priority = status_priorities[status_type];
if not priority then
- self:log("error", "set_status: Invalid status type '%s', assuming 'info'");
+ self:log("error", "set_status: Invalid status type '%s', assuming 'info'", status_type);
status_type, priority = "info", status_priorities.info;
end
local current_priority = status_priorities[self.status_type] or 0;
@@ -601,4 +688,95 @@ function api:get_status()
return self.status_type, self.status_message, self.status_time;
end
+function api:default_permission(role_name, permission)
+ permission = permission:gsub("^:", self.name..":");
+ if self.host == "*" then
+ for _, host in pairs(hosts) do
+ if host.authz then
+ host.authz.add_default_permission(role_name, permission);
+ end
+ end
+ return
+ end
+ hosts[self.host].authz.add_default_permission(role_name, permission);
+end
+
+function api:default_permissions(role_name, permissions)
+ for _, permission in ipairs(permissions) do
+ self:default_permission(role_name, permission);
+ end
+end
+
+function api:could(action, context)
+ return self:may(action, context, true);
+end
+
+function api:may(action, context, peek)
+ if action:byte(1) == 58 then -- action begins with ':'
+ action = self.name..action; -- prepend module name
+ end
+ if type(context) == "string" then -- check JID permissions
+ local role;
+ local node, host = jid_split(context);
+ if host == self.host then
+ role = hosts[host].authz.get_user_role(node);
+ else
+ role = hosts[self.host].authz.get_jid_role(context);
+ end
+ if not role then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (no role found)", context, action);
+ end
+ return false;
+ end
+ local permit = role:may(action);
+ if not permit then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", context, action, role.name);
+ end
+ end
+ return permit;
+ end
+
+ local session = context.origin or context.session;
+ if type(session) ~= "table" then
+ error("Unable to identify actor session from context");
+ end
+ if session.type == "c2s" and session.host == self.host then
+ local role = session.role;
+ if not role then
+ if not peek then
+ self:log("warn", "Access denied: session %s has no role assigned");
+ end
+ return false;
+ end
+ local permit = role:may(action, context);
+ if not permit and not peek then
+ self:log("debug", "Access denied: session %s (%s) may not %s (not permitted by role %s)",
+ session.id, session.full_jid, action, role.name
+ );
+ end
+ return permit;
+ else
+ local actor_jid = context.stanza.attr.from;
+ local role = hosts[self.host].authz.get_jid_role(actor_jid);
+ if not role then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (no role found)", actor_jid, action);
+ end
+ return false;
+ end
+ local permit = role:may(action, context);
+ if not permit and not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role.name);
+ end
+ return permit;
+ end
+end
+
+-- Execute a function, once, but only after startup is complete
+function api:once(f) --luacheck: ignore 212/self
+ return prosody.started:next(f);
+end
+
return api;