Skip to content

Commit

Permalink
Configurable tenant for authentication
Browse files Browse the repository at this point in the history
Applications accepting only Microsoft Personal accounts requires to use
the endpoint under "consumers" tenant, or it will fail unconditionally.

Closes #3.
  • Loading branch information
oxalica committed Mar 23, 2024
1 parent a5a9d60 commit e5d604f
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion onedrive-api-test/src/main.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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}");
Expand Down
1 change: 1 addition & 0 deletions onedrive-api-test/tests/real_test/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);

{
Expand Down
1 change: 1 addition & 0 deletions onedrive-api-test/tests/real_test/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 59 additions & 3 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,46 @@ impl Permission {
}
}

/// Control who can sign into the application.
///
/// It must match the target audience configuration of registered application.
///
/// See: <https://learn.microsoft.com/en-us/graph/auth-v2-user?tabs=http#parameters>
#[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:
/// <https://learn.microsoft.com/en-us/entra/identity-platform/supported-accounts-validation>
Common,
/// For work or school accounts only.
Organizations,
/// For Microsoft accounts only.
Consumers,
/// Tenant identifiers such as the tenant ID or domain name.
///
/// See: <https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints>
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
Expand All @@ -74,6 +114,7 @@ pub struct Auth {
client_id: String,
permission: Permission,
redirect_uri: String,
tenant: Tenant,
}

impl Auth {
Expand All @@ -83,8 +124,9 @@ impl Auth {
client_id: impl Into<String>,
permission: Permission,
redirect_uri: impl Into<String>,
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`.
Expand All @@ -95,12 +137,14 @@ impl Auth {
client_id: impl Into<String>,
permission: Permission,
redirect_uri: impl Into<String>,
tenant: Tenant,
) -> Self {
Self {
client,
client_id: client_id.into(),
permission,
redirect_uri: redirect_uri.into(),
tenant,
}
}

Expand All @@ -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()),
Expand Down Expand Up @@ -152,7 +205,10 @@ impl Auth {
) -> Result<TokenResponse> {
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?;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e5d604f

Please sign in to comment.