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