Skip to main content

xmtp_mls/groups/
group_permissions.rs

1use openmls::{
2    extensions::{Extension, Extensions, UnknownExtension},
3    group::{GroupContext, MlsGroup as OpenMlsGroup},
4};
5use prost::Message;
6use std::collections::HashMap;
7use thiserror::Error;
8use xmtp_common::ErrorCode;
9use xmtp_proto::xmtp::mls::message_contents::{
10    GroupMutablePermissionsV1 as GroupMutablePermissionsProto,
11    MembershipPolicy as MembershipPolicyProto, MetadataPolicy as MetadataPolicyProto,
12    PermissionsUpdatePolicy as PermissionsPolicyProto, PolicySet as PolicySetProto,
13    membership_policy::{
14        AndCondition as AndConditionProto, AnyCondition as AnyConditionProto,
15        BasePolicy as BasePolicyProto, Kind as PolicyKindProto,
16    },
17    metadata_policy::{
18        AndCondition as MetadataAndConditionProto, AnyCondition as MetadataAnyConditionProto,
19        Kind as MetadataPolicyKindProto, MetadataBasePolicy as MetadataBasePolicyProto,
20    },
21    permissions_update_policy::{
22        AndCondition as PermissionsAndConditionProto, AnyCondition as PermissionsAnyConditionProto,
23        Kind as PermissionsPolicyKindProto, PermissionsBasePolicy as PermissionsBasePolicyProto,
24    },
25};
26
27use super::validated_commit::{CommitParticipant, Inbox, MetadataFieldChange, ValidatedCommit};
28use xmtp_configuration::{GROUP_PERMISSIONS_EXTENSION_ID, SUPER_ADMIN_METADATA_PREFIX};
29use xmtp_mls_common::group_mutable_metadata::{GroupMutableMetadata, MetadataField};
30
31/// Errors that can occur when working with GroupMutablePermissions.
32#[derive(Debug, Error, ErrorCode)]
33pub enum GroupMutablePermissionsError {
34    /// Serialization error.
35    ///
36    /// Failed to encode permissions protobuf. Not retryable.
37    #[error("serialization: {0}")]
38    Serialization(#[from] prost::EncodeError),
39    /// Deserialization error.
40    ///
41    /// Failed to decode permissions protobuf. Not retryable.
42    #[error("deserialization: {0}")]
43    Deserialization(#[from] prost::DecodeError),
44    /// Policy error.
45    ///
46    /// Permission policy validation failed. Not retryable.
47    #[error("policy error {0}")]
48    Policy(#[from] PolicyError),
49    /// Invalid conversation type.
50    ///
51    /// Wrong conversation type for this operation. Not retryable.
52    #[error("invalid conversation type")]
53    InvalidConversationType,
54    /// Missing policies.
55    ///
56    /// Required permission policies not present. Not retryable.
57    #[error("missing policies")]
58    MissingPolicies,
59    /// Missing extension.
60    ///
61    /// Required MLS extension not found. Not retryable.
62    #[error("missing extension")]
63    MissingExtension,
64    /// Invalid permission policy option.
65    ///
66    /// Invalid permission policy configuration. Not retryable.
67    #[error("invalid permission policy option")]
68    InvalidPermissionPolicyOption,
69}
70
71/// Represents the mutable permissions for a group.
72///
73/// This struct is stored as an MLS Unknown Group Context Extension.
74#[derive(Debug, Clone, PartialEq)]
75pub struct GroupMutablePermissions {
76    /// The set of policies that define the permissions for the group.
77    pub policies: PolicySet,
78}
79
80impl GroupMutablePermissions {
81    /// Creates a new GroupMutablePermissions instance.
82    pub fn new(policies: PolicySet) -> Self {
83        Self { policies }
84    }
85
86    /// Returns the preconfigured policy for the group permissions.
87    pub fn preconfigured_policy(
88        &self,
89    ) -> Result<PreconfiguredPolicies, GroupMutablePermissionsError> {
90        Ok(PreconfiguredPolicies::from_policy_set(&self.policies)?)
91    }
92
93    /// Creates a GroupMutablePermissions instance from a proto representation.
94    pub(crate) fn from_proto(
95        proto: GroupMutablePermissionsProto,
96    ) -> Result<Self, GroupMutablePermissionsError> {
97        if proto.policies.is_none() {
98            return Err(GroupMutablePermissionsError::MissingPolicies);
99        }
100        let policies = proto.policies.expect("checked for none");
101
102        Ok(Self::new(PolicySet::from_proto(policies)?))
103    }
104
105    /// Converts the GroupMutablePermissions to its proto representation.
106    pub(crate) fn to_proto(
107        &self,
108    ) -> Result<GroupMutablePermissionsProto, GroupMutablePermissionsError> {
109        Ok(GroupMutablePermissionsProto {
110            policies: Some(self.policies.to_proto()?),
111        })
112    }
113}
114
115/// Implements conversion from GroupMutablePermissions to `Vec<u8>`.
116impl TryFrom<GroupMutablePermissions> for Vec<u8> {
117    type Error = GroupMutablePermissionsError;
118
119    fn try_from(value: GroupMutablePermissions) -> Result<Self, Self::Error> {
120        let mut buf = Vec::new();
121        let proto_val = value.to_proto()?;
122        proto_val.encode(&mut buf)?;
123
124        Ok(buf)
125    }
126}
127
128/// Implements conversion from `&Vec<u8>` to [`GroupMutablePermissions`].
129impl TryFrom<&Vec<u8>> for GroupMutablePermissions {
130    type Error = GroupMutablePermissionsError;
131
132    fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
133        let proto_val = GroupMutablePermissionsProto::decode(value.as_slice())?;
134        Self::from_proto(proto_val)
135    }
136}
137
138/// Implements conversion from GroupMutablePermissionsProto to GroupMutablePermissions.
139impl TryFrom<GroupMutablePermissionsProto> for GroupMutablePermissions {
140    type Error = GroupMutablePermissionsError;
141
142    fn try_from(value: GroupMutablePermissionsProto) -> Result<Self, Self::Error> {
143        Self::from_proto(value)
144    }
145}
146
147/// Implements conversion from &Extensions to GroupMutablePermissions.
148impl TryFrom<&Extensions<GroupContext>> for GroupMutablePermissions {
149    type Error = GroupMutablePermissionsError;
150
151    fn try_from(value: &Extensions<GroupContext>) -> Result<Self, Self::Error> {
152        for extension in value.iter() {
153            if let Extension::Unknown(GROUP_PERMISSIONS_EXTENSION_ID, UnknownExtension(metadata)) =
154                extension
155            {
156                return GroupMutablePermissions::try_from(metadata);
157            }
158        }
159        Err(GroupMutablePermissionsError::MissingExtension)
160    }
161}
162
163/// Implements conversion from &OpenMlsGroup to GroupMutablePermissions.
164impl TryFrom<&OpenMlsGroup> for GroupMutablePermissions {
165    type Error = GroupMutablePermissionsError;
166
167    fn try_from(value: &OpenMlsGroup) -> Result<Self, Self::Error> {
168        let extensions = value.extensions();
169        extensions.try_into()
170    }
171}
172
173/// Extracts group permissions from an OpenMlsGroup.
174pub fn extract_group_permissions(
175    group: &OpenMlsGroup,
176) -> Result<GroupMutablePermissions, GroupMutablePermissionsError> {
177    let extensions = group.extensions();
178    extensions.try_into()
179}
180
181/// A trait for policies that can update Metadata for the group.
182pub trait MetadataPolicy: std::fmt::Debug {
183    /// Evaluates the policy for a given actor and metadata change.
184    ///
185    /// Verify relevant metadata is actually changed before evaluating against the MetadataPolicy.
186    /// See evaluate_metadata_policy.
187    fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool;
188
189    /// Converts the policy to its proto representation.
190    fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError>;
191}
192
193/// Represents the base policies for metadata updates.
194#[derive(Clone, Copy, Debug, PartialEq)]
195pub enum MetadataBasePolicies {
196    Allow,
197    Deny,
198    AllowIfActorAdminOrSuperAdmin,
199    AllowIfActorSuperAdmin,
200}
201
202/// Implements the MetadataPolicy trait for MetadataBasePolicies.
203impl MetadataPolicy for &MetadataBasePolicies {
204    fn evaluate(&self, actor: &CommitParticipant, _change: &MetadataFieldChange) -> bool {
205        match self {
206            MetadataBasePolicies::Allow => true,
207            MetadataBasePolicies::Deny => false,
208            MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin => {
209                actor.is_admin || actor.is_super_admin
210            }
211            MetadataBasePolicies::AllowIfActorSuperAdmin => actor.is_super_admin,
212        }
213    }
214
215    fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
216        let inner = match self {
217            MetadataBasePolicies::Allow => MetadataBasePolicyProto::Allow as i32,
218            MetadataBasePolicies::Deny => MetadataBasePolicyProto::Deny as i32,
219            MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin => {
220                MetadataBasePolicyProto::AllowIfAdmin as i32
221            }
222            MetadataBasePolicies::AllowIfActorSuperAdmin => {
223                MetadataBasePolicyProto::AllowIfSuperAdmin as i32
224            }
225        };
226
227        Ok(MetadataPolicyProto {
228            kind: Some(MetadataPolicyKindProto::Base(inner)),
229        })
230    }
231}
232
233/// Represents the different types of metadata policies.
234#[derive(Debug, Clone, PartialEq)]
235#[allow(dead_code)]
236pub enum MetadataPolicies {
237    Standard(MetadataBasePolicies),
238    AndCondition(MetadataAndCondition),
239    AnyCondition(MetadataAnyCondition),
240}
241
242impl MetadataPolicies {
243    /// Creates a default map of metadata policies.
244    pub fn default_map(policies: MetadataPolicies) -> HashMap<String, MetadataPolicies> {
245        let mut map: HashMap<String, MetadataPolicies> = HashMap::new();
246        for field in GroupMutableMetadata::supported_fields() {
247            match field {
248                MetadataField::MessageDisappearInNS => {
249                    map.insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
250                }
251                MetadataField::MessageDisappearFromNS => {
252                    map.insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
253                }
254                MetadataField::MinimumSupportedProtocolVersion => {
255                    map.insert(
256                        field.to_string(),
257                        MetadataPolicies::allow_if_actor_super_admin(),
258                    );
259                }
260                _ => {
261                    map.insert(field.to_string(), policies.clone());
262                }
263            }
264        }
265        map
266    }
267
268    // by default members of DM groups can update all metadata
269    pub fn dm_map() -> HashMap<String, MetadataPolicies> {
270        let mut map: HashMap<String, MetadataPolicies> = HashMap::new();
271        for field in GroupMutableMetadata::supported_fields() {
272            map.insert(field.to_string(), MetadataPolicies::allow());
273        }
274        map
275    }
276
277    /// Creates an "Allow" metadata policy.
278    pub fn allow() -> Self {
279        MetadataPolicies::Standard(MetadataBasePolicies::Allow)
280    }
281
282    /// Creates a "Deny" metadata policy.
283    pub fn deny() -> Self {
284        MetadataPolicies::Standard(MetadataBasePolicies::Deny)
285    }
286
287    /// Creates an "Allow if actor is admin" metadata policy.
288    pub fn allow_if_actor_admin() -> Self {
289        MetadataPolicies::Standard(MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin)
290    }
291
292    /// Creates an "Allow if actor is super admin" metadata policy.
293    pub fn allow_if_actor_super_admin() -> Self {
294        MetadataPolicies::Standard(MetadataBasePolicies::AllowIfActorSuperAdmin)
295    }
296
297    /// Creates an "And" condition metadata policy.
298    pub fn and(policies: Vec<MetadataPolicies>) -> Self {
299        MetadataPolicies::AndCondition(MetadataAndCondition::new(policies))
300    }
301
302    /// Creates an "Any" condition metadata policy.
303    pub fn any(policies: Vec<MetadataPolicies>) -> Self {
304        MetadataPolicies::AnyCondition(MetadataAnyCondition::new(policies))
305    }
306}
307
308/// Implements conversion from MetadataPolicyProto to MetadataPolicies.
309impl TryFrom<MetadataPolicyProto> for MetadataPolicies {
310    type Error = PolicyError;
311
312    fn try_from(proto: MetadataPolicyProto) -> Result<Self, Self::Error> {
313        match proto.kind {
314            Some(MetadataPolicyKindProto::Base(inner)) => match inner {
315                1 => Ok(MetadataPolicies::allow()),
316                2 => Ok(MetadataPolicies::deny()),
317                3 => Ok(MetadataPolicies::allow_if_actor_admin()),
318                4 => Ok(MetadataPolicies::allow_if_actor_super_admin()),
319                _ => Err(PolicyError::InvalidMetadataPolicy),
320            },
321            Some(MetadataPolicyKindProto::AndCondition(inner)) => {
322                if inner.policies.is_empty() {
323                    return Err(PolicyError::InvalidMetadataPolicy);
324                }
325                let policies = inner
326                    .policies
327                    .into_iter()
328                    .map(|policy| policy.try_into())
329                    .collect::<Result<Vec<MetadataPolicies>, PolicyError>>()?;
330
331                Ok(MetadataPolicies::and(policies))
332            }
333            Some(MetadataPolicyKindProto::AnyCondition(inner)) => {
334                if inner.policies.is_empty() {
335                    return Err(PolicyError::InvalidMetadataPolicy);
336                }
337
338                let policies = inner
339                    .policies
340                    .into_iter()
341                    .map(|policy| policy.try_into())
342                    .collect::<Result<Vec<MetadataPolicies>, PolicyError>>()?;
343
344                Ok(MetadataPolicies::any(policies))
345            }
346            None => Err(PolicyError::InvalidMetadataPolicy),
347        }
348    }
349}
350
351/// Implements the MetadataPolicy trait for MetadataPolicies.
352impl MetadataPolicy for MetadataPolicies {
353    fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
354        match self {
355            MetadataPolicies::Standard(policy) => policy.evaluate(actor, change),
356            MetadataPolicies::AndCondition(policy) => policy.evaluate(actor, change),
357            MetadataPolicies::AnyCondition(policy) => policy.evaluate(actor, change),
358        }
359    }
360
361    fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
362        Ok(match self {
363            MetadataPolicies::Standard(policy) => policy.to_proto()?,
364            MetadataPolicies::AndCondition(policy) => policy.to_proto()?,
365            MetadataPolicies::AnyCondition(policy) => policy.to_proto()?,
366        })
367    }
368}
369
370/// An AndCondition evaluates to true if all the policies it contains evaluate to true.
371#[derive(Clone, Debug, PartialEq)]
372pub struct MetadataAndCondition {
373    policies: Vec<MetadataPolicies>,
374}
375
376impl MetadataAndCondition {
377    pub(super) fn new(policies: Vec<MetadataPolicies>) -> Self {
378        Self { policies }
379    }
380}
381
382/// Implements the MetadataPolicy trait for MetadataAndCondition.
383impl MetadataPolicy for MetadataAndCondition {
384    fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
385        self.policies
386            .iter()
387            .all(|policy| policy.evaluate(actor, change))
388    }
389
390    fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
391        Ok(MetadataPolicyProto {
392            kind: Some(MetadataPolicyKindProto::AndCondition(
393                MetadataAndConditionProto {
394                    policies: self
395                        .policies
396                        .iter()
397                        .map(|policy| policy.to_proto())
398                        .collect::<Result<Vec<MetadataPolicyProto>, PolicyError>>()?,
399                },
400            )),
401        })
402    }
403}
404
405/// An AnyCondition evaluates to true if any of the contained policies evaluate to true.
406#[derive(Clone, Debug, PartialEq)]
407pub struct MetadataAnyCondition {
408    policies: Vec<MetadataPolicies>,
409}
410
411#[allow(dead_code)]
412impl MetadataAnyCondition {
413    pub(super) fn new(policies: Vec<MetadataPolicies>) -> Self {
414        Self { policies }
415    }
416}
417
418/// Implements the MetadataPolicy trait for MetadataAnyCondition.
419impl MetadataPolicy for MetadataAnyCondition {
420    fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
421        self.policies
422            .iter()
423            .any(|policy| policy.evaluate(actor, change))
424    }
425
426    fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
427        Ok(MetadataPolicyProto {
428            kind: Some(MetadataPolicyKindProto::AnyCondition(
429                MetadataAnyConditionProto {
430                    policies: self
431                        .policies
432                        .iter()
433                        .map(|policy| policy.to_proto())
434                        .collect::<Result<Vec<MetadataPolicyProto>, PolicyError>>()?,
435                },
436            )),
437        })
438    }
439}
440
441/// A trait for policies that can update Permissions for the group.
442pub trait PermissionsPolicy: std::fmt::Debug {
443    /// Evaluates the policy for a given actor.
444    fn evaluate(&self, actor: &CommitParticipant) -> bool;
445
446    /// Converts the policy to its proto representation.
447    fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError>;
448}
449
450/// Represents the base policies for permissions updates.
451#[derive(Clone, Copy, Debug, PartialEq)]
452pub enum PermissionsBasePolicies {
453    Deny,
454    AllowIfActorAdminOrSuperAdmin,
455    AllowIfActorSuperAdmin,
456}
457
458/// Implements the PermissionsPolicy trait for PermissionsBasePolicies.
459impl PermissionsPolicy for &PermissionsBasePolicies {
460    fn evaluate(&self, actor: &CommitParticipant) -> bool {
461        match self {
462            PermissionsBasePolicies::Deny => false,
463            PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin => {
464                actor.is_admin || actor.is_super_admin
465            }
466            PermissionsBasePolicies::AllowIfActorSuperAdmin => actor.is_super_admin,
467        }
468    }
469
470    fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
471        let inner = match self {
472            PermissionsBasePolicies::Deny => PermissionsBasePolicyProto::Deny as i32,
473            PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin => {
474                PermissionsBasePolicyProto::AllowIfAdmin as i32
475            }
476            PermissionsBasePolicies::AllowIfActorSuperAdmin => {
477                PermissionsBasePolicyProto::AllowIfSuperAdmin as i32
478            }
479        };
480
481        Ok(PermissionsPolicyProto {
482            kind: Some(PermissionsPolicyKindProto::Base(inner)),
483        })
484    }
485}
486
487/// Represents the different types of permissions policies.
488#[derive(Debug, Clone, PartialEq)]
489#[allow(dead_code)]
490pub enum PermissionsPolicies {
491    Standard(PermissionsBasePolicies),
492    AndCondition(PermissionsAndCondition),
493    AnyCondition(PermissionsAnyCondition),
494}
495
496impl PermissionsPolicies {
497    /// Creates a "Deny" permissions policy.
498    pub fn deny() -> Self {
499        PermissionsPolicies::Standard(PermissionsBasePolicies::Deny)
500    }
501
502    /// Creates an "Allow if actor is admin" permissions policy.
503    pub fn allow_if_actor_admin() -> Self {
504        PermissionsPolicies::Standard(PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin)
505    }
506
507    /// Creates an "Allow if actor is super admin" permissions policy.
508    pub fn allow_if_actor_super_admin() -> Self {
509        PermissionsPolicies::Standard(PermissionsBasePolicies::AllowIfActorSuperAdmin)
510    }
511
512    /// Creates an "And" condition permissions policy.
513    pub fn and(policies: Vec<PermissionsPolicies>) -> Self {
514        PermissionsPolicies::AndCondition(PermissionsAndCondition::new(policies))
515    }
516
517    /// Creates an "Any" condition permissions policy.
518    pub fn any(policies: Vec<PermissionsPolicies>) -> Self {
519        PermissionsPolicies::AnyCondition(PermissionsAnyCondition::new(policies))
520    }
521}
522
523/// Implements conversion from PermissionsPolicyProto to PermissionsPolicies.
524impl TryFrom<PermissionsPolicyProto> for PermissionsPolicies {
525    type Error = PolicyError;
526
527    fn try_from(proto: PermissionsPolicyProto) -> Result<Self, Self::Error> {
528        match proto.kind {
529            Some(PermissionsPolicyKindProto::Base(inner)) => match inner {
530                1 => Ok(PermissionsPolicies::deny()),
531                2 => Ok(PermissionsPolicies::allow_if_actor_admin()),
532                3 => Ok(PermissionsPolicies::allow_if_actor_super_admin()),
533                _ => Err(PolicyError::InvalidPermissionsPolicy),
534            },
535            Some(PermissionsPolicyKindProto::AndCondition(inner)) => {
536                if inner.policies.is_empty() {
537                    return Err(PolicyError::InvalidPermissionsPolicy);
538                }
539                let policies = inner
540                    .policies
541                    .into_iter()
542                    .map(|policy| policy.try_into())
543                    .collect::<Result<Vec<PermissionsPolicies>, PolicyError>>()?;
544
545                Ok(PermissionsPolicies::and(policies))
546            }
547            Some(PermissionsPolicyKindProto::AnyCondition(inner)) => {
548                if inner.policies.is_empty() {
549                    return Err(PolicyError::InvalidPermissionsPolicy);
550                }
551
552                let policies = inner
553                    .policies
554                    .into_iter()
555                    .map(|policy| policy.try_into())
556                    .collect::<Result<Vec<PermissionsPolicies>, PolicyError>>()?;
557
558                Ok(PermissionsPolicies::any(policies))
559            }
560            None => Err(PolicyError::InvalidPermissionsPolicy),
561        }
562    }
563}
564
565/// Implements the PermissionsPolicy trait for PermissionsPolicies.
566impl PermissionsPolicy for PermissionsPolicies {
567    fn evaluate(&self, actor: &CommitParticipant) -> bool {
568        match self {
569            PermissionsPolicies::Standard(policy) => policy.evaluate(actor),
570            PermissionsPolicies::AndCondition(policy) => policy.evaluate(actor),
571            PermissionsPolicies::AnyCondition(policy) => policy.evaluate(actor),
572        }
573    }
574
575    fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
576        Ok(match self {
577            PermissionsPolicies::Standard(policy) => policy.to_proto()?,
578            PermissionsPolicies::AndCondition(policy) => policy.to_proto()?,
579            PermissionsPolicies::AnyCondition(policy) => policy.to_proto()?,
580        })
581    }
582}
583
584/// An AndCondition evaluates to true if all the policies it contains evaluate to true.
585#[derive(Clone, Debug, PartialEq)]
586pub struct PermissionsAndCondition {
587    policies: Vec<PermissionsPolicies>,
588}
589
590impl PermissionsAndCondition {
591    pub(super) fn new(policies: Vec<PermissionsPolicies>) -> Self {
592        Self { policies }
593    }
594}
595
596/// Implements the PermissionsPolicy trait for PermissionsAndCondition.
597impl PermissionsPolicy for PermissionsAndCondition {
598    fn evaluate(&self, actor: &CommitParticipant) -> bool {
599        self.policies.iter().all(|policy| policy.evaluate(actor))
600    }
601
602    fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
603        Ok(PermissionsPolicyProto {
604            kind: Some(PermissionsPolicyKindProto::AndCondition(
605                PermissionsAndConditionProto {
606                    policies: self
607                        .policies
608                        .iter()
609                        .map(|policy| policy.to_proto())
610                        .collect::<Result<Vec<PermissionsPolicyProto>, PolicyError>>()?,
611                },
612            )),
613        })
614    }
615}
616
617/// An AnyCondition evaluates to true if any of the contained policies evaluate to true.
618#[derive(Clone, Debug, PartialEq)]
619pub struct PermissionsAnyCondition {
620    policies: Vec<PermissionsPolicies>,
621}
622
623#[allow(dead_code)]
624impl PermissionsAnyCondition {
625    pub(super) fn new(policies: Vec<PermissionsPolicies>) -> Self {
626        Self { policies }
627    }
628}
629
630/// Implements the PermissionsPolicy trait for PermissionsAnyCondition.
631impl PermissionsPolicy for PermissionsAnyCondition {
632    fn evaluate(&self, actor: &CommitParticipant) -> bool {
633        self.policies.iter().any(|policy| policy.evaluate(actor))
634    }
635
636    fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
637        Ok(PermissionsPolicyProto {
638            kind: Some(PermissionsPolicyKindProto::AnyCondition(
639                PermissionsAnyConditionProto {
640                    policies: self
641                        .policies
642                        .iter()
643                        .map(|policy| policy.to_proto())
644                        .collect::<Result<Vec<PermissionsPolicyProto>, PolicyError>>()?,
645                },
646            )),
647        })
648    }
649}
650
651/// A trait for policies that can add/remove members and installations for the group.
652pub trait MembershipPolicy: std::fmt::Debug {
653    /// Evaluates the policy for a given actor and inbox change.
654    fn evaluate(&self, actor: &CommitParticipant, change: &Inbox) -> bool;
655
656    /// Converts the policy to its proto representation.
657    fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError>;
658}
659
660/// Errors that can occur when working with policies.
661#[derive(Debug, Error)]
662pub enum PolicyError {
663    #[error("serialization {0}")]
664    Serialization(#[from] prost::EncodeError),
665    #[error("deserialization {0}")]
666    Deserialization(#[from] prost::DecodeError),
667    #[error("Missing metadata policy field: {name}")]
668    MissingMetadataPolicyField { name: String },
669    #[error("invalid policy")]
670    InvalidPolicy,
671    #[error("unexpected preset policy")]
672    InvalidPresetPolicy,
673    #[error("invalid metadata policy")]
674    InvalidMetadataPolicy,
675    #[error("invalid membership policy")]
676    InvalidMembershipPolicy,
677    #[error("invalid permissions policy")]
678    InvalidPermissionsPolicy,
679    #[error("from proto add member invalid policy")]
680    FromProtoAddMemberInvalidPolicy,
681    #[error("from proto remove member invalid policy")]
682    FromProtoRemoveMemberInvalidPolicy,
683    #[error("from proto add admin invalid policy")]
684    FromProtoAddAdminInvalidPolicy,
685    #[error("from proto remove admin invalid policy")]
686    FromProtoRemoveAdminInvalidPolicy,
687    #[error("from proto update permissions invalid policy")]
688    FromProtoUpdatePermissionsInvalidPolicy,
689}
690
691/// Represents the base policies for membership updates.
692#[derive(Debug, Clone, Copy, PartialEq)]
693#[allow(dead_code)]
694#[repr(u8)]
695pub enum BasePolicies {
696    Allow,
697    Deny,
698    // Allow if the change only applies to subject installations with the same account address as the actor
699    AllowSameMember,
700    AllowIfAdminOrSuperAdmin,
701    AllowIfSuperAdmin,
702}
703
704/// Implements the MembershipPolicy trait for BasePolicies.
705impl MembershipPolicy for BasePolicies {
706    fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
707        match self {
708            BasePolicies::Allow => true,
709            BasePolicies::Deny => false,
710            BasePolicies::AllowSameMember => inbox.inbox_id == actor.inbox_id,
711            BasePolicies::AllowIfAdminOrSuperAdmin => actor.is_admin || actor.is_super_admin,
712            BasePolicies::AllowIfSuperAdmin => actor.is_super_admin,
713        }
714    }
715
716    fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
717        let inner = match self {
718            BasePolicies::Allow => BasePolicyProto::Allow as i32,
719            BasePolicies::Deny => BasePolicyProto::Deny as i32,
720            BasePolicies::AllowSameMember => return Err(PolicyError::InvalidPolicy), // AllowSameMember is not needed on any of the wire format protos
721            BasePolicies::AllowIfAdminOrSuperAdmin => {
722                BasePolicyProto::AllowIfAdminOrSuperAdmin as i32
723            }
724            BasePolicies::AllowIfSuperAdmin => BasePolicyProto::AllowIfSuperAdmin as i32,
725        };
726
727        Ok(MembershipPolicyProto {
728            kind: Some(PolicyKindProto::Base(inner)),
729        })
730    }
731}
732
733/// Represents the different types of membership policies.
734#[derive(Debug, Clone, PartialEq)]
735#[allow(dead_code)]
736pub enum MembershipPolicies {
737    Standard(BasePolicies),
738    AndCondition(AndCondition),
739    AnyCondition(AnyCondition),
740}
741
742impl MembershipPolicies {
743    /// Creates an "Allow" membership policy.
744    pub fn allow() -> Self {
745        MembershipPolicies::Standard(BasePolicies::Allow)
746    }
747
748    /// Creates a "Deny" membership policy.
749    pub fn deny() -> Self {
750        MembershipPolicies::Standard(BasePolicies::Deny)
751    }
752
753    /// Creates an "Allow if actor is admin" membership policy.
754    #[allow(dead_code)]
755    pub fn allow_if_actor_admin() -> Self {
756        MembershipPolicies::Standard(BasePolicies::AllowIfAdminOrSuperAdmin)
757    }
758
759    /// Creates an "Allow if actor is super admin" membership policy.
760    #[allow(dead_code)]
761    pub fn allow_if_actor_super_admin() -> Self {
762        MembershipPolicies::Standard(BasePolicies::AllowIfSuperAdmin)
763    }
764
765    /// Creates an "And" condition membership policy.
766    pub fn and(policies: Vec<MembershipPolicies>) -> Self {
767        MembershipPolicies::AndCondition(AndCondition::new(policies))
768    }
769
770    /// Creates an "Any" condition membership policy.
771    pub fn any(policies: Vec<MembershipPolicies>) -> Self {
772        MembershipPolicies::AnyCondition(AnyCondition::new(policies))
773    }
774}
775
776/// Implements conversion from MembershipPolicyProto to MembershipPolicies.
777impl TryFrom<MembershipPolicyProto> for MembershipPolicies {
778    type Error = PolicyError;
779
780    fn try_from(proto: MembershipPolicyProto) -> Result<Self, Self::Error> {
781        match proto.kind {
782            Some(PolicyKindProto::Base(inner)) => match inner {
783                1 => Ok(MembershipPolicies::allow()),
784                2 => Ok(MembershipPolicies::deny()),
785                3 => Ok(MembershipPolicies::allow_if_actor_admin()),
786                4 => Ok(MembershipPolicies::allow_if_actor_super_admin()),
787                _ => Err(PolicyError::InvalidMembershipPolicy),
788            },
789            Some(PolicyKindProto::AndCondition(inner)) => {
790                if inner.policies.is_empty() {
791                    return Err(PolicyError::InvalidMembershipPolicy);
792                }
793                let policies = inner
794                    .policies
795                    .into_iter()
796                    .map(|policy| policy.try_into())
797                    .collect::<Result<Vec<MembershipPolicies>, PolicyError>>()?;
798
799                Ok(MembershipPolicies::and(policies))
800            }
801            Some(PolicyKindProto::AnyCondition(inner)) => {
802                if inner.policies.is_empty() {
803                    return Err(PolicyError::InvalidMembershipPolicy);
804                }
805
806                let policies = inner
807                    .policies
808                    .into_iter()
809                    .map(|policy| policy.try_into())
810                    .collect::<Result<Vec<MembershipPolicies>, PolicyError>>()?;
811
812                Ok(MembershipPolicies::any(policies))
813            }
814            None => Err(PolicyError::InvalidMembershipPolicy),
815        }
816    }
817}
818
819/// Implements the MembershipPolicy trait for MembershipPolicies.
820impl MembershipPolicy for MembershipPolicies {
821    fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
822        match self {
823            MembershipPolicies::Standard(policy) => policy.evaluate(actor, inbox),
824            MembershipPolicies::AndCondition(policy) => policy.evaluate(actor, inbox),
825            MembershipPolicies::AnyCondition(policy) => policy.evaluate(actor, inbox),
826        }
827    }
828
829    fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
830        Ok(match self {
831            MembershipPolicies::Standard(policy) => policy.to_proto()?,
832            MembershipPolicies::AndCondition(policy) => policy.to_proto()?,
833            MembershipPolicies::AnyCondition(policy) => policy.to_proto()?,
834        })
835    }
836}
837
838/// An AndCondition evaluates to true if all the policies it contains evaluate to true.
839#[derive(Clone, Debug, PartialEq)]
840pub struct AndCondition {
841    policies: Vec<MembershipPolicies>,
842}
843
844impl AndCondition {
845    pub(super) fn new(policies: Vec<MembershipPolicies>) -> Self {
846        Self { policies }
847    }
848}
849
850/// Implements the MembershipPolicy trait for AndCondition.
851impl MembershipPolicy for AndCondition {
852    fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
853        self.policies
854            .iter()
855            .all(|policy| policy.evaluate(actor, inbox))
856    }
857
858    fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
859        Ok(MembershipPolicyProto {
860            kind: Some(PolicyKindProto::AndCondition(AndConditionProto {
861                policies: self
862                    .policies
863                    .iter()
864                    .map(|policy| policy.to_proto())
865                    .collect::<Result<Vec<MembershipPolicyProto>, PolicyError>>()?,
866            })),
867        })
868    }
869}
870
871/// An AnyCondition evaluates to true if any of the contained policies evaluate to true.
872#[derive(Clone, Debug, PartialEq)]
873pub struct AnyCondition {
874    policies: Vec<MembershipPolicies>,
875}
876
877#[allow(dead_code)]
878impl AnyCondition {
879    pub(super) fn new(policies: Vec<MembershipPolicies>) -> Self {
880        Self { policies }
881    }
882}
883
884/// Implements the MembershipPolicy trait for AnyCondition.
885impl MembershipPolicy for AnyCondition {
886    fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
887        self.policies
888            .iter()
889            .any(|policy| policy.evaluate(actor, inbox))
890    }
891
892    fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
893        Ok(MembershipPolicyProto {
894            kind: Some(PolicyKindProto::AnyCondition(AnyConditionProto {
895                policies: self
896                    .policies
897                    .iter()
898                    .map(|policy| policy.to_proto())
899                    .collect::<Result<Vec<MembershipPolicyProto>, PolicyError>>()?,
900            })),
901        })
902    }
903}
904
905/// Represents a set of policies for a group.
906#[derive(Debug, Clone, PartialEq)]
907#[allow(dead_code)]
908pub struct PolicySet {
909    /// The policy for adding members to the group.
910    pub add_member_policy: MembershipPolicies,
911    /// The policy for removing members from the group.
912    pub remove_member_policy: MembershipPolicies,
913    /// The policies for updating metadata fields.
914    pub update_metadata_policy: HashMap<String, MetadataPolicies>,
915    /// The policy for adding admins to the group.
916    pub add_admin_policy: PermissionsPolicies,
917    /// The policy for removing admins from the group.
918    pub remove_admin_policy: PermissionsPolicies,
919    /// The policy for updating permissions.
920    pub update_permissions_policy: PermissionsPolicies,
921}
922
923impl PolicySet {
924    /// Creates a new PolicySet instance.
925    pub fn new(
926        add_member_policy: MembershipPolicies,
927        remove_member_policy: MembershipPolicies,
928        update_metadata_policy: HashMap<String, MetadataPolicies>,
929        add_admin_policy: PermissionsPolicies,
930        remove_admin_policy: PermissionsPolicies,
931        update_permissions_policy: PermissionsPolicies,
932    ) -> Self {
933        Self {
934            add_member_policy,
935            remove_member_policy,
936            update_metadata_policy,
937            add_admin_policy,
938            remove_admin_policy,
939            update_permissions_policy,
940        }
941    }
942
943    pub fn new_dm() -> Self {
944        Self {
945            add_member_policy: MembershipPolicies::deny(),
946            remove_member_policy: MembershipPolicies::deny(),
947            update_metadata_policy: MetadataPolicies::dm_map(),
948            add_admin_policy: PermissionsPolicies::deny(),
949            remove_admin_policy: PermissionsPolicies::deny(),
950            update_permissions_policy: PermissionsPolicies::deny(),
951        }
952    }
953
954    /// The [`evaluate_commit`](Self::evaluate_commit) function is the core function for client side verification
955    /// that [ValidatedCommit]
956    /// adheres to the XMTP permission policies set in the PolicySet.
957    ///
958    /// Permissions are checked against the **proposer** of each action when available,
959    /// not the committer. This allows one member to commit proposals created by another
960    /// member, as long as the proposer had permission to create those proposals.
961    pub fn evaluate_commit(&self, commit: &ValidatedCommit) -> bool {
962        // Verify add member policy was not violated
963        // For each added inbox, check the proposer's permissions (if known), otherwise use actor
964        let mut added_inboxes_valid = self.evaluate_policy_with_proposer(
965            commit.added_inboxes.iter(),
966            &self.add_member_policy,
967            &commit.actor,
968        );
969
970        // We can always add DM member's inboxId to a DM
971        if let Some(dm_members) = &commit.dm_members
972            && commit.added_inboxes.len() == 1
973        {
974            let added_inbox_id = &commit.added_inboxes[0].inbox_id;
975            if (added_inbox_id == &dm_members.member_one_inbox_id
976                || added_inbox_id == &dm_members.member_two_inbox_id)
977                && added_inbox_id != &commit.actor_inbox_id()
978            {
979                added_inboxes_valid = true;
980            }
981        }
982
983        // Verify remove member policy was not violated
984        // Super admin can not be removed from a group
985        // For each removed inbox, check the proposer's permissions (if known), otherwise use actor
986        let removed_inboxes_valid = self.evaluate_policy_with_proposer(
987            commit.removed_inboxes.iter(),
988            &self.remove_member_policy,
989            &commit.actor,
990        ) && !commit
991            .removed_inboxes
992            .iter()
993            .any(|inbox| inbox.is_super_admin);
994
995        // Verify that update metadata policy was not violated
996        // Metadata/admin/permission changes come from GCE proposals. The committer (actor)
997        // is responsible for these changes — they create or endorse the GCE in the commit.
998        // Using proposers.first() was wrong because proposal queue order is arbitrary and
999        // the first proposer may have proposed something unrelated (e.g., an Add proposal).
1000        let metadata_changes_valid = self.evaluate_metadata_policy(
1001            commit
1002                .metadata_validation_info
1003                .metadata_field_changes
1004                .iter(),
1005            &self.update_metadata_policy,
1006            &commit.actor,
1007        );
1008
1009        // Verify that add admin policy was not violated
1010        let admin_actor = &commit.actor;
1011        let added_admins_valid = commit.metadata_validation_info.admins_added.is_empty()
1012            || self.add_admin_policy.evaluate(admin_actor);
1013
1014        // Verify that remove admin policy was not violated
1015        let removed_admins_valid = commit.metadata_validation_info.admins_removed.is_empty()
1016            || self.remove_admin_policy.evaluate(admin_actor);
1017
1018        // Verify that super admin add policy was not violated
1019        let super_admin_add_valid = commit
1020            .metadata_validation_info
1021            .super_admins_added
1022            .is_empty()
1023            || admin_actor.is_super_admin;
1024
1025        // Verify that super admin remove policy was not violated
1026        // You can never remove the last super admin
1027        let super_admin_remove_valid = commit
1028            .metadata_validation_info
1029            .super_admins_removed
1030            .is_empty()
1031            || (admin_actor.is_super_admin && commit.metadata_validation_info.num_super_admins > 0);
1032
1033        // Permissions can only be changed by the super admin
1034        // Use first proposer for permission changes if available
1035        let permissions_changes_valid = !commit.permissions_changed || admin_actor.is_super_admin;
1036
1037        added_inboxes_valid
1038            && removed_inboxes_valid
1039            && metadata_changes_valid
1040            && added_admins_valid
1041            && removed_admins_valid
1042            && super_admin_add_valid
1043            && super_admin_remove_valid
1044            && permissions_changes_valid
1045    }
1046
1047    /// Evaluates a policy for a given set of changes, using the proposer for each inbox when available.
1048    /// This allows permissions to be checked against the proposer instead of the committer.
1049    fn evaluate_policy_with_proposer<'a, I, P>(
1050        &self,
1051        mut changes: I,
1052        policy: &P,
1053        default_actor: &CommitParticipant,
1054    ) -> bool
1055    where
1056        I: Iterator<Item = &'a Inbox>,
1057        P: MembershipPolicy + std::fmt::Debug,
1058    {
1059        changes.all(|change| {
1060            // Use the proposer if available, otherwise fall back to the default actor (committer)
1061            let actor = change.proposer.as_ref().unwrap_or(default_actor);
1062            let is_ok = policy.evaluate(actor, change);
1063            if !is_ok {
1064                tracing::info!(
1065                    "Policy {:?} failed for actor {:?} (proposer: {:?}) and change {:?}",
1066                    policy,
1067                    actor,
1068                    change.proposer.is_some(),
1069                    change
1070                );
1071            }
1072            is_ok
1073        })
1074    }
1075
1076    /// Evaluates metadata policies for a given set of changes.
1077    fn evaluate_metadata_policy<'a, I>(
1078        &self,
1079        mut changes: I,
1080        policies: &HashMap<String, MetadataPolicies>,
1081        actor: &CommitParticipant,
1082    ) -> bool
1083    where
1084        I: Iterator<Item = &'a MetadataFieldChange>,
1085    {
1086        changes.all(|change| {
1087            if let Some(policy) = policies.get(&change.field_name) {
1088                if !policy.evaluate(actor, change) {
1089                    tracing::info!(
1090                        "Policy for field {} failed for actor {:?} and change {:?}",
1091                        change.field_name,
1092                        actor,
1093                        change
1094                    );
1095                    return false;
1096                }
1097                return true;
1098            }
1099            // Policy is not found for metadata change, let's check if the new field contains the super_admin prefix
1100            // and evaluate accordingly
1101            let policy_for_unrecognized_field =
1102                if change.field_name.starts_with(SUPER_ADMIN_METADATA_PREFIX) {
1103                    MetadataPolicies::allow_if_actor_super_admin()
1104                } else {
1105                    // Otherwise we default to admin only for fields with missing policies
1106                    MetadataPolicies::allow_if_actor_admin()
1107                };
1108            if !policy_for_unrecognized_field.evaluate(actor, change) {
1109                tracing::info!(
1110                    "Metadata field update with unknown policy was denied: {}",
1111                    change.field_name
1112                );
1113                return false;
1114            }
1115            true
1116        })
1117    }
1118
1119    /// Converts the PolicySet to its proto representation.
1120    pub(crate) fn to_proto(&self) -> Result<PolicySetProto, PolicyError> {
1121        let add_member_policy = Some(self.add_member_policy.to_proto()?);
1122        let remove_member_policy = Some(self.remove_member_policy.to_proto()?);
1123
1124        let mut update_metadata_policy = HashMap::new();
1125        for (key, policy) in &self.update_metadata_policy {
1126            let policy_proto = policy.to_proto()?;
1127            update_metadata_policy.insert(key.clone(), policy_proto);
1128        }
1129        let add_admin_policy = Some(self.add_admin_policy.to_proto()?);
1130        let remove_admin_policy = Some(self.remove_admin_policy.to_proto()?);
1131        let update_permissions_policy = Some(self.update_permissions_policy.to_proto()?);
1132        Ok(PolicySetProto {
1133            add_member_policy,
1134            remove_member_policy,
1135            update_metadata_policy,
1136            add_admin_policy,
1137            remove_admin_policy,
1138            update_permissions_policy,
1139        })
1140    }
1141
1142    /// Creates a PolicySet from its proto representation.
1143    pub(crate) fn from_proto(proto: PolicySetProto) -> Result<Self, PolicyError> {
1144        let add_member_policy = MembershipPolicies::try_from(
1145            proto
1146                .add_member_policy
1147                .ok_or(PolicyError::FromProtoAddMemberInvalidPolicy)?,
1148        )?;
1149        let remove_member_policy = MembershipPolicies::try_from(
1150            proto
1151                .remove_member_policy
1152                .ok_or(PolicyError::FromProtoRemoveMemberInvalidPolicy)?,
1153        )?;
1154        let add_admin_policy = PermissionsPolicies::try_from(
1155            proto
1156                .add_admin_policy
1157                .ok_or(PolicyError::FromProtoAddAdminInvalidPolicy)?,
1158        )?;
1159        let remove_admin_policy = PermissionsPolicies::try_from(
1160            proto
1161                .remove_admin_policy
1162                .ok_or(PolicyError::FromProtoRemoveAdminInvalidPolicy)?,
1163        )?;
1164        let update_permissions_policy = PermissionsPolicies::try_from(
1165            proto
1166                .update_permissions_policy
1167                .ok_or(PolicyError::FromProtoUpdatePermissionsInvalidPolicy)?,
1168        )?;
1169
1170        let mut update_metadata_policy = HashMap::new();
1171        for (key, policy_proto) in proto.update_metadata_policy {
1172            let policy = MetadataPolicies::try_from(policy_proto)?;
1173            update_metadata_policy.insert(key, policy);
1174        }
1175        Ok(Self::new(
1176            add_member_policy,
1177            remove_member_policy,
1178            update_metadata_policy,
1179            add_admin_policy,
1180            remove_admin_policy,
1181            update_permissions_policy,
1182        ))
1183    }
1184
1185    /// Converts the PolicySet to a `Vec<u8>`.
1186    pub fn to_bytes(&self) -> Result<Vec<u8>, PolicyError> {
1187        let proto = self.to_proto()?;
1188        let mut buf = Vec::new();
1189        proto.encode(&mut buf)?;
1190        Ok(buf)
1191    }
1192
1193    /// Creates a PolicySet from a `Vec<u8>`.
1194    pub fn from_bytes(bytes: &[u8]) -> Result<Self, PolicyError> {
1195        let proto = PolicySetProto::decode(bytes)?;
1196        Self::from_proto(proto)
1197    }
1198}
1199
1200/// Checks if a PolicySet is equivalent to the "All Members" preconfigured policy.
1201///
1202/// Depending on if the client is on a newer or older version of libxmtp
1203/// since the group was created, the number of metadata policies might not match
1204/// the default All Members Policy Set. As long as all metadata policies are allow, we will
1205/// match against All Members Preconfigured Policy
1206pub fn is_policy_default(policy: &PolicySet) -> Result<bool, PolicyError> {
1207    let mut metadata_policies_equal = true;
1208    for field_name in policy.update_metadata_policy.keys() {
1209        let metadata_policy = policy.update_metadata_policy.get(field_name).ok_or(
1210            PolicyError::MissingMetadataPolicyField {
1211                name: field_name.to_string(),
1212            },
1213        )?;
1214        if field_name == MetadataField::MessageDisappearInNS.as_str()
1215            || field_name == MetadataField::MessageDisappearFromNS.as_str()
1216        {
1217            metadata_policies_equal = metadata_policies_equal
1218                && metadata_policy.eq(&MetadataPolicies::allow_if_actor_admin());
1219        } else if field_name == MetadataField::MinimumSupportedProtocolVersion.as_str() {
1220            metadata_policies_equal = metadata_policies_equal
1221                && metadata_policy.eq(&MetadataPolicies::allow_if_actor_super_admin());
1222        } else {
1223            metadata_policies_equal =
1224                metadata_policies_equal && metadata_policy.eq(&MetadataPolicies::allow());
1225        }
1226    }
1227    Ok(metadata_policies_equal
1228        && policy.add_member_policy == MembershipPolicies::allow()
1229        && policy.remove_member_policy == MembershipPolicies::allow_if_actor_admin()
1230        && policy.add_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1231        && policy.remove_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1232        && policy.update_permissions_policy == PermissionsPolicies::allow_if_actor_super_admin())
1233}
1234
1235/// Checks if a PolicySet is equivalent to the "Admin Only" preconfigured policy.
1236///
1237/// Depending on if the client is on a newer or older version of libxmtp
1238/// since the group was created, the number of metadata policies might not match
1239/// the default Admin Only Policy Set. As long as all metadata policies are admin only, we will
1240/// match against Admin Only Preconfigured Policy
1241pub fn is_policy_admin_only(policy: &PolicySet) -> Result<bool, PolicyError> {
1242    let mut metadata_policies_equal = true;
1243    for field_name in policy.update_metadata_policy.keys() {
1244        let metadata_policy = policy.update_metadata_policy.get(field_name).ok_or(
1245            PolicyError::MissingMetadataPolicyField {
1246                name: field_name.to_string(),
1247            },
1248        )?;
1249        if field_name == MetadataField::MinimumSupportedProtocolVersion.as_str() {
1250            metadata_policies_equal = metadata_policies_equal
1251                && metadata_policy.eq(&MetadataPolicies::allow_if_actor_super_admin());
1252        } else {
1253            metadata_policies_equal = metadata_policies_equal
1254                && metadata_policy.eq(&MetadataPolicies::allow_if_actor_admin());
1255        }
1256    }
1257    Ok(metadata_policies_equal
1258        && policy.add_member_policy == MembershipPolicies::allow_if_actor_admin()
1259        && policy.remove_member_policy == MembershipPolicies::allow_if_actor_admin()
1260        && policy.add_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1261        && policy.remove_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1262        && policy.update_permissions_policy == PermissionsPolicies::allow_if_actor_super_admin())
1263}
1264
1265/// Returns the "All Members" preconfigured policy.
1266///
1267/// A policy where any member can add or remove any other member
1268pub(crate) fn default_policy() -> PolicySet {
1269    let mut metadata_policies_map: HashMap<String, MetadataPolicies> = HashMap::new();
1270    for field in GroupMutableMetadata::supported_fields() {
1271        match field {
1272            MetadataField::MessageDisappearInNS => {
1273                metadata_policies_map
1274                    .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1275            }
1276            MetadataField::MessageDisappearFromNS => {
1277                metadata_policies_map
1278                    .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1279            }
1280            MetadataField::MinimumSupportedProtocolVersion => {
1281                metadata_policies_map.insert(
1282                    field.to_string(),
1283                    MetadataPolicies::allow_if_actor_super_admin(),
1284                );
1285            }
1286            _ => {
1287                metadata_policies_map.insert(field.to_string(), MetadataPolicies::allow());
1288            }
1289        }
1290    }
1291
1292    PolicySet::new(
1293        MembershipPolicies::allow(),
1294        MembershipPolicies::allow_if_actor_admin(),
1295        metadata_policies_map,
1296        PermissionsPolicies::allow_if_actor_super_admin(),
1297        PermissionsPolicies::allow_if_actor_super_admin(),
1298        PermissionsPolicies::allow_if_actor_super_admin(),
1299    )
1300}
1301
1302/// Returns the "Admin Only" preconfigured policy.
1303///
1304/// A policy where only the admins can add or remove members
1305pub(crate) fn policy_admin_only() -> PolicySet {
1306    let mut metadata_policies_map: HashMap<String, MetadataPolicies> = HashMap::new();
1307    for field in GroupMutableMetadata::supported_fields() {
1308        match field {
1309            MetadataField::MessageDisappearInNS => {
1310                metadata_policies_map
1311                    .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1312            }
1313            MetadataField::MessageDisappearFromNS => {
1314                metadata_policies_map
1315                    .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1316            }
1317            MetadataField::MinimumSupportedProtocolVersion => {
1318                metadata_policies_map.insert(
1319                    field.to_string(),
1320                    MetadataPolicies::allow_if_actor_super_admin(),
1321                );
1322            }
1323            _ => {
1324                metadata_policies_map
1325                    .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1326            }
1327        }
1328    }
1329
1330    PolicySet::new(
1331        MembershipPolicies::allow_if_actor_admin(),
1332        MembershipPolicies::allow_if_actor_admin(),
1333        metadata_policies_map,
1334        PermissionsPolicies::allow_if_actor_super_admin(),
1335        PermissionsPolicies::allow_if_actor_super_admin(),
1336        PermissionsPolicies::allow_if_actor_super_admin(),
1337    )
1338}
1339
1340/// Implements the Default trait for PolicySet.
1341impl Default for PolicySet {
1342    fn default() -> Self {
1343        PreconfiguredPolicies::default().to_policy_set()
1344    }
1345}
1346
1347/// Represents preconfigured policies for a group.
1348#[derive(Debug, Clone, PartialEq, Default)]
1349pub enum PreconfiguredPolicies {
1350    /// The "All Members" preconfigured policy.
1351    #[default]
1352    Default,
1353    /// The "Admin Only" preconfigured policy.
1354    AdminsOnly,
1355}
1356
1357impl PreconfiguredPolicies {
1358    /// Converts the PreconfiguredPolicies to a PolicySet.
1359    pub fn to_policy_set(&self) -> PolicySet {
1360        match self {
1361            PreconfiguredPolicies::Default => default_policy(),
1362            PreconfiguredPolicies::AdminsOnly => policy_admin_only(),
1363        }
1364    }
1365
1366    /// Creates a PreconfiguredPolicies from a PolicySet.
1367    pub fn from_policy_set(policy_set: &PolicySet) -> Result<Self, PolicyError> {
1368        if is_policy_default(policy_set)? {
1369            Ok(PreconfiguredPolicies::Default)
1370        } else if is_policy_admin_only(policy_set)? {
1371            Ok(PreconfiguredPolicies::AdminsOnly)
1372        } else {
1373            Err(PolicyError::InvalidPresetPolicy)
1374        }
1375    }
1376}
1377
1378/// Implements the Display trait for PreconfiguredPolicies.
1379impl std::fmt::Display for PreconfiguredPolicies {
1380    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1381        write!(f, "{:?}", self)
1382    }
1383}
1384
1385#[cfg(test)]
1386pub(crate) mod tests {
1387    use std::collections::HashSet;
1388
1389    use crate::groups::validated_commit::MutableMetadataValidationInfo;
1390    use xmtp_common::{rand_string, rand_vec};
1391    use xmtp_mls_common::group_metadata::DmMembers;
1392
1393    use super::*;
1394
1395    fn build_change(inbox_id: Option<String>, is_admin: bool, is_super_admin: bool) -> Inbox {
1396        Inbox {
1397            inbox_id: inbox_id.unwrap_or(rand_string::<24>()),
1398            is_creator: is_super_admin,
1399            is_super_admin,
1400            is_admin,
1401            proposer: None,
1402        }
1403    }
1404
1405    /// Test helper function for building a CommitParticipant.
1406    fn build_actor(
1407        inbox_id: Option<String>,
1408        installation_id: Option<Vec<u8>>,
1409        is_admin: bool,
1410        is_super_admin: bool,
1411    ) -> CommitParticipant {
1412        CommitParticipant {
1413            inbox_id: inbox_id.unwrap_or(rand_string::<24>()),
1414            installation_id: installation_id.unwrap_or_else(rand_vec::<24>),
1415            is_creator: is_super_admin,
1416            is_admin,
1417            is_super_admin,
1418        }
1419    }
1420
1421    enum MemberType {
1422        SameAsActor,
1423        DmTarget,
1424        Random,
1425    }
1426
1427    /// Test helper function for building a ValidatedCommit.
1428    fn build_validated_commit(
1429        // Add a member with the same account address as the actor if true, random account address if false
1430        member_added: Option<MemberType>,
1431        member_removed: Option<MemberType>,
1432        metadata_fields_changed: Option<Vec<String>>,
1433        permissions_changed: bool,
1434        actor_is_admin: bool,
1435        actor_is_super_admin: bool,
1436        dm_target_inbox_id: Option<String>,
1437    ) -> ValidatedCommit {
1438        let actor = build_actor(None, None, actor_is_admin, actor_is_super_admin);
1439        let dm_target_inbox_id_clone = dm_target_inbox_id.clone();
1440        let build_membership_change = |member_type: MemberType| match member_type {
1441            MemberType::SameAsActor => vec![build_change(
1442                Some(actor.inbox_id.clone()),
1443                actor_is_admin,
1444                actor_is_super_admin,
1445            )],
1446            MemberType::DmTarget => {
1447                vec![build_change(dm_target_inbox_id_clone.clone(), false, false)]
1448            }
1449            MemberType::Random => vec![build_change(None, false, false)],
1450        };
1451
1452        let field_changes = metadata_fields_changed
1453            .unwrap_or_default()
1454            .into_iter()
1455            .map(|field| {
1456                MetadataFieldChange::new(
1457                    field,
1458                    Some(rand_string::<24>()),
1459                    Some(rand_string::<24>()),
1460                )
1461            })
1462            .collect();
1463
1464        let dm_members = if let Some(dm_target_inbox_id) = dm_target_inbox_id {
1465            Some(DmMembers {
1466                member_one_inbox_id: actor.inbox_id.clone(),
1467                member_two_inbox_id: dm_target_inbox_id,
1468            })
1469        } else {
1470            None
1471        };
1472
1473        ValidatedCommit {
1474            actor: actor.clone(),
1475            proposers: vec![actor.clone()], // In test, actor is also the proposer
1476            added_inboxes: member_added
1477                .map(build_membership_change)
1478                .unwrap_or_default(),
1479            removed_inboxes: member_removed
1480                .map(build_membership_change)
1481                .unwrap_or_default(),
1482            readded_installations: HashSet::new(),
1483            metadata_validation_info: MutableMetadataValidationInfo {
1484                metadata_field_changes: field_changes,
1485                ..Default::default()
1486            },
1487            installations_changed: false,
1488            permissions_changed,
1489            dm_members,
1490        }
1491    }
1492
1493    /// Tests that a commit by a non admin/super admin can add and remove members
1494    /// with allow policies.
1495    #[xmtp_common::test]
1496    fn test_allow_all() {
1497        let permissions = PolicySet::new(
1498            MembershipPolicies::allow(),
1499            MembershipPolicies::allow(),
1500            MetadataPolicies::default_map(MetadataPolicies::allow()),
1501            PermissionsPolicies::allow_if_actor_super_admin(),
1502            PermissionsPolicies::allow_if_actor_super_admin(),
1503            PermissionsPolicies::allow_if_actor_super_admin(),
1504        );
1505
1506        let commit = build_validated_commit(
1507            Some(MemberType::SameAsActor),
1508            Some(MemberType::SameAsActor),
1509            None,
1510            false,
1511            false,
1512            false,
1513            None,
1514        );
1515        assert!(permissions.evaluate_commit(&commit));
1516    }
1517
1518    /// Tests that a commit by a non admin/super admin is denied for add and remove member policies.
1519    #[xmtp_common::test]
1520    fn test_deny() {
1521        let permissions = PolicySet::new(
1522            MembershipPolicies::deny(),
1523            MembershipPolicies::deny(),
1524            MetadataPolicies::default_map(MetadataPolicies::deny()),
1525            PermissionsPolicies::allow_if_actor_super_admin(),
1526            PermissionsPolicies::allow_if_actor_super_admin(),
1527            PermissionsPolicies::allow_if_actor_super_admin(),
1528        );
1529
1530        let member_added_commit = build_validated_commit(
1531            Some(MemberType::Random),
1532            None,
1533            None,
1534            false,
1535            false,
1536            false,
1537            None,
1538        );
1539        assert!(!permissions.evaluate_commit(&member_added_commit));
1540
1541        let member_removed_commit = build_validated_commit(
1542            None,
1543            Some(MemberType::Random),
1544            None,
1545            false,
1546            false,
1547            false,
1548            None,
1549        );
1550        assert!(!permissions.evaluate_commit(&member_removed_commit));
1551    }
1552
1553    /// Tests that a group creator can perform super admin actions.
1554    #[xmtp_common::test]
1555    fn test_actor_is_creator() {
1556        let permissions = PolicySet::new(
1557            MembershipPolicies::allow_if_actor_super_admin(),
1558            MembershipPolicies::allow_if_actor_super_admin(),
1559            MetadataPolicies::default_map(MetadataPolicies::deny()),
1560            PermissionsPolicies::allow_if_actor_super_admin(),
1561            PermissionsPolicies::allow_if_actor_super_admin(),
1562            PermissionsPolicies::allow_if_actor_super_admin(),
1563        );
1564
1565        // Can not remove the creator if they are the only super admin
1566        let commit_with_creator = build_validated_commit(
1567            Some(MemberType::SameAsActor),
1568            Some(MemberType::SameAsActor),
1569            None,
1570            false,
1571            false,
1572            true,
1573            None,
1574        );
1575        assert!(!permissions.evaluate_commit(&commit_with_creator));
1576
1577        let commit_with_creator = build_validated_commit(
1578            Some(MemberType::SameAsActor),
1579            Some(MemberType::Random),
1580            None,
1581            false,
1582            false,
1583            true,
1584            None,
1585        );
1586        assert!(permissions.evaluate_commit(&commit_with_creator));
1587
1588        let commit_without_creator = build_validated_commit(
1589            Some(MemberType::SameAsActor),
1590            Some(MemberType::SameAsActor),
1591            None,
1592            false,
1593            false,
1594            false,
1595            None,
1596        );
1597        assert!(!permissions.evaluate_commit(&commit_without_creator));
1598    }
1599
1600    /// Tests that and conditions are enforced as expected.
1601    #[xmtp_common::test]
1602    fn test_and_condition() {
1603        let permissions = PolicySet::new(
1604            MembershipPolicies::and(vec![
1605                MembershipPolicies::Standard(BasePolicies::Deny),
1606                MembershipPolicies::Standard(BasePolicies::Allow),
1607            ]),
1608            MembershipPolicies::allow(),
1609            MetadataPolicies::default_map(MetadataPolicies::deny()),
1610            PermissionsPolicies::allow_if_actor_super_admin(),
1611            PermissionsPolicies::allow_if_actor_super_admin(),
1612            PermissionsPolicies::allow_if_actor_super_admin(),
1613        );
1614
1615        let member_added_commit = build_validated_commit(
1616            Some(MemberType::SameAsActor),
1617            None,
1618            None,
1619            false,
1620            false,
1621            false,
1622            None,
1623        );
1624        assert!(!permissions.evaluate_commit(&member_added_commit));
1625    }
1626
1627    /// Tests that any conditions are enforced as expected.
1628    #[xmtp_common::test]
1629    fn test_any_condition() {
1630        let permissions = PolicySet::new(
1631            MembershipPolicies::any(vec![
1632                MembershipPolicies::deny(),
1633                MembershipPolicies::allow(),
1634            ]),
1635            MembershipPolicies::allow(),
1636            MetadataPolicies::default_map(MetadataPolicies::deny()),
1637            PermissionsPolicies::allow_if_actor_super_admin(),
1638            PermissionsPolicies::allow_if_actor_super_admin(),
1639            PermissionsPolicies::allow_if_actor_super_admin(),
1640        );
1641
1642        let member_added_commit = build_validated_commit(
1643            Some(MemberType::SameAsActor),
1644            None,
1645            None,
1646            false,
1647            false,
1648            false,
1649            None,
1650        );
1651        assert!(permissions.evaluate_commit(&member_added_commit));
1652    }
1653
1654    /// Tests that the PolicySet can be serialized and deserialized.
1655    #[xmtp_common::test]
1656    fn test_serialize() {
1657        let permissions = PolicySet::new(
1658            MembershipPolicies::any(vec![
1659                MembershipPolicies::allow(),
1660                MembershipPolicies::deny(),
1661            ]),
1662            MembershipPolicies::and(vec![
1663                MembershipPolicies::allow_if_actor_super_admin(),
1664                MembershipPolicies::deny(),
1665            ]),
1666            MetadataPolicies::default_map(MetadataPolicies::deny()),
1667            PermissionsPolicies::allow_if_actor_super_admin(),
1668            PermissionsPolicies::allow_if_actor_super_admin(),
1669            PermissionsPolicies::allow_if_actor_super_admin(),
1670        );
1671
1672        let proto = permissions.to_proto().unwrap();
1673        assert!(proto.add_member_policy.is_some());
1674        assert!(proto.remove_member_policy.is_some());
1675
1676        let as_bytes = permissions.to_bytes().expect("serialization failed");
1677        let restored = PolicySet::from_bytes(as_bytes.as_slice()).expect("proto conversion failed");
1678        // All fields implement PartialEq so this should test equality all the way down
1679        assert!(permissions.eq(&restored))
1680    }
1681
1682    /// Tests that the PolicySet can enforce update group name policy.
1683    #[xmtp_common::test]
1684    /// Tests that the PolicySet can enforce update group name policy.
1685    fn test_update_group_name() {
1686        let allow_permissions = PolicySet::new(
1687            MembershipPolicies::allow(),
1688            MembershipPolicies::allow(),
1689            MetadataPolicies::default_map(MetadataPolicies::allow()),
1690            PermissionsPolicies::allow_if_actor_super_admin(),
1691            PermissionsPolicies::allow_if_actor_super_admin(),
1692            PermissionsPolicies::allow_if_actor_super_admin(),
1693        );
1694
1695        let member_added_commit = build_validated_commit(
1696            Some(MemberType::SameAsActor),
1697            None,
1698            Some(vec![MetadataField::GroupName.to_string()]),
1699            false,
1700            false,
1701            false,
1702            None,
1703        );
1704
1705        assert!(allow_permissions.evaluate_commit(&member_added_commit));
1706
1707        let deny_permissions = PolicySet::new(
1708            MembershipPolicies::allow(),
1709            MembershipPolicies::allow(),
1710            MetadataPolicies::default_map(MetadataPolicies::deny()),
1711            PermissionsPolicies::allow_if_actor_super_admin(),
1712            PermissionsPolicies::allow_if_actor_super_admin(),
1713            PermissionsPolicies::allow_if_actor_super_admin(),
1714        );
1715
1716        assert!(!deny_permissions.evaluate_commit(&member_added_commit));
1717    }
1718
1719    /// Tests that the preconfigured policy functions work as expected
1720    #[xmtp_common::test]
1721    fn test_preconfigured_policy() {
1722        let group_permissions = GroupMutablePermissions::new(default_policy());
1723
1724        assert_eq!(
1725            group_permissions.preconfigured_policy().unwrap(),
1726            PreconfiguredPolicies::Default
1727        );
1728
1729        let group_group_permissions_creator_admin =
1730            GroupMutablePermissions::new(policy_admin_only());
1731
1732        assert_eq!(
1733            group_group_permissions_creator_admin
1734                .preconfigured_policy()
1735                .unwrap(),
1736            PreconfiguredPolicies::AdminsOnly
1737        );
1738    }
1739
1740    /// Tests that the preconfigured policy functions work as expected with new metadata fields.
1741    #[xmtp_common::test]
1742    fn test_preconfigured_policy_equality_new_metadata() {
1743        let mut metadata_policies_map = MetadataPolicies::default_map(MetadataPolicies::allow());
1744        metadata_policies_map.insert("new_metadata_field".to_string(), MetadataPolicies::allow());
1745        let policy_set_new_metadata_permission = PolicySet {
1746            add_member_policy: MembershipPolicies::allow(),
1747            remove_member_policy: MembershipPolicies::allow_if_actor_admin(),
1748            update_metadata_policy: metadata_policies_map,
1749            add_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1750            remove_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1751            update_permissions_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1752        };
1753
1754        assert!(is_policy_default(&policy_set_new_metadata_permission).unwrap());
1755
1756        let mut metadata_policies_map =
1757            MetadataPolicies::default_map(MetadataPolicies::allow_if_actor_admin());
1758        metadata_policies_map.insert(
1759            "new_metadata_field_2".to_string(),
1760            MetadataPolicies::allow_if_actor_admin(),
1761        );
1762        let policy_set_new_metadata_permission = PolicySet {
1763            add_member_policy: MembershipPolicies::allow_if_actor_admin(),
1764            remove_member_policy: MembershipPolicies::allow_if_actor_admin(),
1765            update_metadata_policy: metadata_policies_map,
1766            add_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1767            remove_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1768            update_permissions_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1769        };
1770
1771        assert!(is_policy_admin_only(&policy_set_new_metadata_permission).unwrap());
1772    }
1773
1774    /// Tests that the permission update policy is enforced as expected.
1775    #[xmtp_common::test]
1776    fn test_permission_update() {
1777        let permissions = PolicySet::new(
1778            MembershipPolicies::allow(),
1779            MembershipPolicies::allow_if_actor_admin(),
1780            MetadataPolicies::default_map(MetadataPolicies::allow()),
1781            PermissionsPolicies::allow_if_actor_super_admin(),
1782            PermissionsPolicies::allow_if_actor_super_admin(),
1783            PermissionsPolicies::allow_if_actor_super_admin(),
1784        );
1785
1786        // Commit should fail because actor is not superadmin
1787        let commit = build_validated_commit(None, None, None, true, false, false, None);
1788        assert!(!permissions.evaluate_commit(&commit));
1789
1790        // Commit should pass because actor is superadmin
1791        let commit = build_validated_commit(None, None, None, true, false, true, None);
1792        assert!(permissions.evaluate_commit(&commit));
1793    }
1794
1795    /// Tests that the PolicySet can evaluate field updates with unknown policies.
1796    #[xmtp_common::test]
1797    fn test_evaluate_field_with_unknown_policy() {
1798        // Create a group whose default metadata can be updated by any member
1799        let permissions = PolicySet::new(
1800            MembershipPolicies::allow(),
1801            MembershipPolicies::allow(),
1802            MetadataPolicies::default_map(MetadataPolicies::allow()),
1803            PermissionsPolicies::allow_if_actor_super_admin(),
1804            PermissionsPolicies::allow_if_actor_super_admin(),
1805            PermissionsPolicies::allow_if_actor_super_admin(),
1806        );
1807
1808        // Non admin, non super admin can update group name
1809        let name_updated_commit = build_validated_commit(
1810            None,
1811            None,
1812            Some(vec![MetadataField::GroupName.to_string()]),
1813            false,
1814            false,
1815            false,
1816            None,
1817        );
1818        assert!(permissions.evaluate_commit(&name_updated_commit));
1819
1820        // Non admin, non super admin can NOT update non existing field
1821        let non_existing_field_updated_commit = build_validated_commit(
1822            None,
1823            None,
1824            Some(vec!["non_existing_field".to_string()]),
1825            false,
1826            false,
1827            false,
1828            None,
1829        );
1830        assert!(!permissions.evaluate_commit(&non_existing_field_updated_commit));
1831
1832        // Admin can update non existing field
1833        let non_existing_field_updated_commit = build_validated_commit(
1834            None,
1835            None,
1836            Some(vec!["non_existing_field".to_string()]),
1837            false,
1838            true,
1839            false,
1840            None,
1841        );
1842        assert!(permissions.evaluate_commit(&non_existing_field_updated_commit));
1843
1844        // Admin can NOT update non existing field that starts with super_admin only prefix
1845        let non_existing_field_updated_commit = build_validated_commit(
1846            None,
1847            None,
1848            Some(vec![
1849                SUPER_ADMIN_METADATA_PREFIX.to_string() + "non_existing_field",
1850            ]),
1851            false,
1852            true,
1853            false,
1854            None,
1855        );
1856        assert!(!permissions.evaluate_commit(&non_existing_field_updated_commit));
1857
1858        // Super Admin CAN update non existing field that starts with super_admin only prefix
1859        let non_existing_field_updated_commit = build_validated_commit(
1860            None,
1861            None,
1862            Some(vec![
1863                SUPER_ADMIN_METADATA_PREFIX.to_string() + "non_existing_field",
1864            ]),
1865            false,
1866            false,
1867            true,
1868            None,
1869        );
1870        assert!(permissions.evaluate_commit(&non_existing_field_updated_commit));
1871    }
1872
1873    #[xmtp_common::test]
1874    fn test_dm_group_permissions() {
1875        // Simulate a group with DM Permissions
1876        let permissions = PolicySet::new_dm();
1877
1878        // String below represents the inbox id of the DM target
1879        const TARGET_INBOX_ID: &str = "example_target_dm_id";
1880
1881        // DM group can not add a random inbox
1882        let commit = build_validated_commit(
1883            Some(MemberType::Random),
1884            None,
1885            None,
1886            false,
1887            false,
1888            false,
1889            Some(TARGET_INBOX_ID.to_string()),
1890        );
1891        assert!(!permissions.evaluate_commit(&commit));
1892
1893        // DM group can not add themselves
1894        let commit = build_validated_commit(
1895            Some(MemberType::SameAsActor),
1896            None,
1897            None,
1898            false,
1899            false,
1900            false,
1901            Some(TARGET_INBOX_ID.to_string()),
1902        );
1903        assert!(!permissions.evaluate_commit(&commit));
1904
1905        // DM group can add the target inbox
1906        let commit = build_validated_commit(
1907            Some(MemberType::DmTarget),
1908            None,
1909            None,
1910            false,
1911            false,
1912            false,
1913            Some(TARGET_INBOX_ID.to_string()),
1914        );
1915        assert!(permissions.evaluate_commit(&commit));
1916
1917        // DM group can not remove
1918        let commit = build_validated_commit(
1919            None,
1920            Some(MemberType::Random),
1921            None,
1922            false,
1923            false,
1924            false,
1925            Some(TARGET_INBOX_ID.to_string()),
1926        );
1927        assert!(!permissions.evaluate_commit(&commit));
1928        let commit = build_validated_commit(
1929            None,
1930            Some(MemberType::DmTarget),
1931            None,
1932            false,
1933            false,
1934            false,
1935            Some(TARGET_INBOX_ID.to_string()),
1936        );
1937        assert!(!permissions.evaluate_commit(&commit));
1938        let commit = build_validated_commit(
1939            None,
1940            Some(MemberType::SameAsActor),
1941            None,
1942            false,
1943            false,
1944            false,
1945            Some(TARGET_INBOX_ID.to_string()),
1946        );
1947        assert!(!permissions.evaluate_commit(&commit));
1948
1949        // DM group can update metadata
1950        let commit = build_validated_commit(
1951            None,
1952            None,
1953            Some(vec![MetadataField::GroupName.to_string()]),
1954            false,
1955            false,
1956            false,
1957            Some(TARGET_INBOX_ID.to_string()),
1958        );
1959        assert!(permissions.evaluate_commit(&commit));
1960    }
1961}