cloud_file_signer/azure/
mod.rs

1//! An implementation of the [`CloudFileSigner`] trait for Azure Blob Storage.
2
3use std::time::{Duration, SystemTime};
4
5use azure_storage::prelude::*;
6use azure_storage_blobs::prelude::*;
7
8use crate::{CloudFileSigner, Permission, PresignedUrl, SignerError};
9mod uri;
10
11use self::uri::AzureUri;
12
13/// A signer for Azure Blob Storage.
14#[derive(Debug, Clone)]
15pub struct AbfsFileSigner {
16    storage_account: String,
17    client_builder: ClientBuilder,
18}
19
20impl AbfsFileSigner {
21    /// Create a new signer for Azure Blob Storage.
22    pub fn new<A: Into<String>, C: Into<StorageCredentials>>(
23        storage_account: A,
24        storage_credentials: C,
25    ) -> Self {
26        let storage_account_name = storage_account.into();
27        let client_builder = ClientBuilder::new(storage_account_name.clone(), storage_credentials);
28        Self {
29            storage_account: storage_account_name,
30            client_builder,
31        }
32    }
33
34    /// Create a new signer for Azure Blob Storage with specified client builder.
35    pub fn from_client_builder<A: Into<String>>(
36        storage_account: A,
37        client_builder: ClientBuilder,
38    ) -> Self {
39        let storage_account_name = storage_account.into();
40        Self {
41            storage_account: storage_account_name,
42            client_builder,
43        }
44    }
45
46    /// Return the name of the storage account for which this
47    /// signer is configured.
48    #[must_use]
49    pub fn storage_account(&self) -> &str {
50        &self.storage_account
51    }
52
53    fn client_builder(&self) -> ClientBuilder {
54        self.client_builder.clone()
55    }
56
57    async fn sign_read_request(
58        &self,
59        uri: &AzureUri,
60        expiration: Duration,
61    ) -> Result<PresignedUrl, SignerError> {
62        if uri.storage_account() != self.storage_account() {
63            return Err(SignerError::other_error(
64                "Storage account name in URI does not match signer",
65            ));
66        }
67
68        let valid_from = SystemTime::now();
69        let permissions = BlobSasPermissions {
70            read: true,
71            ..Default::default()
72        };
73        let expiry = time::OffsetDateTime::now_utc() + expiration;
74
75        let blob_client = self
76            .client_builder()
77            .blob_client(uri.container(), uri.blob());
78        let sas_token = blob_client
79            .shared_access_signature(permissions, expiry)
80            .await
81            .unwrap();
82        let sas_token = sas_token.start(time::OffsetDateTime::now_utc());
83
84        let signed_url = blob_client.generate_signed_blob_url(&sas_token).unwrap();
85        Ok(PresignedUrl::new(signed_url, valid_from, expiration))
86    }
87}
88
89#[async_trait::async_trait]
90impl CloudFileSigner for AbfsFileSigner {
91    async fn sign(
92        &self,
93        path: &str,
94        _valid_from: SystemTime,
95        expiration: Duration,
96        permission: Permission,
97    ) -> Result<PresignedUrl, SignerError> {
98        tracing::info!("signing path: {}", path);
99        let azure_uri = path.parse::<AzureUri>()?;
100        match permission {
101            Permission::Read => Ok(self.sign_read_request(&azure_uri, expiration).await?),
102            _ => Err(SignerError::permission_not_supported(format!(
103                "permission {permission:?} not supported"
104            ))),
105        }
106    }
107}