1use openmls::{extensions::Extensions, group::GroupContext};
2use prost::Message;
3use std::collections::HashMap;
4use thiserror::Error;
5use xmtp_common::ErrorCode;
6use xmtp_proto::xmtp::mls::message_contents::{
7 GroupMutablePermissionsV1 as GroupMutablePermissionsProto,
8 MembershipPolicy as MembershipPolicyProto, MetadataPolicy as MetadataPolicyProto,
9 PermissionsUpdatePolicy as PermissionsPolicyProto, PolicySet as PolicySetProto,
10 membership_policy::{
11 AndCondition as AndConditionProto, AnyCondition as AnyConditionProto,
12 BasePolicy as BasePolicyProto, Kind as PolicyKindProto,
13 },
14 metadata_policy::{
15 AndCondition as MetadataAndConditionProto, AnyCondition as MetadataAnyConditionProto,
16 Kind as MetadataPolicyKindProto, MetadataBasePolicy as MetadataBasePolicyProto,
17 },
18 permissions_update_policy::{
19 AndCondition as PermissionsAndConditionProto, AnyCondition as PermissionsAnyConditionProto,
20 Kind as PermissionsPolicyKindProto, PermissionsBasePolicy as PermissionsBasePolicyProto,
21 },
22};
23
24use crate::commit::{CommitParticipant, Inbox, MembershipValidationInfo, MetadataFieldChange};
25use xmtp_mls_common::group_mutable_metadata::{GroupMutableMetadata, MetadataField};
26
27#[derive(Debug, Error, ErrorCode)]
29pub enum GroupMutablePermissionsError {
30 #[error("serialization: {0}")]
34 Serialization(#[from] prost::EncodeError),
35 #[error("deserialization: {0}")]
39 Deserialization(#[from] prost::DecodeError),
40 #[error("policy error {0}")]
44 Policy(#[from] PolicyError),
45 #[error("invalid conversation type")]
49 InvalidConversationType,
50 #[error("missing policies")]
54 MissingPolicies,
55 #[error("missing extension")]
59 MissingExtension,
60 #[error("invalid permission policy option")]
64 InvalidPermissionPolicyOption,
65 #[error("invalid component-registry policy state: {0}")]
69 PolicyProjection(#[from] xmtp_mls_common::app_data::policy_set::PolicyProjectionError),
70}
71
72#[derive(Debug, Clone, PartialEq)]
76pub struct GroupMutablePermissions {
77 pub policies: PolicySet,
79}
80
81impl GroupMutablePermissions {
82 pub fn new(policies: PolicySet) -> Self {
84 Self { policies }
85 }
86
87 pub fn preconfigured_policy(
89 &self,
90 ) -> Result<PreconfiguredPolicies, GroupMutablePermissionsError> {
91 Ok(PreconfiguredPolicies::from_policy_set(&self.policies)?)
92 }
93
94 pub fn from_proto(
96 proto: GroupMutablePermissionsProto,
97 ) -> Result<Self, GroupMutablePermissionsError> {
98 if proto.policies.is_none() {
99 return Err(GroupMutablePermissionsError::MissingPolicies);
100 }
101 let policies = proto.policies.expect("checked for none");
102
103 Ok(Self::new(PolicySet::from_proto(policies)?))
104 }
105
106 pub fn to_proto(&self) -> Result<GroupMutablePermissionsProto, GroupMutablePermissionsError> {
108 Ok(GroupMutablePermissionsProto {
109 policies: Some(self.policies.to_proto()?),
110 })
111 }
112}
113
114impl TryFrom<GroupMutablePermissions> for Vec<u8> {
116 type Error = GroupMutablePermissionsError;
117
118 fn try_from(value: GroupMutablePermissions) -> Result<Self, Self::Error> {
119 let mut buf = Vec::new();
120 let proto_val = value.to_proto()?;
121 proto_val.encode(&mut buf)?;
122
123 Ok(buf)
124 }
125}
126
127impl TryFrom<&Vec<u8>> for GroupMutablePermissions {
129 type Error = GroupMutablePermissionsError;
130
131 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
132 let proto_val = GroupMutablePermissionsProto::decode(value.as_slice())?;
133 Self::from_proto(proto_val)
134 }
135}
136
137impl TryFrom<GroupMutablePermissionsProto> for GroupMutablePermissions {
139 type Error = GroupMutablePermissionsError;
140
141 fn try_from(value: GroupMutablePermissionsProto) -> Result<Self, Self::Error> {
142 Self::from_proto(value)
143 }
144}
145
146pub trait MetadataPolicy: std::fmt::Debug {
148 fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool;
153
154 fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError>;
156}
157
158#[derive(Clone, Copy, Debug, PartialEq)]
160pub enum MetadataBasePolicies {
161 Allow,
162 Deny,
163 AllowIfActorAdminOrSuperAdmin,
164 AllowIfActorSuperAdmin,
165}
166
167impl MetadataPolicy for &MetadataBasePolicies {
169 fn evaluate(&self, actor: &CommitParticipant, _change: &MetadataFieldChange) -> bool {
170 match self {
171 MetadataBasePolicies::Allow => true,
172 MetadataBasePolicies::Deny => false,
173 MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin => {
174 actor.is_admin || actor.is_super_admin
175 }
176 MetadataBasePolicies::AllowIfActorSuperAdmin => actor.is_super_admin,
177 }
178 }
179
180 fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
181 let inner = match self {
182 MetadataBasePolicies::Allow => MetadataBasePolicyProto::Allow as i32,
183 MetadataBasePolicies::Deny => MetadataBasePolicyProto::Deny as i32,
184 MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin => {
185 MetadataBasePolicyProto::AllowIfAdmin as i32
186 }
187 MetadataBasePolicies::AllowIfActorSuperAdmin => {
188 MetadataBasePolicyProto::AllowIfSuperAdmin as i32
189 }
190 };
191
192 Ok(MetadataPolicyProto {
193 kind: Some(MetadataPolicyKindProto::Base(inner)),
194 })
195 }
196}
197
198#[derive(Debug, Clone, PartialEq)]
200#[allow(dead_code)]
201pub enum MetadataPolicies {
202 Standard(MetadataBasePolicies),
203 AndCondition(MetadataAndCondition),
204 AnyCondition(MetadataAnyCondition),
205}
206
207impl MetadataPolicies {
208 pub fn default_map(policies: MetadataPolicies) -> HashMap<String, MetadataPolicies> {
210 let mut map: HashMap<String, MetadataPolicies> = HashMap::new();
211 for field in GroupMutableMetadata::supported_fields() {
212 match field {
213 MetadataField::MessageDisappearInNS => {
214 map.insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
215 }
216 MetadataField::MessageDisappearFromNS => {
217 map.insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
218 }
219 MetadataField::MinimumSupportedProtocolVersion => {
220 map.insert(
221 field.to_string(),
222 MetadataPolicies::allow_if_actor_super_admin(),
223 );
224 }
225 _ => {
226 map.insert(field.to_string(), policies.clone());
227 }
228 }
229 }
230 map
231 }
232
233 pub fn dm_map() -> HashMap<String, MetadataPolicies> {
235 let mut map: HashMap<String, MetadataPolicies> = HashMap::new();
236 for field in GroupMutableMetadata::supported_fields() {
237 map.insert(field.to_string(), MetadataPolicies::allow());
238 }
239 map
240 }
241
242 pub fn allow() -> Self {
244 MetadataPolicies::Standard(MetadataBasePolicies::Allow)
245 }
246
247 pub fn deny() -> Self {
249 MetadataPolicies::Standard(MetadataBasePolicies::Deny)
250 }
251
252 pub fn allow_if_actor_admin() -> Self {
254 MetadataPolicies::Standard(MetadataBasePolicies::AllowIfActorAdminOrSuperAdmin)
255 }
256
257 pub fn allow_if_actor_super_admin() -> Self {
259 MetadataPolicies::Standard(MetadataBasePolicies::AllowIfActorSuperAdmin)
260 }
261
262 pub fn and(policies: Vec<MetadataPolicies>) -> Self {
264 MetadataPolicies::AndCondition(MetadataAndCondition::new(policies))
265 }
266
267 pub fn any(policies: Vec<MetadataPolicies>) -> Self {
269 MetadataPolicies::AnyCondition(MetadataAnyCondition::new(policies))
270 }
271}
272
273impl TryFrom<MetadataPolicyProto> for MetadataPolicies {
275 type Error = PolicyError;
276
277 fn try_from(proto: MetadataPolicyProto) -> Result<Self, Self::Error> {
278 match proto.kind {
279 Some(MetadataPolicyKindProto::Base(inner)) => match inner {
280 1 => Ok(MetadataPolicies::allow()),
281 2 => Ok(MetadataPolicies::deny()),
282 3 => Ok(MetadataPolicies::allow_if_actor_admin()),
283 4 => Ok(MetadataPolicies::allow_if_actor_super_admin()),
284 _ => Err(PolicyError::InvalidMetadataPolicy),
285 },
286 Some(MetadataPolicyKindProto::AndCondition(inner)) => {
287 if inner.policies.is_empty() {
288 return Err(PolicyError::InvalidMetadataPolicy);
289 }
290 let policies = inner
291 .policies
292 .into_iter()
293 .map(|policy| policy.try_into())
294 .collect::<Result<Vec<MetadataPolicies>, PolicyError>>()?;
295
296 Ok(MetadataPolicies::and(policies))
297 }
298 Some(MetadataPolicyKindProto::AnyCondition(inner)) => {
299 if inner.policies.is_empty() {
300 return Err(PolicyError::InvalidMetadataPolicy);
301 }
302
303 let policies = inner
304 .policies
305 .into_iter()
306 .map(|policy| policy.try_into())
307 .collect::<Result<Vec<MetadataPolicies>, PolicyError>>()?;
308
309 Ok(MetadataPolicies::any(policies))
310 }
311 None => Err(PolicyError::InvalidMetadataPolicy),
312 }
313 }
314}
315
316impl MetadataPolicy for MetadataPolicies {
318 fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
319 match self {
320 MetadataPolicies::Standard(policy) => policy.evaluate(actor, change),
321 MetadataPolicies::AndCondition(policy) => policy.evaluate(actor, change),
322 MetadataPolicies::AnyCondition(policy) => policy.evaluate(actor, change),
323 }
324 }
325
326 fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
327 Ok(match self {
328 MetadataPolicies::Standard(policy) => policy.to_proto()?,
329 MetadataPolicies::AndCondition(policy) => policy.to_proto()?,
330 MetadataPolicies::AnyCondition(policy) => policy.to_proto()?,
331 })
332 }
333}
334
335#[derive(Clone, Debug, PartialEq)]
337pub struct MetadataAndCondition {
338 policies: Vec<MetadataPolicies>,
339}
340
341impl MetadataAndCondition {
342 pub(super) fn new(policies: Vec<MetadataPolicies>) -> Self {
343 Self { policies }
344 }
345}
346
347impl MetadataPolicy for MetadataAndCondition {
349 fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
350 self.policies
351 .iter()
352 .all(|policy| policy.evaluate(actor, change))
353 }
354
355 fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
356 Ok(MetadataPolicyProto {
357 kind: Some(MetadataPolicyKindProto::AndCondition(
358 MetadataAndConditionProto {
359 policies: self
360 .policies
361 .iter()
362 .map(|policy| policy.to_proto())
363 .collect::<Result<Vec<MetadataPolicyProto>, PolicyError>>()?,
364 },
365 )),
366 })
367 }
368}
369
370#[derive(Clone, Debug, PartialEq)]
372pub struct MetadataAnyCondition {
373 policies: Vec<MetadataPolicies>,
374}
375
376#[allow(dead_code)]
377impl MetadataAnyCondition {
378 pub(super) fn new(policies: Vec<MetadataPolicies>) -> Self {
379 Self { policies }
380 }
381}
382
383impl MetadataPolicy for MetadataAnyCondition {
385 fn evaluate(&self, actor: &CommitParticipant, change: &MetadataFieldChange) -> bool {
386 self.policies
387 .iter()
388 .any(|policy| policy.evaluate(actor, change))
389 }
390
391 fn to_proto(&self) -> Result<MetadataPolicyProto, PolicyError> {
392 Ok(MetadataPolicyProto {
393 kind: Some(MetadataPolicyKindProto::AnyCondition(
394 MetadataAnyConditionProto {
395 policies: self
396 .policies
397 .iter()
398 .map(|policy| policy.to_proto())
399 .collect::<Result<Vec<MetadataPolicyProto>, PolicyError>>()?,
400 },
401 )),
402 })
403 }
404}
405
406pub trait PermissionsPolicy: std::fmt::Debug {
408 fn evaluate(&self, actor: &CommitParticipant) -> bool;
410
411 fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError>;
413}
414
415#[derive(Clone, Copy, Debug, PartialEq)]
417pub enum PermissionsBasePolicies {
418 Deny,
419 AllowIfActorAdminOrSuperAdmin,
420 AllowIfActorSuperAdmin,
421}
422
423impl PermissionsPolicy for &PermissionsBasePolicies {
425 fn evaluate(&self, actor: &CommitParticipant) -> bool {
426 match self {
427 PermissionsBasePolicies::Deny => false,
428 PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin => {
429 actor.is_admin || actor.is_super_admin
430 }
431 PermissionsBasePolicies::AllowIfActorSuperAdmin => actor.is_super_admin,
432 }
433 }
434
435 fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
436 let inner = match self {
437 PermissionsBasePolicies::Deny => PermissionsBasePolicyProto::Deny as i32,
438 PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin => {
439 PermissionsBasePolicyProto::AllowIfAdmin as i32
440 }
441 PermissionsBasePolicies::AllowIfActorSuperAdmin => {
442 PermissionsBasePolicyProto::AllowIfSuperAdmin as i32
443 }
444 };
445
446 Ok(PermissionsPolicyProto {
447 kind: Some(PermissionsPolicyKindProto::Base(inner)),
448 })
449 }
450}
451
452#[derive(Debug, Clone, PartialEq)]
454#[allow(dead_code)]
455pub enum PermissionsPolicies {
456 Standard(PermissionsBasePolicies),
457 AndCondition(PermissionsAndCondition),
458 AnyCondition(PermissionsAnyCondition),
459}
460
461impl PermissionsPolicies {
462 pub fn deny() -> Self {
464 PermissionsPolicies::Standard(PermissionsBasePolicies::Deny)
465 }
466
467 pub fn allow_if_actor_admin() -> Self {
469 PermissionsPolicies::Standard(PermissionsBasePolicies::AllowIfActorAdminOrSuperAdmin)
470 }
471
472 pub fn allow_if_actor_super_admin() -> Self {
474 PermissionsPolicies::Standard(PermissionsBasePolicies::AllowIfActorSuperAdmin)
475 }
476
477 pub fn and(policies: Vec<PermissionsPolicies>) -> Self {
479 PermissionsPolicies::AndCondition(PermissionsAndCondition::new(policies))
480 }
481
482 pub fn any(policies: Vec<PermissionsPolicies>) -> Self {
484 PermissionsPolicies::AnyCondition(PermissionsAnyCondition::new(policies))
485 }
486}
487
488impl TryFrom<PermissionsPolicyProto> for PermissionsPolicies {
490 type Error = PolicyError;
491
492 fn try_from(proto: PermissionsPolicyProto) -> Result<Self, Self::Error> {
493 match proto.kind {
494 Some(PermissionsPolicyKindProto::Base(inner)) => match inner {
495 1 => Ok(PermissionsPolicies::deny()),
496 2 => Ok(PermissionsPolicies::allow_if_actor_admin()),
497 3 => Ok(PermissionsPolicies::allow_if_actor_super_admin()),
498 _ => Err(PolicyError::InvalidPermissionsPolicy),
499 },
500 Some(PermissionsPolicyKindProto::AndCondition(inner)) => {
501 if inner.policies.is_empty() {
502 return Err(PolicyError::InvalidPermissionsPolicy);
503 }
504 let policies = inner
505 .policies
506 .into_iter()
507 .map(|policy| policy.try_into())
508 .collect::<Result<Vec<PermissionsPolicies>, PolicyError>>()?;
509
510 Ok(PermissionsPolicies::and(policies))
511 }
512 Some(PermissionsPolicyKindProto::AnyCondition(inner)) => {
513 if inner.policies.is_empty() {
514 return Err(PolicyError::InvalidPermissionsPolicy);
515 }
516
517 let policies = inner
518 .policies
519 .into_iter()
520 .map(|policy| policy.try_into())
521 .collect::<Result<Vec<PermissionsPolicies>, PolicyError>>()?;
522
523 Ok(PermissionsPolicies::any(policies))
524 }
525 None => Err(PolicyError::InvalidPermissionsPolicy),
526 }
527 }
528}
529
530impl PermissionsPolicy for PermissionsPolicies {
532 fn evaluate(&self, actor: &CommitParticipant) -> bool {
533 match self {
534 PermissionsPolicies::Standard(policy) => policy.evaluate(actor),
535 PermissionsPolicies::AndCondition(policy) => policy.evaluate(actor),
536 PermissionsPolicies::AnyCondition(policy) => policy.evaluate(actor),
537 }
538 }
539
540 fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
541 Ok(match self {
542 PermissionsPolicies::Standard(policy) => policy.to_proto()?,
543 PermissionsPolicies::AndCondition(policy) => policy.to_proto()?,
544 PermissionsPolicies::AnyCondition(policy) => policy.to_proto()?,
545 })
546 }
547}
548
549#[derive(Clone, Debug, PartialEq)]
551pub struct PermissionsAndCondition {
552 policies: Vec<PermissionsPolicies>,
553}
554
555impl PermissionsAndCondition {
556 pub(super) fn new(policies: Vec<PermissionsPolicies>) -> Self {
557 Self { policies }
558 }
559}
560
561impl PermissionsPolicy for PermissionsAndCondition {
563 fn evaluate(&self, actor: &CommitParticipant) -> bool {
564 self.policies.iter().all(|policy| policy.evaluate(actor))
565 }
566
567 fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
568 Ok(PermissionsPolicyProto {
569 kind: Some(PermissionsPolicyKindProto::AndCondition(
570 PermissionsAndConditionProto {
571 policies: self
572 .policies
573 .iter()
574 .map(|policy| policy.to_proto())
575 .collect::<Result<Vec<PermissionsPolicyProto>, PolicyError>>()?,
576 },
577 )),
578 })
579 }
580}
581
582#[derive(Clone, Debug, PartialEq)]
584pub struct PermissionsAnyCondition {
585 policies: Vec<PermissionsPolicies>,
586}
587
588#[allow(dead_code)]
589impl PermissionsAnyCondition {
590 pub(super) fn new(policies: Vec<PermissionsPolicies>) -> Self {
591 Self { policies }
592 }
593}
594
595impl PermissionsPolicy for PermissionsAnyCondition {
597 fn evaluate(&self, actor: &CommitParticipant) -> bool {
598 self.policies.iter().any(|policy| policy.evaluate(actor))
599 }
600
601 fn to_proto(&self) -> Result<PermissionsPolicyProto, PolicyError> {
602 Ok(PermissionsPolicyProto {
603 kind: Some(PermissionsPolicyKindProto::AnyCondition(
604 PermissionsAnyConditionProto {
605 policies: self
606 .policies
607 .iter()
608 .map(|policy| policy.to_proto())
609 .collect::<Result<Vec<PermissionsPolicyProto>, PolicyError>>()?,
610 },
611 )),
612 })
613 }
614}
615
616pub trait MembershipPolicy: std::fmt::Debug {
618 fn evaluate(&self, actor: &CommitParticipant, change: &Inbox) -> bool;
620
621 fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError>;
623}
624
625#[derive(Debug, Error)]
627pub enum PolicyError {
628 #[error("serialization {0}")]
629 Serialization(#[from] prost::EncodeError),
630 #[error("deserialization {0}")]
631 Deserialization(#[from] prost::DecodeError),
632 #[error("Missing metadata policy field: {name}")]
633 MissingMetadataPolicyField { name: String },
634 #[error("invalid policy")]
635 InvalidPolicy,
636 #[error("unexpected preset policy")]
637 InvalidPresetPolicy,
638 #[error("invalid metadata policy")]
639 InvalidMetadataPolicy,
640 #[error("invalid membership policy")]
641 InvalidMembershipPolicy,
642 #[error("invalid permissions policy")]
643 InvalidPermissionsPolicy,
644 #[error("from proto add member invalid policy")]
645 FromProtoAddMemberInvalidPolicy,
646 #[error("from proto remove member invalid policy")]
647 FromProtoRemoveMemberInvalidPolicy,
648 #[error("from proto add admin invalid policy")]
649 FromProtoAddAdminInvalidPolicy,
650 #[error("from proto remove admin invalid policy")]
651 FromProtoRemoveAdminInvalidPolicy,
652 #[error("from proto update permissions invalid policy")]
653 FromProtoUpdatePermissionsInvalidPolicy,
654}
655
656#[derive(Debug, Clone, Copy, PartialEq)]
658#[allow(dead_code)]
659#[repr(u8)]
660pub enum BasePolicies {
661 Allow,
662 Deny,
663 AllowSameMember,
665 AllowIfAdminOrSuperAdmin,
666 AllowIfSuperAdmin,
667}
668
669impl MembershipPolicy for BasePolicies {
671 fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
672 match self {
673 BasePolicies::Allow => true,
674 BasePolicies::Deny => false,
675 BasePolicies::AllowSameMember => inbox.inbox_id == actor.inbox_id,
676 BasePolicies::AllowIfAdminOrSuperAdmin => actor.is_admin || actor.is_super_admin,
677 BasePolicies::AllowIfSuperAdmin => actor.is_super_admin,
678 }
679 }
680
681 fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
682 let inner = match self {
683 BasePolicies::Allow => BasePolicyProto::Allow as i32,
684 BasePolicies::Deny => BasePolicyProto::Deny as i32,
685 BasePolicies::AllowSameMember => return Err(PolicyError::InvalidPolicy), BasePolicies::AllowIfAdminOrSuperAdmin => {
687 BasePolicyProto::AllowIfAdminOrSuperAdmin as i32
688 }
689 BasePolicies::AllowIfSuperAdmin => BasePolicyProto::AllowIfSuperAdmin as i32,
690 };
691
692 Ok(MembershipPolicyProto {
693 kind: Some(PolicyKindProto::Base(inner)),
694 })
695 }
696}
697
698#[derive(Debug, Clone, PartialEq)]
700#[allow(dead_code)]
701pub enum MembershipPolicies {
702 Standard(BasePolicies),
703 AndCondition(AndCondition),
704 AnyCondition(AnyCondition),
705}
706
707impl MembershipPolicies {
708 pub fn allow() -> Self {
710 MembershipPolicies::Standard(BasePolicies::Allow)
711 }
712
713 pub fn deny() -> Self {
715 MembershipPolicies::Standard(BasePolicies::Deny)
716 }
717
718 #[allow(dead_code)]
720 pub fn allow_if_actor_admin() -> Self {
721 MembershipPolicies::Standard(BasePolicies::AllowIfAdminOrSuperAdmin)
722 }
723
724 #[allow(dead_code)]
726 pub fn allow_if_actor_super_admin() -> Self {
727 MembershipPolicies::Standard(BasePolicies::AllowIfSuperAdmin)
728 }
729
730 pub fn and(policies: Vec<MembershipPolicies>) -> Self {
732 MembershipPolicies::AndCondition(AndCondition::new(policies))
733 }
734
735 pub fn any(policies: Vec<MembershipPolicies>) -> Self {
737 MembershipPolicies::AnyCondition(AnyCondition::new(policies))
738 }
739}
740
741impl TryFrom<MembershipPolicyProto> for MembershipPolicies {
743 type Error = PolicyError;
744
745 fn try_from(proto: MembershipPolicyProto) -> Result<Self, Self::Error> {
746 match proto.kind {
747 Some(PolicyKindProto::Base(inner)) => match inner {
748 1 => Ok(MembershipPolicies::allow()),
749 2 => Ok(MembershipPolicies::deny()),
750 3 => Ok(MembershipPolicies::allow_if_actor_admin()),
751 4 => Ok(MembershipPolicies::allow_if_actor_super_admin()),
752 _ => Err(PolicyError::InvalidMembershipPolicy),
753 },
754 Some(PolicyKindProto::AndCondition(inner)) => {
755 if inner.policies.is_empty() {
756 return Err(PolicyError::InvalidMembershipPolicy);
757 }
758 let policies = inner
759 .policies
760 .into_iter()
761 .map(|policy| policy.try_into())
762 .collect::<Result<Vec<MembershipPolicies>, PolicyError>>()?;
763
764 Ok(MembershipPolicies::and(policies))
765 }
766 Some(PolicyKindProto::AnyCondition(inner)) => {
767 if inner.policies.is_empty() {
768 return Err(PolicyError::InvalidMembershipPolicy);
769 }
770
771 let policies = inner
772 .policies
773 .into_iter()
774 .map(|policy| policy.try_into())
775 .collect::<Result<Vec<MembershipPolicies>, PolicyError>>()?;
776
777 Ok(MembershipPolicies::any(policies))
778 }
779 None => Err(PolicyError::InvalidMembershipPolicy),
780 }
781 }
782}
783
784impl MembershipPolicy for MembershipPolicies {
786 fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
787 match self {
788 MembershipPolicies::Standard(policy) => policy.evaluate(actor, inbox),
789 MembershipPolicies::AndCondition(policy) => policy.evaluate(actor, inbox),
790 MembershipPolicies::AnyCondition(policy) => policy.evaluate(actor, inbox),
791 }
792 }
793
794 fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
795 Ok(match self {
796 MembershipPolicies::Standard(policy) => policy.to_proto()?,
797 MembershipPolicies::AndCondition(policy) => policy.to_proto()?,
798 MembershipPolicies::AnyCondition(policy) => policy.to_proto()?,
799 })
800 }
801}
802
803#[derive(Clone, Debug, PartialEq)]
805pub struct AndCondition {
806 policies: Vec<MembershipPolicies>,
807}
808
809impl AndCondition {
810 pub(super) fn new(policies: Vec<MembershipPolicies>) -> Self {
811 Self { policies }
812 }
813}
814
815impl MembershipPolicy for AndCondition {
817 fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
818 self.policies
819 .iter()
820 .all(|policy| policy.evaluate(actor, inbox))
821 }
822
823 fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
824 Ok(MembershipPolicyProto {
825 kind: Some(PolicyKindProto::AndCondition(AndConditionProto {
826 policies: self
827 .policies
828 .iter()
829 .map(|policy| policy.to_proto())
830 .collect::<Result<Vec<MembershipPolicyProto>, PolicyError>>()?,
831 })),
832 })
833 }
834}
835
836#[derive(Clone, Debug, PartialEq)]
838pub struct AnyCondition {
839 policies: Vec<MembershipPolicies>,
840}
841
842#[allow(dead_code)]
843impl AnyCondition {
844 pub(super) fn new(policies: Vec<MembershipPolicies>) -> Self {
845 Self { policies }
846 }
847}
848
849impl MembershipPolicy for AnyCondition {
851 fn evaluate(&self, actor: &CommitParticipant, inbox: &Inbox) -> bool {
852 self.policies
853 .iter()
854 .any(|policy| policy.evaluate(actor, inbox))
855 }
856
857 fn to_proto(&self) -> Result<MembershipPolicyProto, PolicyError> {
858 Ok(MembershipPolicyProto {
859 kind: Some(PolicyKindProto::AnyCondition(AnyConditionProto {
860 policies: self
861 .policies
862 .iter()
863 .map(|policy| policy.to_proto())
864 .collect::<Result<Vec<MembershipPolicyProto>, PolicyError>>()?,
865 })),
866 })
867 }
868}
869
870#[derive(Debug, Clone, PartialEq)]
872#[allow(dead_code)]
873pub struct PolicySet {
874 pub add_member_policy: MembershipPolicies,
876 pub remove_member_policy: MembershipPolicies,
878 pub update_metadata_policy: HashMap<String, MetadataPolicies>,
880 pub add_admin_policy: PermissionsPolicies,
882 pub remove_admin_policy: PermissionsPolicies,
884 pub update_permissions_policy: PermissionsPolicies,
886}
887
888impl PolicySet {
889 pub fn new(
891 add_member_policy: MembershipPolicies,
892 remove_member_policy: MembershipPolicies,
893 update_metadata_policy: HashMap<String, MetadataPolicies>,
894 add_admin_policy: PermissionsPolicies,
895 remove_admin_policy: PermissionsPolicies,
896 update_permissions_policy: PermissionsPolicies,
897 ) -> Self {
898 Self {
899 add_member_policy,
900 remove_member_policy,
901 update_metadata_policy,
902 add_admin_policy,
903 remove_admin_policy,
904 update_permissions_policy,
905 }
906 }
907
908 pub fn new_dm() -> Self {
909 Self {
910 add_member_policy: MembershipPolicies::deny(),
911 remove_member_policy: MembershipPolicies::deny(),
912 update_metadata_policy: MetadataPolicies::dm_map(),
913 add_admin_policy: PermissionsPolicies::deny(),
914 remove_admin_policy: PermissionsPolicies::deny(),
915 update_permissions_policy: PermissionsPolicies::deny(),
916 }
917 }
918
919 pub fn evaluate_membership(&self, commit: &MembershipValidationInfo<'_>) -> bool {
922 let mut added_inboxes_valid = self.evaluate_policy_with_proposer(
925 commit.added_inboxes.iter(),
926 &self.add_member_policy,
927 commit.actor,
928 );
929
930 if let Some(dm_members) = &commit.dm_members
932 && commit.added_inboxes.len() == 1
933 {
934 let added_inbox_id = &commit.added_inboxes[0].inbox_id;
935 if (added_inbox_id == &dm_members.member_one_inbox_id
936 || added_inbox_id == &dm_members.member_two_inbox_id)
937 && added_inbox_id != &commit.actor.inbox_id
938 {
939 added_inboxes_valid = true;
940 }
941 }
942
943 let removed_inboxes_valid = self.evaluate_policy_with_proposer(
947 commit.removed_inboxes.iter(),
948 &self.remove_member_policy,
949 commit.actor,
950 ) && !commit
951 .removed_inboxes
952 .iter()
953 .any(|inbox| inbox.is_super_admin);
954
955 added_inboxes_valid && removed_inboxes_valid
956 }
957
958 fn evaluate_policy_with_proposer<'a, I, P>(
961 &self,
962 mut changes: I,
963 policy: &P,
964 default_actor: &CommitParticipant,
965 ) -> bool
966 where
967 I: Iterator<Item = &'a Inbox>,
968 P: MembershipPolicy + std::fmt::Debug,
969 {
970 changes.all(|change| {
971 let actor = change.proposer.as_ref().unwrap_or(default_actor);
973 let is_ok = policy.evaluate(actor, change);
974 if !is_ok {
975 tracing::info!(
976 has_proposer = change.proposer.is_some(),
977 actor_is_creator = actor.is_creator,
978 actor_is_admin = actor.is_admin,
979 actor_is_super_admin = actor.is_super_admin,
980 subject_is_creator = change.is_creator,
981 subject_is_admin = change.is_admin,
982 subject_is_super_admin = change.is_super_admin,
983 "Membership policy rejected change"
984 );
985 }
986 is_ok
987 })
988 }
989
990 pub fn to_proto(&self) -> Result<PolicySetProto, PolicyError> {
992 let add_member_policy = Some(self.add_member_policy.to_proto()?);
993 let remove_member_policy = Some(self.remove_member_policy.to_proto()?);
994
995 let mut update_metadata_policy = HashMap::new();
996 for (key, policy) in &self.update_metadata_policy {
997 let policy_proto = policy.to_proto()?;
998 update_metadata_policy.insert(key.clone(), policy_proto);
999 }
1000 let add_admin_policy = Some(self.add_admin_policy.to_proto()?);
1001 let remove_admin_policy = Some(self.remove_admin_policy.to_proto()?);
1002 let update_permissions_policy = Some(self.update_permissions_policy.to_proto()?);
1003 Ok(PolicySetProto {
1004 add_member_policy,
1005 remove_member_policy,
1006 update_metadata_policy,
1007 add_admin_policy,
1008 remove_admin_policy,
1009 update_permissions_policy,
1010 })
1011 }
1012
1013 pub fn from_proto(proto: PolicySetProto) -> Result<Self, PolicyError> {
1015 let add_member_policy = MembershipPolicies::try_from(
1016 proto
1017 .add_member_policy
1018 .ok_or(PolicyError::FromProtoAddMemberInvalidPolicy)?,
1019 )?;
1020 let remove_member_policy = MembershipPolicies::try_from(
1021 proto
1022 .remove_member_policy
1023 .ok_or(PolicyError::FromProtoRemoveMemberInvalidPolicy)?,
1024 )?;
1025 let add_admin_policy = PermissionsPolicies::try_from(
1026 proto
1027 .add_admin_policy
1028 .ok_or(PolicyError::FromProtoAddAdminInvalidPolicy)?,
1029 )?;
1030 let remove_admin_policy = PermissionsPolicies::try_from(
1031 proto
1032 .remove_admin_policy
1033 .ok_or(PolicyError::FromProtoRemoveAdminInvalidPolicy)?,
1034 )?;
1035 let update_permissions_policy = PermissionsPolicies::try_from(
1036 proto
1037 .update_permissions_policy
1038 .ok_or(PolicyError::FromProtoUpdatePermissionsInvalidPolicy)?,
1039 )?;
1040
1041 let mut update_metadata_policy = HashMap::new();
1042 for (key, policy_proto) in proto.update_metadata_policy {
1043 let policy = MetadataPolicies::try_from(policy_proto)?;
1044 update_metadata_policy.insert(key, policy);
1045 }
1046 Ok(Self::new(
1047 add_member_policy,
1048 remove_member_policy,
1049 update_metadata_policy,
1050 add_admin_policy,
1051 remove_admin_policy,
1052 update_permissions_policy,
1053 ))
1054 }
1055
1056 pub fn to_bytes(&self) -> Result<Vec<u8>, PolicyError> {
1058 let proto = self.to_proto()?;
1059 let mut buf = Vec::new();
1060 proto.encode(&mut buf)?;
1061 Ok(buf)
1062 }
1063
1064 pub fn from_bytes(bytes: &[u8]) -> Result<Self, PolicyError> {
1066 let proto = PolicySetProto::decode(bytes)?;
1067 Self::from_proto(proto)
1068 }
1069}
1070
1071pub fn policy_set_from_dictionary(
1073 extensions: &Extensions<GroupContext>,
1074) -> Result<GroupMutablePermissions, GroupMutablePermissionsError> {
1075 let proto = xmtp_mls_common::app_data::policy_set::policy_set_from_dictionary(extensions)
1076 .map_err(|error| {
1077 tracing::warn!(%error, "invalid registry action policy");
1078 GroupMutablePermissionsError::PolicyProjection(error)
1079 })?;
1080 Ok(GroupMutablePermissions::new(PolicySet::from_proto(proto)?))
1081}
1082
1083pub fn is_policy_default(policy: &PolicySet) -> Result<bool, PolicyError> {
1090 let mut metadata_policies_equal = true;
1091 for field_name in policy.update_metadata_policy.keys() {
1092 let metadata_policy = policy.update_metadata_policy.get(field_name).ok_or(
1093 PolicyError::MissingMetadataPolicyField {
1094 name: field_name.to_string(),
1095 },
1096 )?;
1097 if field_name == MetadataField::MessageDisappearInNS.as_str()
1098 || field_name == MetadataField::MessageDisappearFromNS.as_str()
1099 {
1100 metadata_policies_equal = metadata_policies_equal
1101 && metadata_policy.eq(&MetadataPolicies::allow_if_actor_admin());
1102 } else if field_name == MetadataField::MinimumSupportedProtocolVersion.as_str() {
1103 metadata_policies_equal = metadata_policies_equal
1104 && metadata_policy.eq(&MetadataPolicies::allow_if_actor_super_admin());
1105 } else {
1106 metadata_policies_equal =
1107 metadata_policies_equal && metadata_policy.eq(&MetadataPolicies::allow());
1108 }
1109 }
1110 Ok(metadata_policies_equal
1111 && policy.add_member_policy == MembershipPolicies::allow()
1112 && policy.remove_member_policy == MembershipPolicies::allow_if_actor_admin()
1113 && policy.add_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1114 && policy.remove_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1115 && policy.update_permissions_policy == PermissionsPolicies::allow_if_actor_super_admin())
1116}
1117
1118pub fn is_policy_admin_only(policy: &PolicySet) -> Result<bool, PolicyError> {
1125 let mut metadata_policies_equal = true;
1126 for field_name in policy.update_metadata_policy.keys() {
1127 let metadata_policy = policy.update_metadata_policy.get(field_name).ok_or(
1128 PolicyError::MissingMetadataPolicyField {
1129 name: field_name.to_string(),
1130 },
1131 )?;
1132 if field_name == MetadataField::MinimumSupportedProtocolVersion.as_str() {
1133 metadata_policies_equal = metadata_policies_equal
1134 && metadata_policy.eq(&MetadataPolicies::allow_if_actor_super_admin());
1135 } else {
1136 metadata_policies_equal = metadata_policies_equal
1137 && metadata_policy.eq(&MetadataPolicies::allow_if_actor_admin());
1138 }
1139 }
1140 Ok(metadata_policies_equal
1141 && policy.add_member_policy == MembershipPolicies::allow_if_actor_admin()
1142 && policy.remove_member_policy == MembershipPolicies::allow_if_actor_admin()
1143 && policy.add_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1144 && policy.remove_admin_policy == PermissionsPolicies::allow_if_actor_super_admin()
1145 && policy.update_permissions_policy == PermissionsPolicies::allow_if_actor_super_admin())
1146}
1147
1148pub fn default_policy() -> PolicySet {
1152 let mut metadata_policies_map: HashMap<String, MetadataPolicies> = HashMap::new();
1153 for field in GroupMutableMetadata::supported_fields() {
1154 match field {
1155 MetadataField::MessageDisappearInNS => {
1156 metadata_policies_map
1157 .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1158 }
1159 MetadataField::MessageDisappearFromNS => {
1160 metadata_policies_map
1161 .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1162 }
1163 MetadataField::MinimumSupportedProtocolVersion => {
1164 metadata_policies_map.insert(
1165 field.to_string(),
1166 MetadataPolicies::allow_if_actor_super_admin(),
1167 );
1168 }
1169 _ => {
1170 metadata_policies_map.insert(field.to_string(), MetadataPolicies::allow());
1171 }
1172 }
1173 }
1174
1175 PolicySet::new(
1176 MembershipPolicies::allow(),
1177 MembershipPolicies::allow_if_actor_admin(),
1178 metadata_policies_map,
1179 PermissionsPolicies::allow_if_actor_super_admin(),
1180 PermissionsPolicies::allow_if_actor_super_admin(),
1181 PermissionsPolicies::allow_if_actor_super_admin(),
1182 )
1183}
1184
1185pub fn policy_admin_only() -> PolicySet {
1189 let mut metadata_policies_map: HashMap<String, MetadataPolicies> = HashMap::new();
1190 for field in GroupMutableMetadata::supported_fields() {
1191 match field {
1192 MetadataField::MessageDisappearInNS => {
1193 metadata_policies_map
1194 .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1195 }
1196 MetadataField::MessageDisappearFromNS => {
1197 metadata_policies_map
1198 .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1199 }
1200 MetadataField::MinimumSupportedProtocolVersion => {
1201 metadata_policies_map.insert(
1202 field.to_string(),
1203 MetadataPolicies::allow_if_actor_super_admin(),
1204 );
1205 }
1206 _ => {
1207 metadata_policies_map
1208 .insert(field.to_string(), MetadataPolicies::allow_if_actor_admin());
1209 }
1210 }
1211 }
1212
1213 PolicySet::new(
1214 MembershipPolicies::allow_if_actor_admin(),
1215 MembershipPolicies::allow_if_actor_admin(),
1216 metadata_policies_map,
1217 PermissionsPolicies::allow_if_actor_super_admin(),
1218 PermissionsPolicies::allow_if_actor_super_admin(),
1219 PermissionsPolicies::allow_if_actor_super_admin(),
1220 )
1221}
1222
1223impl Default for PolicySet {
1225 fn default() -> Self {
1226 PreconfiguredPolicies::default().to_policy_set()
1227 }
1228}
1229
1230#[derive(Debug, Clone, PartialEq, Default)]
1232pub enum PreconfiguredPolicies {
1233 #[default]
1235 Default,
1236 AdminsOnly,
1238}
1239
1240impl PreconfiguredPolicies {
1241 pub fn to_policy_set(&self) -> PolicySet {
1243 match self {
1244 PreconfiguredPolicies::Default => default_policy(),
1245 PreconfiguredPolicies::AdminsOnly => policy_admin_only(),
1246 }
1247 }
1248
1249 pub fn from_policy_set(policy_set: &PolicySet) -> Result<Self, PolicyError> {
1251 if is_policy_default(policy_set)? {
1252 Ok(PreconfiguredPolicies::Default)
1253 } else if is_policy_admin_only(policy_set)? {
1254 Ok(PreconfiguredPolicies::AdminsOnly)
1255 } else {
1256 Err(PolicyError::InvalidPresetPolicy)
1257 }
1258 }
1259}
1260
1261impl std::fmt::Display for PreconfiguredPolicies {
1263 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1264 write!(f, "{:?}", self)
1265 }
1266}
1267
1268#[cfg(test)]
1269pub mod tests {
1270
1271 use openmls::extensions::{AppDataDictionary, AppDataDictionaryExtension, Extension};
1272 use xmtp_common::{rand_string, rand_vec};
1273 use xmtp_mls_common::{
1274 app_data::{component_id::ComponentId, creation::synthesize_registry_from_policy_set},
1275 group_metadata::DmMembers,
1276 };
1277
1278 use super::*;
1279
1280 fn dictionary_extensions(policy_set: &PolicySet) -> Extensions<GroupContext> {
1281 let proto = policy_set.to_proto().unwrap();
1282 let registry = synthesize_registry_from_policy_set(&proto).unwrap();
1283 let mut dictionary = AppDataDictionary::new();
1284 dictionary.insert(
1285 ComponentId::COMPONENT_REGISTRY.as_u16(),
1286 registry.to_bytes().unwrap(),
1287 );
1288 Extensions::from_vec(vec![Extension::AppDataDictionary(
1289 AppDataDictionaryExtension::new(dictionary),
1290 )])
1291 .unwrap()
1292 }
1293
1294 fn build_change(inbox_id: Option<String>, is_admin: bool, is_super_admin: bool) -> Inbox {
1295 Inbox {
1296 inbox_id: inbox_id.unwrap_or(rand_string::<24>()),
1297 is_creator: is_super_admin,
1298 is_super_admin,
1299 is_admin,
1300 proposer: None,
1301 }
1302 }
1303
1304 fn build_actor(
1306 inbox_id: Option<String>,
1307 installation_id: Option<Vec<u8>>,
1308 is_admin: bool,
1309 is_super_admin: bool,
1310 ) -> CommitParticipant {
1311 CommitParticipant {
1312 inbox_id: inbox_id.unwrap_or(rand_string::<24>()),
1313 installation_id: installation_id.unwrap_or_else(rand_vec::<24>),
1314 is_creator: is_super_admin,
1315 is_admin,
1316 is_super_admin,
1317 }
1318 }
1319
1320 enum MemberType {
1321 SameAsActor,
1322 DmTarget,
1323 Random,
1324 }
1325
1326 struct TestMembershipChanges {
1327 actor: CommitParticipant,
1328 added_inboxes: Vec<Inbox>,
1329 removed_inboxes: Vec<Inbox>,
1330 dm_members: Option<DmMembers<String>>,
1331 }
1332
1333 impl TestMembershipChanges {
1334 fn validation_info(&self) -> MembershipValidationInfo<'_> {
1335 MembershipValidationInfo {
1336 actor: &self.actor,
1337 added_inboxes: &self.added_inboxes,
1338 removed_inboxes: &self.removed_inboxes,
1339 dm_members: self.dm_members.as_ref(),
1340 }
1341 }
1342 }
1343
1344 fn build_membership_changes(
1346 member_added: Option<MemberType>,
1348 member_removed: Option<MemberType>,
1349 actor_is_admin: bool,
1350 actor_is_super_admin: bool,
1351 dm_target_inbox_id: Option<String>,
1352 ) -> TestMembershipChanges {
1353 let actor = build_actor(None, None, actor_is_admin, actor_is_super_admin);
1354 let dm_target_inbox_id_clone = dm_target_inbox_id.clone();
1355 let build_membership_change = |member_type: MemberType| match member_type {
1356 MemberType::SameAsActor => vec![build_change(
1357 Some(actor.inbox_id.clone()),
1358 actor_is_admin,
1359 actor_is_super_admin,
1360 )],
1361 MemberType::DmTarget => {
1362 vec![build_change(dm_target_inbox_id_clone.clone(), false, false)]
1363 }
1364 MemberType::Random => vec![build_change(None, false, false)],
1365 };
1366
1367 let dm_members = if let Some(dm_target_inbox_id) = dm_target_inbox_id {
1368 Some(DmMembers {
1369 member_one_inbox_id: actor.inbox_id.clone(),
1370 member_two_inbox_id: dm_target_inbox_id,
1371 })
1372 } else {
1373 None
1374 };
1375
1376 TestMembershipChanges {
1377 actor: actor.clone(),
1378 added_inboxes: member_added
1379 .map(build_membership_change)
1380 .unwrap_or_default(),
1381 removed_inboxes: member_removed
1382 .map(build_membership_change)
1383 .unwrap_or_default(),
1384 dm_members,
1385 }
1386 }
1387
1388 #[xmtp_common::test]
1391 fn test_allow_all() {
1392 let permissions = PolicySet::new(
1393 MembershipPolicies::allow(),
1394 MembershipPolicies::allow(),
1395 MetadataPolicies::default_map(MetadataPolicies::allow()),
1396 PermissionsPolicies::allow_if_actor_super_admin(),
1397 PermissionsPolicies::allow_if_actor_super_admin(),
1398 PermissionsPolicies::allow_if_actor_super_admin(),
1399 );
1400
1401 let commit = build_membership_changes(
1402 Some(MemberType::SameAsActor),
1403 Some(MemberType::SameAsActor),
1404 false,
1405 false,
1406 None,
1407 );
1408 assert!(permissions.evaluate_membership(&commit.validation_info()));
1409 }
1410
1411 #[xmtp_common::test]
1413 fn test_deny() {
1414 let permissions = PolicySet::new(
1415 MembershipPolicies::deny(),
1416 MembershipPolicies::deny(),
1417 MetadataPolicies::default_map(MetadataPolicies::deny()),
1418 PermissionsPolicies::allow_if_actor_super_admin(),
1419 PermissionsPolicies::allow_if_actor_super_admin(),
1420 PermissionsPolicies::allow_if_actor_super_admin(),
1421 );
1422
1423 let member_added_commit =
1424 build_membership_changes(Some(MemberType::Random), None, false, false, None);
1425 assert!(!permissions.evaluate_membership(&member_added_commit.validation_info()));
1426
1427 let member_removed_commit =
1428 build_membership_changes(None, Some(MemberType::Random), false, false, None);
1429 assert!(!permissions.evaluate_membership(&member_removed_commit.validation_info()));
1430 }
1431
1432 #[xmtp_common::test]
1434 fn test_actor_is_creator() {
1435 let permissions = PolicySet::new(
1436 MembershipPolicies::allow_if_actor_super_admin(),
1437 MembershipPolicies::allow_if_actor_super_admin(),
1438 MetadataPolicies::default_map(MetadataPolicies::deny()),
1439 PermissionsPolicies::allow_if_actor_super_admin(),
1440 PermissionsPolicies::allow_if_actor_super_admin(),
1441 PermissionsPolicies::allow_if_actor_super_admin(),
1442 );
1443
1444 let commit_with_creator = build_membership_changes(
1446 Some(MemberType::SameAsActor),
1447 Some(MemberType::SameAsActor),
1448 false,
1449 true,
1450 None,
1451 );
1452 assert!(!permissions.evaluate_membership(&commit_with_creator.validation_info()));
1453
1454 let commit_with_creator = build_membership_changes(
1455 Some(MemberType::SameAsActor),
1456 Some(MemberType::Random),
1457 false,
1458 true,
1459 None,
1460 );
1461 assert!(permissions.evaluate_membership(&commit_with_creator.validation_info()));
1462
1463 let commit_without_creator = build_membership_changes(
1464 Some(MemberType::SameAsActor),
1465 Some(MemberType::SameAsActor),
1466 false,
1467 false,
1468 None,
1469 );
1470 assert!(!permissions.evaluate_membership(&commit_without_creator.validation_info()));
1471 }
1472
1473 #[xmtp_common::test]
1475 fn test_and_condition() {
1476 let permissions = PolicySet::new(
1477 MembershipPolicies::and(vec![
1478 MembershipPolicies::Standard(BasePolicies::Deny),
1479 MembershipPolicies::Standard(BasePolicies::Allow),
1480 ]),
1481 MembershipPolicies::allow(),
1482 MetadataPolicies::default_map(MetadataPolicies::deny()),
1483 PermissionsPolicies::allow_if_actor_super_admin(),
1484 PermissionsPolicies::allow_if_actor_super_admin(),
1485 PermissionsPolicies::allow_if_actor_super_admin(),
1486 );
1487
1488 let member_added_commit =
1489 build_membership_changes(Some(MemberType::SameAsActor), None, false, false, None);
1490 assert!(!permissions.evaluate_membership(&member_added_commit.validation_info()));
1491 }
1492
1493 #[xmtp_common::test]
1495 fn test_any_condition() {
1496 let permissions = PolicySet::new(
1497 MembershipPolicies::any(vec![
1498 MembershipPolicies::deny(),
1499 MembershipPolicies::allow(),
1500 ]),
1501 MembershipPolicies::allow(),
1502 MetadataPolicies::default_map(MetadataPolicies::deny()),
1503 PermissionsPolicies::allow_if_actor_super_admin(),
1504 PermissionsPolicies::allow_if_actor_super_admin(),
1505 PermissionsPolicies::allow_if_actor_super_admin(),
1506 );
1507
1508 let member_added_commit =
1509 build_membership_changes(Some(MemberType::SameAsActor), None, false, false, None);
1510 assert!(permissions.evaluate_membership(&member_added_commit.validation_info()));
1511 }
1512
1513 #[xmtp_common::test]
1515 fn test_serialize() {
1516 let permissions = PolicySet::new(
1517 MembershipPolicies::any(vec![
1518 MembershipPolicies::allow(),
1519 MembershipPolicies::deny(),
1520 ]),
1521 MembershipPolicies::and(vec![
1522 MembershipPolicies::allow_if_actor_super_admin(),
1523 MembershipPolicies::deny(),
1524 ]),
1525 MetadataPolicies::default_map(MetadataPolicies::deny()),
1526 PermissionsPolicies::allow_if_actor_super_admin(),
1527 PermissionsPolicies::allow_if_actor_super_admin(),
1528 PermissionsPolicies::allow_if_actor_super_admin(),
1529 );
1530
1531 let proto = permissions.to_proto().unwrap();
1532 assert!(proto.add_member_policy.is_some());
1533 assert!(proto.remove_member_policy.is_some());
1534
1535 let as_bytes = permissions.to_bytes().expect("serialization failed");
1536 let restored = PolicySet::from_bytes(as_bytes.as_slice()).expect("proto conversion failed");
1537 assert!(permissions.eq(&restored))
1539 }
1540
1541 #[xmtp_common::test]
1543 fn test_preconfigured_policy() {
1544 let group_permissions = GroupMutablePermissions::new(default_policy());
1545
1546 assert_eq!(
1547 group_permissions.preconfigured_policy().unwrap(),
1548 PreconfiguredPolicies::Default
1549 );
1550
1551 let group_group_permissions_creator_admin =
1552 GroupMutablePermissions::new(policy_admin_only());
1553
1554 assert_eq!(
1555 group_group_permissions_creator_admin
1556 .preconfigured_policy()
1557 .unwrap(),
1558 PreconfiguredPolicies::AdminsOnly
1559 );
1560 }
1561
1562 #[xmtp_common::test(unwrap_try = true)]
1563 fn dictionary_round_trip_preserves_presets_and_dm_permissions() {
1564 for policy_set in [
1565 PreconfiguredPolicies::Default.to_policy_set(),
1566 PreconfiguredPolicies::AdminsOnly.to_policy_set(),
1567 ] {
1568 let reconstructed = policy_set_from_dictionary(&dictionary_extensions(&policy_set))?;
1569 assert_eq!(reconstructed.policies, policy_set);
1570 }
1571
1572 let legacy_dm = PolicySet::new_dm();
1576 assert_eq!(
1577 legacy_dm.update_permissions_policy,
1578 PermissionsPolicies::deny()
1579 );
1580 let mut expected_dm = legacy_dm.clone();
1581 expected_dm.update_permissions_policy = PermissionsPolicies::allow_if_actor_super_admin();
1582 let reconstructed = policy_set_from_dictionary(&dictionary_extensions(&legacy_dm))?;
1583 assert_eq!(reconstructed.policies, expected_dm);
1584
1585 for preset in [
1586 PreconfiguredPolicies::Default,
1587 PreconfiguredPolicies::AdminsOnly,
1588 ] {
1589 let reconstructed =
1590 policy_set_from_dictionary(&dictionary_extensions(&preset.to_policy_set()))?;
1591 assert_eq!(
1592 PreconfiguredPolicies::from_policy_set(&reconstructed.policies)?,
1593 preset
1594 );
1595 }
1596 }
1597
1598 #[xmtp_common::test(unwrap_try = true)]
1599 fn dictionary_reader_denies_only_malformed_metadata_field() {
1600 let mut expected = PreconfiguredPolicies::Default.to_policy_set();
1601 let proto = expected.to_proto()?;
1602 let mut registry = synthesize_registry_from_policy_set(&proto)?;
1603 let malformed = MetadataPolicyProto {
1604 kind: Some(MetadataPolicyKindProto::AndCondition(
1605 MetadataAndConditionProto { policies: vec![] },
1606 )),
1607 };
1608 assert!(matches!(
1609 MetadataPolicies::try_from(malformed.clone()),
1610 Err(PolicyError::InvalidMetadataPolicy)
1611 ));
1612 let mut group_name = registry.get(&ComponentId::GROUP_NAME)?.unwrap();
1613 group_name.permissions.as_mut()?.update_policy = Some(malformed);
1614 registry.set(ComponentId::GROUP_NAME, group_name)?;
1615
1616 let mut dictionary = AppDataDictionary::new();
1617 dictionary.insert(
1618 ComponentId::COMPONENT_REGISTRY.as_u16(),
1619 registry.to_bytes()?,
1620 );
1621 let extensions = Extensions::from_vec(vec![Extension::AppDataDictionary(
1622 AppDataDictionaryExtension::new(dictionary),
1623 )])?;
1624
1625 expected.update_metadata_policy.insert(
1626 MetadataField::GroupName.to_string(),
1627 MetadataPolicies::deny(),
1628 );
1629 assert_eq!(policy_set_from_dictionary(&extensions)?.policies, expected);
1630 }
1631
1632 #[xmtp_common::test]
1634 fn test_preconfigured_policy_equality_new_metadata() {
1635 let mut metadata_policies_map = MetadataPolicies::default_map(MetadataPolicies::allow());
1636 metadata_policies_map.insert("new_metadata_field".to_string(), MetadataPolicies::allow());
1637 let policy_set_new_metadata_permission = PolicySet {
1638 add_member_policy: MembershipPolicies::allow(),
1639 remove_member_policy: MembershipPolicies::allow_if_actor_admin(),
1640 update_metadata_policy: metadata_policies_map,
1641 add_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1642 remove_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1643 update_permissions_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1644 };
1645
1646 assert!(is_policy_default(&policy_set_new_metadata_permission).unwrap());
1647
1648 let mut metadata_policies_map =
1649 MetadataPolicies::default_map(MetadataPolicies::allow_if_actor_admin());
1650 metadata_policies_map.insert(
1651 "new_metadata_field_2".to_string(),
1652 MetadataPolicies::allow_if_actor_admin(),
1653 );
1654 let policy_set_new_metadata_permission = PolicySet {
1655 add_member_policy: MembershipPolicies::allow_if_actor_admin(),
1656 remove_member_policy: MembershipPolicies::allow_if_actor_admin(),
1657 update_metadata_policy: metadata_policies_map,
1658 add_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1659 remove_admin_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1660 update_permissions_policy: PermissionsPolicies::allow_if_actor_super_admin(),
1661 };
1662
1663 assert!(is_policy_admin_only(&policy_set_new_metadata_permission).unwrap());
1664 }
1665
1666 #[xmtp_common::test]
1667 fn test_dm_group_permissions() {
1668 let permissions = PolicySet::new_dm();
1670
1671 const TARGET_INBOX_ID: &str = "example_target_dm_id";
1673
1674 let commit = build_membership_changes(
1676 Some(MemberType::Random),
1677 None,
1678 false,
1679 false,
1680 Some(TARGET_INBOX_ID.to_string()),
1681 );
1682 assert!(!permissions.evaluate_membership(&commit.validation_info()));
1683
1684 let commit = build_membership_changes(
1686 Some(MemberType::SameAsActor),
1687 None,
1688 false,
1689 false,
1690 Some(TARGET_INBOX_ID.to_string()),
1691 );
1692 assert!(!permissions.evaluate_membership(&commit.validation_info()));
1693
1694 let commit = build_membership_changes(
1696 Some(MemberType::DmTarget),
1697 None,
1698 false,
1699 false,
1700 Some(TARGET_INBOX_ID.to_string()),
1701 );
1702 assert!(permissions.evaluate_membership(&commit.validation_info()));
1703
1704 let commit = build_membership_changes(
1706 None,
1707 Some(MemberType::Random),
1708 false,
1709 false,
1710 Some(TARGET_INBOX_ID.to_string()),
1711 );
1712 assert!(!permissions.evaluate_membership(&commit.validation_info()));
1713 let commit = build_membership_changes(
1714 None,
1715 Some(MemberType::DmTarget),
1716 false,
1717 false,
1718 Some(TARGET_INBOX_ID.to_string()),
1719 );
1720 assert!(!permissions.evaluate_membership(&commit.validation_info()));
1721 let commit = build_membership_changes(
1722 None,
1723 Some(MemberType::SameAsActor),
1724 false,
1725 false,
1726 Some(TARGET_INBOX_ID.to_string()),
1727 );
1728 assert!(!permissions.evaluate_membership(&commit.validation_info()));
1729 }
1730}