1use std::collections::HashSet;
2
3use tls_codec::Deserialize;
4use xmtp_proto::xmtp::mls::message_contents::ComponentType;
5use xmtp_proto::xmtp::mls::message_contents::MetadataPolicy as MetadataPolicyProto;
6use xmtp_proto::xmtp::mls::message_contents::metadata_policy::{
7 Kind as MetadataPolicyKind, MetadataBasePolicy,
8};
9
10use super::component_id::ComponentId;
11use super::component_registry::{ComponentOp, ComponentRegistry, ComponentRegistryError};
12use super::component_source::component_type;
13use crate::inbox_id::InboxId;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct ActorAuthority {
24 pub is_admin: bool,
25 pub is_super_admin: bool,
26}
27
28#[derive(Debug, Clone, bon::Builder)]
60pub struct ComponentChange<'a> {
61 pub component_id: ComponentId,
62 pub op: ComponentOp,
63 pub actor: ActorAuthority,
64 pub old_value: Option<&'a [u8]>,
65 pub new_value: Option<&'a [u8]>,
66 pub proposer: Option<InboxId>,
68 #[builder(default)]
70 pub is_dm_participant: bool,
71 pub key: Option<&'a [u8]>,
74 pub membership: Option<&'a HashSet<InboxId>>,
78}
79
80#[derive(Debug, thiserror::Error)]
81pub enum ComponentPermissionError {
82 #[error("immutable component {0} does not allow {1}")]
83 ImmutableViolation(ComponentId, ComponentOp),
84 #[error("component {0} requires super admin")]
85 SuperAdminRequired(ComponentId),
86 #[error("no registry entry for component {0}")]
87 NoRegistryEntry(ComponentId),
88 #[error("missing permissions in metadata for component {0}")]
89 MissingPermissions(ComponentId),
90 #[error("missing policy field for component {0} op {1}")]
91 MissingPolicyField(ComponentId, ComponentOp),
92 #[error("invalid policy for component {0} op {1}")]
93 InvalidPolicy(ComponentId, ComponentOp),
94 #[error("permission denied for component {0} op {1}")]
95 PermissionDenied(ComponentId, ComponentOp),
96 #[error("registry error: {0}")]
97 RegistryError(#[from] ComponentRegistryError),
98}
99
100pub fn validate_component_write(
116 change: &ComponentChange<'_>,
117 registry: &ComponentRegistry,
118) -> Result<(), ComponentPermissionError> {
119 let component_id = change.component_id;
120 let op = change.op;
121
122 let first_write = change.old_value.is_none()
124 && match op {
125 ComponentOp::Insert => true,
126 ComponentOp::Update => change.key.is_none(),
127 ComponentOp::Delete => false,
128 };
129 if component_id.is_immutable() && !first_write {
130 return Err(ComponentPermissionError::ImmutableViolation(
131 component_id,
132 op,
133 ));
134 }
135
136 if component_id.is_hardcoded() {
141 return if change.actor.is_super_admin || is_dm_application_registry_change(change) {
142 Ok(())
143 } else {
144 Err(ComponentPermissionError::SuperAdminRequired(component_id))
145 };
146 }
147
148 let meta = registry
150 .get(&component_id)?
151 .ok_or(ComponentPermissionError::NoRegistryEntry(component_id))?;
152
153 let permissions = meta
158 .permissions
159 .ok_or(ComponentPermissionError::MissingPermissions(component_id))?;
160
161 let policy_proto: MetadataPolicyProto = match op {
162 ComponentOp::Insert => permissions.insert_policy,
163 ComponentOp::Update => permissions.update_policy,
164 ComponentOp::Delete => permissions.delete_policy,
165 }
166 .ok_or(ComponentPermissionError::MissingPolicyField(
167 component_id,
168 op,
169 ))?;
170
171 let component_type = component_type(component_id).map_or(meta.component_type, |ty| ty as i32);
173 let inbox_keyed = [
174 ComponentType::TlsMapInboxIdBytes as i32,
175 ComponentType::TlsMapInboxIdString as i32,
176 ]
177 .contains(&component_type);
178 let subject = PolicySubject {
179 actor: ActorAuthority {
180 is_super_admin: change.actor.is_super_admin
183 || (change.is_dm_participant && component_id.is_app_range()),
184 ..change.actor
185 },
186 op,
187 proposer: change.proposer,
188 inbox_key: change
189 .key
190 .filter(|_| inbox_keyed)
191 .and_then(|key| InboxId::tls_deserialize_exact(key).ok()),
192 membership: change.membership,
193 };
194 match evaluate_policy_proto(&policy_proto, &subject) {
195 PolicyOutcome::Allow => Ok(()),
196 PolicyOutcome::Deny => Err(ComponentPermissionError::PermissionDenied(component_id, op)),
197 PolicyOutcome::Invalid => Err(ComponentPermissionError::InvalidPolicy(component_id, op)),
198 }
199}
200
201fn is_dm_application_registry_change(change: &ComponentChange<'_>) -> bool {
205 change.is_dm_participant
206 && change.component_id == ComponentId::COMPONENT_REGISTRY
207 && change
208 .key
209 .and_then(|key| ComponentId::tls_deserialize_exact(key).ok())
210 .is_some_and(ComponentId::is_app_range)
211}
212
213struct PolicySubject<'a> {
215 actor: ActorAuthority,
217 op: ComponentOp,
218 proposer: Option<InboxId>,
219 inbox_key: Option<InboxId>,
221 membership: Option<&'a HashSet<InboxId>>,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226enum PolicyOutcome {
227 Allow,
229 Deny,
231 Invalid,
234}
235
236impl PolicyOutcome {
237 fn from_bool(allowed: bool) -> Self {
238 if allowed { Self::Allow } else { Self::Deny }
239 }
240}
241
242fn evaluate_policy_proto(
259 proto: &MetadataPolicyProto,
260 subject: &PolicySubject<'_>,
261) -> PolicyOutcome {
262 match &proto.kind {
263 Some(MetadataPolicyKind::Base(base)) => evaluate_base_policy(*base, subject),
264 Some(MetadataPolicyKind::AndCondition(and)) => {
265 if and.policies.is_empty() {
266 return PolicyOutcome::Invalid;
267 }
268 for inner in &and.policies {
269 match evaluate_policy_proto(inner, subject) {
270 PolicyOutcome::Allow => continue,
271 other => return other,
272 }
273 }
274 PolicyOutcome::Allow
275 }
276 Some(MetadataPolicyKind::AnyCondition(any)) => {
277 if any.policies.is_empty() {
278 return PolicyOutcome::Invalid;
279 }
280 for inner in &any.policies {
281 match evaluate_policy_proto(inner, subject) {
282 PolicyOutcome::Allow => return PolicyOutcome::Allow,
283 PolicyOutcome::Deny => {}
284 PolicyOutcome::Invalid => return PolicyOutcome::Invalid,
285 }
286 }
287 PolicyOutcome::Deny
288 }
289 None => PolicyOutcome::Invalid,
290 }
291}
292
293fn evaluate_base_policy(base: i32, subject: &PolicySubject<'_>) -> PolicyOutcome {
295 let actor = subject.actor;
296 let base = match MetadataBasePolicy::try_from(base) {
297 Ok(b) => b,
298 Err(_) => return PolicyOutcome::Invalid,
299 };
300 match base {
301 MetadataBasePolicy::Allow => PolicyOutcome::Allow,
302 MetadataBasePolicy::Deny => PolicyOutcome::Deny,
303 MetadataBasePolicy::AllowIfAdmin => {
304 PolicyOutcome::from_bool(actor.is_admin || actor.is_super_admin)
305 }
306 MetadataBasePolicy::AllowIfSuperAdmin => PolicyOutcome::from_bool(actor.is_super_admin),
307 MetadataBasePolicy::AllowIfSelfOrNonMember => {
308 let Some(key) = subject.inbox_key else {
311 return PolicyOutcome::Deny;
312 };
313 let is_self = subject.proposer == Some(key);
314 PolicyOutcome::from_bool(match subject.op {
315 ComponentOp::Insert | ComponentOp::Update => is_self,
316 ComponentOp::Delete => {
317 is_self || subject.membership.is_some_and(|m| !m.contains(&key))
318 }
319 })
320 }
321 MetadataBasePolicy::Unspecified => PolicyOutcome::Invalid,
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::app_data::component_permissions::component_permissions;
329 use crate::app_data::component_registry::new_component_metadata;
330 use tls_codec::Serialize;
331 use xmtp_proto::xmtp::mls::message_contents::{
332 ComponentType, MetadataPolicy as MetadataPolicyProto,
333 metadata_policy::{Kind as MetadataPolicyKind, MetadataBasePolicy},
334 };
335
336 fn make_policy(base: MetadataBasePolicy) -> MetadataPolicyProto {
337 MetadataPolicyProto {
338 kind: Some(MetadataPolicyKind::Base(base as i32)),
339 }
340 }
341
342 fn allow() -> MetadataPolicyProto {
343 make_policy(MetadataBasePolicy::Allow)
344 }
345
346 fn deny() -> MetadataPolicyProto {
347 make_policy(MetadataBasePolicy::Deny)
348 }
349
350 fn admin_only() -> MetadataPolicyProto {
351 make_policy(MetadataBasePolicy::AllowIfAdmin)
352 }
353
354 fn super_admin_only() -> MetadataPolicyProto {
355 make_policy(MetadataBasePolicy::AllowIfSuperAdmin)
356 }
357
358 fn make_actor(is_admin: bool, is_super_admin: bool) -> ActorAuthority {
359 ActorAuthority {
360 is_admin,
361 is_super_admin,
362 }
363 }
364
365 fn change<'a>(id: ComponentId, op: ComponentOp, actor: ActorAuthority) -> ComponentChange<'a> {
369 ComponentChange::builder()
370 .component_id(id)
371 .op(op)
372 .actor(actor)
373 .build()
374 }
375
376 fn member() -> ActorAuthority {
377 make_actor(false, false)
378 }
379
380 fn admin() -> ActorAuthority {
381 make_actor(true, false)
382 }
383
384 fn super_admin() -> ActorAuthority {
385 make_actor(true, true)
386 }
387
388 fn setup_registry_with(
389 id: ComponentId,
390 insert: MetadataPolicyProto,
391 update: MetadataPolicyProto,
392 delete: MetadataPolicyProto,
393 ) -> ComponentRegistry {
394 typed_registry(id, ComponentType::Bytes, insert, update, delete)
395 }
396
397 fn typed_registry(
398 id: ComponentId,
399 ty: ComponentType,
400 insert: MetadataPolicyProto,
401 update: MetadataPolicyProto,
402 delete: MetadataPolicyProto,
403 ) -> ComponentRegistry {
404 let mut reg = ComponentRegistry::new();
405 reg.set(
406 id,
407 new_component_metadata(
408 component_permissions()
409 .insert(insert)
410 .update(update)
411 .delete(delete)
412 .call(),
413 ty,
414 ),
415 )
416 .unwrap();
417 reg
418 }
419
420 #[xmtp_common::test]
423 fn test_immutable_insert_allowed() {
425 let id = ComponentId::CONVERSATION_TYPE;
426 let reg = setup_registry_with(id, allow(), deny(), deny());
427 let actor = super_admin();
428 let result = validate_component_write(&change(id, ComponentOp::Insert, actor), ®);
429 assert!(result.is_ok());
430 }
431
432 #[xmtp_common::test]
435 fn test_immutable_update_rejected() {
437 let id = ComponentId::CONVERSATION_TYPE;
438 let reg = setup_registry_with(id, allow(), allow(), allow());
439 for op in [ComponentOp::Insert, ComponentOp::Update] {
440 let written = ComponentChange::builder()
441 .component_id(id)
442 .op(op)
443 .actor(super_admin())
444 .old_value(b"dm")
445 .build();
446 assert!(matches!(
447 validate_component_write(&written, ®),
448 Err(ComponentPermissionError::ImmutableViolation(_, got)) if got == op
449 ));
450 }
451 }
452
453 #[xmtp_common::test]
456 fn test_immutable_first_scalar_write_uses_policy() {
458 let id = ComponentId::new(0xFD00);
459 let first = |actor| change(id, ComponentOp::Update, actor);
460 let reg = setup_registry_with(id, deny(), super_admin_only(), deny());
461 assert!(validate_component_write(&first(super_admin()), ®).is_ok());
462 assert!(matches!(
463 validate_component_write(&first(admin()), ®),
464 Err(ComponentPermissionError::PermissionDenied(
465 _,
466 ComponentOp::Update
467 ))
468 ));
469 }
470
471 #[xmtp_common::test(unwrap_try = true)]
475 fn test_immutable_absent_map_rejects_element_update() {
476 let id = ComponentId::new(0xFD01);
477 let reg = typed_registry(
478 id,
479 ComponentType::TlsMapBytesBytes,
480 allow(),
481 allow(),
482 allow(),
483 );
484 let element = |op| {
485 ComponentChange::builder()
486 .component_id(id)
487 .op(op)
488 .actor(super_admin())
489 .key(b"k")
490 .build()
491 };
492 validate_component_write(&element(ComponentOp::Insert), ®)?;
493 assert!(matches!(
494 validate_component_write(&element(ComponentOp::Update), ®),
495 Err(ComponentPermissionError::ImmutableViolation(
496 _,
497 ComponentOp::Update
498 ))
499 ));
500 }
501
502 #[xmtp_common::test]
503 fn test_immutable_delete_rejected() {
505 let id = ComponentId::CONVERSATION_TYPE;
506 let reg = setup_registry_with(id, allow(), allow(), allow());
507 let actor = super_admin();
508 let result = validate_component_write(&change(id, ComponentOp::Delete, actor), ®);
509 assert!(matches!(
510 result,
511 Err(ComponentPermissionError::ImmutableViolation(
512 _,
513 ComponentOp::Delete
514 ))
515 ));
516 }
517
518 #[xmtp_common::test]
521 fn test_registry_super_admin_allowed() {
522 let reg = ComponentRegistry::new();
523 let actor = super_admin();
524 let result = validate_component_write(
525 &change(ComponentId::COMPONENT_REGISTRY, ComponentOp::Update, actor),
526 ®,
527 );
528 assert!(result.is_ok());
529 }
530
531 #[xmtp_common::test]
532 fn test_registry_admin_rejected() {
534 let reg = ComponentRegistry::new();
535 let actor = admin();
536 let result = validate_component_write(
537 &change(ComponentId::COMPONENT_REGISTRY, ComponentOp::Update, actor),
538 ®,
539 );
540 assert!(matches!(
541 result,
542 Err(ComponentPermissionError::SuperAdminRequired(_))
543 ));
544 }
545
546 #[xmtp_common::test]
547 fn test_registry_member_rejected() {
549 let reg = ComponentRegistry::new();
550 let actor = member();
551 let result = validate_component_write(
552 &change(ComponentId::COMPONENT_REGISTRY, ComponentOp::Update, actor),
553 ®,
554 );
555 assert!(matches!(
556 result,
557 Err(ComponentPermissionError::SuperAdminRequired(_))
558 ));
559 }
560
561 #[xmtp_common::test]
562 fn test_super_admin_list_super_admin_allowed() {
563 let reg = ComponentRegistry::new();
564 let actor = super_admin();
565 let result = validate_component_write(
566 &change(ComponentId::SUPER_ADMIN_LIST, ComponentOp::Insert, actor),
567 ®,
568 );
569 assert!(result.is_ok());
570 }
571
572 #[xmtp_common::test]
573 fn test_super_admin_list_admin_rejected() {
575 let reg = ComponentRegistry::new();
576 let actor = admin();
577 let result = validate_component_write(
578 &change(ComponentId::SUPER_ADMIN_LIST, ComponentOp::Insert, actor),
579 ®,
580 );
581 assert!(matches!(
582 result,
583 Err(ComponentPermissionError::SuperAdminRequired(_))
584 ));
585 }
586
587 #[xmtp_common::test]
588 fn test_admin_list_with_admin_policy_admin_allowed() {
589 let reg = setup_registry_with(
590 ComponentId::ADMIN_LIST,
591 admin_only(),
592 admin_only(),
593 admin_only(),
594 );
595 let actor = admin();
596 let result = validate_component_write(
597 &change(ComponentId::ADMIN_LIST, ComponentOp::Insert, actor),
598 ®,
599 );
600 assert!(result.is_ok());
601 }
602
603 #[xmtp_common::test]
604 fn test_admin_list_with_admin_policy_member_rejected() {
605 let reg = setup_registry_with(
606 ComponentId::ADMIN_LIST,
607 admin_only(),
608 admin_only(),
609 admin_only(),
610 );
611 let actor = member();
612 let result = validate_component_write(
613 &change(ComponentId::ADMIN_LIST, ComponentOp::Insert, actor),
614 ®,
615 );
616 assert!(matches!(
617 result,
618 Err(ComponentPermissionError::PermissionDenied(_, _))
619 ));
620 }
621
622 #[xmtp_common::test]
623 fn test_admin_list_with_super_admin_policy() {
624 let reg = setup_registry_with(
625 ComponentId::ADMIN_LIST,
626 super_admin_only(),
627 super_admin_only(),
628 super_admin_only(),
629 );
630 let admin_actor = admin();
631 let super_admin_actor = super_admin();
632 assert!(
634 validate_component_write(
635 &change(ComponentId::ADMIN_LIST, ComponentOp::Insert, admin_actor),
636 ®,
637 )
638 .is_err()
639 );
640 assert!(
642 validate_component_write(
643 &change(
644 ComponentId::ADMIN_LIST,
645 ComponentOp::Insert,
646 super_admin_actor,
647 ),
648 ®,
649 )
650 .is_ok()
651 );
652 }
653
654 #[xmtp_common::test]
657 fn test_deny_by_default_no_entry() {
659 let reg = ComponentRegistry::new();
660 let actor = super_admin();
661 let result = validate_component_write(
662 &change(ComponentId::GROUP_NAME, ComponentOp::Insert, actor),
663 ®,
664 );
665 assert!(matches!(
666 result,
667 Err(ComponentPermissionError::NoRegistryEntry(_))
668 ));
669 }
670
671 #[xmtp_common::test]
672 fn test_insert_allow_policy() {
674 let reg = setup_registry_with(ComponentId::GROUP_NAME, allow(), deny(), deny());
675 let actor = member();
676 let result = validate_component_write(
677 &change(ComponentId::GROUP_NAME, ComponentOp::Insert, actor),
678 ®,
679 );
680 assert!(result.is_ok());
681 }
682
683 #[xmtp_common::test]
684 fn test_update_admin_only_policy_admin_passes() {
686 let reg = setup_registry_with(ComponentId::GROUP_NAME, allow(), admin_only(), deny());
687 let actor = admin();
688 let result = validate_component_write(
689 &change(ComponentId::GROUP_NAME, ComponentOp::Update, actor),
690 ®,
691 );
692 assert!(result.is_ok());
693 }
694
695 #[xmtp_common::test]
696 fn test_update_admin_only_policy_member_fails() {
698 let reg = setup_registry_with(ComponentId::GROUP_NAME, allow(), admin_only(), deny());
699 let actor = member();
700 let result = validate_component_write(
701 &change(ComponentId::GROUP_NAME, ComponentOp::Update, actor),
702 ®,
703 );
704 assert!(matches!(
705 result,
706 Err(ComponentPermissionError::PermissionDenied(
707 _,
708 ComponentOp::Update
709 ))
710 ));
711 }
712
713 #[xmtp_common::test]
714 fn test_delete_deny_policy() {
716 let reg = setup_registry_with(ComponentId::GROUP_NAME, allow(), allow(), deny());
717 let actor = super_admin();
718 let result = validate_component_write(
719 &change(ComponentId::GROUP_NAME, ComponentOp::Delete, actor),
720 ®,
721 );
722 assert!(matches!(
723 result,
724 Err(ComponentPermissionError::PermissionDenied(
725 _,
726 ComponentOp::Delete
727 ))
728 ));
729 }
730
731 #[xmtp_common::test]
732 fn test_delete_super_admin_only_policy() {
734 let reg = setup_registry_with(
735 ComponentId::GROUP_NAME,
736 allow(),
737 allow(),
738 super_admin_only(),
739 );
740 let actor = super_admin();
741 let result = validate_component_write(
742 &change(ComponentId::GROUP_NAME, ComponentOp::Delete, actor),
743 ®,
744 );
745 assert!(result.is_ok());
746 }
747
748 #[xmtp_common::test]
749 fn test_different_insert_vs_update_permissions() {
751 let reg = setup_registry_with(
753 ComponentId::GROUP_MEMBERSHIP,
754 admin_only(),
755 allow(),
756 admin_only(),
757 );
758 let member_actor = member();
759 let admin_actor = admin();
760
761 assert!(
763 validate_component_write(
764 &change(
765 ComponentId::GROUP_MEMBERSHIP,
766 ComponentOp::Update,
767 member_actor,
768 ),
769 ®,
770 )
771 .is_ok()
772 );
773
774 assert!(
776 validate_component_write(
777 &change(
778 ComponentId::GROUP_MEMBERSHIP,
779 ComponentOp::Insert,
780 member_actor,
781 ),
782 ®,
783 )
784 .is_err()
785 );
786
787 assert!(
789 validate_component_write(
790 &change(
791 ComponentId::GROUP_MEMBERSHIP,
792 ComponentOp::Insert,
793 admin_actor,
794 ),
795 ®,
796 )
797 .is_ok()
798 );
799 }
800
801 #[xmtp_common::test]
802 fn test_app_range_component() {
804 let app_id = ComponentId::new(0xC100);
805 let reg = setup_registry_with(app_id, allow(), allow(), deny());
806 let actor = member();
807 let result = validate_component_write(&change(app_id, ComponentOp::Insert, actor), ®);
808 assert!(result.is_ok());
809 }
810
811 const PROFILE: ComponentId = ComponentId::new(0xC100);
814
815 fn inbox(byte: u8) -> InboxId {
816 InboxId::from_bytes([byte; 32])
817 }
818
819 fn self_owned() -> MetadataPolicyProto {
820 make_policy(MetadataBasePolicy::AllowIfSelfOrNonMember)
821 }
822
823 fn self_owned_registry(id: ComponentId, ty: ComponentType) -> ComponentRegistry {
824 typed_registry(id, ty, self_owned(), self_owned(), self_owned())
825 }
826
827 fn write_key(
830 reg: &ComponentRegistry,
831 id: ComponentId,
832 op: ComponentOp,
833 proposer: InboxId,
834 key: Option<InboxId>,
835 membership: &HashSet<InboxId>,
836 ) -> Result<(), ComponentPermissionError> {
837 let key = key.map(|key| key.tls_serialize_detached().unwrap());
838 let change = ComponentChange::builder()
839 .component_id(id)
840 .op(op)
841 .actor(member())
842 .proposer(proposer)
843 .maybe_key(key.as_deref())
844 .membership(membership)
845 .build();
846 validate_component_write(&change, reg)
847 }
848
849 #[xmtp_common::test(unwrap_try = true)]
852 fn self_owned_writes_only_own_key() {
854 let (alice, bob) = (inbox(1), inbox(2));
855 let members = HashSet::from([alice, bob]);
856 for ty in [
857 ComponentType::TlsMapInboxIdBytes,
858 ComponentType::TlsMapInboxIdString,
859 ] {
860 let reg = self_owned_registry(PROFILE, ty);
861 for op in [
862 ComponentOp::Insert,
863 ComponentOp::Update,
864 ComponentOp::Delete,
865 ] {
866 write_key(®, PROFILE, op, alice, Some(alice), &members)?;
867 assert!(matches!(
868 write_key(®, PROFILE, op, alice, Some(bob), &members),
869 Err(ComponentPermissionError::PermissionDenied(_, got)) if got == op
870 ));
871 }
872 }
873 }
874
875 #[xmtp_common::test(unwrap_try = true)]
879 fn self_owned_deletes_non_member_key() {
881 let (alice, gone) = (inbox(1), inbox(3));
882 let members = HashSet::from([alice]);
883 let reg = self_owned_registry(PROFILE, ComponentType::TlsMapInboxIdString);
884 write_key(
885 ®,
886 PROFILE,
887 ComponentOp::Delete,
888 alice,
889 Some(gone),
890 &members,
891 )?;
892 for op in [ComponentOp::Insert, ComponentOp::Update] {
893 assert!(write_key(®, PROFILE, op, alice, Some(gone), &members).is_err());
894 }
895 }
896
897 #[xmtp_common::test(unwrap_try = true)]
901 fn self_owned_fails_closed_without_context() {
903 let (alice, gone) = (inbox(1), inbox(3));
904 let members = HashSet::from([alice]);
905 let map = self_owned_registry(PROFILE, ComponentType::TlsMapInboxIdString);
906 assert!(write_key(&map, PROFILE, ComponentOp::Delete, alice, None, &members).is_err());
907
908 for ty in [
909 ComponentType::TlsMapBytesBytes,
910 ComponentType::TlsSetInboxId,
911 ] {
912 let reg = self_owned_registry(PROFILE, ty);
913 assert!(
914 write_key(
915 ®,
916 PROFILE,
917 ComponentOp::Insert,
918 alice,
919 Some(alice),
920 &members
921 )
922 .is_err()
923 );
924 }
925
926 let forged =
928 self_owned_registry(ComponentId::GROUP_NAME, ComponentType::TlsMapInboxIdBytes);
929 assert!(
930 write_key(
931 &forged,
932 ComponentId::GROUP_NAME,
933 ComponentOp::Update,
934 alice,
935 Some(alice),
936 &members
937 )
938 .is_err()
939 );
940
941 let short_key = ComponentChange::builder()
942 .component_id(PROFILE)
943 .op(ComponentOp::Insert)
944 .actor(member())
945 .proposer(alice)
946 .key(&[0u8; 4])
947 .membership(&members)
948 .build();
949 assert!(validate_component_write(&short_key, &map).is_err());
950
951 let own_key = alice.tls_serialize_detached()?;
954 let no_proposer = ComponentChange::builder()
955 .component_id(PROFILE)
956 .op(ComponentOp::Delete)
957 .actor(super_admin())
958 .key(&own_key)
959 .membership(&members)
960 .build();
961 assert!(validate_component_write(&no_proposer, &map).is_err());
962 let gone_key = gone.tls_serialize_detached()?;
963 let no_membership = ComponentChange {
964 proposer: Some(alice),
965 key: Some(&gone_key),
966 membership: None,
967 ..no_proposer
968 };
969 assert!(validate_component_write(&no_membership, &map).is_err());
970 }
971
972 fn dm_change<'a>(
975 id: ComponentId,
976 op: ComponentOp,
977 key: Option<&'a [u8]>,
978 ) -> ComponentChange<'a> {
979 ComponentChange::builder()
980 .component_id(id)
981 .op(op)
982 .actor(member())
983 .is_dm_participant(true)
984 .maybe_key(key)
985 .build()
986 }
987
988 #[xmtp_common::test(unwrap_try = true)]
992 fn dm_participant_changes_only_application_registry_entries() {
994 let reg = ComponentRegistry::new();
995 let registry = ComponentId::COMPONENT_REGISTRY;
996 for id in [0xC000, 0xFEFF] {
997 let key = ComponentId::new(id).tls_serialize_detached()?;
998 for op in [
999 ComponentOp::Insert,
1000 ComponentOp::Update,
1001 ComponentOp::Delete,
1002 ] {
1003 validate_component_write(&dm_change(registry, op, Some(&key)), ®)?;
1004 }
1005 }
1006 for id in [
1007 ComponentId::GROUP_NAME,
1008 ComponentId::new(0xBFFF),
1009 ComponentId::new(0xFF00),
1010 ] {
1011 let key = id.tls_serialize_detached()?;
1012 assert!(matches!(
1013 validate_component_write(
1014 &dm_change(registry, ComponentOp::Insert, Some(&key)),
1015 ®
1016 ),
1017 Err(ComponentPermissionError::SuperAdminRequired(_))
1018 ));
1019 }
1020 assert!(
1021 validate_component_write(&dm_change(registry, ComponentOp::Delete, None), ®)
1022 .is_err()
1023 );
1024 let app_key = ComponentId::new(0xC000).tls_serialize_detached()?;
1025 assert!(
1026 validate_component_write(
1027 &dm_change(
1028 ComponentId::SUPER_ADMIN_LIST,
1029 ComponentOp::Insert,
1030 Some(&app_key)
1031 ),
1032 ®
1033 )
1034 .is_err()
1035 );
1036 let outsider = ComponentChange {
1037 is_dm_participant: false,
1038 ..dm_change(registry, ComponentOp::Insert, Some(&app_key))
1039 };
1040 assert!(validate_component_write(&outsider, ®).is_err());
1041 }
1042
1043 #[xmtp_common::test(unwrap_try = true)]
1046 fn dm_participant_is_super_admin_for_application_components_only() {
1048 for id in [ComponentId::new(0xC000), ComponentId::new(0xFEFF)] {
1049 let reg = setup_registry_with(id, super_admin_only(), super_admin_only(), deny());
1050 validate_component_write(&dm_change(id, ComponentOp::Update, None), ®)?;
1051 assert!(
1052 validate_component_write(&dm_change(id, ComponentOp::Delete, None), ®).is_err()
1053 );
1054 }
1055 for id in [
1056 ComponentId::GROUP_NAME,
1057 ComponentId::GROUP_MEMBERSHIP,
1058 ComponentId::new(0xBE00),
1059 ] {
1060 let reg = setup_registry_with(id, super_admin_only(), super_admin_only(), deny());
1061 assert!(matches!(
1062 validate_component_write(&dm_change(id, ComponentOp::Insert, None), ®),
1063 Err(ComponentPermissionError::PermissionDenied(_, _))
1064 ));
1065 }
1066 }
1067}