1use crate::aadsts_err_gen::AADSTSError;
20use crate::error::{ErrorResponse, MsalError, AUTH_PENDING};
21#[cfg(feature = "broker")]
22use crate::ssh::{
23 build_ssh_certificate_request_form, parse_ssh_certificate_response, ssh_rsa_public_key_to_jwk,
24 EntraSshCertificate, SshCertificateTokenResponse, SshRsaJwk, AZURE_CLI_APP_ID,
25};
26use base64::engine::general_purpose::URL_SAFE_NO_PAD;
27use base64::Engine;
28#[cfg(feature = "broker")]
29use crypto_glue::traits::SpkiEncodePublicKey;
30#[cfg(feature = "broker")]
31use crypto_glue::x509::oiddb::rfc5912;
32use crypto_glue::x509::Certificate;
33#[cfg(feature = "broker")]
34use der::asn1::{BitString, OctetString, SetOfVec};
35#[cfg(feature = "broker")]
36use der::{Decode, Encode, Sequence};
37use kanidm_hsm_crypto::structures::RS256Key;
38use percent_encoding::percent_decode_str;
39use reqwest::redirect::Policy;
40#[cfg(feature = "proxyable")]
41use reqwest::Proxy;
42use reqwest::{header, Body, Client, Response, Url};
43use scraper::{Html, Selector};
44use serde::de::{self, Deserializer, IgnoredAny, MapAccess, Visitor};
45use serde::ser::{SerializeMap, Serializer};
46use serde::{Deserialize, Serialize};
47use serde_json::{from_str as json_from_str, json, Value};
48#[cfg(feature = "set_timeout")]
49use std::cmp::min;
50use std::collections::HashMap;
51use std::fmt;
52use std::marker::PhantomData;
53use std::str::FromStr;
54use std::sync::RwLock;
55use std::thread::sleep;
56use std::time::Duration;
57use tracing::{error, info, warn};
58use urlencoding::encode as url_encode;
59use uuid::Uuid;
60#[cfg(feature = "broker")]
61use x509_cert::attr::Attribute;
62#[cfg(feature = "broker")]
63use x509_cert::name::Name;
64#[cfg(feature = "broker")]
65use x509_cert::spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned};
66use zeroize::{Zeroize, ZeroizeOnDrop};
67
68#[cfg(feature = "broker")]
69use compact_jwt::{
70 compact::JweCompact,
71 crypto::{JwsTpmRs256Signer, MsOapxbcSessionKey},
72 jwe::Jwe,
73 jws::{Jws, JwsBuilder},
74 traits::{JwsMutSigner, JwsSignable},
75};
76
77#[cfg(feature = "broker")]
78use kanidm_hsm_crypto::{
79 provider::{BoxedDynTpm, Tpm, TpmMsExtensions, TpmRS256},
80 structures::{
81 LoadableMsDeviceEnrolmentKey, LoadableMsHelloKey, LoadableMsOapxbcRsaKey, LoadableRS256Key,
82 MsOapxbcRsaKey, SealedData, StorageKey,
83 },
84 PinValue,
85};
86
87#[cfg(feature = "broker")]
88use openssl::asn1::{Asn1Time, Asn1TimeRef};
89#[cfg(feature = "broker")]
90use openssl::hash::{hash, MessageDigest};
91#[cfg(feature = "broker")]
92use openssl::pkey::{PKey, Public};
93use openssl::rand::rand_bytes;
94#[cfg(feature = "broker")]
95use openssl::rsa::Rsa;
96use openssl::sha::sha256;
97#[cfg(feature = "broker")]
98use openssl::sign::Signer;
99#[cfg(feature = "broker")]
100use openssl::x509::X509;
101#[cfg(feature = "broker")]
102use os_release::OsRelease;
103#[cfg(feature = "broker")]
104use regex::Regex;
105#[cfg(feature = "broker")]
106use serde_json::{from_slice as json_from_slice, to_vec as json_to_vec};
107#[cfg(feature = "broker")]
108use std::convert::TryInto;
109#[cfg(feature = "broker")]
110use std::time::{SystemTime, UNIX_EPOCH};
111#[cfg(feature = "broker")]
112use tracing::debug;
113
114#[cfg(feature = "broker")]
115use crate::discovery::Services;
116#[cfg(feature = "broker")]
117use crate::discovery::{BcryptRsaKeyBlob, EnrollAttrs};
118
119#[cfg(feature = "broker")]
120use libkrimes::ccache::resolve as ccache_resolve;
121#[cfg(feature = "broker")]
122use libkrimes::proto::{AuthenticationReply, DerivedKey, KerberosCredentials, KerberosReply};
123#[cfg(feature = "broker")]
124use std::fs;
125#[cfg(feature = "broker")]
126use std::io::Read;
127
128#[cfg(feature = "broker")]
129use base64::engine::general_purpose::STANDARD;
130#[cfg(feature = "broker")]
131use compact_jwt::JwtError;
132#[cfg(feature = "broker")]
133use serde_json::to_string_pretty;
134#[cfg(feature = "broker")]
135use zeroize::Zeroizing;
136
137use reqwest_cookie_store::{CookieStore, CookieStoreMutex};
138use std::sync::Arc;
139
140#[cfg(feature = "broker")]
141const BROKER_CLIENT_IDENT: &str = "38aa3b87-a06d-4817-b275-7a316988d93b";
142#[cfg(feature = "broker")]
143pub const BROKER_APP_ID: &str = "29d9ed98-a469-4536-ade2-f981bc1d605e";
144#[cfg(feature = "broker")]
145pub const LINUX_BROKER_APP_ID: &str = "b743a22d-6705-4147-8670-d92fa515ee2b";
146#[cfg(feature = "broker")]
147const DRS_APP_ID: &str = "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9";
148#[cfg(feature = "broker")]
149const AZURE_PORTAL_APP_ID: &str = "c44b4083-3bb0-49c1-b47d-974e53cbdf3c";
150const HIMMELBLAU_REDIRECT_URI: &str = "himmelblau://Himmelblau.EntraId.BrokerPlugin";
151
152#[derive(Debug, Deserialize)]
153pub struct SidToName {
154 pub upn: String,
155 pub email: Option<String>,
156 pub name: String,
157 pub family_name: Option<String>,
158 pub sid: String,
159 pub onprem_sam_account_name: Option<String>,
160 pub domain_netbios_name: Option<String>,
161 pub domain_dns_name: Option<String>,
162}
163
164#[cfg(feature = "broker")]
167const FIDO_USER_AGENT: &str =
168 "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0";
169
170#[derive(Default, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
172pub struct DeviceAuthorizationResponse {
173 pub device_code: String,
174 pub user_code: String,
175 pub verification_uri: String,
176 pub verification_uri_complete: Option<String>,
178 pub expires_in: u32,
179 pub interval: Option<u32>,
180 pub message: Option<String>,
181}
182
183#[derive(Clone, Debug, Deserialize)]
184struct ArrUserProofs {
185 #[serde(rename = "authMethodId")]
186 auth_method_id: String,
187 #[serde(rename = "isDefault")]
188 is_default: bool,
189 display: String,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct MfaMethodInfo {
195 pub auth_method_id: String,
197 pub display: String,
199 pub is_default: bool,
201}
202
203impl From<&ArrUserProofs> for MfaMethodInfo {
204 fn from(proof: &ArrUserProofs) -> Self {
205 MfaMethodInfo {
206 auth_method_id: proof.auth_method_id.clone(),
207 display: proof.display.clone(),
208 is_default: proof.is_default,
209 }
210 }
211}
212
213#[derive(Clone, Deserialize)]
214struct AuthConfig {
215 #[serde(rename = "sessionId")]
216 session_id: String,
217 #[serde(rename = "sFT")]
218 sft: Option<String>,
219 #[serde(rename = "sCtx")]
220 sctx: Option<String>,
221 #[serde(rename = "urlPost")]
222 url_post: Option<String>,
223 canary: String,
224 #[serde(rename = "iAllowedIdentities")]
225 allowed_identities: Option<u32>,
226 #[serde(rename = "strServiceExceptionMessage")]
227 service_exception_msg: Option<String>,
228 pgid: Option<String>,
229 #[serde(rename = "urlSkipMfaRegistration")]
230 url_skip_mfa_registration: Option<String>,
231 #[serde(rename = "iRemainingDaysToSkipMfaRegistration")]
232 remaining_days_to_skip_mfa_reg: Option<u32>,
233 #[serde(rename = "arrUserProofs")]
234 arr_user_proofs: Option<Vec<ArrUserProofs>>,
235 #[serde(rename = "arrFidoAllowList")]
236 fido_allow_list: Option<Vec<String>>,
237 #[serde(rename = "urlEndAuth")]
238 url_end_auth: Option<String>,
239 #[serde(rename = "urlBeginAuth")]
240 url_begin_auth: Option<String>,
241 #[serde(rename = "urlFidoLogin")]
242 url_fido_login: Option<String>,
243 #[serde(rename = "urlResume")]
244 url_resume: Option<String>,
245 #[serde(rename = "iMaxPollAttempts")]
246 max_poll_attempts: Option<u32>,
247 #[serde(rename = "iPollingInterval")]
248 polling_interval: Option<u32>,
249 #[serde(rename = "sErrorCode")]
250 error_code: Option<String>,
251 #[serde(rename = "iErrorCode")]
252 error_code2: Option<u32>,
253 #[serde(rename = "sErrTxt")]
254 err_txt: Option<String>,
255 #[serde(rename = "sFidoChallenge")]
256 fido_challenge: Option<String>,
257 #[serde(rename = "sCrossDomainCanary")]
258 cross_domain_canary: Option<String>,
259 #[serde(rename = "urlGetOneTimeCode")]
260 url_get_one_time_code: Option<String>,
261 #[serde(rename = "urlGetCredentialType")]
262 url_get_credential_type: Option<String>,
263 #[serde(rename = "urlSessionState")]
264 url_session_state: Option<String>,
265 #[cfg(feature = "changepassword")]
266 #[serde(rename = "urlAsyncSsprBegin")]
267 url_async_sspr_begin: Option<String>,
268 #[cfg(feature = "changepassword")]
269 #[serde(rename = "urlAsyncSsprPoll")]
270 url_async_sspr_poll: Option<String>,
271 #[serde(rename = "fIsPasskeySupportEnabled")]
272 is_passkey_support_enabled: Option<bool>,
273}
274
275#[derive(Deserialize, Serialize, Default)]
276pub struct MFAAuthContinue {
277 pub msg: String,
278 pub entropy: Option<u8>,
279 pub max_poll_attempts: Option<u32>,
280 pub polling_interval: Option<u32>,
281 pub session_id: String,
282 pub flow_token: String,
283 pub ctx: String,
284 pub canary: String,
285 pub url_end_auth: Option<String>,
286 pub url_post: String,
287 pub url_session_state: Option<String>,
288 pub resource: Option<String>,
289 pub dag: Option<DeviceAuthorizationResponse>,
290 pub fido_challenge: Option<String>,
291 pub fido_allow_list: Option<Vec<String>>,
292 pub cross_domain_canary: Option<String>,
293 pub mfa_methods: Vec<String>,
294 pub mfa_method_details: Vec<MfaMethodInfo>,
295 pub selected_mfa_method_id: Option<String>,
296 pub auth_code: Option<String>,
300 #[deprecated(note = "Use `skip_fido_for_mfa` instead")]
301 pub fido_is_passkey: bool,
302 pub skip_fido_for_mfa: bool,
304 pub has_physical_security_key: bool,
306 pub has_cross_device_passkey: bool,
308}
309
310impl From<DeviceAuthorizationResponse> for MFAAuthContinue {
311 fn from(item: DeviceAuthorizationResponse) -> Self {
312 let msg = match &item.message {
313 Some(msg) => msg.to_string(),
314 None => format!(
315 "Using a browser on another device, visit:\n{}\n \
316 And enter the code:\n{}",
317 item.verification_uri, item.user_code
318 ),
319 };
320 let polling_interval = item.interval.unwrap_or(5);
322 let max_poll_attempts = item.expires_in / polling_interval;
324 MFAAuthContinue {
325 msg,
326 max_poll_attempts: Some(max_poll_attempts),
327 polling_interval: Some(polling_interval * 1000),
328 dag: Some(item),
329 ..Default::default()
330 }
331 }
332}
333
334impl MFAAuthContinue {
335 pub fn mfa_method(&self) -> String {
337 if let Some(method) = self.get_default_mfa_method_details() {
338 method.auth_method_id
339 } else if !self.mfa_methods.is_empty() {
340 for method in self.get_mfa_method_details() {
341 if !self.should_skip_fido_method(&method) {
342 return method.auth_method_id.clone();
343 }
344 }
345 "".to_string()
346 } else {
347 "".to_string()
349 }
350 }
351
352 pub fn get_available_mfa_methods(&self) -> Vec<String> {
354 self.mfa_methods.clone()
355 }
356
357 pub fn get_mfa_method_details(&self) -> Vec<MfaMethodInfo> {
360 self.mfa_method_details.clone()
361 }
362
363 pub fn has_mfa_method(&self, method_id: &str) -> bool {
365 self.get_available_mfa_methods()
366 .contains(&method_id.to_string())
367 }
368
369 pub fn mfa_method_count(&self) -> usize {
371 self.get_available_mfa_methods().len()
372 }
373
374 fn should_skip_fido_method(&self, method: &MfaMethodInfo) -> bool {
375 if method.auth_method_id == "FidoKey" {
376 self.skip_fido_for_mfa
377 } else {
378 false
379 }
380 }
381
382 pub fn get_default_mfa_method_details(&self) -> Option<MfaMethodInfo> {
384 if let Some(details) = self
385 .get_mfa_method_details()
386 .into_iter()
387 .find(|method| method.is_default && !self.should_skip_fido_method(method))
388 {
389 Some(details)
390 } else if !self.mfa_methods.is_empty() {
391 for method in self.get_mfa_method_details() {
392 if !self.should_skip_fido_method(&method) {
393 return Some(method);
394 }
395 }
396 None
397 } else {
398 None
399 }
400 }
401
402 pub fn get_mfa_method_by_id(&self, method_id: &str) -> Option<MfaMethodInfo> {
404 self.get_mfa_method_details()
405 .into_iter()
406 .find(|method| method.auth_method_id == method_id)
407 }
408}
409
410#[derive(Deserialize)]
411struct AuthResponse {
412 #[serde(rename = "Success")]
413 success: bool,
414 #[serde(rename = "Retry")]
415 retry: Option<bool>,
416 #[serde(rename = "Message")]
417 message: Option<String>,
418 #[serde(rename = "ErrCode")] error_code: Option<u32>,
420 #[serde(rename = "Ctx")]
421 ctx: String,
422 #[serde(rename = "FlowToken")]
423 flow_token: String,
424 #[serde(rename = "Entropy")]
425 entropy: u8,
426}
427
428#[derive(Deserialize)]
429struct DeviceCodeStatus {
430 #[serde(rename = "AuthorizationState")]
431 authorization_state: u8,
432}
433
434#[derive(Clone, Deserialize)]
435struct RemoteNgcParams {
436 #[serde(rename = "SessionIdentifier")]
437 session_identifier: String,
438 #[serde(rename = "Entropy")]
439 entropy: u8,
440}
441
442#[derive(Deserialize)]
443struct OTCError {
444 message: String,
445}
446
447#[derive(Deserialize)]
448struct OneTimeCode {
449 #[serde(rename = "RemoteNgcParams")]
450 remote_ngc_params: Option<RemoteNgcParams>,
451 error: Option<OTCError>,
452}
453
454#[derive(Clone, Deserialize)]
455struct FidoParams {
456 #[serde(rename = "AllowList")]
457 fido_allow_list: Vec<String>,
458 #[serde(rename = "HasCrossDeviceCapablePasskey")]
459 has_cross_device_capable_passkey: Option<bool>,
460}
461
462#[allow(dead_code)]
463#[derive(Clone, Deserialize)]
464struct Credentials {
465 #[serde(rename = "FederationRedirectUrl")]
466 federation_redirect_url: Option<String>,
467 #[serde(rename = "HasPassword")]
468 has_password: bool,
469 #[serde(rename = "RemoteNgcParams")]
470 remote_ngc_params: Option<RemoteNgcParams>,
471 #[serde(rename = "FidoParams")]
472 fido_params: Option<FidoParams>,
473 #[serde(rename = "PrefCredential")]
474 pref_credential: u8,
475 #[serde(rename = "HasAccessPass")]
476 has_access_pass: Option<bool>,
477 #[serde(rename = "HasFido")]
478 has_fido: Option<bool>,
479 #[serde(rename = "HasRemoteNGC")]
480 has_remote_ngc: Option<bool>,
481}
482
483#[derive(Clone, Deserialize)]
484struct CredType {
485 #[serde(rename = "Credentials")]
486 credentials: Credentials,
487 #[serde(rename = "ThrottleStatus")]
488 throttle_status: u8,
489 #[serde(rename = "IfExistsResult")]
490 if_exists_result: i32,
491}
492
493impl CredType {
494 fn log_throttle_status(&self) {
502 match self.throttle_status {
503 0 => {}
504 1 => debug!("GetCredentialType reports AAD backend throttling"),
505 2 => debug!("GetCredentialType reports MSA backend throttling"),
506 other => warn!("GetCredentialType returned unknown ThrottleStatus={other}"),
507 }
508 }
509
510 pub fn account_exists(&self) -> Result<bool, MsalError> {
512 match self.if_exists_result {
521 0 | 5 | 6 => Ok(true),
522 1 => Ok(false),
523 2 => Err(MsalError::AADSTSError(AADSTSError::new(90055, None))),
525 -1 | 4 => Err(MsalError::AADSTSError(AADSTSError::new(90006, None))),
527 other => {
528 warn!(
529 "GetCredentialType returned unknown IfExistsResult={other}, ThrottleStatus={}",
530 self.throttle_status
531 );
532 Ok(true)
533 }
534 }
535 }
536
537 pub fn is_personal_account(&self) -> bool {
538 self.if_exists_result == 5
539 }
540}
541
542const MAX_ADFS_RESPONSE_BYTES: usize = 2 * 1024 * 1024;
543const MAX_ADFS_REDIRECTS: usize = 5;
544
545#[derive(Debug, PartialEq, Eq)]
546struct WsFedForm {
547 wa: String,
548 wresult: String,
549 wctx: String,
550}
551
552fn parse_adfs_federation_url(value: &str) -> Result<Option<Url>, MsalError> {
553 let url = Url::parse(value)
554 .map_err(|e| MsalError::URLFormatFailed(format!("Invalid federation URL: {e}")))?;
555 let is_adfs = url
556 .path_segments()
557 .map(|segments| {
558 segments
559 .into_iter()
560 .any(|segment| segment.eq_ignore_ascii_case("adfs"))
561 })
562 .unwrap_or(false);
563 if url.scheme() != "https"
564 || url.host_str().is_none()
565 || !url.username().is_empty()
566 || url.password().is_some()
567 || url.fragment().is_some()
568 || !is_adfs
569 {
570 return Ok(None);
571 }
572 Ok(Some(url))
573}
574
575fn same_origin(left: &Url, right: &Url) -> bool {
576 left.scheme() == right.scheme()
577 && left.host_str() == right.host_str()
578 && left.port_or_known_default() == right.port_or_known_default()
579}
580
581#[derive(Clone, Copy, Debug, PartialEq, Eq)]
582enum AdfsRequestMethod {
583 Get,
584 PostCredentials,
585}
586
587fn resolve_adfs_redirect(
588 origin: &Url,
589 current: &Url,
590 location: &str,
591 status: u16,
592 method: AdfsRequestMethod,
593) -> Result<(Url, AdfsRequestMethod), MsalError> {
594 let next = current
595 .join(location)
596 .map_err(|e| MsalError::URLFormatFailed(format!("Invalid AD FS redirect URL: {e}")))?;
597 if next.scheme() != "https"
598 || !same_origin(origin, &next)
599 || !next.username().is_empty()
600 || next.password().is_some()
601 || next.fragment().is_some()
602 {
603 return Err(MsalError::GeneralFailure(
604 "AD FS attempted an unsafe redirect".to_string(),
605 ));
606 }
607 let next_method = if matches!(status, 307 | 308) {
608 method
609 } else {
610 AdfsRequestMethod::Get
611 };
612 Ok((next, next_method))
613}
614
615fn entra_login_srf_url(authority: &str) -> Result<Url, MsalError> {
616 let mut url = Url::parse(authority)
617 .map_err(|e| MsalError::URLFormatFailed(format!("Invalid authority URL: {e}")))?;
618 if url.scheme() != "https" || url.host_str().is_none() {
619 return Err(MsalError::URLFormatFailed(
620 "Entra authority must be an absolute HTTPS URL".to_string(),
621 ));
622 }
623 url.set_path("/login.srf");
624 url.set_query(None);
625 url.set_fragment(None);
626 Ok(url)
627}
628
629fn parse_ws_fed_form(text: &str) -> Result<WsFedForm, MsalError> {
630 let document = Html::parse_document(text);
631 let form_selector = Selector::parse("form")
632 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing AD FS form: {e:?}")))?;
633 let input_selector = Selector::parse("input")
634 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing AD FS inputs: {e:?}")))?;
635
636 for form in document.select(&form_selector) {
637 let mut wa = None;
638 let mut wresult = None;
639 let mut wctx = None;
640 for input in form.select(&input_selector) {
641 let Some(name) = input.value().attr("name") else {
642 continue;
643 };
644 let Some(value) = input.value().attr("value") else {
645 continue;
646 };
647 if name.eq_ignore_ascii_case("wa") {
648 wa = Some(value.to_string());
649 } else if name.eq_ignore_ascii_case("wresult") {
650 wresult = Some(value.to_string());
651 } else if name.eq_ignore_ascii_case("wctx") {
652 wctx = Some(value.to_string());
653 }
654 }
655 if let (Some(wa), Some(wresult), Some(wctx)) = (wa, wresult, wctx) {
656 if wa == "wsignin1.0" && !wresult.is_empty() && !wctx.is_empty() {
657 return Ok(WsFedForm { wa, wresult, wctx });
658 }
659 }
660 }
661
662 Err(MsalError::GeneralFailure(
663 "AD FS did not return a supported WS-Federation sign-in response".to_string(),
664 ))
665}
666
667#[derive(Default, Clone, Deserialize, Serialize)]
668#[cfg_attr(test, derive(Eq, PartialEq, Debug))]
669pub struct IdToken {
670 pub name: String,
671 pub oid: String,
672 pub preferred_username: Option<String>,
673 pub puid: Option<String>,
674 pub tenant_region_scope: Option<String>,
675 pub tid: String,
676 #[serde(skip_serializing)]
677 pub raw: Option<String>,
678}
679
680fn decode_string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
681where
682 T: Deserialize<'de> + FromStr<Err = MsalError>,
683 D: Deserializer<'de>,
684{
685 struct StringOrStruct<T>(PhantomData<fn() -> T>);
686
687 impl<'de, T> Visitor<'de> for StringOrStruct<T>
688 where
689 T: Deserialize<'de> + FromStr<Err = MsalError>,
690 {
691 type Value = T;
692
693 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
694 formatter.write_str("string or map")
695 }
696
697 fn visit_str<E>(self, value: &str) -> Result<T, E>
698 where
699 E: de::Error,
700 {
701 FromStr::from_str(value)
702 .map_err(|e| serde::de::Error::custom(format!("Failed to parse string: {:?}", e)))
703 }
704
705 fn visit_map<M>(self, map: M) -> Result<T, M::Error>
706 where
707 M: MapAccess<'de>,
708 {
709 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
710 }
711 }
712
713 deserializer.deserialize_any(StringOrStruct(PhantomData))
714}
715
716impl FromStr for IdToken {
717 type Err = MsalError;
718 fn from_str(s: &str) -> Result<Self, Self::Err> {
719 let mut siter = s.splitn(3, '.');
720 if siter.next().is_none() {
721 return Err(MsalError::InvalidParse(
722 "Failed parsing id_token header".to_string(),
723 ));
724 }
725 let payload_str = match siter.next() {
726 Some(payload_str) => URL_SAFE_NO_PAD
727 .decode(payload_str)
728 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing id_token: {}", e)))
729 .and_then(|bytes| {
730 String::from_utf8(bytes).map_err(|e| {
731 MsalError::InvalidParse(format!("Failed parsing id_token: {}", e))
732 })
733 })?,
734 None => {
735 return Err(MsalError::InvalidParse(
736 "Failed parsing id_token payload".to_string(),
737 ));
738 }
739 };
740 let mut payload: IdToken = json_from_str(&payload_str).map_err(|e| {
741 MsalError::InvalidParse(format!("Failed parsing id_token from json: {}", e))
742 })?;
743 payload.raw = Some(s.to_string());
744 Ok(payload)
745 }
746}
747
748#[derive(Clone, Default, Deserialize, Serialize)]
749#[cfg_attr(test, derive(Eq, PartialEq, Debug))]
750pub struct ClientInfo {
751 pub uid: Option<Uuid>,
752 pub utid: Option<Uuid>,
753}
754
755impl FromStr for ClientInfo {
756 type Err = MsalError;
757 fn from_str(s: &str) -> Result<Self, Self::Err> {
758 let client_info: Value = URL_SAFE_NO_PAD
759 .decode(s)
760 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing client_info: {}", e)))
761 .and_then(|bytes| {
762 String::from_utf8(bytes).map_err(|e| {
763 MsalError::InvalidParse(format!("Failed parsing client_info: {}", e))
764 })
765 })
766 .and_then(|client_info_str| {
767 json_from_str(&client_info_str).map_err(|e| {
768 MsalError::InvalidParse(format!("Failed parsing client_info: {}", e))
769 })
770 })?;
771
772 let uid_str = client_info["uid"].to_string();
773 let uid = Uuid::parse_str(uid_str.trim_matches('"'))
774 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing client_info: {}", e)))?;
775
776 let utid_str = client_info["utid"].to_string();
777 let utid = Uuid::parse_str(utid_str.trim_matches('"'))
778 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing client_info: {}", e)))?;
779
780 Ok(ClientInfo {
781 uid: Some(uid),
782 utid: Some(utid),
783 })
784 }
785}
786
787fn v2_scope_to_v1_resource(scope: &str) -> String {
796 if let Some(scheme_end) = scope.find("://") {
797 let authority_start = scheme_end + 3;
798 let after_authority = &scope[authority_start..];
799 if let Some(slash_pos) = after_authority.find('/') {
800 let permission = &after_authority[slash_pos + 1..];
801 if !permission.is_empty() {
802 return scope[..authority_start + slash_pos].to_string();
803 }
804 }
805 }
806 scope.trim_end_matches('/').to_string()
807}
808
809fn decode_number_from_string<'de, D>(d: D) -> Result<u32, D::Error>
810where
811 D: Deserializer<'de>,
812{
813 let v: Value = Deserialize::deserialize(d)?;
814 match v {
815 Value::Number(n) => Ok(n
816 .as_u64()
817 .ok_or(serde::de::Error::custom("Expected number or string"))?
818 as u32),
819 Value::String(s) => s
820 .parse::<u32>()
821 .map_err(|e| serde::de::Error::custom(format!("{}", e))),
822 _ => Err(serde::de::Error::custom("Expected number or string")),
823 }
824}
825
826#[derive(Deserialize, Zeroize, ZeroizeOnDrop)]
827pub struct AccessTokenPayload {
828 amr: Vec<String>,
829 tid: String,
830 unique_name: Option<String>,
833 upn: Option<String>,
834}
835
836#[derive(Clone, Deserialize, Zeroize, ZeroizeOnDrop)]
837pub struct UserToken {
838 pub token_type: String,
839 pub scope: Option<String>,
840 #[serde(deserialize_with = "decode_number_from_string")]
841 pub expires_in: u32,
842 #[serde(deserialize_with = "decode_number_from_string")]
843 pub ext_expires_in: u32,
844 pub access_token: Option<String>,
845 pub refresh_token: String,
846 #[serde(deserialize_with = "decode_string_or_struct", default)]
847 #[zeroize(skip)]
848 pub id_token: IdToken,
849 #[serde(deserialize_with = "decode_string_or_struct", default)]
850 #[zeroize(skip)]
851 pub client_info: ClientInfo,
852 #[cfg(feature = "broker")]
853 #[zeroize(skip)]
854 pub prt: Option<SealedData>,
855}
856
857#[derive(Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
858pub struct AuthorizationCodePkceFlow {
859 pub auth_url: String,
860 pub redirect_uri: String,
861 pub state: String,
862 scope: String,
863 code_verifier: String,
864}
865
866impl AuthorizationCodePkceFlow {
867 pub fn auth_url(&self) -> &str {
868 &self.auth_url
869 }
870
871 pub fn redirect_uri(&self) -> &str {
872 &self.redirect_uri
873 }
874
875 pub fn state(&self) -> &str {
876 &self.state
877 }
878}
879
880fn generate_base64url_random(bytes_len: usize) -> Result<String, MsalError> {
881 let mut bytes = vec![0u8; bytes_len];
882 rand_bytes(&mut bytes)
883 .map_err(|e| MsalError::CryptoFail(format!("Failed generating random bytes: {}", e)))?;
884 Ok(URL_SAFE_NO_PAD.encode(bytes))
885}
886
887fn pkce_code_challenge(code_verifier: &str) -> String {
888 URL_SAFE_NO_PAD.encode(sha256(code_verifier.as_bytes()))
889}
890
891impl UserToken {
892 pub fn tenant_id(&self) -> Result<String, MsalError> {
899 if !self.id_token.tid.is_empty() {
900 Ok(self.id_token.tid.clone())
901 } else if let Some(utid) = self.client_info.utid {
902 Ok(utid.to_string())
903 } else if let Some(access_token) = &self.access_token {
904 let mut siter = access_token.splitn(3, '.');
905 siter.next(); let payload: AccessTokenPayload = json_from_str(
907 &String::from_utf8(
908 URL_SAFE_NO_PAD
909 .decode(siter.next().ok_or_else(|| {
910 MsalError::InvalidParse("Payload not present".to_string())
911 })?)
912 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?,
913 )
914 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
915 )
916 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
917 Ok(payload.tid.clone())
918 } else {
919 Err(MsalError::GeneralFailure(
920 "No tid available for UserToken".to_string(),
921 ))
922 }
923 }
924
925 pub fn uuid(&self) -> Result<Uuid, MsalError> {
932 Uuid::parse_str(&self.id_token.oid).map_err(|e| MsalError::InvalidParse(format!("{}", e)))
933 }
934
935 pub fn spn(&self) -> Result<String, MsalError> {
942 match &self.id_token.preferred_username {
943 Some(spn) => Ok(spn.to_string()),
944 None => match &self.access_token {
946 Some(access_token) => {
947 let mut siter = access_token.splitn(3, '.');
948 siter.next(); let payload: AccessTokenPayload = json_from_str(
950 &String::from_utf8(
951 URL_SAFE_NO_PAD
952 .decode(siter.next().ok_or_else(|| {
953 MsalError::InvalidParse("Payload not present".to_string())
954 })?)
955 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?,
956 )
957 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
958 )
959 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
960 if let Some(upn) = &payload.upn {
961 Ok(upn.clone())
962 } else if let Some(unique_name) = &payload.unique_name {
963 Ok(unique_name.clone())
964 } else {
965 Err(MsalError::GeneralFailure(
966 "No spn available for UserToken".to_string(),
967 ))
968 }
969 }
970 None => Err(MsalError::GeneralFailure(
971 "No spn available for UserToken".to_string(),
972 )),
973 },
974 }
975 }
976
977 pub fn amr_mfa(&self) -> Result<bool, MsalError> {
984 match &self.access_token {
985 Some(access_token) => {
986 let mut siter = access_token.splitn(3, '.');
987 siter.next(); let payload: AccessTokenPayload = json_from_str(
989 &String::from_utf8(
990 URL_SAFE_NO_PAD
991 .decode(siter.next().ok_or_else(|| {
992 MsalError::InvalidParse("Payload not present".to_string())
993 })?)
994 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?,
995 )
996 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
997 )
998 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
999 Ok(payload.amr.iter().any(|s| s == "ngcmfa" || s == "mfa"))
1000 }
1001 None => Err(MsalError::GeneralFailure(
1002 "No access token available for UserToken".to_string(),
1003 )),
1004 }
1005 }
1006
1007 pub fn amr_ngcmfa(&self) -> Result<bool, MsalError> {
1018 match &self.access_token {
1019 Some(access_token) => {
1020 let mut siter = access_token.splitn(3, '.');
1021 siter.next(); let payload: AccessTokenPayload = json_from_str(
1023 &String::from_utf8(
1024 URL_SAFE_NO_PAD
1025 .decode(siter.next().ok_or_else(|| {
1026 MsalError::InvalidParse("Payload not present".to_string())
1027 })?)
1028 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?,
1029 )
1030 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
1031 )
1032 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
1033 Ok(payload.amr.iter().any(|s| s == "ngcmfa"))
1034 }
1035 None => Err(MsalError::GeneralFailure(
1036 "No access token available for UserToken".to_string(),
1037 )),
1038 }
1039 }
1040}
1041
1042#[cfg(feature = "broker")]
1043#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1044struct UsernamePasswordAuthenticationPayload {
1045 client_id: String,
1046 request_nonce: String,
1047 scope: String,
1048 win_ver: Option<String>,
1049 grant_type: String,
1050 username: String,
1051 password: String,
1052}
1053
1054#[cfg(feature = "broker")]
1055impl UsernamePasswordAuthenticationPayload {
1056 fn new(username: &str, password: &str, request_nonce: &str) -> Self {
1057 let os_release = match OsRelease::new() {
1058 Ok(os_release) => Some(format!(
1059 "{} {}",
1060 os_release.pretty_name, os_release.version_id
1061 )),
1062 Err(_) => None,
1063 };
1064 UsernamePasswordAuthenticationPayload {
1065 client_id: BROKER_CLIENT_IDENT.to_string(),
1066 request_nonce: request_nonce.to_string(),
1067 scope: "openid aza ugs".to_string(),
1068 win_ver: os_release,
1069 grant_type: "password".to_string(),
1070 username: username.to_string(),
1071 password: password.to_string(),
1072 }
1073 }
1074}
1075
1076#[cfg(feature = "broker")]
1077#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1078struct RefreshTokenAuthenticationPayload {
1079 client_id: String,
1080 request_nonce: String,
1081 scope: String,
1082 win_ver: Option<String>,
1083 grant_type: String,
1084 refresh_token: String,
1085}
1086
1087#[cfg(feature = "broker")]
1088impl RefreshTokenAuthenticationPayload {
1089 fn new(refresh_token: &str, request_nonce: &str) -> Self {
1090 let os_release = match OsRelease::new() {
1091 Ok(os_release) => Some(format!(
1092 "{} {}",
1093 os_release.pretty_name, os_release.version_id
1094 )),
1095 Err(_) => None,
1096 };
1097 RefreshTokenAuthenticationPayload {
1098 client_id: BROKER_APP_ID.to_string(),
1099 request_nonce: request_nonce.to_string(),
1100 scope: "openid aza ugs".to_string(),
1101 win_ver: os_release,
1102 grant_type: "refresh_token".to_string(),
1103 refresh_token: refresh_token.to_string(),
1104 }
1105 }
1106}
1107
1108#[cfg(feature = "broker")]
1109#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1110struct HelloForBusinessAssertion {
1111 iss: String,
1112 aud: String,
1113 iat: u64,
1114 exp: u64,
1115 scope: String,
1116 request_nonce: String,
1117}
1118
1119#[cfg(feature = "broker")]
1120impl HelloForBusinessAssertion {
1121 fn new(username: &str, request_nonce: &str) -> Result<Self, MsalError> {
1122 let iat: u64 = SystemTime::now()
1123 .duration_since(UNIX_EPOCH)
1124 .map_err(|e| MsalError::GeneralFailure(format!("Failed choosing iat: {}", e)))?
1125 .as_secs();
1126 Ok(HelloForBusinessAssertion {
1127 iss: username.to_string(),
1128 aud: "common".to_string(),
1129 iat: iat - 300,
1130 exp: iat + 300,
1131 scope: "openid aza ugs".to_string(),
1132 request_nonce: request_nonce.to_string(),
1133 })
1134 }
1135}
1136
1137#[cfg(feature = "broker")]
1138#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1139struct HelloForBusinessPayload {
1140 client_id: String,
1141 request_nonce: String,
1142 scope: String,
1143 win_ver: Option<String>,
1144 grant_type: String,
1145 username: String,
1146 assertion: String,
1147}
1148
1149#[cfg(feature = "broker")]
1150impl HelloForBusinessPayload {
1151 fn new(username: &str, assertion: &str, request_nonce: &str) -> Self {
1152 let os_release = match OsRelease::new() {
1153 Ok(os_release) => Some(format!(
1154 "{} {}",
1155 os_release.pretty_name, os_release.version_id
1156 )),
1157 Err(_) => None,
1158 };
1159 HelloForBusinessPayload {
1160 client_id: BROKER_APP_ID.to_string(),
1161 request_nonce: request_nonce.to_string(),
1162 scope: "openid aza ugs".to_string(),
1163 win_ver: os_release,
1164 grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer".to_string(),
1165 username: username.to_string(),
1166 assertion: assertion.to_string(),
1167 }
1168 }
1169}
1170
1171#[cfg(feature = "broker")]
1172#[derive(Serialize, Clone, Default)]
1173struct ExchangePRTPayload {
1174 win_ver: Option<String>,
1175 scope: String,
1176 resource: Option<String>,
1177 request_nonce: String,
1178 refresh_token: String,
1179 iss: String,
1180 grant_type: String,
1181 client_id: String,
1182 aud: String,
1183}
1184
1185#[cfg(feature = "broker")]
1186impl ExchangePRTPayload {
1187 fn new(
1188 prt: &PrimaryRefreshToken,
1189 nonce: &str,
1190 resource: Option<String>,
1191 request_prt: bool,
1192 ) -> Result<Self, MsalError> {
1193 let mut scopes = "openid ugs".to_string();
1194 if request_prt {
1195 scopes = format!("{} aza", scopes);
1196 }
1197 let os_release = match OsRelease::new() {
1198 Ok(os_release) => Some(format!(
1199 "{} {}",
1200 os_release.pretty_name, os_release.version_id
1201 )),
1202 Err(_) => None,
1203 };
1204 Ok(ExchangePRTPayload {
1205 win_ver: os_release,
1206 scope: scopes,
1207 resource,
1208 request_nonce: nonce.to_string(),
1209 refresh_token: prt.refresh_token.clone(),
1210 iss: "aad:brokerplugin".to_string(),
1211 grant_type: "refresh_token".to_string(),
1212 client_id: BROKER_CLIENT_IDENT.to_string(),
1213 aud: "login.microsoftonline.com".to_string(),
1214 })
1215 }
1216}
1217
1218#[cfg(feature = "broker")]
1222#[derive(Serialize, Clone, Default)]
1223struct ExchangePRTForATPayload {
1224 win_ver: Option<String>,
1225 scope: String,
1226 #[serde(skip_serializing_if = "Option::is_none")]
1227 resource: Option<String>,
1228 request_nonce: String,
1229 refresh_token: String,
1230 iss: String,
1231 grant_type: String,
1232 client_id: String,
1233 redirect_uri: String,
1234 aud: String,
1235}
1236
1237#[cfg(feature = "broker")]
1238impl ExchangePRTForATPayload {
1239 fn new(
1240 prt: &PrimaryRefreshToken,
1241 nonce: &str,
1242 scopes: &str,
1243 client_id: &str,
1244 redirect_uri: &str,
1245 resource: Option<&str>,
1246 ) -> Result<Self, MsalError> {
1247 let os_release = match OsRelease::new() {
1248 Ok(os_release) => Some(format!(
1249 "{} {}",
1250 os_release.pretty_name, os_release.version_id
1251 )),
1252 Err(_) => None,
1253 };
1254 Ok(ExchangePRTForATPayload {
1255 win_ver: os_release,
1256 scope: scopes.to_string(),
1257 resource: resource.map(|r| r.to_string()),
1258 request_nonce: nonce.to_string(),
1259 refresh_token: prt.refresh_token.clone(),
1260 iss: "aad:brokerplugin".to_string(),
1261 grant_type: "refresh_token".to_string(),
1262 client_id: client_id.to_string(),
1263 redirect_uri: redirect_uri.to_string(),
1264 aud: "login.microsoftonline.com".to_string(),
1265 })
1266 }
1267}
1268
1269#[cfg(feature = "broker")]
1270#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1271struct RefreshTokenCredentialPayload {
1272 #[serde(skip_serializing_if = "Option::is_none")]
1273 iat: Option<i64>,
1274 refresh_token: String,
1275 #[serde(skip_serializing_if = "Option::is_none")]
1276 request_nonce: Option<String>,
1277 ua_client_id: Option<String>,
1278 ua_redirect_uri: Option<String>,
1279 x_client_platform: Option<String>,
1280 win_ver: Option<String>,
1281 windows_api_version: Option<String>,
1282}
1283
1284#[cfg(feature = "broker")]
1285impl RefreshTokenCredentialPayload {
1286 fn new(prt: &PrimaryRefreshToken, nonce: Option<&str>) -> Result<Self, MsalError> {
1287 let os_release = match OsRelease::new() {
1288 Ok(os_release) => Some(format!(
1289 "{} {}",
1290 os_release.pretty_name, os_release.version_id
1291 )),
1292 Err(_) => None,
1293 };
1294 let (iat, request_nonce) = match nonce {
1298 Some(n) => (None, Some(n.to_string())),
1299 None => {
1300 let iat: i64 = SystemTime::now()
1301 .duration_since(UNIX_EPOCH)
1302 .map_err(|e| MsalError::GeneralFailure(format!("Failed choosing iat: {}", e)))?
1303 .as_secs()
1304 .try_into()
1305 .map_err(|e| {
1306 MsalError::GeneralFailure(format!("Failed choosing iat: {}", e))
1307 })?;
1308 (Some(iat), None)
1309 }
1310 };
1311 Ok(RefreshTokenCredentialPayload {
1312 iat,
1313 refresh_token: prt.refresh_token.clone(),
1314 request_nonce,
1315 ua_client_id: None,
1316 ua_redirect_uri: None,
1317 x_client_platform: None,
1318 win_ver: os_release,
1319 windows_api_version: Some("2.0.1".to_string()),
1320 })
1321 }
1322}
1323
1324#[cfg(feature = "broker")]
1325#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1326struct DeviceCredentialPayload {
1327 grant_type: String,
1328 iss: String,
1329 request_nonce: String,
1330 ua_client_id: Option<String>,
1331 ua_redirect_uri: Option<String>,
1332 x_client_platform: Option<String>,
1333 win_ver: Option<String>,
1334 windows_api_version: Option<String>,
1335}
1336
1337#[cfg(feature = "broker")]
1338impl DeviceCredentialPayload {
1339 fn new(nonce: &str) -> Result<Self, MsalError> {
1340 let os_release = match OsRelease::new() {
1341 Ok(os_release) => Some(format!(
1342 "{} {}",
1343 os_release.pretty_name, os_release.version_id
1344 )),
1345 Err(_) => None,
1346 };
1347 Ok(DeviceCredentialPayload {
1348 grant_type: "device_auth".to_string(),
1349 iss: "aad:brokerplugin".to_string(),
1350 request_nonce: nonce.to_string(),
1351 ua_client_id: None,
1352 ua_redirect_uri: None,
1353 x_client_platform: None,
1354 win_ver: os_release,
1355 windows_api_version: Some("2.0.1".to_string()),
1356 })
1357 }
1358}
1359
1360#[cfg(feature = "broker")]
1361#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1362struct P2PDeviceCertificatePayload {
1363 client_id: String,
1364 request_nonce: String,
1365 win_ver: Option<String>,
1366 grant_type: String,
1367 cert_token_use: String,
1368 csr_type: String,
1369 csr: String,
1370 netbios_name: String,
1371 dns_names: Vec<String>,
1372}
1373
1374#[cfg(feature = "broker")]
1375impl P2PDeviceCertificatePayload {
1376 fn new(nonce: &str, csr: &str, device_name: &str, dns_names: &[String]) -> Self {
1377 let os_release = match OsRelease::new() {
1378 Ok(os_release) => Some(format!(
1379 "{} {}",
1380 os_release.pretty_name, os_release.version_id
1381 )),
1382 Err(_) => None,
1383 };
1384 P2PDeviceCertificatePayload {
1385 client_id: BROKER_CLIENT_IDENT.to_string(),
1386 request_nonce: nonce.to_string(),
1387 win_ver: os_release,
1388 grant_type: "device_auth".to_string(),
1389 cert_token_use: "device_cert".to_string(),
1390 csr_type: "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
1391 .to_string(),
1392 csr: csr.to_string(),
1393 netbios_name: device_name.to_string(),
1394 dns_names: dns_names.to_vec(),
1395 }
1396 }
1397}
1398
1399#[cfg(feature = "broker")]
1400#[derive(Serialize)]
1401struct P2PDeviceCertificateHeader<'a> {
1402 alg: &'static str,
1403 typ: &'static str,
1404 x5c: &'a str,
1409}
1410
1411#[cfg(feature = "broker")]
1412#[derive(Serialize)]
1413struct P2PUserCertificateHeader<'a> {
1414 alg: &'static str,
1415 typ: &'static str,
1416 ctx: &'a str,
1417}
1418
1419#[cfg(feature = "broker")]
1420#[derive(Serialize, Clone, Default, Zeroize, ZeroizeOnDrop)]
1421struct P2PUserCertificatePayload {
1422 iss: String,
1423 grant_type: String,
1424 aud: String,
1425 request_nonce: String,
1426 scope: String,
1427 refresh_token: String,
1428 client_id: String,
1429 cert_token_use: String,
1430 csr_type: String,
1431 csr: String,
1432}
1433
1434#[cfg(feature = "broker")]
1435impl P2PUserCertificatePayload {
1436 fn new(prt: &PrimaryRefreshToken, nonce: &str, csr: &str) -> Self {
1437 P2PUserCertificatePayload {
1438 iss: "aad:brokerplugin".to_string(),
1439 grant_type: "refresh_token".to_string(),
1440 aud: "login.microsoftonline.com".to_string(),
1441 request_nonce: nonce.to_string(),
1442 scope: "openid aza ugs".to_string(),
1443 refresh_token: prt.refresh_token.clone(),
1444 client_id: BROKER_CLIENT_IDENT.to_string(),
1445 cert_token_use: "user_cert".to_string(),
1446 csr_type: "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
1447 .to_string(),
1448 csr: csr.to_string(),
1449 }
1450 }
1451
1452 fn redacted(&self) -> Result<Value, MsalError> {
1454 let mut value = serde_json::to_value(self).map_err(|e| {
1455 MsalError::InvalidJson(format!("Failed serializing P2P user payload: {}", e))
1456 })?;
1457 value["refresh_token"] = "**********".into();
1458 Ok(value)
1459 }
1460}
1461
1462#[cfg(feature = "broker")]
1463#[derive(Deserialize)]
1464struct P2PCertificateResponse {
1465 x5c: String,
1466 x5c_ca: String,
1467}
1468
1469#[cfg(feature = "broker")]
1470#[derive(Debug, Deserialize)]
1471struct Nonce {
1472 #[serde(rename = "Nonce")]
1473 nonce: String,
1474}
1475
1476#[cfg(feature = "broker")]
1477trait Tgt {
1478 fn derived_key(
1480 &self,
1481 tpm: &mut BoxedDynTpm,
1482 transport_key: &MsOapxbcRsaKey,
1483 storage_key: &StorageKey,
1484 session_key: &SessionKey,
1485 ) -> Result<DerivedKey, MsalError>;
1486
1487 fn as_rep(&self) -> Result<AuthenticationReply, MsalError>;
1489}
1490
1491#[cfg(feature = "broker")]
1492impl FromStr for StructuredTgt {
1493 type Err = MsalError;
1494 fn from_str(s: &str) -> Result<Self, Self::Err> {
1495 json_from_str(s).map_err(|e| MsalError::InvalidParse(format!("Failed parsing tgt: {}", e)))
1496 }
1497}
1498
1499#[cfg(feature = "broker")]
1500#[derive(Default, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
1501#[cfg_attr(test, derive(Eq, PartialEq, Debug))]
1502pub struct StructuredTgt {
1503 #[serde(rename = "clientKey")]
1504 client_key: Option<String>,
1505 #[serde(rename = "keyType")]
1506 key_type: u32,
1507 error: Option<String>,
1508 #[serde(rename = "messageBuffer")]
1509 message_buffer: Option<String>,
1510 pub realm: Option<String>,
1511 pub sn: Option<String>,
1512 pub cn: Option<String>,
1513 #[serde(rename = "sessionKeyType")]
1514 pub session_key_type: u32,
1515 #[serde(rename = "accountType")]
1516 pub account_type: u32,
1517}
1518
1519#[cfg(feature = "broker")]
1520impl Tgt for StructuredTgt {
1521 fn derived_key(
1522 &self,
1523 tpm: &mut BoxedDynTpm,
1524 transport_key: &MsOapxbcRsaKey,
1525 storage_key: &StorageKey,
1526 session_key: &SessionKey,
1527 ) -> Result<DerivedKey, MsalError> {
1528 let client_key = match self.client_key.as_deref() {
1529 Some(k) => {
1530 if k.contains('.') {
1533 let jwe = JweCompact::from_str(k)
1535 .map_err(|e| MsalError::InvalidParse(format!("{:?}", e)))?;
1536 session_key
1537 .decipher_tgt_client_key(tpm, transport_key, storage_key, &jwe)
1538 .map_err(|e| {
1539 MsalError::CryptoFail(format!(
1540 "Failed to unwrap the TGT session key: {:?}",
1541 e
1542 ))
1543 })?
1544 } else {
1545 Zeroizing::new(STANDARD.decode(k).map_err(|e| {
1547 MsalError::CryptoFail(format!(
1548 "Failed to decode base64 client key: {:?}",
1549 e
1550 ))
1551 })?)
1552 }
1553 }
1554 None => {
1555 return Err(MsalError::CryptoFail(
1556 "TGT client key is missing".to_string(),
1557 ))
1558 }
1559 };
1560
1561 match self.key_type {
1562 18 => {
1563 let k: [u8; 32] = client_key.as_slice().try_into().map_err(|_| {
1564 MsalError::CryptoFail("Unexpected TGT session key length".to_string())
1565 })?;
1566 let dk = DerivedKey::Aes256CtsHmacSha196 {
1567 k,
1568 i: 0,
1569 s: String::new(),
1570 kvno: 1,
1571 };
1572 Ok(dk)
1573 }
1574 _ => Err(MsalError::CryptoFail(format!(
1575 "Unexpected TGT session key type {}",
1576 self.key_type
1577 ))),
1578 }
1579 }
1580
1581 fn as_rep(&self) -> Result<AuthenticationReply, MsalError> {
1582 let buf = match self.message_buffer.as_deref() {
1583 Some(buf) => STANDARD
1584 .decode(buf)
1585 .map_err(|e| MsalError::CryptoFail(format!("{:?}", e)))?,
1586 None => {
1587 return Err(MsalError::CryptoFail(
1588 "TGT message buffer is missing".to_string(),
1589 ))
1590 }
1591 };
1592
1593 let reply = match KerberosReply::try_from(buf.as_slice()) {
1594 Ok(r) => r,
1595 Err(e) => {
1596 return Err(MsalError::GeneralFailure(format!(
1597 "Failed to decode the cloud kerberos reply: {:?}",
1598 e
1599 )));
1600 }
1601 };
1602
1603 match reply {
1604 KerberosReply::AS(as_rep) => Ok(as_rep),
1605 _ => Err(MsalError::GeneralFailure(
1606 "Unexpected kerberos reply message".to_string(),
1607 )),
1608 }
1609 }
1610}
1611
1612#[derive(Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
1613#[cfg_attr(test, derive(Eq, PartialEq, Debug))]
1614struct RawTgt {
1615 tgt_message_buffer: String,
1617
1618 tgt_client_key: String,
1623
1624 tgt_key_type: u32,
1627}
1628
1629#[cfg(feature = "broker")]
1630impl Tgt for RawTgt {
1631 fn derived_key(
1632 &self,
1633 tpm: &mut BoxedDynTpm,
1634 transport_key: &MsOapxbcRsaKey,
1635 storage_key: &StorageKey,
1636 session_key: &SessionKey,
1637 ) -> Result<DerivedKey, MsalError> {
1638 let jwe = JweCompact::from_str(&self.tgt_client_key)
1639 .map_err(|e| MsalError::InvalidParse(format!("{:?}", e)))?;
1640 let long_term_krb_session_key = session_key
1641 .decipher_tgt_client_key(tpm, transport_key, storage_key, &jwe)
1642 .map_err(|e| {
1643 MsalError::CryptoFail(format!("Failed to unwrap the TGT session key: {:?}", e))
1644 })?;
1645
1646 let dk = match self.tgt_key_type {
1647 18 => {
1648 let k: [u8; 32] =
1649 long_term_krb_session_key
1650 .as_slice()
1651 .try_into()
1652 .map_err(|_| {
1653 MsalError::CryptoFail("Unexpected TGT session key length".to_string())
1654 })?;
1655 DerivedKey::Aes256CtsHmacSha196 {
1656 k,
1657 i: 0,
1658 s: String::new(),
1659 kvno: 1,
1660 }
1661 }
1662 _ => {
1663 return Err(MsalError::CryptoFail(format!(
1664 "Unexpected TGT session key type {}",
1665 self.tgt_key_type
1666 )));
1667 }
1668 };
1669 Ok(dk)
1670 }
1671
1672 fn as_rep(&self) -> Result<AuthenticationReply, MsalError> {
1673 let buf = STANDARD
1674 .decode(&self.tgt_message_buffer)
1675 .map_err(|e| MsalError::CryptoFail(format!("{:?}", e)))?;
1676
1677 let reply = match KerberosReply::try_from(buf.as_slice()) {
1678 Ok(r) => r,
1679 Err(e) => {
1680 return Err(MsalError::GeneralFailure(format!(
1681 "Failed to decode the cloud kerberos reply: {:?}",
1682 e
1683 )));
1684 }
1685 };
1686
1687 match reply {
1688 KerberosReply::AS(as_rep) => Ok(as_rep),
1689 _ => Err(MsalError::GeneralFailure(
1690 "Unexpected kerberos reply message".to_string(),
1691 )),
1692 }
1693 }
1694}
1695
1696#[derive(Clone, Zeroize, ZeroizeOnDrop)]
1697#[cfg_attr(test, derive(Eq, PartialEq, Debug))]
1698enum OnPremTgt {
1699 Raw(RawTgt),
1701 Structured {
1703 tgt_ad: StructuredTgt,
1704 },
1705 Absent,
1706}
1707
1708impl Serialize for OnPremTgt {
1709 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1710 match self {
1711 OnPremTgt::Raw(raw) => {
1713 let mut m = serializer.serialize_map(Some(3))?;
1714 m.serialize_entry("tgt_message_buffer", &raw.tgt_message_buffer)?;
1715 m.serialize_entry("tgt_client_key", &raw.tgt_client_key)?;
1716 m.serialize_entry("tgt_key_type", &raw.tgt_key_type)?;
1717 m.end()
1718 }
1719 OnPremTgt::Structured { tgt_ad } => {
1720 let mut m = serializer.serialize_map(Some(1))?;
1721 m.serialize_entry("tgt_ad", tgt_ad)?;
1722 m.end()
1723 }
1724 OnPremTgt::Absent => serializer.serialize_map(Some(0))?.end(),
1726 }
1727 }
1728}
1729
1730impl<'de> Deserialize<'de> for OnPremTgt {
1731 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1732 struct V;
1733
1734 impl<'de> Visitor<'de> for V {
1735 type Value = OnPremTgt;
1736
1737 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1738 f.write_str("on-prem TGT fields (raw or structured)")
1739 }
1740
1741 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
1742 let mut tgt_message_buffer = None;
1743 let mut tgt_client_key = None;
1744 let mut tgt_key_type = None;
1745 let mut tgt_ad: Option<StructuredTgt> = None;
1746
1747 while let Some(key) = map.next_key::<String>()? {
1748 match key.as_str() {
1749 "tgt_message_buffer" => tgt_message_buffer = Some(map.next_value()?),
1750 "tgt_client_key" => tgt_client_key = Some(map.next_value()?),
1751 "tgt_key_type" => tgt_key_type = Some(map.next_value()?),
1752 "tgt_ad" => tgt_ad = Some(map.next_value()?),
1753 _ => {
1755 map.next_value::<IgnoredAny>()?;
1756 }
1757 }
1758 }
1759
1760 match (tgt_ad, tgt_message_buffer, tgt_client_key, tgt_key_type) {
1761 (Some(tgt_ad), None, None, None) => Ok(OnPremTgt::Structured { tgt_ad }),
1762 (None, Some(buf), Some(key), Some(kt)) => Ok(OnPremTgt::Raw(RawTgt {
1763 tgt_message_buffer: buf,
1764 tgt_client_key: key,
1765 tgt_key_type: kt,
1766 })),
1767 (None, None, None, None) => Ok(OnPremTgt::Absent),
1768 _ => Err(de::Error::custom(
1769 "on-prem TGT is a mix of raw and structured, or is incomplete",
1770 )),
1771 }
1772 }
1773 }
1774
1775 deserializer.deserialize_map(V)
1776 }
1777}
1778
1779#[cfg(feature = "broker")]
1780#[derive(Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
1781#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
1782struct PrimaryRefreshToken {
1783 token_type: String,
1784 expires_in: String,
1785 ext_expires_in: String,
1786 expires_on: String,
1787 refresh_token: String,
1788 refresh_token_expires_in: u64,
1789 session_key_jwe: Option<String>,
1790 #[serde(deserialize_with = "decode_string_or_struct")]
1791 #[zeroize(skip)]
1792 id_token: IdToken,
1793 #[serde(deserialize_with = "decode_string_or_struct", default)]
1794 #[zeroize(skip)]
1795 client_info: ClientInfo,
1796 device_tenant_id: Option<String>,
1797 #[serde(flatten)]
1798 tgt_on_prem: OnPremTgt,
1799 #[serde(deserialize_with = "decode_string_or_struct", default)]
1800 tgt_cloud: StructuredTgt,
1801 kerberos_top_level_names: Option<String>,
1802}
1803
1804#[cfg(feature = "broker")]
1805impl PrimaryRefreshToken {
1806 fn name(&self) -> String {
1807 self.id_token.name.clone()
1808 }
1809
1810 fn spn(&self) -> Result<String, MsalError> {
1811 match &self.id_token.preferred_username {
1812 Some(spn) => Ok(spn.to_string()),
1813 None => Err(MsalError::GeneralFailure(
1814 "No spn available for PRT".to_string(),
1815 )),
1816 }
1817 }
1818
1819 fn uuid(&self) -> Result<Uuid, MsalError> {
1820 Uuid::parse_str(&self.id_token.oid).map_err(|e| MsalError::InvalidParse(format!("{}", e)))
1821 }
1822
1823 fn session_key(&self) -> Result<SessionKey, MsalError> {
1824 match &self.session_key_jwe {
1825 Some(session_key_jwe) => SessionKey::new(session_key_jwe),
1826 None => Err(MsalError::CryptoFail("session_key_jwe missing".to_string())),
1827 }
1828 }
1829
1830 fn clone_session_key(&self, new_prt: &mut PrimaryRefreshToken) {
1831 new_prt.session_key_jwe.clone_from(&self.session_key_jwe);
1832 }
1833
1834 fn is_expired(&self) -> bool {
1835 match self.expires_on.parse::<u64>() {
1836 Ok(expiry_ts) => match SystemTime::now().duration_since(UNIX_EPOCH) {
1837 Ok(now) => {
1838 let now = now.as_secs();
1839 now >= expiry_ts
1840 }
1841 Err(_) => true,
1842 },
1843 Err(e) => {
1844 error!(?e, "Failed parsing PRT expires_on '{}'", self.expires_on);
1845 true
1846 }
1847 }
1848 }
1849}
1850
1851#[cfg(feature = "broker")]
1852struct SessionKey {
1853 session_key_jwe: JweCompact,
1854}
1855
1856#[cfg(feature = "broker")]
1857impl SessionKey {
1858 fn new(session_key_jwe: &str) -> Result<Self, MsalError> {
1859 Ok(SessionKey {
1860 session_key_jwe: JweCompact::from_str(session_key_jwe)
1861 .map_err(|e| MsalError::InvalidParse(format!("Failed parsing jwe: {}", e)))?,
1862 })
1863 }
1864
1865 fn decipher_prt_v2(
1866 &self,
1867 tpm: &mut BoxedDynTpm,
1868 transport_key: &MsOapxbcRsaKey,
1869 storage_key: &StorageKey,
1870 jwe: &JweCompact,
1871 ) -> Result<Jwe, MsalError> {
1872 let maybe_transport_storage_key = tpm.rs256_yield_cek(transport_key);
1876 let storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
1877
1878 let session_key = MsOapxbcSessionKey::complete_tpm_rsa_oaep_key_agreement(
1879 tpm,
1880 storage_key,
1881 transport_key,
1882 &self.session_key_jwe,
1883 )
1884 .map_err(|e| MsalError::CryptoFail(format!("Unable to decipher session_key_jwe: {}", e)))?;
1885 session_key
1886 .decipher_prt_v2(tpm, storage_key, jwe)
1887 .map_err(|e| MsalError::CryptoFail(format!("Failed to decipher Jwe: {}", e)))
1888 }
1889
1890 #[allow(dead_code)]
1891 fn decipher_tgt_client_key(
1892 &self,
1893 tpm: &mut BoxedDynTpm,
1894 transport_key: &MsOapxbcRsaKey,
1895 storage_key: &StorageKey,
1896 jwe: &JweCompact,
1897 ) -> Result<Zeroizing<Vec<u8>>, MsalError> {
1898 let maybe_transport_storage_key = tpm.rs256_yield_cek(transport_key);
1902 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
1903
1904 let session_key = MsOapxbcSessionKey::complete_tpm_rsa_oaep_key_agreement(
1905 tpm,
1906 prt_storage_key,
1907 transport_key,
1908 &self.session_key_jwe,
1909 )
1910 .map_err(|e| MsalError::CryptoFail(format!("Unable to decipher session_key_jwe: {}", e)))?;
1911 match session_key.decipher_prt_v2(tpm, prt_storage_key, jwe) {
1912 Ok(decrypted) => Ok(Zeroizing::new(decrypted.payload().to_vec())),
1913 Err(JwtError::OpenSSLError) => match session_key.decipher(tpm, prt_storage_key, jwe) {
1914 Ok(decrypted) => Ok(Zeroizing::new(decrypted.payload().to_vec())),
1915 Err(e) => Err(MsalError::CryptoFail(format!(
1916 "Failed to decipher Jwe: {}",
1917 e
1918 ))),
1919 },
1920 Err(e) => Err(MsalError::CryptoFail(format!(
1921 "Failed to decipher Jwe: {}",
1922 e
1923 ))),
1924 }
1925 }
1926
1927 fn sign<V: JwsSignable>(
1928 &self,
1929 tpm: &mut BoxedDynTpm,
1930 transport_key: &MsOapxbcRsaKey,
1931 storage_key: &StorageKey,
1932 jws: &V,
1933 ) -> Result<V::Signed, MsalError> {
1934 let maybe_transport_storage_key = tpm.rs256_yield_cek(transport_key);
1938 let storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
1939
1940 let session_key = MsOapxbcSessionKey::complete_tpm_rsa_oaep_key_agreement(
1941 tpm,
1942 storage_key,
1943 transport_key,
1944 &self.session_key_jwe,
1945 )
1946 .map_err(|e| MsalError::CryptoFail(format!("Unable to decipher session_key_jwe: {}", e)))?;
1947
1948 session_key
1949 .sign(tpm, storage_key, jws)
1950 .map_err(|e| MsalError::CryptoFail(format!("Failed signing jwk: {}", e)))
1951 }
1952}
1953
1954pub(crate) fn should_attempt_passwordless_security_key(
1961 options: &[AuthOption],
1962 has_fido_params: bool,
1963) -> bool {
1964 let enabled = options.contains(&AuthOption::PasswordlessSecurityKey)
1965 || options.contains(&AuthOption::PasswordlessFido);
1966 if !enabled {
1967 debug!("passwordless_security_key: skipped (not enabled in config)");
1968 return false;
1969 }
1970 if !has_fido_params {
1971 debug!("passwordless_security_key: skipped (no FIDO params from GetCredentialType)");
1972 return false;
1973 }
1974 debug!("passwordless_security_key: user has FIDO params, attempting");
1975 true
1976}
1977
1978pub(crate) fn should_attempt_passwordless_qr_bluetooth(
1986 options: &[AuthOption],
1987 has_fido_params: bool,
1988 user_has_any_cross_device_fido: bool,
1989) -> bool {
1990 if !options.contains(&AuthOption::PasswordlessQrBluetooth) {
1991 debug!("passwordless_qr_bluetooth: skipped (not enabled in config)");
1992 return false;
1993 }
1994 if !has_fido_params {
1995 debug!("passwordless_qr_bluetooth: skipped (no FIDO params from GetCredentialType)");
1996 return false;
1997 }
1998 if !user_has_any_cross_device_fido {
1999 debug!("passwordless_qr_bluetooth: skipped (user has no cross-device passkey)");
2000 return false;
2001 }
2002 debug!("passwordless_qr_bluetooth: user has cross-device passkey, attempting");
2003 true
2004}
2005
2006#[repr(C)]
2007#[derive(PartialEq)]
2008pub enum AuthOption {
2009 Fido,
2010 Passwordless,
2011 PasswordlessFido,
2012 PasswordlessSecurityKey,
2013 PasswordlessQrBluetooth,
2014 NoDAGFallback,
2015 #[cfg(feature = "optional_mfa")]
2021 ForceMFA,
2022 #[cfg(feature = "optional_mfa")]
2027 RemoteSession,
2028}
2029
2030#[cfg(feature = "ipvers")]
2031#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2032pub enum IpVersion {
2033 V4,
2034 V6,
2035}
2036
2037pub(crate) struct ClientApplication {
2038 pub(crate) client: Client,
2039 pub(crate) client_id: String,
2040 authority: RwLock<String>,
2041 jar: Arc<CookieStoreMutex>,
2042 #[cfg(feature = "ipvers")]
2043 pub(crate) ip_version: Vec<IpVersion>,
2044 #[cfg(feature = "set_timeout")]
2045 pub(crate) timeout: Duration,
2046}
2047
2048impl ClientApplication {
2049 pub(crate) fn new(
2050 client_id: &str,
2051 authority: Option<&str>,
2052 #[cfg(feature = "set_timeout")] timeout: Duration,
2053 #[cfg(feature = "ipvers")] ip_version: &[IpVersion],
2054 ) -> Result<Self, MsalError> {
2055 let jar = Arc::new(CookieStoreMutex::new(CookieStore::default()));
2056
2057 #[cfg(feature = "set_timeout")]
2058 let (timeout, connect_timeout) = { (timeout, min(timeout / 2, Duration::from_secs(3))) };
2059 #[cfg(not(feature = "set_timeout"))]
2060 let (timeout, connect_timeout) = (Duration::from_secs(3), Duration::from_secs(1));
2061
2062 #[allow(unused_mut)]
2063 let mut builder = reqwest::Client::builder()
2064 .connect_timeout(connect_timeout)
2065 .timeout(timeout)
2066 .redirect(Policy::none())
2067 .cookie_provider(jar.clone());
2068
2069 #[cfg(feature = "proxyable")]
2070 {
2071 if let Some(proxy_var) = std::env::var("HTTPS_PROXY")
2072 .ok()
2073 .or_else(|| std::env::var("ALL_PROXY").ok())
2074 {
2075 let proxy = Proxy::https(proxy_var)
2076 .map_err(|e| MsalError::GeneralFailure(format!("{:?}", e)))?;
2077 builder = builder.proxy(proxy).danger_accept_invalid_certs(true);
2078 }
2079 }
2080
2081 #[cfg(feature = "ipvers")]
2082 {
2083 let has_v4 = ip_version.contains(&IpVersion::V4);
2084 let has_v6 = ip_version.contains(&IpVersion::V6);
2085 if has_v4 && !has_v6 {
2086 builder =
2087 builder.local_address(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED))
2088 } else if !has_v4 && has_v6 {
2089 builder =
2090 builder.local_address(std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED))
2091 }
2092 }
2093
2094 let client = builder
2095 .build()
2096 .map_err(|e| MsalError::RequestFailed(format!("{}", e)))?;
2097
2098 Ok(ClientApplication {
2099 client,
2100 client_id: client_id.to_string(),
2101 authority: RwLock::new(match authority {
2102 Some(authority) => authority.to_string(),
2103 None => "https://login.microsoftonline.com/common".to_string(),
2104 }),
2105 jar,
2106 #[cfg(feature = "ipvers")]
2107 ip_version: ip_version.to_vec(),
2108 #[cfg(feature = "set_timeout")]
2109 timeout,
2110 })
2111 }
2112
2113 pub(crate) fn clear_cookies(&self) {
2114 match self.jar.lock() {
2115 Ok(mut jar) => jar.clear(),
2116 Err(e) => error!("Failed to clear cookies: {:?}", e),
2117 }
2118 }
2119
2120 pub(crate) fn authority(&self) -> Result<String, MsalError> {
2121 self.authority
2122 .read()
2123 .map_err(|e| {
2124 MsalError::GeneralFailure(format!(
2125 "Failed to lock authority URL for reading: {:?}",
2126 e
2127 ))
2128 })
2129 .map(|authority| authority.clone())
2130 }
2131
2132 pub(crate) fn set_authority(&self, new_authority: &str) -> Result<(), MsalError> {
2133 self.authority
2134 .write()
2135 .map_err(|e| {
2136 MsalError::GeneralFailure(format!(
2137 "Failed to acquire authority write lock: {:?}",
2138 e
2139 ))
2140 })
2141 .map(|mut auth| *auth = new_authority.to_string())
2142 }
2143
2144 async fn acquire_token_by_username_password(
2145 &self,
2146 username: &str,
2147 password: &str,
2148 scopes: Vec<&str>,
2149 ) -> Result<UserToken, MsalError> {
2150 let mut all_scopes = vec!["openid", "profile", "offline_access"];
2151 all_scopes.extend(scopes);
2152 let scopes_str = all_scopes.join(" ");
2153
2154 let params = [
2155 ("client_id", self.client_id.as_str()),
2156 ("scope", &scopes_str),
2157 ("username", username),
2158 ("password", password),
2159 ("grant_type", "password"),
2160 ("client_info", "1"),
2161 ];
2162 let payload = params
2163 .iter()
2164 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2165 .collect::<Vec<String>>()
2166 .join("&");
2167
2168 let resp = self
2169 .client
2170 .post(format!("{}/oauth2/v2.0/token", self.authority()?))
2171 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2172 .header(header::ACCEPT, "application/json")
2173 .body(payload)
2174 .send()
2175 .await
2176 .map_err(|e| MsalError::request_failed(&e))?;
2177 if resp.status().is_success() {
2178 let token: UserToken = resp
2179 .json()
2180 .await
2181 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2182
2183 Ok(token)
2184 } else {
2185 let json_resp: ErrorResponse = resp
2186 .json()
2187 .await
2188 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2189 Err(MsalError::AcquireTokenFailed(json_resp))
2190 }
2191 }
2192
2193 async fn acquire_token_by_refresh_token(
2194 &self,
2195 refresh_token: &str,
2196 scopes: Vec<&str>,
2197 ) -> Result<UserToken, MsalError> {
2198 let mut all_scopes = vec!["openid", "profile", "offline_access"];
2199 all_scopes.extend(scopes);
2200 let scopes_str = all_scopes.join(" ");
2201
2202 let params = [
2203 ("client_id", self.client_id.as_str()),
2204 ("scope", &scopes_str),
2205 ("grant_type", "refresh_token"),
2206 ("refresh_token", refresh_token),
2207 ("client_info", "1"),
2208 ];
2209 let payload = params
2210 .iter()
2211 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2212 .collect::<Vec<String>>()
2213 .join("&");
2214
2215 let resp = self
2216 .client
2217 .post(format!("{}/oauth2/v2.0/token", self.authority()?))
2218 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2219 .header(header::ACCEPT, "application/json")
2220 .body(payload)
2221 .send()
2222 .await
2223 .map_err(|e| MsalError::request_failed(&e))?;
2224 if resp.status().is_success() {
2225 let token: UserToken = resp
2226 .json()
2227 .await
2228 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2229
2230 Ok(token)
2231 } else {
2232 let json_resp: ErrorResponse = resp
2233 .json()
2234 .await
2235 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2236 Err(MsalError::AcquireTokenFailed(json_resp))
2237 }
2238 }
2239
2240 fn get_auth_redirect_uri(&self, client_id: Option<&str>, resource: Option<&str>) -> String {
2241 let client_id = client_id.unwrap_or(self.client_id.as_str());
2242 let resource = resource.unwrap_or("");
2243
2244 match client_id {
2245 "1fec8e78-bce4-4aaf-ab1b-5451cc387264" => {
2246 "https://login.microsoftonline.com/common/oauth2/nativeclient".to_string()
2247 },
2248 "9bc3ab49-b65d-410a-85ad-de819febfddc" => {
2249 "https://oauth.spops.microsoft.com/".to_string()
2250 },
2251 "c44b4083-3bb0-49c1-b47d-974e53cbdf3c" => {
2252 "https://portal.azure.com/signin/index/?feature.prefetchtokens=true&feature.showservicehealthalerts=true&feature.usemsallogin=true".to_string()
2253 },
2254 "0000000c-0000-0000-c000-000000000000" => {
2255 "https://account.activedirectory.windowsazure.com/".to_string()
2256 },
2257 "19db86c3-b2b9-44cc-b339-36da233a3be2" => {
2258 "https://mysignins.microsoft.com".to_string()
2259 },
2260 "29d9ed98-a469-4536-ade2-f981bc1d605e" => {
2261 if resource.contains("enrollment.manage.microsoft.com") {
2262 "ms-aadj-redir://auth/drs".to_string()
2263 } else {
2264 "msauth://Microsoft.AAD.BrokerPlugin".to_string()
2265 }
2266 },
2267 "b743a22d-6705-4147-8670-d92fa515ee2b" => {
2268 "companyportal://com.microsoft.CompanyPortal".to_string()
2269 }
2270 "d3590ed6-52b3-4102-aeff-aad2292ab01c" => {
2271 "ms-appx-web://Microsoft.AAD.BrokerPlugin/d3590ed6-52b3-4102-aeff-aad2292ab01c".to_string()
2272 },
2273 "0c1307d4-29d6-4389-a11c-5cbe7f65d7fa" => {
2274 "https://azureapp".to_string()
2275 },
2276 "33be1cef-03fb-444b-8fd3-08ca1b4d803f" => {
2277 "https://admin.onedrive.com/".to_string()
2278 },
2279 "ab9b8c07-8f02-4f72-87fa-80105867a763" => {
2280 "https://login.windows.net/common/oauth2/nativeclient".to_string()
2281 },
2282 "3d5cffa9-04da-4657-8cab-c7f074657cad" => {
2283 "http://localhost/m365/commerce".to_string()
2284 },
2285 "4990cffe-04e8-4e8b-808a-1175604b879f" => {
2286 "https://partner.microsoft.com/aad/authPostGateway".to_string()
2287 },
2288 "fb78d390-0c51-40cd-8e17-fdbfab77341b" |
2289 "fdd7719f-d61e-4592-b501-793734eb8a0e" |
2290 "a0c73c16-a7e3-4564-9a95-2bdf47383716" => {
2291 "https://login.microsoftonline.com/common/oauth2/nativeclient".to_string()
2292 },
2293 "3b511579-5e00-46e1-a89e-a6f0870e2f5a" => {
2294 "https://windows365.microsoft.com/signin-oidc".to_string()
2295 },
2296 "08e18876-6177-487e-b8b5-cf950c1e598c" => {
2297 "https://*-admin.sharepoint.com/_forms/spfxsinglesignon.aspx".to_string()
2298 },
2299 "dd762716-544d-4aeb-a526-687b73838a22" => {
2300 "ms-appx-web://microsoft.aad.brokerplugin/dd762716-544d-4aeb-a526-687b73838a22".to_string()
2301 },
2302 "4765445b-32c6-49b0-83e6-1d93765276ca" => {
2303 "https://www.office.com/landingv2".to_string()
2304 },
2305 _ => {
2306 "https://login.microsoftonline.com/common/oauth2/nativeclient".to_string()
2307 },
2308 }
2309 }
2310}
2311
2312#[derive(Clone)]
2313pub struct AuthInit {
2314 auth_config: AuthConfig,
2315 cred_type: CredType,
2316}
2317
2318impl AuthInit {
2319 #[deprecated(
2325 since = "0.8.25",
2326 note = "use AuthInit::try_exists() to distinguish nonexistent accounts from transient Entra ID lookup failures"
2327 )]
2328 pub fn exists(&self) -> bool {
2329 match self.cred_type.account_exists() {
2330 Ok(exists) => exists,
2331 Err(err) => {
2332 warn!("Unable to determine account existence from GetCredentialType: {err:?}");
2333 false
2334 }
2335 }
2336 }
2337
2338 pub fn try_exists(&self) -> Result<bool, MsalError> {
2341 self.cred_type.account_exists()
2342 }
2343
2344 pub fn is_personal_account(&self) -> bool {
2345 self.cred_type.is_personal_account()
2346 }
2347
2348 pub fn passwordless(&self) -> bool {
2350 self.cred_type.credentials.has_access_pass.unwrap_or(false)
2356 || (self.cred_type.credentials.has_remote_ngc.unwrap_or(false)
2357 && self.cred_type.credentials.remote_ngc_params.is_some())
2358 || (self.cred_type.credentials.has_fido.unwrap_or(false)
2359 && self.cred_type.credentials.fido_params.is_some())
2360 || self.cred_type.is_personal_account()
2361 }
2362}
2363
2364#[cfg(feature = "changepassword")]
2365#[derive(Deserialize)]
2366struct SsprResponse {
2367 #[serde(rename = "IsJobPending")]
2368 is_job_pending: bool,
2369 #[serde(rename = "Ctx")]
2370 ctx: String,
2371 #[serde(rename = "FlowToken")]
2372 flow_token: String,
2373 #[serde(rename = "CoupledDataCenter")]
2374 coupled_data_center: String,
2375 #[serde(rename = "CoupledScaleUnit")]
2376 coupled_scale_unit: String,
2377 #[serde(rename = "ErrorMessage")]
2378 error_message: Option<String>,
2379}
2380
2381pub struct PublicClientApplication {
2382 app: ClientApplication,
2383}
2384
2385impl PublicClientApplication {
2386 pub fn new(
2397 client_id: &str,
2398 authority: Option<&str>,
2399 #[cfg(feature = "set_timeout")] timeout: Duration,
2400 #[cfg(feature = "ipvers")] ip_version: &[IpVersion],
2401 ) -> Result<Self, MsalError> {
2402 Ok(PublicClientApplication {
2403 app: ClientApplication::new(
2404 client_id,
2405 authority,
2406 #[cfg(feature = "set_timeout")]
2407 timeout,
2408 #[cfg(feature = "ipvers")]
2409 ip_version,
2410 )?,
2411 })
2412 }
2413
2414 fn client(&self) -> &Client {
2415 &self.app.client
2416 }
2417
2418 fn client_id(&self) -> &str {
2419 &self.app.client_id
2420 }
2421
2422 pub fn clear_cookies(&self) {
2435 self.app.clear_cookies()
2436 }
2437
2438 fn authority(&self) -> Result<String, MsalError> {
2439 self.app.authority()
2440 }
2441
2442 pub fn set_authority(&self, new_authority: &str) -> Result<(), MsalError> {
2452 self.app.set_authority(new_authority)
2453 }
2454
2455 pub async fn acquire_token_by_username_password(
2469 &self,
2470 username: &str,
2471 password: &str,
2472 scopes: Vec<&str>,
2473 ) -> Result<UserToken, MsalError> {
2474 self.app
2475 .acquire_token_by_username_password(username, password, scopes)
2476 .await
2477 }
2478
2479 pub async fn acquire_token_by_refresh_token(
2492 &self,
2493 refresh_token: &str,
2494 scopes: Vec<&str>,
2495 ) -> Result<UserToken, MsalError> {
2496 self.app
2497 .acquire_token_by_refresh_token(refresh_token, scopes)
2498 .await
2499 }
2500
2501 pub fn initiate_authorization_code_pkce_flow(
2517 &self,
2518 scopes: Vec<&str>,
2519 redirect_uri: &str,
2520 ) -> Result<AuthorizationCodePkceFlow, MsalError> {
2521 if redirect_uri.trim().is_empty() {
2522 return Err(MsalError::ConfigError(
2523 "redirect_uri must not be empty".to_string(),
2524 ));
2525 }
2526
2527 let mut all_scopes = vec!["openid", "profile", "offline_access"];
2528 all_scopes.extend(scopes);
2529 let scope = all_scopes.join(" ");
2530 let code_verifier = generate_base64url_random(32)?;
2531 let code_challenge = pkce_code_challenge(&code_verifier);
2532 let state = generate_base64url_random(32)?;
2533
2534 let url = Url::parse_with_params(
2535 &format!("{}/oauth2/v2.0/authorize", self.authority()?),
2536 [
2537 ("client_id", self.client_id()),
2538 ("response_type", "code"),
2539 ("redirect_uri", redirect_uri),
2540 ("response_mode", "query"),
2541 ("scope", &scope),
2542 ("state", &state),
2543 ("code_challenge", &code_challenge),
2544 ("code_challenge_method", "S256"),
2545 ],
2546 )
2547 .map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
2548
2549 Ok(AuthorizationCodePkceFlow {
2550 auth_url: url.to_string(),
2551 redirect_uri: redirect_uri.to_string(),
2552 state,
2553 scope,
2554 code_verifier,
2555 })
2556 }
2557
2558 pub async fn acquire_token_by_authorization_code_pkce_flow(
2565 &self,
2566 flow: &AuthorizationCodePkceFlow,
2567 redirect_url: &str,
2568 ) -> Result<UserToken, MsalError> {
2569 let url =
2570 Url::parse(redirect_url).map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
2571 let params: HashMap<String, String> = url.query_pairs().into_owned().collect();
2572
2573 if let Some(error) = params.get("error") {
2574 return Err(MsalError::AcquireTokenFailed(ErrorResponse {
2575 error: error.clone(),
2576 error_description: params.get("error_description").cloned().unwrap_or_default(),
2577 suberror: params.get("suberror").cloned(),
2578 error_codes: Vec::new(),
2579 }));
2580 }
2581
2582 let returned_state = params
2583 .get("state")
2584 .ok_or_else(|| MsalError::InvalidParse("state missing from redirect".to_string()))?;
2585 if returned_state != &flow.state {
2586 return Err(MsalError::InvalidParse(
2587 "state returned by redirect does not match the PKCE flow".to_string(),
2588 ));
2589 }
2590
2591 let code = params
2592 .get("code")
2593 .ok_or_else(|| MsalError::InvalidParse("code missing from redirect".to_string()))?;
2594
2595 let form = self.authorization_code_pkce_token_form(flow, code);
2596
2597 let resp = self
2598 .client()
2599 .post(format!("{}/oauth2/v2.0/token", self.authority()?))
2600 .header(header::ACCEPT, "application/json")
2601 .form(&form)
2602 .send()
2603 .await
2604 .map_err(|e| MsalError::request_failed(&e))?;
2605 if resp.status().is_success() {
2606 let token: UserToken = resp
2607 .json()
2608 .await
2609 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2610 Ok(token)
2611 } else {
2612 let json_resp: ErrorResponse = resp
2613 .json()
2614 .await
2615 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2616 Err(MsalError::AcquireTokenFailed(json_resp))
2617 }
2618 }
2619
2620 fn authorization_code_pkce_token_form<'a>(
2621 &'a self,
2622 flow: &'a AuthorizationCodePkceFlow,
2623 code: &'a str,
2624 ) -> [(&'static str, &'a str); 7] {
2625 [
2626 ("client_id", self.client_id()),
2627 ("grant_type", "authorization_code"),
2628 ("code", code),
2629 ("redirect_uri", flow.redirect_uri.as_str()),
2630 ("scope", flow.scope.as_str()),
2631 ("code_verifier", flow.code_verifier.as_str()),
2632 ("client_info", "1"),
2633 ]
2634 }
2635
2636 pub async fn initiate_device_flow(
2649 &self,
2650 scopes: Vec<&str>,
2651 ) -> Result<DeviceAuthorizationResponse, MsalError> {
2652 let mut all_scopes = vec!["openid", "profile", "offline_access"];
2653 all_scopes.extend(scopes);
2654 let scopes_str = all_scopes.join(" ");
2655
2656 let params = [("client_id", self.client_id()), ("scope", &scopes_str)];
2657 let payload = params
2658 .iter()
2659 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2660 .collect::<Vec<String>>()
2661 .join("&");
2662
2663 let resp = self
2664 .client()
2665 .post(format!("{}/oauth2/v2.0/devicecode", self.authority()?))
2666 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2667 .header(header::ACCEPT, "application/json")
2668 .body(payload)
2669 .send()
2670 .await
2671 .map_err(|e| MsalError::request_failed(&e))?;
2672 if resp.status().is_success() {
2673 let json_resp: DeviceAuthorizationResponse = resp
2674 .json()
2675 .await
2676 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2677 Ok(json_resp)
2678 } else {
2679 let json_resp: ErrorResponse = resp
2680 .json()
2681 .await
2682 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2683 Err(MsalError::AcquireTokenFailed(json_resp))
2684 }
2685 }
2686
2687 async fn initiate_personal_device_flow(
2688 &self,
2689 scopes: Vec<&str>,
2690 ) -> Result<DeviceAuthorizationResponse, MsalError> {
2691 let mut all_scopes = vec!["openid", "profile", "offline_access"];
2692 all_scopes.extend(scopes);
2693 let scopes_str = all_scopes.join(" ");
2694
2695 let params = [("client_id", self.client_id()), ("scope", &scopes_str)];
2696 let payload = params
2697 .iter()
2698 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2699 .collect::<Vec<String>>()
2700 .join("&");
2701
2702 let resp = self
2703 .client()
2704 .post("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode")
2705 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2706 .header(header::ACCEPT, "application/json")
2707 .body(payload)
2708 .send()
2709 .await
2710 .map_err(|e| MsalError::request_failed(&e))?;
2711 if resp.status().is_success() {
2712 let json_resp: DeviceAuthorizationResponse = resp
2713 .json()
2714 .await
2715 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2716 Ok(json_resp)
2717 } else {
2718 let json_resp: ErrorResponse = resp
2719 .json()
2720 .await
2721 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2722 Err(MsalError::AcquireTokenFailed(json_resp))
2723 }
2724 }
2725
2726 pub async fn acquire_token_by_device_flow(
2738 &self,
2739 flow: DeviceAuthorizationResponse,
2740 ) -> Result<UserToken, MsalError> {
2741 let params = [
2742 ("client_id", self.client_id()),
2743 ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
2744 ("device_code", &flow.device_code),
2745 ];
2746 let payload = params
2747 .iter()
2748 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2749 .collect::<Vec<String>>()
2750 .join("&");
2751
2752 let resp = self
2753 .client()
2754 .post(format!("{}/oauth2/v2.0/token", self.authority()?))
2755 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2756 .header(header::ACCEPT, "application/json")
2757 .body(payload)
2758 .send()
2759 .await
2760 .map_err(|e| MsalError::request_failed(&e))?;
2761 if resp.status().is_success() {
2762 let token: UserToken = resp
2763 .json()
2764 .await
2765 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2766
2767 Ok(token)
2768 } else {
2769 let json_resp: ErrorResponse = resp
2770 .json()
2771 .await
2772 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2773 Err(MsalError::AcquireTokenFailed(json_resp))
2774 }
2775 }
2776
2777 async fn acquire_token_by_personal_device_flow(
2778 &self,
2779 flow: DeviceAuthorizationResponse,
2780 ) -> Result<UserToken, MsalError> {
2781 let params = [
2782 ("client_id", self.client_id()),
2783 ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
2784 ("device_code", &flow.device_code),
2785 ];
2786 let payload = params
2787 .iter()
2788 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
2789 .collect::<Vec<String>>()
2790 .join("&");
2791
2792 let resp = self
2793 .client()
2794 .post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")
2795 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
2796 .header(header::ACCEPT, "application/json")
2797 .body(payload)
2798 .send()
2799 .await
2800 .map_err(|e| MsalError::request_failed(&e))?;
2801 if resp.status().is_success() {
2802 let token: UserToken = resp
2803 .json()
2804 .await
2805 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2806
2807 Ok(token)
2808 } else {
2809 let json_resp: ErrorResponse = resp
2810 .json()
2811 .await
2812 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
2813 Err(MsalError::AcquireTokenFailed(json_resp))
2814 }
2815 }
2816
2817 #[allow(unused_variables)]
2818 fn parse_auth_config(
2819 &self,
2820 text: &str,
2821 initial: bool,
2822 password_change: bool,
2823 ) -> Result<AuthConfig, MsalError> {
2824 let document = Html::parse_document(text);
2825 for script in
2826 document.select(&Selector::parse("script").map_err(|e| {
2827 MsalError::GeneralFailure(format!("Failed parsing auth config: {}", e))
2828 })?)
2829 {
2830 let text = script.inner_html();
2831 if let Some(config_index) = text.find(r"$Config=") {
2832 let sconfig = &text[config_index + 8..];
2833 if let Some(end_index) = sconfig.rfind(r"//]]>") {
2834 let config = &sconfig[..end_index - 2];
2835 let auth_config: AuthConfig = json_from_str(config).map_err(|e| {
2836 MsalError::InvalidJson(format!("Failed parsing auth config: {}", e))
2837 })?;
2838 if !initial {
2841 let error_code = match auth_config.error_code {
2842 Some(ref error_code) => {
2843 Some(error_code.parse::<u32>().map_err(|e| {
2844 MsalError::InvalidParse(format!(
2845 "error_code {}: {:?}",
2846 error_code, e
2847 ))
2848 })?)
2849 }
2850 None => auth_config.error_code2,
2851 };
2852 if let Some(error_code) = error_code {
2853 let description =
2854 auth_config.err_txt.or(auth_config.service_exception_msg);
2855
2856 if let Some(err_txt) = description.clone() {
2858 if !err_txt.is_empty() {
2859 error!("{}", err_txt);
2860 }
2861 }
2862
2863 if error_code == 50203 {
2865 if let Some(url_skip_mfa_registration) =
2866 auth_config.url_skip_mfa_registration
2867 {
2868 return Err(MsalError::SkipMfaRegistration(
2869 url_skip_mfa_registration,
2870 auth_config.sft,
2871 auth_config.canary,
2872 ));
2873 }
2874 }
2875 return Err(MsalError::AADSTSError(AADSTSError::new(
2876 error_code,
2877 description,
2878 )));
2879 }
2880 }
2881 #[cfg(feature = "changepassword")]
2882 if !password_change {
2883 if let Some(ref pgid) = auth_config.pgid {
2884 if pgid == "ConvergedChangePassword" {
2885 return Err(MsalError::ChangePassword);
2886 }
2887 }
2888 }
2889 if let Some(ref pgid) = auth_config.pgid {
2890 if pgid == "ConvergedConsent" {
2891 return Err(MsalError::ConsentRequested(
2892 "The client application requires additional consent to proceed."
2893 .to_string(),
2894 ));
2895 }
2896 }
2897 return Ok(auth_config);
2898 }
2899 }
2900 }
2901 Err(MsalError::GeneralFailure(
2902 "Auth config was not found".to_string(),
2903 ))
2904 }
2905
2906 #[cfg(feature = "changepassword")]
2927 pub async fn handle_password_change(
2928 &self,
2929 username: &str,
2930 password: &str,
2931 new_password: &str,
2932 ) -> Result<(), MsalError> {
2933 let request_id = Uuid::new_v4().to_string();
2934 let auth_config = self
2935 .request_auth_config_internal(vec![], &request_id, None, false)
2936 .await?;
2937 let ctx = auth_config
2938 .sctx
2939 .clone()
2940 .ok_or(MsalError::GeneralFailure("ctx is missing".to_string()))?;
2941 let flow_token = auth_config
2942 .sft
2943 .clone()
2944 .ok_or(MsalError::GeneralFailure("sft is missing".to_string()))?;
2945
2946 let params = vec![
2947 ("login", username),
2948 ("passwd", password),
2949 ("ctx", &ctx),
2950 ("flowToken", &flow_token),
2951 ("canary", &auth_config.canary),
2952 ("client_id", self.client_id()),
2953 ("client-request-id", &request_id),
2954 ];
2955 let auth_config = self
2956 .handle_auth_config_req_internal(¶ms, &auth_config, &[], true)
2957 .await?;
2958
2959 let payload = json!({
2960 "Ctx": &auth_config
2961 .sctx
2962 .ok_or(MsalError::GeneralFailure("ctx is missing".to_string()))?,
2963 "FlowToken": &auth_config
2964 .sft
2965 .ok_or(MsalError::GeneralFailure("sft is missing".to_string()))?,
2966 "OldPassword": password,
2967 "NewPassword": new_password,
2968 });
2969
2970 let url_async_sspr_begin = match &auth_config.url_async_sspr_begin {
2971 Some(url_async_sspr_begin) => url_async_sspr_begin.clone(),
2972 None => {
2973 return Err(MsalError::GeneralFailure(
2974 "url_async_sspr_begin missing from auth config".to_string(),
2975 ))
2976 }
2977 };
2978 let url = match url_async_sspr_begin.starts_with('/') {
2979 true => {
2980 let authority = self.authority()?.to_string();
2981 let index = authority.rfind('/').ok_or(MsalError::GeneralFailure(
2982 "Failed to splice auth config url".to_string(),
2983 ))?;
2984 format!("{}/{}", &authority[..index], url_async_sspr_begin)
2985 }
2986 false => url_async_sspr_begin.clone(),
2987 };
2988
2989 let resp = self
2990 .client()
2991 .post(&url)
2992 .json(&payload)
2993 .send()
2994 .await
2995 .map_err(|e| MsalError::request_failed(&e))?;
2996 if resp.status().is_success() {
2997 let mut sspr_response: SsprResponse = resp
2998 .json()
2999 .await
3000 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
3001
3002 let url_async_sspr_poll = match &auth_config.url_async_sspr_poll {
3003 Some(url_async_sspr_poll) => url_async_sspr_poll.clone(),
3004 None => {
3005 return Err(MsalError::GeneralFailure(
3006 "url_async_sspr_poll missing from auth config".to_string(),
3007 ))
3008 }
3009 };
3010 let url = match url_async_sspr_poll.starts_with('/') {
3011 true => {
3012 let authority = self.authority()?.to_string();
3013 let index = authority.rfind('/').ok_or(MsalError::GeneralFailure(
3014 "Failed to splice auth config url".to_string(),
3015 ))?;
3016 format!("{}/{}", &authority[..index], url_async_sspr_poll)
3017 }
3018 false => url_async_sspr_poll.clone(),
3019 };
3020
3021 while sspr_response.is_job_pending {
3022 sleep(Duration::from_secs(1));
3023 let poll_body = json!({
3024 "Ctx": sspr_response.ctx,
3025 "FlowToken": sspr_response.flow_token,
3026 "CoupledDataCenter": sspr_response.coupled_data_center,
3027 "CoupledScaleUnit": sspr_response.coupled_scale_unit,
3028 });
3029 sspr_response = self
3030 .client()
3031 .post(&url)
3032 .json(&poll_body)
3033 .send()
3034 .await
3035 .map_err(|e| MsalError::request_failed(&e))?
3036 .json()
3037 .await
3038 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
3039
3040 if let Some(e) = sspr_response.error_message {
3041 return Err(MsalError::GeneralFailure(format!(
3042 "Failed changing password: {}",
3043 e
3044 )));
3045 }
3046 }
3047
3048 let url_post = match &auth_config.url_post {
3049 Some(url_post) => url_post.clone(),
3050 None => {
3051 return Err(MsalError::GeneralFailure(
3052 "urlPost missing from auth config".to_string(),
3053 ))
3054 }
3055 };
3056 let url = match url_post.starts_with('/') {
3057 true => {
3058 let authority = self.authority()?.to_string();
3059 let index = authority.rfind('/').ok_or(MsalError::GeneralFailure(
3060 "Failed to splice auth config url".to_string(),
3061 ))?;
3062 format!("{}/{}", &authority[..index], url_post)
3063 }
3064 false => url_post.clone(),
3065 };
3066
3067 let final_body = json!({
3068 "Ctx": sspr_response.ctx,
3069 "FlowToken": sspr_response.flow_token,
3070 "currentpasswd": password,
3071 "confirmnewpasswd": new_password,
3072 "canary": auth_config.canary,
3073 });
3074 let resp = self
3075 .client()
3076 .post(&url)
3077 .json(&final_body)
3078 .send()
3079 .await
3080 .map_err(|e| MsalError::request_failed(&e))?;
3081 if resp.status().is_success() {
3082 Ok(())
3083 } else {
3084 let text = resp.text().await.map_err(|e| {
3085 MsalError::GeneralFailure(format!("Failed changing password: {}", e))
3086 })?;
3087 Err(MsalError::GeneralFailure(format!(
3088 "Failed changing password: {}",
3089 text
3090 )))
3091 }
3092 } else {
3093 let text = resp.text().await.map_err(|e| {
3094 MsalError::GeneralFailure(format!("Failed changing password: {}", e))
3095 })?;
3096 Err(MsalError::GeneralFailure(format!(
3097 "Failed changing password: {}",
3098 text
3099 )))
3100 }
3101 }
3102
3103 async fn handle_auth_config_fido_get(
3104 &self,
3105 username: &str,
3106 auth_config: &AuthConfig,
3107 request_id: &str,
3108 ) -> Result<AuthConfig, MsalError> {
3109 let url_post = match &auth_config.url_post {
3110 Some(url_post) => url_post.clone(),
3111 None => {
3112 return Err(MsalError::GeneralFailure(
3113 "urlPost missing from auth config".to_string(),
3114 ))
3115 }
3116 };
3117
3118 let url_resume = match &auth_config.url_resume {
3119 Some(url_resume) => url_resume.clone(),
3120 None => {
3121 return Err(MsalError::GeneralFailure(
3122 "urlResume missing from auth config".to_string(),
3123 ))
3124 }
3125 };
3126
3127 let credentials_json = match auth_config.fido_allow_list.as_deref() {
3128 Some([credentials_json, ..]) => credentials_json,
3129 _ => {
3130 return Err(MsalError::GeneralFailure(
3131 "arrFidoAllowList missing from auth config".to_string(),
3132 ))
3133 }
3134 };
3135
3136 let sctx = match &auth_config.sctx {
3137 Some(sctx) => sctx.clone(),
3138 None => {
3139 return Err(MsalError::GeneralFailure(
3140 "sCtx missing from auth config".to_string(),
3141 ));
3142 }
3143 };
3144
3145 let sft = match &auth_config.sft {
3146 Some(sft) => sft.clone(),
3147 None => {
3148 return Err(MsalError::GeneralFailure(
3149 "sFt missing from auth config".to_string(),
3150 ));
3151 }
3152 };
3153
3154 let allowed_identities = match &auth_config.allowed_identities {
3155 Some(allowed_identities) => format!("{}", allowed_identities),
3156 None => {
3157 return Err(MsalError::GeneralFailure(
3158 "iAllowedIdentities missing from auth config".to_string(),
3159 ));
3160 }
3161 };
3162
3163 let params = [
3164 ("flow", "mfa"),
3165 ("allowedIdentities", &allowed_identities),
3166 ("canary", &sft),
3167 ("serverChallenge", &sft),
3168 ("postBackUrl", &url_post),
3169 ("postBackUrlAad", &url_post),
3170 ("cancelUrl", &url_resume),
3171 ("resumeUrl", &url_resume),
3172 ("correlationId", request_id),
3173 ("credentialsJson", credentials_json),
3174 ("ctx", &sctx),
3175 ("username", username),
3176 ("loginCanary", &auth_config.canary),
3177 ];
3178 let payload = params
3179 .iter()
3180 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
3181 .collect::<Vec<String>>()
3182 .join("&");
3183
3184 let url_fido_login = match &auth_config.url_fido_login {
3185 Some(url_fido_login) => url_fido_login.clone(),
3186 None => {
3187 return Err(MsalError::GeneralFailure(
3188 "urlFidoLogin missing from auth config".to_string(),
3189 ))
3190 }
3191 };
3192
3193 let mut resp = self
3194 .client()
3195 .post(url_fido_login)
3196 .header(header::USER_AGENT, FIDO_USER_AGENT)
3197 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
3198 .body(payload)
3199 .send()
3200 .await
3201 .map_err(|e| MsalError::request_failed(&e))?;
3202 let text;
3203 (text, resp) = self.await_working(resp).await?;
3204 if resp.status().is_success() {
3205 self.parse_auth_config(&text, false, false)
3206 } else {
3207 Err(MsalError::GeneralFailure(resp.text().await.map_err(
3208 |e| MsalError::GeneralFailure(format!("Request to FIDO login URL failed: {}", e)),
3209 )?))
3210 }
3211 }
3212
3213 async fn handle_auth_config_req_internal(
3214 &self,
3215 req_params: &[(&str, &str)],
3216 auth_config: &AuthConfig,
3217 options: &[AuthOption],
3218 password_change: bool,
3219 ) -> Result<AuthConfig, MsalError> {
3220 let payload = req_params
3221 .iter()
3222 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
3223 .collect::<Vec<String>>()
3224 .join("&");
3225
3226 let url_post = match &auth_config.url_post {
3227 Some(url_post) => url_post.clone(),
3228 None => {
3229 return Err(MsalError::GeneralFailure(
3230 "urlPost missing from auth config".to_string(),
3231 ))
3232 }
3233 };
3234 let url = match url_post.starts_with('/') {
3235 true => {
3236 let authority = self.authority()?.to_string();
3237 let index = authority.rfind('/').ok_or(MsalError::GeneralFailure(
3238 "Failed to splice auth config url".to_string(),
3239 ))?;
3240 format!("{}/{}", &authority[..index], url_post)
3241 }
3242 false => url_post.clone(),
3243 };
3244
3245 let user_agent = if options.contains(&AuthOption::Fido) {
3246 FIDO_USER_AGENT
3247 } else {
3248 env!("CARGO_PKG_NAME")
3249 };
3250 let resp = self
3251 .client()
3252 .post(url)
3253 .header(header::USER_AGENT, user_agent)
3254 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
3255 .body(payload)
3256 .send()
3257 .await
3258 .map_err(|e| MsalError::request_failed(&e))?;
3259 self.handle_auth_config_response_internal(resp, password_change)
3260 .await
3261 }
3262
3263 async fn handle_auth_config_response_internal(
3264 &self,
3265 mut resp: Response,
3266 password_change: bool,
3267 ) -> Result<AuthConfig, MsalError> {
3268 let text;
3269 (text, resp) = self.await_working(resp).await?;
3270 if resp.status().is_success() {
3271 self.parse_auth_config(&text, false, password_change)
3272 } else if resp.status().is_redirection() {
3273 let redirect = resp
3274 .headers()
3275 .get(header::LOCATION)
3276 .ok_or_else(|| MsalError::InvalidParse("Redirect location is missing".to_string()))?
3277 .to_str()
3278 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
3279 let url =
3280 Url::parse(redirect).map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
3281 let params = url.query_pairs().collect::<Vec<_>>();
3282
3283 if let Some((_, error_description)) =
3285 params.iter().find(|(k, _)| k == "error_description")
3286 {
3287 let error_code_regex = Regex::new(r"AADSTS(\d+):");
3288
3289 if let Ok(regex) = error_code_regex {
3290 if let Some(captures) = regex.captures(error_description) {
3291 if let Some(code_match) = captures.get(1) {
3292 if let Ok(code) = code_match.as_str().parse::<u32>() {
3293 return Err(MsalError::AADSTSError(AADSTSError::new(
3294 code,
3295 Some(error_description.to_string()),
3296 )));
3297 }
3298 }
3299 }
3300 }
3301
3302 return Err(MsalError::GeneralFailure(format!(
3303 "Unknown error in request to {}: {}",
3304 url, error_description
3305 )));
3306 }
3307
3308 let (_, code) =
3309 params
3310 .iter()
3311 .find(|(k, _)| k == "code")
3312 .ok_or(MsalError::InvalidParse(
3313 "Authorization code missing from redirect".to_string(),
3314 ))?;
3315 debug!("Received auth code directly from login redirect");
3316 Err(MsalError::AuthCodeReceived(code.to_string()))
3317 } else {
3318 Err(MsalError::GeneralFailure(resp.text().await.map_err(
3319 |e| {
3320 MsalError::GeneralFailure(format!(
3321 "Request for handle_auth_config_req_internal() failed: {}",
3322 e
3323 ))
3324 },
3325 )?))
3326 }
3327 }
3328
3329 async fn read_adfs_response_body(&self, mut resp: Response) -> Result<String, MsalError> {
3330 let mut body = Vec::new();
3331 while let Some(chunk) = resp
3332 .chunk()
3333 .await
3334 .map_err(|e| MsalError::RequestFailed(format!("Failed reading AD FS response: {e}")))?
3335 {
3336 if body.len().saturating_add(chunk.len()) > MAX_ADFS_RESPONSE_BYTES {
3337 return Err(MsalError::GeneralFailure(
3338 "AD FS response exceeded the 2 MiB limit".to_string(),
3339 ));
3340 }
3341 body.extend_from_slice(&chunk);
3342 }
3343 String::from_utf8(body)
3344 .map_err(|e| MsalError::InvalidParse(format!("AD FS response was not UTF-8: {e}")))
3345 }
3346
3347 async fn request_adfs_form_internal(
3348 &self,
3349 federation_url: Url,
3350 username: &str,
3351 password: &str,
3352 ) -> Result<String, MsalError> {
3353 let origin = federation_url.clone();
3354 let mut url = federation_url;
3355 let mut method = AdfsRequestMethod::PostCredentials;
3356
3357 for redirect_count in 0..=MAX_ADFS_REDIRECTS {
3358 let response = if method == AdfsRequestMethod::PostCredentials {
3359 self.client()
3360 .post(url.clone())
3361 .header(header::USER_AGENT, FIDO_USER_AGENT)
3362 .form(&[
3363 ("UserName", username),
3364 ("Password", password),
3365 ("Kmsi", ""),
3366 ("AuthMethod", "FormsAuthentication"),
3367 ])
3368 .send()
3369 .await
3370 } else {
3371 self.client()
3372 .get(url.clone())
3373 .header(header::USER_AGENT, FIDO_USER_AGENT)
3374 .send()
3375 .await
3376 }
3377 .map_err(|e| MsalError::request_failed(&e))?;
3378
3379 if response.status().is_redirection() {
3380 if redirect_count == MAX_ADFS_REDIRECTS {
3381 return Err(MsalError::GeneralFailure(
3382 "AD FS redirect limit exceeded".to_string(),
3383 ));
3384 }
3385 let location = response
3386 .headers()
3387 .get(header::LOCATION)
3388 .ok_or_else(|| {
3389 MsalError::InvalidParse(
3390 "AD FS redirect did not include a location".to_string(),
3391 )
3392 })?
3393 .to_str()
3394 .map_err(|e| {
3395 MsalError::InvalidParse(format!("Invalid AD FS redirect location: {e}"))
3396 })?;
3397 (url, method) = resolve_adfs_redirect(
3398 &origin,
3399 &url,
3400 location,
3401 response.status().as_u16(),
3402 method,
3403 )?;
3404 continue;
3405 }
3406
3407 if !response.status().is_success() {
3408 return Err(MsalError::GeneralFailure(format!(
3409 "AD FS authentication failed with HTTP status {}",
3410 response.status()
3411 )));
3412 }
3413 let content_type = response
3414 .headers()
3415 .get(header::CONTENT_TYPE)
3416 .and_then(|value| value.to_str().ok())
3417 .map(|value| value.to_ascii_lowercase())
3418 .unwrap_or_default();
3419 if !content_type.starts_with("text/html")
3420 && !content_type.starts_with("application/xhtml+xml")
3421 {
3422 return Err(MsalError::GeneralFailure(
3423 "AD FS returned an unsupported content type".to_string(),
3424 ));
3425 }
3426 return self.read_adfs_response_body(response).await;
3427 }
3428
3429 Err(MsalError::GeneralFailure(
3430 "AD FS redirect limit exceeded".to_string(),
3431 ))
3432 }
3433
3434 async fn submit_ws_fed_form_internal(&self, form: &WsFedForm) -> Result<AuthConfig, MsalError> {
3435 let login_srf = entra_login_srf_url(&self.authority()?)?;
3436 let resp = self
3437 .client()
3438 .post(login_srf)
3439 .header(header::USER_AGENT, FIDO_USER_AGENT)
3440 .form(&[
3441 ("wa", form.wa.as_str()),
3442 ("wresult", form.wresult.as_str()),
3443 ("wctx", form.wctx.as_str()),
3444 ])
3445 .send()
3446 .await
3447 .map_err(|e| MsalError::request_failed(&e))?;
3448 self.handle_auth_config_response_internal(resp, false).await
3449 }
3450
3451 pub async fn check_user_exists(
3467 &self,
3468 username: &str,
3469 resource: Option<&str>,
3470 options: &[AuthOption],
3471 ) -> Result<AuthInit, MsalError> {
3472 let request_id = Uuid::new_v4().to_string();
3473 #[cfg(feature = "optional_mfa")]
3477 let force_mfa = options.contains(&AuthOption::ForceMFA);
3478 #[cfg(not(feature = "optional_mfa"))]
3479 let force_mfa = true;
3480 let auth_config = self
3481 .request_auth_config_internal(vec![], &request_id, resource, force_mfa)
3482 .await?;
3483 let cred_type = self
3484 .get_cred_type(username, &auth_config, &request_id, options)
3485 .await?;
3486 Ok(AuthInit {
3487 auth_config,
3488 cred_type,
3489 })
3490 }
3491
3492 pub async fn initiate_acquire_token_by_mfa_flow(
3520 &self,
3521 username: &str,
3522 password: Option<&str>,
3523 scopes: Vec<&str>,
3524 resource: Option<&str>,
3525 options: &[AuthOption],
3526 auth_init: Option<AuthInit>,
3527 #[cfg(feature = "mfa_method_selection")] mfa_method: Option<&str>,
3528 ) -> Result<MFAAuthContinue, MsalError> {
3529 #[cfg(not(feature = "mfa_method_selection"))]
3530 let mfa_method: Option<&str> = None;
3531
3532 #[cfg(feature = "optional_mfa")]
3533 let force_mfa = options.contains(&AuthOption::ForceMFA);
3534 #[cfg(not(feature = "optional_mfa"))]
3535 let force_mfa = true;
3536
3537 macro_rules! dag_fallback {
3538 () => {
3539 if !options.contains(&AuthOption::NoDAGFallback) {
3540 let mut dag_scopes: Vec<String> =
3541 scopes.into_iter().map(|s| s.to_string()).collect();
3542 let has_resource_scope =
3547 dag_scopes.iter().any(|s| s.contains("://"));
3548 if force_mfa && !has_resource_scope {
3549 dag_scopes.push(format!("{}/.default", AZURE_PORTAL_APP_ID));
3550 }
3551 info!("MFA auth failed, falling back to Device Authorization Grant.");
3552 let flow = self
3553 .initiate_device_flow(dag_scopes.iter().map(|i| i.as_str()).collect())
3554 .await?;
3555 let mut flow: MFAAuthContinue = flow.into();
3556 flow.resource = resource.map(|s| s.to_string());
3557 return Ok(flow);
3558 } else {
3559 return Err(MsalError::MFADAGFallbackDisabled);
3560 }
3561 };
3562 ($err:expr) => {
3563 if !options.contains(&AuthOption::NoDAGFallback) {
3564 #[cfg(feature = "changepassword")]
3566 if let MsalError::ChangePassword = $err {
3567 return Err($err);
3568 }
3569
3570 if let MsalError::AADSTSError(ref e) = $err {
3573 #[cfg(feature = "optional_mfa")]
3580 if options.contains(&AuthOption::RemoteSession)
3581 && [50072, 50203].contains(&e.code)
3582 {
3583 error!(
3584 "Remote session with unenrolled MFA user denied. \
3585 User must enroll in MFA before remote authentication is permitted."
3586 );
3587 return Err($err);
3588 }
3589 if ![16000, 50072, 50203].contains(&e.code) {
3597 return Err($err);
3598 }
3599 }
3600
3601 let mut dag_scopes: Vec<String> =
3602 scopes.into_iter().map(|s| s.to_string()).collect();
3603 let has_resource_scope =
3608 dag_scopes.iter().any(|s| s.contains("://"));
3609 if force_mfa && !has_resource_scope {
3610 dag_scopes.push(format!("{}/.default", AZURE_PORTAL_APP_ID));
3611 }
3612 info!("MFA auth failed, falling back to Device Authorization Grant.");
3613 let flow = self
3614 .initiate_device_flow(dag_scopes.iter().map(|i| i.as_str()).collect())
3615 .await?;
3616 let mut flow: MFAAuthContinue = flow.into();
3617 flow.resource = resource.map(|s| s.to_string());
3618 return Ok(flow);
3619 } else {
3620 return Err($err);
3621 }
3622 };
3623 }
3624 macro_rules! dag_personal_fallback {
3625 () => {
3626 let flow = self.initiate_personal_device_flow(scopes).await?;
3627 let mut flow: MFAAuthContinue = flow.into();
3628 flow.resource = resource.map(|s| s.to_string());
3629 return Ok(flow);
3630 };
3631 }
3632
3633 const OIDC_SCOPES: &[&str] = &["openid", "profile", "email", "offline_access"];
3639 let _derived_resource: Option<String>;
3640 let resource: Option<&str> = match resource {
3641 Some(r) => Some(r),
3642 None => {
3643 _derived_resource = scopes
3644 .iter()
3645 .find(|&&s| !OIDC_SCOPES.contains(&s))
3646 .map(|&s| v2_scope_to_v1_resource(s));
3647 _derived_resource.as_deref()
3648 }
3649 };
3650
3651 let request_id = Uuid::new_v4().to_string();
3652 let (mut auth_config, cred_type) = if let Some(auth_init) = auth_init {
3653 (auth_init.auth_config, auth_init.cred_type)
3654 } else {
3655 let auth_config = match self
3656 .request_auth_config_internal(scopes.clone(), &request_id, resource, force_mfa)
3657 .await
3658 {
3659 Ok(auth_config) => auth_config,
3660 Err(e) => {
3661 error!("{:?}", e);
3662 dag_fallback!();
3663 }
3664 };
3665 let cred_type = match self
3666 .get_cred_type(username, &auth_config, &request_id, options)
3667 .await
3668 {
3669 Ok(cred_type) => cred_type,
3670 Err(e) => {
3671 error!("{:?}", e);
3672 dag_fallback!(e);
3673 }
3674 };
3675 (auth_config, cred_type)
3676 };
3677 let sctx = match &auth_config.sctx {
3678 Some(sctx) => sctx.clone(),
3679 None => {
3680 info!("sCtx is missing");
3681 dag_fallback!();
3682 }
3683 };
3684 let sft = match &auth_config.sft {
3685 Some(sft) => sft.clone(),
3686 None => {
3687 info!("sFt is missing");
3688 dag_fallback!();
3689 }
3690 };
3691
3692 macro_rules! passwordless_tap {
3693 () => {
3694 if cred_type.credentials.has_access_pass.unwrap_or(false) {
3695 debug!("passwordless_tap: attempting (has_access_pass=true)");
3696 let msg = "Enter Temporary Access Pass: ".to_string();
3697 let url_post = match &auth_config.url_post {
3698 Some(url_post) => url_post.clone(),
3699 None => {
3700 return Err(MsalError::GeneralFailure(
3701 "urlBeginAuth is missing".to_string(),
3702 ))
3703 }
3704 };
3705 return Ok(MFAAuthContinue {
3706 msg,
3707 entropy: None,
3708 max_poll_attempts: None,
3709 polling_interval: None,
3710 session_id: auth_config.session_id,
3711 flow_token: sft,
3712 ctx: sctx,
3713 canary: auth_config.canary,
3714 url_end_auth: None,
3715 url_post,
3716 resource: resource.map(|s| s.to_string()),
3717 dag: None,
3718 fido_challenge: None,
3719 fido_allow_list: None,
3720 cross_domain_canary: None,
3721 url_session_state: auth_config.url_session_state,
3722 mfa_methods: vec!["AccessPass".to_string()].into(),
3723 mfa_method_details: vec![MfaMethodInfo {
3724 auth_method_id: "AccessPass".to_string(),
3725 display: "AccessPass".to_string(),
3726 is_default: true,
3727 }],
3728 selected_mfa_method_id: Some("AccessPass".to_string()),
3729 auth_code: None,
3730 #[allow(deprecated)]
3731 fido_is_passkey: false,
3732 skip_fido_for_mfa: false,
3733 has_physical_security_key: false,
3734 has_cross_device_passkey: false,
3735 });
3736 } else {
3737 debug!("passwordless_tap: skipped (has_access_pass=false)");
3738 }
3739 };
3740 }
3741
3742 let mut passwordless_remote_ngc_called = false;
3747 let mut remote_ngc_push_attempted = false;
3750 macro_rules! passwordless_remote_ngc {
3751 () => {
3752 if !passwordless_remote_ngc_called {
3753 passwordless_remote_ngc_called = true;
3754 if let Some(ref remote_ngc_params) = cred_type.credentials.remote_ngc_params {
3755 debug!("passwordless_remote_ngc: attempting (remote_ngc_params present)");
3756 remote_ngc_push_attempted = true;
3758 if let Ok(remote_ngc_params) = self
3760 .get_one_time_code(&auth_config, &remote_ngc_params, &request_id)
3761 .await
3762 {
3763 let msg = format!(
3765 "Open your Authenticator app, and enter the number '{}' to sign in.",
3766 remote_ngc_params.entropy
3767 );
3768 let url_post = match &auth_config.url_post {
3769 Some(url_post) => url_post.clone(),
3770 None => {
3771 return Err(MsalError::GeneralFailure(
3772 "urlBeginAuth is missing".to_string(),
3773 ))
3774 }
3775 };
3776 return Ok(MFAAuthContinue {
3777 msg,
3778 entropy: Some(remote_ngc_params.entropy),
3779 max_poll_attempts: auth_config.max_poll_attempts,
3780 polling_interval: Some(5000),
3781 session_id: remote_ngc_params.session_identifier,
3782 flow_token: sft,
3783 ctx: sctx,
3784 canary: auth_config.canary,
3785 url_end_auth: None,
3786 url_post,
3787 resource: resource.map(|s| s.to_string()),
3788 dag: None,
3789 fido_challenge: None,
3790 fido_allow_list: None,
3791 cross_domain_canary: None,
3792 url_session_state: auth_config.url_session_state,
3793 mfa_methods: vec!["PhoneAppNotification".to_string()].into(),
3794 mfa_method_details: vec![MfaMethodInfo {
3795 auth_method_id: "PhoneAppNotification".to_string(),
3796 display: "PhoneAppNotification".to_string(),
3797 is_default: true
3798 }],
3799 selected_mfa_method_id: Some("PhoneAppNotification".to_string()),
3800 auth_code: None,
3801 #[allow(deprecated)]
3802 fido_is_passkey: false,
3803 skip_fido_for_mfa: false,
3804 has_physical_security_key: false,
3805 has_cross_device_passkey: false,
3806 });
3807 }
3808 } else {
3809 debug!("passwordless_remote_ngc: skipped (remote_ngc_params absent)");
3810 }
3811 } else {
3812 debug!("passwordless_remote_ngc: skipped (already called)");
3813 }
3814 };
3815 }
3816
3817 debug!("Credential type: pref_credential={}, has_password={}, has_fido={:?}, has_remote_ngc={:?}, has_access_pass={:?}, is_passkey_support_enabled={:?}",
3818 cred_type.credentials.pref_credential,
3819 cred_type.credentials.has_password,
3820 cred_type.credentials.has_fido,
3821 cred_type.credentials.has_remote_ngc,
3822 cred_type.credentials.has_access_pass,
3823 auth_config.is_passkey_support_enabled,
3824 );
3825 if let Some(ref fido_params) = cred_type.credentials.fido_params {
3826 debug!(
3827 "FIDO params: has_cross_device_capable_passkey={:?}, allow_list_count={}",
3828 fido_params.has_cross_device_capable_passkey,
3829 fido_params.fido_allow_list.len(),
3830 );
3831 }
3832
3833 let user_has_any_cross_device_fido = cred_type
3834 .credentials
3835 .fido_params
3836 .as_ref()
3837 .map(|fido_params| {
3838 fido_params
3839 .has_cross_device_capable_passkey
3840 .unwrap_or(false)
3841 })
3842 .unwrap_or(false);
3843 let attempt_security_key = should_attempt_passwordless_security_key(
3844 options,
3845 cred_type.credentials.fido_params.is_some(),
3846 );
3847 let attempt_qr_bluetooth = should_attempt_passwordless_qr_bluetooth(
3848 options,
3849 cred_type.credentials.fido_params.is_some(),
3850 user_has_any_cross_device_fido,
3851 );
3852
3853 macro_rules! passwordless_fido {
3854 () => {
3855 if attempt_security_key || attempt_qr_bluetooth {
3856 let fido_params = cred_type.credentials.fido_params.as_ref().unwrap();
3857 let url_post = match &auth_config.url_post {
3858 Some(url_post) => url_post.clone(),
3859 None => {
3860 return Err(MsalError::GeneralFailure(
3861 "urlBeginAuth is missing".to_string(),
3862 ))
3863 }
3864 };
3865 auth_config.fido_allow_list = Some(fido_params.fido_allow_list.clone());
3866 let fido_auth_config = self
3867 .handle_auth_config_fido_get(username, &auth_config, &request_id)
3868 .await?;
3869 return Ok(MFAAuthContinue {
3870 msg: "".to_string(),
3871 entropy: None,
3872 max_poll_attempts: auth_config.max_poll_attempts,
3873 polling_interval: Some(5000),
3874 session_id: fido_auth_config.session_id,
3875 flow_token: sft,
3876 ctx: sctx,
3877 canary: auth_config.canary,
3878 url_end_auth: auth_config.url_end_auth,
3879 url_post,
3880 resource: resource.map(|s| s.to_string()),
3881 dag: None,
3882 fido_challenge: fido_auth_config.fido_challenge,
3883 fido_allow_list: Some(fido_params.fido_allow_list.clone()),
3884 cross_domain_canary: fido_auth_config.cross_domain_canary,
3885 url_session_state: auth_config.url_session_state,
3886 mfa_methods: vec!["FidoKey".to_string()].into(),
3887 mfa_method_details: vec![MfaMethodInfo {
3888 auth_method_id: "FidoKey".to_string(),
3889 display: "FidoKey".to_string(),
3890 is_default: true,
3891 }],
3892 selected_mfa_method_id: Some("FidoKey".to_string()),
3893 auth_code: None,
3894 #[allow(deprecated)]
3895 fido_is_passkey: false,
3896 skip_fido_for_mfa: false,
3897 has_physical_security_key: attempt_security_key,
3898 has_cross_device_passkey: attempt_qr_bluetooth,
3899 });
3900 }
3901 };
3902 }
3903
3904 match cred_type.credentials.pref_credential {
3909 13 => passwordless_tap!(),
3910 2 | 7 => {
3911 debug!(
3912 "passwordless_fido triggered via pref_credential={}",
3913 cred_type.credentials.pref_credential
3914 );
3915 passwordless_fido!();
3916 passwordless_remote_ngc!();
3917 }
3918 _ => {}
3919 }
3920
3921 debug!("passwordless_tap triggered via fallthrough");
3924 passwordless_tap!();
3925 debug!("passwordless_fido triggered via fallthrough");
3926 passwordless_fido!();
3927 debug!("passwordless_remote_ngc triggered via fallthrough");
3928 passwordless_remote_ngc!();
3929
3930 if !cred_type.account_exists()? {
3931 return Err(MsalError::GeneralFailure(
3932 "An account with that name does not exist.".to_string(),
3933 ));
3934 }
3935 if cred_type.is_personal_account() {
3936 dag_personal_fallback!();
3937 }
3938
3939 let auth_response = if let Some(ref federation_redirect_url) =
3940 cred_type.credentials.federation_redirect_url
3941 {
3942 let federation_url = match parse_adfs_federation_url(federation_redirect_url) {
3943 Ok(Some(url)) => url,
3944 Ok(None) => {
3945 info!("Federated identity is not a supported AD FS HTTPS endpoint.");
3946 dag_fallback!();
3947 }
3948 Err(e) => {
3949 error!("Unable to parse federation endpoint: {e}");
3950 dag_fallback!(e);
3951 }
3952 };
3953 info!(
3954 "Attempting AD FS forms authentication against {}",
3955 federation_url.host_str().unwrap_or("unknown host")
3956 );
3957 let password = password.ok_or(MsalError::PasswordRequired)?;
3958 match self
3959 .request_adfs_form_internal(federation_url, username, password)
3960 .await
3961 .and_then(|text| parse_ws_fed_form(&text))
3962 {
3963 Ok(form) => self.submit_ws_fed_form_internal(&form).await,
3964 Err(e) => Err(e),
3965 }
3966 } else {
3967 if !cred_type.credentials.has_password {
3968 info!("Password authentication is not supported.");
3969 dag_fallback!();
3970 }
3971 let sctx = match &auth_config.sctx {
3972 Some(sctx) => sctx.clone(),
3973 None => {
3974 info!("sCtx is missing");
3975 dag_fallback!();
3976 }
3977 };
3978 let sft = match &auth_config.sft {
3979 Some(sft) => sft.clone(),
3980 None => {
3981 info!("sFt is missing");
3982 dag_fallback!();
3983 }
3984 };
3985 let params = vec![
3986 ("login", username),
3987 ("passwd", password.ok_or(MsalError::PasswordRequired)?),
3988 ("ctx", &sctx),
3989 ("flowToken", &sft),
3990 ("canary", &auth_config.canary),
3991 ("client_id", self.client_id()),
3992 ("client-request-id", &request_id),
3993 ];
3994 self.handle_auth_config_req_internal(¶ms, &auth_config, options, false)
3995 .await
3996 };
3997 match auth_response {
3998 Ok(mut auth_config) => {
3999 if let Some(msg) = auth_config.service_exception_msg {
4000 error!("{}", msg);
4001 dag_fallback!();
4002 }
4003 if let Some(ref pgid) = auth_config.pgid {
4004 if pgid == "KmsiInterrupt" {
4005 let sctx = match &auth_config.sctx {
4006 Some(sctx) => sctx.clone(),
4007 None => {
4008 info!("sCtx is missing");
4009 dag_fallback!();
4010 }
4011 };
4012 let sft = match &auth_config.sft {
4013 Some(sft) => sft.clone(),
4014 None => {
4015 info!("sFt is missing");
4016 dag_fallback!();
4017 }
4018 };
4019 let params = vec![
4020 ("LoginOptions", "1"),
4021 ("ctx", &sctx),
4022 ("flowToken", &sft),
4023 ("canary", &auth_config.canary),
4024 ("client-request-id", &request_id),
4025 ];
4026 auth_config = match self
4027 .handle_auth_config_req_internal(¶ms, &auth_config, options, false)
4028 .await
4029 {
4030 Ok(auth_config) => auth_config,
4031 Err(e) => {
4032 error!("{:?}", e);
4033 dag_fallback!(e);
4034 }
4035 };
4036 }
4037 }
4038 if let Some(ref pgid) = auth_config.pgid {
4039 if pgid == "ConvergedProofUpRedirect" {
4040 if let Some(remaining_days) = auth_config.remaining_days_to_skip_mfa_reg {
4041 info!("MFA must be set up in {} days", remaining_days);
4042 let params = vec![
4043 ("LoginOptions", "1"),
4044 ("ctx", &sctx),
4045 ("flowToken", &sft),
4046 ("canary", &auth_config.canary),
4047 ("client-request-id", &request_id),
4048 ];
4049 auth_config = match self
4050 .handle_auth_config_req_internal(
4051 ¶ms,
4052 &auth_config,
4053 options,
4054 false,
4055 )
4056 .await
4057 {
4058 Ok(auth_config) => auth_config,
4059 Err(e) => {
4060 error!("{:?}", e);
4061 dag_fallback!(e);
4062 }
4063 };
4064 } else {
4065 info!("MFA method must be registered.");
4066 dag_fallback!();
4067 }
4068 }
4069 }
4070 if let Some(ref pgid) = auth_config.pgid {
4071 if pgid == "ConvergedChangePassword" {
4072 info!("Password is expired!");
4073 #[cfg(feature = "changepassword")]
4074 return Err(MsalError::ChangePassword);
4075 #[cfg(not(feature = "changepassword"))]
4076 dag_fallback!();
4077 }
4078 }
4079 if let Some(ref arr_user_proofs) = auth_config.arr_user_proofs {
4080 debug!("MFA methods available: {:?}", arr_user_proofs);
4081 let skip_fido_for_mfa = user_has_any_cross_device_fido
4082 || auth_config.is_passkey_support_enabled.unwrap_or(false);
4083
4084 let selected_auth_method = if let Some(requested_method) = mfa_method {
4086 arr_user_proofs
4087 .iter()
4088 .find(|proof| {
4089 proof.auth_method_id == requested_method
4090 && (!skip_fido_for_mfa || proof.auth_method_id != "FidoKey")
4091 })
4092 .ok_or_else(|| {
4093 let available = arr_user_proofs
4094 .iter()
4095 .map(|p| p.auth_method_id.as_str())
4096 .collect::<Vec<_>>();
4097 MsalError::GeneralFailure(format!(
4098 "Requested MFA method '{}' not available. Available methods: {}",
4099 requested_method, available.join(", ")
4100 ))
4101 })?
4102 } else if let Some(method) = arr_user_proofs.iter().find(|proof| {
4103 proof.is_default
4104 && (!skip_fido_for_mfa || proof.auth_method_id != "FidoKey")
4105 }) {
4106 method
4107 } else if skip_fido_for_mfa {
4108 match arr_user_proofs
4110 .iter()
4111 .find(|proof| proof.auth_method_id == "PhoneAppNotification")
4112 .or_else(|| {
4113 arr_user_proofs
4114 .iter()
4115 .find(|proof| proof.auth_method_id != "FidoKey")
4116 }) {
4117 Some(method) => method,
4118 None => {
4119 info!("No usable MFA methods found (FIDO was cross-device)");
4120 dag_fallback!();
4121 }
4122 }
4123 } else if arr_user_proofs.is_empty() {
4124 info!("No MFA methods found");
4125 dag_fallback!();
4126 } else {
4127 &arr_user_proofs[0]
4129 };
4130
4131 let sctx = match &auth_config.sctx {
4132 Some(sctx) => sctx.clone(),
4133 None => {
4134 info!("sCtx is missing");
4135 dag_fallback!();
4136 }
4137 };
4138 let sft = match &auth_config.sft {
4139 Some(sft) => sft.clone(),
4140 None => {
4141 info!("sFt is missing");
4142 dag_fallback!();
4143 }
4144 };
4145 let url_begin_auth = match &auth_config.url_begin_auth {
4146 Some(url_begin_auth) => url_begin_auth.clone(),
4147 None => {
4148 info!("urlBeginAuth is missing");
4149 dag_fallback!();
4150 }
4151 };
4152 let url_post = match &auth_config.url_post {
4153 Some(url_post) => url_post.clone(),
4154 None => {
4155 info!("urlPost is missing");
4156 dag_fallback!();
4157 }
4158 };
4159 let (flow_token, ctx, msg) = if selected_auth_method.auth_method_id == "FidoKey"
4160 {
4161 let fido_auth_config = self
4162 .handle_auth_config_fido_get(username, &auth_config, &request_id)
4163 .await?;
4164 auth_config.fido_challenge = fido_auth_config.fido_challenge.clone();
4165 auth_config.session_id = fido_auth_config.session_id.clone();
4166 auth_config.cross_domain_canary =
4167 fido_auth_config.cross_domain_canary.clone();
4168 (sft, sctx, "".to_string())
4169 } else if selected_auth_method.auth_method_id == "AccessPass" {
4170 (sft, sctx, "Enter Temporary Access Pass: ".to_string())
4171 } else if remote_ngc_push_attempted
4172 && (selected_auth_method.auth_method_id == "PhoneAppNotification"
4173 || selected_auth_method.auth_method_id == "CompanionAppsNotification")
4174 {
4175 info!(
4181 "Remote NGC push was attempted but failed. Avoiding duplicate push for {}. Falling back to DAG.",
4182 selected_auth_method.auth_method_id
4183 );
4184 dag_fallback!();
4185 } else {
4186 let auth_response = match self
4187 .mfa_begin_auth_internal(
4188 &selected_auth_method.auth_method_id,
4189 &url_begin_auth,
4190 &sctx,
4191 &sft,
4192 &auth_config.canary,
4193 )
4194 .await
4195 {
4196 Ok(auth_response) => match auth_response.success {
4197 true => auth_response,
4198 false => {
4199 return Err(MsalError::GeneralFailure(
4200 "Begin Auth failed".to_string(),
4201 ))
4202 }
4203 },
4204 Err(e) => {
4205 error!("{:?}", e);
4206 dag_fallback!(e);
4207 }
4208 };
4209 let msg = match selected_auth_method.auth_method_id.as_str() {
4210 "PhoneAppNotification" | "CompanionAppsNotification" => format!("Open your Authenticator app, and enter the number '{}' to sign in.", auth_response.entropy),
4211 "PhoneAppOTP" =>
4212 "Please type in the code displayed on your authenticator app from your device:".to_string(),
4213 "ConsolidatedTelephony" | "OneWaySMS" =>
4214 format!("We texted your phone {}. Please enter the code to sign in:", selected_auth_method.display),
4215 "TwoWayVoiceMobile" =>
4216 format!("We're calling your phone {}. Please answer it to continue.", selected_auth_method.display),
4217 "TwoWayVoiceAlternateMobile" =>
4218 format!("We're calling your phone {}. Please answer it to continue.", selected_auth_method.display),
4219 "TwoWayVoiceOffice" =>
4220 format!("We're calling your office phone {}. Please answer it to continue.", selected_auth_method.display),
4221 method => {
4222 info!("Unsupported MFA method {}", method);
4223 dag_fallback!();
4224 }
4225 };
4226 (auth_response.flow_token, auth_response.ctx, msg)
4227 };
4228 Ok(MFAAuthContinue {
4229 msg,
4230 entropy: None,
4231 max_poll_attempts: auth_config.max_poll_attempts,
4232 polling_interval: auth_config.polling_interval,
4233 session_id: auth_config.session_id,
4234 flow_token,
4235 ctx,
4236 canary: auth_config.canary,
4237 url_end_auth: auth_config.url_end_auth,
4238 url_post,
4239 resource: resource.map(|s| s.to_string()),
4240 dag: None,
4241 fido_challenge: auth_config.fido_challenge.clone(),
4242 fido_allow_list: auth_config.fido_allow_list.clone(),
4243 cross_domain_canary: auth_config.cross_domain_canary.clone(),
4244 url_session_state: None,
4245 mfa_methods: arr_user_proofs
4246 .iter()
4247 .map(|proof| proof.auth_method_id.clone())
4248 .collect(),
4249 mfa_method_details: arr_user_proofs
4250 .iter()
4251 .map(|proof| proof.into())
4252 .collect(),
4253 selected_mfa_method_id: Some(selected_auth_method.auth_method_id.clone()),
4254 auth_code: None,
4255 #[allow(deprecated)]
4256 fido_is_passkey: skip_fido_for_mfa,
4257 skip_fido_for_mfa,
4258 has_physical_security_key: false,
4259 has_cross_device_passkey: false,
4260 })
4261 } else {
4262 info!("No MFA methods found");
4263 dag_fallback!();
4264 }
4265 }
4266 Err(MsalError::AuthCodeReceived(auth_code)) => {
4267 Ok(MFAAuthContinue {
4271 msg: "".to_string(),
4272 entropy: None,
4273 max_poll_attempts: Some(1),
4274 polling_interval: Some(0),
4275 session_id: String::new(),
4276 flow_token: String::new(),
4277 ctx: String::new(),
4278 canary: String::new(),
4279 url_end_auth: None,
4280 url_post: String::new(),
4281 url_session_state: None,
4282 resource: resource.map(|s| s.to_string()),
4283 dag: None,
4284 fido_challenge: None,
4285 fido_allow_list: None,
4286 cross_domain_canary: None,
4287 mfa_methods: vec![],
4288 mfa_method_details: vec![],
4289 selected_mfa_method_id: None,
4290 auth_code: Some(auth_code),
4291 #[allow(deprecated)]
4292 fido_is_passkey: false,
4293 skip_fido_for_mfa: false,
4294 has_physical_security_key: false,
4295 has_cross_device_passkey: false,
4296 })
4297 }
4298 Err(e) => {
4299 error!("{:?}", e);
4300 dag_fallback!(e);
4301 }
4302 }
4303 }
4304
4305 async fn get_one_time_code(
4306 &self,
4307 auth_config: &AuthConfig,
4308 remote_ngc_params: &RemoteNgcParams,
4309 request_id: &str,
4310 ) -> Result<RemoteNgcParams, MsalError> {
4311 let payload = json!({
4312 "Channel": "Authenticator",
4313 "FlowToken": &auth_config.sft,
4314 "OldDeviceCode": remote_ngc_params.session_identifier,
4315 "OriginalRequest": &auth_config.sctx,
4316 });
4317
4318 let url = match &auth_config.url_get_one_time_code {
4319 Some(url) => url.to_string(),
4320 None => format!("{}/GetOneTimeCode", self.authority()?),
4321 };
4322
4323 let resp = self
4324 .client()
4325 .post(url)
4326 .header(header::CONTENT_TYPE, "application/json; charset=UTF-8")
4327 .header("client-request-id", request_id)
4328 .header("Canary", &auth_config.canary)
4329 .json(&payload)
4330 .send()
4331 .await
4332 .map_err(|e| MsalError::request_failed(&e))?;
4333 if resp.status().is_success() {
4334 let json_resp: OneTimeCode = resp
4335 .json()
4336 .await
4337 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
4338 if let Some(error) = &json_resp.error {
4339 return Err(MsalError::GeneralFailure(format!(
4340 "Failed to parse response for /GetOneTimeCode: {}",
4341 error.message.clone()
4342 )));
4343 }
4344 json_resp.remote_ngc_params.ok_or(MsalError::GeneralFailure(
4345 "remote_ngc_params missing".to_string(),
4346 ))
4347 } else {
4348 let text = resp
4349 .text()
4350 .await
4351 .map_err(|e| MsalError::GeneralFailure(format!("Failed getting otc: {}", e)))?;
4352 Err(MsalError::GeneralFailure(format!(
4353 "Request to /GetOneTimeCode failed: {}",
4354 text
4355 )))
4356 }
4357 }
4358
4359 async fn get_cred_type(
4360 &self,
4361 username: &str,
4362 auth_config: &AuthConfig,
4363 request_id: &str,
4364 options: &[AuthOption],
4365 ) -> Result<CredType, MsalError> {
4366 let payload = json!({
4367 "username": username,
4368 "isOtherIdpSupported": true,
4369 "checkPhones": true,
4370 "isRemoteNGCSupported": options.contains(&AuthOption::Passwordless),
4371 "isCookieBannerShown": false,
4372 "isFidoSupported": options.contains(&AuthOption::Fido),
4373 "isAccessPassSupported": true,
4374 "originalRequest": &auth_config.sctx,
4375 "flowToken": &auth_config.sft,
4376 });
4377
4378 let url = match &auth_config.url_get_credential_type {
4379 Some(url) => url.to_string(),
4380 None => format!("{}/GetCredentialType", self.authority()?),
4381 };
4382
4383 let resp = self
4384 .client()
4385 .post(url)
4386 .header(header::CONTENT_TYPE, "application/json; charset=UTF-8")
4387 .header("client-request-id", request_id)
4388 .header(header::USER_AGENT, FIDO_USER_AGENT)
4389 .json(&payload)
4390 .send()
4391 .await
4392 .map_err(|e| MsalError::request_failed(&e))?;
4393 if resp.status().is_success() {
4394 let json_resp: CredType = resp
4395 .json()
4396 .await
4397 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
4398 json_resp.log_throttle_status();
4399 Ok(json_resp)
4400 } else {
4401 let json_resp: ErrorResponse = resp
4402 .json()
4403 .await
4404 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
4405 Err(MsalError::AcquireTokenFailed(json_resp))
4406 }
4407 }
4408
4409 async fn request_auth_config_internal(
4410 &self,
4411 scopes: Vec<&str>,
4412 request_id: &str,
4413 resource: Option<&str>,
4414 mfa: bool,
4415 ) -> Result<AuthConfig, MsalError> {
4416 let scope = format!("openid profile {}", scopes.join(" "));
4417 let redirect_uri = self.app.get_auth_redirect_uri(None, resource);
4418 let caller_app_redirect_uri = self
4419 .app
4420 .get_auth_redirect_uri(Some(LINUX_BROKER_APP_ID), resource);
4421
4422 debug!("request_auth_config_internal() client_id={} redirect_uri={} scope={} resource={} caller_app_redirect_uri={}",
4423 self.client_id(),
4424 redirect_uri.as_str(),
4425 &scope,
4426 resource.unwrap_or("https://graph.microsoft.com"),
4427 caller_app_redirect_uri.as_str()
4428 );
4429
4430 let mut params = vec![
4434 ("client_id", self.client_id()),
4435 ("response_type", "code"),
4436 ("redirect_uri", redirect_uri.as_str()),
4437 ("client-request-id", request_id),
4438 ("prompt", "login"),
4439 ("scope", &scope),
4440 ("response_mode", "query"),
4441 ("sso_reload", "True"),
4442 (
4443 "resource",
4444 (resource.unwrap_or("https://graph.microsoft.com")),
4445 ),
4446 ("caller_app_client_id", LINUX_BROKER_APP_ID),
4447 ("caller_app_redirect_uri", caller_app_redirect_uri.as_str()),
4448 ];
4449 if mfa {
4452 params.push(("amr_values", "ngcmfa"));
4453 }
4454 let url = Url::parse_with_params(
4455 &format!("{}/oauth2/authorize", self.authority()?),
4456 ¶ms.to_vec(),
4457 )
4458 .map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
4459
4460 let resp = self
4461 .client()
4462 .get(url)
4463 .header(header::USER_AGENT, FIDO_USER_AGENT)
4464 .send()
4465 .await
4466 .map_err(|e| MsalError::request_failed(&e))?;
4467 if resp.status().is_success() {
4468 self.parse_auth_config(
4469 &resp.text().await.map_err(|e| {
4470 MsalError::GeneralFailure(format!("Failed parsing auth config: {}", e))
4471 })?,
4472 true,
4473 false,
4474 )
4475 } else {
4476 Err(MsalError::GeneralFailure(
4477 "Failed requesting auth config".to_string(),
4478 ))
4479 }
4480 }
4481
4482 async fn mfa_begin_auth_internal(
4483 &self,
4484 mfa_method: &str,
4485 url_begin_auth: &str,
4486 ctx: &str,
4487 flow_token: &str,
4488 canary: &str,
4489 ) -> Result<AuthResponse, MsalError> {
4490 let payload = json!({
4491 "AuthMethodId": mfa_method,
4492 "ctx": ctx,
4493 "flowToken": flow_token,
4494 "Method": "BeginAuth",
4495 });
4496
4497 let resp = self
4498 .client()
4499 .post(url_begin_auth)
4500 .header(header::USER_AGENT, FIDO_USER_AGENT)
4501 .header(header::CONTENT_TYPE, "application/json; charset=utf-8")
4502 .header("canary", canary)
4503 .json(&payload)
4504 .send()
4505 .await
4506 .map_err(|e| MsalError::request_failed(&e))?;
4507 if resp.status().is_success() {
4508 let text = resp
4509 .text()
4510 .await
4511 .map_err(|e| MsalError::GeneralFailure(format!("{}", e)))?;
4512 let auth_response: AuthResponse =
4513 json_from_str(&text).map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
4514 if auth_response.success {
4515 Ok(auth_response)
4516 } else if let Some(error_code) = auth_response.error_code {
4517 Err(MsalError::AADSTSError(AADSTSError::new(error_code, None)))
4518 } else if let Some(msg) = auth_response.message {
4519 Err(MsalError::GeneralFailure(format!(
4520 "BeginAuth failed with message: {}",
4521 msg
4522 )))
4523 } else {
4524 Err(MsalError::GeneralFailure("BeginAuth failed".to_string()))
4525 }
4526 } else {
4527 Err(MsalError::GeneralFailure(
4528 "BeginAuth Authentication request failed".to_string(),
4529 ))
4530 }
4531 }
4532
4533 async fn await_working(&self, mut resp: Response) -> Result<(String, Response), MsalError> {
4534 let mut body = Vec::new();
4537 while let Some(chunk) = resp.chunk().await.map_err(|e| {
4538 MsalError::GeneralFailure(format!("Error reading response chunks: {}", e))
4539 })? {
4540 body.extend(&chunk);
4541 }
4542 let mut text = String::from_utf8(body)
4543 .map_err(|e| MsalError::GeneralFailure(format!("UTF-8 error: {}", e)))?;
4544 for _ in 0..10 {
4545 if !text.contains("Click Submit to continue")
4546 && !text.contains("Working...")
4547 && !text.contains("Click here to finish the authorization process")
4548 && !text.contains("<input type=\"submit\"")
4549 {
4550 return Ok((text, resp));
4551 }
4552 sleep(Duration::from_secs(1));
4553 let (post_url, form_data) = tokio::task::spawn_blocking(
4554 move || -> Result<(String, HashMap<String, String>), MsalError> {
4555 let document = Html::parse_document(&text);
4556 let form_selector = Selector::parse("form")
4557 .map_err(|e| MsalError::InvalidParse(format!("{:?}", e)))?;
4558 let input_selector = Selector::parse("input")
4559 .map_err(|e| MsalError::InvalidParse(format!("{:?}", e)))?;
4560
4561 let form = document
4562 .select(&form_selector)
4563 .next()
4564 .ok_or(MsalError::InvalidParse("Document parse failed".to_string()))?;
4565 let post_url = form
4566 .value()
4567 .attr("action")
4568 .ok_or(MsalError::InvalidParse("Form action not found".to_string()))?;
4569
4570 let mut form_data = HashMap::new();
4571
4572 for input in form.select(&input_selector) {
4573 if let Some(name) = input.value().attr("name") {
4574 if let Some(value) = input.value().attr("value") {
4575 form_data.insert(name.to_string(), value.to_string());
4576 }
4577 }
4578 }
4579
4580 Ok((post_url.to_string(), form_data))
4581 },
4582 )
4583 .await
4584 .map_err(|e| MsalError::InvalidParse(format!("{:?}", e)))??;
4585
4586 resp = self
4587 .client()
4588 .post(post_url)
4589 .form(&form_data)
4590 .send()
4591 .await
4592 .map_err(|e| MsalError::request_failed(&e))?;
4593 let mut body = Vec::new();
4594 while let Some(chunk) = resp.chunk().await.map_err(|e| {
4595 MsalError::GeneralFailure(format!("Error reading response chunks: {}", e))
4596 })? {
4597 body.extend(&chunk);
4598 }
4599 text = String::from_utf8(body)
4600 .map_err(|e| MsalError::GeneralFailure(format!("UTF-8 error: {}", e)))?;
4601 }
4602 Err(MsalError::GeneralFailure(
4603 "Pending request timed out after 10 seconds".to_string(),
4604 ))
4605 }
4606
4607 async fn auth_code_intercept_internal(
4608 &self,
4609 url: &str,
4610 payload: String,
4611 ) -> Result<String, MsalError> {
4612 let mut resp = self
4613 .client()
4614 .post(url)
4615 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
4616 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
4617 .body(payload)
4618 .send()
4619 .await
4620 .map_err(|e| MsalError::request_failed(&e))?;
4621 let text;
4622 (text, resp) = self.await_working(resp).await?;
4623 if resp.status().is_redirection() {
4624 let redirect = resp.headers()["location"]
4625 .to_str()
4626 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
4627 let url =
4628 Url::parse(redirect).map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
4629 let params = url.query_pairs().collect::<Vec<_>>();
4630
4631 if let Some((_, error_description)) =
4633 params.iter().find(|(k, _)| k == "error_description")
4634 {
4635 let error_code_regex = Regex::new(r"AADSTS(\d+):");
4636
4637 if let Ok(regex) = error_code_regex {
4638 if let Some(captures) = regex.captures(error_description) {
4639 if let Some(code_match) = captures.get(1) {
4640 if let Ok(code) = code_match.as_str().parse::<u32>() {
4641 return Err(MsalError::AADSTSError(AADSTSError::new(
4642 code,
4643 Some(error_description.to_string()),
4644 )));
4645 }
4646 }
4647 }
4648 }
4649
4650 return Err(MsalError::GeneralFailure(format!(
4651 "Unknown error in request to {}: {}",
4652 url, error_description
4653 )));
4654 }
4655
4656 let (_, code) =
4657 params
4658 .iter()
4659 .find(|(k, _)| k == "code")
4660 .ok_or(MsalError::InvalidParse(
4661 "Authorization code missing from redirect".to_string(),
4662 ))?;
4663 Ok(code.to_string())
4664 } else if resp.status().is_success() {
4665 match self.parse_auth_config(&text, false, false) {
4669 #[cfg(feature = "changepassword")]
4670 Err(MsalError::ChangePassword) => Err(MsalError::ChangePassword),
4671 Err(MsalError::AADSTSError(e)) => Err(MsalError::AADSTSError(e)),
4672 Err(MsalError::SkipMfaRegistration(url_skip_mfa_registration, sft, canary)) => Err(
4673 MsalError::SkipMfaRegistration(url_skip_mfa_registration, sft, canary),
4674 ),
4675 Err(error) => Err(MsalError::GeneralFailure(format!(
4676 "MsalError in auth_code_intercept_internal(), {}: {}",
4677 error, text
4678 ))),
4679 Ok(value) => Ok(format!(
4681 "auth_code_intercept_internal() succeeded without redirect, pgid={:?}",
4682 value.pgid
4683 )),
4684 }
4685 } else {
4686 Err(MsalError::GeneralFailure(
4687 "ProcessAuth Authorization request failed".to_string(),
4688 ))
4689 }
4690 }
4691
4692 async fn request_authorization_passwordless_internal(
4693 &self,
4694 username: &str,
4695 flow: &MFAAuthContinue,
4696 ) -> Result<String, MsalError> {
4697 let entropy = format!(
4698 "{}",
4699 flow.entropy
4700 .ok_or(MsalError::GeneralFailure("Missing entropy".to_string()))?
4701 );
4702 let params = [
4703 ("code", &flow.session_id),
4704 ("psRNGCSLK", &flow.session_id),
4705 ("login", &username.to_string()),
4706 ("loginfmt", &username.to_string()),
4707 ("psRNGCEntropy", &entropy),
4708 ("flowToken", &flow.flow_token),
4709 ("canary", &flow.canary),
4710 ("ctx", &flow.ctx),
4711 ];
4712 let payload = params
4713 .iter()
4714 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
4715 .collect::<Vec<String>>()
4716 .join("&");
4717
4718 let url = match &flow.url_post.starts_with('/') {
4719 true => {
4720 let authority = self.authority()?.to_string();
4721 let index = authority.rfind('/').ok_or(MsalError::GeneralFailure(
4722 "Failed to splice auth config url".to_string(),
4723 ))?;
4724 format!("{}/{}", &authority[..index], flow.url_post)
4725 }
4726 false => flow.url_post.clone(),
4727 };
4728
4729 let mut resp = self
4730 .client()
4731 .post(url)
4732 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
4733 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
4734 .body(payload)
4735 .send()
4736 .await
4737 .map_err(|e| MsalError::request_failed(&e))?;
4738 let _text;
4739 (_text, resp) = self.await_working(resp).await?;
4740 if resp.status().is_redirection() {
4741 let redirect = resp.headers()["location"]
4742 .to_str()
4743 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
4744 let url =
4745 Url::parse(redirect).map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
4746 let params = url.query_pairs().collect::<Vec<_>>();
4747
4748 if let Some((_, error_description)) =
4750 params.iter().find(|(k, _)| k == "error_description")
4751 {
4752 return Err(MsalError::GeneralFailure(format!("Error found in redirect URL parameters for request_authorization_passwordless_internal() redirect: {}", error_description)));
4753 }
4754
4755 let (_, code) =
4756 params
4757 .iter()
4758 .find(|(k, _)| k == "code")
4759 .ok_or(MsalError::InvalidParse(
4760 "Authorization code missing from redirect".to_string(),
4761 ))?;
4762 Ok(code.to_string())
4763 } else {
4764 Err(MsalError::GeneralFailure(
4765 "ProcessAuth Authorization request failed".to_string(),
4766 ))
4767 }
4768 }
4769
4770 async fn request_authorization_internal(
4771 &self,
4772 username: &str,
4773 flow: &MFAAuthContinue,
4774 selected_mfa_method: &MfaMethodInfo,
4775 ) -> Result<String, MsalError> {
4776 let mfa_method = match selected_mfa_method.auth_method_id.as_str() {
4777 "ConsolidatedTelephony" => "OneWaySMS".to_string(),
4782 other => other.to_string(),
4783 };
4784 let params = [
4785 ("request", &flow.ctx),
4786 ("mfaAuthMethod", &mfa_method),
4787 ("login", &username.to_string()),
4788 ("flowToken", &flow.flow_token),
4789 ("canary", &flow.canary),
4790 ];
4791 let payload = params
4792 .iter()
4793 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
4794 .collect::<Vec<String>>()
4795 .join("&");
4796
4797 match self
4798 .auth_code_intercept_internal(&flow.url_post, payload)
4799 .await
4800 {
4801 Ok(code) => Ok(code),
4802 Err(MsalError::SkipMfaRegistration(url_skip_mfa_registration, sft, canary)) => {
4803 let params = [
4804 (
4805 "flowtoken",
4806 &sft.ok_or(MsalError::GeneralFailure("Missing flow token".to_string()))?,
4807 ),
4808 ("ctx", &flow.ctx),
4809 ("canary", &canary),
4810 ];
4811 let payload = params
4812 .iter()
4813 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
4814 .collect::<Vec<String>>()
4815 .join("&");
4816 self.auth_code_intercept_internal(&url_skip_mfa_registration, payload)
4817 .await
4818 }
4819 Err(e) => Err(e),
4820 }
4821 }
4822
4823 async fn exchange_authorization_code_for_access_token_internal(
4824 &self,
4825 authorization_code: String,
4826 resource: Option<&str>,
4827 custom_redirect_uri: Option<&str>,
4828 ) -> Result<UserToken, MsalError> {
4829 let redirect_uri = if let Some(custom_redirect_uri) = custom_redirect_uri {
4830 custom_redirect_uri.to_string()
4831 } else {
4832 self.app.get_auth_redirect_uri(None, resource)
4833 };
4834 let params = [
4835 ("client_id", self.client_id()),
4836 ("grant_type", "authorization_code"),
4837 ("code", &authorization_code),
4838 ("redirect_uri", &redirect_uri),
4839 ];
4840 let payload = params
4841 .iter()
4842 .map(|(k, v)| format!("{}={}", k, v))
4843 .collect::<Vec<String>>()
4844 .join("&");
4845
4846 let resp = self
4847 .client()
4848 .post(format!("{}/oauth2/token", self.authority()?))
4849 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
4850 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
4851 .body(payload)
4852 .send()
4853 .await
4854 .map_err(|e| MsalError::request_failed(&e))?;
4855 if resp.status().is_success() {
4856 let token: UserToken = resp
4857 .json()
4858 .await
4859 .map_err(|e| MsalError::InvalidJson(format!("Failed to parse UserToken: {}", e)))?;
4860
4861 Ok(token)
4862 } else {
4863 let text = resp.text().await.map_err(|e| {
4864 MsalError::RequestFailed(format!("Failed to read response text: {}", e))
4865 })?;
4866
4867 let json_resp: ErrorResponse = json_from_str(&text).map_err(|e| {
4868 MsalError::InvalidJson(format!(
4869 "Failed to parse ErrorResponse: {}. Raw response: {}",
4870 e, text
4871 ))
4872 })?;
4873 error!(
4874 "exchange_authorization_code_for_access_token_internal: {}",
4875 json_resp.error_description
4876 );
4877 Err(MsalError::AcquireTokenFailed(json_resp))
4878 }
4879 }
4880
4881 async fn exchange_accesspass_for_auth_code_internal(
4882 &self,
4883 username: &str,
4884 accesspass: &str,
4885 flow: &mut MFAAuthContinue,
4886 ) -> Result<String, MsalError> {
4887 let mut params = vec![
4888 ("login", username),
4889 ("loginfmt", username),
4890 ("accesspass", accesspass),
4891 ("canary", &flow.canary),
4892 ("hpgrequestid", &flow.session_id),
4893 ("flowToken", &flow.flow_token),
4894 ];
4895 if flow.url_post.contains("ProcessAuth") {
4896 params.push(("request", &flow.ctx));
4897 } else {
4898 params.push(("ctx", &flow.ctx));
4899 }
4900 let payload = params
4901 .iter()
4902 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
4903 .collect::<Vec<String>>()
4904 .join("&");
4905
4906 self.auth_code_intercept_internal(&flow.url_post, payload)
4907 .await
4908 }
4909
4910 async fn exchange_fido_assertion_for_auth_code_internal(
4911 &self,
4912 assertion: &str,
4913 flow: &mut MFAAuthContinue,
4914 ) -> Result<String, MsalError> {
4915 let cross_domain_canary = flow.cross_domain_canary.clone().ok_or(MsalError::Missing(
4916 "sCrossDomainCanary missing from response".to_string(),
4917 ))?;
4918 let params = [
4919 ("type", "23"),
4920 ("ps", "23"),
4921 ("assertion", assertion),
4922 ("lmcCanary", &cross_domain_canary),
4923 ("hpgrequestid", &flow.session_id),
4924 ("ctx", &flow.ctx),
4925 ("canary", &flow.canary),
4926 ("flowToken", &flow.flow_token),
4927 ];
4928 let payload = serde_urlencoded::to_string(params).map_err(|e| {
4933 MsalError::GeneralFailure(format!("Failed to encode FIDO assertion payload: {}", e))
4934 })?;
4935
4936 let mut resp = self
4937 .client()
4938 .post(&flow.url_post)
4939 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
4940 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
4941 .body(payload)
4942 .send()
4943 .await
4944 .map_err(|e| MsalError::request_failed(&e))?;
4945 let text;
4946 (text, resp) = self.await_working(resp).await?;
4947 debug!(
4948 "exchange_fido_assertion_for_auth_code_internal: status={}, body length={}",
4949 resp.status(),
4950 text.len()
4951 );
4952 if resp.status().is_redirection() {
4953 if let Some(location) = resp.headers().get("location") {
4956 if let Ok(redirect) = location.to_str() {
4957 if let Ok(url) = Url::parse(redirect) {
4958 let params = url.query_pairs().collect::<Vec<_>>();
4959
4960 if let Some((_, error_description)) =
4961 params.iter().find(|(k, _)| k == "error_description")
4962 {
4963 return Err(MsalError::GeneralFailure(format!(
4964 "Error in FIDO redirect: {}",
4965 error_description
4966 )));
4967 }
4968
4969 if let Some((_, code)) = params.iter().find(|(k, _)| k == "code") {
4970 return Ok(code.to_string());
4971 }
4972 }
4973 }
4974 }
4975
4976 let document = Html::parse_document(&text);
4979 let selector = Selector::parse("a[href]").map_err(|_| {
4980 MsalError::InvalidParse("Failed parsing auth code response".to_string())
4981 })?;
4982 if let Some(element) = document.select(&selector).next() {
4983 if let Some(href_encoded) = element.value().attr("href") {
4984 let href = percent_decode_str(href_encoded)
4985 .decode_utf8()
4986 .map_err(|e| {
4987 MsalError::URLFormatFailed(format!("Failed decoding url: {:?}", e))
4988 })?;
4989 if let Ok(url) = Url::parse(&href) {
4990 return url
4991 .query_pairs()
4992 .find_map(|(key, value)| {
4993 if key == "code" {
4994 Some(value.into_owned())
4995 } else {
4996 None
4997 }
4998 })
4999 .ok_or(MsalError::GeneralFailure(format!(
5000 "Authorization code not found in FIDO redirect. Body: {}",
5001 text
5002 )));
5003 }
5004 }
5005 }
5006 Err(MsalError::GeneralFailure(format!(
5007 "Authorization code not found in FIDO redirect. Body: {}",
5008 text
5009 )))
5010 } else if resp.status().is_success() {
5011 let re = Regex::new(r#"document\.location\.replace\("([^"]+)"\)"#)
5015 .map_err(|e| MsalError::InvalidRegex(format!("{}", e)))?;
5016 if let Some(m) = re.captures(&text) {
5017 if let Some(redirect) = m.get(1) {
5018 let redirect_decoded = Url::parse(&redirect.as_str().replace(r#"\u0026"#, "&"))
5019 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
5020 for (k, v) in redirect_decoded.query_pairs().collect::<Vec<_>>() {
5021 if k == "code" {
5022 return Ok(v.to_string());
5023 }
5024 if k == "error_description" {
5025 return Err(MsalError::GeneralFailure(v.to_string()));
5026 }
5027 }
5028 }
5029 }
5030
5031 let document = Html::parse_document(&text);
5033 let selector = Selector::parse("a[href]").map_err(|_| {
5034 MsalError::InvalidParse(format!("Failed parsing error response: {}", text))
5035 })?;
5036 if let Some(element) = document.select(&selector).next() {
5037 if let Some(href_encoded) = element.value().attr("href") {
5038 let href = percent_decode_str(href_encoded)
5039 .decode_utf8()
5040 .map_err(|e| {
5041 MsalError::URLFormatFailed(format!("Failed decoding url: {:?}", e))
5042 })?;
5043 if let Ok(url) = Url::parse(&href) {
5044 for (key, value) in url.query_pairs() {
5045 if key == "code" {
5046 return Ok(value.to_string());
5047 }
5048 if key == "error_description" {
5049 return Err(MsalError::GeneralFailure(format!(
5050 "error_description in FIDO response: {}",
5051 value
5052 )));
5053 }
5054 }
5055 }
5056 }
5057 }
5058
5059 match self.parse_auth_config(&text, false, false) {
5061 #[cfg(feature = "changepassword")]
5062 Err(MsalError::ChangePassword) => return Err(MsalError::ChangePassword),
5063 Err(MsalError::AADSTSError(e)) => return Err(MsalError::AADSTSError(e)),
5064 Err(MsalError::ConsentRequested(e)) => return Err(MsalError::ConsentRequested(e)),
5065 Ok(auth_config) if auth_config.pgid.as_deref() == Some("ConvergedTFA") => {
5066 return Err(MsalError::MFARequired);
5067 }
5068 _ => {}
5069 }
5070
5071 Err(MsalError::GeneralFailure(format!(
5072 "Authorization code not found in FIDO response. Body: {}",
5073 text
5074 )))
5075 } else {
5076 Err(MsalError::GeneralFailure(format!(
5077 "FIDO assertion request failed with status {}. Body: {}",
5078 resp.status(),
5079 text
5080 )))
5081 }
5082 }
5083
5084 pub async fn acquire_token_by_mfa_flow(
5104 &self,
5105 username: &str,
5106 auth_data: Option<&str>,
5107 poll_attempt: Option<u32>,
5108 flow: &mut MFAAuthContinue,
5109 ) -> Result<UserToken, MsalError> {
5110 if let Some(auth_code) = flow.auth_code.take() {
5113 return self
5114 .exchange_authorization_code_for_access_token_internal(
5115 auth_code,
5116 flow.resource.as_deref(),
5117 None,
5118 )
5119 .await;
5120 }
5121
5122 if let Some(dag_flow) = &flow.dag {
5123 if dag_flow.verification_uri.contains("www.microsoft.com/link") {
5125 return match self
5126 .acquire_token_by_personal_device_flow(dag_flow.clone())
5127 .await
5128 {
5129 Ok(token) => {
5130 if token.spn()?.to_lowercase() != username.to_lowercase() {
5131 return Err(MsalError::GeneralFailure(
5132 "The authenticating user did not match".to_string(),
5133 ));
5134 }
5135 if let Some(resource) = &flow.resource {
5139 if !resource.contains("enrollment.manage.microsoft.com") {
5140 let scope = format!("{}/.default", resource);
5141 return self
5142 .acquire_token_by_refresh_token(
5143 &token.refresh_token,
5144 vec![&scope],
5145 )
5146 .await;
5147 }
5148 }
5149 Ok(token)
5150 }
5151 Err(MsalError::AcquireTokenFailed(ref resp)) => {
5152 if resp.error_codes.contains(&AUTH_PENDING) {
5153 info!("Polling for acquire_token_by_personal_device_flow");
5154 return Err(MsalError::MFAPollContinue);
5155 }
5156 error!(
5157 "acquire_token_by_mfa_flow_internal: {}",
5158 resp.error_description
5159 );
5160 Err(MsalError::AcquireTokenFailed(resp.clone()))
5161 }
5162 Err(e) => Err(e),
5163 };
5164 } else {
5165 return match self.acquire_token_by_device_flow(dag_flow.clone()).await {
5167 Ok(token) => {
5168 if token.spn()?.to_lowercase() != username.to_lowercase() {
5169 return Err(MsalError::GeneralFailure(
5170 "The authenticating user did not match".to_string(),
5171 ));
5172 }
5173 let token = if let Some(resource) = &flow.resource {
5175 let scope = format!("{}/.default", resource);
5176 self.acquire_token_by_refresh_token(&token.refresh_token, vec![&scope])
5177 .await?
5178 } else {
5179 token
5180 };
5181 Ok(token)
5182 }
5183 Err(MsalError::AcquireTokenFailed(ref resp)) => {
5184 if resp.error_codes.contains(&AUTH_PENDING) {
5185 info!("Polling for acquire_token_by_device_flow");
5186 return Err(MsalError::MFAPollContinue);
5187 }
5188 error!(
5189 "acquire_token_by_mfa_flow_internal: {}",
5190 resp.error_description
5191 );
5192 Err(MsalError::AcquireTokenFailed(resp.clone()))
5193 }
5194 Err(e) => Err(e),
5195 };
5196 }
5197 }
5198
5199 let mfa_method = flow.selected_mfa_method_id.as_deref();
5201
5202 if let Some(method) = mfa_method {
5204 if !flow.has_mfa_method(method) {
5205 return Err(MsalError::GeneralFailure(format!(
5206 "Stored MFA method '{}' is not available. Available methods: {:?}",
5207 method,
5208 flow.get_available_mfa_methods().join(", ")
5209 )));
5210 }
5211 }
5212
5213 let selected_mfa_method = match mfa_method {
5214 Some(method) => flow.get_mfa_method_by_id(method),
5215 None => flow.get_default_mfa_method_details(),
5216 };
5217
5218 let selected_mfa_method = match selected_mfa_method {
5219 Some(value) => value,
5220 None => {
5221 let method_desc = mfa_method.unwrap_or("default");
5222 return Err(MsalError::GeneralFailure(format!(
5223 "Unable to determine MFA method details - selected method was: {}",
5224 method_desc
5225 )));
5226 }
5227 };
5228
5229 match auth_data {
5230 Some(auth_data) => {
5231 if selected_mfa_method.auth_method_id == "FidoKey" {
5232 let auth_code = self
5233 .exchange_fido_assertion_for_auth_code_internal(auth_data, flow)
5234 .await?;
5235 self.exchange_authorization_code_for_access_token_internal(
5236 auth_code,
5237 flow.resource.as_deref(),
5238 None,
5239 )
5240 .await
5241 } else if selected_mfa_method.auth_method_id == "AccessPass" {
5242 let auth_code = self
5243 .exchange_accesspass_for_auth_code_internal(username, auth_data, flow)
5244 .await?;
5245 self.exchange_authorization_code_for_access_token_internal(
5246 auth_code,
5247 flow.resource.as_deref(),
5248 None,
5249 )
5250 .await
5251 } else {
5252 let payload = json!({
5253 "AdditionalAuthData": auth_data.trim(),
5254 "AuthMethodId": &selected_mfa_method.auth_method_id,
5255 "SessionId": &flow.session_id,
5256 "FlowToken": &flow.flow_token,
5257 "Ctx": &flow.ctx,
5258 "Method": "EndAuth",
5259 });
5260 let url_end_auth = match &flow.url_end_auth {
5261 Some(url_end_auth) => url_end_auth,
5262 None => {
5263 return Err(MsalError::GeneralFailure(
5264 "urlEndAuth is missing".to_string(),
5265 ))
5266 }
5267 };
5268
5269 let resp = self
5270 .client()
5271 .post(url_end_auth)
5272 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
5273 .header(header::CONTENT_TYPE, "application/json; charset=utf-8")
5274 .header("canary", &flow.canary)
5275 .json(&payload)
5276 .send()
5277 .await
5278 .map_err(|e| match MsalError::request_failed(&e) {
5279 MsalError::RequestFailed(msg) => MsalError::RequestFailed(format!(
5280 "Request to {} failed: {}",
5281 url_end_auth, msg
5282 )),
5283 other => other,
5284 })?;
5285 if resp.status().is_success() {
5286 let text = resp.text().await.map_err(|e| {
5287 MsalError::GeneralFailure(format!("Response decoding failed: {}", e))
5288 })?;
5289 if let Ok(auth_config) = self.parse_auth_config(&text, false, false) {
5291 if let Some(service_exception_msg) = auth_config.service_exception_msg {
5292 return Err(MsalError::GeneralFailure(
5293 format!("Service exception during acquire_token_by_mfa_flow_internal(): {}", service_exception_msg),
5294 ));
5295 }
5296 }
5297 let auth_response: AuthResponse = json_from_str(&text)
5299 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
5300 if auth_response.success {
5301 flow.ctx = auth_response.ctx;
5302 flow.flow_token = auth_response.flow_token;
5303 let auth_code = self
5304 .request_authorization_internal(
5305 username,
5306 flow,
5307 &selected_mfa_method,
5308 )
5309 .await?;
5310 self.exchange_authorization_code_for_access_token_internal(
5311 auth_code,
5312 flow.resource.as_deref(),
5313 None,
5314 )
5315 .await
5316 } else if let Some(msg) = auth_response.message {
5317 Err(MsalError::MFAInvalidCode(msg))
5326 } else {
5327 Err(MsalError::GeneralFailure("EndAuth failed".to_string()))
5328 }
5329 } else {
5330 Err(MsalError::GeneralFailure(
5331 "EndAuth Authentication request failed".to_string(),
5332 ))
5333 }
5334 }
5335 }
5336 None => {
5337 let resp = if let Some(url_end_auth) = &flow.url_end_auth {
5338 let url = Url::parse_with_params(
5339 url_end_auth,
5340 [
5341 ("authMethodId", &selected_mfa_method.auth_method_id),
5342 (
5343 "pollCount",
5344 &format!(
5345 "{}",
5346 poll_attempt.ok_or(MsalError::GeneralFailure(
5347 "Poll attempt required".to_string()
5348 ))?
5349 ),
5350 ),
5351 ],
5352 )
5353 .map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
5354
5355 self.client()
5356 .get(url)
5357 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
5358 .header("x-ms-sessionId", &flow.session_id)
5359 .header("x-ms-flowToken", &flow.flow_token)
5360 .header("x-ms-ctx", &flow.ctx)
5361 .send()
5362 .await
5363 .map_err(|e| MsalError::request_failed(&e))?
5364 } else if let Some(url_session_state) = &flow.url_session_state {
5365 let url =
5366 Url::parse_with_params(url_session_state, [("code", &flow.session_id)])
5367 .map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
5368 let payload = json!({
5369 "DeviceCode": &flow.session_id,
5370 });
5371
5372 self.client()
5373 .post(url)
5374 .header(header::USER_AGENT, env!("CARGO_PKG_NAME"))
5375 .header(header::CONTENT_TYPE, "application/json")
5376 .header("canary", &flow.canary)
5377 .json(&payload)
5378 .send()
5379 .await
5380 .map_err(|e| MsalError::request_failed(&e))?
5381 } else {
5382 return Err(MsalError::GeneralFailure("Request invalid".to_string()));
5383 };
5384 if resp.status().is_success() {
5385 let text = resp.text().await.map_err(|e| {
5386 MsalError::GeneralFailure(format!("Response decoding failed: {}", e))
5387 })?;
5388 if flow.url_end_auth.is_some() {
5389 if let Ok(auth_config) = self.parse_auth_config(&text, false, false) {
5391 if let Some(service_exception_msg) = auth_config.service_exception_msg {
5392 return Err(MsalError::GeneralFailure(format!(
5393 "Service exception: {}",
5394 service_exception_msg
5395 )));
5396 }
5397 }
5398 let auth_response: AuthResponse = json_from_str(&text)
5400 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
5401 if auth_response.success {
5402 flow.ctx = auth_response.ctx;
5403 flow.flow_token = auth_response.flow_token;
5404 let auth_code = self
5405 .request_authorization_internal(
5406 username,
5407 flow,
5408 &selected_mfa_method,
5409 )
5410 .await?;
5411 return self
5412 .exchange_authorization_code_for_access_token_internal(
5413 auth_code,
5414 flow.resource.as_deref(),
5415 None,
5416 )
5417 .await;
5418 } else if !auth_response.retry.ok_or(MsalError::GeneralFailure(
5419 "Auth response Retry missing".to_string(),
5420 ))? {
5421 return Err(MsalError::AuthorizationDenied);
5425 }
5426 Err(MsalError::MFAPollContinue)
5427 } else {
5428 let status: DeviceCodeStatus = json_from_str(&text)
5429 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
5430 if status.authorization_state == 0 {
5431 Err(MsalError::MFAPollContinue)
5432 } else if status.authorization_state == 1 {
5433 Err(MsalError::AuthorizationDenied)
5434 } else if status.authorization_state == 2 {
5435 let auth_code = self
5436 .request_authorization_passwordless_internal(username, flow)
5437 .await?;
5438 return self
5439 .exchange_authorization_code_for_access_token_internal(
5440 auth_code,
5441 flow.resource.as_deref(),
5442 None,
5443 )
5444 .await;
5445 } else {
5446 Err(MsalError::GeneralFailure(format!(
5447 "Unexpected authorization_state in DeviceCodeStatus {}: {}",
5448 status.authorization_state, text
5449 )))
5450 }
5451 }
5452 } else {
5453 Err(MsalError::GeneralFailure(
5454 "EndAuth Authentication request failed".to_string(),
5455 ))
5456 }
5457 }
5458 }
5459 }
5460
5461 fn get_auth_redirect_uri(&self, client_id: Option<&str>, resource: Option<&str>) -> String {
5462 self.app.get_auth_redirect_uri(client_id, resource)
5463 }
5464}
5465
5466struct EnrollmentKeyWrapper {
5467 key: RS256Key,
5468 cert: Certificate,
5469}
5470
5471#[cfg(feature = "broker")]
5478#[derive(Clone, Serialize, Deserialize)]
5479pub enum P2PPrivateKey {
5480 ExistingDevice(LoadableMsDeviceEnrolmentKey),
5481 GeneratedUser(LoadableRS256Key),
5482}
5483
5484#[cfg(feature = "broker")]
5485#[derive(Clone, Serialize, Deserialize)]
5486pub struct P2PCertificate {
5487 pub certificate_der: Vec<u8>,
5488 pub ca_certificate_der: Vec<u8>,
5490 pub ca_certificate_pem: Option<String>,
5493 pub subject: String,
5494 pub issuer: String,
5495 pub thumbprint_sha1: String,
5496 pub dns_names: Vec<String>,
5497 pub not_after_unix: i64,
5499 pub private_key: P2PPrivateKey,
5500}
5501
5502#[cfg(feature = "broker")]
5503impl fmt::Debug for P2PCertificate {
5504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5505 f.debug_struct("P2PCertificate")
5506 .field("subject", &self.subject)
5507 .field("issuer", &self.issuer)
5508 .field("thumbprint_sha1", &self.thumbprint_sha1)
5509 .field("dns_names", &self.dns_names)
5510 .field("not_after_unix", &self.not_after_unix)
5511 .field("private_key", &"<redacted>")
5512 .finish()
5513 }
5514}
5515
5516#[cfg(feature = "broker")]
5521#[derive(Clone, Debug, PartialEq, Eq, Sequence)]
5522struct P2PCertReqInfo {
5523 pub version: x509_cert::request::Version,
5524 pub subject: Name,
5525 pub public_key: SubjectPublicKeyInfoOwned,
5526 #[asn1(context_specific = "0", tag_mode = "IMPLICIT")]
5527 pub attributes: SetOfVec<Attribute>,
5528}
5529
5530#[cfg(feature = "broker")]
5531pub struct BrokerClientApplication {
5532 app: PublicClientApplication,
5533 transport_key: Option<LoadableMsOapxbcRsaKey>,
5534 cert_key: Option<LoadableMsDeviceEnrolmentKey>,
5535 on_behalf_of_client_id: Option<String>,
5536}
5537
5538#[cfg(feature = "broker")]
5539impl BrokerClientApplication {
5540 pub fn new(
5562 authority: Option<&str>,
5563 client_id: Option<&str>,
5564 transport_key: Option<LoadableMsOapxbcRsaKey>,
5565 cert_key: Option<LoadableMsDeviceEnrolmentKey>,
5566 #[cfg(feature = "set_timeout")] timeout: Duration,
5567 #[cfg(feature = "ipvers")] ip_version: &[IpVersion],
5568 ) -> Result<Self, MsalError> {
5569 Ok(BrokerClientApplication {
5570 app: PublicClientApplication::new(
5571 BROKER_APP_ID,
5572 authority,
5573 #[cfg(feature = "set_timeout")]
5574 timeout,
5575 #[cfg(feature = "ipvers")]
5576 ip_version,
5577 )?,
5578 transport_key,
5579 cert_key,
5580 on_behalf_of_client_id: client_id.map(|s| s.to_string()),
5581 })
5582 }
5583
5584 fn client(&self) -> &Client {
5585 self.app.client()
5586 }
5587
5588 pub fn clear_cookies(&self) {
5601 self.app.clear_cookies()
5602 }
5603
5604 fn authority(&self) -> Result<String, MsalError> {
5605 self.app.authority()
5606 }
5607
5608 pub fn set_authority(&self, new_authority: &str) -> Result<(), MsalError> {
5618 self.app.set_authority(new_authority)
5619 }
5620
5621 fn transport_key(
5622 &self,
5623 tpm: &mut BoxedDynTpm,
5624 storage_key: &StorageKey,
5625 ) -> Result<MsOapxbcRsaKey, MsalError> {
5626 let transport_key = &self.transport_key.as_ref()
5627 .ok_or_else(||
5628 MsalError::ConfigError("The transport key was not found. Please provide the transport key during initialize of the BrokerClientApplication, or enroll the device.".to_string())
5629 )?;
5630
5631 tpm.msoapxbc_rsa_key_load(storage_key, transport_key)
5632 .map_err(|e| MsalError::TPMFail(format!("Failed to load Msoapxbc: {:?}", e)))
5633 }
5634
5635 pub fn set_transport_key(&mut self, transport_key: Option<LoadableMsOapxbcRsaKey>) {
5642 self.transport_key = transport_key;
5643 }
5644
5645 fn cert_key(
5646 &self,
5647 tpm: &mut BoxedDynTpm,
5648 storage_key: &StorageKey,
5649 ) -> Result<EnrollmentKeyWrapper, MsalError> {
5650 let cert_key = self.cert_key.clone()
5651 .ok_or_else(||
5652 MsalError::ConfigError("The certificate key was not found. Please provide the certificate key during initialize of the BrokerClientApplication, or enroll the device.".to_string())
5653 )?;
5654
5655 let (key, cert) = tpm
5656 .ms_device_enrolment_key_load(storage_key, cert_key)
5657 .map_err(|e| MsalError::TPMFail(format!("Failed to load IdentityKey: {:?}", e)))?;
5658 Ok(EnrollmentKeyWrapper { key, cert })
5659 }
5660
5661 pub fn set_cert_key(&mut self, cert_key: Option<LoadableMsDeviceEnrolmentKey>) {
5668 self.cert_key = cert_key;
5669 }
5670
5671 pub async fn acquire_device_p2p_certificate(
5687 &self,
5688 tenant_id: &str,
5689 device_name: &str,
5690 dns_names: Option<&[&str]>,
5691 tpm: &mut BoxedDynTpm,
5692 storage_key: &StorageKey,
5693 ) -> Result<P2PCertificate, MsalError> {
5694 debug!("Acquiring a device P2P certificate");
5695
5696 let loadable_cert_key = self.cert_key.clone().ok_or_else(|| {
5697 MsalError::ConfigError(
5698 "The certificate key was not found. Please provide the certificate key during initialize of the BrokerClientApplication, or enroll the device.".to_string(),
5699 )
5700 })?;
5701 let cert_key = self.cert_key(tpm, storage_key)?;
5702 let cert_der = cert_key.cert.to_der().map_err(|e| {
5703 MsalError::CryptoFail(format!("Failed to convert certificate to DER: {:?}", e))
5704 })?;
5705 let request_tenant_id =
5706 Self::device_certificate_tenant_id(&cert_der)?.unwrap_or_else(|| tenant_id.to_string());
5707 if request_tenant_id != tenant_id {
5708 warn!(
5709 "P2P device certificate tenant {} differs from requested tenant {}; using certificate tenant",
5710 request_tenant_id, tenant_id
5711 );
5712 }
5713 let token_endpoint = self.tenant_token_endpoint(&request_tenant_id)?;
5714 let nonce = self.request_nonce_from_endpoint(&token_endpoint).await?;
5715 let cert = X509::from_der(&cert_der).map_err(|e| {
5716 MsalError::CryptoFail(format!("Failed to create X509 from DER: {:?}", e))
5717 })?;
5718 let subject = Name::from_der(
5719 cert.subject_name()
5720 .to_der()
5721 .map_err(|e| MsalError::CryptoFail(format!("Failed encoding subject: {}", e)))?
5722 .as_slice(),
5723 )
5724 .map_err(|e| MsalError::CryptoFail(format!("Failed parsing subject: {:?}", e)))?;
5725 let (csr_der, public_key_der) = Self::create_p2p_csr(tpm, &cert_key.key, subject)?;
5726 let csr = STANDARD.encode(&csr_der);
5727 let dns_names_vec = dns_names
5728 .map(|names| names.iter().map(|name| name.to_string()).collect())
5729 .unwrap_or_else(|| vec![device_name.to_string()]);
5730
5731 let payload = P2PDeviceCertificatePayload::new(&nonce, &csr, device_name, &dns_names_vec);
5732 if let Ok(pretty) = to_string_pretty(&payload) {
5733 debug!("P2P Device Certificate Payload: {}", pretty);
5734 }
5735
5736 let signed_jwt = Self::sign_p2p_device_jwt(&payload, &cert_der, tpm, &cert_key.key)?;
5737 let response = self
5738 .post_p2p_certificate_request(&token_endpoint, &signed_jwt, "2.0")
5739 .await?;
5740
5741 Self::p2p_certificate_from_response(
5742 &response,
5743 P2PPrivateKey::ExistingDevice(loadable_cert_key),
5744 &public_key_der,
5745 )
5746 }
5747
5748 pub async fn acquire_user_p2p_certificate(
5760 &self,
5761 sealed_prt: &SealedData,
5762 tpm: &mut BoxedDynTpm,
5763 storage_key: &StorageKey,
5764 ) -> Result<P2PCertificate, MsalError> {
5765 debug!("Acquiring a user P2P certificate");
5766
5767 let transport_key = self.transport_key(tpm, storage_key)?;
5768 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
5769 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
5770 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
5771 let tenant_id = if !prt.id_token.tid.is_empty() {
5772 prt.id_token.tid.clone()
5773 } else if let Some(utid) = prt.client_info.utid {
5774 utid.to_string()
5775 } else {
5776 return Err(MsalError::GeneralFailure(
5777 "No tenant id available for P2P user certificate request".to_string(),
5778 ));
5779 };
5780 let token_endpoint = self.tenant_token_endpoint(&tenant_id)?;
5781 let nonce = self.request_nonce_from_endpoint(&token_endpoint).await?;
5782 let session_key = prt.session_key()?;
5783
5784 let loadable_user_key = tpm
5785 .rs256_create(storage_key)
5786 .map_err(|e| MsalError::TPMFail(format!("Failed creating P2P user key: {:?}", e)))?;
5787 let user_key = tpm
5788 .rs256_load(storage_key, &loadable_user_key)
5789 .map_err(|e| MsalError::TPMFail(format!("Failed loading P2P user key: {:?}", e)))?;
5790 let subject = Name::from_str("CN=")
5791 .map_err(|e| MsalError::CryptoFail(format!("Failed parsing subject: {:?}", e)))?;
5792 let (csr_der, public_key_der) = Self::create_p2p_csr(tpm, &user_key, subject)?;
5793 let csr = STANDARD.encode(&csr_der);
5794
5795 let payload = P2PUserCertificatePayload::new(&prt, &nonce, &csr);
5796 if let Ok(pretty) = payload.redacted().and_then(|redacted| {
5797 to_string_pretty(&redacted).map_err(|e| MsalError::InvalidJson(format!("{}", e)))
5798 }) {
5799 debug!("P2P User Certificate Payload: {}", pretty);
5800 }
5801
5802 let signed_jwt =
5803 self.sign_p2p_user_jwt(&payload, tpm, storage_key, &transport_key, &session_key)?;
5804 let response = self
5805 .post_p2p_certificate_request(&token_endpoint, &signed_jwt, "1.0")
5806 .await?;
5807
5808 Self::p2p_certificate_from_response(
5809 &response,
5810 P2PPrivateKey::GeneratedUser(loadable_user_key),
5811 &public_key_der,
5812 )
5813 }
5814
5815 pub async fn enroll_device(
5835 &mut self,
5836 refresh_token: &str,
5837 attrs: EnrollAttrs,
5838 tpm: &mut BoxedDynTpm,
5839 storage_key: &StorageKey,
5840 ) -> Result<(LoadableMsOapxbcRsaKey, LoadableMsDeviceEnrolmentKey, String), MsalError> {
5841 let token = self
5843 .acquire_token_by_refresh_token_for_device_enrollment(refresh_token)
5844 .await?;
5845 let (in_progess_enrolment, csr) = tpm
5848 .ms_device_enrolment_begin(storage_key, "7E980AD9-B86D-4306-9425-9AC066FB014A")
5849 .map_err(|e| MsalError::TPMFail(format!("Failed creating certificate key: {:?}", e)))?;
5850
5851 let csr_der = csr.to_der().map_err(|_|
5853 MsalError::GeneralFailure("Unable to convert X509 Request to DER".into()))?;
5855
5856 let loadable_transport_key = tpm
5857 .msoapxbc_rsa_key_create(storage_key)
5858 .map_err(|e| MsalError::TPMFail(format!("Failed creating transport key: {:?}", e)))?;
5859 self.transport_key = Some(loadable_transport_key.clone());
5860
5861 let transport_key = match tpm.msoapxbc_rsa_key_load(storage_key, &loadable_transport_key) {
5863 Ok(transport_key) => transport_key,
5864 Err(e) => {
5865 return Err(MsalError::TPMFail(format!(
5866 "Failed loading id key: {:?}",
5867 e
5868 )))
5869 }
5870 };
5871
5872 let transport_key_der = tpm
5873 .msoapxbc_rsa_public_as_der(&transport_key)
5874 .map_err(|err| {
5875 MsalError::TPMFail(format!("Failed getting transport key as der: {:?}", err))
5876 })?;
5877
5878 let transport_key_rsa = Rsa::public_key_from_der(&transport_key_der)
5879 .map_err(|e| MsalError::TPMFail(format!("{}", e)))?;
5880
5881 let (cert, device_id) = match &token.access_token {
5882 Some(access_token) => {
5883 self.enroll_device_internal(access_token, attrs, &transport_key_rsa, &csr_der)
5884 .await?
5885 }
5886 None => {
5887 return Err(MsalError::GeneralFailure(
5888 "Access token not found".to_string(),
5889 ))
5890 }
5891 };
5892
5893 let cert_der = cert.to_der().map_err(|_|
5894 MsalError::GeneralFailure("Unable to convert X509 to DER".into()))?;
5896
5897 let new_loadable_cert_key = tpm
5901 .ms_device_enrolment_finalise(storage_key, in_progess_enrolment, &cert_der)
5902 .map_err(|err| {
5903 MsalError::TPMFail(format!("Failed creating loadable identity key: {:?}", err))
5904 })?;
5905
5906 self.cert_key = Some(new_loadable_cert_key.clone());
5907 Ok((
5908 loadable_transport_key,
5909 new_loadable_cert_key,
5910 device_id.to_string(),
5911 ))
5912 }
5913
5914 async fn enroll_device_internal(
5915 &self,
5916 access_token: &str,
5917 attrs: EnrollAttrs,
5918 transport_key: &Rsa<Public>,
5919 csr_der: &Vec<u8>,
5920 ) -> Result<(X509, String), MsalError> {
5921 let services = Services::new(
5922 access_token,
5923 &attrs.target_domain,
5924 #[cfg(feature = "set_timeout")]
5925 self.app.app.timeout,
5926 #[cfg(feature = "ipvers")]
5927 &self.app.app.ip_version,
5928 )
5929 .await?;
5930 services
5931 .enroll_device(access_token, attrs, transport_key, csr_der)
5932 .await
5933 }
5934
5935 pub async fn acquire_token_by_username_password(
5960 &self,
5961 username: &str,
5962 password: &str,
5963 scopes: Vec<&str>,
5964 request_resource: Option<String>,
5965 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
5966 tpm: &mut BoxedDynTpm,
5967 storage_key: &StorageKey,
5968 ) -> Result<UserToken, MsalError> {
5969 let v2_endpoint = !scopes.is_empty();
5970 if !scopes.is_empty() && request_resource.is_some() {
5971 return Err(MsalError::GeneralFailure(
5972 "Scopes cannot be specified with a request_resource".to_string(),
5973 ));
5974 }
5975 let prt = self
5976 .acquire_user_prt_by_username_password_internal(username, password, tpm, storage_key)
5977 .await?;
5978 let transport_key = self.transport_key(tpm, storage_key)?;
5979
5980 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
5984 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
5985
5986 let session_key = prt.session_key()?;
5987 let mut token = self
5988 .exchange_prt_for_access_token_internal(
5989 &prt,
5990 scopes.clone(),
5991 v2_endpoint,
5992 tpm,
5993 storage_key,
5994 &session_key,
5995 request_resource,
5996 #[cfg(feature = "on_behalf_of")]
5997 on_behalf_of_client_id,
5998 #[cfg(feature = "redirect_uri")]
5999 None,
6000 #[cfg(feature = "pop_support")]
6001 None,
6002 false,
6003 )
6004 .await?;
6005 token.client_info = prt.client_info.clone();
6006 token.prt = Some(self.seal_user_prt(&prt, tpm, prt_storage_key)?);
6007 Ok(token)
6008 }
6009
6010 pub async fn acquire_token_by_refresh_token(
6033 &self,
6034 refresh_token: &str,
6035 scopes: Vec<&str>,
6036 request_resource: Option<String>,
6037 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
6038 tpm: &mut BoxedDynTpm,
6039 storage_key: &StorageKey,
6040 ) -> Result<UserToken, MsalError> {
6041 self.acquire_token_by_refresh_token_internal(
6042 refresh_token,
6043 scopes,
6044 request_resource,
6045 #[cfg(feature = "on_behalf_of")]
6046 on_behalf_of_client_id,
6047 false,
6048 tpm,
6049 storage_key,
6050 )
6051 .await
6052 }
6053
6054 async fn acquire_token_by_refresh_token_internal(
6055 &self,
6056 refresh_token: &str,
6057 scopes: Vec<&str>,
6058 request_resource: Option<String>,
6059 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
6060 demand_mfa: bool,
6061 tpm: &mut BoxedDynTpm,
6062 storage_key: &StorageKey,
6063 ) -> Result<UserToken, MsalError> {
6064 let prt = self
6065 .acquire_user_prt_by_refresh_token_internal(refresh_token, tpm, storage_key)
6066 .await?;
6067 let transport_key = self.transport_key(tpm, storage_key)?;
6068
6069 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
6073 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
6074
6075 let session_key = prt.session_key()?;
6076 let v2_endpoint = !scopes.is_empty();
6077 if !scopes.is_empty() && request_resource.is_some() {
6078 return Err(MsalError::GeneralFailure(
6079 "Scopes cannot be specified with a request_resource".to_string(),
6080 ));
6081 }
6082 let mut token = self
6083 .exchange_prt_for_access_token_internal(
6084 &prt,
6085 scopes.clone(),
6086 v2_endpoint,
6087 tpm,
6088 storage_key,
6089 &session_key,
6090 request_resource,
6091 #[cfg(feature = "on_behalf_of")]
6092 on_behalf_of_client_id,
6093 #[cfg(feature = "redirect_uri")]
6094 None,
6095 #[cfg(feature = "pop_support")]
6096 None,
6097 demand_mfa,
6098 )
6099 .await?;
6100 token.client_info = prt.client_info.clone();
6101 token.prt = Some(self.seal_user_prt(&prt, tpm, prt_storage_key)?);
6102 Ok(token)
6103 }
6104
6105 pub fn initiate_authorization_code_pkce_flow(
6110 &self,
6111 scopes: Vec<&str>,
6112 redirect_uri: &str,
6113 ) -> Result<AuthorizationCodePkceFlow, MsalError> {
6114 self.app
6115 .initiate_authorization_code_pkce_flow(scopes, redirect_uri)
6116 }
6117
6118 pub async fn acquire_token_by_authorization_code_pkce_flow(
6120 &self,
6121 flow: &AuthorizationCodePkceFlow,
6122 redirect_url: &str,
6123 ) -> Result<UserToken, MsalError> {
6124 self.app
6125 .acquire_token_by_authorization_code_pkce_flow(flow, redirect_url)
6126 .await
6127 }
6128
6129 pub async fn acquire_token_by_username_password_for_device_enrollment(
6141 &self,
6142 username: &str,
6143 password: &str,
6144 ) -> Result<UserToken, MsalError> {
6145 let drs_scope = "https://enrollment.manage.microsoft.com/.default";
6146 self.app
6147 .acquire_token_by_username_password(username, password, vec![drs_scope])
6148 .await
6149 }
6150
6151 async fn acquire_token_by_refresh_token_for_device_enrollment(
6152 &self,
6153 refresh_token: &str,
6154 ) -> Result<UserToken, MsalError> {
6155 let drs_scope = format!("{}/.default", DRS_APP_ID);
6156 self.app
6157 .acquire_token_by_refresh_token(refresh_token, vec![&drs_scope])
6158 .await
6159 }
6160
6161 pub async fn initiate_device_flow_for_device_enrollment(
6170 &self,
6171 #[cfg(feature = "optional_mfa")] options: &[AuthOption],
6172 ) -> Result<DeviceAuthorizationResponse, MsalError> {
6173 #[cfg(feature = "optional_mfa")]
6174 let portal_scope = if options.contains(&AuthOption::ForceMFA) {
6175 format!("{}/.default", AZURE_PORTAL_APP_ID)
6176 } else {
6177 format!("{}/.default", DRS_APP_ID)
6178 };
6179 #[cfg(not(feature = "optional_mfa"))]
6180 let portal_scope = format!("{}/.default", AZURE_PORTAL_APP_ID);
6181 self.app.initiate_device_flow(vec![&portal_scope]).await
6182 }
6183
6184 pub async fn acquire_token_by_device_flow(
6197 &self,
6198 flow: DeviceAuthorizationResponse,
6199 ) -> Result<UserToken, MsalError> {
6200 let portal_token = self.app.acquire_token_by_device_flow(flow).await?;
6201 let drs_scope = "https://enrollment.manage.microsoft.com/.default";
6202 self.app
6203 .acquire_token_by_refresh_token(&portal_token.refresh_token, vec![&drs_scope])
6204 .await
6205 }
6206
6207 pub async fn check_user_exists(
6220 &self,
6221 username: &str,
6222 options: &[AuthOption],
6223 ) -> Result<AuthInit, MsalError> {
6224 let intune_resource = "0000000a-0000-0000-c000-000000000000";
6232 self.app
6233 .check_user_exists(username, Some(intune_resource), options)
6234 .await
6235 }
6236
6237 pub async fn initiate_acquire_token_by_mfa_flow_for_device_enrollment(
6258 &self,
6259 username: &str,
6260 password: Option<&str>,
6261 options: &[AuthOption],
6262 auth_init: Option<AuthInit>,
6263 #[cfg(feature = "mfa_method_selection")] selected_method: Option<&str>,
6264 ) -> Result<MFAAuthContinue, MsalError> {
6265 let intune_resource = "0000000a-0000-0000-c000-000000000000";
6272 self.app
6273 .initiate_acquire_token_by_mfa_flow(
6274 username,
6275 password,
6276 vec![],
6277 Some(intune_resource),
6278 options,
6279 auth_init,
6280 #[cfg(feature = "mfa_method_selection")]
6281 selected_method,
6282 )
6283 .await
6284 }
6285
6286 pub async fn initiate_acquire_token_by_mfa_flow(
6312 &self,
6313 username: &str,
6314 password: Option<&str>,
6315 options: &[AuthOption],
6316 auth_init: Option<AuthInit>,
6317 #[cfg(feature = "mfa_method_selection")] selected_method: Option<&str>,
6318 ) -> Result<MFAAuthContinue, MsalError> {
6319 self.app
6320 .initiate_acquire_token_by_mfa_flow(
6321 username,
6322 password,
6323 vec![],
6324 None,
6325 options,
6326 auth_init,
6327 #[cfg(feature = "mfa_method_selection")]
6328 selected_method,
6329 )
6330 .await
6331 }
6332
6333 pub async fn acquire_token_by_mfa_flow(
6352 &self,
6353 username: &str,
6354 auth_data: Option<&str>,
6355 poll_attempt: Option<u32>,
6356 flow: &mut MFAAuthContinue,
6357 ) -> Result<UserToken, MsalError> {
6358 self.app
6359 .acquire_token_by_mfa_flow(username, auth_data, poll_attempt, flow)
6360 .await
6361 }
6362
6363 async fn request_nonce(&self) -> Result<String, MsalError> {
6365 let token_endpoint = format!("{}/oauth2/token", self.authority()?);
6366 self.request_nonce_from_endpoint(&token_endpoint).await
6367 }
6368
6369 fn tenant_token_endpoint(&self, tenant_id: &str) -> Result<String, MsalError> {
6370 if tenant_id.is_empty() || tenant_id.contains('/') {
6371 return Err(MsalError::ConfigError(
6372 "Invalid tenant id for P2P certificate request".to_string(),
6373 ));
6374 }
6375
6376 let mut authority = Url::parse(&self.authority()?)
6377 .map_err(|e| MsalError::URLFormatFailed(format!("{}", e)))?;
6378 authority.set_path(&format!("{}/oauth2/token", tenant_id));
6379 authority.set_query(None);
6380 authority.set_fragment(None);
6381 Ok(authority.to_string())
6382 }
6383
6384 async fn request_nonce_from_endpoint(&self, token_endpoint: &str) -> Result<String, MsalError> {
6385 let resp = self
6386 .client()
6387 .post(token_endpoint)
6388 .body("grant_type=srv_challenge")
6389 .send()
6390 .await
6391 .map_err(|e| MsalError::request_failed(&e))?;
6392 if resp.status().is_success() {
6393 let json_resp: Nonce = resp
6394 .json()
6395 .await
6396 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
6397 Ok(json_resp.nonce)
6398 } else {
6399 let json_resp: ErrorResponse = resp
6400 .json()
6401 .await
6402 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
6403 Err(MsalError::AcquireTokenFailed(json_resp))
6404 }
6405 }
6406
6407 fn device_certificate_tenant_id(cert_der: &[u8]) -> Result<Option<String>, MsalError> {
6408 const TENANT_ID_OID: &str = "1.2.840.113556.1.5.284.5";
6409
6410 let cert = x509_cert::Certificate::from_der(cert_der).map_err(|e| {
6411 MsalError::CryptoFail(format!("Failed parsing device certificate DER: {:?}", e))
6412 })?;
6413 let extensions = match cert.tbs_certificate.extensions.as_ref() {
6414 Some(extensions) => extensions,
6415 None => return Ok(None),
6416 };
6417 let raw = extensions
6418 .iter()
6419 .find(|ext| ext.extn_id.to_string() == TENANT_ID_OID)
6420 .map(|ext| ext.extn_value.as_bytes());
6421 let raw = match raw {
6422 Some(raw) => raw,
6423 None => return Ok(None),
6424 };
6425 match Self::tenant_id_from_device_certificate_oid(raw) {
6428 Ok(tenant_id) => Ok(Some(tenant_id)),
6429 Err(e) => {
6430 warn!("Ignoring device certificate tenant OID: {:?}", e);
6431 Ok(None)
6432 }
6433 }
6434 }
6435
6436 fn tenant_id_from_device_certificate_oid(raw: &[u8]) -> Result<String, MsalError> {
6437 let tenant_bytes = Self::device_certificate_oid_guid(raw).ok_or_else(|| {
6438 MsalError::CryptoFail("Invalid device certificate tenant OID length".to_string())
6439 })?;
6440
6441 Ok(Uuid::from_bytes_le(tenant_bytes).to_string())
6442 }
6443
6444 fn device_certificate_oid_guid(raw: &[u8]) -> Option<[u8; 16]> {
6447 if let Ok(octet_string) = OctetString::from_der(raw) {
6449 if let Ok(guid) = octet_string.as_bytes().try_into() {
6450 return Some(guid);
6451 }
6452 }
6453 if let [0x04, 0x81, len, guid @ ..] = raw {
6456 if usize::from(*len) == guid.len() {
6457 if let Ok(guid) = guid.try_into() {
6458 return Some(guid);
6459 }
6460 }
6461 }
6462 raw.try_into().ok()
6464 }
6465
6466 fn p2p_compact_signing_input<T: Serialize, U: Serialize>(
6467 header: &T,
6468 payload: &U,
6469 ) -> Result<String, MsalError> {
6470 let header_json = json_to_vec(header).map_err(|e| {
6471 MsalError::InvalidJson(format!("Failed serializing P2P JWT header: {}", e))
6472 })?;
6473 let payload_json = json_to_vec(payload).map_err(|e| {
6474 MsalError::InvalidJson(format!("Failed serializing P2P JWT payload: {}", e))
6475 })?;
6476
6477 Ok(format!(
6478 "{}.{}",
6479 URL_SAFE_NO_PAD.encode(header_json),
6480 URL_SAFE_NO_PAD.encode(payload_json)
6481 ))
6482 }
6483
6484 fn sign_p2p_device_jwt(
6485 payload: &P2PDeviceCertificatePayload,
6486 cert_der: &[u8],
6487 tpm: &mut BoxedDynTpm,
6488 signing_key: &RS256Key,
6489 ) -> Result<String, MsalError> {
6490 let x5c = STANDARD.encode(cert_der);
6491 let header = P2PDeviceCertificateHeader {
6492 alg: "RS256",
6493 typ: "JWT",
6494 x5c: &x5c,
6495 };
6496 debug!(
6497 "P2P Device Certificate Header: {}",
6498 json!({
6499 "alg": header.alg,
6500 "typ": header.typ,
6501 "x5c": "<base64 DER certificate>"
6502 })
6503 );
6504 let signing_input = Self::p2p_compact_signing_input(&header, payload)?;
6505 let signature = tpm
6506 .rs256_sign(signing_key, signing_input.as_bytes())
6507 .map_err(|e| MsalError::TPMFail(format!("Failed signing P2P device JWT: {:?}", e)))?;
6508 let signature_bytes: Box<[u8]> = signature.into();
6509
6510 Ok(format!(
6511 "{}.{}",
6512 signing_input,
6513 URL_SAFE_NO_PAD.encode(&signature_bytes)
6514 ))
6515 }
6516
6517 fn sign_p2p_user_jwt_with_key(
6518 payload: &P2PUserCertificatePayload,
6519 ctx: &[u8],
6520 hmac_key: &[u8],
6521 ) -> Result<String, MsalError> {
6522 let ctx = STANDARD.encode(ctx);
6523 let header = P2PUserCertificateHeader {
6524 alg: "HS256",
6525 typ: "JWT",
6526 ctx: &ctx,
6527 };
6528 debug!(
6529 "P2P User Certificate Header: {}",
6530 json!({
6531 "alg": header.alg,
6532 "typ": header.typ,
6533 "ctx": "<base64 context>"
6534 })
6535 );
6536 let signing_input = Self::p2p_compact_signing_input(&header, payload)?;
6537 let signature = Self::hmac_sha256(hmac_key, signing_input.as_bytes())?;
6538
6539 Ok(format!(
6540 "{}.{}",
6541 signing_input,
6542 URL_SAFE_NO_PAD.encode(signature)
6543 ))
6544 }
6545
6546 fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Vec<u8>, MsalError> {
6547 let key = PKey::hmac(key)
6548 .map_err(|e| MsalError::CryptoFail(format!("Failed creating HMAC key: {}", e)))?;
6549 let mut signer = Signer::new(MessageDigest::sha256(), &key)
6550 .map_err(|e| MsalError::CryptoFail(format!("Failed creating HMAC signer: {}", e)))?;
6551 signer
6552 .update(data)
6553 .map_err(|e| MsalError::CryptoFail(format!("Failed updating HMAC signer: {}", e)))?;
6554 signer
6555 .sign_to_vec()
6556 .map_err(|e| MsalError::CryptoFail(format!("Failed signing HMAC: {}", e)))
6557 }
6558
6559 fn sign_p2p_user_jwt(
6569 &self,
6570 payload: &P2PUserCertificatePayload,
6571 tpm: &mut BoxedDynTpm,
6572 storage_key: &StorageKey,
6573 transport_key: &MsOapxbcRsaKey,
6574 session_key: &SessionKey,
6575 ) -> Result<String, MsalError> {
6576 const P2P_CTX_LEN: usize = 24;
6577 const AAD_KDF_LABEL: &[u8; 26] = b"AzureAD-SecureConversation";
6578
6579 let mut ctx = [0u8; P2P_CTX_LEN];
6580 rand_bytes(&mut ctx)
6581 .map_err(|e| MsalError::CryptoFail(format!("Failed creating P2P context: {}", e)))?;
6582
6583 let maybe_transport_storage_key = tpm.rs256_yield_cek(transport_key);
6584 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
6585 let session_key = MsOapxbcSessionKey::complete_tpm_rsa_oaep_key_agreement(
6586 tpm,
6587 prt_storage_key,
6588 transport_key,
6589 &session_key.session_key_jwe,
6590 )
6591 .map_err(|e| MsalError::CryptoFail(format!("Unable to decipher session_key_jwe: {}", e)))?;
6592 let MsOapxbcSessionKey::A256GCM { sealed_session_key } = session_key;
6593 let aes_key = tpm
6594 .unseal_data(prt_storage_key, &sealed_session_key)
6595 .map_err(|e| {
6596 MsalError::TPMFail(format!("Failed unsealing PRT session key: {:?}", e))
6597 })?;
6598 let derived_key = crypto_glue::nist_sp800_108_kdf_hmac_sha256::derive_key_aes256(
6599 &aes_key,
6600 AAD_KDF_LABEL,
6601 &ctx,
6602 )
6603 .ok_or_else(|| {
6604 MsalError::CryptoFail("Failed deriving P2P user JWT signing key".to_string())
6605 })?;
6606
6607 Self::sign_p2p_user_jwt_with_key(payload, &ctx, &derived_key)
6608 }
6609
6610 fn create_p2p_csr(
6611 tpm: &mut BoxedDynTpm,
6612 signing_key: &RS256Key,
6613 subject: Name,
6614 ) -> Result<(Vec<u8>, Vec<u8>), MsalError> {
6615 let public_key = tpm
6616 .rs256_public(signing_key)
6617 .map_err(|e| MsalError::TPMFail(format!("Failed getting public key: {:?}", e)))?;
6618 let public_key_der = public_key
6619 .to_public_key_der()
6620 .map_err(|e| MsalError::CryptoFail(format!("Failed encoding public key: {:?}", e)))?;
6621 let spki = SubjectPublicKeyInfoOwned::try_from(public_key_der.as_bytes())
6622 .map_err(|e| MsalError::CryptoFail(format!("Failed parsing SPKI: {:?}", e)))?;
6623
6624 let cert_req_info = P2PCertReqInfo {
6625 version: x509_cert::request::Version::V1,
6626 subject,
6627 public_key: spki,
6628 attributes: SetOfVec::new(),
6629 };
6630 let tbs_der = cert_req_info
6631 .to_der()
6632 .map_err(|e| MsalError::CryptoFail(format!("Failed encoding CSR info: {:?}", e)))?;
6633 let signature = tpm
6634 .rs256_sign(signing_key, &tbs_der)
6635 .map_err(|e| MsalError::TPMFail(format!("Failed signing CSR: {:?}", e)))?;
6636 let signature_bytes: Box<[u8]> = signature.into();
6637 let signature_algorithm = AlgorithmIdentifierOwned {
6638 oid: rfc5912::SHA_256_WITH_RSA_ENCRYPTION,
6639 parameters: Some(der::asn1::AnyRef::from(der::asn1::Null).into()),
6640 };
6641
6642 #[derive(Sequence)]
6643 struct P2PCertReq {
6644 info: P2PCertReqInfo,
6645 algorithm: AlgorithmIdentifierOwned,
6646 signature: BitString,
6647 }
6648
6649 let cert_req = P2PCertReq {
6650 info: cert_req_info,
6651 algorithm: signature_algorithm,
6652 signature: BitString::from_bytes(&signature_bytes).map_err(|e| {
6653 MsalError::CryptoFail(format!("Failed creating CSR signature: {:?}", e))
6654 })?,
6655 };
6656 let csr_der = cert_req
6657 .to_der()
6658 .map_err(|e| MsalError::CryptoFail(format!("Failed encoding CSR: {:?}", e)))?;
6659
6660 Ok((csr_der, public_key_der.to_vec()))
6661 }
6662
6663 fn x509_name_to_string(name: &openssl::x509::X509NameRef) -> String {
6664 name.entries()
6665 .map(|entry| {
6666 let key = entry.object().nid().short_name().unwrap_or("OID");
6667 let value = entry
6668 .data()
6669 .to_string()
6670 .map(|value| value.to_string())
6671 .unwrap_or_else(|_| "<non-utf8>".to_string());
6672 format!("{}={}", key, value)
6673 })
6674 .collect::<Vec<String>>()
6675 .join(", ")
6676 }
6677
6678 fn p2p_certificate_from_response(
6679 response: &P2PCertificateResponse,
6680 private_key: P2PPrivateKey,
6681 expected_public_key_der: &[u8],
6682 ) -> Result<P2PCertificate, MsalError> {
6683 let certificate_der = STANDARD
6684 .decode(&response.x5c)
6685 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?;
6686 let cert = X509::from_der(&certificate_der)
6687 .map_err(|e| MsalError::CryptoFail(format!("Failed parsing P2P certificate: {}", e)))?;
6688 let cert_public_key_der = cert
6689 .public_key()
6690 .and_then(|key| key.public_key_to_der())
6691 .map_err(|e| {
6692 MsalError::CryptoFail(format!("Failed reading P2P certificate public key: {}", e))
6693 })?;
6694 if cert_public_key_der != expected_public_key_der {
6695 return Err(MsalError::CryptoFail(
6696 "P2P certificate public key does not match CSR key".to_string(),
6697 ));
6698 }
6699
6700 let thumbprint_sha1 = hash(MessageDigest::sha1(), &certificate_der)
6701 .map_err(|e| MsalError::CryptoFail(format!("{}", e)))?
6702 .iter()
6703 .map(|byte| format!("{:02X}", byte))
6704 .collect::<String>();
6705 let dns_names = cert
6706 .subject_alt_names()
6707 .map(|names| {
6708 names
6709 .iter()
6710 .filter_map(|name| name.dnsname().map(|dns| dns.to_string()))
6711 .collect()
6712 })
6713 .unwrap_or_default();
6714 let not_after_unix = Self::asn1_time_to_unix(cert.not_after())?;
6715
6716 let ca_certificate_der = STANDARD
6720 .decode(&response.x5c_ca)
6721 .map_err(|e| MsalError::InvalidBase64(format!("{}", e)))?;
6722 let ca_certificate_pem = match X509::from_der(&ca_certificate_der) {
6723 Ok(_) => Some(format!(
6724 "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n",
6725 response.x5c_ca
6726 )),
6727 Err(e) => {
6728 warn!("Failed parsing the P2P issuing CA certificate: {}", e);
6729 None
6730 }
6731 };
6732
6733 Ok(P2PCertificate {
6734 certificate_der,
6735 ca_certificate_der,
6736 ca_certificate_pem,
6737 subject: Self::x509_name_to_string(cert.subject_name()),
6738 issuer: Self::x509_name_to_string(cert.issuer_name()),
6739 thumbprint_sha1,
6740 dns_names,
6741 not_after_unix,
6742 private_key,
6743 })
6744 }
6745
6746 fn asn1_time_to_unix(time: &Asn1TimeRef) -> Result<i64, MsalError> {
6747 let epoch = Asn1Time::from_unix(0)
6748 .map_err(|e| MsalError::CryptoFail(format!("Failed creating unix epoch: {}", e)))?;
6749 let diff = epoch.diff(time).map_err(|e| {
6750 MsalError::CryptoFail(format!("Failed comparing certificate time: {}", e))
6751 })?;
6752
6753 Ok(i64::from(diff.days) * 86400 + i64::from(diff.secs))
6754 }
6755
6756 async fn post_p2p_certificate_request(
6757 &self,
6758 token_endpoint: &str,
6759 signed_jwt: &str,
6760 windows_api_version: &str,
6761 ) -> Result<P2PCertificateResponse, MsalError> {
6762 let params = [
6763 ("windows_api_version", windows_api_version),
6764 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
6765 ("request", signed_jwt),
6766 ];
6767 let payload = params
6768 .iter()
6769 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
6770 .collect::<Vec<String>>()
6771 .join("&");
6772
6773 let mut debug_payload = params;
6774 debug_payload[2] = ("request", "**********");
6775 if let Ok(pretty) = to_string_pretty(&debug_payload) {
6776 debug!("POST {}: {}", token_endpoint, pretty);
6777 }
6778
6779 let resp = self
6780 .client()
6781 .post(token_endpoint)
6782 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
6783 .body(payload)
6784 .send()
6785 .await
6786 .map_err(|e| MsalError::request_failed(&e))?;
6787 if resp.status().is_success() {
6788 resp.json()
6789 .await
6790 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))
6791 } else {
6792 let json_resp: ErrorResponse = resp
6793 .json()
6794 .await
6795 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
6796 Err(MsalError::AcquireTokenFailed(json_resp))
6797 }
6798 }
6799
6800 async fn build_jwt_by_username_password(
6801 &self,
6802 username: &str,
6803 password: &str,
6804 cert: Option<&X509>,
6805 ) -> Result<Jws, MsalError> {
6806 let nonce = self.request_nonce().await?;
6807
6808 let mut builder = JwsBuilder::from(
6809 serde_json::to_vec(&UsernamePasswordAuthenticationPayload::new(
6810 username, password, &nonce,
6811 ))
6812 .map_err(|e| {
6813 MsalError::InvalidJson(format!("Failed serializing UsernamePassword JWT: {}", e))
6814 })?,
6815 )
6816 .set_typ(Some("JWT"));
6817
6818 if let Some(cert) = cert {
6819 builder = builder.set_x5c(Some(vec![cert
6820 .to_der()
6821 .map_err(|e| MsalError::CryptoFail(format!("{}", e)))?]));
6822 }
6823
6824 let jwt = builder.build();
6825
6826 if let Ok(mut debug_jwt) = jwt.from_json::<Value>() {
6827 debug_jwt["password"] = "**********".into();
6828 if let Ok(pretty) = to_string_pretty(&debug_jwt) {
6829 debug!("Username/Password JWT: {}", pretty);
6830 }
6831 }
6832
6833 Ok(jwt)
6834 }
6835
6836 pub async fn acquire_user_prt_by_username_password(
6852 &self,
6853 username: &str,
6854 password: &str,
6855 tpm: &mut BoxedDynTpm,
6856 storage_key: &StorageKey,
6857 ) -> Result<SealedData, MsalError> {
6858 let prt = self
6859 .acquire_user_prt_by_username_password_internal(username, password, tpm, storage_key)
6860 .await?;
6861 let transport_key = self.transport_key(tpm, storage_key)?;
6862
6863 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
6867 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
6868
6869 self.seal_user_prt(&prt, tpm, prt_storage_key)
6870 }
6871
6872 async fn acquire_user_prt_by_username_password_internal(
6873 &self,
6874 username: &str,
6875 password: &str,
6876 tpm: &mut BoxedDynTpm,
6877 storage_key: &StorageKey,
6878 ) -> Result<PrimaryRefreshToken, MsalError> {
6879 debug!("Acquiring User PRT via Username/Password");
6880
6881 let cert_key = self.cert_key(tpm, storage_key)?;
6882 let cert_der = cert_key.cert.to_der().map_err(|e| {
6883 MsalError::CryptoFail(format!("Failed to convert certificate to DER: {:?}", e))
6884 })?;
6885 let cert = X509::from_der(&cert_der).map_err(|e| {
6886 MsalError::CryptoFail(format!("Failed to create X509 from DER: {:?}", e))
6887 })?;
6888 let jwt = self
6889 .build_jwt_by_username_password(username, password, Some(&cert))
6890 .await?;
6891 let signed_jwt = self.sign_jwt(&jwt, tpm, storage_key).await?;
6892
6893 self.acquire_user_prt_jwt(&signed_jwt).await
6894 }
6895
6896 async fn build_jwt_by_refresh_token(
6897 &self,
6898 refresh_token: &str,
6899 cert: Option<&X509>,
6900 ) -> Result<Jws, MsalError> {
6901 let nonce = self.request_nonce().await?;
6902
6903 let mut builder = JwsBuilder::from(
6904 serde_json::to_vec(&RefreshTokenAuthenticationPayload::new(
6905 refresh_token,
6906 &nonce,
6907 ))
6908 .map_err(|e| {
6909 MsalError::InvalidJson(format!("Failed serializing RefreshToken JWT: {}", e))
6910 })?,
6911 )
6912 .set_typ(Some("JWT"));
6913
6914 if let Some(cert) = cert {
6915 builder = builder.set_x5c(Some(vec![cert
6916 .to_der()
6917 .map_err(|e| MsalError::CryptoFail(format!("{}", e)))?]));
6918 }
6919
6920 let jwt = builder.build();
6921
6922 if let Ok(mut debug_jwt) = jwt.from_json::<Value>() {
6923 debug_jwt["refresh_token"] = "**********".into();
6924 if let Ok(pretty) = to_string_pretty(&debug_jwt) {
6925 debug!("Refresh Token JWT: {}", pretty);
6926 }
6927 }
6928
6929 Ok(jwt)
6930 }
6931
6932 pub async fn acquire_user_prt_by_refresh_token(
6947 &self,
6948 refresh_token: &str,
6949 tpm: &mut BoxedDynTpm,
6950 storage_key: &StorageKey,
6951 ) -> Result<SealedData, MsalError> {
6952 let prt = self
6953 .acquire_user_prt_by_refresh_token_internal(refresh_token, tpm, storage_key)
6954 .await?;
6955 let transport_key = self.transport_key(tpm, storage_key)?;
6956
6957 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
6961 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
6962
6963 self.seal_user_prt(&prt, tpm, prt_storage_key)
6964 }
6965
6966 async fn acquire_user_prt_by_refresh_token_internal(
6967 &self,
6968 refresh_token: &str,
6969 tpm: &mut BoxedDynTpm,
6970 storage_key: &StorageKey,
6971 ) -> Result<PrimaryRefreshToken, MsalError> {
6972 debug!("Acquiring User PRT via Refresh Token");
6973
6974 let cert_key = self.cert_key(tpm, storage_key)?;
6975 let cert_der = cert_key.cert.to_der().map_err(|e| {
6976 MsalError::CryptoFail(format!("Failed to convert certificate to DER: {:?}", e))
6977 })?;
6978 let cert = X509::from_der(&cert_der).map_err(|e| {
6979 MsalError::CryptoFail(format!("Failed to create X509 from DER: {:?}", e))
6980 })?;
6981 let jwt = self
6982 .build_jwt_by_refresh_token(refresh_token, Some(&cert))
6983 .await?;
6984 let signed_jwt = self.sign_jwt(&jwt, tpm, storage_key).await?;
6985
6986 self.acquire_user_prt_jwt(&signed_jwt).await
6987 }
6988
6989 async fn sign_jwt(
6990 &self,
6991 jwt: &Jws,
6992 tpm: &mut BoxedDynTpm,
6993 storage_key: &StorageKey,
6994 ) -> Result<String, MsalError> {
6995 let cert_key = self.cert_key(tpm, storage_key)?;
6996 let mut jws_tpm_signer = match JwsTpmRs256Signer::new(tpm, &cert_key.key) {
6997 Ok(jws_tpm_signer) => jws_tpm_signer,
6998 Err(e) => {
6999 return Err(MsalError::TPMFail(format!(
7000 "Failed loading tpm signer: {}",
7001 e
7002 )))
7003 }
7004 };
7005
7006 let signed_jwt = match jws_tpm_signer.sign(jwt) {
7007 Ok(signed_jwt) => signed_jwt,
7008 Err(e) => return Err(MsalError::TPMFail(format!("Failed signing jwk: {}", e))),
7009 };
7010
7011 Ok(format!("{}", signed_jwt))
7012 }
7013
7014 async fn acquire_user_prt_jwt(
7015 &self,
7016 signed_jwt: &str,
7017 ) -> Result<PrimaryRefreshToken, MsalError> {
7018 debug!("Acquiring User PRT via JWT");
7019
7020 let params = [
7022 ("windows_api_version", "2.0"),
7023 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
7024 ("request", signed_jwt),
7025 ("client_info", "1"),
7026 ("tgt", "true"),
7027 ];
7028 let payload = params
7029 .iter()
7030 .map(|(k, v)| format!("{}={}", k, v))
7031 .collect::<Vec<String>>()
7032 .join("&");
7033
7034 let url = format!("{}/oauth2/token", self.authority()?);
7036
7037 let mut debug_payload = params;
7038 debug_payload[2] = ("request", "**********");
7039 if let Ok(pretty) = to_string_pretty(&debug_payload) {
7040 debug!("POST {}: {}", url, pretty);
7041 }
7042
7043 let resp = self
7044 .client()
7045 .post(url)
7046 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
7047 .body(payload)
7048 .send()
7049 .await
7050 .map_err(|e| MsalError::request_failed(&e))?;
7051 if resp.status().is_success() {
7052 let json_resp: PrimaryRefreshToken = resp
7053 .json()
7054 .await
7055 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7056 Ok(json_resp)
7057 } else {
7058 let json_resp: ErrorResponse = resp
7059 .json()
7060 .await
7061 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7062 Err(MsalError::AcquireTokenFailed(json_resp))
7063 }
7064 }
7065
7066 async fn sign_session_key_jwt(
7067 &self,
7068 jwt: &Jws,
7069 tpm: &mut BoxedDynTpm,
7070 storage_key: &StorageKey,
7071 session_key: &SessionKey,
7072 ) -> Result<String, MsalError> {
7073 let transport_key = self.transport_key(tpm, storage_key)?;
7074
7075 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7079 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7080
7081 let signed_jwt = session_key.sign(tpm, &transport_key, prt_storage_key, jwt)?;
7082
7083 Ok(format!("{}", signed_jwt))
7084 }
7085
7086 pub async fn exchange_prt_for_ssh_certificate(
7092 &self,
7093 sealed_prt: &SealedData,
7094 openssh_public_key: &str,
7095 tpm: &mut BoxedDynTpm,
7096 storage_key: &StorageKey,
7097 ) -> Result<EntraSshCertificate, MsalError> {
7098 let jwk = ssh_rsa_public_key_to_jwk(openssh_public_key)?;
7099 let transport_key = self.transport_key(tpm, storage_key)?;
7100 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7101 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7102 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
7103 let session_key = prt.session_key()?;
7104 let redirect_uri = self.app.get_auth_redirect_uri(Some(AZURE_CLI_APP_ID), None);
7105 let bearer_token = self
7106 .exchange_prt_for_refresh_token_jwt_bearer(
7107 &prt,
7108 tpm,
7109 storage_key,
7110 &session_key,
7111 AZURE_CLI_APP_ID,
7112 &redirect_uri,
7113 None,
7114 )
7115 .await?;
7116 self.acquire_ssh_certificate_with_refresh_token(&bearer_token.refresh_token, &jwk)
7117 .await
7118 }
7119
7120 async fn acquire_ssh_certificate_with_refresh_token(
7121 &self,
7122 refresh_token: &str,
7123 jwk: &SshRsaJwk,
7124 ) -> Result<EntraSshCertificate, MsalError> {
7125 let payload = build_ssh_certificate_request_form(refresh_token, jwk)?;
7126 let url = format!("{}/oauth2/v2.0/token", self.authority()?);
7127 debug!("POST SSH certificate request {} for key {}", url, jwk.kid);
7128
7129 let response = self
7130 .client()
7131 .post(&url)
7132 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
7133 .header(header::ACCEPT, "application/json")
7134 .body(payload)
7135 .send()
7136 .await
7137 .map_err(|e| MsalError::request_failed(&e))?;
7138 if response.status().is_success() {
7139 let response: SshCertificateTokenResponse = response
7140 .json()
7141 .await
7142 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7143 parse_ssh_certificate_response(response, jwk)
7144 } else {
7145 let response: ErrorResponse = response
7146 .json()
7147 .await
7148 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7149 Err(MsalError::AcquireTokenFailed(response))
7150 }
7151 }
7152
7153 #[allow(clippy::too_many_arguments)]
7177 pub async fn exchange_prt_for_access_token(
7178 &self,
7179 sealed_prt: &SealedData,
7180 scope: Vec<&str>,
7181 request_resource: Option<String>,
7182 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
7183 tpm: &mut BoxedDynTpm,
7184 storage_key: &StorageKey,
7185 #[cfg(feature = "redirect_uri")] redirect_uri: Option<&str>,
7186 #[cfg(feature = "pop_support")] req_cnf: Option<&str>,
7187 ) -> Result<UserToken, MsalError> {
7188 #[cfg(not(feature = "pop_support"))]
7189 let _req_cnf: Option<&str> = None;
7190
7191 let v2_endpoint = !scope.is_empty();
7192 if !scope.is_empty() && request_resource.is_some() {
7193 return Err(MsalError::GeneralFailure(
7194 "Scopes cannot be specified with a request_resource".to_string(),
7195 ));
7196 }
7197 let transport_key = self.transport_key(tpm, storage_key)?;
7198
7199 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7203 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7204
7205 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
7206 let session_key = prt.session_key()?;
7207 let mut token = self
7208 .exchange_prt_for_access_token_internal(
7209 &prt,
7210 scope,
7211 v2_endpoint,
7212 tpm,
7213 storage_key,
7215 &session_key,
7216 request_resource,
7217 #[cfg(feature = "on_behalf_of")]
7218 on_behalf_of_client_id,
7219 #[cfg(feature = "redirect_uri")]
7220 redirect_uri,
7221 #[cfg(feature = "pop_support")]
7222 req_cnf,
7223 false,
7224 )
7225 .await?;
7226 token.client_info = prt.client_info.clone();
7227 Ok(token)
7228 }
7229
7230 #[allow(clippy::too_many_arguments)]
7231 async fn exchange_prt_for_access_token_internal(
7232 &self,
7233 prt: &PrimaryRefreshToken,
7234 scope: Vec<&str>,
7235 v2_endpoint: bool,
7236 tpm: &mut BoxedDynTpm,
7237 storage_key: &StorageKey,
7238 session_key: &SessionKey,
7239 request_resource: Option<String>,
7240 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
7241 #[cfg(feature = "redirect_uri")] redirect_uri: Option<&str>,
7242 #[cfg(feature = "pop_support")] req_cnf: Option<&str>,
7243 demand_mfa: bool,
7244 ) -> Result<UserToken, MsalError> {
7245 debug!("Exchanging a PRT for an Access Token");
7246
7247 #[cfg(not(feature = "pop_support"))]
7248 let req_cnf: Option<&str> = None;
7249
7250 if let Some(req_cnf_val) = req_cnf {
7255 return self
7256 .exchange_prt_for_access_token_jwt_bearer(
7257 prt,
7258 scope,
7259 v2_endpoint,
7260 tpm,
7261 storage_key,
7262 session_key,
7263 request_resource,
7264 req_cnf_val,
7265 #[cfg(feature = "on_behalf_of")]
7266 on_behalf_of_client_id,
7267 #[cfg(feature = "redirect_uri")]
7268 redirect_uri,
7269 )
7270 .await;
7271 }
7272
7273 let request_id = Uuid::new_v4().to_string();
7274 let auth_code = self
7275 .exchange_prt_for_auth_code(
7276 prt,
7277 scope.clone(),
7278 &request_id,
7279 request_resource.as_deref(),
7280 v2_endpoint,
7281 session_key,
7282 #[cfg(feature = "on_behalf_of")]
7283 on_behalf_of_client_id,
7284 tpm,
7285 storage_key,
7286 #[cfg(feature = "redirect_uri")]
7287 redirect_uri,
7288 req_cnf,
7289 demand_mfa,
7290 )
7291 .await?;
7292
7293 self.exchange_auth_code_for_access_token_internal(
7294 scope,
7295 &request_id,
7296 v2_endpoint,
7297 auth_code,
7298 request_resource.as_deref(),
7299 #[cfg(feature = "on_behalf_of")]
7300 on_behalf_of_client_id,
7301 #[cfg(feature = "redirect_uri")]
7302 redirect_uri,
7303 req_cnf,
7304 )
7305 .await
7306 }
7307
7308 #[allow(clippy::too_many_arguments)]
7309 async fn exchange_prt_for_refresh_token_jwt_bearer(
7310 &self,
7311 prt: &PrimaryRefreshToken,
7312 tpm: &mut BoxedDynTpm,
7313 storage_key: &StorageKey,
7314 session_key: &SessionKey,
7315 client_id: &str,
7316 redirect_uri: &str,
7317 request_resource: Option<&str>,
7318 ) -> Result<UserToken, MsalError> {
7319 let nonce = self.request_nonce().await?;
7320 let jwt_payload = ExchangePRTForATPayload::new(
7321 prt,
7322 &nonce,
7323 "openid profile offline_access",
7324 client_id,
7325 redirect_uri,
7326 request_resource,
7327 )?;
7328 let jwt = JwsBuilder::from(serde_json::to_vec(&jwt_payload).map_err(|e| {
7329 MsalError::InvalidJson(format!("Failed serializing ExchangePRTForAT JWT: {}", e))
7330 })?)
7331 .set_typ(Some("JWT"))
7332 .build();
7333
7334 if let Ok(mut payload) = jwt.from_json::<Value>() {
7335 payload["refresh_token"] = "**********".into();
7336 if let Ok(pretty) = to_string_pretty(&payload) {
7337 debug!("Exchange PRT for refresh token payload: {}", pretty);
7338 }
7339 }
7340
7341 let signed_jwt = self
7342 .sign_session_key_jwt(&jwt, tpm, storage_key, session_key)
7343 .await?;
7344 let params = [
7345 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
7346 ("windows_api_version", "2.2"),
7347 ("request", signed_jwt.as_str()),
7348 ("client_id", client_id),
7349 ("redirect_uri", redirect_uri),
7350 ("client_info", "1"),
7351 ];
7352 let payload = params
7353 .iter()
7354 .map(|(key, value)| format!("{}={}", key, value))
7355 .collect::<Vec<String>>()
7356 .join("&");
7357 let url = format!("{}/oauth2/token", self.authority()?);
7358 let mut debug_params = params;
7359 debug_params[2] = ("request", "**********");
7360 if let Ok(pretty) = to_string_pretty(&debug_params) {
7361 debug!("POST PRT refresh-token exchange {}: {}", url, pretty);
7362 }
7363
7364 let response = self
7365 .client()
7366 .post(&url)
7367 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
7368 .body(payload)
7369 .send()
7370 .await
7371 .map_err(|e| MsalError::request_failed(&e))?;
7372 let token: UserToken = if response.status().is_success() {
7373 let response_text = response
7374 .text()
7375 .await
7376 .map_err(|e| MsalError::GeneralFailure(format!("{}", e)))?;
7377 let transport_key = self.transport_key(tpm, storage_key)?;
7378 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7379 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7380 if let Ok(jwe) = JweCompact::from_str(&response_text) {
7381 let decrypted =
7382 session_key.decipher_prt_v2(tpm, &transport_key, prt_storage_key, &jwe)?;
7383 json_from_str(
7384 std::str::from_utf8(decrypted.payload())
7385 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
7386 )
7387 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?
7388 } else {
7389 json_from_str(&response_text)
7390 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?
7391 }
7392 } else {
7393 let response: ErrorResponse = response
7394 .json()
7395 .await
7396 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7397 return Err(MsalError::AcquireTokenFailed(response));
7398 };
7399 debug!(
7400 "PRT refresh-token exchange succeeded, refresh_token present={}",
7401 !token.refresh_token.is_empty()
7402 );
7403 Ok(token)
7404 }
7405
7406 #[allow(clippy::too_many_arguments)]
7418 async fn exchange_prt_for_access_token_jwt_bearer(
7419 &self,
7420 prt: &PrimaryRefreshToken,
7421 scope: Vec<&str>,
7422 v2_endpoint: bool,
7423 tpm: &mut BoxedDynTpm,
7424 storage_key: &StorageKey,
7425 session_key: &SessionKey,
7426 request_resource: Option<String>,
7427 req_cnf: &str,
7428 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
7429 #[cfg(feature = "redirect_uri")] redirect_uri_override: Option<&str>,
7430 ) -> Result<UserToken, MsalError> {
7431 debug!("Exchanging a PRT for an Access Token via JWT bearer (PoP)");
7432
7433 #[cfg(not(feature = "on_behalf_of"))]
7434 let on_behalf_of_client_id: Option<&str> = None;
7435 #[cfg(not(feature = "redirect_uri"))]
7436 let redirect_uri_override: Option<&str> = None;
7437
7438 let (client_id, redirect_uri) = if let Some(uri) = redirect_uri_override {
7441 let cid = if v2_endpoint {
7442 if let Some(obo) = on_behalf_of_client_id {
7443 obo.to_string()
7444 } else if let Some(obo) = &self.on_behalf_of_client_id {
7445 obo.clone()
7446 } else {
7447 LINUX_BROKER_APP_ID.to_string()
7448 }
7449 } else {
7450 self.app.client_id().to_string()
7451 };
7452 (cid, uri.to_string())
7453 } else if v2_endpoint {
7454 if let Some(obo) = on_behalf_of_client_id {
7455 (
7456 obo.to_string(),
7457 self.app
7458 .get_auth_redirect_uri(Some(obo), request_resource.as_deref()),
7459 )
7460 } else if let Some(obo) = &self.on_behalf_of_client_id {
7461 (obo.clone(), HIMMELBLAU_REDIRECT_URI.to_string())
7462 } else {
7463 (
7464 LINUX_BROKER_APP_ID.to_string(),
7465 self.app.get_auth_redirect_uri(
7466 Some(LINUX_BROKER_APP_ID),
7467 request_resource.as_deref(),
7468 ),
7469 )
7470 }
7471 } else {
7472 (
7473 self.app.client_id().to_string(),
7474 self.app
7475 .get_auth_redirect_uri(None, request_resource.as_deref()),
7476 )
7477 };
7478
7479 let bearer_token = self
7480 .exchange_prt_for_refresh_token_jwt_bearer(
7481 prt,
7482 tpm,
7483 storage_key,
7484 session_key,
7485 &client_id,
7486 &redirect_uri,
7487 request_resource.as_deref(),
7488 )
7489 .await?;
7490
7491 let target_scopes = if v2_endpoint {
7494 format!("openid profile offline_access {}", scope.join(" "))
7495 } else {
7496 "openid".to_string()
7497 };
7498
7499 let mut step2_params = vec![
7500 ("client_id", client_id.as_str()),
7501 ("scope", target_scopes.as_str()),
7502 ("grant_type", "refresh_token"),
7503 ("refresh_token", &bearer_token.refresh_token),
7504 ("client_info", "1"),
7505 ("token_type", "pop"),
7506 ("req_cnf", req_cnf),
7507 ];
7508 if !v2_endpoint {
7509 if let Some(resource) = request_resource.as_deref() {
7510 step2_params.push(("resource", resource));
7511 }
7512 }
7513 let step2_payload = step2_params
7514 .iter()
7515 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
7516 .collect::<Vec<String>>()
7517 .join("&");
7518
7519 let step2_url = if v2_endpoint {
7522 format!("{}/oauth2/v2.0/token", self.authority()?)
7523 } else {
7524 format!("{}/oauth2/token", self.authority()?)
7525 };
7526
7527 let mut debug_params2 = step2_params.clone();
7528 debug_params2[3] = ("refresh_token", "**********");
7529 if let Ok(pretty) = to_string_pretty(&debug_params2) {
7530 debug!("Step 2 POST {}: {}", step2_url, pretty);
7531 }
7532
7533 let resp2 = self
7534 .client()
7535 .post(&step2_url)
7536 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
7537 .header(header::ACCEPT, "application/json")
7538 .body(step2_payload)
7539 .send()
7540 .await
7541 .map_err(|e| MsalError::request_failed(&e))?;
7542
7543 if resp2.status().is_success() {
7544 let token: UserToken = resp2
7545 .json()
7546 .await
7547 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7548 Ok(token)
7549 } else {
7550 let json_resp: ErrorResponse = resp2
7551 .json()
7552 .await
7553 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7554 Err(MsalError::AcquireTokenFailed(json_resp))
7555 }
7556 }
7557
7558 pub async fn exchange_prt_for_prt(
7577 &self,
7578 sealed_prt: &SealedData,
7579 tpm: &mut BoxedDynTpm,
7580 storage_key: &StorageKey,
7581 request_tgt: bool,
7582 ) -> Result<SealedData, MsalError> {
7583 debug!("Exchanging a PRT for a new PRT");
7584
7585 let transport_key = self.transport_key(tpm, storage_key)?;
7586
7587 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7591 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7592
7593 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
7594 let session_key = prt.session_key()?;
7595 let nonce = self.request_nonce().await?;
7596 let jwt = JwsBuilder::from(
7597 serde_json::to_vec(&ExchangePRTPayload::new(&prt, &nonce, None, true)?).map_err(
7598 |e| MsalError::InvalidJson(format!("Failed serializing ExchangePRT JWT: {}", e)),
7599 )?,
7600 )
7601 .set_typ(Some("JWT"))
7602 .build();
7603
7604 if let Ok(mut payload) = jwt.from_json::<Value>() {
7605 payload["refresh_token"] = "**********".into();
7606 if let Ok(pretty) = to_string_pretty(&payload) {
7607 debug!("Exchange PRT Payload JWT: {}", pretty);
7608 }
7609 }
7610
7611 let signed_jwt = self
7612 .sign_session_key_jwt(&jwt, tpm, storage_key, &session_key)
7613 .await?;
7614
7615 let mut params = vec![
7616 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
7617 ("windows_api_version", "2.2"),
7618 ("request", &signed_jwt),
7619 ("client_info", "1"),
7620 ];
7621 if request_tgt {
7622 params.push(("tgt", "true"));
7623 }
7624 let payload = params
7625 .iter()
7626 .map(|(k, v)| format!("{}={}", k, v))
7627 .collect::<Vec<String>>()
7628 .join("&");
7629
7630 let url = format!("{}/oauth2/token", self.authority()?);
7632
7633 let mut debug_payload = params.clone();
7634 debug_payload[2] = ("request", "**********");
7635 if let Ok(pretty) = to_string_pretty(&debug_payload) {
7636 debug!("POST {}: {}", url, pretty);
7637 }
7638
7639 let resp = self
7640 .client()
7641 .post(url)
7642 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
7643 .body(payload)
7644 .send()
7645 .await
7646 .map_err(|e| MsalError::request_failed(&e))?;
7647 if resp.status().is_success() {
7648 let enc = resp
7649 .text()
7650 .await
7651 .map_err(|e| MsalError::GeneralFailure(format!("{}", e)))?;
7652 let jwe = JweCompact::from_str(&enc)
7653 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
7654 let mut new_prt: PrimaryRefreshToken = json_from_str(
7655 std::str::from_utf8(
7656 session_key
7657 .decipher_prt_v2(tpm, &transport_key, prt_storage_key, &jwe)?
7658 .payload(),
7659 )
7660 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?,
7661 )
7662 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7663 prt.clone_session_key(&mut new_prt);
7664 self.seal_user_prt(&new_prt, tpm, prt_storage_key)
7665 } else {
7666 let json_resp: ErrorResponse = resp
7667 .json()
7668 .await
7669 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
7670 Err(MsalError::AcquireTokenFailed(json_resp))
7671 }
7672 }
7673
7674 pub async fn provision_hello_for_business_key(
7693 &self,
7694 token: &UserToken,
7695 tpm: &mut BoxedDynTpm,
7696 storage_key: &StorageKey,
7697 pin: &str,
7698 ) -> Result<LoadableMsHelloKey, MsalError> {
7699 debug!("Provisioning a Hello for Business Key");
7700
7701 if !token.amr_ngcmfa()? && !token.amr_mfa()? {
7704 error!("Key provisioning is impossible without an ngcmfa amr!");
7705 return Err(MsalError::GeneralFailure(
7706 "Token is missing an ngcmfa amr".to_string(),
7707 ));
7708 }
7709
7710 let pin = PinValue::new(pin)
7711 .map_err(|e| MsalError::TPMFail(format!("Failed setting pin value: {:?}", e)))?;
7712
7713 let access_token = match &token.access_token {
7715 Some(access_token) => access_token.clone(),
7716 None => {
7717 return Err(MsalError::GeneralFailure(
7718 "Access token missing".to_string(),
7719 ))
7720 }
7721 };
7722 let services = Services::new(
7723 &access_token,
7724 &token.tenant_id()?,
7725 #[cfg(feature = "set_timeout")]
7726 self.app.app.timeout,
7727 #[cfg(feature = "ipvers")]
7728 &self.app.app.ip_version,
7729 )
7730 .await?;
7731 let resource_id = services.key_provisioning_resource_id();
7732
7733 let token = self
7735 .acquire_token_by_refresh_token_internal(
7736 &token.refresh_token,
7737 vec![],
7738 Some(resource_id),
7739 #[cfg(feature = "on_behalf_of")]
7740 None,
7741 true, tpm,
7743 storage_key,
7744 )
7745 .await?;
7746
7747 let loadable_win_hello_key = tpm.ms_hello_key_create(storage_key, &pin).map_err(|e| {
7749 MsalError::TPMFail(format!("Failed creating Windows Hello Key: {:?}", e))
7750 })?;
7751
7752 let (win_hello_key, _win_hello_storage_key) = tpm
7755 .ms_hello_key_load(storage_key, &loadable_win_hello_key, &pin)
7756 .map_err(|e| {
7757 MsalError::TPMFail(format!("Failed loading Windows Hello Key: {:?}", e))
7758 })?;
7759
7760 let win_hello_pub_der = tpm
7761 .ms_hello_rsa_public_as_der(&win_hello_key)
7762 .map_err(|e| {
7763 MsalError::TPMFail(format!("Failed getting Windows Hello Key as der: {:?}", e))
7764 })?;
7765
7766 let win_hello_rsa = Rsa::public_key_from_der(&win_hello_pub_der)
7767 .map_err(|e| MsalError::TPMFail(format!("{}", e)))?;
7768
7769 let access_token = match &token.access_token {
7770 Some(access_token) => access_token.clone(),
7771 None => {
7772 return Err(MsalError::GeneralFailure(
7773 "Access token missing".to_string(),
7774 ))
7775 }
7776 };
7777
7778 match services.provision_key(&access_token, &win_hello_rsa).await {
7779 Ok(()) => Ok(loadable_win_hello_key.clone()),
7780 Err(_) => Err(MsalError::GeneralFailure(
7781 "Failed registering Windows Hello Key".to_string(),
7782 )),
7783 }
7784 }
7785
7786 #[allow(clippy::too_many_arguments)]
7814 pub async fn acquire_token_by_hello_for_business_key(
7815 &self,
7816 username: &str,
7817 key: &LoadableMsHelloKey,
7818 scopes: Vec<&str>,
7819 request_resource: Option<String>,
7820 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
7821 tpm: &mut BoxedDynTpm,
7822 storage_key: &StorageKey,
7823 pin: &str,
7824 ) -> Result<UserToken, MsalError> {
7825 let v2_endpoint = !scopes.is_empty();
7826 if !scopes.is_empty() && request_resource.is_some() {
7827 return Err(MsalError::GeneralFailure(
7828 "Scopes cannot be specified with a request_resource".to_string(),
7829 ));
7830 }
7831
7832 let pin = PinValue::new(pin)
7833 .map_err(|e| MsalError::TPMFail(format!("Failed setting pin value: {:?}", e)))?;
7834
7835 let prt = self
7836 .acquire_user_prt_by_hello_for_business_key_internal(
7837 username,
7838 key,
7839 tpm,
7840 storage_key,
7841 &pin,
7842 )
7843 .await?;
7844 let transport_key = self.transport_key(tpm, storage_key)?;
7845
7846 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
7850 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
7851
7852 let session_key = prt.session_key()?;
7853 let mut token = self
7854 .exchange_prt_for_access_token_internal(
7855 &prt,
7856 scopes.clone(),
7857 v2_endpoint,
7858 tpm,
7859 storage_key,
7860 &session_key,
7861 request_resource,
7862 #[cfg(feature = "on_behalf_of")]
7863 on_behalf_of_client_id,
7864 #[cfg(feature = "redirect_uri")]
7865 None,
7866 #[cfg(feature = "pop_support")]
7867 None,
7868 false,
7869 )
7870 .await?;
7871 token.client_info = prt.client_info.clone();
7872 token.prt = Some(self.seal_user_prt(&prt, tpm, prt_storage_key)?);
7874 Ok(token)
7875 }
7876
7877 async fn build_jwt_by_hello_for_business_key(
7878 &self,
7879 username: &str,
7880 loadable_key: &LoadableMsHelloKey,
7881 tpm: &mut BoxedDynTpm,
7882 storage_key: &StorageKey,
7883 pin: &PinValue,
7884 ) -> Result<Jws, MsalError> {
7885 debug!("Building a Hello for Business JWT");
7886
7887 let mut nonce = self.request_nonce().await?;
7888 let (key, _win_hello_storage_key) = tpm
7889 .ms_hello_key_load(storage_key, loadable_key, pin)
7890 .map_err(|e| MsalError::TPMFail(format!("{:?}", e)))?;
7891 let win_hello_pub_der = tpm.ms_hello_rsa_public_as_der(&key).map_err(|e| {
7892 MsalError::TPMFail(format!("Failed getting Windows Hello Key as der: {:?}", e))
7893 })?;
7894 let win_hello_rsa = Rsa::public_key_from_der(&win_hello_pub_der)
7895 .map_err(|e| MsalError::TPMFail(format!("{}", e)))?;
7896 let win_hello_blob: Vec<u8> = BcryptRsaKeyBlob::new(
7897 2048,
7898 &win_hello_rsa.e().to_vec(),
7899 &win_hello_rsa.n().to_vec(),
7900 )
7901 .try_into()?;
7902 let kid = STANDARD.encode(
7903 hash(MessageDigest::sha256(), &win_hello_blob)
7904 .map_err(|e| MsalError::CryptoFail(format!("{}", e)))?,
7905 );
7906 let assertion_jwt = JwsBuilder::from(
7907 serde_json::to_vec(
7908 &HelloForBusinessAssertion::new(username, &nonce)
7909 .map_err(|e| MsalError::GeneralFailure(format!("{:?}", e)))?,
7910 )
7911 .map_err(|e| {
7912 MsalError::InvalidJson(format!(
7913 "Failed serializing Hello for Business Assertion JWT: {}",
7914 e
7915 ))
7916 })?,
7917 )
7918 .set_typ(Some("JWT"))
7919 .set_use(Some("ngc"))
7920 .set_kid(Some(&kid))
7921 .build();
7922
7923 if let Ok(payload) = assertion_jwt.from_json::<Value>() {
7924 if let Ok(pretty) = to_string_pretty(&payload) {
7925 debug!("Hello for Business Assertion: {}", pretty);
7926 }
7927 }
7928
7929 let mut jws_tpm_signer = match JwsTpmRs256Signer::new(tpm, &key) {
7930 Ok(jws_tpm_signer) => jws_tpm_signer,
7931 Err(e) => {
7932 return Err(MsalError::TPMFail(format!(
7933 "Failed loading tpm signer: {}",
7934 e
7935 )))
7936 }
7937 };
7938 let signed_assertion = match jws_tpm_signer.sign(&assertion_jwt) {
7939 Ok(signed_jwt) => signed_jwt,
7940 Err(e) => return Err(MsalError::TPMFail(format!("Failed signing jwk: {}", e))),
7941 };
7942 let assertion = format!("{}", signed_assertion);
7943
7944 nonce = self.request_nonce().await?;
7945
7946 let cert_key = self.cert_key(tpm, storage_key)?;
7947 let cert_der = cert_key.cert.to_der().map_err(|e| {
7948 MsalError::CryptoFail(format!("Failed to convert certificate to DER: {:?}", e))
7949 })?;
7950 let jwt = JwsBuilder::from(
7951 serde_json::to_vec(&HelloForBusinessPayload::new(username, &assertion, &nonce))
7952 .map_err(|e| {
7953 MsalError::InvalidJson(format!(
7954 "Failed serializing Hello for Business JWT: {}",
7955 e
7956 ))
7957 })?,
7958 )
7959 .set_typ(Some("JWT"))
7960 .set_x5c(Some(vec![cert_der]))
7961 .build();
7962
7963 if let Ok(mut jwt_debug) = jwt.from_json::<Value>() {
7964 jwt_debug["assertion"] = "**********".into();
7965 if let Ok(pretty) = to_string_pretty(&jwt_debug) {
7966 debug!("Hello for Business Payload: {}", pretty);
7967 }
7968 }
7969
7970 Ok(jwt)
7971 }
7972
7973 async fn acquire_user_prt_by_hello_for_business_key_internal(
7974 &self,
7975 username: &str,
7976 key: &LoadableMsHelloKey,
7977 tpm: &mut BoxedDynTpm,
7978 storage_key: &StorageKey,
7979 pin: &PinValue,
7980 ) -> Result<PrimaryRefreshToken, MsalError> {
7981 debug!("Acquiring a User PRT via a Hello for Business Key");
7982
7983 let jwt = self
7984 .build_jwt_by_hello_for_business_key(username, key, tpm, storage_key, pin)
7985 .await?;
7986 let signed_jwt = self.sign_jwt(&jwt, tpm, storage_key).await?;
7987
7988 self.acquire_user_prt_jwt(&signed_jwt).await
7989 }
7990
7991 pub async fn acquire_user_prt_by_hello_for_business_key(
8010 &self,
8011 username: &str,
8012 key: &LoadableMsHelloKey,
8013 tpm: &mut BoxedDynTpm,
8014 storage_key: &StorageKey,
8015 pin: &str,
8016 ) -> Result<SealedData, MsalError> {
8017 let pin = PinValue::new(pin)
8018 .map_err(|e| MsalError::TPMFail(format!("Failed setting pin value: {:?}", e)))?;
8019
8020 let prt = self
8021 .acquire_user_prt_by_hello_for_business_key_internal(
8022 username,
8023 key,
8024 tpm,
8025 storage_key,
8026 &pin,
8027 )
8028 .await?;
8029 let transport_key = self.transport_key(tpm, storage_key)?;
8030
8031 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8035 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8036
8037 self.seal_user_prt(&prt, tpm, prt_storage_key)
8038 }
8039
8040 #[allow(clippy::too_many_arguments)]
8041 async fn exchange_prt_for_auth_code_internal(
8042 &self,
8043 scope: Vec<&str>,
8044 request_id: &str,
8045 resource: Option<&str>,
8046 v2_endpoint: bool,
8047 signed_prt_payload: Option<String>,
8048 signed_device_payload: Option<String>,
8049 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
8050 #[cfg(feature = "redirect_uri")] redirect_uri_override: Option<&str>,
8051 _req_cnf: Option<&str>,
8052 demand_mfa: bool,
8053 ) -> Result<String, MsalError> {
8054 #[cfg(not(feature = "on_behalf_of"))]
8055 let on_behalf_of_client_id: Option<&str> = None;
8056 #[cfg(not(feature = "redirect_uri"))]
8057 let redirect_uri_override: Option<&str> = None;
8058
8059 let scope = format!("openid profile {}", scope.join(" "));
8060 let (client_id, redirect_uri) = if let Some(uri) = redirect_uri_override {
8061 let cid = if v2_endpoint {
8063 if let Some(obo) = on_behalf_of_client_id {
8064 obo.to_string()
8065 } else if let Some(obo) = &self.on_behalf_of_client_id {
8066 obo.clone()
8067 } else {
8068 LINUX_BROKER_APP_ID.to_string()
8069 }
8070 } else {
8071 self.app.client_id().to_string()
8072 };
8073 (cid, uri.to_string())
8074 } else if v2_endpoint {
8075 if let Some(on_behalf_of_client_id) = on_behalf_of_client_id {
8076 (
8077 on_behalf_of_client_id.to_string(),
8078 self.app
8079 .get_auth_redirect_uri(Some(on_behalf_of_client_id), resource),
8080 )
8081 } else if let Some(on_behalf_of_client_id) = &self.on_behalf_of_client_id {
8082 (
8083 on_behalf_of_client_id.clone(),
8084 HIMMELBLAU_REDIRECT_URI.to_string(),
8085 )
8086 } else {
8087 (
8088 LINUX_BROKER_APP_ID.to_string(),
8089 self.app
8090 .get_auth_redirect_uri(Some(LINUX_BROKER_APP_ID), resource),
8091 )
8092 }
8093 } else {
8094 (
8095 self.app.client_id().to_string(),
8096 self.app.get_auth_redirect_uri(None, resource),
8097 )
8098 };
8099
8100 let mut params = vec![
8101 ("client_id", client_id.as_str()),
8102 ("response_type", "code"),
8103 ("redirect_uri", redirect_uri.as_str()),
8104 ("client-request-id", request_id),
8105 ];
8106 if v2_endpoint {
8107 params.push(("scope", &scope));
8108 } else if let Some(resource) = resource {
8109 params.push(("resource", resource));
8110 } else {
8111 params.push(("resource", "https://graph.microsoft.com"));
8112 }
8113 if !v2_endpoint && demand_mfa {
8114 params.push(("amr_values", "ngcmfa"));
8115 }
8116 let payload = params
8117 .iter()
8118 .map(|(k, v)| format!("{}={}", k, url_encode(v)))
8119 .collect::<Vec<String>>()
8120 .join("&");
8121
8122 let url = if v2_endpoint {
8123 format!("{}/oAuth2/v2.0/authorize?{}", self.authority()?, payload)
8124 } else {
8125 format!("{}/oauth2/authorize?{}", self.authority()?, payload)
8126 };
8127 debug!("GET {}", url);
8128
8129 let mut req = self.client().get(url).header(header::USER_AGENT, "");
8130 if let Some(signed_prt_payload) = signed_prt_payload {
8131 req = req.header("x-ms-RefreshTokenCredential", signed_prt_payload);
8132 }
8133 if let Some(signed_device_payload) = signed_device_payload {
8134 req = req.header("x-ms-DeviceCredential", signed_device_payload);
8135 }
8136 let mut resp = req
8137 .send()
8138 .await
8139 .map_err(|e| MsalError::request_failed(&e))?;
8140 let text;
8141 (text, resp) = self.app.await_working(resp).await?;
8142 if resp.status().is_redirection() {
8143 let document = Html::parse_document(&text);
8144 let selector = Selector::parse("a[href]").map_err(|_| {
8145 MsalError::InvalidParse("Failed parsing auth code response".to_string())
8146 })?;
8147 if let Some(element) = document.select(&selector).next() {
8148 if let Some(href_encoded) = element.value().attr("href") {
8149 let href = percent_decode_str(href_encoded)
8150 .decode_utf8()
8151 .map_err(|e| {
8152 MsalError::URLFormatFailed(format!("Failed decoding url: {:?}", e))
8153 })?;
8154 if let Ok(url) = Url::parse(&href) {
8155 return url
8156 .query_pairs()
8157 .find_map(|(key, value)| {
8158 if key == "code" {
8159 Some(value.into_owned())
8160 } else {
8161 None
8162 }
8163 })
8164 .ok_or(MsalError::GeneralFailure(
8165 "Authorization code not found".to_string(),
8166 ));
8167 }
8168 }
8169 }
8170
8171 match self.app.parse_auth_config(&text, false, false) {
8175 #[cfg(feature = "changepassword")]
8176 Err(MsalError::ChangePassword) => return Err(MsalError::ChangePassword),
8177 Err(MsalError::AADSTSError(e)) => return Err(MsalError::AADSTSError(e)),
8178 Err(MsalError::ConsentRequested(e)) => return Err(MsalError::ConsentRequested(e)),
8179 Ok(auth_config) if auth_config.pgid.as_deref() == Some("ConvergedTFA") => {
8180 return Err(MsalError::MFARequired);
8181 }
8182 _ => {}
8183 }
8184
8185 Err(MsalError::GeneralFailure(format!(
8186 "Authorization code not found in: {}",
8187 text
8188 )))
8189 } else if resp.status().is_success() {
8190 let re = Regex::new(r#"document\.location\.replace\("([^"]+)"\)"#)
8191 .map_err(|e| MsalError::InvalidRegex(format!("{}", e)))?;
8192 if let Some(m) = re.captures(&text) {
8193 if let Some(redirect) = m.get(1) {
8194 let redirect_decoded = Url::parse(&redirect.as_str().replace(r#"\u0026"#, "&"))
8195 .map_err(|e| MsalError::InvalidParse(format!("{}", e)))?;
8196 for (k, v) in redirect_decoded.query_pairs().collect::<Vec<_>>() {
8197 if k == "code" {
8198 return Ok(v.to_string());
8199 }
8200 if k == "error_description" {
8201 return Err(MsalError::GeneralFailure(v.to_string()));
8202 }
8203 }
8204 }
8205 }
8206
8207 match self.app.parse_auth_config(&text, false, false) {
8211 #[cfg(feature = "changepassword")]
8212 Err(MsalError::ChangePassword) => return Err(MsalError::ChangePassword),
8213 Err(MsalError::AADSTSError(e)) => return Err(MsalError::AADSTSError(e)),
8214 Err(MsalError::ConsentRequested(e)) => return Err(MsalError::ConsentRequested(e)),
8215 Ok(auth_config) if auth_config.pgid.as_deref() == Some("ConvergedTFA") => {
8216 return Err(MsalError::MFARequired);
8217 }
8218 _ => {}
8219 }
8220
8221 Err(MsalError::GeneralFailure(format!(
8222 "Authorization code not found in: {}",
8223 text
8224 )))
8225 } else {
8226 let json_resp: ErrorResponse = resp
8227 .json()
8228 .await
8229 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
8230 Err(MsalError::AcquireTokenFailed(json_resp))
8231 }
8232 }
8233
8234 pub async fn acquire_prt_sso_cookie(
8275 &self,
8276 prt: &SealedData,
8277 tpm: &mut BoxedDynTpm,
8278 storage_key: &StorageKey,
8279 ) -> Result<String, MsalError> {
8280 self.acquire_prt_sso_cookie_with_nonce(prt, None, tpm, storage_key)
8281 .await
8282 }
8283
8284 pub async fn acquire_prt_sso_cookie_with_nonce(
8292 &self,
8293 prt: &SealedData,
8294 sso_nonce: Option<&str>,
8295 tpm: &mut BoxedDynTpm,
8296 storage_key: &StorageKey,
8297 ) -> Result<String, MsalError> {
8298 debug!("Creating a prt sso cookie");
8299
8300 let transport_key = self.transport_key(tpm, storage_key)?;
8301
8302 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8306 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8307
8308 let prt = self.unseal_user_prt(prt, tpm, prt_storage_key)?;
8309 let session_key = prt.session_key()?;
8310
8311 let jwt = JwsBuilder::from(
8312 serde_json::to_vec(&RefreshTokenCredentialPayload::new(&prt, sso_nonce)?).map_err(
8313 |e| MsalError::InvalidJson(format!("Failed serializing Authorization JWT: {}", e)),
8314 )?,
8315 )
8316 .set_typ(Some("JWT"))
8317 .build();
8318
8319 self.sign_session_key_jwt(&jwt, tpm, storage_key, &session_key)
8320 .await
8321 }
8322
8323 #[allow(clippy::too_many_arguments)]
8324 async fn exchange_prt_for_auth_code(
8325 &self,
8326 prt: &PrimaryRefreshToken,
8327 scope: Vec<&str>,
8328 request_id: &str,
8329 resource: Option<&str>,
8330 v2_endpoint: bool,
8331 session_key: &SessionKey,
8332 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
8333 tpm: &mut BoxedDynTpm,
8334 storage_key: &StorageKey,
8335 #[cfg(feature = "redirect_uri")] redirect_uri: Option<&str>,
8336 req_cnf: Option<&str>,
8337 demand_mfa: bool,
8338 ) -> Result<String, MsalError> {
8339 debug!("Exchanging a PRT for an Authorization Code");
8340
8341 let nonce = self.request_nonce().await?;
8342
8343 let jwt = JwsBuilder::from(
8344 serde_json::to_vec(&RefreshTokenCredentialPayload::new(prt, Some(&nonce))?).map_err(
8345 |e| MsalError::InvalidJson(format!("Failed serializing Authorization JWT: {}", e)),
8346 )?,
8347 )
8348 .set_typ(Some("JWT"))
8349 .build();
8350 let signed_prt_payload = self
8351 .sign_session_key_jwt(&jwt, tpm, storage_key, session_key)
8352 .await?;
8353 if let Ok(mut payload) = jwt.from_json::<Value>() {
8354 payload["refresh_token"] = "**********".into();
8355 if let Ok(pretty) = to_string_pretty(&payload) {
8356 debug!("Refresh Token Credential Payload: {}", pretty);
8357 }
8358 }
8359
8360 let cert_key = self.cert_key(tpm, storage_key)?;
8361 let cert_der = cert_key.cert.to_der().map_err(|e| {
8362 MsalError::CryptoFail(format!("Failed to convert certificate to DER: {:?}", e))
8363 })?;
8364 let jwt = JwsBuilder::from(
8365 serde_json::to_vec(&DeviceCredentialPayload::new(&nonce)?).map_err(|e| {
8366 MsalError::InvalidJson(format!("Failed serializing Authorization JWT: {}", e))
8367 })?,
8368 )
8369 .set_typ(Some("JWT"))
8370 .set_x5c(Some(vec![cert_der]))
8371 .build();
8372 let signed_device_payload = self.sign_jwt(&jwt, tpm, storage_key).await?;
8373 if let Ok(payload) = jwt.from_json::<Value>() {
8374 if let Ok(pretty) = to_string_pretty(&payload) {
8375 debug!("Device Credential Payload: {}", pretty);
8376 }
8377 }
8378
8379 let result = self
8380 .exchange_prt_for_auth_code_internal(
8381 scope.clone(),
8382 request_id,
8383 resource,
8384 v2_endpoint,
8385 Some(signed_prt_payload.clone()),
8386 Some(signed_device_payload.clone()),
8387 #[cfg(feature = "on_behalf_of")]
8388 on_behalf_of_client_id,
8389 #[cfg(feature = "redirect_uri")]
8390 redirect_uri,
8391 req_cnf,
8392 demand_mfa,
8393 )
8394 .await;
8395
8396 match result {
8399 Err(MsalError::AADSTSError(ref e)) if e.code == 16000 => {
8400 warn!("PRT exchange failed with AADSTS16000, clearing cookies and retrying");
8401 self.clear_cookies();
8402 self.exchange_prt_for_auth_code_internal(
8403 scope,
8404 request_id,
8405 resource,
8406 v2_endpoint,
8407 Some(signed_prt_payload),
8408 Some(signed_device_payload),
8409 #[cfg(feature = "on_behalf_of")]
8410 on_behalf_of_client_id,
8411 #[cfg(feature = "redirect_uri")]
8412 redirect_uri,
8413 req_cnf,
8414 demand_mfa,
8415 )
8416 .await
8417 }
8418 other => other,
8419 }
8420 }
8421
8422 #[allow(clippy::too_many_arguments)]
8423 async fn exchange_auth_code_for_access_token_internal(
8424 &self,
8425 scope: Vec<&str>,
8426 request_id: &str,
8427 v2_endpoint: bool,
8428 authorization_code: String,
8429 request_resource: Option<&str>,
8430 #[cfg(feature = "on_behalf_of")] on_behalf_of_client_id: Option<&str>,
8431 #[cfg(feature = "redirect_uri")] redirect_uri_override: Option<&str>,
8432 req_cnf: Option<&str>,
8433 ) -> Result<UserToken, MsalError> {
8434 debug!("Exchanging an Authorization Code for an Access Token");
8435 #[cfg(not(feature = "on_behalf_of"))]
8436 let on_behalf_of_client_id: Option<&str> = None;
8437 #[cfg(not(feature = "redirect_uri"))]
8438 let redirect_uri_override: Option<&str> = None;
8439
8440 let scopes_str = format!("openid profile offline_access {}", scope.join(" "));
8441 let (client_id, redirect_uri) = if let Some(uri) = redirect_uri_override {
8442 let cid = if v2_endpoint {
8444 if let Some(obo) = on_behalf_of_client_id {
8445 obo.to_string()
8446 } else if let Some(obo) = &self.on_behalf_of_client_id {
8447 obo.clone()
8448 } else {
8449 LINUX_BROKER_APP_ID.to_string()
8450 }
8451 } else {
8452 self.app.client_id().to_string()
8453 };
8454 (cid, uri.to_string())
8455 } else if v2_endpoint {
8456 if let Some(on_behalf_of_client_id) = on_behalf_of_client_id {
8457 (
8458 on_behalf_of_client_id.to_string(),
8459 self.app
8460 .get_auth_redirect_uri(Some(on_behalf_of_client_id), request_resource),
8461 )
8462 } else if let Some(on_behalf_of_client_id) = &self.on_behalf_of_client_id {
8463 (
8464 on_behalf_of_client_id.clone(),
8465 HIMMELBLAU_REDIRECT_URI.to_string(),
8466 )
8467 } else {
8468 (
8469 LINUX_BROKER_APP_ID.to_string(),
8470 self.app
8471 .get_auth_redirect_uri(Some(LINUX_BROKER_APP_ID), request_resource),
8472 )
8473 }
8474 } else {
8475 (
8476 self.app.client_id().to_string(),
8477 self.app.get_auth_redirect_uri(None, request_resource),
8478 )
8479 };
8480 let mut params = vec![
8481 ("client_id", client_id.as_str()),
8482 ("grant_type", "authorization_code"),
8483 ("code", &authorization_code),
8484 ("redirect_uri", &redirect_uri),
8485 ("client-request-id", request_id),
8486 ];
8487 if v2_endpoint {
8488 params.push(("scope", &scopes_str));
8489 } else if let Some(request_resource) = request_resource {
8490 params.push(("resource", request_resource));
8491 } else {
8492 params.push(("resource", "https://graph.microsoft.com"));
8493 }
8494 if let Some(req_cnf) = req_cnf {
8495 params.push(("req_cnf", req_cnf));
8496 }
8497 let payload = params
8498 .iter()
8499 .map(|(k, v)| format!("{}={}", k, v))
8500 .collect::<Vec<String>>()
8501 .join("&");
8502
8503 let url = if v2_endpoint {
8504 format!("{}/oAuth2/v2.0/token", self.authority()?)
8505 } else {
8506 format!("{}/oauth2/token", self.authority()?)
8507 };
8508 let mut debug_payload = params;
8509 debug_payload[2] = ("code", "**********");
8510 if let Ok(pretty) = to_string_pretty(&debug_payload) {
8511 debug!("POST {}: {}", url, pretty);
8512 }
8513
8514 let resp = self
8515 .client()
8516 .post(url)
8517 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
8518 .body(payload)
8519 .send()
8520 .await
8521 .map_err(|e| MsalError::request_failed(&e))?;
8522 if resp.status().is_success() {
8523 let token: UserToken = resp
8524 .json()
8525 .await
8526 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
8527
8528 Ok(token)
8529 } else {
8530 let json_resp: ErrorResponse = resp
8531 .json()
8532 .await
8533 .map_err(|e| MsalError::InvalidJson(format!("{}", e)))?;
8534 Err(MsalError::AcquireTokenFailed(json_resp))
8535 }
8536 }
8537
8538 #[cfg(feature = "changepassword")]
8559 pub async fn handle_password_change(
8560 &self,
8561 username: &str,
8562 password: &str,
8563 new_password: &str,
8564 ) -> Result<(), MsalError> {
8565 self.app
8566 .handle_password_change(username, password, new_password)
8567 .await
8568 }
8569
8570 pub fn name_from_prt(
8588 &self,
8589 sealed_data: &SealedData,
8590 tpm: &mut BoxedDynTpm,
8591 storage_key: &StorageKey,
8592 ) -> Result<String, MsalError> {
8593 let transport_key = self.transport_key(tpm, storage_key)?;
8594
8595 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8599 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8600
8601 let prt = self.unseal_user_prt(sealed_data, tpm, prt_storage_key)?;
8602 Ok(prt.name())
8603 }
8604
8605 pub fn spn_from_prt(
8623 &self,
8624 sealed_data: &SealedData,
8625 tpm: &mut BoxedDynTpm,
8626 storage_key: &StorageKey,
8627 ) -> Result<String, MsalError> {
8628 let transport_key = self.transport_key(tpm, storage_key)?;
8629
8630 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8634 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8635
8636 let prt = self.unseal_user_prt(sealed_data, tpm, prt_storage_key)?;
8637 prt.spn()
8638 }
8639
8640 pub fn uuid_from_prt(
8658 &self,
8659 sealed_data: &SealedData,
8660 tpm: &mut BoxedDynTpm,
8661 storage_key: &StorageKey,
8662 ) -> Result<Uuid, MsalError> {
8663 let transport_key = self.transport_key(tpm, storage_key)?;
8664
8665 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8669 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8670
8671 let prt = self.unseal_user_prt(sealed_data, tpm, prt_storage_key)?;
8672 prt.uuid()
8673 }
8674
8675 pub fn store_cloud_tgt(
8691 &self,
8692 _sealed_prt: &SealedData,
8693 _filename: &str,
8694 _tpm: &mut BoxedDynTpm,
8695 _storage_key: &StorageKey,
8696 ) -> Result<(), MsalError> {
8697 Err(MsalError::NotImplemented)
8698 }
8699
8700 pub fn store_ad_tgt(
8716 &self,
8717 _sealed_prt: &SealedData,
8718 _filename: &str,
8719 _tpm: &mut BoxedDynTpm,
8720 _storage_key: &StorageKey,
8721 ) -> Result<(), MsalError> {
8722 Err(MsalError::NotImplemented)
8723 }
8724
8725 fn kerberos_credentials_to_ccache_bytes(
8727 credentials: &KerberosCredentials,
8728 ) -> Result<Vec<u8>, MsalError> {
8729 let temp_path = format!("/tmp/himmelblau_ccache_{}", Uuid::new_v4().as_hyphenated());
8731 let ccache_name = format!("FILE:{}", temp_path);
8732
8733 let mut ccache = ccache_resolve(Some(&ccache_name)).map_err(|e| {
8735 error!("Failed to resolve ccache: {:?}", e);
8736 MsalError::CryptoFail("Failed to resolve ccache".to_string())
8737 })?;
8738
8739 ccache.init(credentials.name(), None).map_err(|e| {
8740 error!("Failed to init ccache: {:?}", e);
8741 MsalError::CryptoFail("Failed to init ccache".to_string())
8742 })?;
8743
8744 ccache.store(credentials).map_err(|e| {
8745 error!("Failed to store credentials in ccache: {:?}", e);
8746 MsalError::CryptoFail("Failed to store credentials in ccache".to_string())
8747 })?;
8748
8749 let mut file = fs::File::open(&temp_path).map_err(|e| {
8751 error!("Failed to open ccache file: {:?}", e);
8752 MsalError::CryptoFail("Failed to open ccache file".to_string())
8753 })?;
8754
8755 let mut bytes = Vec::new();
8756 file.read_to_end(&mut bytes).map_err(|e| {
8757 error!("Failed to read ccache file: {:?}", e);
8758 MsalError::CryptoFail("Failed to read ccache file".to_string())
8759 })?;
8760
8761 let _ = fs::remove_file(&temp_path);
8763
8764 Ok(bytes)
8765 }
8766
8767 pub fn fetch_cloud_ccache(
8782 &self,
8783 sealed_prt: &SealedData,
8784 tpm: &mut BoxedDynTpm,
8785 storage_key: &StorageKey,
8786 ) -> Result<Vec<u8>, MsalError> {
8787 let credentials = self.fetch_cloud_tgt(sealed_prt, tpm, storage_key)?;
8788 Self::kerberos_credentials_to_ccache_bytes(&credentials)
8789 }
8790
8791 pub fn fetch_ad_ccache(
8806 &self,
8807 sealed_prt: &SealedData,
8808 tpm: &mut BoxedDynTpm,
8809 storage_key: &StorageKey,
8810 ) -> Result<Vec<u8>, MsalError> {
8811 let credentials = self.fetch_ad_tgt(sealed_prt, tpm, storage_key)?;
8812 Self::kerberos_credentials_to_ccache_bytes(&credentials)
8813 }
8814
8815 pub fn fetch_cloud_tgt(
8830 &self,
8831 sealed_prt: &SealedData,
8832 tpm: &mut BoxedDynTpm,
8833 storage_key: &StorageKey,
8834 ) -> Result<Box<KerberosCredentials>, MsalError> {
8835 let transport_key = self.transport_key(tpm, storage_key)?;
8836
8837 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8841 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8842
8843 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
8844 if let Some(error) = &prt.tgt_cloud.error {
8845 return Err(MsalError::Missing(error.to_string()));
8846 }
8847
8848 let session_key = prt.session_key()?;
8849 let client_key =
8850 prt.tgt_cloud
8851 .derived_key(tpm, &transport_key, storage_key, &session_key)?;
8852
8853 let as_rep = prt.tgt_cloud.as_rep()?;
8854 let kdc_reply = as_rep
8855 .enc_part
8856 .decrypt_enc_kdc_rep(&client_key)
8857 .map_err(|e| {
8858 let msg = format!("Failed to decrypt KDC reply part from AS reply: {:?}", e);
8859 MsalError::CryptoFail(msg)
8860 })?;
8861
8862 let creds = KerberosCredentials::new(as_rep.name, as_rep.ticket, kdc_reply);
8863 Ok(Box::new(creds))
8864 }
8865
8866 pub fn fetch_ad_tgt(
8881 &self,
8882 sealed_prt: &SealedData,
8883 tpm: &mut BoxedDynTpm,
8884 storage_key: &StorageKey,
8885 ) -> Result<Box<KerberosCredentials>, MsalError> {
8886 let transport_key = self.transport_key(tpm, storage_key)?;
8887
8888 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8892 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8893
8894 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
8895 let session_key = prt.session_key()?;
8896
8897 let (client_key, as_rep) = match &prt.tgt_on_prem {
8898 OnPremTgt::Structured { tgt_ad } => {
8899 if let Some(error) = tgt_ad.error.as_ref() {
8900 return Err(MsalError::Missing(error.clone()));
8901 }
8902 let client_key =
8903 tgt_ad.derived_key(tpm, &transport_key, storage_key, &session_key)?;
8904 let as_rep = tgt_ad.as_rep()?;
8905 (client_key, as_rep)
8906 }
8907 OnPremTgt::Raw(raw_tgt) => {
8908 let client_key =
8909 raw_tgt.derived_key(tpm, &transport_key, storage_key, &session_key)?;
8910 let as_rep = raw_tgt.as_rep()?;
8911 (client_key, as_rep)
8912 }
8913 OnPremTgt::Absent => {
8914 return Err(MsalError::Missing(
8915 "No on-prem partial ticket bundled".to_string(),
8916 ))
8917 }
8918 };
8919
8920 let kdc_reply = as_rep
8921 .enc_part
8922 .decrypt_enc_kdc_rep(&client_key)
8923 .map_err(|e| {
8924 let msg = format!("Failed to decrypt KDC reply part from AS reply: {:?}", e);
8925 MsalError::CryptoFail(msg)
8926 })?;
8927
8928 let creds = KerberosCredentials::new(as_rep.name, as_rep.ticket, kdc_reply);
8929 Ok(Box::new(creds))
8930 }
8931
8932 pub fn unseal_prt_kerberos_top_level_names(
8947 &self,
8948 sealed_prt: &SealedData,
8949 tpm: &mut BoxedDynTpm,
8950 storage_key: &StorageKey,
8951 ) -> Result<String, MsalError> {
8952 let transport_key = self.transport_key(tpm, storage_key)?;
8953 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
8958 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
8959
8960 let prt = self.unseal_user_prt(sealed_prt, tpm, prt_storage_key)?;
8961 let kerberos_top_level_names =
8962 prt.kerberos_top_level_names
8963 .clone()
8964 .ok_or(MsalError::Missing(
8965 "kerberos_top_level_names missing from PRT".to_string(),
8966 ))?;
8967 Ok(kerberos_top_level_names.clone())
8968 }
8969
8970 fn seal_user_prt(
8971 &self,
8972 prt: &PrimaryRefreshToken,
8973 tpm: &mut BoxedDynTpm,
8974 storage_key: &StorageKey,
8975 ) -> Result<SealedData, MsalError> {
8976 let prt_data = json_to_vec(prt)
8977 .map(Zeroizing::new)
8978 .map_err(|e| MsalError::InvalidJson(format!("Failed serializing PRT {:?}", e)))?;
8979 tpm.seal_data(storage_key, prt_data)
8980 .map_err(|e| MsalError::TPMFail(format!("Failed sealing PRT {:?}", e)))
8981 }
8982
8983 fn unseal_user_prt(
8984 &self,
8985 sealed_data: &SealedData,
8986 tpm: &mut BoxedDynTpm,
8987 storage_key: &StorageKey,
8988 ) -> Result<PrimaryRefreshToken, MsalError> {
8989 let prt_data = tpm
8990 .unseal_data(storage_key, sealed_data)
8991 .map_err(|e| MsalError::TPMFail(format!("Failed unsealing PRT {:?}", e)))?;
8992 json_from_slice(&prt_data)
8993 .map_err(|e| MsalError::InvalidJson(format!("Failed deserializing PRT {:?}", e)))
8994 }
8995
8996 pub fn seal_user_prt_with_hello_key(
9023 &self,
9024 prt: &SealedData,
9025 hello_key: &LoadableMsHelloKey,
9026 pin: &str,
9027 tpm: &mut BoxedDynTpm,
9028 storage_key: &StorageKey,
9029 ) -> Result<SealedData, MsalError> {
9030 let transport_key = self.transport_key(tpm, storage_key)?;
9031
9032 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
9036 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
9037
9038 let prt = self.unseal_user_prt(prt, tpm, prt_storage_key)?;
9039 let prt_data = json_to_vec(&prt)
9040 .map(Zeroizing::new)
9041 .map_err(|e| MsalError::InvalidJson(format!("Failed serializing PRT {:?}", e)))?;
9042 let pin = PinValue::new(pin)
9043 .map_err(|e| MsalError::TPMFail(format!("Failed setting pin value: {:?}", e)))?;
9044 let (_key, win_hello_storage_key) = tpm
9045 .ms_hello_key_load(storage_key, hello_key, &pin)
9046 .map_err(|e| MsalError::TPMFail(format!("{:?}", e)))?;
9047 tpm.seal_data(&win_hello_storage_key, prt_data)
9048 .map_err(|e| MsalError::TPMFail(format!("Failed sealing PRT {:?}", e)))
9049 }
9050
9051 pub fn unseal_user_prt_with_hello_key(
9075 &self,
9076 sealed_data: &SealedData,
9077 hello_key: &LoadableMsHelloKey,
9078 pin: &str,
9079 tpm: &mut BoxedDynTpm,
9080 storage_key: &StorageKey,
9081 ) -> Result<SealedData, MsalError> {
9082 let pin = PinValue::new(pin)
9083 .map_err(|e| MsalError::TPMFail(format!("Failed setting pin value: {:?}", e)))?;
9084 let (_key, win_hello_storage_key) = tpm
9085 .ms_hello_key_load(storage_key, hello_key, &pin)
9086 .map_err(|e| MsalError::TPMFail(format!("{:?}", e)))?;
9087 let prt_data = tpm
9088 .unseal_data(&win_hello_storage_key, sealed_data)
9089 .map_err(|e| MsalError::TPMFail(format!("Failed unsealing PRT {:?}", e)))?;
9090 let prt = json_from_slice(&prt_data)
9091 .map_err(|e| MsalError::InvalidJson(format!("Failed deserializing PRT {:?}", e)))?;
9092
9093 let transport_key = self.transport_key(tpm, storage_key)?;
9094
9095 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
9099 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
9100
9101 self.seal_user_prt(&prt, tpm, prt_storage_key)
9102 }
9103
9104 pub async fn resolve_nametosid(
9105 &self,
9106 username: &str,
9107 tpm: &mut BoxedDynTpm,
9108 machine_key: &StorageKey,
9109 ) -> Result<SidToName, MsalError> {
9110 let nonce = self.request_nonce().await?;
9111
9112 let os_release = match OsRelease::new() {
9113 Ok(os_release) => Some(format!(
9114 "{} {}",
9115 os_release.pretty_name, os_release.version_id
9116 )),
9117 Err(_) => None,
9118 };
9119
9120 let jwt_body = json!({
9121 "win_ver": os_release,
9122 "version": "1.0",
9123 "nonce": nonce,
9124 "username": username,
9125 });
9126
9127 let jwt = JwsBuilder::from(
9128 serde_json::to_vec(&jwt_body)
9129 .map_err(|e| MsalError::InvalidJson(format!("Failed to serialize JWT: {}", e)))?,
9130 )
9131 .set_typ(Some("JWT"))
9132 .build();
9133
9134 let signed_jwt = self.sign_jwt(&jwt, tpm, machine_key).await?;
9135
9136 let form_body = serde_urlencoded::to_string([
9137 ("windows_api_version", "2.2"),
9138 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
9139 ("signedRequest", &signed_jwt),
9140 ])
9141 .map_err(|e| MsalError::InvalidJson(format!("Failed to encode form: {}", e)))?;
9142
9143 let url = format!("{}/sidtoname", self.authority()?);
9144
9145 let resp = self
9146 .client()
9147 .get(url)
9148 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
9149 .body(Body::from(form_body))
9150 .send()
9151 .await
9152 .map_err(|e| MsalError::request_failed(&e))?;
9153
9154 if resp.status().is_success() {
9155 let json_resp: SidToName = resp
9156 .json()
9157 .await
9158 .map_err(|e| MsalError::RequestFailed(format!("{:?}", e)))?;
9159 Ok(json_resp)
9160 } else {
9161 Err(MsalError::RequestFailed(format!("{}", resp.status())))
9162 }
9163 }
9164
9165 pub fn is_prt_expired(
9180 &self,
9181 prt: &SealedData,
9182 tpm: &mut BoxedDynTpm,
9183 storage_key: &StorageKey,
9184 ) -> Result<bool, MsalError> {
9185 let transport_key = self.transport_key(tpm, storage_key)?;
9186
9187 let maybe_transport_storage_key = tpm.rs256_yield_cek(&transport_key);
9191 let prt_storage_key = maybe_transport_storage_key.as_ref().unwrap_or(storage_key);
9192
9193 let prt = self.unseal_user_prt(prt, tpm, prt_storage_key)?;
9194
9195 Ok(prt.is_expired())
9196 }
9197}
9198
9199#[cfg(test)]
9200#[allow(clippy::unwrap_used, clippy::expect_used)]
9201mod tests {
9202 use super::*;
9203
9204 #[cfg(feature = "broker")]
9205 use kanidm_hsm_crypto::{provider::SoftTpm, AuthValue};
9206 #[cfg(feature = "broker")]
9207 use openssl::{
9208 asn1::{Asn1Object, Asn1OctetString, Asn1Time},
9209 bn::BigNum,
9210 pkey::{PKey, Private},
9211 sign::Verifier,
9212 x509::{X509Builder, X509Extension, X509NameBuilder},
9213 };
9214
9215 fn test_cred_type(if_exists_result: i32, throttle_status: u8) -> CredType {
9216 serde_json::from_value(json!({
9217 "Credentials": {
9218 "PrefCredential": 1,
9219 "HasPassword": true
9220 },
9221 "ThrottleStatus": throttle_status,
9222 "IfExistsResult": if_exists_result
9223 }))
9224 .expect("test credential type should deserialize")
9225 }
9226
9227 #[test]
9228 fn adfs_url_detection_requires_a_secure_complete_path_segment() {
9229 for value in [
9230 "https://fs.example.com/adfs/ls/?wa=wsignin1.0",
9231 "https://fs.example.com/ADFS/LS/",
9232 "https://fs.example.com:8443/prefix/adfs/ls",
9233 ] {
9234 assert!(parse_adfs_federation_url(value).unwrap().is_some());
9235 }
9236
9237 for value in [
9238 "http://fs.example.com/adfs/ls/",
9239 "https://user:secret@fs.example.com/adfs/ls/",
9240 "https://adfs.example.com/login/",
9241 "https://fs.example.com/notadfs/ls/",
9242 "https://fs.example.com/login/?next=/adfs/ls/",
9243 "https://fs.example.com/adfs/ls/#fragment",
9244 ] {
9245 assert!(parse_adfs_federation_url(value).unwrap().is_none());
9246 }
9247 assert!(parse_adfs_federation_url("not a URL").is_err());
9248 }
9249
9250 #[test]
9251 fn ws_fed_form_parser_selects_complete_form_and_decodes_entities() {
9252 let html = r#"
9253 <html><body>
9254 <form><input name="UserName" value="ignored"></form>
9255 <form action="https://untrusted.example/collect">
9256 <input type="hidden" name="WCTX" value="ctx&value">
9257 <input type="hidden" name="wresult" value="<Assertion>ok</Assertion>">
9258 <input type="hidden" name="WA" value="wsignin1.0">
9259 <input type="hidden" name="Password" value="must-not-be-forwarded">
9260 </form>
9261 </body></html>
9262 "#;
9263 let form = parse_ws_fed_form(html).unwrap();
9264 assert_eq!(form.wa, "wsignin1.0");
9265 assert_eq!(form.wctx, "ctx&value");
9266 assert_eq!(form.wresult, "<Assertion>ok</Assertion>");
9267 }
9268
9269 #[test]
9270 fn ws_fed_form_parser_rejects_login_and_incomplete_forms() {
9271 for html in [
9272 r#"<form><input name="UserName"><input name="Password"></form>"#,
9273 r#"<form><input name="wa" value="wsignin1.0"><input name="wctx" value="ctx"></form>"#,
9274 r#"<form><input name="wa" value="wrong"><input name="wctx" value="ctx"><input name="wresult" value="assertion"></form>"#,
9275 r#"<form><input name="wa" value="wsignin1.0"><input name="wctx" value=""><input name="wresult" value="assertion"></form>"#,
9276 ] {
9277 assert!(parse_ws_fed_form(html).is_err());
9278 }
9279 }
9280
9281 #[test]
9282 fn login_srf_is_derived_from_authority_origin() {
9283 assert_eq!(
9284 entra_login_srf_url("https://login.microsoftonline.com/common/?ignored=true")
9285 .unwrap()
9286 .as_str(),
9287 "https://login.microsoftonline.com/login.srf"
9288 );
9289 assert_eq!(
9290 entra_login_srf_url("https://login.microsoftonline.us:8443/tenant")
9291 .unwrap()
9292 .as_str(),
9293 "https://login.microsoftonline.us:8443/login.srf"
9294 );
9295 assert!(entra_login_srf_url("http://login.example.com/common").is_err());
9296 }
9297
9298 #[test]
9299 fn adfs_redirects_stay_on_https_origin_and_control_password_replay() {
9300 let origin = Url::parse("https://fs.example.com/adfs/ls/").unwrap();
9301 let (next, method) = resolve_adfs_redirect(
9302 &origin,
9303 &origin,
9304 "../continue",
9305 302,
9306 AdfsRequestMethod::PostCredentials,
9307 )
9308 .unwrap();
9309 assert_eq!(next.as_str(), "https://fs.example.com/adfs/continue");
9310 assert_eq!(method, AdfsRequestMethod::Get);
9311
9312 let (_, method) = resolve_adfs_redirect(
9313 &origin,
9314 &origin,
9315 "/adfs/resubmit",
9316 307,
9317 AdfsRequestMethod::PostCredentials,
9318 )
9319 .unwrap();
9320 assert_eq!(method, AdfsRequestMethod::PostCredentials);
9321
9322 for location in [
9323 "http://fs.example.com/adfs/continue",
9324 "https://other.example.com/adfs/continue",
9325 "https://user:secret@fs.example.com/adfs/continue",
9326 "/adfs/continue#fragment",
9327 ] {
9328 assert!(resolve_adfs_redirect(
9329 &origin,
9330 &origin,
9331 location,
9332 302,
9333 AdfsRequestMethod::PostCredentials,
9334 )
9335 .is_err());
9336 }
9337 }
9338
9339 #[test]
9340 fn usable_account_result_is_not_rejected_by_backend_throttle_status() {
9341 for throttle_status in [0, 1, 2, u8::MAX] {
9342 let cred_type = test_cred_type(0, throttle_status);
9343 assert!(cred_type.account_exists().unwrap());
9344 }
9345 }
9346
9347 #[test]
9348 fn throttled_account_lookup_remains_retryable_error() {
9349 let cred_type = test_cred_type(2, 0);
9350 assert!(matches!(
9351 cred_type.account_exists(),
9352 Err(MsalError::AADSTSError(err)) if err.code == 90055
9353 ));
9354 }
9355
9356 #[test]
9357 fn retryable_account_lookup_failures_keep_existing_error() {
9358 for if_exists_result in [-1, 4] {
9359 let cred_type = test_cred_type(if_exists_result, 0);
9360 assert!(matches!(
9361 cred_type.account_exists(),
9362 Err(MsalError::AADSTSError(err)) if err.code == 90006
9363 ));
9364 }
9365 }
9366
9367 fn build_access_token(payload_json: &str) -> String {
9368 let header = URL_SAFE_NO_PAD.encode(r#"{"alg":"none"}"#.as_bytes());
9369 let payload = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
9370 format!("{}.{}.", header, payload)
9371 }
9372
9373 #[cfg(feature = "broker")]
9374 const TEST_TENANT_GUID: &str = "6973bb37-65f8-440e-b39e-dd57da64f6cf";
9375 #[cfg(feature = "broker")]
9378 const TEST_TENANT_BYTES_LE: [u8; 16] = [
9379 0x37, 0xbb, 0x73, 0x69, 0xf8, 0x65, 0x0e, 0x44, 0xb3, 0x9e, 0xdd, 0x57, 0xda, 0x64, 0xf6,
9380 0xcf,
9381 ];
9382 #[cfg(feature = "broker")]
9383 const TENANT_ID_OID: &str = "1.2.840.113556.1.5.284.5";
9384
9385 #[cfg(feature = "broker")]
9386 fn test_tpm() -> (BoxedDynTpm, StorageKey) {
9387 let mut tpm = BoxedDynTpm::new(SoftTpm::new());
9388 let auth_str = AuthValue::generate().unwrap();
9389 let auth_value = AuthValue::from_str(&auth_str).unwrap();
9390 let loadable_machine_key = tpm.root_storage_key_create(&auth_value).unwrap();
9391 let machine_key = tpm
9392 .root_storage_key_load(&auth_value, &loadable_machine_key)
9393 .unwrap();
9394
9395 (tpm, machine_key)
9396 }
9397
9398 #[cfg(feature = "broker")]
9399 fn test_prt(refresh_token: &str) -> PrimaryRefreshToken {
9400 PrimaryRefreshToken {
9401 token_type: "Bearer".to_string(),
9402 expires_in: "3600".to_string(),
9403 ext_expires_in: "3600".to_string(),
9404 expires_on: "9999999999".to_string(),
9405 refresh_token: refresh_token.to_string(),
9406 refresh_token_expires_in: 3600,
9407 session_key_jwe: None,
9408 id_token: IdToken::default(),
9409 client_info: ClientInfo::default(),
9410 device_tenant_id: None,
9411 tgt_on_prem: OnPremTgt::Absent,
9412 tgt_cloud: StructuredTgt::default(),
9413 kerberos_top_level_names: None,
9414 }
9415 }
9416
9417 #[cfg(feature = "broker")]
9418 fn self_signed_cert(pkey: &PKey<Private>, extensions: &[X509Extension]) -> Vec<u8> {
9419 let mut name = X509NameBuilder::new().unwrap();
9420 name.append_entry_by_text("CN", "p2p-test").unwrap();
9421 let name = name.build();
9422 let mut builder = X509Builder::new().unwrap();
9423 builder.set_version(2).unwrap();
9424 let serial = BigNum::from_u32(1).unwrap().to_asn1_integer().unwrap();
9425 builder.set_serial_number(&serial).unwrap();
9426 builder.set_subject_name(&name).unwrap();
9427 builder.set_issuer_name(&name).unwrap();
9428 builder.set_pubkey(pkey).unwrap();
9429 builder
9430 .set_not_before(Asn1Time::days_from_now(0).unwrap().as_ref())
9431 .unwrap();
9432 builder
9433 .set_not_after(Asn1Time::days_from_now(1).unwrap().as_ref())
9434 .unwrap();
9435 for extension in extensions {
9436 builder.append_extension2(extension).unwrap();
9437 }
9438 builder.sign(pkey, MessageDigest::sha256()).unwrap();
9439
9440 builder.build().to_der().unwrap()
9441 }
9442
9443 #[cfg(feature = "broker")]
9446 fn tenant_oid_extension(value: &[u8]) -> X509Extension {
9447 let oid = Asn1Object::from_str(TENANT_ID_OID).unwrap();
9448 let value = Asn1OctetString::new_from_bytes(value).unwrap();
9449
9450 X509Extension::new_from_der(&oid, false, &value).unwrap()
9451 }
9452
9453 #[cfg(feature = "broker")]
9454 fn test_broker_app(authority: &str) -> BrokerClientApplication {
9455 BrokerClientApplication::new(
9456 Some(authority),
9457 None,
9458 None,
9459 None,
9460 #[cfg(feature = "set_timeout")]
9461 Duration::from_secs(30),
9462 #[cfg(feature = "ipvers")]
9463 &[],
9464 )
9465 .unwrap()
9466 }
9467
9468 #[cfg(feature = "broker")]
9469 fn decode_jwt_header(jwt: &str) -> Value {
9470 let header = jwt.split('.').next().unwrap();
9471 let header = URL_SAFE_NO_PAD.decode(header).unwrap();
9472 json_from_slice(&header).unwrap()
9473 }
9474
9475 fn build_user_token(access_token: String) -> UserToken {
9476 UserToken {
9477 token_type: "Bearer".to_string(),
9478 scope: None,
9479 expires_in: 3600,
9480 ext_expires_in: 3600,
9481 access_token: Some(access_token),
9482 refresh_token: "refresh-token".to_string(),
9483 id_token: IdToken::default(),
9484 client_info: ClientInfo::default(),
9485 #[cfg(feature = "broker")]
9486 prt: None,
9487 }
9488 }
9489
9490 #[test]
9491 fn pkce_challenge_matches_rfc7636_vector() {
9492 assert_eq!(
9493 pkce_code_challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"),
9494 "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
9495 );
9496 }
9497
9498 #[test]
9499 fn initiate_authorization_code_pkce_flow_builds_expected_url() {
9500 let app = PublicClientApplication::new(
9501 "client-id",
9502 Some("https://login.microsoftonline.com/tenant"),
9503 #[cfg(feature = "set_timeout")]
9504 Duration::from_secs(3),
9505 #[cfg(feature = "ipvers")]
9506 &[],
9507 )
9508 .unwrap();
9509
9510 let flow = app
9511 .initiate_authorization_code_pkce_flow(
9512 vec!["https://graph.microsoft.com/User.Read"],
9513 "http://localhost/callback",
9514 )
9515 .unwrap();
9516 let url = Url::parse(&flow.auth_url).unwrap();
9517 let params: HashMap<String, String> = url.query_pairs().into_owned().collect();
9518
9519 assert_eq!(
9520 url.as_str().split('?').next().unwrap(),
9521 "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize"
9522 );
9523 assert_eq!(params["client_id"], "client-id");
9524 assert_eq!(params["response_type"], "code");
9525 assert_eq!(params["redirect_uri"], "http://localhost/callback");
9526 assert_eq!(params["response_mode"], "query");
9527 assert_eq!(
9528 params["scope"],
9529 "openid profile offline_access https://graph.microsoft.com/User.Read"
9530 );
9531 assert_eq!(params["state"], flow.state);
9532 assert_eq!(
9533 params["code_challenge"],
9534 pkce_code_challenge(&flow.code_verifier)
9535 );
9536 assert_eq!(params["code_challenge_method"], "S256");
9537 assert!(flow.code_verifier.len() >= 43);
9538 }
9539
9540 #[tokio::test]
9541 async fn acquire_token_by_authorization_code_pkce_flow_rejects_wrong_state() {
9542 let app = PublicClientApplication::new(
9543 "client-id",
9544 Some("https://login.microsoftonline.com/tenant"),
9545 #[cfg(feature = "set_timeout")]
9546 Duration::from_secs(3),
9547 #[cfg(feature = "ipvers")]
9548 &[],
9549 )
9550 .unwrap();
9551 let flow = app
9552 .initiate_authorization_code_pkce_flow(vec!["User.Read"], "http://localhost/callback")
9553 .unwrap();
9554
9555 let result = app
9556 .acquire_token_by_authorization_code_pkce_flow(
9557 &flow,
9558 "http://localhost/callback?code=abc&state=wrong",
9559 )
9560 .await;
9561
9562 assert!(matches!(result, Err(MsalError::InvalidParse(_))));
9563 }
9564
9565 #[tokio::test]
9566 async fn acquire_token_by_authorization_code_pkce_flow_maps_redirect_error() {
9567 let app = PublicClientApplication::new(
9568 "client-id",
9569 Some("https://login.microsoftonline.com/tenant"),
9570 #[cfg(feature = "set_timeout")]
9571 Duration::from_secs(3),
9572 #[cfg(feature = "ipvers")]
9573 &[],
9574 )
9575 .unwrap();
9576 let flow = app
9577 .initiate_authorization_code_pkce_flow(vec!["User.Read"], "http://localhost/callback")
9578 .unwrap();
9579 let redirect = format!(
9580 "http://localhost/callback?error=access_denied&error_description=nope&state={}",
9581 flow.state
9582 );
9583
9584 let result = app
9585 .acquire_token_by_authorization_code_pkce_flow(&flow, &redirect)
9586 .await;
9587
9588 assert!(matches!(result, Err(MsalError::AcquireTokenFailed(_))));
9589 if let Err(MsalError::AcquireTokenFailed(error)) = result {
9590 assert_eq!(error.error, "access_denied");
9591 assert_eq!(error.error_description, "nope");
9592 }
9593 }
9594
9595 #[test]
9596 fn authorization_code_pkce_token_form_contains_expected_fields() {
9597 let app = PublicClientApplication::new(
9598 "client-id",
9599 Some("https://login.microsoftonline.com/tenant"),
9600 #[cfg(feature = "set_timeout")]
9601 Duration::from_secs(3),
9602 #[cfg(feature = "ipvers")]
9603 &[],
9604 )
9605 .unwrap();
9606 let flow = app
9607 .initiate_authorization_code_pkce_flow(vec!["User.Read"], "http://localhost/callback")
9608 .unwrap();
9609 let form: HashMap<&str, &str> = app
9610 .authorization_code_pkce_token_form(&flow, "auth-code")
9611 .into_iter()
9612 .collect();
9613
9614 assert_eq!(form["client_id"], "client-id");
9615 assert_eq!(form["grant_type"], "authorization_code");
9616 assert_eq!(form["code"], "auth-code");
9617 assert_eq!(form["redirect_uri"], "http://localhost/callback");
9618 assert_eq!(form["scope"], "openid profile offline_access User.Read");
9619 assert_eq!(form["code_verifier"], flow.code_verifier);
9620 assert_eq!(form["client_info"], "1");
9621 }
9622
9623 #[cfg(feature = "broker")]
9624 #[test]
9625 fn p2p_device_payload_serializes_expected_fields() {
9626 let payload = P2PDeviceCertificatePayload::new(
9627 "nonce",
9628 "csr",
9629 "host1",
9630 &["host1.example.com".to_string()],
9631 );
9632 let value: Value = serde_json::to_value(payload).unwrap();
9633
9634 assert_eq!(value["client_id"], BROKER_CLIENT_IDENT);
9635 assert_eq!(value["request_nonce"], "nonce");
9636 assert_eq!(value["grant_type"], "device_auth");
9637 assert_eq!(value["cert_token_use"], "device_cert");
9638 assert_eq!(
9639 value["csr_type"],
9640 "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
9641 );
9642 assert_eq!(value["csr"], "csr");
9643 assert_eq!(value["netbios_name"], "host1");
9644 assert_eq!(value["dns_names"][0], "host1.example.com");
9645 }
9646
9647 #[cfg(feature = "broker")]
9648 #[test]
9649 fn p2p_device_jwt_header_matches_broker_shape() {
9650 let (mut tpm, machine_key) = test_tpm();
9651 let loadable_key = tpm.rs256_create(&machine_key).unwrap();
9652 let key = tpm.rs256_load(&machine_key, &loadable_key).unwrap();
9653 let cert_der = vec![0x30, 0x03, 0x02, 0x01, 0x00];
9654 let payload =
9655 P2PDeviceCertificatePayload::new("nonce", "csr", "host1", &["host1".to_string()]);
9656
9657 let jwt = BrokerClientApplication::sign_p2p_device_jwt(&payload, &cert_der, &mut tpm, &key)
9658 .unwrap();
9659 let header = decode_jwt_header(&jwt);
9660 let header = header.as_object().unwrap();
9661
9662 assert_eq!(header.len(), 3);
9663 assert_eq!(header["alg"], "RS256");
9664 assert_eq!(header["typ"], "JWT");
9665 assert_eq!(header["x5c"], STANDARD.encode(cert_der));
9667 assert!(!header.contains_key("kid"));
9668 }
9669
9670 #[cfg(feature = "broker")]
9671 #[test]
9672 fn p2p_device_jwt_signature_verifies() {
9673 let (mut tpm, machine_key) = test_tpm();
9674 let loadable_key = tpm.rs256_create(&machine_key).unwrap();
9675 let key = tpm.rs256_load(&machine_key, &loadable_key).unwrap();
9676 let payload =
9677 P2PDeviceCertificatePayload::new("nonce", "csr", "host1", &["host1".to_string()]);
9678
9679 let jwt =
9680 BrokerClientApplication::sign_p2p_device_jwt(&payload, &[0x30, 0x00], &mut tpm, &key)
9681 .unwrap();
9682 let (signing_input, signature) = jwt.rsplit_once('.').unwrap();
9683 let signature = URL_SAFE_NO_PAD.decode(signature).unwrap();
9684
9685 let public_key = PKey::public_key_from_der(&tpm.rs256_public_der(&key).unwrap()).unwrap();
9686 let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key).unwrap();
9687 verifier.update(signing_input.as_bytes()).unwrap();
9688
9689 assert!(verifier.verify(&signature).unwrap());
9690 }
9691
9692 #[cfg(feature = "broker")]
9693 #[test]
9694 fn p2p_user_payload_serializes_expected_fields() {
9695 let prt = test_prt("prt-refresh-token");
9696
9697 let payload = P2PUserCertificatePayload::new(&prt, "nonce", "csr");
9698 let value: Value = serde_json::to_value(payload).unwrap();
9699
9700 assert_eq!(value["iss"], "aad:brokerplugin");
9701 assert_eq!(value["aud"], "login.microsoftonline.com");
9702 assert_eq!(value["grant_type"], "refresh_token");
9703 assert_eq!(value["scope"], "openid aza ugs");
9704 assert_eq!(value["refresh_token"], "prt-refresh-token");
9705 assert_eq!(value["client_id"], BROKER_CLIENT_IDENT);
9706 assert_eq!(value["cert_token_use"], "user_cert");
9707 assert_eq!(value["csr"], "csr");
9708 }
9709
9710 #[cfg(feature = "broker")]
9711 #[test]
9712 fn p2p_user_payload_redacts_refresh_token() {
9713 let prt = test_prt("prt-refresh-token");
9714 let payload = P2PUserCertificatePayload::new(&prt, "nonce", "csr");
9715
9716 let redacted = payload.redacted().unwrap();
9717
9718 assert_eq!(redacted["refresh_token"], "**********");
9719 assert_eq!(redacted["csr"], "csr");
9721 assert_eq!(
9722 serde_json::to_value(&payload).unwrap()["refresh_token"],
9723 "prt-refresh-token"
9724 );
9725 }
9726
9727 #[cfg(feature = "broker")]
9728 #[test]
9729 fn p2p_user_jwt_header_matches_broker_shape() {
9730 let prt = test_prt("prt-refresh-token");
9731 let payload = P2PUserCertificatePayload::new(&prt, "nonce", "csr");
9732 let ctx = [0xA5; 24];
9733 let hmac_key = [0x5A; 32];
9734
9735 let jwt =
9736 BrokerClientApplication::sign_p2p_user_jwt_with_key(&payload, &ctx, &hmac_key).unwrap();
9737 let header = decode_jwt_header(&jwt);
9738 let header = header.as_object().unwrap();
9739
9740 assert_eq!(header.len(), 3);
9741 assert_eq!(header["alg"], "HS256");
9742 assert_eq!(header["typ"], "JWT");
9743 assert_eq!(
9744 STANDARD
9745 .decode(header["ctx"].as_str().unwrap())
9746 .unwrap()
9747 .len(),
9748 24
9749 );
9750 assert!(!header.contains_key("kid"));
9751 }
9752
9753 #[cfg(feature = "broker")]
9754 #[test]
9755 fn p2p_device_tenant_oid_uses_little_endian_guid() {
9756 let raw = [
9757 0x04, 0x81, 0x10, 0x37, 0xbb, 0x73, 0x69, 0xf8, 0x65, 0x0e, 0x44, 0xb3, 0x9e, 0xdd,
9758 0x57, 0xda, 0x64, 0xf6, 0xcf,
9759 ];
9760
9761 assert_eq!(
9762 BrokerClientApplication::tenant_id_from_device_certificate_oid(&raw).unwrap(),
9763 TEST_TENANT_GUID
9764 );
9765 }
9766
9767 #[cfg(feature = "broker")]
9768 #[test]
9769 fn p2p_tenant_oid_accepts_all_encodings() {
9770 let mut long_form = vec![0x04, 0x81, 0x10];
9773 long_form.extend_from_slice(&TEST_TENANT_BYTES_LE);
9774 let mut short_form = vec![0x04, 0x10];
9775 short_form.extend_from_slice(&TEST_TENANT_BYTES_LE);
9776
9777 for raw in [long_form, short_form, TEST_TENANT_BYTES_LE.to_vec()] {
9778 assert_eq!(
9779 BrokerClientApplication::tenant_id_from_device_certificate_oid(&raw).unwrap(),
9780 TEST_TENANT_GUID
9781 );
9782 }
9783 }
9784
9785 #[cfg(feature = "broker")]
9786 #[test]
9787 fn p2p_device_tenant_id_absent_extension() {
9788 let rsa = Rsa::generate(2048).unwrap();
9789 let pkey = PKey::from_rsa(rsa).unwrap();
9790 let cert_der = self_signed_cert(&pkey, &[]);
9791
9792 assert_eq!(
9793 BrokerClientApplication::device_certificate_tenant_id(&cert_der).unwrap(),
9794 None
9795 );
9796 }
9797
9798 #[cfg(feature = "broker")]
9799 #[test]
9800 fn p2p_device_tenant_id_extracts_from_extension() {
9801 let rsa = Rsa::generate(2048).unwrap();
9802 let pkey = PKey::from_rsa(rsa).unwrap();
9803 let tenant_value = OctetString::new(TEST_TENANT_BYTES_LE)
9804 .unwrap()
9805 .to_der()
9806 .unwrap();
9807 let cert_der = self_signed_cert(&pkey, &[tenant_oid_extension(&tenant_value)]);
9808
9809 assert_eq!(
9810 BrokerClientApplication::device_certificate_tenant_id(&cert_der).unwrap(),
9811 Some(TEST_TENANT_GUID.to_string())
9812 );
9813 }
9814
9815 #[cfg(feature = "broker")]
9816 #[test]
9817 fn p2p_device_tenant_id_ignores_malformed_oid() {
9818 let rsa = Rsa::generate(2048).unwrap();
9819 let pkey = PKey::from_rsa(rsa).unwrap();
9820 let cert_der = self_signed_cert(&pkey, &[tenant_oid_extension(b"contoso.onmicrosoft.com")]);
9823
9824 assert_eq!(
9825 BrokerClientApplication::device_certificate_tenant_id(&cert_der).unwrap(),
9826 None
9827 );
9828 }
9829
9830 #[cfg(feature = "broker")]
9831 #[test]
9832 fn p2p_tenant_endpoint_uses_current_authority_host() {
9833 let app = test_broker_app("https://login.microsoftonline.com/common");
9834 assert_eq!(
9835 app.tenant_token_endpoint("11111111-1111-1111-1111-111111111111")
9836 .unwrap(),
9837 "https://login.microsoftonline.com/11111111-1111-1111-1111-111111111111/oauth2/token"
9838 );
9839
9840 let app = test_broker_app("https://login.microsoftonline.us/organizations");
9841 assert_eq!(
9842 app.tenant_token_endpoint("contoso.com").unwrap(),
9843 "https://login.microsoftonline.us/contoso.com/oauth2/token"
9844 );
9845 assert!(app.tenant_token_endpoint("../bad").is_err());
9846 }
9847
9848 #[cfg(feature = "broker")]
9849 #[test]
9850 fn p2p_tenant_endpoint_rejects_empty_tenant() {
9851 let app = test_broker_app("https://login.microsoftonline.com/common");
9852
9853 assert!(app.tenant_token_endpoint("").is_err());
9854 }
9855
9856 #[cfg(feature = "broker")]
9857 #[test]
9858 fn p2p_csr_is_der_and_matches_key() {
9859 let (mut tpm, machine_key) = test_tpm();
9860 let loadable_key = tpm.rs256_create(&machine_key).unwrap();
9861 let key = tpm.rs256_load(&machine_key, &loadable_key).unwrap();
9862 let subject = Name::from_str("CN=test-device").unwrap();
9863
9864 let (csr_der, public_key_der) =
9865 BrokerClientApplication::create_p2p_csr(&mut tpm, &key, subject).unwrap();
9866
9867 assert!(!csr_der.is_empty());
9868 assert_eq!(public_key_der, tpm.rs256_public_der(&key).unwrap());
9869 }
9870
9871 #[cfg(feature = "broker")]
9872 #[test]
9873 fn p2p_user_csr_accepts_blank_common_name() {
9874 let (mut tpm, machine_key) = test_tpm();
9875 let loadable_key = tpm.rs256_create(&machine_key).unwrap();
9876 let key = tpm.rs256_load(&machine_key, &loadable_key).unwrap();
9877 let subject = Name::from_str("CN=").unwrap();
9878
9879 let (csr_der, public_key_der) =
9880 BrokerClientApplication::create_p2p_csr(&mut tpm, &key, subject).unwrap();
9881
9882 assert!(!csr_der.is_empty());
9883 assert_eq!(public_key_der, tpm.rs256_public_der(&key).unwrap());
9884 }
9885
9886 #[cfg(feature = "broker")]
9887 fn test_private_key() -> P2PPrivateKey {
9888 P2PPrivateKey::GeneratedUser(LoadableRS256Key::Soft2048V2 {
9889 key: vec![],
9890 tag: [0; 16],
9891 iv: [0; 16],
9892 })
9893 }
9894
9895 #[cfg(feature = "broker")]
9896 #[test]
9897 fn p2p_response_parses_certificate_metadata() {
9898 let rsa = Rsa::generate(2048).unwrap();
9899 let pkey = PKey::from_rsa(rsa).unwrap();
9900 let public_key_der = pkey.public_key_to_der().unwrap();
9901 let cert_der = self_signed_cert(&pkey, &[]);
9902
9903 let cert = BrokerClientApplication::p2p_certificate_from_response(
9904 &P2PCertificateResponse {
9905 x5c: STANDARD.encode(&cert_der),
9906 x5c_ca: STANDARD.encode(&cert_der),
9907 },
9908 test_private_key(),
9909 &public_key_der,
9910 )
9911 .unwrap();
9912
9913 assert_eq!(cert.certificate_der, cert_der);
9914 assert_eq!(cert.subject, "CN=p2p-test");
9915 assert_eq!(cert.issuer, "CN=p2p-test");
9916 assert_eq!(cert.thumbprint_sha1.len(), 40);
9917 assert!(cert.not_after_unix > 0);
9919 }
9920
9921 #[cfg(feature = "broker")]
9922 #[test]
9923 fn p2p_response_parses_ca_certificate() {
9924 let rsa = Rsa::generate(2048).unwrap();
9925 let pkey = PKey::from_rsa(rsa).unwrap();
9926 let public_key_der = pkey.public_key_to_der().unwrap();
9927 let cert_der = self_signed_cert(&pkey, &[]);
9928 let ca_der = self_signed_cert(&pkey, &[]);
9929
9930 let cert = BrokerClientApplication::p2p_certificate_from_response(
9931 &P2PCertificateResponse {
9932 x5c: STANDARD.encode(&cert_der),
9933 x5c_ca: STANDARD.encode(&ca_der),
9934 },
9935 test_private_key(),
9936 &public_key_der,
9937 )
9938 .unwrap();
9939
9940 assert_eq!(cert.ca_certificate_der, ca_der);
9941 assert_eq!(
9942 cert.ca_certificate_pem,
9943 Some(format!(
9944 "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n",
9945 STANDARD.encode(&ca_der)
9946 ))
9947 );
9948
9949 let cert = BrokerClientApplication::p2p_certificate_from_response(
9952 &P2PCertificateResponse {
9953 x5c: STANDARD.encode(&cert_der),
9954 x5c_ca: "AQID".to_string(),
9955 },
9956 test_private_key(),
9957 &public_key_der,
9958 )
9959 .unwrap();
9960
9961 assert_eq!(cert.ca_certificate_der, vec![0x01, 0x02, 0x03]);
9962 assert_eq!(cert.ca_certificate_pem, None);
9963 }
9964
9965 #[cfg(feature = "broker")]
9966 #[test]
9967 fn p2p_response_rejects_public_key_mismatch() {
9968 let pkey = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
9969 let other = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
9970 let cert_der = self_signed_cert(&pkey, &[]);
9971
9972 assert!(BrokerClientApplication::p2p_certificate_from_response(
9975 &P2PCertificateResponse {
9976 x5c: STANDARD.encode(&cert_der),
9977 x5c_ca: STANDARD.encode(&cert_der),
9978 },
9979 test_private_key(),
9980 &other.public_key_to_der().unwrap(),
9981 )
9982 .is_err());
9983 }
9984
9985 #[test]
9986 fn user_token_spn_uses_upn_when_present() {
9987 let access_token = build_access_token(
9988 r#"{"amr":["pwd"],"tid":"11111111-1111-1111-1111-111111111111","upn":"user@example.com"}"#,
9989 );
9990 let token = build_user_token(access_token);
9991
9992 assert_eq!(token.spn().unwrap_or_default(), "user@example.com");
9993 }
9994
9995 #[test]
9996 fn user_token_spn_falls_back_to_unique_name() {
9997 let access_token = build_access_token(
9998 r#"{"amr":["pwd"],"tid":"11111111-1111-1111-1111-111111111111","unique_name":"alias@example.com"}"#,
9999 );
10000 let token = build_user_token(access_token);
10001
10002 assert_eq!(token.spn().unwrap_or_default(), "alias@example.com");
10003 }
10004
10005 #[test]
10006 fn user_token_spn_prefers_upn_when_both_fields_present() {
10007 let access_token = build_access_token(
10008 r#"{"amr":["pwd"],"tid":"11111111-1111-1111-1111-111111111111","upn":"primary@example.com","unique_name":"alias@example.com"}"#,
10009 );
10010 let token = build_user_token(access_token);
10011
10012 assert_eq!(token.spn().unwrap_or_default(), "primary@example.com");
10013 }
10014
10015 fn build_mfa_auth_continue(
10016 methods: Vec<MfaMethodInfo>,
10017 skip_fido_for_mfa: bool,
10018 ) -> MFAAuthContinue {
10019 let mfa_methods = methods.iter().map(|m| m.auth_method_id.clone()).collect();
10020 MFAAuthContinue {
10021 mfa_method_details: methods,
10022 mfa_methods,
10023 skip_fido_for_mfa,
10024 ..Default::default()
10025 }
10026 }
10027
10028 #[test]
10032 fn mfa_prefers_sms_when_default_and_fido_is_cross_device_passkey() {
10033 let methods = vec![
10034 MfaMethodInfo {
10035 auth_method_id: "OneWaySMS".to_string(),
10036 display: "+X XXXXXXXX90".to_string(),
10037 is_default: true,
10038 },
10039 MfaMethodInfo {
10040 auth_method_id: "FidoKey".to_string(),
10041 display: "MS Authenticator passkey".to_string(),
10042 is_default: false,
10043 },
10044 MfaMethodInfo {
10045 auth_method_id: "PhoneAppNotification".to_string(),
10046 display: "Microsoft Authenticator".to_string(),
10047 is_default: false,
10048 },
10049 ];
10050
10051 let mfa = build_mfa_auth_continue(methods, true);
10052
10053 assert_eq!(mfa.mfa_method(), "OneWaySMS");
10054
10055 let default = mfa.get_default_mfa_method_details();
10056 assert!(default.is_some(), "default MFA method should exist");
10057 if let Some(default) = default {
10058 assert_eq!(default.auth_method_id, "OneWaySMS");
10059 assert!(default.is_default);
10060 }
10061
10062 assert!(mfa.has_mfa_method("FidoKey"));
10063 let fido = mfa.get_mfa_method_by_id("FidoKey");
10064 assert!(fido.is_some(), "FidoKey method should exist");
10065 if let Some(fido) = fido {
10066 assert!(mfa.should_skip_fido_method(&fido));
10067 }
10068
10069 assert_eq!(mfa.mfa_method_count(), 3);
10070 }
10071
10072 #[test]
10077 fn passwordless_fido_skipped_when_cross_device_passkey_and_sms_preferred() {
10078 let options = vec![AuthOption::PasswordlessFido, AuthOption::Fido];
10079
10080 let result = should_attempt_passwordless_security_key(
10081 &options, true, );
10083
10084 assert!(
10085 result,
10086 "Should attempt security key when FIDO params present and legacy flag enabled"
10087 );
10088 }
10089
10090 #[test]
10091 fn security_key_skipped_when_no_fido_params() {
10092 let options = vec![AuthOption::PasswordlessSecurityKey];
10093 let result = should_attempt_passwordless_security_key(&options, false);
10094 assert!(
10095 !result,
10096 "Should not attempt security key without FIDO params"
10097 );
10098 }
10099
10100 #[test]
10101 fn security_key_skipped_when_not_enabled() {
10102 let options = vec![AuthOption::Fido];
10103 let result = should_attempt_passwordless_security_key(&options, true);
10104 assert!(
10105 !result,
10106 "Should not attempt security key when not enabled in config"
10107 );
10108 }
10109
10110 #[test]
10111 fn qr_bluetooth_attempted_when_cross_device_passkey() {
10112 let options = vec![AuthOption::PasswordlessQrBluetooth];
10113 let result = should_attempt_passwordless_qr_bluetooth(&options, true, true);
10114 assert!(
10115 result,
10116 "Should attempt QR/Bluetooth when user has cross-device passkey"
10117 );
10118 }
10119
10120 #[test]
10121 fn qr_bluetooth_skipped_when_no_cross_device_passkey() {
10122 let options = vec![AuthOption::PasswordlessQrBluetooth];
10123 let result = should_attempt_passwordless_qr_bluetooth(&options, true, false);
10124 assert!(
10125 !result,
10126 "Should not attempt QR/Bluetooth when user has no cross-device passkey"
10127 );
10128 }
10129
10130 #[test]
10131 fn qr_bluetooth_skipped_when_not_enabled() {
10132 let options = vec![AuthOption::PasswordlessFido];
10133 let result = should_attempt_passwordless_qr_bluetooth(&options, true, true);
10134 assert!(
10135 !result,
10136 "Legacy PasswordlessFido should not enable QR/Bluetooth"
10137 );
10138 }
10139
10140 #[test]
10141 fn both_flows_when_fido_params_and_cross_device() {
10142 let options = vec![
10143 AuthOption::PasswordlessSecurityKey,
10144 AuthOption::PasswordlessQrBluetooth,
10145 ];
10146 let security_key = should_attempt_passwordless_security_key(&options, true);
10147 let qr_bluetooth = should_attempt_passwordless_qr_bluetooth(&options, true, true);
10148 assert!(security_key, "Should attempt security key");
10149 assert!(qr_bluetooth, "Should attempt QR/Bluetooth");
10150 }
10151
10152 #[test]
10153 fn prt_serialize_deserialize() {
10154 let prt = PrimaryRefreshToken {
10155 token_type: "Bearer".to_string(),
10156 expires_in: "3600".to_string(),
10157 ext_expires_in: "3600".to_string(),
10158 expires_on: "9999999999".to_string(),
10159 refresh_token: "refresh_token".to_string(),
10160 refresh_token_expires_in: 3600,
10161 session_key_jwe: None,
10162 id_token: IdToken::default(),
10163 client_info: ClientInfo::default(),
10164 device_tenant_id: None,
10165 tgt_on_prem: OnPremTgt::Absent {},
10166 tgt_cloud: StructuredTgt::default(),
10167 kerberos_top_level_names: None,
10168 };
10169 let prt_json = r#"
10170 {
10171 "token_type":"Bearer",
10172 "expires_in":"3600",
10173 "ext_expires_in":"3600",
10174 "expires_on":"9999999999",
10175 "refresh_token":"refresh_token",
10176 "refresh_token_expires_in":3600,
10177 "session_key_jwe":null,
10178 "id_token":{
10179 "name":"",
10180 "oid":"",
10181 "preferred_username":null,
10182 "puid":null,
10183 "tenant_region_scope":null,
10184 "tid":""
10185 },
10186 "client_info":{
10187 "uid":null,
10188 "utid":null
10189 },
10190 "device_tenant_id":null,
10191 "tgt_cloud":{
10192 "clientKey":null,
10193 "keyType":0,
10194 "error":null,
10195 "messageBuffer":null,
10196 "realm":null,
10197 "sn":null,
10198 "cn":null,
10199 "sessionKeyType":0,
10200 "accountType":0
10201 },
10202 "kerberos_top_level_names":null
10203 }
10204 "#
10205 .to_string()
10206 .replace("\n", "")
10207 .replace(" ", "");
10208 let se = serde_json::to_string(&prt).expect("Failed to serialize");
10209 assert_eq!(prt_json, se);
10210
10211 let de: PrimaryRefreshToken = serde_json::from_str(&se).expect("Falied to deserialize");
10212 assert_eq!(prt, de);
10213 }
10214
10215 #[test]
10216 fn prt_serialize_deserialize_tgt_ad() {
10217 let prt = PrimaryRefreshToken {
10218 token_type: "Bearer".to_string(),
10219 expires_in: "3600".to_string(),
10220 ext_expires_in: "3600".to_string(),
10221 expires_on: "9999999999".to_string(),
10222 refresh_token: "refresh_token".to_string(),
10223 refresh_token_expires_in: 3600,
10224 session_key_jwe: None,
10225 id_token: IdToken::default(),
10226 client_info: ClientInfo::default(),
10227 device_tenant_id: None,
10228 tgt_on_prem: OnPremTgt::Structured {
10229 tgt_ad: StructuredTgt {
10230 client_key: Some("a".to_string()),
10231 key_type: 18,
10232 error: None,
10233 message_buffer: Some("b".to_string()),
10234 realm: Some("c".to_string()),
10235 sn: Some("d".to_string()),
10236 cn: Some("e".to_string()),
10237 session_key_type: 18,
10238 account_type: 1,
10239 },
10240 },
10241 tgt_cloud: StructuredTgt::default(),
10242 kerberos_top_level_names: None,
10243 };
10244 let prt_json = r#"
10245 {
10246 "token_type":"Bearer",
10247 "expires_in":"3600",
10248 "ext_expires_in":"3600",
10249 "expires_on":"9999999999",
10250 "refresh_token":"refresh_token",
10251 "refresh_token_expires_in":3600,
10252 "session_key_jwe":null,
10253 "id_token":{
10254 "name":"",
10255 "oid":"",
10256 "preferred_username":null,
10257 "puid":null,
10258 "tenant_region_scope":null,
10259 "tid":""
10260 },
10261 "client_info":{
10262 "uid":null,
10263 "utid":null
10264 },
10265 "device_tenant_id":null,
10266 "tgt_ad":{
10267 "clientKey":"a",
10268 "keyType":18,
10269 "error":null,
10270 "messageBuffer":"b",
10271 "realm":"c",
10272 "sn":"d",
10273 "cn":"e",
10274 "sessionKeyType":18,
10275 "accountType":1
10276 },
10277 "tgt_cloud":{
10278 "clientKey":null,
10279 "keyType":0,
10280 "error":null,
10281 "messageBuffer":null,
10282 "realm":null,
10283 "sn":null,
10284 "cn":null,
10285 "sessionKeyType":0,
10286 "accountType":0
10287 },
10288 "kerberos_top_level_names":null
10289 }
10290 "#
10291 .to_string()
10292 .replace("\n", "")
10293 .replace(" ", "");
10294 let se = serde_json::to_string(&prt).expect("Failed to serialize");
10295 assert_eq!(prt_json, se);
10296
10297 let de: PrimaryRefreshToken = serde_json::from_str(&se).expect("Falied to deserialize");
10298 assert_eq!(prt, de);
10299 }
10300
10301 #[test]
10302 fn prt_serialize_deserialize_tgt_message_buffer() {
10303 let prt = PrimaryRefreshToken {
10304 token_type: "Bearer".to_string(),
10305 expires_in: "3600".to_string(),
10306 ext_expires_in: "3600".to_string(),
10307 expires_on: "9999999999".to_string(),
10308 refresh_token: "refresh_token".to_string(),
10309 refresh_token_expires_in: 3600,
10310 session_key_jwe: None,
10311 id_token: IdToken::default(),
10312 client_info: ClientInfo::default(),
10313 device_tenant_id: None,
10314 tgt_on_prem: OnPremTgt::Raw(RawTgt {
10315 tgt_message_buffer: "a".to_string(),
10316 tgt_client_key: "b".to_string(),
10317 tgt_key_type: 18,
10318 }),
10319 tgt_cloud: StructuredTgt::default(),
10320 kerberos_top_level_names: None,
10321 };
10322 let prt_json = r#"
10323 {
10324 "token_type":"Bearer",
10325 "expires_in":"3600",
10326 "ext_expires_in":"3600",
10327 "expires_on":"9999999999",
10328 "refresh_token":"refresh_token",
10329 "refresh_token_expires_in":3600,
10330 "session_key_jwe":null,
10331 "id_token":{
10332 "name":"",
10333 "oid":"",
10334 "preferred_username":null,
10335 "puid":null,
10336 "tenant_region_scope":null,
10337 "tid":""
10338 },
10339 "client_info":{
10340 "uid":null,
10341 "utid":null
10342 },
10343 "device_tenant_id":null,
10344 "tgt_message_buffer":"a",
10345 "tgt_client_key":"b",
10346 "tgt_key_type":18,
10347 "tgt_cloud":{
10348 "clientKey":null,
10349 "keyType":0,
10350 "error":null,
10351 "messageBuffer":null,
10352 "realm":null,
10353 "sn":null,
10354 "cn":null,
10355 "sessionKeyType":0,
10356 "accountType":0
10357 },
10358 "kerberos_top_level_names":null
10359 }
10360 "#
10361 .to_string()
10362 .replace("\n", "")
10363 .replace(" ", "");
10364 let se = serde_json::to_string(&prt).expect("Failed to serialize");
10365 assert_eq!(prt_json, se);
10366
10367 let de: PrimaryRefreshToken = serde_json::from_str(&se).expect("Falied to deserialize");
10368 assert_eq!(prt, de);
10369 }
10370
10371 #[test]
10372 fn prt_deserialize_invalid() {
10373 let se = r#"
10374 {
10375 "token_type":"Bearer",
10376 "expires_in":"3600",
10377 "ext_expires_in":"3600",
10378 "expires_on":"9999999999",
10379 "refresh_token":"refresh_token",
10380 "refresh_token_expires_in":3600,
10381 "session_key_jwe":null,
10382 "id_token":{
10383 "name":"",
10384 "oid":"",
10385 "preferred_username":null,
10386 "puid":null,
10387 "tenant_region_scope":null,
10388 "tid":""
10389 },
10390 "client_info":{
10391 "uid":null,
10392 "utid":null
10393 },
10394 "device_tenant_id":null,
10395 "tgt_message_buffer":"a",
10396 "tgt_client_key":"b",
10397 "tgt_key_type":18,
10398 "tgt_ad":{
10399 "clientKey":"a",
10400 "keyType":18,
10401 "error":null,
10402 "messageBuffer":"b",
10403 "realm":"c",
10404 "sn":"d",
10405 "cn":"e",
10406 "sessionKeyType":18,
10407 "accountType":1
10408 },
10409 "tgt_cloud":{
10410 "clientKey":null,
10411 "keyType":0,
10412 "error":null,
10413 "messageBuffer":null,
10414 "realm":null,
10415 "sn":null,
10416 "cn":null,
10417 "sessionKeyType":0,
10418 "accountType":0
10419 },
10420 "kerberos_top_level_names":null
10421 }
10422 "#
10423 .to_string()
10424 .replace("\n", "")
10425 .replace(" ", "");
10426 let de = serde_json::from_str::<PrimaryRefreshToken>(&se);
10427 assert!(de.is_err());
10428 }
10430}