Skip to main content

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