1use crate::admin::AdminApiClient;
2use crate::agreement_acceptances::{
3 AgreementAcceptancesApiClient, AgreementAcceptancesIdApiClient,
4};
5use crate::agreements::{AgreementsApiClient, AgreementsIdApiClient};
6use crate::api_default_imports::*;
7use crate::app_catalogs::AppCatalogsApiClient;
8use crate::applications::{ApplicationsApiClient, ApplicationsIdApiClient};
9use crate::audit_logs::AuditLogsApiClient;
10use crate::authentication_method_configurations::{
11 AuthenticationMethodConfigurationsApiClient, AuthenticationMethodConfigurationsIdApiClient,
12};
13use crate::authentication_methods_policy::AuthenticationMethodsPolicyApiClient;
14use crate::batch::BatchApiClient;
15use crate::branding::BrandingApiClient;
16use crate::certificate_based_auth_configuration::{
17 CertificateBasedAuthConfigurationApiClient, CertificateBasedAuthConfigurationIdApiClient,
18};
19use crate::chats::{ChatsApiClient, ChatsIdApiClient};
20use crate::communications::CommunicationsApiClient;
21use crate::contracts::{ContractsApiClient, ContractsIdApiClient};
22use crate::data_policy_operations::DataPolicyOperationsApiClient;
23use crate::default_drive::DefaultDriveApiClient;
24use crate::device_app_management::DeviceAppManagementApiClient;
25use crate::device_management::DeviceManagementApiClient;
26use crate::devices::{DevicesApiClient, DevicesIdApiClient};
27use crate::directory::DirectoryApiClient;
28use crate::directory_objects::{DirectoryObjectsApiClient, DirectoryObjectsIdApiClient};
29use crate::directory_role_templates::{
30 DirectoryRoleTemplatesApiClient, DirectoryRoleTemplatesIdApiClient,
31};
32use crate::directory_roles::{DirectoryRolesApiClient, DirectoryRolesIdApiClient};
33use crate::domain_dns_records::{DomainDnsRecordsApiClient, DomainDnsRecordsIdApiClient};
34use crate::domains::{DomainsApiClient, DomainsIdApiClient};
35use crate::drives::{DrivesApiClient, DrivesIdApiClient};
36use crate::education::EducationApiClient;
37use crate::group_lifecycle_policies::{
38 GroupLifecyclePoliciesApiClient, GroupLifecyclePoliciesIdApiClient,
39};
40use crate::groups::{GroupsApiClient, GroupsIdApiClient};
41use crate::identity::{
42 AllowedHostValidator, AuthorizationCodeAssertionCredential,
43 AuthorizationCodeCertificateCredential, AuthorizationCodeCredential, BearerTokenCredential,
44 ClientAssertionCredential, ClientCertificateCredential, ClientSecretCredential,
45 ConfidentialClientApplication, DeviceCodeCredential, HostIs, OpenIdCredential,
46 PublicClientApplication, ResourceOwnerPasswordCredential, Token,
47};
48use crate::identity_access::IdentityApiClient;
49use crate::identity_governance::IdentityGovernanceApiClient;
50use crate::identity_providers::{IdentityProvidersApiClient, IdentityProvidersIdApiClient};
51use crate::invitations::InvitationsApiClient;
52use crate::me::MeApiClient;
53use crate::oauth2_permission_grants::{
54 Oauth2PermissionGrantsApiClient, Oauth2PermissionGrantsIdApiClient,
55};
56use crate::organization::{OrganizationApiClient, OrganizationIdApiClient};
57use crate::permission_grants::{PermissionGrantsApiClient, PermissionGrantsIdApiClient};
58use crate::places::PlacesApiClient;
59use crate::planner::PlannerApiClient;
60use crate::policies::PoliciesApiClient;
61use crate::reports::ReportsApiClient;
62use crate::schema_extensions::{SchemaExtensionsApiClient, SchemaExtensionsIdApiClient};
63use crate::service_principals::{ServicePrincipalsApiClient, ServicePrincipalsIdApiClient};
64use crate::sites::{SitesApiClient, SitesIdApiClient};
65use crate::solutions::SolutionsApiClient;
66use crate::subscribed_skus::SubscribedSkusApiClient;
67use crate::subscriptions::{SubscriptionsApiClient, SubscriptionsIdApiClient};
68use crate::teams::{TeamsApiClient, TeamsIdApiClient};
69use crate::teams_templates::{TeamsTemplatesApiClient, TeamsTemplatesIdApiClient};
70use crate::teamwork::TeamworkApiClient;
71use crate::users::{UsersApiClient, UsersIdApiClient};
72use crate::{GRAPH_URL, GRAPH_URL_BETA};
73use graph_core::identity::ForceTokenRefresh;
74use graph_oauth::AuthorizationCodeSpaCredential;
75use lazy_static::lazy_static;
76
77lazy_static! {
78 static ref PARSED_GRAPH_URL: Url = Url::parse(GRAPH_URL).expect("Unable to set v1 endpoint");
79 static ref PARSED_GRAPH_URL_BETA: Url =
80 Url::parse(GRAPH_URL_BETA).expect("Unable to set beta endpoint");
81}
82
83pub type Graph = GraphClient;
85
86#[derive(Debug, Clone)]
87pub struct GraphClient {
88 client: Client,
89 endpoint: Url,
90 allowed_host_validator: AllowedHostValidator,
91}
92
93impl GraphClient {
94 pub fn new<AT: ToString>(access_token: AT) -> GraphClient {
95 GraphClient {
96 client: Client::new(BearerTokenCredential::from(access_token.to_string())),
97 endpoint: PARSED_GRAPH_URL.clone(),
98 allowed_host_validator: AllowedHostValidator::default(),
99 }
100 }
101
102 pub fn from_client_app<CA: ClientApplication + 'static>(client_app: CA) -> GraphClient {
103 GraphClient {
104 client: Client::new(client_app),
105 endpoint: PARSED_GRAPH_URL.clone(),
106 allowed_host_validator: AllowedHostValidator::default(),
107 }
108 }
109
110 pub fn v1(&mut self) -> &mut GraphClient {
127 self.endpoint = PARSED_GRAPH_URL.clone();
128 self
129 }
130
131 pub fn use_v1(&mut self) {
146 self.endpoint = PARSED_GRAPH_URL.clone();
147 }
148
149 pub fn beta(&mut self) -> &mut GraphClient {
165 self.endpoint = PARSED_GRAPH_URL_BETA.clone();
166 self
167 }
168
169 pub fn use_beta(&mut self) {
184 self.endpoint = PARSED_GRAPH_URL_BETA.clone();
185 }
186
187 pub fn url(&self) -> &Url {
188 &self.endpoint
189 }
190
191 pub fn with_force_token_refresh(
192 &mut self,
193 force_token_refresh: ForceTokenRefresh,
194 ) -> &mut Self {
195 self.client.with_force_token_refresh(force_token_refresh);
196 self
197 }
198
199 pub fn use_force_token_refresh(&mut self, force_token_refresh: ForceTokenRefresh) {
200 self.client.with_force_token_refresh(force_token_refresh);
201 }
202
203 pub fn custom_endpoint(&mut self, url: &Url) -> &mut GraphClient {
243 self.use_endpoint(url);
244 self
245 }
246
247 pub fn use_endpoint(&mut self, url: &Url) {
285 if url.query().is_some() {
286 panic!(
287 "Invalid query - provide only the scheme, host, and optional path of the Uri such as https://graph.microsoft.com/v1.0"
288 );
289 }
290
291 match self.allowed_host_validator.validate_url(url) {
292 HostIs::Valid => {
293 self.endpoint = url.clone();
294 }
295 HostIs::Invalid => panic!("Invalid host"),
296 }
297 }
298
299 #[cfg(feature = "test-util")]
300 pub fn use_test_endpoint(&mut self, url: &Url) {
301 self.endpoint = url.clone();
302 }
303
304 api_client_impl!(admin, AdminApiClient);
305
306 api_client_impl!(app_catalogs, AppCatalogsApiClient);
307
308 api_client_impl!(
309 agreement_acceptances,
310 AgreementAcceptancesApiClient,
311 agreement_acceptance,
312 AgreementAcceptancesIdApiClient
313 );
314
315 api_client_impl!(
316 agreements,
317 AgreementsApiClient,
318 agreement,
319 AgreementsIdApiClient
320 );
321
322 api_client_impl!(
323 applications,
324 ApplicationsApiClient,
325 application,
326 ApplicationsIdApiClient
327 );
328
329 api_client_impl!(audit_logs, AuditLogsApiClient);
330
331 api_client_impl!(
332 authentication_method_configurations,
333 AuthenticationMethodConfigurationsApiClient,
334 authentication_method_configuration,
335 AuthenticationMethodConfigurationsIdApiClient
336 );
337
338 api_client_impl!(
339 authentication_methods_policy,
340 AuthenticationMethodsPolicyApiClient
341 );
342
343 api_client_impl!(branding, BrandingApiClient);
344
345 api_client_impl!(
346 certificate_based_auth_configurations,
347 CertificateBasedAuthConfigurationApiClient,
348 certificate_based_auth_configuration,
349 CertificateBasedAuthConfigurationIdApiClient
350 );
351
352 api_client_impl!(chats, ChatsApiClient, chat, ChatsIdApiClient);
353
354 api_client_impl!(communications, CommunicationsApiClient);
355
356 api_client_impl!(
357 contracts,
358 ContractsApiClient,
359 contract,
360 ContractsIdApiClient
361 );
362
363 api_client_impl!(data_policy_operations, DataPolicyOperationsApiClient);
364
365 api_client_impl!(device_app_management, DeviceAppManagementApiClient);
366
367 api_client_impl!(device_management, DeviceManagementApiClient);
368
369 api_client_impl!(devices, DevicesApiClient, device, DevicesIdApiClient);
370
371 api_client_impl!(directory, DirectoryApiClient);
372
373 api_client_impl!(
374 directory_objects,
375 DirectoryObjectsApiClient,
376 directory_object,
377 DirectoryObjectsIdApiClient
378 );
379
380 api_client_impl!(
381 directory_role_templates,
382 DirectoryRoleTemplatesApiClient,
383 directory_role_template,
384 DirectoryRoleTemplatesIdApiClient
385 );
386
387 api_client_impl!(
388 directory_roles,
389 DirectoryRolesApiClient,
390 directory_role,
391 DirectoryRolesIdApiClient
392 );
393
394 api_client_impl!(
395 domain_dns_records,
396 DomainDnsRecordsApiClient,
397 domain_dns_record,
398 DomainDnsRecordsIdApiClient
399 );
400
401 api_client_impl!(domains, DomainsApiClient, domain, DomainsIdApiClient);
402
403 api_client_impl!(drives, DrivesApiClient, drive, DrivesIdApiClient);
404
405 api_client_impl_link!(default_drive, DefaultDriveApiClient);
406
407 api_client_impl_link!(education, EducationApiClient);
408
409 api_client_impl!(groups, GroupsApiClient, group, GroupsIdApiClient);
410
411 api_client_impl!(
412 group_lifecycle_policies,
413 GroupLifecyclePoliciesApiClient,
414 group_lifecycle_policy,
415 GroupLifecyclePoliciesIdApiClient
416 );
417
418 api_client_impl_link!(identity, IdentityApiClient);
419
420 api_client_impl!(identity_governance, IdentityGovernanceApiClient);
421
422 api_client_impl!(
423 identity_providers,
424 IdentityProvidersApiClient,
425 identity_provider,
426 IdentityProvidersIdApiClient
427 );
428
429 api_client_impl!(invitations, InvitationsApiClient);
430
431 api_client_impl_link!(me, MeApiClient);
432
433 api_client_impl!(
434 oauth2_permission_grants,
435 Oauth2PermissionGrantsApiClient,
436 oauth2_permission_grant,
437 Oauth2PermissionGrantsIdApiClient
438 );
439
440 api_client_impl!(
441 organizations,
442 OrganizationApiClient,
443 organization,
444 OrganizationIdApiClient
445 );
446
447 api_client_impl!(places, PlacesApiClient);
448
449 api_client_impl!(
450 permission_grants,
451 PermissionGrantsApiClient,
452 permission_grant,
453 PermissionGrantsIdApiClient
454 );
455
456 api_client_impl!(planner, PlannerApiClient);
457
458 api_client_impl!(policies, PoliciesApiClient);
459
460 api_client_impl!(reports, ReportsApiClient);
461
462 api_client_impl!(
463 schema_extensions,
464 SchemaExtensionsApiClient,
465 schema_extension,
466 SchemaExtensionsIdApiClient
467 );
468
469 api_client_impl!(
470 service_principals,
471 ServicePrincipalsApiClient,
472 service_principal,
473 ServicePrincipalsIdApiClient
474 );
475
476 api_client_impl!(sites, SitesApiClient, site, SitesIdApiClient);
477
478 api_client_impl!(solutions, SolutionsApiClient);
479
480 api_client_impl!(
481 subscribed_skus,
482 SubscribedSkusApiClient,
483 subscribed_sku,
484 SubscriptionsIdApiClient
485 );
486
487 api_client_impl!(
488 subscriptions,
489 SubscriptionsApiClient,
490 subscription,
491 SubscriptionsIdApiClient
492 );
493
494 api_client_impl!(teams, TeamsApiClient, team, TeamsIdApiClient);
495
496 api_client_impl!(
497 teams_templates,
498 TeamsTemplatesApiClient,
499 teams_template,
500 TeamsTemplatesIdApiClient
501 );
502
503 api_client_impl_link!(teamwork, TeamworkApiClient);
504
505 api_client_impl!(users, UsersApiClient, user, UsersIdApiClient);
506
507 pub fn custom(&self, method: Method, body: Option<BodyRead>) -> RequestHandler {
508 let body_result = body.map(|body| body.into_body());
509 if let Some(b) = body_result {
510 if let Err(err) = b {
511 return RequestHandler::new(
512 self.client.clone(),
513 RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
514 Some(err),
515 None,
516 );
517 } else if let Ok(body_read) = b {
518 return RequestHandler::new(
519 self.client.clone(),
520 RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
521 None,
522 Some(body_read),
523 );
524 }
525 }
526
527 RequestHandler::new(
528 self.client.clone(),
529 RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
530 None,
531 None,
532 )
533 }
534
535 pub fn batch<B: serde::Serialize>(&self, batch: &B) -> RequestHandler {
536 BatchApiClient::new(
537 self.client.clone(),
538 ResourceProvisioner::resource_config_with_url(
539 self.endpoint.clone(),
540 ResourceIdentity::Batch,
541 ),
542 Handlebars::new(),
543 )
544 .batch(batch)
545 }
546}
547
548impl From<&str> for GraphClient {
549 fn from(token: &str) -> Self {
550 GraphClient::from_client_app(BearerTokenCredential::from(token.to_string()))
551 }
552}
553
554impl From<String> for GraphClient {
555 fn from(token: String) -> Self {
556 GraphClient::from_client_app(BearerTokenCredential::from(token))
557 }
558}
559
560impl From<&Token> for GraphClient {
561 fn from(token: &Token) -> Self {
562 GraphClient::from_client_app(BearerTokenCredential::from(token.access_token.clone()))
563 }
564}
565
566impl From<GraphClientConfiguration> for GraphClient {
567 fn from(graph_client_builder: GraphClientConfiguration) -> Self {
568 GraphClient {
569 client: Client::from(graph_client_builder),
570 endpoint: PARSED_GRAPH_URL.clone(),
571 allowed_host_validator: AllowedHostValidator::default(),
572 }
573 }
574}
575
576impl From<&ConfidentialClientApplication<AuthorizationCodeCredential>> for GraphClient {
577 fn from(value: &ConfidentialClientApplication<AuthorizationCodeCredential>) -> Self {
578 GraphClient::from_client_app(value.clone())
579 }
580}
581
582impl From<&ConfidentialClientApplication<AuthorizationCodeAssertionCredential>> for GraphClient {
583 fn from(value: &ConfidentialClientApplication<AuthorizationCodeAssertionCredential>) -> Self {
584 GraphClient::from_client_app(value.clone())
585 }
586}
587
588impl From<&ConfidentialClientApplication<AuthorizationCodeCertificateCredential>> for GraphClient {
589 fn from(value: &ConfidentialClientApplication<AuthorizationCodeCertificateCredential>) -> Self {
590 GraphClient::from_client_app(value.clone())
591 }
592}
593
594impl From<&ConfidentialClientApplication<ClientSecretCredential>> for GraphClient {
595 fn from(value: &ConfidentialClientApplication<ClientSecretCredential>) -> Self {
596 GraphClient::from_client_app(value.clone())
597 }
598}
599
600impl From<&ConfidentialClientApplication<ClientCertificateCredential>> for GraphClient {
601 fn from(value: &ConfidentialClientApplication<ClientCertificateCredential>) -> Self {
602 GraphClient::from_client_app(value.clone())
603 }
604}
605
606impl From<&ConfidentialClientApplication<ClientAssertionCredential>> for GraphClient {
607 fn from(value: &ConfidentialClientApplication<ClientAssertionCredential>) -> Self {
608 GraphClient::from_client_app(value.clone())
609 }
610}
611
612impl From<&ConfidentialClientApplication<OpenIdCredential>> for GraphClient {
613 fn from(value: &ConfidentialClientApplication<OpenIdCredential>) -> Self {
614 GraphClient::from_client_app(value.clone())
615 }
616}
617
618impl From<&PublicClientApplication<DeviceCodeCredential>> for GraphClient {
619 fn from(value: &PublicClientApplication<DeviceCodeCredential>) -> Self {
620 GraphClient::from_client_app(value.clone())
621 }
622}
623
624impl From<&PublicClientApplication<ResourceOwnerPasswordCredential>> for GraphClient {
625 fn from(value: &PublicClientApplication<ResourceOwnerPasswordCredential>) -> Self {
626 GraphClient::from_client_app(value.clone())
627 }
628}
629
630impl From<&PublicClientApplication<AuthorizationCodeSpaCredential>> for GraphClient {
631 fn from(value: &PublicClientApplication<AuthorizationCodeSpaCredential>) -> Self {
632 GraphClient::from_client_app(value.clone())
633 }
634}
635
636#[cfg(test)]
637mod test {
638 use super::*;
639
640 #[test]
641 #[should_panic]
642 fn try_invalid_host() {
643 let mut client = GraphClient::new("token");
644 client.custom_endpoint(&Url::parse("https://example.org").unwrap());
645 }
646
647 #[test]
648 #[should_panic]
649 fn try_invalid_http_scheme() {
650 let mut client = GraphClient::new("token");
651 client.custom_endpoint(&Url::parse("http://example.org").unwrap());
652 }
653
654 #[test]
655 #[should_panic]
656 fn try_invalid_query() {
657 let mut client = GraphClient::new("token");
658 client.custom_endpoint(&Url::parse("https://example.org?user=name").unwrap());
659 }
660
661 #[test]
662 #[should_panic]
663 fn try_invalid_path() {
664 let mut client = GraphClient::new("token");
665 client.custom_endpoint(&Url::parse("https://example.org/v1").unwrap());
666 }
667
668 #[test]
669 #[should_panic]
670 fn try_invalid_host2() {
671 let mut client = GraphClient::new("token");
672 client.use_endpoint(&Url::parse("https://example.org").unwrap());
673 }
674
675 #[test]
676 #[should_panic]
677 fn try_invalid_scheme2() {
678 let mut client = GraphClient::new("token");
679 client.use_endpoint(&Url::parse("http://example.org").unwrap());
680 }
681
682 #[test]
683 #[should_panic]
684 fn try_invalid_query2() {
685 let mut client = GraphClient::new("token");
686 client.use_endpoint(&Url::parse("https://example.org?user=name").unwrap());
687 }
688
689 #[test]
690 #[should_panic]
691 fn try_invalid_path2() {
692 let mut client = GraphClient::new("token");
693 client.use_endpoint(&Url::parse("https://example.org/v1").unwrap());
694 }
695
696 #[test]
697 fn try_valid_hosts() {
698 let urls = [
699 "https://graph.microsoft.com/v1.0",
700 "https://graph.microsoft.us",
701 "https://dod-graph.microsoft.us",
702 "https://graph.microsoft.de",
703 "https://microsoftgraph.chinacloudapi.cn",
704 "https://canary.graph.microsoft.com",
705 ];
706
707 let mut client = Graph::new("token");
708
709 for url in urls.iter() {
710 client.custom_endpoint(&Url::parse(url).unwrap());
711 assert_eq!(client.url().clone(), Url::parse(url).unwrap());
712 }
713 }
714}
715
716#[cfg(test)]
717#[cfg(feature = "test-util")]
718mod test_util_feature {
719 use crate::{http::Url, Graph, GraphClientConfiguration, ODataQuery};
720 use wiremock::matchers::{bearer_token, method, path, query_param};
721 use wiremock::{Mock, MockServer, ResponseTemplate};
722
723 #[tokio::test]
725 async fn can_set_test_endpoint() {
726 let mock_server = MockServer::start().await;
727
728 Mock::given(method("GET"))
729 .and(path("/users"))
730 .and(query_param("$top", "10"))
731 .and(bearer_token("token"))
732 .respond_with(ResponseTemplate::new(200))
733 .mount(&mock_server)
734 .await;
735
736 let graph_client_configuration = GraphClientConfiguration::new()
737 .access_token("token")
738 .https_only(false);
739
740 let mut client = Graph::from(graph_client_configuration);
741 let uri = mock_server.uri();
742 client.use_test_endpoint(&Url::parse(uri.as_str()).unwrap());
743
744 let response = client.users().list_user().top("10").send().await.unwrap();
745 let status = response.status();
746 assert_eq!(status.as_u16(), 200);
747 }
748
749 #[tokio::test]
750 #[should_panic]
751 async fn test_util_feature_use_endpoint_panics() {
752 let mock_server = MockServer::start().await;
753
754 Mock::given(method("GET"))
755 .and(path("/users"))
756 .and(query_param("$top", "10"))
757 .and(bearer_token("token"))
758 .respond_with(ResponseTemplate::new(200))
759 .mount(&mock_server)
760 .await;
761
762 let graph_client_configuration = GraphClientConfiguration::new()
763 .access_token("token")
764 .https_only(false);
765
766 let mut client = Graph::from(graph_client_configuration);
767 let uri = mock_server.uri();
768 client.use_endpoint(&Url::parse(uri.as_str()).unwrap());
769
770 let response = client.users().list_user().top("10").send().await.unwrap();
771 let status = response.status();
772 assert_eq!(status.as_u16(), 200);
773 }
774}