1use super::*;
4
5pub(crate) fn build_protected_metadata_extension(
6 creator_inbox_id: &str,
7 conversation_type: ConversationType,
8 oneshot_message: Option<OneshotMessage>,
9) -> Result<Extension, MetadataPermissionsError> {
10 assert!(conversation_type != ConversationType::Dm);
11 let metadata = GroupMetadata::new(
12 conversation_type,
13 creator_inbox_id.to_string(),
14 None,
15 oneshot_message,
16 );
17 let protected_metadata = Metadata::new(metadata.try_into()?);
18
19 Ok(Extension::ImmutableMetadata(protected_metadata))
20}
21
22pub(in crate::groups) fn build_dm_protected_metadata_extension(
23 creator_inbox_id: &str,
24 dm_inbox_id: InboxId,
25) -> Result<Extension, GroupError> {
26 let dm_members = Some(DmMembers {
27 member_one_inbox_id: creator_inbox_id.to_string(),
28 member_two_inbox_id: dm_inbox_id,
29 });
30
31 let metadata = GroupMetadata::new(
32 ConversationType::Dm,
33 creator_inbox_id.to_string(),
34 dm_members,
35 None,
36 );
37 let protected_metadata = Metadata::new(
38 metadata
39 .try_into()
40 .map_err(MetadataPermissionsError::from)?,
41 );
42
43 Ok(Extension::ImmutableMetadata(protected_metadata))
44}
45
46pub(crate) fn build_mutable_permissions_extension(
47 policies: PolicySet,
48) -> Result<Extension, MetadataPermissionsError> {
49 let permissions: Vec<u8> = GroupMutablePermissions::new(policies).try_into()?;
50 let unknown_gc_extension = UnknownExtension(permissions);
51
52 Ok(Extension::Unknown(
53 GROUP_PERMISSIONS_EXTENSION_ID,
54 unknown_gc_extension,
55 ))
56}
57
58pub fn build_mutable_metadata_extension_default(
59 creator_inbox_id: &str,
60 opts: GroupMetadataOptions,
61 commit_log_enabled: bool,
62) -> Result<Extension, GroupError> {
63 let mut commit_log_signer = None;
64 if commit_log_enabled {
66 commit_log_signer = Some(xmtp_cryptography::rand::rand_secret::<ED25519_KEY_LENGTH>());
68 }
69 let mutable_metadata: Vec<u8> =
70 GroupMutableMetadata::new_default(creator_inbox_id.to_string(), commit_log_signer, opts)
71 .try_into()
72 .map_err(MetadataPermissionsError::from)?;
73 let unknown_gc_extension = UnknownExtension(mutable_metadata);
74
75 Ok(Extension::Unknown(
76 MUTABLE_METADATA_EXTENSION_ID,
77 unknown_gc_extension,
78 ))
79}
80
81pub fn build_dm_mutable_metadata_extension_default(
82 creator_inbox_id: &str,
83 dm_target_inbox_id: &str,
84 opts: DMMetadataOptions,
85 commit_log_enabled: bool,
86) -> Result<Extension, MetadataPermissionsError> {
87 let mut commit_log_signer = None;
88 if commit_log_enabled {
90 commit_log_signer = Some(xmtp_cryptography::rand::rand_secret::<ED25519_KEY_LENGTH>());
91 }
92 let mutable_metadata: Vec<u8> = GroupMutableMetadata::new_dm_default(
93 creator_inbox_id.to_string(),
94 dm_target_inbox_id,
95 commit_log_signer,
96 opts,
97 )
98 .try_into()?;
99 let unknown_gc_extension = UnknownExtension(mutable_metadata);
100
101 Ok(Extension::Unknown(
102 MUTABLE_METADATA_EXTENSION_ID,
103 unknown_gc_extension,
104 ))
105}
106
107#[tracing::instrument(level = "trace", skip_all)]
108pub fn build_extensions_for_metadata_update(
109 group: &OpenMlsGroup,
110 field_name: String,
111 field_value: String,
112) -> Result<Extensions<GroupContext>, MetadataPermissionsError> {
113 let existing_metadata: GroupMutableMetadata = group.try_into()?;
114 let mut attributes = existing_metadata.attributes.clone();
115 attributes.insert(field_name, field_value);
116 let new_mutable_metadata: Vec<u8> = GroupMutableMetadata::new(
117 attributes,
118 existing_metadata.admin_list,
119 existing_metadata.super_admin_list,
120 )
121 .try_into()?;
122 let unknown_gc_extension = UnknownExtension(new_mutable_metadata);
123 let extension = Extension::Unknown(MUTABLE_METADATA_EXTENSION_ID, unknown_gc_extension);
124 let mut extensions = group.extensions().clone();
125 extensions.add_or_replace(extension)?;
126 Ok(extensions)
127}
128
129#[tracing::instrument(level = "trace", skip_all)]
130pub fn build_extensions_for_permissions_update(
131 group: &OpenMlsGroup,
132 update_permissions_intent: UpdatePermissionIntentData,
133) -> Result<Extensions<GroupContext>, MetadataPermissionsError> {
134 let existing_permissions: GroupMutablePermissions = group.try_into()?;
135 let existing_policy_set = existing_permissions.policies.clone();
136 let new_policy_set = match update_permissions_intent.update_type {
137 PermissionUpdateType::AddMember => PolicySet::new(
138 update_permissions_intent.policy_option.into(),
139 existing_policy_set.remove_member_policy,
140 existing_policy_set.update_metadata_policy,
141 existing_policy_set.add_admin_policy,
142 existing_policy_set.remove_admin_policy,
143 existing_policy_set.update_permissions_policy,
144 ),
145 PermissionUpdateType::RemoveMember => PolicySet::new(
146 existing_policy_set.add_member_policy,
147 update_permissions_intent.policy_option.into(),
148 existing_policy_set.update_metadata_policy,
149 existing_policy_set.add_admin_policy,
150 existing_policy_set.remove_admin_policy,
151 existing_policy_set.update_permissions_policy,
152 ),
153 PermissionUpdateType::AddAdmin => PolicySet::new(
154 existing_policy_set.add_member_policy,
155 existing_policy_set.remove_member_policy,
156 existing_policy_set.update_metadata_policy,
157 update_permissions_intent.policy_option.into(),
158 existing_policy_set.remove_admin_policy,
159 existing_policy_set.update_permissions_policy,
160 ),
161 PermissionUpdateType::RemoveAdmin => PolicySet::new(
162 existing_policy_set.add_member_policy,
163 existing_policy_set.remove_member_policy,
164 existing_policy_set.update_metadata_policy,
165 existing_policy_set.add_admin_policy,
166 update_permissions_intent.policy_option.into(),
167 existing_policy_set.update_permissions_policy,
168 ),
169 PermissionUpdateType::UpdateMetadata => {
170 let mut metadata_policy = existing_policy_set.update_metadata_policy.clone();
171 metadata_policy.insert(
172 update_permissions_intent
173 .metadata_field_name
174 .ok_or(GroupMutableMetadataError::MissingMetadataField)?,
175 update_permissions_intent.policy_option.into(),
176 );
177 PolicySet::new(
178 existing_policy_set.add_member_policy,
179 existing_policy_set.remove_member_policy,
180 metadata_policy,
181 existing_policy_set.add_admin_policy,
182 existing_policy_set.remove_admin_policy,
183 existing_policy_set.update_permissions_policy,
184 )
185 }
186 };
187 let new_group_permissions: Vec<u8> = GroupMutablePermissions::new(new_policy_set).try_into()?;
188 let unknown_gc_extension = UnknownExtension(new_group_permissions);
189 let extension = Extension::Unknown(GROUP_PERMISSIONS_EXTENSION_ID, unknown_gc_extension);
190 let mut extensions = group.extensions().clone();
191 extensions.add_or_replace(extension)?;
192 Ok(extensions)
193}
194
195#[tracing::instrument(level = "trace", skip_all)]
196pub fn build_extensions_for_admin_lists_update(
197 group: &OpenMlsGroup,
198 admin_lists_update: UpdateAdminListIntentData,
199) -> Result<Extensions<GroupContext>, MetadataPermissionsError> {
200 let existing_metadata: GroupMutableMetadata = group.try_into()?;
201 let attributes = existing_metadata.attributes.clone();
202 let mut admin_list = existing_metadata.admin_list;
203 let mut super_admin_list = existing_metadata.super_admin_list;
204 match admin_lists_update.action_type {
205 AdminListActionType::Add => {
206 if !admin_list.contains(&admin_lists_update.inbox_id) {
207 admin_list.push(admin_lists_update.inbox_id);
208 }
209 }
210 AdminListActionType::Remove => admin_list.retain(|x| x != &admin_lists_update.inbox_id),
211 AdminListActionType::AddSuper => {
212 if !super_admin_list.contains(&admin_lists_update.inbox_id) {
213 super_admin_list.push(admin_lists_update.inbox_id);
214 }
215 }
216 AdminListActionType::RemoveSuper => {
217 super_admin_list.retain(|x| x != &admin_lists_update.inbox_id)
218 }
219 }
220 let new_mutable_metadata: Vec<u8> =
221 GroupMutableMetadata::new(attributes, admin_list, super_admin_list).try_into()?;
222 let unknown_gc_extension = UnknownExtension(new_mutable_metadata);
223 let extension = Extension::Unknown(MUTABLE_METADATA_EXTENSION_ID, unknown_gc_extension);
224 let mut extensions = group.extensions().clone();
225 extensions.add_or_replace(extension)?;
226 Ok(extensions)
227}
228
229pub fn build_starting_group_membership_extension(inbox_id: &str, sequence_id: u64) -> Extension {
230 let mut group_membership = GroupMembership::new();
231 group_membership.add(inbox_id.to_string(), sequence_id);
232 build_group_membership_extension(&group_membership)
233}
234
235pub fn build_group_membership_extension(group_membership: &GroupMembership) -> Extension {
236 let unknown_gc_extension = UnknownExtension(group_membership.into());
237
238 Extension::Unknown(GROUP_MEMBERSHIP_EXTENSION_ID, unknown_gc_extension)
239}
240
241pub fn check_proposals_enabled(extensions: &Extensions<GroupContext>) -> bool {
252 crate::groups::app_data::is_migrated_extensions(extensions)
253}
254
255pub fn update_required_capabilities_for_proposals(
261 extensions: &mut Extensions<GroupContext>,
262 add: bool,
263) -> Result<(), GroupError> {
264 let app_data_ext_type = ExtensionType::AppDataDictionary;
265
266 if let Some(required_caps) = extensions.required_capabilities() {
267 let mut ext_types: Vec<ExtensionType> = required_caps.extension_types().to_vec();
268 let has_it = ext_types.contains(&app_data_ext_type);
269
270 if add && !has_it {
271 ext_types.push(app_data_ext_type);
272 } else if !add && has_it {
273 ext_types.retain(|t| *t != app_data_ext_type);
274 } else {
275 return Ok(()); }
277
278 let new_required = Extension::RequiredCapabilities(RequiredCapabilitiesExtension::new(
279 &ext_types,
280 required_caps.proposal_types(),
281 required_caps.credential_types(),
282 ));
283 extensions.add_or_replace(new_required)?;
284 }
285
286 Ok(())
287}
288
289pub fn update_required_capabilities_for_bootstrap(
302 extensions: &mut Extensions<GroupContext>,
303) -> Result<(), GroupError> {
304 let app_data_ext_type = ExtensionType::AppDataDictionary;
305 let to_remove: &[ExtensionType] = &[
306 ExtensionType::Unknown(MUTABLE_METADATA_EXTENSION_ID),
307 ExtensionType::Unknown(GROUP_PERMISSIONS_EXTENSION_ID),
308 ExtensionType::Unknown(GROUP_MEMBERSHIP_EXTENSION_ID),
309 ExtensionType::ImmutableMetadata,
310 ];
311
312 if let Some(required_caps) = extensions.required_capabilities() {
313 let mut ext_types: Vec<ExtensionType> = required_caps
314 .extension_types()
315 .iter()
316 .copied()
317 .filter(|t| !to_remove.contains(t))
318 .collect();
319 if !ext_types.contains(&app_data_ext_type) {
320 ext_types.push(app_data_ext_type);
321 }
322 let new_required = Extension::RequiredCapabilities(RequiredCapabilitiesExtension::new(
323 &ext_types,
324 required_caps.proposal_types(),
325 required_caps.credential_types(),
326 ));
327 extensions.add_or_replace(new_required)?;
328 }
329 Ok(())
330}
331
332#[tracing::instrument(level = "trace", skip_all)]
336pub fn build_extensions_for_membership_update(
337 group: &OpenMlsGroup,
338 new_membership: &GroupMembership,
339) -> Result<Extensions<GroupContext>, GroupError> {
340 let mut extensions: Extensions<GroupContext> = group.extensions().clone();
341 extensions.add_or_replace(build_group_membership_extension(new_membership))?;
342 Ok(extensions)
343}
344
345pub(crate) fn build_group_config(
346 protected_metadata_extension: Extension,
347 mutable_metadata_extension: Extension,
348 group_membership_extension: Extension,
349 mutable_permission_extension: Extension,
350) -> Result<MlsGroupCreateConfig, GroupError> {
351 let required_extension_types = &[
353 ExtensionType::Unknown(GROUP_MEMBERSHIP_EXTENSION_ID),
354 ExtensionType::Unknown(MUTABLE_METADATA_EXTENSION_ID),
355 ExtensionType::Unknown(GROUP_PERMISSIONS_EXTENSION_ID),
356 ExtensionType::ImmutableMetadata,
357 ExtensionType::LastResort,
358 ExtensionType::ApplicationId,
359 ];
360
361 let mut creator_capability_extensions = required_extension_types.to_vec();
366 creator_capability_extensions.push(ExtensionType::Unknown(
367 WELCOME_WRAPPER_ENCRYPTION_EXTENSION_ID,
368 ));
369 creator_capability_extensions.push(ExtensionType::Unknown(
370 WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID,
371 ));
372 creator_capability_extensions.push(ExtensionType::AppDataDictionary);
378
379 let required_proposal_types = &[ProposalType::GroupContextExtensions];
380
381 let creator_capability_proposals = &[
391 ProposalType::GroupContextExtensions,
392 ProposalType::AppDataUpdate,
393 ];
394
395 let capabilities = Capabilities::new(
396 None,
397 None,
398 Some(&creator_capability_extensions),
399 Some(creator_capability_proposals),
400 None,
401 );
402 let credentials = &[CredentialType::Basic];
403
404 let required_capabilities =
405 Extension::RequiredCapabilities(RequiredCapabilitiesExtension::new(
406 required_extension_types,
407 required_proposal_types,
408 credentials,
409 ));
410
411 let extensions = Extensions::from_vec(vec![
412 protected_metadata_extension,
413 mutable_metadata_extension,
414 group_membership_extension,
415 mutable_permission_extension,
416 required_capabilities,
417 ])?;
418
419 Ok(MlsGroupCreateConfig::builder()
420 .with_group_context_extensions(extensions)
421 .capabilities(capabilities)
422 .ciphersuite(CIPHERSUITE)
423 .wire_format_policy(WireFormatPolicy::default())
424 .max_past_epochs(MAX_PAST_EPOCHS)
425 .use_ratchet_tree_extension(true)
426 .build())
427}
428
429pub fn filter_inbox_ids_needing_updates<'a>(
430 conn: &impl DbQuery,
431 filters: &[(&'a str, i64)],
432) -> Result<Vec<&'a str>, xmtp_db::ConnectionError> {
433 let existing_sequence_ids =
434 conn.get_latest_sequence_id(&filters.iter().map(|f| f.0).collect::<Vec<&str>>())?;
435
436 let needs_update = filters
437 .iter()
438 .filter_map(|&(inbox_id, seq)| {
439 let existing_sequence_id = existing_sequence_ids.get(inbox_id);
440 if existing_sequence_id.is_some_and(|&s| s >= seq) {
441 return None;
442 }
443
444 Some(inbox_id)
445 })
446 .collect();
447 Ok(needs_update)
448}
449
450pub(in crate::groups) fn validate_dm_group(
451 context: impl XmtpSharedContext,
452 mls_group: &OpenMlsGroup,
453 added_by_inbox: &str,
454) -> Result<(), MetadataPermissionsError> {
455 let metadata = extract_group_metadata(mls_group.extensions())?;
457
458 if metadata.conversation_type != ConversationType::Dm {
460 return Err(DmValidationError::InvalidConversationType.into());
461 }
462
463 let dm_members = match &metadata.dm_members {
465 Some(dm) => dm,
466 None => {
467 return Err(DmValidationError::MustHaveMembersSet.into());
468 }
469 };
470
471 let identity = context.identity();
474 if added_by_inbox == identity.inbox_id() {
475 if !(dm_members.member_one_inbox_id == identity.inbox_id()
476 || dm_members.member_two_inbox_id == identity.inbox_id())
477 {
478 return Err(DmValidationError::OurInboxMustBeMember.into());
479 }
480 return Ok(());
481 }
482
483 let is_expected_pair = (dm_members.member_one_inbox_id == added_by_inbox
485 && dm_members.member_two_inbox_id == identity.inbox_id())
486 || (dm_members.member_one_inbox_id == identity.inbox_id()
487 && dm_members.member_two_inbox_id == added_by_inbox);
488
489 if !is_expected_pair {
490 return Err(DmValidationError::ExpectedInboxesDoNotMatch.into());
491 }
492
493 let mutable_metadata: GroupMutableMetadata = mls_group.try_into()?;
495
496 if !mutable_metadata.admin_list.is_empty() || !mutable_metadata.super_admin_list.is_empty() {
498 return Err(DmValidationError::MustHaveEmptyAdminAndSuperAdmin.into());
499 }
500
501 let permissions = extract_group_permissions(mls_group)?;
504 let expected_permissions = GroupMutablePermissions::new(PolicySet::new_dm());
505
506 if permissions.policies.add_member_policy != expected_permissions.policies.add_member_policy
507 && permissions.policies.remove_member_policy
508 != expected_permissions.policies.remove_member_policy
509 && permissions.policies.add_admin_policy != expected_permissions.policies.add_admin_policy
510 && permissions.policies.remove_admin_policy
511 != expected_permissions.policies.remove_admin_policy
512 && permissions.policies.update_permissions_policy
513 != expected_permissions.policies.update_permissions_policy
514 {
515 return Err(DmValidationError::InvalidPermissions.into());
516 }
517
518 Ok(())
519}