diff --git a/sdk/storage/.dict.txt b/sdk/storage/.dict.txt index 0bcd38e1b8..186df8af9a 100644 --- a/sdk/storage/.dict.txt +++ b/sdk/storage/.dict.txt @@ -24,6 +24,7 @@ testblob2 testblob3 testblob4 testcontainer +testid uncommittedblobs westus yourtagname diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index bad8863f67..07da66676b 100644 --- a/sdk/storage/azure_storage_blob/assets.json +++ b/sdk/storage/azure_storage_blob/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_094782fa40", + "Tag": "rust/azure_storage_blob_c8c2bbe44d", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs index 2853763a0c..9a67f3e5f7 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs @@ -2,20 +2,24 @@ // Licensed under the MIT License. use crate::{ - generated::clients::BlobContainerClient as GeneratedBlobContainerClient, - generated::models::{ - BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, - BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, - BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, - BlobContainerClientRenewLeaseResult, + generated::{ + clients::BlobContainerClient as GeneratedBlobContainerClient, + models::{ + BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, + BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, + BlobContainerClientRenewLeaseResult, SignedIdentifier, + }, }, models::{ - BlobContainerClientAcquireLeaseOptions, BlobContainerClientBreakLeaseOptions, - BlobContainerClientChangeLeaseOptions, BlobContainerClientCreateOptions, - BlobContainerClientDeleteOptions, BlobContainerClientFindBlobsByTagsOptions, + format_signed_identifiers, BlobContainerClientAcquireLeaseOptions, + BlobContainerClientBreakLeaseOptions, BlobContainerClientChangeLeaseOptions, + BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, + BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetPropertiesOptions, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, - BlobContainerClientRenewLeaseOptions, BlobContainerClientSetMetadataOptions, + BlobContainerClientRenewLeaseOptions, BlobContainerClientSetAccessPolicyOptions, + BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetMetadataOptions, FilterBlobSegment, ListBlobsFlatSegmentResponse, }, pipeline::StorageHeadersPolicy, @@ -25,7 +29,7 @@ use azure_core::{ credentials::TokenCredential, http::{ policies::{BearerTokenCredentialPolicy, Policy}, - NoFormat, PageIterator, Pager, Response, Url, XmlFormat, + NoFormat, PageIterator, Pager, RequestContent, Response, Url, XmlFormat, }, Result, }; @@ -275,4 +279,34 @@ impl BlobContainerClient { ) -> Result> { self.client.get_account_info(options).await } + + /// Sets the permissions for the specified container. The permissions indicate whether blobs in a + /// container may be accessed publicly. + /// + /// # Arguments + /// + /// * `container_acl` - The access control list for the container. + /// * `options` - Optional configuration for the request. + pub async fn set_access_policy( + &self, + container_acl: Vec, + options: Option>, + ) -> Result> { + self.client + .set_access_policy(format_signed_identifiers(container_acl)?, options) + .await + } + + /// Gets the permissions for the specified container. The permissions indicate whether container data + /// may be accessed publicly. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_access_policy( + &self, + options: Option>, + ) -> Result, XmlFormat>> { + self.client.get_access_policy(options).await + } } diff --git a/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs b/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs index feba35cbf9..fa925bce96 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs @@ -30,7 +30,7 @@ pub struct AccessPolicy { default, rename = "Expiry", skip_serializing_if = "Option::is_none", - with = "azure_core::time::rfc7231::option" + with = "azure_core::time::rfc3339::option" )] pub expiry: Option, @@ -43,7 +43,7 @@ pub struct AccessPolicy { default, rename = "Start", skip_serializing_if = "Option::is_none", - with = "azure_core::time::rfc7231::option" + with = "azure_core::time::rfc3339::option" )] pub start: Option, } diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 0aec332293..9477c77d7a 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -1,9 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use azure_core::{ + http::{RequestContent, XmlFormat}, + xml::to_xml_with_root, +}; +use serde::Serialize; + use crate::models::{ AppendBlobClientCreateOptions, BlobTag, BlobTags, BlockBlobClientUploadBlobFromUrlOptions, - BlockBlobClientUploadOptions, PageBlobClientCreateOptions, + BlockBlobClientUploadOptions, PageBlobClientCreateOptions, SignedIdentifier, }; use azure_core::error::ErrorKind; use std::collections::HashMap; @@ -109,3 +115,18 @@ impl From> for BlobTags { } } } + +// SignedIdentifiers wrapper for correct XML serialization. +#[derive(Serialize)] +struct SignedIdentifiersWrapper { + #[serde(rename = "SignedIdentifier")] + items: Vec, +} + +// Converts a `Vec` into `RequestContent, XmlFormat>`. +pub(crate) fn format_signed_identifiers( + value: Vec, +) -> Result, XmlFormat>, azure_core::Error> { + let wrapper = SignedIdentifiersWrapper { items: value }; + Ok(to_xml_with_root("SignedIdentifiers", &wrapper)?.into()) +} diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index c9090c6c09..46c54d3d5b 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -4,7 +4,7 @@ mod extensions; pub use crate::generated::models::{ - AccessTier, AccountKind, AppendBlobClientAppendBlockFromUrlOptions, + AccessPolicy, AccessTier, AccountKind, AppendBlobClientAppendBlockFromUrlOptions, AppendBlobClientAppendBlockFromUrlResult, AppendBlobClientAppendBlockFromUrlResultHeaders, AppendBlobClientAppendBlockOptions, AppendBlobClientAppendBlockResult, AppendBlobClientAppendBlockResultHeaders, AppendBlobClientCreateOptions, @@ -36,15 +36,16 @@ pub use crate::generated::models::{ BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccountInfoOptions, - BlobContainerClientGetAccountInfoResult, BlobContainerClientGetAccountInfoResultHeaders, - BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, - BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, - BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenameResult, - BlobContainerClientRenameResultHeaders, BlobContainerClientRenewLeaseOptions, - BlobContainerClientRenewLeaseResult, BlobContainerClientRenewLeaseResultHeaders, - BlobContainerClientRestoreResult, BlobContainerClientRestoreResultHeaders, + BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, + BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, + BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, + BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, + BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, + BlobContainerClientRenameResult, BlobContainerClientRenameResultHeaders, + BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, + BlobContainerClientRenewLeaseResultHeaders, BlobContainerClientRestoreResult, + BlobContainerClientRestoreResultHeaders, BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders, BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobFlatListSegment, BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal, diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 9bfed276b1..039fd930d1 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::models::{BlobTag, BlobTags}; -use std::collections::HashMap; -use std::io::{Error, ErrorKind}; +use std::{ + collections::HashMap, + io::{Error, ErrorKind}, +}; /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and /// returns the HTTP range in String format. diff --git a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs index 1b372ca5e2..b516f6cefe 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -1,21 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::http::{RequestContent, StatusCode}; -use azure_core_test::{recorded, Matcher, TestContext, TestMode}; -use azure_storage_blob::format_filter_expression; +use azure_core::{ + http::StatusCode, + time::{Duration, OffsetDateTime}, +}; +use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ - AccountKind, BlobContainerClientAcquireLeaseResultHeaders, + AccessPolicy, AccountKind, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, + BlobContainerClientSetMetadataOptions, BlobType, LeaseState, SignedIdentifier, }; use azure_storage_blob_test::{ - create_test_blob, get_blob_name, get_blob_service_client, get_container_client, - get_container_name, + create_test_blob, get_blob_service_client, get_container_client, get_container_name, }; use futures::{StreamExt, TryStreamExt}; -use std::{collections::HashMap, error::Error, time::Duration}; +use std::{collections::HashMap, error::Error}; use tokio::time; #[recorded::test] @@ -275,7 +276,7 @@ async fn test_container_lease_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { } #[recorded::test] -async fn test_find_blobs_by_tags_container(ctx: TestContext) -> Result<(), Box> { +async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - recording.set_matcher(Matcher::HeaderlessMatcher).await?; - let container_client = get_container_client(recording, true).await?; - - // Create Test Blobs with Tags - let blob1_name = get_blob_name(recording); - create_test_blob( - &container_client.blob_client(blob1_name.clone()), - Some(RequestContent::from("hello world".as_bytes().into())), - Some( - BlockBlobClientUploadOptions::default().with_tags(HashMap::from([ - ("foo".to_string(), "bar".to_string()), - ("alice".to_string(), "bob".to_string()), - ])), - ), - ) - .await?; - let blob2_name = get_blob_name(recording); - let blob2_tags = HashMap::from([("fizz".to_string(), "buzz".to_string())]); - create_test_blob( - &container_client.blob_client(blob2_name.clone()), - Some(RequestContent::from("ferris the crab".as_bytes().into())), - Some(BlockBlobClientUploadOptions::default().with_tags(blob2_tags.clone())), - ) - .await?; + let container_client = get_container_client(recording, false).await?; + container_client.create_container(None).await?; - // Sleep in live mode to allow tags to be indexed on the service - if recording.test_mode() == TestMode::Live { - time::sleep(Duration::from_secs(5)).await; - } + // Set Access Policy w/ Policy Defined + let access_policy = AccessPolicy { + expiry: Some(OffsetDateTime::now_utc() + Duration::seconds(10)), + permission: Some("rw".to_string()), + start: Some(OffsetDateTime::now_utc()), + }; + let signed_identifier = SignedIdentifier { + access_policy: Some(access_policy), + id: Some("testid".into()), + }; - // Find "hello world" blob by its tag {"foo": "bar"} - let response = container_client - .find_blobs_by_tags("\"foo\"='bar'", None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob1_name), - "Failed to find \"{blob1_name}\" in filtered blob results." - ); - - // Find "ferris the crab" blob by its tag {"fizz": "buzz"} - let response = container_client - .find_blobs_by_tags(&format_filter_expression(&blob2_tags)?, None) + container_client + .set_access_policy(vec![signed_identifier], None) .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob2_name), - "Failed to find \"{blob2_name}\" in filtered blob results." - ); + + // Assert + let access_policy_response = container_client.get_access_policy(None).await?; + let signed_identifiers = access_policy_response.into_body().await?; + for signed_identifier in &signed_identifiers { + if let Some(access_policy) = &signed_identifier.access_policy { + assert!(signed_identifier.id.is_some()); + assert!(access_policy.start.is_some()); + assert!(access_policy.expiry.is_some()); + assert_eq!("rw", access_policy.permission.as_ref().unwrap()); + } + } container_client.delete_container(None).await?; Ok(()) diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 89427203d9..db16abff38 100644 --- a/sdk/storage/azure_storage_blob/tsp-location.yaml +++ b/sdk/storage/azure_storage_blob/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/storage/Microsoft.BlobStorage -commit: 2e3571e7b2c729281b6819574cf358fd87ded3ab +commit: c3d773693455458c91efba4c2e72c3084458d8fe repo: Azure/azure-rest-api-specs -additionalDirectories: +additionalDirectories: