pub const APP_CLIENT_ID: &str = "e49f90dc-c265-4392-b62f-b26704f9088f";
pub const SCOPES: &[&str] = &[
"openid",
"profile",
"offline_access",
"User.Read",
"Mail.ReadWrite",
"Mail.Send",
"Calendars.ReadWrite",
"MailboxSettings.ReadWrite",
];
pub const LATER_SCOPES: &[&str] = &["MailboxSettings.ReadWrite"];
pub const AUTHORITY: &str = "https://login.microsoftonline.com/common";
pub const DEVICE_CODE_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode";
pub const TOKEN_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
pub const GRAPH_BASE: &str = "https://graph.microsoft.com/v1.0";
pub fn client_id() -> Option<String> {
if let Ok(v) = std::env::var("PIDGE_CLIENT_ID")
&& !v.is_empty()
{
return Some(v);
}
if APP_CLIENT_ID.is_empty() {
None
} else {
Some(APP_CLIENT_ID.to_string())
}
}
pub fn scope_string() -> String {
SCOPES.join(" ")
}
pub fn grants(granted: &str, scope: &str) -> bool {
granted.split_whitespace().any(|g| {
g.rsplit('/')
.next()
.unwrap_or(g)
.eq_ignore_ascii_case(scope)
})
}
pub fn refresh_scope_string(requested: &str, granted: Option<&str>) -> String {
requested
.split_whitespace()
.filter(|s| {
!LATER_SCOPES.iter().any(|l| l.eq_ignore_ascii_case(s))
|| granted.is_some_and(|g| grants(g, s))
})
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grants_reads_microsofts_scope_spellings() {
let g = "openid https://graph.microsoft.com/Mail.ReadWrite https://graph.microsoft.com/MailboxSettings.ReadWrite";
assert!(grants(g, "MailboxSettings.ReadWrite"));
assert!(grants(
"mailboxsettings.readwrite",
"MailboxSettings.ReadWrite"
));
assert!(!grants(g, "Calendars.ReadWrite"));
}
#[test]
fn a_refresh_never_asks_for_a_later_scope_the_token_was_not_granted() {
let all = scope_string();
assert!(all.contains("MailboxSettings.ReadWrite"));
let old = refresh_scope_string(&all, None);
assert!(!old.contains("MailboxSettings"), "{old}");
assert!(old.contains("Mail.ReadWrite") && old.contains("offline_access"));
let old = refresh_scope_string(&all, Some("User.Read Mail.ReadWrite Mail.Send"));
assert!(!old.contains("MailboxSettings"), "{old}");
let new = refresh_scope_string(
&all,
Some("https://graph.microsoft.com/MailboxSettings.ReadWrite"),
);
assert_eq!(new, all);
}
}