graph_oauth/identity/credentials/
application_builder.rs

1use crate::identity::{
2    application_options::ApplicationOptions, credentials::app_config::AppConfig,
3    AuthCodeAuthorizationUrlParameterBuilder, Authority,
4    AuthorizationCodeAssertionCredentialBuilder, AuthorizationCodeCredentialBuilder,
5    AzureCloudInstance, ClientAssertionCredentialBuilder,
6    ClientCredentialsAuthorizationUrlParameterBuilder, ClientSecretCredentialBuilder,
7    DeviceCodeCredentialBuilder, DeviceCodePollingExecutor, EnvironmentCredential,
8    OpenIdAuthorizationUrlParameterBuilder, OpenIdCredentialBuilder, PublicClientApplication,
9    ResourceOwnerPasswordCredential, ResourceOwnerPasswordCredentialBuilder,
10};
11#[cfg(feature = "openssl")]
12use crate::identity::{
13    AuthorizationCodeCertificateCredentialBuilder, ClientCertificateCredentialBuilder,
14    X509Certificate,
15};
16use crate::AuthorizationCodeSpaCredentialBuilder;
17use graph_error::{IdentityResult, AF};
18use http::{HeaderMap, HeaderName, HeaderValue};
19use std::collections::HashMap;
20use std::env::VarError;
21use uuid::Uuid;
22
23pub struct ConfidentialClientApplicationBuilder {
24    pub(crate) app_config: AppConfig,
25}
26
27impl ConfidentialClientApplicationBuilder {
28    pub fn new(client_id: impl TryInto<Uuid>) -> Self {
29        ConfidentialClientApplicationBuilder {
30            app_config: AppConfig::new(client_id),
31        }
32    }
33
34    pub fn new_with_application_options(
35        application_options: ApplicationOptions,
36    ) -> IdentityResult<ConfidentialClientApplicationBuilder> {
37        ConfidentialClientApplicationBuilder::try_from(application_options)
38    }
39
40    pub fn with_tenant(&mut self, tenant_id: impl AsRef<str>) -> &mut Self {
41        self.app_config.with_tenant(tenant_id);
42        self
43    }
44
45    pub fn with_authority<T: Into<Authority>>(&mut self, authority: T) -> &mut Self {
46        self.app_config.with_authority(authority.into());
47        self
48    }
49
50    pub fn with_azure_cloud_instance(
51        &mut self,
52        azure_cloud_instance: AzureCloudInstance,
53    ) -> &mut Self {
54        self.app_config
55            .with_azure_cloud_instance(azure_cloud_instance);
56        self
57    }
58
59    /// Extends the query parameters of both the default query params and user defined params.
60    /// Does not overwrite default params.
61    pub fn with_extra_query_param(&mut self, query_param: (String, String)) -> &mut Self {
62        self.app_config.with_extra_query_param(query_param);
63        self
64    }
65
66    /// Extends the query parameters of both the default query params and user defined params.
67    /// Does not overwrite default params.
68    pub fn with_extra_query_parameters(
69        &mut self,
70        query_parameters: HashMap<String, String>,
71    ) -> &mut Self {
72        self.app_config
73            .with_extra_query_parameters(query_parameters);
74        self
75    }
76
77    /// Extends the header parameters of both the default header params and user defined params.
78    /// Does not overwrite default params.
79    pub fn with_extra_header_param<K: Into<HeaderName>, V: Into<HeaderValue>>(
80        &mut self,
81        header_name: K,
82        header_value: V,
83    ) -> &mut Self {
84        self.app_config
85            .with_extra_header_param(header_name, header_value);
86        self
87    }
88
89    /// Extends the header parameters of both the default header params and user defined params.
90    /// Does not overwrite default params.
91    pub fn with_extra_header_parameters(&mut self, header_parameters: HeaderMap) -> &mut Self {
92        self.app_config
93            .with_extra_header_parameters(header_parameters);
94        self
95    }
96
97    pub fn with_scope<T: ToString, I: IntoIterator<Item = T>>(&mut self, scope: I) -> &mut Self {
98        self.app_config.with_scope(scope);
99        self
100    }
101
102    /// Auth Code Authorization Url Builder
103    pub fn auth_code_url_builder(&mut self) -> AuthCodeAuthorizationUrlParameterBuilder {
104        AuthCodeAuthorizationUrlParameterBuilder::new_with_app_config(self.app_config.clone())
105    }
106
107    /// Client Credentials Authorization Url Builder
108    pub fn client_credential_url_builder(
109        &mut self,
110    ) -> ClientCredentialsAuthorizationUrlParameterBuilder {
111        ClientCredentialsAuthorizationUrlParameterBuilder::new_with_app_config(
112            self.app_config.clone(),
113        )
114    }
115
116    /// OpenId Authorization Url Builder
117    pub fn openid_url_builder(&mut self) -> OpenIdAuthorizationUrlParameterBuilder {
118        OpenIdAuthorizationUrlParameterBuilder::new_with_app_config(self.app_config.clone())
119    }
120
121    /// Client Credentials Using X509 Certificate
122    #[cfg(feature = "openssl")]
123    pub fn with_client_x509_certificate(
124        &mut self,
125        certificate: &X509Certificate,
126    ) -> IdentityResult<ClientCertificateCredentialBuilder> {
127        ClientCertificateCredentialBuilder::new_with_certificate(
128            certificate,
129            self.app_config.clone(),
130        )
131    }
132
133    /// Client Credentials Using Client Secret.
134    pub fn with_client_secret(
135        &mut self,
136        client_secret: impl AsRef<str>,
137    ) -> ClientSecretCredentialBuilder {
138        ClientSecretCredentialBuilder::new_with_client_secret(
139            client_secret,
140            self.app_config.clone(),
141        )
142    }
143
144    /// Client Credentials Using Assertion.
145    pub fn with_client_assertion(
146        &mut self,
147        signed_assertion: impl AsRef<str>,
148    ) -> ClientAssertionCredentialBuilder {
149        ClientAssertionCredentialBuilder::new_with_signed_assertion(
150            signed_assertion,
151            self.app_config.clone(),
152        )
153    }
154
155    /// Client Credentials Authorization Url Builder
156    pub fn with_auth_code(
157        &mut self,
158        authorization_code: impl AsRef<str>,
159    ) -> AuthorizationCodeCredentialBuilder {
160        AuthorizationCodeCredentialBuilder::new_with_auth_code(
161            authorization_code,
162            self.app_config.clone(),
163        )
164    }
165
166    /// Auth Code Using Assertion
167    pub fn with_auth_code_assertion(
168        &mut self,
169        authorization_code: impl AsRef<str>,
170        assertion: impl AsRef<str>,
171    ) -> AuthorizationCodeAssertionCredentialBuilder {
172        AuthorizationCodeAssertionCredentialBuilder::from_assertion(
173            authorization_code,
174            assertion,
175            self.app_config.clone(),
176        )
177    }
178
179    /// Auth Code Using X509 Certificate
180    #[cfg(feature = "openssl")]
181    pub fn with_auth_code_x509_certificate(
182        &mut self,
183        authorization_code: impl AsRef<str>,
184        x509: &X509Certificate,
185    ) -> IdentityResult<AuthorizationCodeCertificateCredentialBuilder> {
186        AuthorizationCodeCertificateCredentialBuilder::new_with_auth_code_and_x509(
187            authorization_code,
188            x509,
189            self.app_config.clone(),
190        )
191    }
192
193    //#[cfg(feature = "interactive-auth")]
194
195    /// Auth Code Using OpenId.
196    pub fn with_openid(
197        &mut self,
198        authorization_code: impl AsRef<str>,
199        client_secret: impl AsRef<str>,
200    ) -> OpenIdCredentialBuilder {
201        OpenIdCredentialBuilder::new_with_auth_code_and_secret(
202            authorization_code,
203            client_secret,
204            self.app_config.clone(),
205        )
206    }
207}
208
209impl From<ConfidentialClientApplicationBuilder> for AppConfig {
210    fn from(value: ConfidentialClientApplicationBuilder) -> Self {
211        value.app_config
212    }
213}
214
215impl TryFrom<ApplicationOptions> for ConfidentialClientApplicationBuilder {
216    type Error = AF;
217
218    fn try_from(value: ApplicationOptions) -> Result<Self, Self::Error> {
219        AF::condition(
220            !value.client_id.to_string().is_empty(),
221            "Client Id",
222            "Client Id cannot be empty",
223        )?;
224        AF::condition(
225            !(value.instance.is_some() && value.azure_cloud_instance.is_some()),
226            "Instance | AzureCloudInstance",
227            "Both specify the azure cloud instance and cannot be set at the same time",
228        )?;
229        AF::condition(
230            !(value.tenant_id.is_some() && value.aad_authority_audience.is_some()),
231            "TenantId | AadAuthorityAudience",
232            "Both represent an authority audience and cannot be set at the same time",
233        )?;
234
235        Ok(ConfidentialClientApplicationBuilder {
236            app_config: AppConfig::try_from(value)?,
237        })
238    }
239}
240
241#[allow(dead_code)]
242pub struct PublicClientApplicationBuilder {
243    app_config: AppConfig,
244}
245
246impl PublicClientApplicationBuilder {
247    #[allow(dead_code)]
248    pub fn new(client_id: impl AsRef<str>) -> PublicClientApplicationBuilder {
249        PublicClientApplicationBuilder {
250            app_config: AppConfig::new(client_id.as_ref()),
251        }
252    }
253
254    #[allow(dead_code)]
255    pub fn create_with_application_options(
256        application_options: ApplicationOptions,
257    ) -> IdentityResult<PublicClientApplicationBuilder> {
258        PublicClientApplicationBuilder::try_from(application_options)
259    }
260
261    pub fn with_tenant(&mut self, tenant_id: impl AsRef<str>) -> &mut Self {
262        self.app_config.with_tenant(tenant_id);
263        self
264    }
265
266    pub fn with_authority<T: Into<Authority>>(&mut self, authority: T) -> &mut Self {
267        self.app_config.with_authority(authority.into());
268        self
269    }
270
271    pub fn with_azure_cloud_instance(
272        &mut self,
273        azure_cloud_instance: AzureCloudInstance,
274    ) -> &mut Self {
275        self.app_config
276            .with_azure_cloud_instance(azure_cloud_instance);
277        self
278    }
279
280    /// Extends the query parameters of both the default query params and user defined params.
281    /// Does not overwrite default params.
282    pub fn with_extra_query_param(&mut self, query_param: (String, String)) -> &mut Self {
283        self.app_config.with_extra_query_param(query_param);
284        self
285    }
286
287    /// Extends the query parameters of both the default query params and user defined params.
288    /// Does not overwrite default params.
289    pub fn with_extra_query_parameters(
290        &mut self,
291        query_parameters: HashMap<String, String>,
292    ) -> &mut Self {
293        self.app_config
294            .with_extra_query_parameters(query_parameters);
295        self
296    }
297
298    /// Extends the header parameters of both the default header params and user defined params.
299    /// Does not overwrite default params.
300    pub fn with_extra_header_param<K: Into<HeaderName>, V: Into<HeaderValue>>(
301        &mut self,
302        header_name: K,
303        header_value: V,
304    ) -> &mut Self {
305        self.app_config
306            .with_extra_header_param(header_name, header_value);
307        self
308    }
309
310    /// Extends the header parameters of both the default header params and user defined params.
311    /// Does not overwrite default params.
312    pub fn with_extra_header_parameters(&mut self, header_parameters: HeaderMap) -> &mut Self {
313        self.app_config
314            .with_extra_header_parameters(header_parameters);
315        self
316    }
317
318    pub fn with_scope<T: ToString, I: IntoIterator<Item = T>>(&mut self, scope: I) -> &mut Self {
319        self.app_config.with_scope(scope);
320        self
321    }
322
323    pub fn with_device_code_executor(&mut self) -> DeviceCodePollingExecutor {
324        DeviceCodePollingExecutor::new_with_app_config(self.app_config.clone())
325    }
326
327    pub fn with_device_code(
328        &mut self,
329        device_code: impl AsRef<str>,
330    ) -> DeviceCodeCredentialBuilder {
331        DeviceCodeCredentialBuilder::new_with_device_code(
332            device_code.as_ref(),
333            self.app_config.clone(),
334        )
335    }
336
337    pub fn with_username_password(
338        &mut self,
339        username: impl AsRef<str>,
340        password: impl AsRef<str>,
341    ) -> ResourceOwnerPasswordCredentialBuilder {
342        ResourceOwnerPasswordCredentialBuilder::new_with_username_password(
343            username.as_ref(),
344            password.as_ref(),
345            self.app_config.clone(),
346        )
347    }
348
349    pub fn with_username_password_from_environment(
350    ) -> Result<PublicClientApplication<ResourceOwnerPasswordCredential>, VarError> {
351        EnvironmentCredential::resource_owner_password_credential()
352    }
353
354    pub fn with_auth_code(
355        &mut self,
356        authorization_code: impl AsRef<str>,
357    ) -> AuthorizationCodeSpaCredentialBuilder {
358        AuthorizationCodeSpaCredentialBuilder::new_with_auth_code(
359            authorization_code,
360            self.app_config.clone(),
361        )
362    }
363}
364
365impl TryFrom<ApplicationOptions> for PublicClientApplicationBuilder {
366    type Error = AF;
367
368    fn try_from(value: ApplicationOptions) -> Result<Self, Self::Error> {
369        AF::condition(
370            !value.client_id.is_nil(),
371            "client_id",
372            "Client id cannot be empty",
373        )?;
374        AF::condition(
375            !(value.instance.is_some() && value.azure_cloud_instance.is_some()),
376            "Instance | AzureCloudInstance",
377            "Instance and AzureCloudInstance both specify the azure cloud instance and cannot be set at the same time",
378        )?;
379        AF::condition(
380            !(value.tenant_id.is_some() && value.aad_authority_audience.is_some()),
381            "TenantId | AadAuthorityAudience",
382            "TenantId and AadAuthorityAudience both represent an authority audience and cannot be set at the same time",
383        )?;
384
385        Ok(PublicClientApplicationBuilder {
386            app_config: AppConfig::try_from(value)?,
387        })
388    }
389}
390
391#[cfg(test)]
392mod test {
393    use http::header::AUTHORIZATION;
394    use http::HeaderValue;
395    use url::Url;
396    use uuid::Uuid;
397
398    use crate::identity::{AadAuthorityAudience, AzureCloudInstance, TokenCredentialExecutor};
399
400    use super::*;
401
402    #[test]
403    #[should_panic]
404    fn confidential_client_error_result_on_instance_and_aci() {
405        ConfidentialClientApplicationBuilder::try_from(ApplicationOptions {
406            client_id: Uuid::new_v4(),
407            tenant_id: None,
408            aad_authority_audience: None,
409            instance: Some(Url::parse("https://login.microsoft.com").unwrap()),
410            azure_cloud_instance: Some(AzureCloudInstance::AzurePublic),
411            redirect_uri: None,
412        })
413        .unwrap();
414    }
415
416    #[test]
417    #[should_panic]
418    fn confidential_client_error_result_on_tenant_id_and_aad_audience() {
419        ConfidentialClientApplicationBuilder::try_from(ApplicationOptions {
420            client_id: Uuid::new_v4(),
421            tenant_id: Some("tenant_id".to_owned()),
422            aad_authority_audience: Some(AadAuthorityAudience::AzureAdAndPersonalMicrosoftAccount),
423            instance: None,
424            azure_cloud_instance: None,
425            redirect_uri: None,
426        })
427        .unwrap();
428    }
429
430    #[test]
431    #[should_panic]
432    fn public_client_error_result_on_instance_and_aci() {
433        PublicClientApplicationBuilder::try_from(ApplicationOptions {
434            client_id: Uuid::new_v4(),
435            tenant_id: None,
436            aad_authority_audience: None,
437            instance: Some(Url::parse("https://login.microsoft.com").unwrap()),
438            azure_cloud_instance: Some(AzureCloudInstance::AzurePublic),
439            redirect_uri: None,
440        })
441        .unwrap();
442    }
443
444    #[test]
445    #[should_panic]
446    fn public_client_error_result_on_tenant_id_and_aad_audience() {
447        PublicClientApplicationBuilder::try_from(ApplicationOptions {
448            client_id: Uuid::new_v4(),
449            tenant_id: Some("tenant_id".to_owned()),
450            aad_authority_audience: Some(AadAuthorityAudience::AzureAdAndPersonalMicrosoftAccount),
451            instance: None,
452            azure_cloud_instance: None,
453            redirect_uri: None,
454        })
455        .unwrap();
456    }
457
458    #[test]
459    fn extra_parameters() {
460        let mut confidential_client = ConfidentialClientApplicationBuilder::new("client-id");
461        let mut map = HashMap::new();
462        map.insert("name".to_owned(), "123".to_owned());
463        confidential_client.with_extra_query_parameters(map);
464
465        let mut header_map = HeaderMap::new();
466        header_map.insert(AUTHORIZATION, HeaderValue::from_static("Bearer Token"));
467        confidential_client.with_extra_header_parameters(header_map);
468
469        assert_eq!(
470            confidential_client
471                .app_config
472                .extra_header_parameters
473                .get(AUTHORIZATION)
474                .unwrap(),
475            &HeaderValue::from_static("Bearer Token")
476        );
477        assert_eq!(
478            confidential_client
479                .app_config
480                .extra_query_parameters
481                .get("name")
482                .unwrap(),
483            &String::from("123")
484        );
485    }
486
487    #[test]
488    fn confidential_client_builder() {
489        let client_id = Uuid::new_v4();
490        let confidential_client = ConfidentialClientApplicationBuilder::new(client_id)
491            .with_tenant("tenant-id")
492            .with_client_secret("client-secret")
493            .with_scope(vec!["scope"])
494            .build();
495
496        assert_eq!(
497            confidential_client.client_id().to_string(),
498            client_id.to_string()
499        );
500    }
501}