Skip to content

Commit 91eeb4e

Browse files
authored
support account and service level SAS signatures (#433)
1 parent ddc4d0b commit 91eeb4e

File tree

9 files changed

+656
-178
lines changed

9 files changed

+656
-178
lines changed

sdk/storage/examples/copy_blob.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
5757
let later = now + Duration::hours(1);
5858
let sas = source_storage_account_client
5959
.shared_access_signature()?
60-
.with_resource(SasResource::Blob)
61-
.with_resource_type(SasResourceType::Object)
60+
.with_resource(AccountSasResource::Blob)
61+
.with_resource_type(AccountSasResourceType::Object)
6262
.with_start(now)
6363
.with_expiry(later)
64-
.with_permissions(SasPermissions {
64+
.with_permissions(AccountSasPermissions {
6565
read: true,
6666
..Default::default()
6767
})

sdk/storage/examples/shared_access_signature.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,67 @@ fn code() -> Result<(), Box<dyn Error + Sync + Send>> {
2323
.nth(2)
2424
.expect("please specify blob name as command line parameter");
2525

26+
// allow for some time skew
27+
let now = Utc::now() - Duration::minutes(15);
28+
let later = now + Duration::hours(1);
29+
2630
let http_client = new_http_client();
2731

2832
let storage_account_client =
2933
StorageAccountClient::new_access_key(http_client.clone(), &account, &master_key);
30-
let blob = storage_account_client
34+
35+
let container_client = storage_account_client
3136
.as_storage_client()
32-
.as_container_client(&container_name)
33-
.as_blob_client(&blob_name);
37+
.as_container_client(&container_name);
38+
39+
let blob_client = container_client.as_blob_client(&blob_name);
3440

35-
let now = Utc::now();
36-
let later = now + Duration::hours(1);
3741
let sas = storage_account_client
3842
.shared_access_signature()?
39-
.with_resource(SasResource::Blob)
40-
.with_resource_type(SasResourceType::Object)
43+
.with_resource(AccountSasResource::Blob)
44+
.with_resource_type(AccountSasResourceType::Object)
4145
.with_start(now)
4246
.with_expiry(later)
43-
.with_permissions(SasPermissions {
47+
.with_permissions(AccountSasPermissions {
48+
read: true,
49+
..Default::default()
50+
})
51+
.with_protocol(SasProtocol::Https)
52+
.finalize();
53+
54+
println!("blob account level token: '{}'", sas.token());
55+
let url = blob_client.generate_signed_blob_url(&sas)?;
56+
println!("blob account level url: '{}'", url);
57+
58+
let sas = blob_client
59+
.shared_access_signature()?
60+
.with_expiry(later)
61+
.with_start(now)
62+
.with_permissions(BlobSasPermissions {
63+
write: true,
64+
..Default::default()
65+
})
66+
.finalize();
67+
println!("blob service token: {}", sas.token());
68+
let url = blob_client.generate_signed_blob_url(&sas)?;
69+
println!("blob service level url: '{}'", url);
70+
71+
let sas = container_client
72+
.shared_access_signature()?
73+
.with_expiry(later)
74+
.with_start(now)
75+
.with_permissions(BlobSasPermissions {
4476
read: true,
77+
list: true,
78+
write: true,
4579
..Default::default()
4680
})
4781
.with_protocol(SasProtocol::HttpHttps)
4882
.finalize();
49-
println!("token: '{}'", sas.token());
5083

51-
let url = blob.generate_signed_blob_url(&sas)?;
52-
println!("url: '{}'", url);
84+
println!("container sas token: {}", sas.token());
85+
let url = container_client.generate_signed_container_url(&sas)?;
86+
println!("container level url: '{}'", url);
5387

5488
Ok(())
5589
}

sdk/storage/src/blob/clients/blob_client.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use crate::blob::blob::requests::*;
22
use crate::blob::prelude::*;
3+
use crate::core::clients::StorageCredentials;
34
use crate::core::prelude::*;
4-
use crate::shared_access_signature::SharedAccessSignature;
5+
use crate::shared_access_signature::{
6+
service_sas::{BlobSharedAccessSignatureBuilder, BlobSignedResource, SetResources},
7+
SasToken,
8+
};
59
use azure_core::prelude::*;
610
use azure_core::HttpClient;
711
use bytes::Bytes;
@@ -159,10 +163,35 @@ impl BlobClient {
159163
BreakLeaseBuilder::new(self)
160164
}
161165

162-
pub fn generate_signed_blob_url(
166+
pub fn shared_access_signature(
163167
&self,
164-
signature: &SharedAccessSignature,
165-
) -> Result<url::Url, Box<dyn std::error::Error + Send + Sync>> {
168+
) -> Result<BlobSharedAccessSignatureBuilder<(), SetResources, ()>, crate::Error> {
169+
let canonicalized_resource = format!(
170+
"/blob/{}/{}/{}",
171+
self.container_client.storage_account_client().account(),
172+
self.container_client.container_name(),
173+
self.blob_name()
174+
);
175+
176+
match self.storage_account_client().storage_credentials() {
177+
StorageCredentials::Key(ref _account, ref key) => Ok(
178+
BlobSharedAccessSignatureBuilder::new(key.to_string(), canonicalized_resource)
179+
.with_resources(BlobSignedResource::Blob),
180+
),
181+
_ => Err(crate::Error::OperationNotSupported(
182+
"Shared access signature generation".to_owned(),
183+
"SAS can be generated only from key and account clients".to_owned(),
184+
)),
185+
}
186+
}
187+
188+
pub fn generate_signed_blob_url<T>(
189+
&self,
190+
signature: &T,
191+
) -> Result<url::Url, Box<dyn std::error::Error + Send + Sync>>
192+
where
193+
T: SasToken,
194+
{
166195
let mut url = self.url_with_segments(None)?;
167196
url.set_query(Some(&signature.token()));
168197
Ok(url)

sdk/storage/src/blob/clients/container_client.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use crate::blob::prelude::PublicAccess;
22
use crate::container::requests::*;
3-
use crate::core::clients::{StorageAccountClient, StorageClient};
3+
use crate::core::clients::{StorageAccountClient, StorageClient, StorageCredentials};
4+
use crate::shared_access_signature::{
5+
service_sas::{BlobSharedAccessSignatureBuilder, BlobSignedResource, SetResources},
6+
SasToken,
7+
};
48
use azure_core::prelude::*;
59
use bytes::Bytes;
610
use http::method::Method;
@@ -106,6 +110,39 @@ impl ContainerClient {
106110
self.storage_client
107111
.prepare_request(url, method, http_header_adder, request_body)
108112
}
113+
114+
pub fn shared_access_signature(
115+
&self,
116+
) -> Result<BlobSharedAccessSignatureBuilder<(), SetResources, ()>, crate::Error> {
117+
let canonicalized_resource = format!(
118+
"/blob/{}/{}",
119+
self.storage_account_client().account(),
120+
self.container_name(),
121+
);
122+
123+
match self.storage_account_client().storage_credentials() {
124+
StorageCredentials::Key(ref _account, ref key) => Ok(
125+
BlobSharedAccessSignatureBuilder::new(key.to_string(), canonicalized_resource)
126+
.with_resources(BlobSignedResource::Container),
127+
),
128+
_ => Err(crate::Error::OperationNotSupported(
129+
"Shared access signature generation".to_owned(),
130+
"SAS can be generated only from key and account clients".to_owned(),
131+
)),
132+
}
133+
}
134+
135+
pub fn generate_signed_container_url<T>(
136+
&self,
137+
signature: &T,
138+
) -> Result<url::Url, Box<dyn std::error::Error + Send + Sync>>
139+
where
140+
T: SasToken,
141+
{
142+
let mut url = self.url_with_segments(None)?;
143+
url.set_query(Some(&signature.token()));
144+
Ok(url)
145+
}
109146
}
110147

111148
#[cfg(test)]

sdk/storage/src/core/clients/storage_account_client.rs

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::headers::CONTENT_MD5;
22
use crate::{
33
core::{ConnectionString, No},
4-
shared_access_signature::SharedAccessSignatureBuilder,
4+
shared_access_signature::account_sas::{
5+
AccountSharedAccessSignatureBuilder, ClientAccountSharedAccessSignature,
6+
},
57
};
68
use azure_core::headers::*;
79
use azure_core::prelude::*;
@@ -53,6 +55,7 @@ pub struct StorageAccountClient {
5355
queue_storage_url: Url,
5456
queue_storage_secondary_url: Url,
5557
filesystem_url: Url,
58+
account: String,
5659
}
5760

5861
fn get_sas_token_parms(sas_token: &str) -> Result<Vec<(String, String)>, url::ParseError> {
@@ -98,8 +101,9 @@ impl StorageAccountClient {
98101
.unwrap(),
99102
filesystem_url: Url::parse(&format!("https://{}.dfs.core.windows.net", &account))
100103
.unwrap(),
101-
storage_credentials: StorageCredentials::Key(account, key.into()),
104+
storage_credentials: StorageCredentials::Key(account.clone(), key.into()),
102105
http_client,
106+
account,
103107
})
104108
}
105109

@@ -167,8 +171,9 @@ impl StorageAccountClient {
167171
queue_storage_url: queue_storage_url.clone(),
168172
queue_storage_secondary_url: queue_storage_url,
169173
filesystem_url,
170-
storage_credentials: StorageCredentials::Key(account, key.into()),
174+
storage_credentials: StorageCredentials::Key(account.clone(), key.into()),
171175
http_client,
176+
account,
172177
})
173178
}
174179

@@ -196,6 +201,7 @@ impl StorageAccountClient {
196201
sas_token.as_ref(),
197202
)?),
198203
http_client,
204+
account,
199205
}))
200206
}
201207

@@ -227,6 +233,7 @@ impl StorageAccountClient {
227233
.unwrap(),
228234
storage_credentials: StorageCredentials::BearerToken(bearer_token),
229235
http_client,
236+
account,
230237
})
231238
}
232239

@@ -257,6 +264,7 @@ impl StorageAccountClient {
257264
queue_storage_secondary_url: get_endpoint_uri(queue_endpoint, &format!("{}-secondary", account), "queue")?,
258265
filesystem_url: get_endpoint_uri(file_endpoint, account, "dfs")?,
259266
http_client,
267+
account: account.to_string(),
260268
}))
261269
}
262270
ConnectionString {
@@ -275,6 +283,7 @@ impl StorageAccountClient {
275283
queue_storage_secondary_url: get_endpoint_uri(queue_endpoint, &format!("{}-secondary", account), "queue")?,
276284
filesystem_url: get_endpoint_uri(file_endpoint, account, "dfs")?,
277285
http_client,
286+
account: account.to_string(),
278287
})),
279288
ConnectionString {
280289
account_name: Some(account),
@@ -292,6 +301,7 @@ impl StorageAccountClient {
292301
queue_storage_secondary_url: get_endpoint_uri(queue_endpoint, &format!("{}-secondary", account), "queue")?,
293302
filesystem_url: get_endpoint_uri(file_endpoint, account, "dfs")?,
294303
http_client,
304+
account: account.to_string(),
295305
})),
296306
_ => {
297307
Err(crate::Error::GenericErrorWithText(
@@ -326,18 +336,12 @@ impl StorageAccountClient {
326336
&self.filesystem_url
327337
}
328338

329-
pub fn shared_access_signature(
330-
&self,
331-
) -> Result<SharedAccessSignatureBuilder<No, No, No, No>, crate::Error> {
332-
match self.storage_credentials {
333-
StorageCredentials::Key(ref account, ref key) => {
334-
Ok(SharedAccessSignatureBuilder::new(account, key))
335-
}
336-
_ => Err(crate::Error::OperationNotSupported(
337-
"Shared access signature generation".to_owned(),
338-
"SAS can be generated only from key and account clients".to_owned(),
339-
)),
340-
}
339+
pub fn account(&self) -> &str {
340+
&self.account
341+
}
342+
343+
pub fn storage_credentials(&self) -> &StorageCredentials {
344+
&self.storage_credentials
341345
}
342346

343347
pub(crate) fn prepare_request(
@@ -418,6 +422,22 @@ impl StorageAccountClient {
418422
}
419423
}
420424

425+
impl ClientAccountSharedAccessSignature for StorageAccountClient {
426+
fn shared_access_signature(
427+
&self,
428+
) -> Result<AccountSharedAccessSignatureBuilder<No, No, No, No>, crate::Error> {
429+
match self.storage_credentials {
430+
StorageCredentials::Key(ref account, ref key) => {
431+
Ok(AccountSharedAccessSignatureBuilder::new(account, key))
432+
}
433+
_ => Err(crate::Error::OperationNotSupported(
434+
"Shared access signature generation".to_owned(),
435+
"SAS can be generated only from key and account clients".to_owned(),
436+
)),
437+
}
438+
}
439+
}
440+
421441
fn generate_authorization(
422442
h: &HeaderMap,
423443
u: &url::Url,

sdk/storage/src/core/prelude.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
pub use crate::core::clients::{AsStorageClient, StorageAccountClient, StorageClient};
2-
pub use crate::core::shared_access_signature::{
3-
ClientSharedAccessSignature, SasExpirySupport, SasIpSupport, SasPermissions,
4-
SasPermissionsSupport, SasProtocol, SasProtocolSupport, SasResource, SasResourceSupport,
5-
SasResourceType, SasResourceTypeSupport, SasService, SasStartSupport, SasVersion,
1+
pub use crate::core::{
2+
clients::{AsStorageClient, StorageAccountClient, StorageClient},
3+
shared_access_signature::{
4+
account_sas::{
5+
AccountSasPermissions, AccountSasResource, AccountSasResourceType,
6+
ClientAccountSharedAccessSignature, SasExpirySupport, SasPermissionsSupport,
7+
SasProtocolSupport, SasResourceSupport, SasResourceTypeSupport, SasStartSupport,
8+
},
9+
service_sas::{BlobSasPermissions, BlobSignedResource},
10+
SasProtocol, SasToken,
11+
},
12+
{ConsistencyCRC64, ConsistencyMD5, CopyId, IPRange},
613
};
7-
pub use crate::core::{ConsistencyCRC64, ConsistencyMD5, CopyId, IPRange};

0 commit comments

Comments
 (0)