Skip to content

Commit 93f069e

Browse files
authored
enable updating current credentials (#1424)
This PR allows replacing credential details for existing StorageCredential instances. This allows clients to update the credentials as runtime, as seen in other SDKs. Ref: azure-sdk-for-net's [AzureSasCredential.Update](https://learn.microsoft.com/en-us/dotnet/api/azure.azuresascredential.update?view=azure-dotnet#azure-azuresascredential-update(system-string))
1 parent d607009 commit 93f069e

File tree

9 files changed

+234
-132
lines changed

9 files changed

+234
-132
lines changed

sdk/storage/src/authorization/authorization_policy.rs

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use crate::clients::ServiceType;
2-
use crate::StorageCredentials;
3-
use azure_core::error::{ErrorKind, ResultExt};
4-
use azure_core::Method;
5-
use azure_core::{headers::*, Context, Policy, PolicyResult, Request};
6-
use std::borrow::Cow;
7-
use std::sync::Arc;
1+
use crate::{clients::ServiceType, StorageCredentials, StorageCredentialsInner};
2+
use azure_core::{
3+
error::{ErrorKind, ResultExt},
4+
headers::*,
5+
Context, Method, Policy, PolicyResult, Request,
6+
};
7+
use std::{borrow::Cow, ops::Deref, sync::Arc};
88
use url::Url;
99

1010
const STORAGE_TOKEN_SCOPE: &str = "https://storage.azure.com/";
@@ -35,49 +35,51 @@ impl Policy for AuthorizationPolicy {
3535
!next.is_empty(),
3636
"Authorization policies cannot be the last policy of a pipeline"
3737
);
38-
let request = match &self.credentials {
39-
StorageCredentials::Key(account, key) => {
40-
if !request.url().query_pairs().any(|(k, _)| &*k == "sig") {
41-
let auth = generate_authorization(
42-
request.headers(),
43-
request.url(),
44-
*request.method(),
45-
account,
46-
key,
47-
*ctx.get()
48-
.expect("ServiceType must be in the Context at this point"),
49-
)?;
50-
request.insert_header(AUTHORIZATION, auth);
38+
39+
// lock the credentials within a scope so that it is released as soon as possible
40+
{
41+
let creds = self.credentials.0.lock().await;
42+
43+
match creds.deref() {
44+
StorageCredentialsInner::Key(account, key) => {
45+
if !request.url().query_pairs().any(|(k, _)| &*k == "sig") {
46+
let auth = generate_authorization(
47+
request.headers(),
48+
request.url(),
49+
*request.method(),
50+
account,
51+
key,
52+
*ctx.get()
53+
.expect("ServiceType must be in the Context at this point"),
54+
)?;
55+
request.insert_header(AUTHORIZATION, auth);
56+
}
5157
}
52-
request
53-
}
54-
StorageCredentials::SASToken(query_pairs) => {
55-
// Ensure the signature param is not already present
56-
if !request.url().query_pairs().any(|(k, _)| &*k == "sig") {
57-
request
58-
.url_mut()
59-
.query_pairs_mut()
60-
.extend_pairs(query_pairs);
58+
StorageCredentialsInner::SASToken(query_pairs) => {
59+
// Ensure the signature param is not already present
60+
if !request.url().query_pairs().any(|(k, _)| &*k == "sig") {
61+
request
62+
.url_mut()
63+
.query_pairs_mut()
64+
.extend_pairs(query_pairs);
65+
}
6166
}
62-
request
63-
}
64-
StorageCredentials::BearerToken(token) => {
65-
request.insert_header(AUTHORIZATION, format!("Bearer {token}"));
66-
request
67-
}
68-
StorageCredentials::TokenCredential(token_credential) => {
69-
let bearer_token = token_credential
70-
.get_token(STORAGE_TOKEN_SCOPE)
71-
.await
72-
.context(ErrorKind::Credential, "failed to get bearer token")?;
73-
74-
request.insert_header(
75-
AUTHORIZATION,
76-
format!("Bearer {}", bearer_token.token.secret()),
77-
);
78-
request
67+
StorageCredentialsInner::BearerToken(token) => {
68+
request.insert_header(AUTHORIZATION, format!("Bearer {token}"));
69+
}
70+
StorageCredentialsInner::TokenCredential(token_credential) => {
71+
let bearer_token = token_credential
72+
.get_token(STORAGE_TOKEN_SCOPE)
73+
.await
74+
.context(ErrorKind::Credential, "failed to get bearer token")?;
75+
76+
request.insert_header(
77+
AUTHORIZATION,
78+
format!("Bearer {}", bearer_token.token.secret()),
79+
);
80+
}
81+
StorageCredentialsInner::Anonymous => {}
7982
}
80-
StorageCredentials::Anonymous => request,
8183
};
8284

8385
next[0].send(ctx, request, &next[1..]).await

sdk/storage/src/authorization/mod.rs

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
mod authorization_policy;
22

3+
pub(crate) use self::authorization_policy::AuthorizationPolicy;
4+
use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
35
use azure_core::{
46
auth::TokenCredential,
57
error::{ErrorKind, ResultExt},
68
};
7-
use std::sync::Arc;
9+
use futures::lock::Mutex;
10+
use std::{
11+
mem::replace,
12+
ops::{Deref, DerefMut},
13+
sync::Arc,
14+
};
815
use url::Url;
916

10-
pub(crate) use authorization_policy::AuthorizationPolicy;
11-
12-
use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
13-
1417
/// Credentials for accessing a storage account.
1518
///
1619
/// # Example
@@ -22,7 +25,10 @@ use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
2225
/// azure_storage::StorageCredentials::access_key("my_account", "SOMEACCESSKEY");
2326
/// ```
2427
#[derive(Clone)]
25-
pub enum StorageCredentials {
28+
pub struct StorageCredentials(pub Arc<Mutex<StorageCredentialsInner>>);
29+
30+
#[derive(Clone)]
31+
pub enum StorageCredentialsInner {
2632
Key(String, String),
2733
SASToken(Vec<(String, String)>),
2834
BearerToken(String),
@@ -31,6 +37,11 @@ pub enum StorageCredentials {
3137
}
3238

3339
impl StorageCredentials {
40+
/// Create a new `StorageCredentials` from a `StorageCredentialsInner`
41+
fn wrap(inner: StorageCredentialsInner) -> Self {
42+
Self(Arc::new(Mutex::new(inner)))
43+
}
44+
3445
/// Create an Access Key based credential
3546
///
3647
/// When you create a storage account, Azure generates two 512-bit storage
@@ -44,7 +55,7 @@ impl StorageCredentials {
4455
A: Into<String>,
4556
K: Into<String>,
4657
{
47-
Self::Key(account.into(), key.into())
58+
Self::wrap(StorageCredentialsInner::Key(account.into(), key.into()))
4859
}
4960

5061
/// Create a Shared Access Signature (SAS) token based credential
@@ -60,7 +71,7 @@ impl StorageCredentials {
6071
S: AsRef<str>,
6172
{
6273
let params = get_sas_token_parms(token.as_ref())?;
63-
Ok(Self::SASToken(params))
74+
Ok(Self::wrap(StorageCredentialsInner::SASToken(params)))
6475
}
6576

6677
/// Create an Bearer Token based credential
@@ -77,7 +88,7 @@ impl StorageCredentials {
7788
where
7889
T: Into<String>,
7990
{
80-
Self::BearerToken(token.into())
91+
Self::wrap(StorageCredentialsInner::BearerToken(token.into()))
8192
}
8293

8394
/// Create a `TokenCredential` based credential
@@ -98,7 +109,7 @@ impl StorageCredentials {
98109
///
99110
/// ref: <https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory>
100111
pub fn token_credential(credential: Arc<dyn TokenCredential>) -> Self {
101-
Self::TokenCredential(credential)
112+
Self::wrap(StorageCredentialsInner::TokenCredential(credential))
102113
}
103114

104115
/// Create an anonymous credential
@@ -113,45 +124,71 @@ impl StorageCredentials {
113124
///
114125
/// ref: <https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-configure>
115126
pub fn anonymous() -> Self {
116-
Self::Anonymous
127+
Self::wrap(StorageCredentialsInner::Anonymous)
117128
}
118129

119130
/// Create an Access Key credential for use with the Azure Storage emulator
120131
pub fn emulator() -> Self {
121132
Self::access_key(EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY)
122133
}
134+
135+
/// Replace the current credentials with new credentials
136+
///
137+
/// This method is useful for updating credentials that are used by multiple
138+
/// clients at once.
139+
pub async fn replace(&self, other: Self) -> azure_core::Result<()> {
140+
if Arc::ptr_eq(&self.0, &other.0) {
141+
return Ok(());
142+
}
143+
144+
let mut creds = self.0.lock().await;
145+
let other = other.0.lock().await;
146+
let creds = creds.deref_mut();
147+
let other = other.deref().clone();
148+
let _old_creds = replace(creds, other);
149+
150+
Ok(())
151+
}
123152
}
124153

125154
impl std::fmt::Debug for StorageCredentials {
126155
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127-
match &self {
128-
StorageCredentials::Key(_, _) => f
129-
.debug_struct("StorageCredentials")
130-
.field("credential", &"Key")
131-
.finish(),
132-
StorageCredentials::SASToken(_) => f
133-
.debug_struct("StorageCredentials")
134-
.field("credential", &"SASToken")
135-
.finish(),
136-
StorageCredentials::BearerToken(_) => f
137-
.debug_struct("StorageCredentials")
138-
.field("credential", &"BearerToken")
139-
.finish(),
140-
StorageCredentials::TokenCredential(_) => f
141-
.debug_struct("StorageCredentials")
142-
.field("credential", &"TokenCredential")
143-
.finish(),
144-
StorageCredentials::Anonymous => f
156+
let creds = self.0.try_lock();
157+
158+
match creds.as_deref() {
159+
None => f
145160
.debug_struct("StorageCredentials")
146-
.field("credential", &"Anonymous")
161+
.field("credential", &"locked")
147162
.finish(),
163+
Some(inner) => match &inner {
164+
StorageCredentialsInner::Key(_, _) => f
165+
.debug_struct("StorageCredentials")
166+
.field("credential", &"Key")
167+
.finish(),
168+
StorageCredentialsInner::SASToken(_) => f
169+
.debug_struct("StorageCredentials")
170+
.field("credential", &"SASToken")
171+
.finish(),
172+
StorageCredentialsInner::BearerToken(_) => f
173+
.debug_struct("StorageCredentials")
174+
.field("credential", &"BearerToken")
175+
.finish(),
176+
StorageCredentialsInner::TokenCredential(_) => f
177+
.debug_struct("StorageCredentials")
178+
.field("credential", &"TokenCredential")
179+
.finish(),
180+
StorageCredentialsInner::Anonymous => f
181+
.debug_struct("StorageCredentials")
182+
.field("credential", &"Anonymous")
183+
.finish(),
184+
},
148185
}
149186
}
150187
}
151188

152189
impl From<Arc<dyn TokenCredential>> for StorageCredentials {
153190
fn from(cred: Arc<dyn TokenCredential>) -> Self {
154-
Self::TokenCredential(cred)
191+
Self::token_credential(cred)
155192
}
156193
}
157194

@@ -160,7 +197,7 @@ impl TryFrom<&Url> for StorageCredentials {
160197
fn try_from(value: &Url) -> Result<Self, Self::Error> {
161198
match value.query() {
162199
Some(query) => Self::sas_token(query),
163-
None => Ok(Self::Anonymous),
200+
None => Ok(Self::anonymous()),
164201
}
165202
}
166203
}
@@ -188,3 +225,30 @@ fn get_sas_token_parms(sas_token: &str) -> azure_core::Result<Vec<(String, Strin
188225
.map(|p| (String::from(p.0), String::from(p.1)))
189226
.collect())
190227
}
228+
229+
#[cfg(test)]
230+
mod tests {
231+
use super::*;
232+
233+
#[tokio::test]
234+
async fn test_replacement() -> azure_core::Result<()> {
235+
let base = StorageCredentials::anonymous();
236+
let other = StorageCredentials::bearer_token("foo");
237+
238+
base.replace(other).await?;
239+
240+
// check that the value was updated
241+
{
242+
let inner = base.0.lock().await;
243+
let inner_locked = inner.deref();
244+
assert!(
245+
matches!(&inner_locked, &StorageCredentialsInner::BearerToken(value) if value == "foo")
246+
);
247+
}
248+
249+
// updating with the same StorageCredentials shouldn't deadlock
250+
base.replace(base.clone()).await?;
251+
252+
Ok(())
253+
}
254+
}

sdk/storage/src/clients.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
use crate::authorization::AuthorizationPolicy;
2-
use crate::shared_access_signature::account_sas::{
3-
AccountSasPermissions, AccountSasResource, AccountSasResourceType, AccountSharedAccessSignature,
1+
use crate::{
2+
authorization::{AuthorizationPolicy, StorageCredentialsInner},
3+
shared_access_signature::account_sas::{
4+
AccountSasPermissions, AccountSasResource, AccountSasResourceType,
5+
AccountSharedAccessSignature,
6+
},
7+
StorageCredentials,
48
};
5-
use crate::StorageCredentials;
6-
use azure_core::date;
79
use azure_core::{
10+
date,
811
error::{Error, ErrorKind},
912
headers::*,
1013
Body, ClientOptions, Method, Pipeline, Request,
1114
};
12-
use std::sync::Arc;
15+
use std::{ops::Deref, sync::Arc};
1316
use time::OffsetDateTime;
1417
use url::Url;
1518

@@ -44,19 +47,29 @@ impl ServiceType {
4447
}
4548
}
4649

47-
pub fn shared_access_signature(
50+
pub async fn shared_access_signature(
4851
storage_credentials: &StorageCredentials,
4952
resource: AccountSasResource,
5053
resource_type: AccountSasResourceType,
5154
expiry: OffsetDateTime,
5255
permissions: AccountSasPermissions,
5356
) -> Result<AccountSharedAccessSignature, Error> {
54-
match storage_credentials {
55-
StorageCredentials::Key(account, key) => {
56-
Ok(AccountSharedAccessSignature::new(account.clone(), key.clone(), resource, resource_type, expiry, permissions))
57-
}
58-
_ => Err(Error::message(ErrorKind::Credential, "failed shared access signature generation. SAS can be generated only from key and account clients")),
59-
}
57+
let creds = storage_credentials.0.lock().await;
58+
let StorageCredentialsInner::Key(account, key) = creds.deref() else {
59+
return Err(Error::message(
60+
ErrorKind::Credential,
61+
"Shared access signature generation - SAS can be generated with access_key clients",
62+
));
63+
};
64+
65+
Ok(AccountSharedAccessSignature::new(
66+
account.clone(),
67+
key.clone(),
68+
resource,
69+
resource_type,
70+
expiry,
71+
permissions,
72+
))
6073
}
6174

6275
pub fn finalize_request(

sdk/storage/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub mod shared_access_signature;
3939

4040
pub use self::connection_string::{ConnectionString, EndpointProtocol};
4141
pub use self::connection_string_builder::ConnectionStringBuilder;
42-
pub use authorization::StorageCredentials;
42+
pub use authorization::{StorageCredentials, StorageCredentialsInner};
4343
pub use cloud_location::*;
4444
pub mod headers;
4545
pub use copy_id::{copy_id_from_headers, CopyId};

0 commit comments

Comments
 (0)