azure_storage/shared_access_signature/
account_sas.rs

1use crate::shared_access_signature::{format_date, SasProtocol, SasToken};
2use azure_core::{auth::Secret, hmac::hmac_sha256};
3use std::fmt;
4use time::OffsetDateTime;
5use url::form_urlencoded;
6
7/// Service version of the shared access signature ([Azure documentation](https://docs.microsoft.com/rest/api/storageservices/create-service-sas#specifying-the-signed-version-field)).
8#[derive(Copy, Clone, PartialEq, Eq, Debug)]
9pub enum AccountSasVersion {
10    V20181109,
11    V20150405,
12    V20130815,
13    V20120212,
14}
15
16impl fmt::Display for AccountSasVersion {
17    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18        match *self {
19            Self::V20181109 => write!(f, "2018-11-09"),
20            Self::V20150405 => write!(f, "2015-04-05"),
21            Self::V20130815 => write!(f, "2013-08-15"),
22            Self::V20120212 => write!(f, "2012-02-12"),
23        }
24    }
25}
26
27#[derive(Copy, Clone, PartialEq, Eq, Debug)]
28pub enum AccountSasService {
29    Blob,
30    Queue,
31    Table,
32    File,
33}
34
35impl fmt::Display for AccountSasService {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        match *self {
38            Self::Blob => write!(f, "b"),
39            Self::Queue => write!(f, "q"),
40            Self::Table => write!(f, "t"),
41            Self::File => write!(f, "f"),
42        }
43    }
44}
45
46/// Which resources are accessible via the shared access signature ([Azure documentation](https://docs.microsoft.com/rest/api/storageservices/create-service-sas#specifying-the-signed-resource-blob-service-only)).
47#[derive(Copy, Clone, PartialEq, Eq, Debug)]
48pub enum AccountSasResource {
49    Blob,
50    Queue,
51    Table,
52    File,
53}
54
55impl fmt::Display for AccountSasResource {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        match *self {
58            Self::Blob => write!(f, "b"),
59            Self::Queue => write!(f, "q"),
60            Self::Table => write!(f, "t"),
61            Self::File => write!(f, "f"),
62        }
63    }
64}
65
66#[derive(Copy, Clone, PartialEq, Eq, Debug)]
67pub enum AccountSasResourceType {
68    Service,
69    Container,
70    Object,
71}
72
73impl fmt::Display for AccountSasResourceType {
74    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75        match *self {
76            Self::Service => write!(f, "s"),
77            Self::Container => write!(f, "c"),
78            Self::Object => write!(f, "o"),
79        }
80    }
81}
82
83/// Indicate which operations a `key_client` may perform on the resource ([Azure documentation](https://docs.microsoft.com/rest/api/storageservices/create-service-sas#specifying-permissions)).
84#[allow(clippy::struct_excessive_bools)]
85#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
86pub struct AccountSasPermissions {
87    pub read: bool,
88    pub write: bool,
89    pub delete: bool,
90    pub list: bool,
91    pub add: bool,
92    pub create: bool,
93    pub update: bool,
94    pub process: bool,
95}
96
97impl fmt::Display for AccountSasPermissions {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        // NOTE: order *must* be `racwdxltmeop` per documentation:
100        // https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas#specifying-permissions
101
102        if self.read {
103            write!(f, "r")?;
104        }
105        if self.add {
106            write!(f, "a")?;
107        }
108        if self.create {
109            write!(f, "c")?;
110        }
111        if self.write {
112            write!(f, "w")?;
113        }
114        if self.delete {
115            write!(f, "d")?;
116        }
117        if self.list {
118            write!(f, "l")?;
119        }
120        if self.update {
121            write!(f, "u")?;
122        }
123        if self.process {
124            write!(f, "p")?;
125        }
126
127        Ok(())
128    }
129}
130
131#[derive(PartialEq, Eq, Debug)]
132pub struct AccountSharedAccessSignature {
133    account: String,
134    key: Secret,
135    version: AccountSasVersion,
136    resource: AccountSasResource,
137    resource_type: AccountSasResourceType,
138    expiry: OffsetDateTime,
139    permissions: AccountSasPermissions,
140    start: Option<OffsetDateTime>,
141    ip: Option<String>,
142    protocol: Option<SasProtocol>,
143}
144
145impl AccountSharedAccessSignature {
146    pub fn new(
147        account: String,
148        key: Secret,
149        resource: AccountSasResource,
150        resource_type: AccountSasResourceType,
151        expiry: OffsetDateTime,
152        permissions: AccountSasPermissions,
153    ) -> Self {
154        Self {
155            account,
156            key,
157            version: AccountSasVersion::V20181109,
158            resource,
159            resource_type,
160            expiry,
161            permissions,
162            start: None,
163            ip: None,
164            protocol: None,
165        }
166    }
167
168    setters! {
169        version: AccountSasVersion => version,
170        start: OffsetDateTime => Some(start),
171        ip: String => Some(ip),
172        protocol: SasProtocol => Some(protocol),
173    }
174
175    // Azure documentation: https://docs.microsoft.com/rest/api/storageservices/create-service-sas#constructing-the-signature-string
176    fn sign(&self) -> azure_core::Result<String> {
177        match self.version {
178            AccountSasVersion::V20181109 => {
179                let string_to_sign = format!(
180                    "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
181                    self.account,
182                    self.permissions,
183                    self.resource,
184                    self.resource_type,
185                    self.start.map_or(String::new(), format_date),
186                    format_date(self.expiry),
187                    self.ip.clone().unwrap_or_default(),
188                    self.protocol
189                        .as_ref()
190                        .map_or(String::new(), ToString::to_string),
191                    self.version,
192                );
193
194                hmac_sha256(&string_to_sign, &self.key)
195            }
196            _ => {
197                // TODO: support other version tags?
198                unimplemented!("Versions older than 2018-11-09 not supported");
199            }
200        }
201    }
202}
203
204impl SasToken for AccountSharedAccessSignature {
205    /// [Example](https://docs.microsoft.com/rest/api/storageservices/create-service-sas#service-sas-example) from Azure documentation.
206    fn token(&self) -> azure_core::Result<String> {
207        let mut form = form_urlencoded::Serializer::new(String::new());
208        form.extend_pairs(&[
209            ("sv", &self.version.to_string()),
210            ("ss", &self.resource.to_string()),
211            ("srt", &self.resource_type.to_string()),
212            ("se", &format_date(self.expiry)),
213            ("sp", &self.permissions.to_string()),
214        ]);
215
216        if let Some(start) = &self.start {
217            form.append_pair("st", &format_date(*start));
218        }
219        if let Some(ip) = &self.ip {
220            form.append_pair("sip", ip);
221        }
222        if let Some(protocol) = &self.protocol {
223            form.append_pair("spr", &protocol.to_string());
224        }
225        let sig = self.sign()?;
226        form.append_pair("sig", &sig);
227        Ok(form.finish())
228    }
229}