mz_repr/adt/
mz_acl_item.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A Materialize specific access control list abstract data type.
11
12use std::collections::BTreeMap;
13use std::fmt;
14use std::mem::size_of;
15use std::ops::BitOrAssign;
16use std::str::FromStr;
17
18use crate::adt::system::Oid;
19use anyhow::{Error, anyhow};
20use bitflags::bitflags;
21use columnation::{Columnation, CopyRegion};
22use mz_ore::soft_assert_no_log;
23use mz_ore::str::StrExt;
24use mz_persist_types::columnar::FixedSizeCodec;
25use mz_proto::{RustType, TryFromProtoError};
26use proptest::arbitrary::Arbitrary;
27use proptest::prelude::*;
28use proptest::strategy::{BoxedStrategy, Strategy};
29use proptest_derive::Arbitrary;
30use serde::{Deserialize, Serialize};
31
32use crate::role_id::RoleId;
33
34include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.mz_acl_item.rs"));
35
36// Append
37const INSERT_CHAR: char = 'a';
38// Read
39const SELECT_CHAR: char = 'r';
40// Write
41const UPDATE_CHAR: char = 'w';
42// Delete
43const DELETE_CHAR: char = 'd';
44// Usage
45const USAGE_CHAR: char = 'U';
46// Create
47const CREATE_CHAR: char = 'C';
48// Role
49const CREATE_ROLE_CHAR: char = 'R';
50// dataBase
51const CREATE_DB_CHAR: char = 'B';
52// compute Node
53const CREATE_CLUSTER_CHAR: char = 'N';
54// compute network Policy
55const CREATE_NETWORK_POLICY_CHAR: char = 'P';
56
57const INSERT_STR: &str = "INSERT";
58const SELECT_STR: &str = "SELECT";
59const UPDATE_STR: &str = "UPDATE";
60const DELETE_STR: &str = "DELETE";
61const USAGE_STR: &str = "USAGE";
62const CREATE_STR: &str = "CREATE";
63const CREATE_ROLE_STR: &str = "CREATEROLE";
64const CREATE_DB_STR: &str = "CREATEDB";
65const CREATE_CLUSTER_STR: &str = "CREATECLUSTER";
66const CREATE_NETWORK_POLICY_STR: &str = "CREATENETWORKPOLICY";
67
68/// The OID used to represent the PUBLIC role. See:
69/// <https://github.com/postgres/postgres/blob/29a0ccbce97978e5d65b8f96c85a00611bb403c4/src/include/utils/acl.h#L46>
70pub const PUBLIC_ROLE_OID: Oid = Oid(0);
71
72bitflags! {
73    /// A bit flag representing all the privileges that can be granted to a role.
74    ///
75    /// Modeled after:
76    /// https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/nodes/parsenodes.h#L74-L101
77    ///
78    /// The lower 32 bits are used for different privilege types.
79    ///
80    /// The upper 32 bits indicate a grant option on the privilege for the current bit shifted
81    /// right by 32 bits (Currently unimplemented in Materialize).
82    ///
83    /// Privileges that exist in Materialize but not PostgreSQL start at the highest available bit
84    /// and move down towards the PostgreSQL compatible bits. This is try to avoid collisions with
85    /// privileges that PostgreSQL may add in the future.
86    #[derive(Serialize, Deserialize)]
87    pub struct AclMode: u64 {
88        // PostgreSQL compatible privileges.
89        const INSERT = 1 << 0;
90        const SELECT = 1 << 1;
91        const UPDATE = 1 << 2;
92        const DELETE = 1 << 3;
93        const USAGE = 1 << 8;
94        const CREATE = 1 << 9;
95
96        // Materialize custom privileges.
97        const CREATE_CLUSTER = 1 << 29;
98        const CREATE_DB = 1 << 30;
99        const CREATE_ROLE = 1 << 31;
100        const CREATE_NETWORK_POLICY = 1 << 32;
101
102        // No additional privileges should be defined at a bit larger than 1 << 31. Those bits are
103        // reserved for grant options.
104    }
105}
106
107impl AclMode {
108    pub fn parse_single_privilege(s: &str) -> Result<Self, Error> {
109        match s.trim().to_uppercase().as_str() {
110            INSERT_STR => Ok(AclMode::INSERT),
111            SELECT_STR => Ok(AclMode::SELECT),
112            UPDATE_STR => Ok(AclMode::UPDATE),
113            DELETE_STR => Ok(AclMode::DELETE),
114            USAGE_STR => Ok(AclMode::USAGE),
115            CREATE_STR => Ok(AclMode::CREATE),
116            CREATE_ROLE_STR => Ok(AclMode::CREATE_ROLE),
117            CREATE_DB_STR => Ok(AclMode::CREATE_DB),
118            CREATE_CLUSTER_STR => Ok(AclMode::CREATE_CLUSTER),
119            CREATE_NETWORK_POLICY_STR => Ok(AclMode::CREATE_NETWORK_POLICY),
120            _ => Err(anyhow!("{}", s.quoted())),
121        }
122    }
123
124    pub fn parse_multiple_privileges(s: &str) -> Result<Self, Error> {
125        let mut acl_mode = AclMode::empty();
126        for privilege in s.split(',') {
127            let privilege = AclMode::parse_single_privilege(privilege)?;
128            acl_mode.bitor_assign(privilege);
129        }
130        Ok(acl_mode)
131    }
132
133    pub fn to_error_string(&self) -> String {
134        self.explode().join(", ")
135    }
136
137    pub fn explode(&self) -> Vec<&'static str> {
138        let mut privileges = Vec::new();
139        if self.contains(AclMode::SELECT) {
140            privileges.push(SELECT_STR);
141        }
142        if self.contains(AclMode::INSERT) {
143            privileges.push(INSERT_STR);
144        }
145        if self.contains(AclMode::UPDATE) {
146            privileges.push(UPDATE_STR);
147        }
148        if self.contains(AclMode::DELETE) {
149            privileges.push(DELETE_STR);
150        }
151        if self.contains(AclMode::USAGE) {
152            privileges.push(USAGE_STR);
153        }
154        if self.contains(AclMode::CREATE) {
155            privileges.push(CREATE_STR);
156        }
157        if self.contains(AclMode::CREATE_ROLE) {
158            privileges.push(CREATE_ROLE_STR);
159        }
160        if self.contains(AclMode::CREATE_DB) {
161            privileges.push(CREATE_DB_STR);
162        }
163        if self.contains(AclMode::CREATE_CLUSTER) {
164            privileges.push(CREATE_CLUSTER_STR);
165        }
166        if self.contains(AclMode::CREATE_NETWORK_POLICY) {
167            privileges.push(CREATE_NETWORK_POLICY_STR);
168        }
169        privileges
170    }
171}
172
173impl FromStr for AclMode {
174    type Err = Error;
175
176    fn from_str(s: &str) -> Result<Self, Self::Err> {
177        let mut acl_mode = AclMode::empty();
178        for c in s.chars() {
179            match c {
180                INSERT_CHAR => acl_mode.bitor_assign(AclMode::INSERT),
181                SELECT_CHAR => acl_mode.bitor_assign(AclMode::SELECT),
182                UPDATE_CHAR => acl_mode.bitor_assign(AclMode::UPDATE),
183                DELETE_CHAR => acl_mode.bitor_assign(AclMode::DELETE),
184                USAGE_CHAR => acl_mode.bitor_assign(AclMode::USAGE),
185                CREATE_CHAR => acl_mode.bitor_assign(AclMode::CREATE),
186                CREATE_ROLE_CHAR => acl_mode.bitor_assign(AclMode::CREATE_ROLE),
187                CREATE_DB_CHAR => acl_mode.bitor_assign(AclMode::CREATE_DB),
188                CREATE_CLUSTER_CHAR => acl_mode.bitor_assign(AclMode::CREATE_CLUSTER),
189                CREATE_NETWORK_POLICY_CHAR => acl_mode.bitor_assign(AclMode::CREATE_NETWORK_POLICY),
190                _ => return Err(anyhow!("invalid privilege '{c}' in acl mode '{s}'")),
191            }
192        }
193        Ok(acl_mode)
194    }
195}
196
197impl fmt::Display for AclMode {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        // The order of each flag matches PostgreSQL, which uses the same order that they're
200        // defined in.
201        if self.contains(AclMode::INSERT) {
202            write!(f, "{INSERT_CHAR}")?;
203        }
204        if self.contains(AclMode::SELECT) {
205            write!(f, "{SELECT_CHAR}")?;
206        }
207        if self.contains(AclMode::UPDATE) {
208            write!(f, "{UPDATE_CHAR}")?;
209        }
210        if self.contains(AclMode::DELETE) {
211            write!(f, "{DELETE_CHAR}")?;
212        }
213        if self.contains(AclMode::USAGE) {
214            write!(f, "{USAGE_CHAR}")?;
215        }
216        if self.contains(AclMode::CREATE) {
217            write!(f, "{CREATE_CHAR}")?;
218        }
219        if self.contains(AclMode::CREATE_ROLE) {
220            write!(f, "{CREATE_ROLE_CHAR}")?;
221        }
222        if self.contains(AclMode::CREATE_DB) {
223            write!(f, "{CREATE_DB_CHAR}")?;
224        }
225        if self.contains(AclMode::CREATE_CLUSTER) {
226            write!(f, "{CREATE_CLUSTER_CHAR}")?;
227        }
228        if self.contains(AclMode::CREATE_NETWORK_POLICY) {
229            write!(f, "{CREATE_NETWORK_POLICY_CHAR}")?;
230        }
231        Ok(())
232    }
233}
234
235impl RustType<ProtoAclMode> for AclMode {
236    fn into_proto(&self) -> ProtoAclMode {
237        ProtoAclMode {
238            acl_mode: self.bits,
239        }
240    }
241
242    fn from_proto(proto: ProtoAclMode) -> Result<Self, TryFromProtoError> {
243        Ok(AclMode {
244            bits: proto.acl_mode,
245        })
246    }
247}
248
249impl Columnation for AclMode {
250    type InnerRegion = CopyRegion<AclMode>;
251}
252
253impl Arbitrary for AclMode {
254    type Parameters = ();
255    type Strategy = BoxedStrategy<AclMode>;
256
257    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
258        proptest::bits::BitSetStrategy::masked(AclMode::all().bits)
259            .prop_map(|bits| AclMode::from_bits(bits).expect("invalid proptest implementation"))
260            .boxed()
261    }
262}
263
264/// A list of privileges granted to a role in Materialize.
265///
266/// This is modelled after the AclItem type in PostgreSQL, but the OIDs are replaced with RoleIds
267/// because we don't use OID as persistent identifiers for roles.
268///
269/// See: <https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/utils/acl.h#L48-L59>
270#[derive(
271    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Hash, Deserialize, Arbitrary,
272)]
273pub struct MzAclItem {
274    /// Role that this item grants privileges to.
275    pub grantee: RoleId,
276    /// Grantor of privileges.
277    pub grantor: RoleId,
278    /// Privileges bit flag.
279    pub acl_mode: AclMode,
280}
281
282impl MzAclItem {
283    pub fn empty(grantee: RoleId, grantor: RoleId) -> MzAclItem {
284        MzAclItem {
285            grantee,
286            grantor,
287            acl_mode: AclMode::empty(),
288        }
289    }
290
291    pub fn encode_binary(&self) -> Vec<u8> {
292        let mut res = Vec::with_capacity(Self::binary_size());
293        res.extend_from_slice(&self.grantee.encode_binary());
294        res.extend_from_slice(&self.grantor.encode_binary());
295        res.extend_from_slice(&self.acl_mode.bits().to_le_bytes());
296        res
297    }
298
299    pub fn decode_binary(raw: &[u8]) -> Result<MzAclItem, Error> {
300        if raw.len() != MzAclItem::binary_size() {
301            return Err(anyhow!(
302                "invalid binary size, expecting {}, found {}",
303                MzAclItem::binary_size(),
304                raw.len()
305            ));
306        }
307
308        let role_id_size = RoleId::binary_size();
309
310        let grantee = RoleId::decode_binary(&raw[0..role_id_size])?;
311        let raw = &raw[role_id_size..];
312        let grantor = RoleId::decode_binary(&raw[0..role_id_size])?;
313        let raw = &raw[role_id_size..];
314        let acl_mode = u64::from_le_bytes(raw.try_into()?);
315
316        Ok(MzAclItem {
317            grantee,
318            grantor,
319            acl_mode: AclMode { bits: acl_mode },
320        })
321    }
322
323    pub const fn binary_size() -> usize {
324        RoleId::binary_size() + RoleId::binary_size() + size_of::<u64>()
325    }
326}
327
328impl FromStr for MzAclItem {
329    type Err = Error;
330
331    fn from_str(s: &str) -> Result<Self, Self::Err> {
332        let parts: Vec<_> = s.split('=').collect();
333        let &[grantee, rest] = parts.as_slice() else {
334            return Err(anyhow!("invalid mz_aclitem '{s}'"));
335        };
336
337        let parts: Vec<_> = rest.split('/').collect();
338        let &[acl_mode, grantor] = parts.as_slice() else {
339            return Err(anyhow!("invalid mz_aclitem '{s}'"));
340        };
341
342        let grantee: RoleId = if grantee.is_empty() {
343            RoleId::Public
344        } else {
345            grantee.parse()?
346        };
347        let acl_mode: AclMode = acl_mode.parse()?;
348        let grantor: RoleId = grantor.parse()?;
349
350        Ok(MzAclItem {
351            grantee,
352            grantor,
353            acl_mode,
354        })
355    }
356}
357
358impl fmt::Display for MzAclItem {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        if !self.grantee.is_public() {
361            write!(f, "{}", self.grantee)?;
362        }
363        write!(f, "={}/{}", self.acl_mode, self.grantor)
364    }
365}
366
367impl RustType<ProtoMzAclItem> for MzAclItem {
368    fn into_proto(&self) -> ProtoMzAclItem {
369        ProtoMzAclItem {
370            grantee: Some(self.grantee.into_proto()),
371            grantor: Some(self.grantor.into_proto()),
372            acl_mode: Some(self.acl_mode.into_proto()),
373        }
374    }
375
376    fn from_proto(proto: ProtoMzAclItem) -> Result<Self, TryFromProtoError> {
377        match (proto.grantee, proto.grantor, proto.acl_mode) {
378            (Some(grantee), Some(grantor), Some(acl_mode)) => Ok(MzAclItem {
379                grantee: RoleId::from_proto(grantee)?,
380                grantor: RoleId::from_proto(grantor)?,
381                acl_mode: AclMode::from_proto(acl_mode)?,
382            }),
383            (None, _, _) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::grantee")),
384            (_, None, _) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::grantor")),
385            (_, _, None) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::acl_mode")),
386        }
387    }
388}
389
390impl Columnation for MzAclItem {
391    type InnerRegion = CopyRegion<MzAclItem>;
392}
393
394/// An encoded packed variant of [`MzAclItem`].
395///
396/// We uphold the variant that [`PackedMzAclItem`] sorts the same as [`MzAclItem`].
397#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
398pub struct PackedMzAclItem([u8; Self::SIZE]);
399
400impl PackedMzAclItem {
401    // Note: It's critical to the sort order guarantee that these tags sort the same as the
402    // variants of RoleId. We leave space between each tag for future variants.
403    //
404    // Note: An alternative would be to use a `u8` for the tag, while this is more memory
405    // efficient micro benchmarks show throughput increases by ~40% for the encode path and ~130%
406    // for the decode path when using a `u32`. Looking at the generated assembly there are more
407    // load and store instructions when using a `u8` and unaligned reads.
408
409    pub const SYSTEM_TAG: u32 = 100;
410    pub const PREDEFINED_TAG: u32 = 200;
411    pub const USER_TAG: u32 = 300;
412    pub const PUBLIC_TAG: u32 = 400;
413
414    #[inline]
415    fn encode_role(buf: &mut [u8], role: RoleId) {
416        soft_assert_no_log!(buf.len() == 12);
417
418        match role {
419            RoleId::System(val) => {
420                buf[..4].copy_from_slice(&Self::SYSTEM_TAG.to_be_bytes());
421                buf[4..].copy_from_slice(&val.to_be_bytes());
422            }
423            RoleId::Predefined(val) => {
424                buf[..4].copy_from_slice(&Self::PREDEFINED_TAG.to_be_bytes());
425                buf[4..].copy_from_slice(&val.to_be_bytes());
426            }
427            RoleId::User(val) => {
428                buf[..4].copy_from_slice(&Self::USER_TAG.to_be_bytes());
429                buf[4..].copy_from_slice(&val.to_be_bytes());
430            }
431            RoleId::Public => {
432                buf[..4].copy_from_slice(&Self::PUBLIC_TAG.to_be_bytes());
433            }
434        }
435    }
436
437    #[inline]
438    fn decode_role(buf: &[u8]) -> RoleId {
439        soft_assert_no_log!(buf.len() == 12);
440
441        let tag: [u8; 4] = buf[..4]
442            .try_into()
443            .expect("PackedMzAclItem should roundtrip");
444        let tag = u32::from_be_bytes(tag);
445
446        let val: [u8; 8] = buf[4..]
447            .try_into()
448            .expect("PackedMzAclItem should roundtrip");
449        let val = u64::from_be_bytes(val);
450
451        match tag {
452            Self::SYSTEM_TAG => RoleId::System(val),
453            Self::PREDEFINED_TAG => RoleId::Predefined(val),
454            Self::USER_TAG => RoleId::User(val),
455            Self::PUBLIC_TAG => RoleId::Public,
456            x => panic!("unrecognized tag {x}"),
457        }
458    }
459}
460
461impl FixedSizeCodec<MzAclItem> for PackedMzAclItem {
462    const SIZE: usize = 32;
463
464    fn as_bytes(&self) -> &[u8] {
465        &self.0
466    }
467
468    fn from_bytes(val: &[u8]) -> Result<Self, String>
469    where
470        Self: Sized,
471    {
472        let buf: [u8; Self::SIZE] = val.try_into().map_err(|_| {
473            format!(
474                "size for PackedMzAclItem is {} bytes, got {}",
475                Self::SIZE,
476                val.len()
477            )
478        })?;
479
480        Ok(PackedMzAclItem(buf))
481    }
482
483    #[inline]
484    fn from_value(value: MzAclItem) -> Self {
485        let mut buf = [0u8; 32];
486
487        Self::encode_role(&mut buf[..12], value.grantee);
488        Self::encode_role(&mut buf[12..24], value.grantor);
489        buf[24..].copy_from_slice(&value.acl_mode.bits().to_be_bytes());
490
491        PackedMzAclItem(buf)
492    }
493
494    #[inline]
495    fn into_value(self) -> MzAclItem {
496        let grantee = PackedMzAclItem::decode_role(&self.0[..12]);
497        let grantor = PackedMzAclItem::decode_role(&self.0[12..24]);
498
499        let acl_mode: [u8; 8] = self.0[24..]
500            .try_into()
501            .expect("PackedMzAclItem should roundtrip");
502        let acl_mode = AclMode::from_bits(u64::from_be_bytes(acl_mode))
503            .expect("PackedMzAclItem should roundtrip");
504
505        MzAclItem {
506            grantee,
507            grantor,
508            acl_mode,
509        }
510    }
511}
512
513/// A list of privileges granted to a role.
514///
515/// This is primarily used for compatibility in PostgreSQL.
516///
517/// See: <https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/utils/acl.h#L48-L59>
518#[derive(
519    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Hash, Deserialize, Arbitrary,
520)]
521pub struct AclItem {
522    /// Role that this item grants privileges to.
523    pub grantee: Oid,
524    /// Grantor of privileges.
525    pub grantor: Oid,
526    /// Privileges bit flag.
527    pub acl_mode: AclMode,
528}
529
530impl AclItem {
531    pub fn empty(grantee: Oid, grantor: Oid) -> AclItem {
532        AclItem {
533            grantee,
534            grantor,
535            acl_mode: AclMode::empty(),
536        }
537    }
538}
539
540// PostgreSQL has no stable binary encoding for AclItems. However, we need to be able to convert
541// types to and from binary, to pack a Datum in a row, so we invent our own encoding. The encoding
542// matches the in memory format that PostgreSQL uses, which is (Oid, Oid, AclMode).
543impl AclItem {
544    pub fn encode_binary(&self) -> Vec<u8> {
545        let mut res = Vec::with_capacity(Self::binary_size());
546        res.extend_from_slice(&self.grantee.0.to_le_bytes());
547        res.extend_from_slice(&self.grantor.0.to_le_bytes());
548        res.extend_from_slice(&self.acl_mode.bits().to_le_bytes());
549        res
550    }
551
552    pub fn decode_binary(raw: &[u8]) -> Result<AclItem, Error> {
553        if raw.len() != AclItem::binary_size() {
554            return Err(anyhow!(
555                "invalid binary size, expecting {}, found {}",
556                AclItem::binary_size(),
557                raw.len()
558            ));
559        }
560
561        let oid_size = size_of::<u32>();
562
563        let grantee = Oid(u32::from_le_bytes(raw[0..oid_size].try_into()?));
564        let raw = &raw[oid_size..];
565        let grantor = Oid(u32::from_le_bytes(raw[0..oid_size].try_into()?));
566        let raw = &raw[oid_size..];
567        let acl_mode = u64::from_le_bytes(raw.try_into()?);
568
569        Ok(AclItem {
570            grantee,
571            grantor,
572            acl_mode: AclMode { bits: acl_mode },
573        })
574    }
575
576    pub const fn binary_size() -> usize {
577        size_of::<u32>() + size_of::<u32>() + size_of::<u64>()
578    }
579}
580
581impl FromStr for AclItem {
582    type Err = Error;
583
584    /// For the PostgreSQL implementation see:
585    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L580-L609>
586    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L223-L390>
587    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L127-L187>
588    ///
589    /// PostgreSQL is able to look up OIDs in the catalog while parsing, so it accepts aclitems that
590    /// contain role OIDs or role names. We have no way to access the catalog here, so we can only
591    /// accept oids. Therefore our implementation is much simpler than PostgreSQL's.
592    fn from_str(s: &str) -> Result<Self, Self::Err> {
593        let parts: Vec<_> = s.split('=').collect();
594        let &[grantee, rest] = parts.as_slice() else {
595            return Err(anyhow!("invalid aclitem '{s}'"));
596        };
597
598        let parts: Vec<_> = rest.split('/').collect();
599        let &[acl_mode, grantor] = parts.as_slice() else {
600            return Err(anyhow!("invalid mz_aclitem '{s}'"));
601        };
602
603        let grantee: Oid = if grantee.is_empty() {
604            PUBLIC_ROLE_OID
605        } else {
606            Oid(grantee.parse()?)
607        };
608        let acl_mode: AclMode = acl_mode.parse()?;
609        let grantor: Oid = Oid(grantor.parse()?);
610
611        Ok(AclItem {
612            grantee,
613            grantor,
614            acl_mode,
615        })
616    }
617}
618
619impl fmt::Display for AclItem {
620    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
621        if self.grantee != PUBLIC_ROLE_OID {
622            write!(f, "{}", self.grantee.0)?;
623        }
624        write!(f, "={}/{}", self.acl_mode, self.grantor.0)
625    }
626}
627
628impl RustType<ProtoAclItem> for AclItem {
629    fn into_proto(&self) -> ProtoAclItem {
630        ProtoAclItem {
631            grantee: self.grantee.0,
632            grantor: self.grantor.0,
633            acl_mode: Some(self.acl_mode.into_proto()),
634        }
635    }
636
637    fn from_proto(proto: ProtoAclItem) -> Result<Self, TryFromProtoError> {
638        match proto.acl_mode {
639            Some(acl_mode) => Ok(AclItem {
640                grantee: Oid(proto.grantee),
641                grantor: Oid(proto.grantor),
642                acl_mode: AclMode::from_proto(acl_mode)?,
643            }),
644            None => Err(TryFromProtoError::missing_field("ProtoMzAclItem::acl_mode")),
645        }
646    }
647}
648
649impl Columnation for AclItem {
650    type InnerRegion = CopyRegion<AclItem>;
651}
652
653/// An encoded packed variant of [`AclItem`].
654///
655/// We uphold the variant that [`PackedAclItem`] sorts the same as [`AclItem`].
656#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
657pub struct PackedAclItem([u8; Self::SIZE]);
658
659impl FixedSizeCodec<AclItem> for PackedAclItem {
660    const SIZE: usize = 16;
661
662    fn as_bytes(&self) -> &[u8] {
663        &self.0
664    }
665
666    fn from_bytes(slice: &[u8]) -> Result<Self, String> {
667        let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
668            format!(
669                "size for PackedAclItem is {} bytes, got {}",
670                Self::SIZE,
671                slice.len()
672            )
673        })?;
674        Ok(PackedAclItem(buf))
675    }
676
677    #[inline]
678    fn from_value(value: AclItem) -> Self {
679        let mut buf = [0u8; 16];
680
681        buf[..4].copy_from_slice(&value.grantee.0.to_be_bytes());
682        buf[4..8].copy_from_slice(&value.grantor.0.to_be_bytes());
683        buf[8..].copy_from_slice(&value.acl_mode.bits().to_be_bytes());
684
685        PackedAclItem(buf)
686    }
687
688    #[inline]
689    fn into_value(self) -> AclItem {
690        let mut grantee = [0; 4];
691        grantee.copy_from_slice(&self.0[..4]);
692
693        let mut grantor = [0; 4];
694        grantor.copy_from_slice(&self.0[4..8]);
695
696        let mut acl_mode = [0; 8];
697        acl_mode.copy_from_slice(&self.0[8..]);
698        let acl_mode = AclMode::from_bits(u64::from_be_bytes(acl_mode))
699            .expect("PackedAclItem should roundtrip");
700
701        AclItem {
702            grantee: Oid(u32::from_be_bytes(grantee)),
703            grantor: Oid(u32::from_be_bytes(grantor)),
704            acl_mode,
705        }
706    }
707}
708
709/// A container of [`MzAclItem`]s that is optimized to look up an [`MzAclItem`] by the grantee.
710#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
711pub struct PrivilegeMap(
712    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] BTreeMap<RoleId, Vec<MzAclItem>>,
713);
714
715impl PrivilegeMap {
716    /// Creates a new empty `PrivilegeMap`.
717    pub fn new() -> PrivilegeMap {
718        PrivilegeMap(BTreeMap::new())
719    }
720
721    /// Creates a new `PrivilegeMap` from a collection of [`MzAclItem`]s.
722    pub fn from_mz_acl_items(items: impl IntoIterator<Item = MzAclItem>) -> PrivilegeMap {
723        let mut map = PrivilegeMap::new();
724        map.grant_all(items);
725        map
726    }
727
728    /// Get the acl item granted to `grantee` by `grantor`.
729    pub fn get_acl_item(&self, grantee: &RoleId, grantor: &RoleId) -> Option<&MzAclItem> {
730        self.0.get(grantee).and_then(|privileges| {
731            privileges
732                .into_iter()
733                .find(|mz_acl_item| &mz_acl_item.grantor == grantor)
734        })
735    }
736
737    /// Get all acl items granted to `grantee`.
738    pub fn get_acl_items_for_grantee(&self, grantee: &RoleId) -> impl Iterator<Item = &MzAclItem> {
739        self.0
740            .get(grantee)
741            .into_iter()
742            .flat_map(|privileges| privileges.into_iter())
743    }
744
745    /// Returns references to all contained [`MzAclItem`].
746    pub fn all_values(&self) -> impl Iterator<Item = &MzAclItem> {
747        self.0
748            .values()
749            .flat_map(|privileges| privileges.into_iter())
750    }
751
752    /// Returns clones of all contained [`MzAclItem`].
753    pub fn all_values_owned(&self) -> impl Iterator<Item = MzAclItem> + '_ {
754        self.all_values().cloned()
755    }
756
757    /// Consumes self and returns all contained [`MzAclItem`].
758    pub fn into_all_values(self) -> impl Iterator<Item = MzAclItem> {
759        self.0
760            .into_values()
761            .flat_map(|privileges| privileges.into_iter())
762    }
763
764    /// Adds an [`MzAclItem`] to this map.
765    pub fn grant(&mut self, privilege: MzAclItem) {
766        let grantee_privileges = self.0.entry(privilege.grantee).or_default();
767        if let Some(existing_privilege) = grantee_privileges
768            .iter_mut()
769            .find(|cur_privilege| cur_privilege.grantor == privilege.grantor)
770        {
771            // sanity check that the key is consistent.
772            assert_eq!(
773                privilege.grantee, existing_privilege.grantee,
774                "PrivilegeMap out of sync"
775            );
776            existing_privilege.acl_mode = existing_privilege.acl_mode.union(privilege.acl_mode);
777        } else {
778            grantee_privileges.push(privilege);
779        }
780    }
781
782    /// Grant multiple [`MzAclItem`]s to this map.
783    pub fn grant_all(&mut self, mz_acl_items: impl IntoIterator<Item = MzAclItem>) {
784        for mz_acl_item in mz_acl_items {
785            self.grant(mz_acl_item);
786        }
787    }
788
789    /// Removes an [`MzAclItem`] from this map.
790    pub fn revoke(&mut self, privilege: &MzAclItem) {
791        let grantee_privileges = self.0.entry(privilege.grantee).or_default();
792        if let Some(existing_privilege) = grantee_privileges
793            .iter_mut()
794            .find(|cur_privilege| cur_privilege.grantor == privilege.grantor)
795        {
796            // sanity check that the key is consistent.
797            assert_eq!(
798                privilege.grantee, existing_privilege.grantee,
799                "PrivilegeMap out of sync"
800            );
801            existing_privilege.acl_mode =
802                existing_privilege.acl_mode.difference(privilege.acl_mode);
803        }
804
805        // Remove empty privileges.
806        grantee_privileges.retain(|privilege| !privilege.acl_mode.is_empty());
807        if grantee_privileges.is_empty() {
808            self.0.remove(&privilege.grantee);
809        }
810    }
811
812    /// Returns a `PrivilegeMap` formatted as a `serde_json::Value` that is suitable for debugging. For
813    /// example `CatalogState::dump`.
814    pub fn debug_json(&self) -> serde_json::Value {
815        let privileges_by_str: BTreeMap<String, _> = self
816            .0
817            .iter()
818            .map(|(key, value)| (key.to_string(), value))
819            .collect();
820        serde_json::json!(privileges_by_str)
821    }
822}
823
824impl Default for PrivilegeMap {
825    fn default() -> PrivilegeMap {
826        PrivilegeMap::new()
827    }
828}
829
830/// Combines all [`MzAclItem`]s that have the same grantee and grantor.
831pub fn merge_mz_acl_items(
832    mz_acl_items: impl Iterator<Item = MzAclItem>,
833) -> impl Iterator<Item = MzAclItem> {
834    mz_acl_items
835        .fold(BTreeMap::new(), |mut accum, mz_acl_item| {
836            let item = accum
837                .entry((mz_acl_item.grantee, mz_acl_item.grantor))
838                .or_insert(MzAclItem::empty(mz_acl_item.grantee, mz_acl_item.grantor));
839            item.acl_mode |= mz_acl_item.acl_mode;
840            accum
841        })
842        .into_values()
843}
844
845#[mz_ore::test]
846fn test_mz_acl_parsing() {
847    let s = "u42=rw/s666";
848    let mz_acl: MzAclItem = s.parse().unwrap();
849    assert_eq!(RoleId::User(42), mz_acl.grantee);
850    assert_eq!(RoleId::System(666), mz_acl.grantor);
851    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
852    assert!(mz_acl.acl_mode.contains(AclMode::SELECT));
853    assert!(mz_acl.acl_mode.contains(AclMode::UPDATE));
854    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
855    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
856    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
857    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
858    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
859    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
860    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
861    assert_eq!(s, mz_acl.to_string());
862
863    let s = "=UC/u4";
864    let mz_acl: MzAclItem = s.parse().unwrap();
865    assert_eq!(RoleId::Public, mz_acl.grantee);
866    assert_eq!(RoleId::User(4), mz_acl.grantor);
867    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
868    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
869    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
870    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
871    assert!(mz_acl.acl_mode.contains(AclMode::USAGE));
872    assert!(mz_acl.acl_mode.contains(AclMode::CREATE));
873    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
874    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
875    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
876    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
877    assert_eq!(s, mz_acl.to_string());
878
879    let s = "s7=/s12";
880    let mz_acl: MzAclItem = s.parse().unwrap();
881    assert_eq!(RoleId::System(7), mz_acl.grantee);
882    assert_eq!(RoleId::System(12), mz_acl.grantor);
883    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
884    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
885    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
886    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
887    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
888    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
889    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
890    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
891    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
892    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
893    assert_eq!(s, mz_acl.to_string());
894
895    let s = "=/u100";
896    let mz_acl: MzAclItem = s.parse().unwrap();
897    assert_eq!(RoleId::Public, mz_acl.grantee);
898    assert_eq!(RoleId::User(100), mz_acl.grantor);
899    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
900    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
901    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
902    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
903    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
904    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
905    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
906    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
907    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
908    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
909    assert_eq!(s, mz_acl.to_string());
910
911    let s = "u1=RBNP/u2";
912    let mz_acl: MzAclItem = s.parse().unwrap();
913    assert_eq!(RoleId::User(1), mz_acl.grantee);
914    assert_eq!(RoleId::User(2), mz_acl.grantor);
915    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
916    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
917    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
918    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
919    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
920    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
921    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
922    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_DB));
923    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
924    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
925    assert_eq!(s, mz_acl.to_string());
926
927    mz_ore::assert_err!("u42/rw=u666".parse::<MzAclItem>());
928    mz_ore::assert_err!("u32=C/".parse::<MzAclItem>());
929    mz_ore::assert_err!("=/".parse::<MzAclItem>());
930    mz_ore::assert_err!("f62hfiuew827fhh".parse::<MzAclItem>());
931    mz_ore::assert_err!("u2=rw/s66=CU/u33".parse::<MzAclItem>());
932}
933
934#[mz_ore::test]
935fn test_mz_acl_item_binary() {
936    use std::ops::BitAnd;
937
938    let mz_acl_item = MzAclItem {
939        grantee: RoleId::User(42),
940        grantor: RoleId::System(666),
941        acl_mode: AclMode::empty()
942            .bitand(AclMode::SELECT)
943            .bitand(AclMode::UPDATE),
944    };
945    assert_eq!(
946        mz_acl_item,
947        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
948    );
949
950    let mz_acl_item = MzAclItem {
951        grantee: RoleId::Public,
952        grantor: RoleId::User(4),
953        acl_mode: AclMode::empty()
954            .bitand(AclMode::USAGE)
955            .bitand(AclMode::CREATE),
956    };
957    assert_eq!(
958        mz_acl_item,
959        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
960    );
961
962    let mz_acl_item = MzAclItem {
963        grantee: RoleId::System(7),
964        grantor: RoleId::System(12),
965        acl_mode: AclMode::empty(),
966    };
967    assert_eq!(
968        mz_acl_item,
969        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
970    );
971
972    let mz_acl_item = MzAclItem {
973        grantee: RoleId::Public,
974        grantor: RoleId::User(100),
975        acl_mode: AclMode::empty(),
976    };
977    assert_eq!(
978        mz_acl_item,
979        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
980    );
981
982    mz_ore::assert_err!(MzAclItem::decode_binary(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))
983}
984
985#[mz_ore::test]
986fn test_mz_acl_item_binary_size() {
987    assert_eq!(26, MzAclItem::binary_size());
988}
989
990#[mz_ore::test]
991fn test_acl_parsing() {
992    let s = "42=rw/666";
993    let acl: AclItem = s.parse().unwrap();
994    assert_eq!(42, acl.grantee.0);
995    assert_eq!(666, acl.grantor.0);
996    assert!(!acl.acl_mode.contains(AclMode::INSERT));
997    assert!(acl.acl_mode.contains(AclMode::SELECT));
998    assert!(acl.acl_mode.contains(AclMode::UPDATE));
999    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1000    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1001    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1002    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1003    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1004    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1005    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1006    assert_eq!(s, acl.to_string());
1007
1008    let s = "=UC/4";
1009    let acl: AclItem = s.parse().unwrap();
1010    assert_eq!(PUBLIC_ROLE_OID, acl.grantee);
1011    assert_eq!(4, acl.grantor.0);
1012    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1013    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1014    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1015    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1016    assert!(acl.acl_mode.contains(AclMode::USAGE));
1017    assert!(acl.acl_mode.contains(AclMode::CREATE));
1018    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1019    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1020    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1021    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1022    assert_eq!(s, acl.to_string());
1023
1024    let s = "7=/12";
1025    let acl: AclItem = s.parse().unwrap();
1026    assert_eq!(7, acl.grantee.0);
1027    assert_eq!(12, acl.grantor.0);
1028    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1029    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1030    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1031    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1032    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1033    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1034    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1035    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1036    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1037    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1038    assert_eq!(s, acl.to_string());
1039
1040    let s = "=/100";
1041    let acl: AclItem = s.parse().unwrap();
1042    assert_eq!(PUBLIC_ROLE_OID, acl.grantee);
1043    assert_eq!(100, acl.grantor.0);
1044    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1045    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1046    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1047    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1048    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1049    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1050    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1051    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1052    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1053    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1054    assert_eq!(s, acl.to_string());
1055
1056    let s = "1=RBNP/2";
1057    let acl: AclItem = s.parse().unwrap();
1058    assert_eq!(1, acl.grantee.0);
1059    assert_eq!(2, acl.grantor.0);
1060    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1061    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1062    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1063    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1064    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1065    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1066    assert!(acl.acl_mode.contains(AclMode::CREATE_ROLE));
1067    assert!(acl.acl_mode.contains(AclMode::CREATE_DB));
1068    assert!(acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1069    assert!(acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1070    assert_eq!(s, acl.to_string());
1071
1072    mz_ore::assert_err!("42/rw=666".parse::<AclItem>());
1073    mz_ore::assert_err!("u42=rw/u666".parse::<AclItem>());
1074    mz_ore::assert_err!("s42=rw/s666".parse::<AclItem>());
1075    mz_ore::assert_err!("u32=C/".parse::<AclItem>());
1076    mz_ore::assert_err!("=/".parse::<AclItem>());
1077    mz_ore::assert_err!("f62hfiuew827fhh".parse::<AclItem>());
1078    mz_ore::assert_err!("u2=rw/s66=CU/u33".parse::<AclItem>());
1079}
1080
1081#[mz_ore::test]
1082fn test_acl_item_binary() {
1083    use std::ops::BitAnd;
1084
1085    let acl_item = AclItem {
1086        grantee: Oid(42),
1087        grantor: Oid(666),
1088        acl_mode: AclMode::empty()
1089            .bitand(AclMode::SELECT)
1090            .bitand(AclMode::UPDATE),
1091    };
1092    assert_eq!(
1093        acl_item,
1094        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1095    );
1096
1097    let acl_item = AclItem {
1098        grantee: PUBLIC_ROLE_OID,
1099        grantor: Oid(4),
1100        acl_mode: AclMode::empty()
1101            .bitand(AclMode::USAGE)
1102            .bitand(AclMode::CREATE),
1103    };
1104    assert_eq!(
1105        acl_item,
1106        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1107    );
1108
1109    let acl_item = AclItem {
1110        grantee: Oid(7),
1111        grantor: Oid(12),
1112        acl_mode: AclMode::empty(),
1113    };
1114    assert_eq!(
1115        acl_item,
1116        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1117    );
1118
1119    let acl_item = AclItem {
1120        grantee: PUBLIC_ROLE_OID,
1121        grantor: Oid(100),
1122        acl_mode: AclMode::empty(),
1123    };
1124    assert_eq!(
1125        acl_item,
1126        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1127    );
1128
1129    mz_ore::assert_err!(AclItem::decode_binary(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))
1130}
1131
1132#[mz_ore::test]
1133fn test_acl_item_binary_size() {
1134    assert_eq!(16, AclItem::binary_size());
1135}
1136
1137proptest! {
1138  #[mz_ore::test]
1139  #[cfg_attr(miri, ignore)] // slow
1140  fn proptest_acl_item_binary_encoding_roundtrip(acl_item: AclItem) {
1141      let encoded = acl_item.encode_binary();
1142      let decoded = AclItem::decode_binary(&encoded).unwrap();
1143      assert_eq!(acl_item, decoded);
1144  }
1145
1146  #[mz_ore::test]
1147  #[cfg_attr(miri, ignore)] // slow
1148  fn proptest_valid_acl_item_str(acl_item: AclItem) {
1149      let encoded = acl_item.to_string();
1150      let decoded = AclItem::from_str(&encoded).unwrap();
1151      assert_eq!(acl_item, decoded);
1152  }
1153}
1154
1155#[mz_ore::test]
1156fn proptest_packed_acl_item_roundtrips() {
1157    fn roundtrip_acl_item(og: AclItem) {
1158        let packed = PackedAclItem::from_value(og);
1159        let rnd = packed.into_value();
1160        assert_eq!(og, rnd);
1161    }
1162
1163    proptest!(|(acl_item in proptest::arbitrary::any::<AclItem>())| {
1164        roundtrip_acl_item(acl_item);
1165    })
1166}
1167
1168#[mz_ore::test]
1169#[cfg_attr(miri, ignore)] // slow
1170fn proptest_packed_acl_item_sorts() {
1171    fn sort_acl_items(mut og: Vec<AclItem>) {
1172        let mut packed: Vec<_> = og.iter().copied().map(PackedAclItem::from_value).collect();
1173
1174        og.sort();
1175        packed.sort();
1176
1177        let rnd: Vec<_> = packed.into_iter().map(PackedAclItem::into_value).collect();
1178        assert_eq!(og, rnd);
1179    }
1180
1181    proptest!(|(acl_items in proptest::collection::vec(any::<AclItem>(), 0..64))| {
1182        sort_acl_items(acl_items);
1183    });
1184}
1185
1186#[mz_ore::test]
1187fn proptest_packed_mz_acl_item_roundtrips() {
1188    fn roundtrip_mz_acl_item(og: MzAclItem) {
1189        let packed = PackedMzAclItem::from_value(og);
1190        let rnd = packed.into_value();
1191        assert_eq!(og, rnd);
1192    }
1193
1194    proptest!(|(acl_item in proptest::arbitrary::any::<MzAclItem>())| {
1195        roundtrip_mz_acl_item(acl_item);
1196    })
1197}
1198
1199#[mz_ore::test]
1200#[cfg_attr(miri, ignore)] // slow
1201fn proptest_packed_mz_acl_item_sorts() {
1202    fn sort_mz_acl_items(mut og: Vec<MzAclItem>) {
1203        let mut packed: Vec<_> = og
1204            .iter()
1205            .copied()
1206            .map(PackedMzAclItem::from_value)
1207            .collect();
1208
1209        og.sort();
1210        packed.sort();
1211
1212        let rnd: Vec<_> = packed
1213            .into_iter()
1214            .map(PackedMzAclItem::into_value)
1215            .collect();
1216        assert_eq!(og, rnd);
1217    }
1218
1219    proptest!(|(acl_items in proptest::collection::vec(any::<MzAclItem>(), 0..64))| {
1220        sort_mz_acl_items(acl_items);
1221    });
1222}