Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AzureSasCredential #17636

Merged
merged 13 commits into from
Jan 5, 2021
3 changes: 3 additions & 0 deletions sdk/core/Azure.Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 1.8.0-beta.1 (Unreleased)

### Added
- `AzureSasCredential` and its respective policy.

## 1.7.0 (2020-12-14)

### New Features
Expand Down
7 changes: 7 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net461.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public AzureKeyCredential(string key) { }
public string Key { get { throw null; } }
public void Update(string key) { }
}
public partial class AzureSasCredential
{
public AzureSasCredential(string signature) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string Signature { get { throw null; } }
public void Update(string signature) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct ETag : System.IEquatable<Azure.ETag>
{
Expand Down
7 changes: 7 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net5.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public AzureKeyCredential(string key) { }
public string Key { get { throw null; } }
public void Update(string key) { }
}
public partial class AzureSasCredential
{
public AzureSasCredential(string signature) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string Signature { get { throw null; } }
public void Update(string signature) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct ETag : System.IEquatable<Azure.ETag>
{
Expand Down
7 changes: 7 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public AzureKeyCredential(string key) { }
public string Key { get { throw null; } }
public void Update(string key) { }
}
public partial class AzureSasCredential
{
public AzureSasCredential(string signature) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public string Signature { get { throw null; } }
public void Update(string signature) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct ETag : System.IEquatable<Azure.ETag>
{
Expand Down
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/src/Azure.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<Compile Remove="Shared\**\*.cs" />
<Compile Include="Shared\Argument.cs" />
<Compile Include="Shared\AzureKeyCredentialPolicy.cs" />
<Compile Include="Shared\AzureSasCredentialSynchronousPolicy.cs" />
<Compile Include="Shared\EventSourceEventFormatting.cs" />
<Compile Include="Shared\HashCodeBuilder.cs" />
<Compile Include="Shared\ClientDiagnostics.cs" />
Expand Down
60 changes: 60 additions & 0 deletions sdk/core/Azure.Core/src/AzureSasCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ComponentModel;
using System.Threading;
using Azure.Core;

namespace Azure
{
/// <summary>
/// Shared access signature credential used to authenticate to an Azure Service.
/// It provides the ability to update the shared access signature without creating a new client.
/// </summary>
public class AzureSasCredential
{
private string _signature;

/// <summary>
/// Shared access signature used to authenticate to an Azure service.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string Signature
{
get => Volatile.Read(ref _signature);
private set => Volatile.Write(ref _signature, value);
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureSasCredential"/> class.
/// </summary>
/// <param name="signature">Shared access signature to use to authenticate with the Azure service.</param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when the <paramref name="signature"/> is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Thrown when the <paramref name="signature"/> is empty.
/// </exception>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public AzureSasCredential(string signature) => Update(signature);
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.

/// <summary>
/// Updates the shared access signature.
/// This is intended to be used when you've regenerated your shared access signature
/// and want to update long lived clients.
/// </summary>
/// <param name="signature">Shared access signature to authenticate the service against.</param>
/// <exception cref="System.ArgumentNullException">
/// Thrown when the <paramref name="signature"/> is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Thrown when the <paramref name="signature"/> is empty.
/// </exception>
public void Update(string signature)
{
Argument.AssertNotNullOrWhiteSpace(signature, nameof(signature));
Signature = signature;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Azure.Core.Pipeline;

namespace Azure.Core
{
internal class AzureSasCredentialSynchronousPolicy : HttpPipelineSynchronousPolicy
{
private readonly AzureSasCredential _credential;

/// <summary>
/// Initializes a new instance of the <see cref="AzureSasCredentialSynchronousPolicy"/> class.
/// </summary>
/// <param name="credential">The <see cref="AzureSasCredentialSynchronousPolicy"/> used to authenticate requests.</param>
public AzureSasCredentialSynchronousPolicy(AzureSasCredential credential)
{
Argument.AssertNotNull(credential, nameof(credential));
_credential = credential;
}

/// <inheritdoc/>
public override void OnSendingRequest(HttpMessage message)
{
string query = message.Request.Uri.Query;
string signature = _credential.Signature;
if (signature.StartsWith("?", StringComparison.InvariantCulture))
{
signature = signature.Substring(1);
}
if (!query.Contains(signature))
{
query = string.IsNullOrEmpty(query) ? '?' + signature : query + '&' + signature;
message.Request.Uri.Query = query;
}

base.OnSendingRequest(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
using Azure.Core.TestFramework;
using NUnit.Framework;

namespace Azure.Core.Tests
{
public class AzureSasCredentialSynchronousPolicyTests : PolicyTestBase
{
[TestCase("sig=test_signature_value")]
[TestCase("?sig=test_signature_value")]
public async Task SetsSignatureEmptyQuery(string signatureValue)
{
var transport = new MockTransport(new MockResponse(200));
var sasPolicy = new AzureSasCredentialSynchronousPolicy(new AzureSasCredential(signatureValue));

await SendGetRequest(transport, sasPolicy);

Assert.AreEqual("?sig=test_signature_value", transport.SingleRequest.Uri.Query);
}

[TestCase("sig=test_signature_value")]
[TestCase("?sig=test_signature_value")]
public async Task SetsSignatureNonEmptyQuery(string signatureValue)
{
var transport = new MockTransport(new MockResponse(200));
var sasPolicy = new AzureSasCredentialSynchronousPolicy(new AzureSasCredential(signatureValue));
string query = "?foo=bar";

await SendGetRequest(transport, sasPolicy, query: query);

Assert.AreEqual($"?foo=bar&sig=test_signature_value", transport.SingleRequest.Uri.Query);
}

[TestCase("sig=test_signature_value")]
[TestCase("?sig=test_signature_value")]
public async Task VerifyRetryEmptyQuery(string signatureValue)
{
var transport = new MockTransport(new MockResponse(200), new MockResponse(200));
var sasPolicy = new AzureSasCredentialSynchronousPolicy(new AzureSasCredential(signatureValue));

using (Request request = transport.CreateRequest())
{
request.Method = RequestMethod.Get;
var pipeline = new HttpPipeline(transport, new[] { sasPolicy });
await pipeline.SendRequestAsync(request, CancellationToken.None);
await pipeline.SendRequestAsync(request, CancellationToken.None);
}

Assert.AreEqual("?sig=test_signature_value", transport.Requests[0].Uri.Query);
}

[TestCase("sig=test_signature_value")]
[TestCase("?sig=test_signature_value")]
public async Task VerifyRetryNonEmptyQuery(string signatureValue)
{
var transport = new MockTransport(new MockResponse(200), new MockResponse(200));
var sasPolicy = new AzureSasCredentialSynchronousPolicy(new AzureSasCredential(signatureValue));
string query = "?foo=bar";

using (Request request = transport.CreateRequest())
{
request.Method = RequestMethod.Get;
request.Uri.Query = query;
var pipeline = new HttpPipeline(transport, new[] { sasPolicy });
await pipeline.SendRequestAsync(request, CancellationToken.None);
await pipeline.SendRequestAsync(request, CancellationToken.None);
}

Assert.AreEqual("?foo=bar&sig=test_signature_value", transport.Requests[0].Uri.Query);
}
}
}
3 changes: 2 additions & 1 deletion sdk/core/Azure.Core/tests/PolicyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ namespace Azure.Core.Tests
{
public abstract class PolicyTestBase
{
protected static async Task<Response> SendGetRequest(HttpPipelineTransport transport, HttpPipelinePolicy policy, ResponseClassifier responseClassifier = null)
protected static async Task<Response> SendGetRequest(HttpPipelineTransport transport, HttpPipelinePolicy policy, ResponseClassifier responseClassifier = null, string query = null)
{
Assert.IsInstanceOf<HttpPipelineSynchronousPolicy>(policy, "Use SyncAsyncPolicyTestBase base type for non-sync policies");

using (Request request = transport.CreateRequest())
{
request.Method = RequestMethod.Get;
request.Uri.Reset(new Uri("http://example.com"));
request.Uri.Query = query;
var pipeline = new HttpPipeline(transport, new[] { policy }, responseClassifier);
return await pipeline.SendRequestAsync(request, CancellationToken.None);
}
Expand Down