From e5d604f8ceaf9ad7179f6b88614a6c40a39788e2 Mon Sep 17 00:00:00 2001 From: oxalica Date: Sat, 23 Mar 2024 05:48:01 -0400 Subject: [PATCH] Configurable tenant for authentication Applications accepting only Microsoft Personal accounts requires to use the endpoint under "consumers" tenant, or it will fail unconditionally. Closes #3. --- CHANGELOG.md | 2 + onedrive-api-test/src/main.rs | 3 +- onedrive-api-test/tests/real_test/main.rs | 1 + onedrive-api-test/tests/real_test/util.rs | 1 + src/auth.rs | 62 +++++++++++++++++++++-- src/lib.rs | 2 +- 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d091c1d..4f5a422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### Added - `impl Clone for OneDrive`. +- Configurable `Tenant` for `Auth`, this allows login into applications that + accept only personal accounts. ### Changed diff --git a/onedrive-api-test/src/main.rs b/onedrive-api-test/src/main.rs index eedeb89..3e4cc97 100644 --- a/onedrive-api-test/src/main.rs +++ b/onedrive-api-test/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{ensure, Context as _, Result}; -use onedrive_api::{Auth, Permission}; +use onedrive_api::{Auth, Permission, Tenant}; use std::{ env, fs::File, @@ -65,6 +65,7 @@ async fn main() -> Result<()> { args.client_id.clone(), Permission::new_read().write(true).offline_access(true), args.redirect_uri.clone(), + Tenant::Consumers, ); let url = auth.code_auth_url(); eprintln!("Code auth url: {url}"); diff --git a/onedrive-api-test/tests/real_test/main.rs b/onedrive-api-test/tests/real_test/main.rs index 99fcf4b..6c4a9d6 100644 --- a/onedrive-api-test/tests/real_test/main.rs +++ b/onedrive-api-test/tests/real_test/main.rs @@ -620,6 +620,7 @@ async fn test_auth_error() { "11111111-2222-3333-4444-555555555555", Permission::new_read().offline_access(true), "https://login.microsoftonline.com/common/oauth2/nativeclient", + Tenant::Consumers, ); { diff --git a/onedrive-api-test/tests/real_test/util.rs b/onedrive-api-test/tests/real_test/util.rs index 50ecb09..32b4636 100644 --- a/onedrive-api-test/tests/real_test/util.rs +++ b/onedrive-api-test/tests/real_test/util.rs @@ -19,6 +19,7 @@ pub async fn get_logined_onedrive() -> OneDrive { env.client_id, Permission::new_read().write(true).offline_access(true), env.redirect_uri, + Tenant::Consumers, ); auth.login_with_refresh_token(&env.refresh_token, env.client_secret.as_deref()) .await diff --git a/src/auth.rs b/src/auth.rs index bc37484..7ab36b5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -64,6 +64,46 @@ impl Permission { } } +/// Control who can sign into the application. +/// +/// It must match the target audience configuration of registered application. +/// +/// See: +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Tenant { + /// For both Microsoft accounts and work or school accounts. + /// + /// # Notes + /// + /// This is only allowed for application with type `AzureADandPersonalMicrosoftAccount` + /// (Accounts in any organizational directory (Any Microsoft Entra directory - Multitenant) and + /// personal Microsoft accounts (e.g. Skype, Xbox)). If the coresponding application by + /// Client ID does not have this type, authentications will fail unconditionally. + /// + /// See: + /// + Common, + /// For work or school accounts only. + Organizations, + /// For Microsoft accounts only. + Consumers, + /// Tenant identifiers such as the tenant ID or domain name. + /// + /// See: + Issuer(String), +} + +impl Tenant { + fn to_issuer(&self) -> &str { + match self { + Tenant::Common => "common", + Tenant::Organizations => "organizations", + Tenant::Consumers => "consumers", + Tenant::Issuer(s) => s, + } + } +} + /// OAuth2 authentication and authorization basics for Microsoft Graph. /// /// # See also @@ -74,6 +114,7 @@ pub struct Auth { client_id: String, permission: Permission, redirect_uri: String, + tenant: Tenant, } impl Auth { @@ -83,8 +124,9 @@ impl Auth { client_id: impl Into, permission: Permission, redirect_uri: impl Into, + tenant: Tenant, ) -> Self { - Self::new_with_client(Client::new(), client_id, permission, redirect_uri) + Self::new_with_client(Client::new(), client_id, permission, redirect_uri, tenant) } /// Same as [`Auth::new`][auth_new] but with custom `reqwest::Client`. @@ -95,12 +137,14 @@ impl Auth { client_id: impl Into, permission: Permission, redirect_uri: impl Into, + tenant: Tenant, ) -> Self { Self { client, client_id: client_id.into(), permission, redirect_uri: redirect_uri.into(), + tenant, } } @@ -122,9 +166,18 @@ impl Auth { &self.redirect_uri } + /// Get the `tenant` used to create this instance. + #[must_use] + pub fn tenant(&self) -> &Tenant { + &self.tenant + } + fn auth_url(&self, response_type: &str) -> String { Url::parse_with_params( - "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + &format!( + "https://login.microsoftonline.com/{}/oauth2/v2.0/authorize", + self.tenant.to_issuer() + ), &[ ("client_id", &*self.client_id), ("scope", &self.permission.to_scope_string()), @@ -152,7 +205,10 @@ impl Auth { ) -> Result { let resp = self .client - .post("https://login.microsoftonline.com/common/oauth2/v2.0/token") + .post(format!( + "https://login.microsoftonline.com/{}/oauth2/v2.0/token", + self.tenant.to_issuer(), + )) .form(params) .send() .await?; diff --git a/src/lib.rs b/src/lib.rs index 60eb38d..bc620d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ pub mod resource; mod util; pub use self::{ - auth::{Auth, Permission, TokenResponse}, + auth::{Auth, Permission, Tenant, TokenResponse}, error::{Error, Result}, onedrive::{ CopyProgressMonitor, ListChildrenFetcher, OneDrive, TrackChangeFetcher, UploadSession,