From 343a743b0c6ffb9d20d85fc9383e8861fad38145 Mon Sep 17 00:00:00 2001 From: Amanda Tarafa Mas Date: Mon, 4 Mar 2024 15:41:04 -0800 Subject: [PATCH] feat: Allows creating UrlSigner directly from a StorageClient. The created UrlSigner will use the same credential and base URI as the StorageClient it is created from. --- .../Conformance/V4SignerConformanceTest.cs | 41 ++++++++++++++++--- .../UrlSignerTest.cs | 2 + .../Google.Cloud.Storage.V1/StorageClient.cs | 35 ++++++++++++++++ .../Google.Cloud.Storage.V1/UrlSigner.cs | 19 +++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/Conformance/V4SignerConformanceTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/Conformance/V4SignerConformanceTest.cs index 51d564999d94..9b499cbe32a2 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/Conformance/V4SignerConformanceTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/Conformance/V4SignerConformanceTest.cs @@ -27,9 +27,18 @@ namespace Google.Cloud.Storage.V1.Tests.Conformance { public class V4SignerConformanceTest { - public static TheoryData 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 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 for the time beeing. + + public static TheoryData 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 V4PostPolicyTestData { get; } = StorageConformanceTestData.TestData.GetTheoryData(f => f.PostPolicyV4Tests); private static readonly Dictionary s_methods = new Dictionary @@ -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.UrlSigner; + + 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) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/UrlSignerTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/UrlSignerTest.cs index a2354a32f1d4..b07a4924991f 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/UrlSignerTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/UrlSignerTest.cs @@ -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; @@ -48,6 +49,7 @@ public void FromCredential_Validations() Assert.Throws(() => UrlSigner.FromCredential((ComputeCredential) null)); Assert.Throws(() => UrlSigner.FromCredential((ServiceAccountCredential) null)); Assert.Throws(() => UrlSigner.FromCredential((ImpersonatedCredential) null)); + Assert.Throws(() => UrlSigner.FromCredential((IHttpExecuteInterceptor) null)); } [Fact] diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.cs index 460377b95fe8..8c0d15a7b983 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.cs @@ -66,6 +66,41 @@ public abstract partial class StorageClient : IDisposable /// public virtual EncryptionKey EncryptionKey { get { throw new NotImplementedException(); } } + /// + /// A URL signer built base on this client, that is using the same credential as this client, as well + /// as defaulting to this client's URI scheme and host. If the credential used by this client is not compatible + /// with this will be null. See 's documentation for more information + /// on compatible credentials. + /// + /// + /// Because credentials used by this client may changed, this property will return a new instance + /// each time it is called. + /// + public UrlSigner UrlSigner + { + get + { + UrlSigner signer; + try + { + signer = UrlSigner.FromCredential(Service.HttpClient.MessageHandler.Credential); + } + catch (InvalidOperationException) + { + // The credential does not support signing. + return null; + } + + var baseUri = new Uri(Service.BaseUri); + return signer.WithDefaultOptionsOverride(new UrlSigner.DefaultOptionsOverrides( + baseUri.Scheme, baseUri.Host, + // 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. + baseUri.IsDefaultPort && !Service.BaseUri.Contains(baseUri.Port.ToString()) ? null : baseUri.Port)); + } + } + /// /// Asynchronously creates a using application default credentials. /// For any non-default values, please use . diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UrlSigner.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UrlSigner.cs index 5dbe81715c83..6b0364c9538b 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UrlSigner.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UrlSigner.cs @@ -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; @@ -135,6 +136,24 @@ public static UrlSigner FromCredential(ComputeCredential credential) _ => throw new InvalidOperationException($"The credential type {credential.UnderlyingCredential.GetType()} is not supported for signing.") }; + /// + /// Creates a new instace for a if + /// the is of a type supported for signing. + /// + /// + /// The type of is not supported for signing. + /// + 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.") + }; + /// /// Creates a new instance from the JSON configuration file of a Google credential. /// See for more information about supported credential types.