Skip to content

Commit 5ab558f

Browse files
authored
Added support for copy source authorization to append block from url (Azure#20557)
1 parent d2534f7 commit 5ab558f

File tree

6 files changed

+123
-4
lines changed

6 files changed

+123
-4
lines changed

sdk/storage/azblob/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Added [Blob Batch API](https://learn.microsoft.com/rest/api/storageservices/blob-batch).
88
* Added support for bearer challenge for identity based managed disks.
99
* Added support for GetAccountInfo to container and blob level clients.
10+
* Added support for CopySourceAuthorization to appendblob.AppendBlockFromURL
1011

1112
### Breaking Changes
1213

sdk/storage/azblob/appendblob/client_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"context"
1212
"crypto/md5"
1313
"encoding/binary"
14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1415
"hash/crc64"
1516
"io"
1617
"math/rand"
@@ -407,6 +408,94 @@ func (s *AppendBlobUnrecordedTestsSuite) TestAppendBlockFromURLWithMD5() {
407408
testcommon.ValidateBlobErrorCode(_require, err, bloberror.MD5Mismatch)
408409
}
409410

411+
func (s *AppendBlobRecordedTestsSuite) TestAppendBlockFromURLCopySourceAuth() {
412+
_require := require.New(s.T())
413+
testName := s.T().Name()
414+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil)
415+
_require.NoError(err)
416+
417+
// Random seed for data generation
418+
seed := int64(crc64.Checksum([]byte(testName), shared.CRC64Table))
419+
random := rand.New(rand.NewSource(seed))
420+
421+
// Getting AAD Authentication
422+
cred, err := testcommon.GetGenericTokenCredential()
423+
_require.NoError(err)
424+
425+
containerName := testcommon.GenerateContainerName(testName)
426+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
427+
defer testcommon.DeleteContainer(context.Background(), _require, containerClient)
428+
429+
// Create source and destination blobs
430+
srcABClient := containerClient.NewAppendBlobClient(testcommon.GenerateBlobName("appendsrc"))
431+
destABClient := containerClient.NewAppendBlobClient(testcommon.GenerateBlobName("appenddest"))
432+
433+
// Upload some data to source
434+
_, err = srcABClient.Create(context.Background(), nil)
435+
_require.Nil(err)
436+
contentSize := 4 * 1024 // 4KB
437+
r, sourceData := testcommon.GetDataAndReader(random, contentSize)
438+
_, err = srcABClient.AppendBlock(context.Background(), streaming.NopCloser(r), nil)
439+
_require.Nil(err)
440+
_, err = destABClient.Create(context.Background(), nil)
441+
_require.Nil(err)
442+
443+
// Getting token
444+
token, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{"https://storage.azure.com/.default"}})
445+
_require.NoError(err)
446+
447+
options := appendblob.AppendBlockFromURLOptions{
448+
CopySourceAuthorization: to.Ptr("Bearer " + token.Token),
449+
}
450+
451+
pbResp, err := destABClient.AppendBlockFromURL(context.Background(), srcABClient.URL(), &options)
452+
_require.NoError(err)
453+
_require.NotNil(pbResp)
454+
455+
// Download data from destination
456+
destBuffer := make([]byte, 4*1024)
457+
_, err = destABClient.DownloadBuffer(context.Background(), destBuffer, nil)
458+
_require.Nil(err)
459+
_require.Equal(destBuffer, sourceData)
460+
}
461+
462+
func (s *AppendBlobRecordedTestsSuite) TestAppendBlockFromURLCopySourceAuthNegative() {
463+
_require := require.New(s.T())
464+
testName := s.T().Name()
465+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil)
466+
_require.NoError(err)
467+
468+
// Random seed for data generation
469+
seed := int64(crc64.Checksum([]byte(testName), shared.CRC64Table))
470+
random := rand.New(rand.NewSource(seed))
471+
472+
containerName := testcommon.GenerateContainerName(testName)
473+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
474+
defer testcommon.DeleteContainer(context.Background(), _require, containerClient)
475+
476+
// Create source and destination blobs
477+
srcABClient := containerClient.NewAppendBlobClient(testcommon.GenerateBlobName("appendsrc"))
478+
destABClient := containerClient.NewAppendBlobClient(testcommon.GenerateBlobName("appenddest"))
479+
480+
// Upload some data to source
481+
_, err = srcABClient.Create(context.Background(), nil)
482+
_require.Nil(err)
483+
contentSize := 4 * 1024 // 4KB
484+
r, _ := testcommon.GetDataAndReader(random, contentSize)
485+
_, err = srcABClient.AppendBlock(context.Background(), streaming.NopCloser(r), nil)
486+
_require.Nil(err)
487+
_, err = destABClient.Create(context.Background(), nil)
488+
_require.Nil(err)
489+
490+
options := appendblob.AppendBlockFromURLOptions{
491+
CopySourceAuthorization: to.Ptr("Bearer faketoken"),
492+
}
493+
494+
_, err = destABClient.AppendBlockFromURL(context.Background(), srcABClient.URL(), &options)
495+
_require.Error(err)
496+
_require.True(bloberror.HasCode(err, bloberror.CannotVerifyCopySource))
497+
}
498+
410499
func (s *AppendBlobRecordedTestsSuite) TestBlobCreateAppendMetadataNonEmpty() {
411500
_require := require.New(s.T())
412501
testName := s.T().Name()

sdk/storage/azblob/appendblob/models.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ func (o *AppendBlockOptions) format() (*generated.AppendBlobClientAppendBlockOpt
100100

101101
// AppendBlockFromURLOptions contains the optional parameters for the Client.AppendBlockFromURL method.
102102
type AppendBlockFromURLOptions struct {
103+
// Only Bearer type is supported. Credentials should be a valid OAuth access token to copy source.
104+
CopySourceAuthorization *string
105+
103106
// SourceContentValidation contains the validation mechanism used on the range of bytes read from the source.
104107
SourceContentValidation blob.SourceContentValidationType
105108

@@ -125,7 +128,8 @@ func (o *AppendBlockFromURLOptions) format() (*generated.AppendBlobClientAppendB
125128
}
126129

127130
options := &generated.AppendBlobClientAppendBlockFromURLOptions{
128-
SourceRange: exported.FormatHTTPRange(o.Range),
131+
SourceRange: exported.FormatHTTPRange(o.Range),
132+
CopySourceAuthorization: o.CopySourceAuthorization,
129133
}
130134

131135
if o.SourceContentValidation != nil {

sdk/storage/azblob/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "go",
44
"TagPrefix": "go/storage/azblob",
5-
"Tag": "go/storage/azblob_5d20008f59"
5+
"Tag": "go/storage/azblob_fad5549316"
66
}

sdk/storage/azblob/internal/testcommon/clients_auth.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"context"
1212
"errors"
1313
"fmt"
14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1415
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
1516
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
1617
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
@@ -55,6 +56,7 @@ const (
5556
const (
5657
FakeStorageAccount = "fakestorage"
5758
FakeStorageURL = "https://fakestorage.blob.core.windows.net"
59+
FakeToken = "faketoken"
5860
)
5961

6062
var (
@@ -145,6 +147,20 @@ func GetServiceClientNoCredential(t *testing.T, sasUrl string, options *service.
145147
return serviceClient, err
146148
}
147149

150+
type FakeCredential struct {
151+
}
152+
153+
func (c *FakeCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
154+
return azcore.AccessToken{Token: FakeToken, ExpiresOn: time.Now().Add(time.Hour).UTC()}, nil
155+
}
156+
157+
func GetGenericTokenCredential() (azcore.TokenCredential, error) {
158+
if recording.GetRecordMode() == recording.PlaybackMode {
159+
return &FakeCredential{}, nil
160+
}
161+
return azidentity.NewDefaultAzureCredential(nil)
162+
}
163+
148164
func GetGenericAccountInfo(accountType TestAccountType) (string, string) {
149165
if recording.GetRecordMode() == recording.PlaybackMode {
150166
return FakeStorageAccount, "ZmFrZQ=="

sdk/storage/azblob/internal/testcommon/common.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ package testcommon
1010
import (
1111
"bytes"
1212
"context"
13-
"crypto/rand"
13+
crypto_rand "crypto/rand"
1414
"encoding/base64"
1515
"encoding/binary"
1616
"errors"
1717
"fmt"
1818
"io"
19+
"math/rand"
1920
"os"
2021
"runtime"
2122
"strconv"
@@ -83,7 +84,13 @@ func GetReaderToGeneratedBytes(n int) io.ReadSeekCloser {
8384

8485
func GetRandomDataAndReader(n int) (*bytes.Reader, []byte) {
8586
data := make([]byte, n)
86-
_, _ = rand.Read(data)
87+
_, _ = crypto_rand.Read(data)
88+
return bytes.NewReader(data), data
89+
}
90+
91+
func GetDataAndReader(r *rand.Rand, n int) (*bytes.Reader, []byte) {
92+
data := make([]byte, n)
93+
_, _ = r.Read(data)
8794
return bytes.NewReader(data), data
8895
}
8996

@@ -181,8 +188,10 @@ func GetRequiredEnv(name string) (string, error) {
181188

182189
func BeforeTest(t *testing.T, suite string, test string) {
183190
const urlRegex = `https://\S+\.blob\.core\.windows\.net`
191+
const tokenRegex = `(?:Bearer\s).*`
184192
require.NoError(t, recording.AddURISanitizer(FakeStorageURL, urlRegex, nil))
185193
require.NoError(t, recording.AddHeaderRegexSanitizer("x-ms-copy-source", FakeStorageURL, urlRegex, nil))
194+
require.NoError(t, recording.AddHeaderRegexSanitizer("x-ms-copy-source-authorization", FakeToken, tokenRegex, nil))
186195
// we freeze request IDs and timestamps to avoid creating noisy diffs
187196
// NOTE: we can't freeze time stamps as that breaks some tests that use if-modified-since etc (maybe it can be fixed?)
188197
//testframework.AddHeaderRegexSanitizer("X-Ms-Date", "Wed, 10 Aug 2022 23:34:14 GMT", "", nil)

0 commit comments

Comments
 (0)