Decide guest grants explicitly instead of by permission name
guest_grantable_permissions() opened any permission whose name ended
'.view' or '.read' to the guest role, on the reasoning that reading is
the safe default. That is true of a permission gating a list of asset
numbers and false of one gating bytes, and a suffix cannot tell them
apart. Twenty-four of eighty-five qualified and none had been decided:
applications.view reached the SYSTEM-context PowerShell bodies and
the installer binaries pushed to bays
backups.view returned the full payload that the separate
backups.download permission exists to protect
assets.view gated a global search that queries the staff
directory, USB history and network inventory with
no per-domain check of its own
audit.view usernames, addresses, the before/after of every
settings.view change, and every setting row - both admin
category, opened by naming alone
users.view gates nothing at all; list_users is
require_role('admin')
Every entry is now declared, by CORE_GUEST_GRANTABLE or by the plugin
that owns the permission, having read what its routes actually return.
Adding one is a code change, which is the point: it puts the decision
in a diff rather than in a suffix.
The two routes serving script bodies and installer packages move to a
new applications.download permission, which is not grantable, so a site
can still open the catalogue metadata to its floor without the bytes
going with it.
The declared set preserves the grants this site had already chosen:
applications.view, computers.view, endpoints.view, reports.view,
tools.secrets, warranty.view.
Two known gaps are recorded on the hooks that expose them rather than
silently carried: the computer serializer publishes every column plus
resolved rdp:// and vnc:// links, and the per-asset endpoint panels are
keyed on a small integer so walking ids reconstructs much of the roster
that endpoints.fleet exists to protect. Neither is widened here.
A shipped test was granting guests audit.view and passing.
Scope: product
This commit is contained in:
@@ -46,6 +46,12 @@ class Permission(db.Model):
|
||||
('assets.delete', 'Delete assets', 'assets'),
|
||||
# Applications
|
||||
('applications.view', 'View applications', 'applications'),
|
||||
# Deliberately NOT '.view': reading the catalogue and pulling the bytes
|
||||
# are different acts. A script body is SYSTEM-context PowerShell and an
|
||||
# installer is a vendor binary, so the routes that serve them are gated
|
||||
# here rather than on the metadata permission a site may hand to guests.
|
||||
('applications.download',
|
||||
'Download application installers and script bodies', 'applications'),
|
||||
('applications.create', 'Create applications', 'applications'),
|
||||
('applications.edit', 'Edit applications', 'applications'),
|
||||
('applications.delete', 'Delete applications', 'applications'),
|
||||
@@ -152,28 +158,58 @@ def full_permission_catalog(include_disabled=False):
|
||||
return catalog
|
||||
|
||||
|
||||
# Core permissions a site MAY grant the guest role. Declared, not derived.
|
||||
# Each one has been read against what its routes actually return.
|
||||
#
|
||||
# Deliberately absent, with the reason, so nobody re-adds them by reflex:
|
||||
# assets.view - gates the global search, which queries the staff directory
|
||||
# and the USB, notification and network domains with no
|
||||
# per-domain check of its own
|
||||
# settings.view - every setting row, and secret masking is a substring test
|
||||
# audit.view - usernames, addresses, and the before/after of every change
|
||||
# users.view - gates nothing; list_users is require_role('admin')
|
||||
CORE_GUEST_GRANTABLE = {
|
||||
# Aggregate counts only; no row-level data reaches the caller.
|
||||
'reports.view',
|
||||
# Catalogue metadata. Safe only because the bytes moved to
|
||||
# applications.download, which is not grantable.
|
||||
'applications.view',
|
||||
}
|
||||
|
||||
|
||||
def guest_grantable_permissions():
|
||||
"""Which permissions the guest role may be granted.
|
||||
|
||||
Reading is the safe default and covers almost everything a site wants to
|
||||
open up: any `.view` or `.read` permission may be handed to guests.
|
||||
Every entry is DECLARED, by core below or by a plugin through
|
||||
get_guest_grantable_permissions(). Nothing qualifies by being spelled a
|
||||
particular way.
|
||||
|
||||
A write is different - it lets somebody with no account change this
|
||||
database, and the audit trail can then say when and from where but never
|
||||
who. So a write has to be BLESSED by whoever owns it, through the plugin's
|
||||
get_guest_grantable_permissions(). Secure Send is the case that exists:
|
||||
creating a one-view link for somebody is the whole point of the page, and
|
||||
the page has always been usable by people with no account here.
|
||||
It used to: any name ending `.view` or `.read` was grantable, on the
|
||||
reasoning that reading is the safe default. That is true of a permission
|
||||
that gates a list of asset numbers and false of one that gates bytes, and
|
||||
the suffix cannot tell them apart. `applications.view` reached the
|
||||
SYSTEM-context PowerShell bodies and the installer binaries;
|
||||
`backups.view` returned the full payload the separate backups.download
|
||||
permission exists to protect; `assets.view` gated a global search that
|
||||
queries the staff directory. None of those were decided. They were named.
|
||||
|
||||
Everything else is refused at the door rather than warned about, because a
|
||||
checkbox that quietly makes deleting an asset anonymous is not a warning
|
||||
anybody reads twice.
|
||||
So the rule is inverted: a permission is grantable when somebody wrote it
|
||||
down, having looked at what it actually serves. Adding one is a code
|
||||
change, which is the point - it puts the decision in a diff rather than in
|
||||
a suffix.
|
||||
|
||||
Naming one here does not open it. It makes the checkbox available in the
|
||||
role grid; a site still has to tick it.
|
||||
|
||||
The bar, for a read: could an anonymous visitor hold this, forever, and
|
||||
would the site be content with everything it returns? For a write, add
|
||||
that the act can never be attributed to anybody.
|
||||
"""
|
||||
from flask import current_app, has_app_context
|
||||
|
||||
catalog = full_permission_catalog()
|
||||
grantable = {name for name, _, _ in catalog
|
||||
if name.endswith('.view') or name.endswith('.read')}
|
||||
known = {name for name, _, _ in catalog}
|
||||
grantable = {name for name in CORE_GUEST_GRANTABLE if name in known}
|
||||
|
||||
if not has_app_context():
|
||||
return grantable
|
||||
@@ -181,7 +217,6 @@ def guest_grantable_permissions():
|
||||
if not pm:
|
||||
return grantable
|
||||
|
||||
known = {name for name, _, _ in catalog}
|
||||
for name, plugin in pm.get_all_plugins().items():
|
||||
if not pm.registry.is_enabled(name):
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user