Skip to content

Commit

Permalink
feat: Allows creating UrlSigner directly from a StorageClient.
Browse files Browse the repository at this point in the history
The created UrlSigner will use the same credential and base URI as the StorageClient it is created from.
  • Loading branch information
amanda-tarafa committed Mar 6, 2024
1 parent 3c2ecfb commit fc00fa2
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@ namespace Google.Cloud.Storage.V1.Tests.Conformance
{
public class V4SignerConformanceTest
{
public static TheoryData<SigningV4Test> V4SigningTestData { get; } = StorageConformanceTestData.TestData.GetTheoryData(f =>
// We skip test data with features that we don't support.
f.SigningV4Tests.Where(data => data.EmulatorHostname == "" && data.ClientEndpoint == "" && data.UniverseDomain == ""));
public static TheoryData<SigningV4Test> V4SignerTestData { get; } = StorageConformanceTestData.TestData.GetTheoryData(f =>
// We skip test data with features that we don't support or test elsewhere.
f.SigningV4Tests.Where(data =>
data.EmulatorHostname == "" // We don't support signers for emulators.
&& data.ClientEndpoint == "" // This is tested in StorageClientSignerTest.
&& data.UniverseDomain == "")); // This is tested in StorageClientSignerTest.

public static TheoryData<SigningV4Test> StorageClientSignerTestData { get; } = StorageConformanceTestData.TestData.GetTheoryData(f =>
// We only get test data with features that are relevant for testing the StorageClient Signer.
f.SigningV4Tests.Where(
data => data.EmulatorHostname == "" // We don't support signers for emulators.
&& (data.ClientEndpoint != "" || data.UniverseDomain != "")));
public static TheoryData<PostPolicyV4Test> V4PostPolicyTestData { get; } = StorageConformanceTestData.TestData.GetTheoryData(f => f.PostPolicyV4Tests);

private static readonly Dictionary<string, HttpMethod> s_methods = new Dictionary<string, HttpMethod>
Expand All @@ -39,12 +48,32 @@ public class V4SignerConformanceTest
{ "PUT", HttpMethod.Put }
};

[Theory, MemberData(nameof(V4SigningTestData))]
public void SigningTest(SigningV4Test test)
[Theory, MemberData(nameof(V4SignerTestData))]
public void V4SignerTest(SigningV4Test test) =>
SignerTest(test, UrlSigner.FromCredential(StorageConformanceTestData.TestCredential));

[Theory, MemberData(nameof(StorageClientSignerTestData))]
public void StorageClientSignerTest(SigningV4Test test)
{
var storageClient = new StorageClientBuilder
{
UniverseDomain = test.UniverseDomain == "" ? null : test.UniverseDomain,
BaseUri = test.ClientEndpoint == "" ? null :
// We may need to format client endpoints from tests, for instance storage.googleapis.com:443,
// as the library only accepts the base full URI, which needs to include the scheme.
(test.ClientEndpoint.StartsWith("http") ? test.ClientEndpoint : $"https://{test.ClientEndpoint}"),
Credential = StorageConformanceTestData.TestCredential,
}.Build();
var signer = storageClient.CreateUrlSigner();

SignerTest(test, signer);
}

private void SignerTest(SigningV4Test test, UrlSigner signer)
{
var timestamp = test.Timestamp.ToDateTime();
var clock = new FakeClock(timestamp);
var signer = UrlSigner.FromCredential(StorageConformanceTestData.TestCredential).WithClock(clock);
signer = signer.WithClock(clock);

var requestTemplate = RequestTemplate
.FromBucket(test.Bucket)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using Google.Apis.Auth.OAuth2;
using Google.Apis.Http;
using System;
using System.IO;
using System.Security.Cryptography;
Expand Down Expand Up @@ -48,6 +49,7 @@ public void FromCredential_Validations()
Assert.Throws<ArgumentNullException>(() => UrlSigner.FromCredential((ComputeCredential) null));
Assert.Throws<ArgumentNullException>(() => UrlSigner.FromCredential((ServiceAccountCredential) null));
Assert.Throws<ArgumentNullException>(() => UrlSigner.FromCredential((ImpersonatedCredential) null));
Assert.Throws<ArgumentNullException>(() => UrlSigner.FromCredential((IHttpExecuteInterceptor) null));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,26 @@ public abstract partial class StorageClient : IDisposable
UnauthenticatedAccess = true
}.Build();

/// <summary>
/// Creates a URL signer based on this client, that uses the same credential as this client, as well
/// as defaulting to this client's URI scheme, host and port.
/// </summary>
/// <exception cref="InvalidOperationException">
/// If the credential used by this client is not compatible with <see cref="UrlSigner"/>.
/// See <see cref="UrlSigner"/>'s documentation for more information on compatible credentials.
/// </exception>
public UrlSigner CreateUrlSigner()
{
UrlSigner signer = UrlSigner.FromCredential(Service.HttpClient.MessageHandler.Credential);

var baseUri = new Uri(Service.BaseUri);
// If the original URI didn't specify a port, we want signed URLs to not have a port either;
// but Uri.Port returns the default port for the URI scheme when the port was not specified on the
// original URI.
int? port = baseUri.IsDefaultPort && !Service.BaseUri.Contains(baseUri.Port.ToString()) ? null : baseUri.Port;
return signer.WithDefaultOptionsOverride(new UrlSigner.DefaultOptionsOverrides(baseUri.Scheme, baseUri.Host, port));
}

/// <summary>
/// Dispose of this instance. See the <see cref="StorageClient"/> remarks on when this should be called.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UrlSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using Google.Api.Gax;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Http;
using System;
using System.IO;
using System.Net.Http;
Expand Down Expand Up @@ -135,6 +136,24 @@ public static UrlSigner FromCredential(ComputeCredential credential)
_ => throw new InvalidOperationException($"The credential type {credential.UnderlyingCredential.GetType()} is not supported for signing.")
};

/// <summary>
/// Creates a new <see cref="UrlSigner"/> instace for a <see cref="IHttpExecuteInterceptor"/> if
/// the <paramref name="credential"/> is of a type supported for signing.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The type of <paramref name="credential"/> is not supported for signing.
/// </exception>
internal static UrlSigner FromCredential(IHttpExecuteInterceptor credential) =>
GaxPreconditions.CheckNotNull(credential, nameof(credential)) switch
{
GoogleCredential gc => FromCredential(gc),
ServiceAccountCredential sa => FromCredential(sa),
ImpersonatedCredential imp => FromCredential(imp),
ComputeCredential comp => FromCredential(comp),
IBlobSigner blobSigner => FromBlobSigner(blobSigner),
_ => throw new InvalidOperationException($"The type {credential.GetType()} is not supported for signing.")
};

/// <summary>
/// Creates a new <see cref="UrlSigner"/> instance from the JSON configuration file of a Google credential.
/// See <see cref="FromCredential(GoogleCredential)"/> for more information about supported credential types.
Expand Down

0 comments on commit fc00fa2

Please sign in to comment.