diff options
Diffstat (limited to 'core/moduleapi.lua')
-rw-r--r-- | core/moduleapi.lua | 250 |
1 files changed, 224 insertions, 26 deletions
diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 870a6a50..31d1b1bd 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,91 @@ 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 + ret = human_io.parse_duration_lax(value); + if ret then + local num = value:match("%d+"); + self:log("error", "Config option '%s' is set to ambiguous period '%s' - use full syntax e.g. '%s months' or '%s minutes'", name, value, num, num); + -- COMPAT: w/more relaxed behaviour in post-0.12 trunk. Return nil for this case too, eventually. + else + self:log("error", "Config option '%s' not understood, expecting a period (e.g. \"2 days\")", name); + return nil; + end + 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 +390,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 +422,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 +631,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 +651,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 +673,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 +696,107 @@ 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 + + do + -- JID-based actor + local actor_jid = type(context) == "string" and context or context.actor_jid; + if actor_jid then -- check JID permissions + local role; + local node, host = jid_split(actor_jid); + if host == self.host then + role = hosts[host].authz.get_user_role(node); + else + role = hosts[self.host].authz.get_jid_role(actor_jid); + end + 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); + if not permit then + if not peek then + self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role.name); + end + end + return permit; + end + end + + -- Session-based actor + 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:on_ready(f) --luacheck: ignore 212/self + return prosody.started:next(f); +end + +-- COMPAT w/post 0.12 trunk +function api:once(f) + self:log("warn", "This module uses deprecated module:once() - switch to module:on_ready() or (better) expose function module.ready()"); + return self:on_ready(f); +end + return api; |