You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
7.5 KiB
127 lines
7.5 KiB
-- Replace the membership-level `access_all` flag with the persisted Custom role and its nine
|
|
-- granular permissions.
|
|
--
|
|
-- Two different columns are called `access_all`, and everything below depends on keeping them apart:
|
|
--
|
|
-- * `users_organizations.access_all` -- the MEMBERSHIP-level bit this migration replaces. Dropped
|
|
-- at the end of this file.
|
|
-- * `groups.access_all` -- the GROUP-level flag, a separate and still-supported feature. Only read
|
|
-- here, to decide a legacy Manager's permissions; never written, and it keeps granting group
|
|
-- members access to every collection afterwards exactly as before.
|
|
--
|
|
-- Base `Collection::is_coll_manageable_by_user` accepts either, so a Manager reached every collection
|
|
-- through either. Only the membership bit is going away, but the capability an owner configured
|
|
-- through either route is preserved, so both are read below. While this file runs the membership
|
|
-- column still exists and `atype = 3` still unambiguously means "legacy Manager".
|
|
--
|
|
-- One state cannot be converted and is refused before the first mutation; `src/db/mod.rs` evaluates
|
|
-- the same condition at startup and prints the recovery text, because Diesel would surface the abort
|
|
-- below as nothing but a driver-level duplicate-key error.
|
|
--
|
|
-- A temporary table on purpose: on MySQL/MariaDB it is the only DDL that does not commit implicitly,
|
|
-- so a refusal cannot leave a half-applied migration behind.
|
|
|
|
-- A plain User carrying membership `access_all`, reachable only on databases written before the web
|
|
-- vault stopped sending the flag. The bit gave read/write reach over every collection, present and
|
|
-- future, with no management authority, and the new model has no permission for that:
|
|
-- `edit_any_collection` would add management authority, dropping the bit would take the reach away.
|
|
-- Refuse and let an owner choose. The duplicate key aborts the migration, and is only inserted when
|
|
-- such a membership exists.
|
|
CREATE TEMPORARY TABLE __vw_legacy_user_access_all_guard (
|
|
blocked INTEGER NOT NULL PRIMARY KEY
|
|
);
|
|
INSERT INTO __vw_legacy_user_access_all_guard (blocked) VALUES (1);
|
|
INSERT INTO __vw_legacy_user_access_all_guard (blocked)
|
|
SELECT 1
|
|
FROM users_organizations
|
|
WHERE atype = 2
|
|
AND access_all = TRUE
|
|
LIMIT 1;
|
|
DROP TEMPORARY TABLE __vw_legacy_user_access_all_guard;
|
|
|
|
-- One ALTER TABLE for all nine columns: MySQL/MariaDB commit every DDL statement implicitly, so nine
|
|
-- separate statements would leave nine points at which a crash produces a partially migrated schema.
|
|
-- A single ALTER is one such point, and on MySQL 8 it is atomic.
|
|
ALTER TABLE users_organizations
|
|
ADD COLUMN manage_users BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN manage_groups BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN manage_policies BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN create_new_collections BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN edit_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN delete_any_collection BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN access_event_logs BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN access_import_export BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN access_reports BOOLEAN NOT NULL DEFAULT FALSE;
|
|
|
|
-- Owners and Admins are not touched: they carried `access_all` implicitly and the new model gives
|
|
-- them every permission by role. A plain User cannot reach this point carrying the bit (the guard
|
|
-- above), so only a Manager becomes Custom, keeping the organization-wide collection-management
|
|
-- capability it is configured with right now:
|
|
--
|
|
-- * membership `access_all` -- the "Manage all collections" checkbox -- covered all three
|
|
-- collection permissions, including creating collections;
|
|
-- * an organization-local `access_all` group covered editing and deleting every collection, but
|
|
-- never creation -- that always required the membership bit;
|
|
-- * a Manager with neither keeps all three at FALSE.
|
|
--
|
|
-- The second case is a deliberate policy choice. That capability was dynamic: it ended with the
|
|
-- group, with the group's own `access_all`, and with the member leaving it. It was never gated on
|
|
-- ORG_GROUPS_ENABLED -- `Collection::is_coll_manageable_by_user` reads `groups.access_all` in SQL
|
|
-- with no configuration check -- so it applied even where groups were never enabled. Nothing in the
|
|
-- new model is bound to a group, so it becomes a membership permission and no longer lapses on its
|
|
-- own. The alternative is silently revoking access these members have today, or refusing an ordinary
|
|
-- upgrade; the permission is visible in the member's permission list and an owner can clear it.
|
|
--
|
|
-- The management (manage_users / manage_groups / manage_policies) and access (event logs /
|
|
-- import-export / reports) permissions keep their FALSE default. Nothing they unlock was a Manager
|
|
-- capability -- every member mutation, every policy write, the organization export and both
|
|
-- event-log routes were gated on Admin/Owner -- so granting one here would be a new privilege.
|
|
--
|
|
-- One read is not carried over, and only for members who held the MEMBERSHIP bit: `has_full_access()`
|
|
-- read `self.access_all` and the role, never `groups.access_all`, so it gated the full member list
|
|
-- (`GET /organizations/<org>/users`) for them and for nobody whose reach came from a group.
|
|
-- `manage_users` is not granted to restore it, because it also carries invite, confirm, revoke,
|
|
-- restore and delete, which the Manager role never had; such members keep `/users/mini-details`, and
|
|
-- an owner can grant `manage_users` deliberately. In the other direction `edit_any_collection`
|
|
-- satisfies `has_full_access()`, which opens the organization collection list and
|
|
-- `GET /ciphers/organization-details` to the group-derived class -- data they could already reach
|
|
-- through the group, so only the route is new.
|
|
--
|
|
-- Role conversion and permission values are one statement, so `atype = 3` unambiguously still means
|
|
-- Manager everywhere it is read.
|
|
--
|
|
-- Status is deliberately not part of the predicate: an invited, accepted or revoked membership is
|
|
-- converted like a confirmed one, since none holds authority in that state and the permissions are
|
|
-- what it would come back with -- the same thing `access_all` would have done.
|
|
--
|
|
-- The group lookup is bound to the membership's own organization: a `groups_users` row pointing at
|
|
-- another organization's `access_all` group conveys nothing, exactly as it conveys nothing today.
|
|
UPDATE users_organizations
|
|
SET create_new_collections = access_all,
|
|
edit_any_collection = access_all
|
|
OR EXISTS (
|
|
SELECT 1
|
|
FROM groups_users AS gu
|
|
INNER JOIN `groups` AS g ON g.uuid = gu.groups_uuid
|
|
WHERE gu.users_organizations_uuid = users_organizations.uuid
|
|
AND g.organizations_uuid = users_organizations.org_uuid
|
|
AND g.access_all = TRUE
|
|
),
|
|
delete_any_collection = access_all
|
|
OR EXISTS (
|
|
SELECT 1
|
|
FROM groups_users AS gu
|
|
INNER JOIN `groups` AS g ON g.uuid = gu.groups_uuid
|
|
WHERE gu.users_organizations_uuid = users_organizations.uuid
|
|
AND g.organizations_uuid = users_organizations.org_uuid
|
|
AND g.access_all = TRUE
|
|
),
|
|
atype = 4
|
|
WHERE atype = 3;
|
|
|
|
-- The flag is now fully represented by the role model, so drop the redundant column. This concerns
|
|
-- `users_organizations` only; `groups.access_all` stays.
|
|
ALTER TABLE users_organizations DROP COLUMN access_all;
|
|
|
|
-- Never inherit a downgrade acknowledgement left behind by an earlier revert.
|
|
DROP TABLE IF EXISTS __vw_allow_custom_role_downgrade;
|
|
|