1mod serde;
6
7pub mod v1 {
8 use std::collections::BTreeSet;
13
14 use ruma_common::{
15 DeviceId,
16 api::{auth_scheme::NoAuthentication, request, response},
17 metadata,
18 serde::{Raw, StringEnum},
19 };
20 use serde::Serialize;
21 use url::Url;
22
23 use crate::PrivOwnedStr;
24
25 metadata! {
26 method: GET,
27 rate_limited: false,
28 authentication: NoAuthentication,
29 history: {
30 unstable => "/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",
31 1.15 => "/_matrix/client/v1/auth_metadata",
32 }
33 }
34
35 #[request(error = crate::Error)]
37 #[derive(Default)]
38 pub struct Request {}
39
40 #[response(error = crate::Error)]
42 pub struct Response {
43 #[ruma_api(body)]
47 pub metadata: Raw<AuthorizationServerMetadata>,
48 }
49
50 impl Request {
51 pub fn new() -> Self {
53 Self {}
54 }
55 }
56
57 impl Response {
58 pub fn new(metadata: Raw<AuthorizationServerMetadata>) -> Self {
60 Self { metadata }
61 }
62 }
63
64 #[derive(Debug, Clone, Serialize)]
80 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
81 pub struct AuthorizationServerMetadata {
82 pub issuer: Url,
86
87 pub authorization_endpoint: Url,
91
92 pub token_endpoint: Url,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
102 pub registration_endpoint: Option<Url>,
103
104 pub response_types_supported: BTreeSet<ResponseType>,
113
114 pub response_modes_supported: BTreeSet<ResponseMode>,
122
123 pub grant_types_supported: BTreeSet<GrantType>,
133
134 pub revocation_endpoint: Url,
138
139 pub code_challenge_methods_supported: BTreeSet<CodeChallengeMethod>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
152 pub account_management_uri: Option<Url>,
153
154 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
158 pub account_management_actions_supported: BTreeSet<AccountManagementAction>,
159
160 #[cfg(feature = "unstable-msc4108")]
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub device_authorization_endpoint: Option<Url>,
166
167 #[serde(skip_serializing_if = "Vec::is_empty")]
172 pub prompt_values_supported: Vec<Prompt>,
173 }
174
175 impl AuthorizationServerMetadata {
176 pub fn validate_urls(&self) -> Result<(), AuthorizationServerMetadataUrlError> {
184 self.validate_urls_inner(false)
185 }
186
187 pub fn insecure_validate_urls(&self) -> Result<(), AuthorizationServerMetadataUrlError> {
195 self.validate_urls_inner(true)
196 }
197
198 fn validate_urls_inner(
201 &self,
202 insecure: bool,
203 ) -> Result<(), AuthorizationServerMetadataUrlError> {
204 if self.issuer.query().is_some() || self.issuer.fragment().is_some() {
205 return Err(AuthorizationServerMetadataUrlError::IssuerHasQueryOrFragment);
206 }
207
208 if insecure {
209 return Ok(());
211 }
212
213 let required_urls = &[
214 ("issuer", &self.issuer),
215 ("authorization_endpoint", &self.authorization_endpoint),
216 ("token_endpoint", &self.token_endpoint),
217 ("revocation_endpoint", &self.revocation_endpoint),
218 ];
219 let optional_urls = &[
220 self.registration_endpoint.as_ref().map(|string| ("registration_endpoint", string)),
221 self.account_management_uri
222 .as_ref()
223 .map(|string| ("account_management_uri", string)),
224 #[cfg(feature = "unstable-msc4108")]
225 self.device_authorization_endpoint
226 .as_ref()
227 .map(|string| ("device_authorization_endpoint", string)),
228 ];
229
230 for (field, url) in required_urls.iter().chain(optional_urls.iter().flatten()) {
231 if url.scheme() != "https" {
232 return Err(AuthorizationServerMetadataUrlError::NotHttpsScheme(field));
233 }
234 }
235
236 Ok(())
237 }
238
239 pub fn is_action_management_action_supported(
244 &self,
245 action: &AccountManagementAction,
246 ) -> bool {
247 match action {
248 AccountManagementAction::UnstableSessionsList
249 | AccountManagementAction::DevicesList => {
250 self.account_management_actions_supported
251 .contains(&AccountManagementAction::DevicesList)
252 || self
253 .account_management_actions_supported
254 .contains(&AccountManagementAction::UnstableSessionsList)
255 }
256 AccountManagementAction::UnstableSessionView
257 | AccountManagementAction::DeviceView => {
258 self.account_management_actions_supported
259 .contains(&AccountManagementAction::DeviceView)
260 || self
261 .account_management_actions_supported
262 .contains(&AccountManagementAction::UnstableSessionView)
263 }
264 AccountManagementAction::UnstableSessionEnd
265 | AccountManagementAction::DeviceDelete => {
266 self.account_management_actions_supported
267 .contains(&AccountManagementAction::DeviceDelete)
268 || self
269 .account_management_actions_supported
270 .contains(&AccountManagementAction::UnstableSessionEnd)
271 }
272 action => self.account_management_actions_supported.contains(action),
273 }
274 }
275
276 pub fn action_management_url_with_action(
285 &self,
286 action: AccountManagementActionData<'_>,
287 ) -> Option<Url> {
288 const QUERY_NAME_ACTION: &str = "action";
289 const QUERY_NAME_DEVICE_ID: &str = "device_id";
290
291 let mut url = self.account_management_uri.clone()?;
292
293 match action {
294 AccountManagementActionData::Profile => {
295 url.query_pairs_mut()
296 .append_pair(QUERY_NAME_ACTION, AccountManagementAction::Profile.as_str());
297 }
298 AccountManagementActionData::DevicesList => {
299 let action = if self
302 .account_management_actions_supported
303 .contains(&AccountManagementAction::DevicesList)
304 {
305 AccountManagementAction::DevicesList
306 } else if self
307 .account_management_actions_supported
308 .contains(&AccountManagementAction::UnstableSessionsList)
309 {
310 AccountManagementAction::UnstableSessionsList
311 } else {
312 AccountManagementAction::DevicesList
313 };
314
315 url.query_pairs_mut().append_pair(QUERY_NAME_ACTION, action.as_str());
316 }
317 AccountManagementActionData::DeviceView(DeviceViewData { device_id }) => {
318 let action = if self
321 .account_management_actions_supported
322 .contains(&AccountManagementAction::DeviceView)
323 {
324 AccountManagementAction::DeviceView
325 } else if self
326 .account_management_actions_supported
327 .contains(&AccountManagementAction::UnstableSessionView)
328 {
329 AccountManagementAction::UnstableSessionView
330 } else {
331 AccountManagementAction::DeviceView
332 };
333
334 url.query_pairs_mut()
335 .append_pair(QUERY_NAME_ACTION, action.as_str())
336 .append_pair(QUERY_NAME_DEVICE_ID, device_id.as_str());
337 }
338 AccountManagementActionData::DeviceDelete(DeviceDeleteData { device_id }) => {
339 let action = if self
342 .account_management_actions_supported
343 .contains(&AccountManagementAction::DeviceDelete)
344 {
345 AccountManagementAction::DeviceDelete
346 } else if self
347 .account_management_actions_supported
348 .contains(&AccountManagementAction::UnstableSessionEnd)
349 {
350 AccountManagementAction::UnstableSessionEnd
351 } else {
352 AccountManagementAction::DeviceDelete
353 };
354
355 url.query_pairs_mut()
356 .append_pair(QUERY_NAME_ACTION, action.as_str())
357 .append_pair(QUERY_NAME_DEVICE_ID, device_id.as_str());
358 }
359 AccountManagementActionData::AccountDeactivate => {
360 url.query_pairs_mut().append_pair(
361 QUERY_NAME_ACTION,
362 AccountManagementAction::AccountDeactivate.as_str(),
363 );
364 }
365 AccountManagementActionData::CrossSigningReset => {
366 url.query_pairs_mut().append_pair(
367 QUERY_NAME_ACTION,
368 AccountManagementAction::CrossSigningReset.as_str(),
369 );
370 }
371 }
372
373 Some(url)
374 }
375 }
376
377 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
379 #[derive(Clone, StringEnum)]
380 #[ruma_enum(rename_all = "lowercase")]
381 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
382 pub enum ResponseType {
383 Code,
387
388 #[doc(hidden)]
389 _Custom(PrivOwnedStr),
390 }
391
392 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
397 #[derive(Clone, StringEnum)]
400 #[ruma_enum(rename_all = "lowercase")]
401 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
402 pub enum ResponseMode {
403 Query,
406
407 Fragment,
410
411 #[doc(hidden)]
412 _Custom(PrivOwnedStr),
413 }
414
415 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
417 #[derive(Clone, StringEnum)]
418 #[ruma_enum(rename_all = "snake_case")]
419 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
420 pub enum GrantType {
421 AuthorizationCode,
425
426 RefreshToken,
430
431 #[cfg(feature = "unstable-msc4108")]
435 #[ruma_enum(rename = "urn:ietf:params:oauth:grant-type:device_code")]
436 DeviceCode,
437
438 #[doc(hidden)]
439 _Custom(PrivOwnedStr),
440 }
441
442 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
444 #[derive(Clone, StringEnum)]
445 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
446 pub enum CodeChallengeMethod {
447 S256,
451
452 #[doc(hidden)]
453 _Custom(PrivOwnedStr),
454 }
455
456 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
462 #[derive(Clone, StringEnum)]
465 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
466 pub enum AccountManagementAction {
467 #[ruma_enum(rename = "org.matrix.profile")]
469 Profile,
470
471 #[ruma_enum(rename = "org.matrix.sessions_list")]
478 UnstableSessionsList,
479
480 #[ruma_enum(rename = "org.matrix.devices_list")]
482 DevicesList,
483
484 #[ruma_enum(rename = "org.matrix.session_view")]
491 UnstableSessionView,
492
493 #[ruma_enum(rename = "org.matrix.device_view")]
495 DeviceView,
496
497 #[ruma_enum(rename = "org.matrix.session_end")]
504 UnstableSessionEnd,
505
506 #[ruma_enum(rename = "org.matrix.device_delete")]
508 DeviceDelete,
509
510 #[ruma_enum(rename = "org.matrix.account_deactivate")]
512 AccountDeactivate,
513
514 #[ruma_enum(rename = "org.matrix.cross_signing_reset")]
520 CrossSigningReset,
521
522 #[doc(hidden)]
523 _Custom(PrivOwnedStr),
524 }
525
526 #[derive(Debug, Clone)]
531 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
532 pub enum AccountManagementActionData<'a> {
533 Profile,
535
536 DevicesList,
538
539 DeviceView(DeviceViewData<'a>),
541
542 DeviceDelete(DeviceDeleteData<'a>),
544
545 AccountDeactivate,
547
548 CrossSigningReset,
550 }
551
552 #[derive(Debug, Clone)]
554 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
555 pub struct DeviceViewData<'a> {
556 pub device_id: &'a DeviceId,
558 }
559
560 impl<'a> DeviceViewData<'a> {
561 fn new(device_id: &'a DeviceId) -> Self {
563 Self { device_id }
564 }
565 }
566
567 impl<'a> From<&'a DeviceId> for DeviceViewData<'a> {
568 fn from(device_id: &'a DeviceId) -> Self {
569 Self::new(device_id)
570 }
571 }
572
573 #[derive(Debug, Clone)]
575 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
576 pub struct DeviceDeleteData<'a> {
577 pub device_id: &'a DeviceId,
579 }
580
581 impl<'a> DeviceDeleteData<'a> {
582 fn new(device_id: &'a DeviceId) -> Self {
584 Self { device_id }
585 }
586 }
587
588 impl<'a> From<&'a DeviceId> for DeviceDeleteData<'a> {
589 fn from(device_id: &'a DeviceId) -> Self {
590 Self::new(device_id)
591 }
592 }
593
594 #[derive(Debug, Clone, thiserror::Error)]
596 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
597 pub enum AuthorizationServerMetadataUrlError {
598 #[error("URL in `{0}` must use the `https` scheme")]
600 NotHttpsScheme(&'static str),
601
602 #[error("URL in `issuer` cannot have a query or fragment component")]
604 IssuerHasQueryOrFragment,
605 }
606
607 #[derive(Clone, StringEnum)]
609 #[ruma_enum(rename_all = "lowercase")]
610 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
611 pub enum Prompt {
612 Create,
617
618 #[doc(hidden)]
619 _Custom(PrivOwnedStr),
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use ruma_common::device_id;
626 use serde_json::{Value as JsonValue, from_value as from_json_value, json};
627 use url::Url;
628
629 use super::v1::{
630 AccountManagementAction, AccountManagementActionData, AuthorizationServerMetadata,
631 };
632
633 pub(super) fn authorization_server_metadata_json() -> JsonValue {
635 json!({
636 "issuer": "https://server.local/",
637 "authorization_endpoint": "https://server.local/authorize",
638 "token_endpoint": "https://server.local/token",
639 "registration_endpoint": "https://server.local/register",
640 "response_types_supported": ["code"],
641 "response_modes_supported": ["query", "fragment"],
642 "grant_types_supported": ["authorization_code", "refresh_token"],
643 "revocation_endpoint": "https://server.local/revoke",
644 "code_challenge_methods_supported": ["S256"],
645 "account_management_uri": "https://server.local/account",
646 "account_management_actions_supported": [
647 "org.matrix.profile",
648 "org.matrix.devices_list",
649 "org.matrix.device_view",
650 "org.matrix.device_delete",
651 "org.matrix.account_deactivate",
652 "org.matrix.cross_signing_reset",
653 ],
654 "device_authorization_endpoint": "https://server.local/device",
655 })
656 }
657
658 fn authorization_server_metadata() -> AuthorizationServerMetadata {
660 from_json_value(authorization_server_metadata_json()).unwrap()
661 }
662
663 #[test]
664 fn metadata_valid_urls() {
665 let metadata = authorization_server_metadata();
666 metadata.validate_urls().unwrap();
667 metadata.insecure_validate_urls().unwrap();
668 }
669
670 #[test]
671 fn metadata_invalid_or_insecure_issuer() {
672 let original_metadata = authorization_server_metadata();
673
674 let mut metadata = original_metadata.clone();
676 metadata.issuer = Url::parse("https://server.local/?session=1er45elp").unwrap();
677 metadata.validate_urls().unwrap_err();
678 metadata.insecure_validate_urls().unwrap_err();
679
680 let mut metadata = original_metadata.clone();
682 metadata.issuer = Url::parse("https://server.local/#session").unwrap();
683 metadata.validate_urls().unwrap_err();
684 metadata.insecure_validate_urls().unwrap_err();
685
686 let mut metadata = original_metadata;
688 metadata.issuer = Url::parse("http://server.local/").unwrap();
689 metadata.validate_urls().unwrap_err();
690 metadata.insecure_validate_urls().unwrap();
691 }
692
693 #[test]
694 fn metadata_insecure_urls() {
695 let original_metadata = authorization_server_metadata();
696
697 let mut metadata = original_metadata.clone();
698 metadata.authorization_endpoint = Url::parse("http://server.local/authorize").unwrap();
699 metadata.validate_urls().unwrap_err();
700 metadata.insecure_validate_urls().unwrap();
701
702 let mut metadata = original_metadata.clone();
703 metadata.token_endpoint = Url::parse("http://server.local/token").unwrap();
704 metadata.validate_urls().unwrap_err();
705 metadata.insecure_validate_urls().unwrap();
706
707 let mut metadata = original_metadata.clone();
708 metadata.registration_endpoint = Some(Url::parse("http://server.local/register").unwrap());
709 metadata.validate_urls().unwrap_err();
710 metadata.insecure_validate_urls().unwrap();
711
712 let mut metadata = original_metadata.clone();
713 metadata.revocation_endpoint = Url::parse("http://server.local/revoke").unwrap();
714 metadata.validate_urls().unwrap_err();
715 metadata.insecure_validate_urls().unwrap();
716
717 let mut metadata = original_metadata.clone();
718 metadata.account_management_uri = Some(Url::parse("http://server.local/account").unwrap());
719 metadata.validate_urls().unwrap_err();
720 metadata.insecure_validate_urls().unwrap();
721
722 #[cfg(feature = "unstable-msc4108")]
723 {
724 let mut metadata = original_metadata.clone();
725 metadata.device_authorization_endpoint =
726 Some(Url::parse("http://server.local/device").unwrap());
727 metadata.validate_urls().unwrap_err();
728 metadata.insecure_validate_urls().unwrap();
729 }
730 }
731
732 #[test]
733 fn metadata_is_account_management_action_supported() {
734 let mut original_metadata = authorization_server_metadata();
735
736 assert!(
738 original_metadata
739 .is_action_management_action_supported(&AccountManagementAction::Profile)
740 );
741
742 original_metadata
744 .account_management_actions_supported
745 .remove(&AccountManagementAction::Profile);
746 assert!(
747 !original_metadata
748 .is_action_management_action_supported(&AccountManagementAction::Profile)
749 );
750
751 assert!(
753 original_metadata
754 .is_action_management_action_supported(&AccountManagementAction::DevicesList)
755 );
756 assert!(
757 original_metadata.is_action_management_action_supported(
758 &AccountManagementAction::UnstableSessionsList
759 )
760 );
761
762 original_metadata
764 .account_management_actions_supported
765 .remove(&AccountManagementAction::DevicesList);
766 assert!(
767 !original_metadata
768 .is_action_management_action_supported(&AccountManagementAction::DevicesList)
769 );
770 assert!(
771 !original_metadata.is_action_management_action_supported(
772 &AccountManagementAction::UnstableSessionsList
773 )
774 );
775
776 assert!(
778 original_metadata
779 .is_action_management_action_supported(&AccountManagementAction::DeviceView)
780 );
781 assert!(
782 original_metadata.is_action_management_action_supported(
783 &AccountManagementAction::UnstableSessionView
784 )
785 );
786
787 original_metadata
789 .account_management_actions_supported
790 .remove(&AccountManagementAction::DeviceView);
791 assert!(
792 !original_metadata
793 .is_action_management_action_supported(&AccountManagementAction::DeviceView)
794 );
795 assert!(
796 !original_metadata.is_action_management_action_supported(
797 &AccountManagementAction::UnstableSessionView
798 )
799 );
800
801 assert!(
803 original_metadata
804 .is_action_management_action_supported(&AccountManagementAction::DeviceDelete)
805 );
806 assert!(
807 original_metadata.is_action_management_action_supported(
808 &AccountManagementAction::UnstableSessionEnd
809 )
810 );
811
812 original_metadata
814 .account_management_actions_supported
815 .remove(&AccountManagementAction::DeviceDelete);
816 assert!(
817 !original_metadata
818 .is_action_management_action_supported(&AccountManagementAction::DeviceDelete)
819 );
820 assert!(
821 !original_metadata.is_action_management_action_supported(
822 &AccountManagementAction::UnstableSessionEnd
823 )
824 );
825 }
826
827 #[test]
828 fn metadata_account_management_url_with_action() {
829 let mut original_metadata = authorization_server_metadata();
830 let device_id = device_id!("DEVICE");
831
832 let url = original_metadata
834 .action_management_url_with_action(AccountManagementActionData::Profile)
835 .unwrap();
836 assert_eq!(url.query().unwrap(), "action=org.matrix.profile");
837
838 let url = original_metadata
840 .action_management_url_with_action(AccountManagementActionData::DevicesList)
841 .unwrap();
842 assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
843
844 original_metadata
846 .account_management_actions_supported
847 .insert(AccountManagementAction::UnstableSessionsList);
848 let url = original_metadata
849 .action_management_url_with_action(AccountManagementActionData::DevicesList)
850 .unwrap();
851 assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
852
853 original_metadata
855 .account_management_actions_supported
856 .remove(&AccountManagementAction::DevicesList);
857 let url = original_metadata
858 .action_management_url_with_action(AccountManagementActionData::DevicesList)
859 .unwrap();
860 assert_eq!(url.query().unwrap(), "action=org.matrix.sessions_list");
861
862 original_metadata
864 .account_management_actions_supported
865 .remove(&AccountManagementAction::UnstableSessionsList);
866 let url = original_metadata
867 .action_management_url_with_action(AccountManagementActionData::DevicesList)
868 .unwrap();
869 assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
870
871 let url = original_metadata
873 .action_management_url_with_action(AccountManagementActionData::DeviceView(
874 device_id.into(),
875 ))
876 .unwrap();
877 assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
878
879 original_metadata
881 .account_management_actions_supported
882 .insert(AccountManagementAction::UnstableSessionView);
883 let url = original_metadata
884 .action_management_url_with_action(AccountManagementActionData::DeviceView(
885 device_id.into(),
886 ))
887 .unwrap();
888 assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
889
890 original_metadata
892 .account_management_actions_supported
893 .remove(&AccountManagementAction::DeviceView);
894 let url = original_metadata
895 .action_management_url_with_action(AccountManagementActionData::DeviceView(
896 device_id.into(),
897 ))
898 .unwrap();
899 assert_eq!(url.query().unwrap(), "action=org.matrix.session_view&device_id=DEVICE");
900
901 original_metadata
903 .account_management_actions_supported
904 .remove(&AccountManagementAction::UnstableSessionView);
905 let url = original_metadata
906 .action_management_url_with_action(AccountManagementActionData::DeviceView(
907 device_id.into(),
908 ))
909 .unwrap();
910 assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
911
912 let url = original_metadata
914 .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
915 device_id.into(),
916 ))
917 .unwrap();
918 assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
919
920 original_metadata
922 .account_management_actions_supported
923 .insert(AccountManagementAction::UnstableSessionEnd);
924 let url = original_metadata
925 .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
926 device_id.into(),
927 ))
928 .unwrap();
929 assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
930
931 original_metadata
933 .account_management_actions_supported
934 .remove(&AccountManagementAction::DeviceDelete);
935 let url = original_metadata
936 .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
937 device_id.into(),
938 ))
939 .unwrap();
940 assert_eq!(url.query().unwrap(), "action=org.matrix.session_end&device_id=DEVICE");
941
942 original_metadata
944 .account_management_actions_supported
945 .remove(&AccountManagementAction::UnstableSessionEnd);
946 let url = original_metadata
947 .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
948 device_id.into(),
949 ))
950 .unwrap();
951 assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
952 }
953}