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 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 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 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 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 pub fn auth_code_url_builder(&mut self) -> AuthCodeAuthorizationUrlParameterBuilder {
104 AuthCodeAuthorizationUrlParameterBuilder::new_with_app_config(self.app_config.clone())
105 }
106
107 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 pub fn openid_url_builder(&mut self) -> OpenIdAuthorizationUrlParameterBuilder {
118 OpenIdAuthorizationUrlParameterBuilder::new_with_app_config(self.app_config.clone())
119 }
120
121 #[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 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 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 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 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 #[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 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 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 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 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 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}