-
Notifications
You must be signed in to change notification settings - Fork 351
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Microsoft 365 HTTP API validation (#1194)
* feat: add Outlook HTTP API validation - check the validity of Outlook/Office 365 email addresses via the method outlined [here](https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/). - run only via the `--outlook-use-api` flags (defaulting to `false`.) relates #937 * fix: restrict Office 365 domain use `.mail.protection.outlook.com.` for domains backed by Outlook/Office 365. * fix: continue for non-definitive responses from Outlook API if using `--outlook-use-api`, only return immediately in the event of a positive response: negative responses are ambiguous and the process should fall back to subsequent checks. * fix: amend Outlook references update references to "Microsoft 365" to make is more explicit that this pertains to the underlying services, not Outlook addresses. * fix: continue in the event of a ReqwestError allow both failures in the HTTP request and 404 responses to continue. Co-authored-by: Amaury <[email protected]>
- Loading branch information
1 parent
31146d8
commit 5d3c49f
Showing
4 changed files
with
112 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,19 +23,24 @@ use fantoccini::{ | |
ClientBuilder, Locator, | ||
}; | ||
use futures::TryFutureExt; | ||
use reqwest::Error as ReqwestError; | ||
use serde::Serialize; | ||
use serde_json::Map; | ||
|
||
use super::SmtpDetails; | ||
use crate::util::ser_with_display::ser_with_display; | ||
use crate::LOG_TARGET; | ||
use crate::{ | ||
smtp::http_api::create_client, util::ser_with_display::ser_with_display, CheckEmailInput, | ||
}; | ||
|
||
#[derive(Debug, Serialize)] | ||
pub enum HotmailError { | ||
#[serde(serialize_with = "ser_with_display")] | ||
Cmd(CmdError), | ||
#[serde(serialize_with = "ser_with_display")] | ||
NewSession(NewSessionError), | ||
#[serde(serialize_with = "ser_with_display")] | ||
ReqwestError(ReqwestError), | ||
} | ||
|
||
impl From<CmdError> for HotmailError { | ||
|
@@ -50,6 +55,12 @@ impl From<NewSessionError> for HotmailError { | |
} | ||
} | ||
|
||
impl From<ReqwestError> for HotmailError { | ||
fn from(error: ReqwestError) -> Self { | ||
HotmailError::ReqwestError(error) | ||
} | ||
} | ||
|
||
/// Check if a Hotmail/Outlook email exists by connecting to the password | ||
/// recovery page https://account.live.com/password/reset using a headless | ||
/// browser. Make sure you have a WebDriver server running locally before | ||
|
@@ -140,9 +151,65 @@ pub async fn check_password_recovery( | |
}) | ||
} | ||
|
||
/// Convert an email address to its corresponding OneDrive URL. | ||
fn get_onedrive_url(email_address: &str) -> String { | ||
let (username, domain) = email_address | ||
.split_once('@') | ||
.expect("Email address syntax already validated."); | ||
let (tenant, _) = domain | ||
.split_once('.') | ||
.expect("Email domain syntax already validated."); | ||
|
||
format!( | ||
"https://{}-my.sharepoint.com/personal/{}_{}/_layouts/15/onedrive.aspx", | ||
tenant, | ||
username.replace('.', "_"), | ||
domain.replace('.', "_"), | ||
) | ||
} | ||
|
||
/// Use a HTTP request to verify if an Microsoft 365 email address exists. | ||
/// | ||
/// See | ||
/// [this article](<https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/>) | ||
/// for details on the underlying principles. | ||
/// | ||
/// Note that a positive response from this function is (at present) considered | ||
/// a reliable indicator that an email-address is valid. However, a negative | ||
/// response is ambigious: the email address may or may not be valid but this | ||
/// cannot be determined by the method outlined here. | ||
pub async fn check_microsoft365_api( | ||
to_email: &EmailAddress, | ||
input: &CheckEmailInput, | ||
) -> Result<Option<SmtpDetails>, HotmailError> { | ||
let url = get_onedrive_url(to_email.as_ref()); | ||
|
||
let response = create_client(input, "microsoft365")? | ||
.head(url) | ||
.send() | ||
.await?; | ||
|
||
log::debug!( | ||
target: LOG_TARGET, | ||
"[email={}] microsoft365 response: {:?}", | ||
to_email, | ||
response | ||
); | ||
|
||
if response.status() == 403 { | ||
Ok(Some(SmtpDetails { | ||
can_connect_smtp: true, | ||
is_deliverable: true, | ||
..Default::default() | ||
})) | ||
} else { | ||
Ok(None) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::check_password_recovery; | ||
use super::{check_password_recovery, get_onedrive_url}; | ||
use async_smtp::EmailAddress; | ||
use async_std::prelude::FutureExt; | ||
use std::str::FromStr; | ||
|
@@ -193,4 +260,12 @@ mod tests { | |
let f = f1.try_join(f2).await; | ||
assert!(f.is_ok(), "{:?}", f); | ||
} | ||
|
||
#[test] | ||
fn test_onedrive_url() { | ||
let email_address = "[email protected]"; | ||
let expected = "https://acmecomputercompany-my.sharepoint.com/personal/lightmand_acmecomputercompany_com/_layouts/15/onedrive.aspx"; | ||
|
||
assert_eq!(expected, get_onedrive_url(email_address)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters