Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Url instead of strings #1478

Merged
merged 5 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 0 additions & 16 deletions .github/workflows/check-all-services.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tokio = {version="1.0", optional=true}
hmac = {version="0.12", optional=true}
sha2 = {version="0.10", optional=true}
openssl = {version="0.10", optional=true}
once_cell = "1.18"

# When target is `wasm32`, include `getrandom` and enable `wasm-bindgen` feature in `time`.
[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
55 changes: 47 additions & 8 deletions sdk/core/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
/// Endpoints for Azure Resource Manager in different Azure clouds
pub mod resource_manager_endpoint {
/// Azure Resource Manager China cloud endpoint
pub const AZURE_CHINA_CLOUD: &str = "https://management.chinacloudapi.cn";
static_url!(
/// Azure Resource Manager China cloud endpoint
AZURE_CHINA_CLOUD,
"https://management.chinacloudapi.cn"
);

/// Azure Resource Manager Germany cloud endpoint
pub const AZURE_GERMANY_CLOUD: &str = "https://management.microsoftazure.de";
static_url!(
/// Azure Resource Manager Germany cloud endpoint
AZURE_GERMANY_CLOUD,
"https://management.microsoftazure.de"
);

/// Azure Resource Manager public cloud endpoint
pub const AZURE_PUBLIC_CLOUD: &str = "https://management.azure.com";
static_url!(
/// Azure Resource Manager public cloud endpoint
AZURE_PUBLIC_CLOUD,
"https://management.azure.com"
);

/// Azure Resource Manager US government cloud endpoint
pub const AZURE_US_GOVERNMENT_CLOUD: &str = "https://management.usgovcloudapi.net";
static_url!(
/// Azure Resource Manager US government cloud endpoint
AZURE_US_GOVERNMENT_CLOUD,
"https://management.usgovcloudapi.net"
);
}

/// A list of known Azure authority hosts
pub mod authority_hosts {
static_url!(
/// China-based Azure Authority Host
AZURE_CHINA_CLOUD,
"https://login.chinacloudapi.cn"
);

static_url!(
/// Germany-based Azure Authority Host
AZURE_GERMANY_CLOUD,
"https://login.microsoftonline.de"
);

static_url!(
/// US Government Azure Authority Host
AZURE_US_GOVERNMENT_CLOUD,
"https://login.microsoftonline.us"
);

static_url!(
/// Public Cloud Azure Authority Host
AZURE_PUBLIC_CLOUD,
"https://login.microsoftonline.com"
);
}

/// Constants related to the Content-Type header
Expand Down
13 changes: 13 additions & 0 deletions sdk/core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,19 @@ macro_rules! create_enum {
)
}

// once Rust's `lazy_cell` feature lands, this should be replaced with that.
// ref: https://github.com/rust-lang/rust/issues/109736

#[macro_export]
macro_rules! static_url {
( $(#[$outer:meta])* $name:ident, $value:expr) => {
$(#[$outer])*
pub static $name: once_cell::sync::Lazy<$crate::Url> = once_cell::sync::Lazy::new(|| {
$crate::Url::parse($value).expect("hardcoded URL must parse")
});
};
}

#[cfg(test)]
mod test {
create_enum!(Colors, (Black, "Black"), (White, "White"), (Red, "Red"));
Expand Down
5 changes: 3 additions & 2 deletions sdk/identity/examples/federated_credential.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use azure_identity::{authority_hosts, federated_credentials_flow};
use azure_core::authority_hosts::AZURE_PUBLIC_CLOUD;
use azure_identity::federated_credentials_flow;
use std::{
env::{args, var},
error::Error,
Expand All @@ -23,7 +24,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
&token,
&["https://vault.azure.net/.default"],
&tenant_id,
authority_hosts::AZURE_PUBLIC_CLOUD,
&AZURE_PUBLIC_CLOUD,
)
.await
.expect("federated_credentials_flow failed");
Expand Down
11 changes: 6 additions & 5 deletions sdk/identity/src/federated_credentials_flow/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Authorize using the OAuth 2.0 client credentials flow with federated credentials.
//!
//! ```no_run
//! use azure_identity::{authority_hosts, federated_credentials_flow};
//! use azure_core::authority_hosts::AZURE_PUBLIC_CLOUD;
//! use azure_identity::{federated_credentials_flow};
//! use url::Url;
//!
//! use std::env;
Expand All @@ -24,8 +25,7 @@
//! &token,
//! &["https://management.azure.com/"],
//! &tenant_id,
//! authority_hosts::AZURE_PUBLIC_CLOUD.clone(),
//!
//! &AZURE_PUBLIC_CLOUD,
//! )
//! .await?;
//! Ok(())
Expand Down Expand Up @@ -54,7 +54,7 @@ pub async fn perform(
client_assertion: &str,
scopes: &[&str],
tenant_id: &str,
host: &str,
host: &Url,
) -> azure_core::Result<LoginResponse> {
let encoded: String = form_urlencoded::Serializer::new(String::new())
.append_pair("client_id", client_id)
Expand All @@ -67,7 +67,8 @@ pub async fn perform(
.append_pair("grant_type", "client_credentials")
.finish();

let url = Url::parse(&format!("{host}/{tenant_id}/oauth2/v2.0/token"))
let url = host
.join(&format!("/{tenant_id}/oauth2/v2.0/token"))
.with_context(ErrorKind::DataConversion, || {
format!("The supplied tenant id could not be url encoded: {tenant_id}")
})?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,20 @@ impl AzureauthCliCredential {
("azureauth", false)
};

let mut resource = resource.to_owned();
if !resource.ends_with("/.default") {
if resource.ends_with('/') {
resource.push_str(".default");
} else {
resource.push_str("/.default");
}
}

let mut cmd = Command::new(cmd_name);
cmd.args([
"aad",
"--scope",
&format!("{resource}/.default"),
&resource,
resource,
"--client",
self.client_id.as_str(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::authority_hosts;
use azure_core::{
auth::{AccessToken, Secret, TokenCredential},
authority_hosts::AZURE_PUBLIC_CLOUD,
base64, content_type,
error::{Error, ErrorKind},
headers, new_http_client, HttpClient, Method, Request,
Expand All @@ -26,35 +26,35 @@ const DEFAULT_REFRESH_TIME: i64 = 300;
/// requests to Azure Active Directory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateCredentialOptions {
authority_host: String,
authority_host: Url,
send_certificate_chain: bool,
}

impl Default for CertificateCredentialOptions {
fn default() -> Self {
Self {
authority_host: authority_hosts::AZURE_PUBLIC_CLOUD.to_owned(),
authority_host: AZURE_PUBLIC_CLOUD.to_owned(),
send_certificate_chain: false,
}
}
}

impl CertificateCredentialOptions {
/// Create a new `TokenCredentialsOptions`. default() may also be used.
pub fn new(authority_host: String, send_certificate_chain: bool) -> Self {
pub fn new(authority_host: Url, send_certificate_chain: bool) -> Self {
Self {
authority_host,
send_certificate_chain,
}
}
/// Set the authority host for authentication requests.
pub fn set_authority_host(&mut self, authority_host: String) {
pub fn set_authority_host(&mut self, authority_host: Url) {
self.authority_host = authority_host;
}

/// The authority host to use for authentication requests. The default is
/// <https://login.microsoftonline.com>.
pub fn authority_host(&self) -> &str {
pub fn authority_host(&self) -> &Url {
&self.authority_host
}

Expand Down Expand Up @@ -154,11 +154,10 @@ fn openssl_error(err: ErrorStack) -> azure_core::error::Error {
impl TokenCredential for ClientCertificateCredential {
async fn get_token(&self, resource: &str) -> azure_core::Result<AccessToken> {
let options = self.options();
let url = &format!(
"{}/{}/oauth2/v2.0/token",
options.authority_host(),
self.tenant_id
);

let url = options
.authority_host()
.join(&format!("{}/oauth2/v2.0/token", self.tenant_id))?;

let certificate = base64::decode(self.client_certificate.secret())
.map_err(|_| Error::message(ErrorKind::Credential, "Base64 decode failed"))?;
Expand Down Expand Up @@ -223,11 +222,20 @@ impl TokenCredential for ClientCertificateCredential {
let sig = ClientCertificateCredential::as_jwt_part(&signature);
let client_assertion = format!("{}.{}", jwt, sig);

let mut resource = resource.to_owned();
if !resource.ends_with("/.default") {
if resource.ends_with('/') {
resource.push_str(".default");
} else {
resource.push_str("/.default");
}
}

let encoded = {
let mut encoded = &mut form_urlencoded::Serializer::new(String::new());
encoded = encoded
.append_pair("client_id", self.client_id.as_str())
.append_pair("scope", format!("{}/.default", resource).as_str())
.append_pair("scope", &resource)
.append_pair(
"client_assertion_type",
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
Expand All @@ -237,7 +245,6 @@ impl TokenCredential for ClientCertificateCredential {
encoded.finish()
};

let url = Url::parse(url)?;
let mut req = Request::new(url, Method::Post);
req.insert_header(
headers::CONTENT_TYPE,
Expand Down
34 changes: 16 additions & 18 deletions sdk/identity/src/token_credentials/client_secret_credentials.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::oauth2_http_client::Oauth2HttpClient;
use azure_core::auth::{AccessToken, Secret, TokenCredential};
use azure_core::authority_hosts::AZURE_PUBLIC_CLOUD;
use azure_core::error::{ErrorKind, ResultExt};
use azure_core::HttpClient;
use oauth2::{basic::BasicClient, AuthType, AuthUrl, Scope, TokenUrl};
Expand All @@ -12,46 +13,34 @@ use url::Url;
/// requests to Azure Active Directory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TokenCredentialOptions {
authority_host: String,
authority_host: Url,
}

impl Default for TokenCredentialOptions {
fn default() -> Self {
Self {
authority_host: authority_hosts::AZURE_PUBLIC_CLOUD.to_owned(),
authority_host: AZURE_PUBLIC_CLOUD.to_owned(),
}
}
}

impl TokenCredentialOptions {
/// Create a new `TokenCredentialsOptions`. `default()` may also be used.
pub fn new(authority_host: String) -> Self {
pub fn new(authority_host: Url) -> Self {
Self { authority_host }
}
/// Set the authority host for authentication requests.
pub fn set_authority_host(&mut self, authority_host: String) {
pub fn set_authority_host(&mut self, authority_host: Url) {
self.authority_host = authority_host;
}

/// The authority host to use for authentication requests. The default is
/// `https://login.microsoftonline.com`.
pub fn authority_host(&self) -> &str {
pub fn authority_host(&self) -> &Url {
&self.authority_host
}
}

/// A list of known Azure authority hosts
pub mod authority_hosts {
/// China-based Azure Authority Host
pub const AZURE_CHINA: &str = "https://login.chinacloudapi.cn";
/// Germany-based Azure Authority Host
pub const AZURE_GERMANY: &str = "https://login.microsoftonline.de";
/// US Government Azure Authority Host
pub const AZURE_GOVERNMENT: &str = "https://login.microsoftonline.us";
/// Public Cloud Azure Authority Host
pub const AZURE_PUBLIC_CLOUD: &str = "https://login.microsoftonline.com";
}

/// A list of tenant IDs
pub mod tenant_ids {
/// The tenant ID for multi-tenant apps
Expand Down Expand Up @@ -139,10 +128,19 @@ impl TokenCredential for ClientSecretCredential {
)
.set_auth_type(AuthType::RequestBody);

let mut resource = resource.to_owned();
if !resource.ends_with("/.default") {
if resource.ends_with('/') {
resource.push_str(".default");
} else {
resource.push_str("/.default");
}
}

let oauth_http_client = Oauth2HttpClient::new(self.http_client.clone());
let token_result = client
.exchange_client_credentials()
.add_scope(Scope::new(format!("{resource}/.default")))
.add_scope(Scope::new(resource))
.request_async(|request| oauth_http_client.request(request))
.await
.map(|r| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use azure_core::auth::{AccessToken, TokenCredential};
use azure_core::error::{Error, ErrorKind, ResultExt};
use azure_core::HttpClient;
use std::sync::Arc;
use url::Url;

const AZURE_TENANT_ID_ENV_KEY: &str = "AZURE_TENANT_ID";
const AZURE_CLIENT_ID_ENV_KEY: &str = "AZURE_CLIENT_ID";
Expand Down Expand Up @@ -79,7 +80,7 @@ impl TokenCredential for EnvironmentCredential {
let authority_host = std::env::var(AZURE_AUTHORITY_HOST);

let options: TokenCredentialOptions = if let Ok(authority_host) = authority_host {
TokenCredentialOptions::new(authority_host)
TokenCredentialOptions::new(Url::parse(&authority_host)?)
} else {
self.options.clone()
};
Expand Down
Loading