azure_storage/authorization/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
mod authorization_policy;

pub(crate) use self::authorization_policy::AuthorizationPolicy;
use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
use async_lock::RwLock;
use azure_core::{
    auth::{Secret, TokenCredential},
    error::{ErrorKind, ResultExt},
    Url,
};
use std::{
    mem::replace,
    ops::{Deref, DerefMut},
    sync::Arc,
};

/// Credentials for accessing a storage account.
///
/// # Example
///
/// The best way to create `StorageCredentials` is through use of one of the helper functions.
///
/// For example, to use an account name and access key:
/// ```rust
/// azure_storage::StorageCredentials::access_key("my_account", azure_core::auth::Secret::new("SOMEACCESSKEY"));
/// ```
#[derive(Clone)]
pub struct StorageCredentials(pub Arc<RwLock<StorageCredentialsInner>>);

#[derive(Clone)]
pub enum StorageCredentialsInner {
    Key(String, Secret),
    SASToken(Vec<(String, String)>),
    BearerToken(Secret),
    TokenCredential(Arc<dyn TokenCredential>),
    Anonymous,
}

impl StorageCredentials {
    /// Create a new `StorageCredentials` from a `StorageCredentialsInner`
    fn wrap(inner: StorageCredentialsInner) -> Self {
        Self(Arc::new(RwLock::new(inner)))
    }

    /// Create an Access Key based credential
    ///
    /// When you create a storage account, Azure generates two 512-bit storage
    /// account access keys for that account. These keys can be used to
    /// authorize access to data in your storage account via Shared Key
    /// authorization.
    ///
    /// ref: <https://docs.microsoft.com/azure/storage/common/storage-account-keys-manage>
    pub fn access_key<A, K>(account: A, key: K) -> Self
    where
        A: Into<String>,
        K: Into<Secret>,
    {
        Self::wrap(StorageCredentialsInner::Key(account.into(), key.into()))
    }

    /// Create a Shared Access Signature (SAS) token based credential
    ///
    /// SAS tokens are HTTP query strings that provide delegated access to
    /// resources in a storage account with granular control over how the client
    /// can access data in the account.
    ///
    /// * ref: [Grant limited access to Azure Storage resources using shared access signatures (SAS)](https://docs.microsoft.com/azure/storage/common/storage-sas-overview)
    /// * ref: [Create SAS tokens for storage containers](https://docs.microsoft.com/azure/applied-ai-services/form-recognizer/create-sas-tokens)
    pub fn sas_token<S>(token: S) -> azure_core::Result<Self>
    where
        S: AsRef<str>,
    {
        let params = get_sas_token_parms(token.as_ref())?;
        Ok(Self::wrap(StorageCredentialsInner::SASToken(params)))
    }

    /// Create an Bearer Token based credential
    ///
    /// Azure Storage accepts OAuth 2.0 access tokens from the Azure AD tenant
    /// associated with the subscription that contains the storage account.
    ///
    /// While `StorageCredentials::TokenCredential` is the preferred way to
    /// manage access tokens, this method is provided for manual management of
    /// Oauth2 tokens.
    ///
    /// ref: <https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory>
    pub fn bearer_token<T>(token: T) -> Self
    where
        T: Into<Secret>,
    {
        Self::wrap(StorageCredentialsInner::BearerToken(token.into()))
    }

    /// Create a `TokenCredential` based credential
    ///
    /// Azure Storage accepts OAuth 2.0 access tokens from the Azure AD tenant
    /// associated with the subscription that contains the storage account.
    ///
    /// Token Credentials can be created and automatically updated using
    /// `azure_identity`.
    ///
    /// ```
    /// use azure_storage::prelude::*;
    /// let credential = azure_identity::create_credential().unwrap();
    /// let storage_credentials = StorageCredentials::token_credential(credential);
    /// ```
    ///
    /// ref: <https://docs.microsoft.com/rest/api/storageservices/authorize-with-azure-active-directory>
    pub fn token_credential(credential: Arc<dyn TokenCredential>) -> Self {
        Self::wrap(StorageCredentialsInner::TokenCredential(credential))
    }

    /// Create an anonymous credential
    ///
    /// Azure Storage supports optional anonymous public read access for
    /// containers and blobs. By default, anonymous access to data in a storage
    /// account data is not permitted. Unless anonymous access is explicitly
    /// enabled, all requests to a container and its blobs must be authorized.
    /// When a container's public access level setting is configured to permit
    /// anonymous access, clients can read data in that container without
    /// authorizing the request.
    ///
    /// ref: <https://docs.microsoft.com/azure/storage/blobs/anonymous-read-access-configure>
    pub fn anonymous() -> Self {
        Self::wrap(StorageCredentialsInner::Anonymous)
    }

    /// Create an Access Key credential for use with the Azure Storage emulator
    pub fn emulator() -> Self {
        Self::access_key(EMULATOR_ACCOUNT, Secret::new(EMULATOR_ACCOUNT_KEY))
    }

    /// Replace the current credentials with new credentials
    ///
    /// This method is useful for updating credentials that are used by multiple
    /// clients at once.
    pub async fn replace(&self, other: Self) -> azure_core::Result<()> {
        if Arc::ptr_eq(&self.0, &other.0) {
            return Ok(());
        }

        let mut creds = self.0.write().await;
        let other = other.0.write().await;
        let creds = creds.deref_mut();
        let other = other.deref().clone();
        let _old_creds = replace(creds, other);

        Ok(())
    }
}

impl std::fmt::Debug for StorageCredentials {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let creds = self.0.try_read();

        match creds.as_deref() {
            None => f
                .debug_struct("StorageCredentials")
                .field("credential", &"locked")
                .finish(),
            Some(inner) => match &inner {
                StorageCredentialsInner::Key(_, _) => f
                    .debug_struct("StorageCredentials")
                    .field("credential", &"Key")
                    .finish(),
                StorageCredentialsInner::SASToken(_) => f
                    .debug_struct("StorageCredentials")
                    .field("credential", &"SASToken")
                    .finish(),
                StorageCredentialsInner::BearerToken(_) => f
                    .debug_struct("StorageCredentials")
                    .field("credential", &"BearerToken")
                    .finish(),
                StorageCredentialsInner::TokenCredential(_) => f
                    .debug_struct("StorageCredentials")
                    .field("credential", &"TokenCredential")
                    .finish(),
                StorageCredentialsInner::Anonymous => f
                    .debug_struct("StorageCredentials")
                    .field("credential", &"Anonymous")
                    .finish(),
            },
        }
    }
}

impl From<Arc<dyn TokenCredential>> for StorageCredentials {
    fn from(cred: Arc<dyn TokenCredential>) -> Self {
        Self::token_credential(cred)
    }
}

impl TryFrom<&Url> for StorageCredentials {
    type Error = azure_core::Error;
    fn try_from(value: &Url) -> Result<Self, Self::Error> {
        match value.query() {
            Some(query) => Self::sas_token(query),
            None => Ok(Self::anonymous()),
        }
    }
}

fn get_sas_token_parms(sas_token: &str) -> azure_core::Result<Vec<(String, String)>> {
    // Any base url will do: we just need to parse the SAS token
    // to get its query pairs.
    let base_url = Url::parse("https://blob.core.windows.net").unwrap();

    let url = Url::options().base_url(Some(&base_url));

    // this code handles the leading ?
    // we support both with or without
    let url = if sas_token.starts_with('?') {
        url.parse(sas_token)
    } else {
        url.parse(&format!("?{sas_token}"))
    }
    .with_context(ErrorKind::DataConversion, || {
        format!("failed to parse SAS token: {sas_token}")
    })?;

    Ok(url
        .query_pairs()
        .map(|p| (String::from(p.0), String::from(p.1)))
        .collect())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_replacement() -> azure_core::Result<()> {
        let base = StorageCredentials::anonymous();
        let other = StorageCredentials::bearer_token(Secret::new("foo"));

        base.replace(other).await?;

        // check that the value was updated
        {
            let inner = base.0.read().await;
            let inner_locked = inner.deref();
            assert!(
                matches!(&inner_locked, &StorageCredentialsInner::BearerToken(value) if value.secret() == "foo")
            );
        }

        // updating with the same StorageCredentials shouldn't deadlock
        base.replace(base.clone()).await?;

        Ok(())
    }
}