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}