Skip to main content

uqa_sql/catalog/roles/memberships/command/
revoke.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Revocation resolves every recipient against the original target graph before publishing changes.
8
9use super::{
10    super::{clear_membership_admin, revoke_membership},
11    BTreeMap, MembershipTarget, MembershipUpdate, RoleBinding, RoleDefinition, RoleMembership,
12    RoleMembershipKey, RoleMembershipOptions, SQLError,
13};
14
15pub struct MembershipRevocation<'a> {
16    target: &'a MembershipTarget,
17    before: BTreeMap<RoleMembershipKey, RoleMembership>,
18    after: BTreeMap<RoleMembershipKey, RoleMembership>,
19}
20
21impl<'a> MembershipRevocation<'a> {
22    pub fn new(
23        target: &'a MembershipTarget,
24        memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
25    ) -> Self {
26        let before = memberships
27            .iter()
28            .filter(|(key, _)| key.role == target.role.identity())
29            .map(|(key, value)| (*key, value.clone()))
30            .collect::<BTreeMap<_, _>>();
31        Self {
32            target,
33            after: before.clone(),
34            before,
35        }
36    }
37
38    /// Returns a warning only when the original graph lacks the recipient, including duplicate or already cascaded requests.
39    pub fn member(
40        &mut self,
41        roles: &BTreeMap<String, RoleDefinition>,
42        member: &RoleBinding,
43    ) -> Result<Option<String>, SQLError> {
44        let key = RoleMembershipKey {
45            role: self.target.role.identity(),
46            member: member.identity(),
47            grantor: self.target.grantor.identity(),
48        };
49        if !self.before.contains_key(&key) {
50            return self
51                .target
52                .membership_notice(roles, member, "has not been granted")
53                .map(Some);
54        }
55        let options = self.target.options;
56        if options == RoleMembershipOptions::default() {
57            revoke_membership(&mut self.after, &key, self.target.cascade, true)?;
58        } else {
59            if options.admin == Some(false)
60                && self.after.get(&key).is_some_and(|row| row.admin_option)
61            {
62                clear_membership_admin(&mut self.after, &key, self.target.cascade)?;
63            }
64            if let Some(row) = self.after.get_mut(&key) {
65                if options.inherit == Some(false) {
66                    row.inherit_option = false;
67                }
68                if options.set == Some(false) {
69                    row.set_option = false;
70                }
71            }
72        }
73        Ok(None)
74    }
75
76    pub fn into_updates(self) -> Vec<MembershipUpdate> {
77        self.before
78            .into_iter()
79            .filter_map(|(key, before)| {
80                let after = self.after.get(&key).cloned();
81                (after.as_ref() != Some(&before)).then_some(MembershipUpdate { before, after })
82            })
83            .collect()
84    }
85}