--- a/.luacheckrc Tue Aug 16 13:10:39 2022 +0200
+++ b/.luacheckrc Mon Aug 22 15:39:02 2022 +0100
@@ -29,6 +29,8 @@
"module.hourly",
"module.broadcast",
"module.context",
+ "module.default_permission",
+ "module.default_permissions",
"module.depends",
"module.fire_event",
"module.get_directory",
@@ -54,6 +56,7 @@
"module.load_resource",
"module.log",
"module.log_status",
+ "module.may",
"module.measure",
"module.metric",
"module.open_store",
--- a/mod_admin_message/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_admin_message/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -29,7 +29,7 @@
=============
--------- ---------------
- trunk Works
+ trunk Doesn't work (uses is_admin)
0.9 Works
\<= 0.8 Not supported
--------- ---------------
--- a/mod_admin_probe/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_admin_probe/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -5,3 +5,11 @@
This module lets server administrators send `<presence type="probe"/>`
to any local user and receive their presence in response, bypassing
roster checks.
+
+Compatibility
+=============
+
+ ------- --------------
+ trunk Doesn't work (uses is_admin)
+ 0.12 Works?
+ ------- --------------
--- a/mod_block_outgoing/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_block_outgoing/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -26,3 +26,13 @@
```
block_outgoing_stanzas defaults to "message" if not specified.
+
+Compatibility
+=============
+
+ ------- --------------
+ trunk Doesn't work (uses is_admin)
+ 0.12 Works
+ 0.11 Works
+ ------- --------------
+
--- a/mod_broadcast/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_broadcast/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -32,4 +32,5 @@
------ -------
0.9 Works
0.10 Works
+ trunk Doesn't work (uses is_admin)
------ -------
--- a/mod_cloud_notify/mod_cloud_notify.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_cloud_notify/mod_cloud_notify.lua Mon Aug 22 15:39:02 2022 +0100
@@ -390,7 +390,7 @@
notification_stanza = push_publish;
notification_payload = push_notification_payload;
original_stanza = stanza;
- node = node;
+ username = node;
push_info = push_info;
push_summary = form_data;
important = not not form_data["last-message-body"];
--- a/mod_data_access/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_data_access/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -72,3 +72,11 @@
### TODO
- Use `Accept` header.
+
+Compatibility
+=============
+
+ ------- --------------
+ trunk Doesn't work (uses is_admin)
+ 0.12 Works?
+ ------- --------------
--- a/mod_firewall/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_firewall/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -435,8 +435,40 @@
NOT SENT DIRECTED PRESENCE TO SENDER?
BOUNCE=service-unavailable
+### Permissions
+
+Rules can consult Prosody's internal role and permissions system to check whether a certain action may
+be performed. The acting entity, their role, and appropriate context is automatically inferred. All you
+need to do is provide the identifier of the permission that should be checked.
+
+ Condition Description
+ ----------------------- --------------------------------------------------------------------
+ `MAY=permission` Checks whether 'permission' is allowed in the current context.
+
+As with all other conditions, `MAY` can be combined with `NOT` to negate the result of the check.
+
+Example, blocking outgoing stanzas from users with roles that do not allow the 'xmpp:federate' permission:
+
+```
+::deliver_remote
+MAY NOT: xmpp:federate
+BOUNCE=policy-violation (You are not allowed access to the federation)
+```
+
+### Roles
+
+ Condition Matches
+ ---------------- -------------------------------------------------------------------------------------
+ `TO ROLE` When the recipient JID of the stanza has the named role
+ `FROM ROLE` When the sender JID of the stanza has the named role
+
+**Note:** In most cases, you should avoid checking for specific roles, and instead check for
+permissions granted by those roles (using the 'MAY' condition).
+
### Admins
+**Deprecated:** These conditions should no longer be used. Prefer 'MAY', 'TO ROLE' or 'FROM ROLE'.
+
Prosody allows certain JIDs to be declared as administrators of a host, component or the whole server.
Condition Matches
--- a/mod_firewall/conditions.lib.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_firewall/conditions.lib.lua Mon Aug 22 15:39:02 2022 +0100
@@ -175,22 +175,39 @@
return "not "..table.concat(code, " or "), { "group_contains", "bare_to", "bare_from" };
end
+-- COMPAT w/0.12: Deprecated
function condition_handlers.FROM_ADMIN_OF(host)
return ("is_admin(bare_from, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_from" };
end
+-- COMPAT w/0.12: Deprecated
function condition_handlers.TO_ADMIN_OF(host)
return ("is_admin(bare_to, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_to" };
end
+-- COMPAT w/0.12: Deprecated
function condition_handlers.FROM_ADMIN()
return ("is_admin(bare_from, current_host)"), { "is_admin", "bare_from", "current_host" };
end
+-- COMPAT w/0.12: Deprecated
function condition_handlers.TO_ADMIN()
return ("is_admin(bare_to, current_host)"), { "is_admin", "bare_to", "current_host" };
end
+-- MAY: permission_to_check
+function condition_handlers.MAY(permission_to_check)
+ return ("module:may(%q, event)"):format(permission_to_check);
+end
+
+function condition_handlers.TO_ROLE(role_name)
+ return ("get_jid_role(bare_to, current_host) == %q"):format(role_name), { "get_jid_role", "current_host", "bare_to" };
+end
+
+function condition_handlers.FROM_ROLE(role_name)
+ return ("get_jid_role(bare_from, current_host) == %q"):format(role_name), { "get_jid_role", "current_host", "bare_from" };
+end
+
local day_numbers = { sun = 0, mon = 2, tue = 3, wed = 4, thu = 5, fri = 6, sat = 7 };
local function current_time_check(op, hour, minute)
--- a/mod_firewall/mod_firewall.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_firewall/mod_firewall.lua Mon Aug 22 15:39:02 2022 +0100
@@ -6,6 +6,9 @@
local it = require "util.iterators";
local set = require "util.set";
+local have_features, features = pcall(require, "core.features");
+features = have_features and features.available or set.new();
+
-- [definition_type] = definition_factory(param)
local definitions = module:shared("definitions");
@@ -181,7 +184,8 @@
group_contains = {
global_code = [[local group_contains = module:depends("groups").group_contains]];
};
- is_admin = { global_code = [[local is_admin = require "core.usermanager".is_admin;]]};
+ is_admin = features:contains("permissions") and { global_code = [[local is_admin = require "core.usermanager".is_admin;]]} or nil;
+ get_jid_role = require "core.usermanager".get_jid_role and { global_code = [[local get_jid_role = require "core.usermanager".get_jid_role;]] } or nil;
core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza;]] };
zone = { global_code = function (zone)
local var = zone;
--- a/mod_http_admin_api/mod_http_admin_api.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_http_admin_api/mod_http_admin_api.lua Mon Aug 22 15:39:02 2022 +0100
@@ -1,5 +1,6 @@
local usermanager = require "core.usermanager";
+local it = require "util.iterators";
local json = require "util.json";
local st = require "util.stanza";
local array = require "util.array";
@@ -33,25 +34,24 @@
end
if auth_type == "Bearer" then
- local token_info = tokens.get_token_info(auth_data);
- if not token_info or not token_info.session then
- return false;
- end
- return token_info.session;
+ return tokens.get_token_session(auth_data);
end
return nil;
end
+module:default_permission("prosody:admin", ":access-admin-api");
+
function check_auth(routes)
local function check_request_auth(event)
local session = check_credentials(event.request);
if not session then
event.response.headers.authorization = www_authenticate_header;
return false, 401;
- elseif session.auth_scope ~= "prosody:scope:admin" then
+ end
+ event.session = session;
+ if not module:may(":access-admin-api", event) then
return false, 403;
end
- event.session = session;
return true;
end
@@ -179,21 +179,24 @@
end
end
- local roles = nil;
- if usermanager.get_roles then
- local roles_map = usermanager.get_roles(username.."@"..module.host, module.host)
- roles = array()
- if roles_map then
- for role in pairs(roles_map) do
- roles:push(role)
- end
+ local primary_role, secondary_roles, legacy_roles;
+ if usermanager.get_user_role then
+ primary_role = usermanager.get_user_role(username, module.host);
+ secondary_roles = array.collect(it.keys(usermanager.get_user_secondary_roles(username, module.host)));
+ elseif usermanager.get_user_roles then -- COMPAT w/0.12
+ legacy_roles = array();
+ local roles_map = usermanager.get_user_roles(username, module.host);
+ for role_name in pairs(roles_map) do
+ legacy_roles:push(role_name);
end
end
return {
username = username;
display_name = display_name;
- roles = roles;
+ role = primary_role and primary_role.name or nil;
+ secondary_roles = secondary_roles;
+ roles = legacy_roles; -- COMPAT w/0.12
};
end
@@ -309,7 +312,7 @@
};
-- Online sessions
do
- local user_sessions = hosts[module.host].sessions[username];
+ local user_sessions = prosody.hosts[module.host].sessions[username];
if user_sessions then
user_sessions = user_sessions.sessions
end
@@ -415,8 +418,18 @@
end
end
- if new_user.roles then
- if not usermanager.set_roles then
+ if new_user.role then
+ if not usermanager.set_user_role then
+ return 500, "feature-not-implemented";
+ end
+ if not usermanager.set_user_role(username, module.host, new_user.role) then
+ module:log("error", "failed to set role %s for %s", new_user.role, username);
+ return 500;
+ end
+ end
+
+ if new_user.roles then -- COMPAT w/0.12
+ if not usermanager.set_user_roles then
return 500, "feature-not-implemented"
end
@@ -425,7 +438,7 @@
backend_roles[role] = true;
end
local jid = username.."@"..module.host;
- if not usermanager.set_roles(jid, module.host, backend_roles) then
+ if not usermanager.set_user_roles(username, module.host, backend_roles) then
module:log("error", "failed to set roles %q for %s", backend_roles, jid)
return 500
end
--- a/mod_http_oauth2/mod_http_oauth2.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_http_oauth2/mod_http_oauth2.lua Mon Aug 22 15:39:02 2022 +0100
@@ -14,13 +14,20 @@
local clients = module:open_store("oauth2_clients", "map");
-local function filter_scopes(request_jid, requested_scope_string) --luacheck: ignore 212/requested_scope_string
- -- We currently don't really support scopes, so override
- -- to whatever real permissions the user has
- if usermanager.is_admin(request_jid, module.host) then
- return "prosody:scope:admin";
+local function filter_scopes(username, host, requested_scope_string)
+ if host ~= module.host then
+ return usermanager.get_jid_role(username.."@"..host, module.host).name;
end
- return "prosody:scope:default";
+
+ if requested_scope_string then -- Specific role requested
+ -- TODO: The requested scope string is technically a space-delimited list
+ -- of scopes, but for simplicity we're mapping this slot to role names.
+ if usermanager.user_can_assume_role(username, module.host, requested_scope_string) then
+ return requested_scope_string;
+ end
+ end
+
+ return usermanager.get_user_role(username, module.host).name;
end
local function code_expires_in(code)
@@ -81,7 +88,7 @@
end
local granted_jid = jid.join(request_username, request_host, request_resource);
- local granted_scopes = filter_scopes(granted_jid, params.scope);
+ local granted_scopes = filter_scopes(request_username, request_host, params.scope);
return json.encode(new_access_token(granted_jid, granted_scopes, nil));
end
@@ -99,7 +106,7 @@
return oauth_error("invalid_client", "incorrect credentials");
end
- local granted_scopes = filter_scopes(granted_jid, params.scope);
+ local granted_scopes = filter_scopes(client_owner, client_host, params.scope);
local code = uuid.generate();
local ok = codes:set(params.client_id .. "#" .. code, {
--- a/mod_http_xep227/mod_http_xep227.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_http_xep227/mod_http_xep227.lua Mon Aug 22 15:39:02 2022 +0100
@@ -346,11 +346,7 @@
end
if auth_type == "Bearer" then
- local token_info = tokens.get_token_info(auth_data);
- if not token_info or not token_info.session then
- return false;
- end
- return token_info.session;
+ return tokens.get_token_session(auth_data);
end
return nil;
end
--- a/mod_invites_adhoc/mod_invites_adhoc.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_invites_adhoc/mod_invites_adhoc.lua Mon Aug 22 15:39:02 2022 +0100
@@ -13,9 +13,19 @@
-- on the server, use the option above instead.
local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true);
+-- These options are deprecated since module:may()
local allow_user_invite_roles = module:get_option_set("allow_user_invites_by_roles");
local deny_user_invite_roles = module:get_option_set("deny_user_invites_by_roles");
+if module.may then
+ if allow_user_invites then
+ module:default_permission("prosody:user", ":invite-new-users");
+ end
+ if not allow_user_invite_roles:empty() or not deny_user_invite_roles:empty() then
+ return error("allow_user_invites_by_roles and deny_user_invites_by_roles are deprecated options");
+ end
+end
+
local invites;
if prosody.shutdown then -- COMPAT hack to detect prosodyctl
invites = module:depends("invites");
@@ -42,8 +52,10 @@
-- This is for checking if the specified JID may create invites
-- that allow people to register accounts on this host.
-local function may_invite_new_users(jid)
- if usermanager.get_roles then
+local function may_invite_new_users(jid, context)
+ if module.may then
+ return module:may(":invite-new-users", context);
+ elseif usermanager.get_roles then -- COMPAT w/0.12
local user_roles = usermanager.get_roles(jid, module.host);
if not user_roles then return; end
if user_roles["prosody:admin"] then
@@ -87,7 +99,7 @@
};
};
end
- local invite = invites.create_contact(username, may_invite_new_users(data.from), {
+ local invite = invites.create_contact(username, may_invite_new_users(data.from, data), {
source = data.from
});
--TODO: check errors
--- a/mod_isolate_host/mod_isolate_host.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_isolate_host/mod_isolate_host.lua Mon Aug 22 15:39:02 2022 +0100
@@ -1,6 +1,5 @@
local jid = require "util.jid";
-local jid_bare, jid_split = jid.bare, jid.split;
-local is_admin = require "core.usermanager".is_admin;
+local jid_bare, jid_host = jid.bare, jid.host;
local set = require "util.set";
local st = require "util.stanza";
@@ -10,10 +9,14 @@
local except_domains = module:get_option_inherited_set("isolate_except_domains", {});
local except_users = module:get_option_inherited_set("isolate_except_users", {});
+if not module.may then
+ module:depends("compat_roles");
+end
+
function check_stanza(event)
local origin, stanza = event.origin, event.stanza;
if origin.no_host_isolation then return; end
- local to_user, to_host = jid_split(event.stanza.attr.to);
+ local to_host = jid_host(event.stanza.attr.to);
if to_host and to_host ~= origin.host and not except_domains:contains(to_host) then
if to_host:match("^[^.]+%.(.+)$") == origin.host then -- Permit subdomains
except_domains:add(to_host);
@@ -31,10 +34,12 @@
end
end
+module:default_permission("prosody:admin", "xmpp:federate");
+
function check_user_isolated(event)
local session = event.session;
local bare_jid = jid_bare(session.full_jid);
- if is_admin(bare_jid, module.host) or except_users:contains(bare_jid) then
+ if module:may("xmpp:federate") or except_users:contains(bare_jid) then
session.no_host_isolation = true;
end
module:log("debug", "%s is %sisolated", session.full_jid or "[?]", session.no_host_isolation and "" or "not ");
--- a/mod_muc_config_restrict/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_muc_config_restrict/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -57,7 +57,6 @@
=============
------- --------------
- trunk Works
- 0.9 Doesn't work
- 0.8 Doesn't work
+ trunk Doesn't work (uses is_admin)
+ 0.12 Works?
------- --------------
--- a/mod_muc_restrict_rooms/README.markdown Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_muc_restrict_rooms/README.markdown Mon Aug 22 15:39:02 2022 +0100
@@ -49,6 +49,7 @@
=============
----- -------------
+ trunk Doesn't work (uses is_admin)
0.9 Works
0.8 Should work
----- -------------
--- a/mod_rest/mod_rest.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_rest/mod_rest.lua Mon Aug 22 15:39:02 2022 +0100
@@ -49,11 +49,15 @@
end
return { username = username, host = module.host };
elseif auth_type == "Bearer" then
- local token_info = tokens.get_token_info(auth_data);
- if not token_info or not token_info.session then
- return false;
+ if tokens.get_token_session then
+ return tokens.get_token_session(auth_data);
+ else -- COMPAT w/0.12
+ local token_info = tokens.get_token_info(auth_data);
+ if not token_info or not token_info.session then
+ return false;
+ end
+ return token_info.session;
end
- return token_info.session;
end
return nil;
end
--- a/mod_sentry/mod_sentry.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_sentry/mod_sentry.lua Mon Aug 22 15:39:02 2022 +0100
@@ -29,6 +29,8 @@
end;
};
+local serialize = require "util.serialization".serialize;
+
local function sentry_error_handler(e)
module:log("error", "Failed to submit event to sentry: %s", e);
end
--- a/mod_sentry/sentry.lib.lua Tue Aug 16 13:10:39 2022 +0200
+++ b/mod_sentry/sentry.lib.lua Mon Aug 22 15:39:02 2022 +0100
@@ -221,6 +221,7 @@
local data = json.decode(response.body);
return data;
end
+ module:log("warn", "Unexpected response from server: %d: %s", response.code, response.body);
return promise.reject(response);
end