diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/DataMovementSharesExtensions.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/DataMovementSharesExtensions.cs index b838c46e5922..eca0c2ed3d99 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/DataMovementSharesExtensions.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/DataMovementSharesExtensions.cs @@ -41,7 +41,7 @@ internal static StorageResourceProperties ToStorageResourceProperties( copyStatusDescription: properties?.CopyStatusDescription, copyId: properties?.CopyId, copyProgress: properties?.CopyProgress, - copySource: new Uri(properties?.CopySource), + copySource: properties?.CopySource != null ? new Uri(properties?.CopySource) : null, contentLength: properties.ContentLength, contentType: properties?.ContentType, eTag: properties.ETag, diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/PathScanner.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/PathScanner.cs index a40918eb2271..f5a84e19e24b 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/PathScanner.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/PathScanner.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; @@ -13,6 +14,8 @@ namespace Azure.Storage.DataMovement.Files.Shares { internal class PathScanner { + public static Lazy Singleton { get; } = new Lazy(() => new PathScanner()); + public virtual async IAsyncEnumerable ScanFilesAsync( ShareDirectoryClient directory, [EnumeratorCancellation] CancellationToken cancellationToken) diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareDirectoryStorageResourceContainer.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareDirectoryStorageResourceContainer.cs index 161024ebb0fa..f448de6b17f7 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareDirectoryStorageResourceContainer.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareDirectoryStorageResourceContainer.cs @@ -14,7 +14,7 @@ namespace Azure.Storage.DataMovement.Files.Shares internal class ShareDirectoryStorageResourceContainer : StorageResourceContainerInternal { internal ShareFileStorageResourceOptions ResourceOptions { get; set; } - internal PathScanner PathScanner { get; set; } + internal PathScanner PathScanner { get; set; } = PathScanner.Singleton.Value; internal ShareDirectoryClient ShareDirectoryClient { get; } diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileClientInternals.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileClientInternals.cs new file mode 100644 index 000000000000..648ed97d52df --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileClientInternals.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Azure.Storage.Files.Shares; +using Azure.Storage.Files.Shares.Models; + +namespace Azure.Storage.DataMovement.Blobs +{ + internal class ShareFileClientInternals : ShareFileClient + { + public static Task GetCopyAuthorizationTokenAsync( + ShareFileClient client, + CancellationToken cancellationToken) + => ShareFileClient.GetCopyAuthorizationHeaderAsync(client, cancellationToken); + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs index e141e14a50df..efff697fc3c8 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core; +using Azure.Storage.DataMovement.Blobs; using Azure.Storage.Files.Shares; using Azure.Storage.Files.Shares.Models; @@ -171,11 +172,9 @@ protected override async Task DeleteIfExistsAsync(CancellationToken cancel return await ShareFileClient.DeleteIfExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } - protected override Task GetCopyAuthorizationHeaderAsync(CancellationToken cancellationToken = default) + protected override async Task GetCopyAuthorizationHeaderAsync(CancellationToken cancellationToken = default) { - CancellationHelper.ThrowIfCancellationRequested(cancellationToken); - // TODO: This needs an update to ShareFileClient to allow getting the Copy Authorization Token - throw new NotImplementedException(); + return await ShareFileClientInternals.GetCopyAuthorizationTokenAsync(ShareFileClient, cancellationToken).ConfigureAwait(false); } protected override async Task GetPropertiesAsync(CancellationToken cancellationToken = default) diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs index 5555330d32fe..163948699234 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs @@ -8,11 +8,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Azure.Core; using Azure.Core.TestFramework; using Azure.Storage.Files.Shares; using Azure.Storage.Files.Shares.Models; using Azure.Storage.Test; using Moq; +using Moq.Protected; using NUnit.Framework; namespace Azure.Storage.DataMovement.Files.Shares.Tests @@ -544,5 +546,37 @@ await TestHelper.AssertExpectedExceptionAsync( Times.Once()); mock.VerifyNoOtherCalls(); } + + [Test] + public async Task GetCopyAuthorizationHeaderAsync() + { + CancellationToken cancellationToken = new(); + string expectedToken = "foo"; + AccessToken accessToken = new(expectedToken, DateTimeOffset.UtcNow); + + Mock tokenCred = new(); + tokenCred.Setup(t => t.GetTokenAsync(It.IsAny(), It.IsAny())) + .Returns(new ValueTask(Task.FromResult(accessToken))); + + ShareFileClient client = new(new Uri("https://example.file.core.windows.net/share/file"), tokenCred.Object); + ShareFileStorageResource resource = new(client); + + // Act - Get access token + HttpAuthorization authorization = await resource.GetCopyAuthorizationHeaderInternalAsync(cancellationToken); + + // Assert + Assert.That(authorization.Parameter, Is.EqualTo(expectedToken)); + tokenCred.Verify(t => t.GetTokenAsync(It.IsAny(), cancellationToken), Times.Once()); + tokenCred.VerifyNoOtherCalls(); + } + + [Test] + public async Task GetCopyAuthorizationHeaderAsync_NoOAuth() + { + ShareFileClient nonOAuthClient = new(new Uri("https://example.file.core.windows.net/share/file")); + ShareFileStorageResource resource = new(nonOAuthClient); + + Assert.That(await resource.GetCopyAuthorizationHeaderInternalAsync(), Is.Null); + } } } diff --git a/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.net6.0.cs b/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.net6.0.cs index 327a672990a9..ad8a11ac99d9 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.net6.0.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.net6.0.cs @@ -250,6 +250,7 @@ public ShareFileClient(System.Uri fileUri, Azure.Storage.StorageSharedKeyCredent public virtual System.Threading.Tasks.Task> ForceCloseHandleAsync(string handleId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.ShareFileSasPermissions permissions, System.DateTimeOffset expiresOn) { throw null; } public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.ShareSasBuilder builder) { throw null; } + protected static System.Threading.Tasks.Task GetCopyAuthorizationHeaderAsync(Azure.Storage.Files.Shares.ShareFileClient client, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Pageable GetHandles(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.AsyncPageable GetHandlesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal virtual Azure.Storage.Files.Shares.ShareClient GetParentShareClientCore() { throw null; } diff --git a/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.netstandard2.0.cs b/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.netstandard2.0.cs index 327a672990a9..ad8a11ac99d9 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/api/Azure.Storage.Files.Shares.netstandard2.0.cs @@ -250,6 +250,7 @@ public ShareFileClient(System.Uri fileUri, Azure.Storage.StorageSharedKeyCredent public virtual System.Threading.Tasks.Task> ForceCloseHandleAsync(string handleId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.ShareFileSasPermissions permissions, System.DateTimeOffset expiresOn) { throw null; } public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.ShareSasBuilder builder) { throw null; } + protected static System.Threading.Tasks.Task GetCopyAuthorizationHeaderAsync(Azure.Storage.Files.Shares.ShareFileClient client, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Pageable GetHandles(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.AsyncPageable GetHandlesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal virtual Azure.Storage.Files.Shares.ShareClient GetParentShareClientCore() { throw null; } diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/ShareClient.cs b/sdk/storage/Azure.Storage.Files.Shares/src/ShareClient.cs index 8d3406e9bf24..2097a3f444ec 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/ShareClient.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/src/ShareClient.cs @@ -346,7 +346,10 @@ internal ShareClient( sasCredential: sasCredential, tokenCredential: tokenCredential, clientDiagnostics: new ClientDiagnostics(options), - clientOptions: options); + clientOptions: options) + { + Audience = options.Audience ?? ShareAudience.DefaultAudience, + }; _shareRestClient = BuildShareRestClient(shareUri); } diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/ShareClientConfiguration.cs b/sdk/storage/Azure.Storage.Files.Shares/src/ShareClientConfiguration.cs index 1d799e151516..900c8ad47e64 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/ShareClientConfiguration.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/src/ShareClientConfiguration.cs @@ -14,6 +14,8 @@ internal class ShareClientConfiguration : StorageClientConfiguration public TransferValidationOptions TransferValidation { get; internal set; } + public ShareAudience Audience { get; internal set; } + /// /// Create a with shared key authentication. /// diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/ShareDirectoryClient.cs b/sdk/storage/Azure.Storage.Files.Shares/src/ShareDirectoryClient.cs index 4b94f5fddc28..fe7ec2acad9c 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/ShareDirectoryClient.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/src/ShareDirectoryClient.cs @@ -412,7 +412,10 @@ internal ShareDirectoryClient( sasCredential: sasCredential, tokenCredential: tokenCredential, clientDiagnostics: new ClientDiagnostics(options), - clientOptions: options); + clientOptions: options) + { + Audience = options.Audience ?? ShareAudience.DefaultAudience, + }; _directoryRestClient = BuildDirectoryRestClient(directoryUri); } diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/ShareFileClient.cs b/sdk/storage/Azure.Storage.Files.Shares/src/ShareFileClient.cs index c9605b686651..545c30db3ebf 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/ShareFileClient.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/src/ShareFileClient.cs @@ -422,7 +422,10 @@ internal ShareFileClient( sasCredential: sasCredential, tokenCredential: tokenCredential, clientDiagnostics: new ClientDiagnostics(options), - clientOptions: options); + clientOptions: options) + { + Audience = options.Audience ?? ShareAudience.DefaultAudience, + }; _fileRestClient = BuildFileRestClient(fileUri); } @@ -498,6 +501,38 @@ private void SetNameFieldsIfNull() } } + #region internal static accessors for Azure.Storage.DataMovement.Blobs + /// + /// Get a 's + /// for passing the authorization when performing service to service copy + /// where OAuth is necessary to authenticate the source. + /// + /// + /// The storage client which to generate the + /// authorization header off of. + /// + /// + /// Optional to propagate + /// notifications that the operation should be cancelled. + /// + /// The BlobServiceClient's HttpPipeline. + protected static async Task GetCopyAuthorizationHeaderAsync( + ShareFileClient client, + CancellationToken cancellationToken = default) + { + if (client.ClientConfiguration.TokenCredential != default) + { + AccessToken accessToken = await client.ClientConfiguration.TokenCredential.GetTokenAsync( + new TokenRequestContext(new string[] { client.ClientConfiguration.Audience.ToString() }), + cancellationToken).ConfigureAwait(false); + return new HttpAuthorization( + Constants.CopyHttpAuthorization.BearerScheme, + accessToken.Token); + } + return default; + } + #endregion internal static accessors for Azure.Storage.DataMovement.Blobs + #region Create /// /// Creates a new file or replaces an existing file. diff --git a/sdk/storage/Azure.Storage.Files.Shares/src/ShareServiceClient.cs b/sdk/storage/Azure.Storage.Files.Shares/src/ShareServiceClient.cs index 296459388823..4ab8331339c2 100644 --- a/sdk/storage/Azure.Storage.Files.Shares/src/ShareServiceClient.cs +++ b/sdk/storage/Azure.Storage.Files.Shares/src/ShareServiceClient.cs @@ -325,7 +325,10 @@ internal ShareServiceClient( sasCredential: sasCredential, tokenCredential: tokenCredential, clientDiagnostics: new ClientDiagnostics(options), - clientOptions: options); + clientOptions: options) + { + Audience = options.Audience ?? ShareAudience.DefaultAudience, + }; _serviceRestClient = BuildServiceRestClient(); }