Software /
code /
prosody
File
util/dbuffer.lua @ 12642:9061f9621330
Switch to a new role-based authorization framework, removing is_admin()
We began moving away from simple "is this user an admin?" permission checks
before 0.12, with the introduction of mod_authz_internal and the ability to
dynamically change the roles of individual users.
The approach in 0.12 still had various limitations however, and apart from
the introduction of roles other than "admin" and the ability to pull that info
from storage, not much actually changed.
This new framework shakes things up a lot, though aims to maintain the same
functionality and behaviour on the surface for a default Prosody
configuration. That is, if you don't take advantage of any of the new
features, you shouldn't notice any change.
The biggest change visible to developers is that usermanager.is_admin() (and
the auth provider is_admin() method) have been removed. Gone. Completely.
Permission checks should now be performed using a new module API method:
module:may(action_name, context)
This method accepts an action name, followed by either a JID (string) or
(preferably) a table containing 'origin'/'session' and 'stanza' fields (e.g.
the standard object passed to most events). It will return true if the action
should be permitted, or false/nil otherwise.
Modules should no longer perform permission checks based on the role name.
E.g. a lot of code previously checked if the user's role was prosody:admin
before permitting some action. Since many roles might now exist with similar
permissions, and the permissions of prosody:admin may be redefined
dynamically, it is no longer suitable to use this method for permission
checks. Use module:may().
If you start an action name with ':' (recommended) then the current module's
name will automatically be used as a prefix.
To define a new permission, use the new module API:
module:default_permission(role_name, action_name)
module:default_permissions(role_name, { action_name[, action_name...] })
This grants the specified role permission to execute the named action(s) by
default. This may be overridden via other mechanisms external to your module.
The built-in roles that developers should use are:
- prosody:user (normal user)
- prosody:admin (host admin)
- prosody:operator (global admin)
The new prosody:operator role is intended for server-wide actions (such as
shutting down Prosody).
Finally, all usage of is_admin() in modules has been fixed by this commit.
Some of these changes were trickier than others, but no change is expected to
break existing deployments.
EXCEPT: mod_auth_ldap no longer supports the ldap_admin_filter option. It's
very possible nobody is using this, but if someone is then we can later update
it to pull roles from LDAP somehow.
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Wed, 15 Jun 2022 12:15:01 +0100 |
parent | 11637:19cddf92fcc2 |
child | 12762:79b89f382290 |
line wrap: on
line source
local queue = require "util.queue"; local s_byte, s_sub = string.byte, string.sub; local dbuffer_methods = {}; local dynamic_buffer_mt = { __name = "dbuffer", __index = dbuffer_methods }; function dbuffer_methods:write(data) if self.max_size and #data + self._length > self.max_size then return nil; end local ok = self.items:push(data); if not ok then self:collapse(); ok = self.items:push(data); end if not ok then return nil; end self._length = self._length + #data; return true; end function dbuffer_methods:read_chunk(requested_bytes) local chunk, consumed = self.items:peek(), self.front_consumed; if not chunk then return; end local chunk_length = #chunk; local remaining_chunk_length = chunk_length - consumed; if not requested_bytes then requested_bytes = remaining_chunk_length; end if remaining_chunk_length <= requested_bytes then self.front_consumed = 0; self._length = self._length - remaining_chunk_length; self.items:pop(); assert(#chunk:sub(consumed + 1, -1) == remaining_chunk_length); return chunk:sub(consumed + 1, -1), remaining_chunk_length; end local end_pos = consumed + requested_bytes; self.front_consumed = end_pos; self._length = self._length - requested_bytes; assert(#chunk:sub(consumed + 1, end_pos) == requested_bytes); return chunk:sub(consumed + 1, end_pos), requested_bytes; end function dbuffer_methods:read(requested_bytes) local chunks; if requested_bytes and requested_bytes > self._length then return nil; end local chunk, read_bytes = self:read_chunk(requested_bytes); if not requested_bytes then return chunk; elseif chunk then requested_bytes = requested_bytes - read_bytes; if requested_bytes == 0 then -- Already read everything we need return chunk; end chunks = {}; else return nil; end -- Need to keep reading more chunks while chunk do table.insert(chunks, chunk); if requested_bytes > 0 then chunk, read_bytes = self:read_chunk(requested_bytes); requested_bytes = requested_bytes - read_bytes; else break; end end return table.concat(chunks); end -- Read to, and including, the specified character sequence (return nil if not found) function dbuffer_methods:read_until(char) local buffer_pos = 0; for i, chunk in self.items:items() do local start = 1 + ((i == 1) and self.front_consumed or 0); local char_pos = chunk:find(char, start, true); if char_pos then return self:read(1 + buffer_pos + char_pos - start); end buffer_pos = buffer_pos + #chunk - (start - 1); end return nil; end function dbuffer_methods:discard(requested_bytes) if requested_bytes > self._length then return nil; end local chunk, read_bytes = self:read_chunk(requested_bytes); if chunk then requested_bytes = requested_bytes - read_bytes; if requested_bytes == 0 then -- Already read everything we need return true; end else return nil; end while chunk do if requested_bytes > 0 then chunk, read_bytes = self:read_chunk(requested_bytes); requested_bytes = requested_bytes - read_bytes; else break; end end return true; end -- Normalize i, j into absolute offsets within the -- front chunk (accounting for front_consumed), and -- ensure there is enough data in the first chunk -- to cover any subsequent :sub() or :byte() operation function dbuffer_methods:_prep_sub(i, j) if j == nil then j = -1; end if j < 0 then j = self._length + (j+1); end if i < 0 then i = self._length + (i+1); end if i < 1 then i = 1; end if j > self._length then j = self._length; end if i > j then return nil; end self:collapse(j); if self.front_consumed > 0 then i = i + self.front_consumed; j = j + self.front_consumed; end return i, j; end function dbuffer_methods:sub(i, j) i, j = self:_prep_sub(i, j); if not i then return ""; end return s_sub(self.items:peek(), i, j); end function dbuffer_methods:byte(i, j) i = i or 1; j = j or i; i, j = self:_prep_sub(i, j); if not i then return; end return s_byte(self.items:peek(), i, j); end function dbuffer_methods:length() return self._length; end dbuffer_methods.len = dbuffer_methods.length; -- strings have :len() dynamic_buffer_mt.__len = dbuffer_methods.length; -- support # operator function dbuffer_methods:collapse(bytes) bytes = bytes or self._length; local front_chunk = self.items:peek(); if not front_chunk or #front_chunk - self.front_consumed >= bytes then return; end local front_chunks = { front_chunk:sub(self.front_consumed+1) }; local front_bytes = #front_chunks[1]; while front_bytes < bytes do self.items:pop(); local chunk = self.items:peek(); front_bytes = front_bytes + #chunk; table.insert(front_chunks, chunk); end self.items:replace(table.concat(front_chunks)); self.front_consumed = 0; end local function new(max_size, max_chunks) if max_size and max_size <= 0 then return nil; end return setmetatable({ front_consumed = 0; _length = 0; max_size = max_size; items = queue.new(max_chunks or 32); }, dynamic_buffer_mt); end return { new = new; };