azure_storage/authorization/
mod.rs

1mod authorization_policy;
2
3pub(crate) use self::authorization_policy::AuthorizationPolicy;
4use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
5use async_lock::RwLock;
6use azure_core::{
7    auth::{Secret, TokenCredential},
8    error::{ErrorKind, ResultExt},
9    Url,
10};
11use std::{
12    mem::replace,
13    ops::{Deref, DerefMut},
14    sync::Arc,
15};
16
17/// Credentials for accessing a storage account.
18///
19/// # Example
20///
21/// The best way to create `StorageCredentials` is through use of one of the helper functions.
22///
23/// For example, to use an account name and access key:
24/// ```rust
25/// azure_storage::StorageCredentials::access_key("my_account", azure_core::auth::Secret::new("SOMEACCESSKEY"));
26/// ```
27#[derive(Clone)]
28pub struct StorageCredentials(pub Arc<RwLock<StorageCredentialsInner>>);
29
30#[derive(Clone)]
31pub enum StorageCredentialsInner {
32    Key(String, Secret),
33    SASToken(Vec<(String, String)>),
34    BearerToken(Secret),
35    TokenCredential(Arc<dyn TokenCredential>),
36    Anonymous,
37}
38
39impl StorageCredentials {
40    /// Create a new `StorageCredentials` from a `StorageCredentialsInner`
41    fn wrap(inner: StorageCredentialsInner) -> Self {
42        Self(Arc::new(RwLock::new(inner)))
43    }
44
45    /// Create an Access Key based credential
46    ///
47    /// When you create a storage account, Azure generates two 512-bit storage
48    /// account access keys for that account. These keys can be used to
49    /// authorize access to data in your storage account via Shared Key
50    /// authorization.
51    ///
52    /// ref: <https://docs.microsoft.com/azure/storage/common/storage-account-keys-manage>
53    pub fn access_key<A, K>(account: A, key: K) -> Self
54    where
55        A: Into<String>,
56        K: Into<Secret>,
57    {
58        Self::wrap(StorageCredentialsInner::Key(account.into(), key.into()))
59    }
60
61    /// Create a Shared Access Signature (SAS) token based credential
62    ///
63    /// SAS tokens are HTTP query strings that provide delegated access to
64    /// resources in a storage account with granular control over how the client
65    /// can access data in the account.
66    ///
67    /// * ref: [Grant limited access to Azure Storage resources using shared access signatures (SAS)](https://docs.microsoft.com/azure/storage/common/storage-sas-overview)
68    /// * ref: [Create SAS tokens for storage containers](https://docs.microsoft.com/azure/applied-ai-services/form-recognizer/create-sas-tokens)
69    pub fn sas_token<S>(token: S) -> azure_core::Result<Self>
70    where
71        S: AsRef<str>,
72    {
73        let params = get_sas_token_parms(token.as_ref())?;
74        Ok(Self::wrap(StorageCredentialsInner::SASToken(params)))
75    }
76
77    /// Create an Bearer Token based credential
78    ///
79    /// Azure Storage accepts OAuth 2.0 access tokens from the Azure AD tenant
80    /// associated with the subscription that contains the storage account.
81    ///
82    /// While `StorageCredentials::TokenCredential` is the preferred way to
83    /// manage access tokens, this method is provided for manual management of
84    /// Oauth2 tokens.
85    ///
86    /// ref: <https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory>
87    pub fn bearer_token<T>(token: T) -> Self
88    where
89        T: Into<Secret>,
90    {
91        Self::wrap(StorageCredentialsInner::BearerToken(token.into()))
92    }
93
94    /// Create a `TokenCredential` based credential
95    ///
96    /// Azure Storage accepts OAuth 2.0 access tokens from the Azure AD tenant
97    /// associated with the subscription that contains the storage account.
98    ///
99    /// Token Credentials can be created and automatically updated using
100    /// `azure_identity`.
101    ///
102    /// ```
103    /// use azure_storage::prelude::*;
104    /// let credential = azure_identity::create_credential().unwrap();
105    /// let storage_credentials = StorageCredentials::token_credential(credential);
106    /// ```
107    ///
108    /// ref: <https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory>
109    pub fn token_credential(credential: Arc<dyn TokenCredential>) -> Self {
110        Self::wrap(StorageCredentialsInner::TokenCredential(credential))
111    }
112
113    /// Create an anonymous credential
114    ///
115    /// Azure Storage supports optional anonymous public read access for
116    /// containers and blobs. By default, anonymous access to data in a storage
117    /// account data is not permitted. Unless anonymous access is explicitly
118    /// enabled, all requests to a container and its blobs must be authorized.
119    /// When a container's public access level setting is configured to permit
120    /// anonymous access, clients can read data in that container without
121    /// authorizing the request.
122    ///
123    /// ref: <https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-configure>
124    pub fn anonymous() -> Self {
125        Self::wrap(StorageCredentialsInner::Anonymous)
126    }
127
128    /// Create an Access Key credential for use with the Azure Storage emulator
129    pub fn emulator() -> Self {
130        Self::access_key(EMULATOR_ACCOUNT, Secret::new(EMULATOR_ACCOUNT_KEY))
131    }
132
133    /// Replace the current credentials with new credentials
134    ///
135    /// This method is useful for updating credentials that are used by multiple
136    /// clients at once.
137    pub async fn replace(&self, other: Self) -> azure_core::Result<()> {
138        if Arc::ptr_eq(&self.0, &other.0) {
139            return Ok(());
140        }
141
142        let mut creds = self.0.write().await;
143        let other = other.0.write().await;
144        let creds = creds.deref_mut();
145        let other = other.deref().clone();
146        let _old_creds = replace(creds, other);
147
148        Ok(())
149    }
150}
151
152impl std::fmt::Debug for StorageCredentials {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        let creds = self.0.try_read();
155
156        match creds.as_deref() {
157            None => f
158                .debug_struct("StorageCredentials")
159                .field("credential", &"locked")
160                .finish(),
161            Some(inner) => match &inner {
162                StorageCredentialsInner::Key(_, _) => f
163                    .debug_struct("StorageCredentials")
164                    .field("credential", &"Key")
165                    .finish(),
166                StorageCredentialsInner::SASToken(_) => f
167                    .debug_struct("StorageCredentials")
168                    .field("credential", &"SASToken")
169                    .finish(),
170                StorageCredentialsInner::BearerToken(_) => f
171                    .debug_struct("StorageCredentials")
172                    .field("credential", &"BearerToken")
173                    .finish(),
174                StorageCredentialsInner::TokenCredential(_) => f
175                    .debug_struct("StorageCredentials")
176                    .field("credential", &"TokenCredential")
177                    .finish(),
178                StorageCredentialsInner::Anonymous => f
179                    .debug_struct("StorageCredentials")
180                    .field("credential", &"Anonymous")
181                    .finish(),
182            },
183        }
184    }
185}
186
187impl From<Arc<dyn TokenCredential>> for StorageCredentials {
188    fn from(cred: Arc<dyn TokenCredential>) -> Self {
189        Self::token_credential(cred)
190    }
191}
192
193impl TryFrom<&Url> for StorageCredentials {
194    type Error = azure_core::Error;
195    fn try_from(value: &Url) -> Result<Self, Self::Error> {
196        match value.query() {
197            Some(query) => Self::sas_token(query),
198            None => Ok(Self::anonymous()),
199        }
200    }
201}
202
203fn get_sas_token_parms(sas_token: &str) -> azure_core::Result<Vec<(String, String)>> {
204    // Any base url will do: we just need to parse the SAS token
205    // to get its query pairs.
206    let base_url = Url::parse("https://blob.core.windows.net").unwrap();
207
208    let url = Url::options().base_url(Some(&base_url));
209
210    // this code handles the leading ?
211    // we support both with or without
212    let url = if sas_token.starts_with('?') {
213        url.parse(sas_token)
214    } else {
215        url.parse(&format!("?{sas_token}"))
216    }
217    .with_context(ErrorKind::DataConversion, || {
218        format!("failed to parse SAS token: {sas_token}")
219    })?;
220
221    Ok(url
222        .query_pairs()
223        .map(|p| (String::from(p.0), String::from(p.1)))
224        .collect())
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[tokio::test]
232    async fn test_replacement() -> azure_core::Result<()> {
233        let base = StorageCredentials::anonymous();
234        let other = StorageCredentials::bearer_token(Secret::new("foo"));
235
236        base.replace(other).await?;
237
238        // check that the value was updated
239        {
240            let inner = base.0.read().await;
241            let inner_locked = inner.deref();
242            assert!(
243                matches!(&inner_locked, &StorageCredentialsInner::BearerToken(value) if value.secret() == "foo")
244            );
245        }
246
247        // updating with the same StorageCredentials shouldn't deadlock
248        base.replace(base.clone()).await?;
249
250        Ok(())
251    }
252}