Skip to content

Commit

Permalink
feat: Support overriding some URLSigner.Options default values.
Browse files Browse the repository at this point in the history
This will allow creating signers from StorageClients.
  • Loading branch information
amanda-tarafa committed Mar 6, 2024
1 parent adaa7c4 commit 4cc8131
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 31 deletions.
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using Xunit;
using static Google.Cloud.Storage.V1.UrlSigner;

Expand Down Expand Up @@ -240,6 +241,45 @@ public void WithPort_Null()
Assert.NotSame(options, newOptions);
Assert.Null(newOptions.Port);
}

[Fact]
public void WithDefaultOverrides_NullOverrideValues()
{
var options = Options.FromDuration(TimeSpan.FromMinutes(1));

var newOptions = options.WithDefaultOptionsOverrides(new DefaultOptionsOverrides(null, null, null));

Assert.NotSame(options, newOptions);
Assert.Equal(Options.DefaultScheme, newOptions.Scheme);
Assert.Equal(Options.DefaultStorageHost, newOptions.Host);
Assert.Null(newOptions.Port);
}

[Fact]
public void WithDefaultOverrides()
{
var options = Options.FromDuration(TimeSpan.FromMinutes(1));

var newOptions = options.WithDefaultOptionsOverrides(new DefaultOptionsOverrides("http", "overrideHost", 1234));

Assert.NotSame(options, newOptions);
Assert.Equal("http", newOptions.Scheme);
Assert.Equal("overrideHost", newOptions.Host);
Assert.Equal(1234, newOptions.Port);
}

[Fact]
public void WithDefaultOverrides_DoesNotOverrideExplicit()
{
var options = Options.FromDuration(TimeSpan.FromMinutes(1)).WithScheme("https").WithHost("explicitHost").WithPort(5678);

var newOptions = options.WithDefaultOptionsOverrides(new DefaultOptionsOverrides("http", "overrideHost", 1234));

Assert.NotSame(options, newOptions);
Assert.Equal("https", newOptions.Scheme);
Assert.Equal("explicitHost", newOptions.Host);
Assert.Equal(5678, newOptions.Port);
}
}
}
}
@@ -0,0 +1,54 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License").
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Google.Cloud.Storage.V1;
public sealed partial class UrlSigner
{
/// <summary>
/// Overrides for default values for some of the options in <see cref="Options"/>.
/// Useful for creating signers where signing options default to specific values, for instance
/// for creating signers from a <see cref="StorageClient"/> where the scheme, host and port should
/// default to that of the client.
/// </summary>
internal sealed class DefaultOptionsOverrides
{
internal DefaultOptionsOverrides(string scheme, string host, int? port)
{
Scheme = scheme;
Host = host;
Port = port;
}

/// <summary>
/// A scheme to be used by the <see cref="UrlSigner"/> signing operations if
/// <see cref="Options.ExplicitScheme"/> is null. May be null in which case
/// <see cref="Options.DefaultScheme"/> will be used.
/// </summary>
internal string Scheme { get; }

/// <summary>
/// A host to be used by the <see cref="UrlSigner"/> signing operations if
/// <see cref="Options.ExplicitHost"/> is null. May be null in which case
/// <see cref="Options.DefaultStorageHost"/> will be used.
/// </summary>
internal string Host { get; }

/// <summary>
/// A port to be used by the <see cref="UrlSigner"/> signing operations if
/// <see cref="Options.ExplicitPort"/> is null. May be null in which case
/// no port will be used.
/// </summary>
internal int? Port { get; }
}
}
Expand Up @@ -25,7 +25,8 @@ public sealed partial class UrlSigner
/// </summary>
public sealed class Options
{
private const string DefaultStorageHost = "storage.googleapis.com";
internal const string DefaultScheme = "https";
internal const string DefaultStorageHost = "storage.googleapis.com";

/// <summary>
/// The length of time for which the signed URL should remain usable,
Expand Down Expand Up @@ -64,25 +65,42 @@ public sealed class Options
/// <summary>
/// The Scheme to use for the request. Only http or https supported.
/// This will never be null. If null is specified in <see cref="WithScheme(string)"/>
/// then https will be used.
/// Defaults to https.
/// an appropriate default value will be used.
/// </summary>
public string Scheme { get; }
public string Scheme => ExplicitScheme ?? DefaultOptionsOverrides?.Scheme ?? DefaultScheme;

/// <summary>
/// The scheme value originally set by client code via <see cref="WithScheme(string)"/> or null
/// if the user hasn't set an explicit value. May be null.
/// </summary>
private string ExplicitScheme { get; }

/// <summary>
/// The host to use for generating the signed URL.
/// This will never be null. If null is specified in <see cref="WithHost(string)"/>
/// then <see cref="DefaultStorageHost"/> will be used.
/// then the appropriate default value will be used.
/// Will be ignored if <see cref="UrlStyle"/> is set to <see cref="UrlStyle.BucketBoundHostname"/>.
/// </summary>
public string Host { get; }
public string Host => ExplicitHost ?? DefaultOptionsOverrides?.Host ?? DefaultStorageHost;

/// <summary>
/// The host value originally set by client code via <see cref="WithHost(string)"/> or null
/// if the user hasn't set an explicit value. May be null.
/// </summary>
private string ExplicitHost { get; }

/// <summary>
/// The port for the signed URL. The port is not included on the signature itself, only on the
/// resulting signed URL. Defaults to null.
/// Will be ignored if <see cref="UrlStyle"/> is set to <see cref="UrlStyle.BucketBoundHostname"/>.
/// </summary>
public int? Port { get; }
public int? Port => ExplicitPort ?? DefaultOptionsOverrides?.Port;

/// <summary>
/// The port value originally set by client code via <see cref="WithPort(int?)"/> or null
/// if the user hasn't set an explicit value. May be null.
/// </summary>
private int? ExplicitPort { get; }

internal string HostAndPort => Port is null ? Host : $"{Host}:{Port}";

Expand All @@ -100,8 +118,16 @@ public sealed class Options
/// </remarks>
public string BucketBoundHostname { get; }

private Options(
TimeSpan? duration, DateTimeOffset? expiration, SigningVersion version, UrlStyle? urlStyle, string scheme, string host, int? port, string bucketBoundHostname)
/// <summary>
/// Values to be used as default for some options in case they are not set,
/// instead of the usual default values. May be null.
/// </summary>
internal DefaultOptionsOverrides DefaultOptionsOverrides { get; }

private Options(TimeSpan? duration, DateTimeOffset? expiration,
SigningVersion version,
UrlStyle? urlStyle, string scheme, string host, int? port, string bucketBoundHostname,
DefaultOptionsOverrides defaultOptionsOverrides)
{
GaxPreconditions.CheckArgument(
duration.HasValue != expiration.HasValue,
Expand Down Expand Up @@ -131,10 +157,11 @@ public sealed class Options
Expiration = expiration;
SigningVersion = version;
UrlStyle = urlStyle ?? UrlStyle.PathStyle;
Scheme = scheme ?? "https";
Host = host ?? DefaultStorageHost;
Port = port;
BucketBoundHostname = bucketBoundHostname;
ExplicitScheme = scheme;
ExplicitHost = host;
ExplicitPort = port;
BucketBoundHostname = bucketBoundHostname;
DefaultOptionsOverrides = defaultOptionsOverrides;
}

/// <summary>
Expand All @@ -143,15 +170,15 @@ public sealed class Options
/// <param name="duration">The duration to create these options with.</param>
/// <returns>A new options set.</returns>
public static Options FromDuration(TimeSpan duration) =>
new Options(duration, null, SigningVersion.Default, null, null, null, null, null);
new Options(duration, null, SigningVersion.Default, null, null, null, null, null, null);

/// <summary>
/// Creates a new <see cref="UrlSigner.Options"/> from the given expiration.
/// </summary>
/// <param name="expiration">The expiration to create these options with.</param>
/// <returns>A new options set.</returns>
public static Options FromExpiration(DateTimeOffset expiration) =>
new Options(null, expiration, SigningVersion.Default, null, null, null, null, null);
new Options(null, expiration, SigningVersion.Default, null, null, null, null, null, null);

/// <summary>
/// If this set of options was duration based, this method will return a new set
Expand All @@ -170,23 +197,23 @@ public sealed class Options
/// <param name="duration">The new duration.</param>
/// <returns>A new set of options with the given duration.</returns>
public Options WithDuration(TimeSpan duration) =>
new Options(duration, null, SigningVersion, UrlStyle, Scheme, Host, Port, BucketBoundHostname);
new Options(duration, null, SigningVersion, UrlStyle, ExplicitScheme, ExplicitHost, ExplicitPort, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one but expiration based.
/// </summary>
/// <param name="expiration">The new expiration.</param>
/// <returns>A new set of options with the given expiration.</returns>
public Options WithExpiration(DateTimeOffset expiration) =>
new Options(null, expiration, SigningVersion, UrlStyle, Scheme, Host, Port, BucketBoundHostname);
new Options(null, expiration, SigningVersion, UrlStyle, ExplicitScheme, ExplicitHost, ExplicitPort, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the signing version.
/// </summary>
/// <param name="version">The new signing version.</param>
/// <returns>A set of options with the given signing version.</returns>
public Options WithSigningVersion(SigningVersion version) =>
new Options(Duration, Expiration, GaxPreconditions.CheckEnumValue(version, nameof(version)), UrlStyle, Scheme, Host, Port, BucketBoundHostname);
new Options(Duration, Expiration, GaxPreconditions.CheckEnumValue(version, nameof(version)), UrlStyle, ExplicitScheme, ExplicitHost, ExplicitPort, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the
Expand All @@ -196,31 +223,31 @@ public sealed class Options
/// <param name="urlStyle">The new url style.</param>
/// <returns>A new set ofoptions with the given url style.</returns>
public Options WithUrlStyle(UrlStyle urlStyle) =>
new Options(Duration, Expiration, SigningVersion, urlStyle, Scheme, Host, Port, null);
new Options(Duration, Expiration, SigningVersion, urlStyle, ExplicitScheme, ExplicitHost, ExplicitPort, null, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the scheme.
/// </summary>
/// <param name="scheme">The new scheme. May be null in which case https will be used.</param>
/// <returns>A new set of options with the given scheme.</returns>
public Options WithScheme(string scheme) =>
new Options(Duration, Expiration, SigningVersion, UrlStyle, scheme, Host, Port, BucketBoundHostname);
new Options(Duration, Expiration, SigningVersion, UrlStyle, scheme, ExplicitHost, ExplicitPort, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the host.
/// </summary>
/// <param name="host">The new host. May be null in which case <see cref="DefaultStorageHost"/> will be used.</param>
/// <returns>A new set of options with the given host.</returns>
public Options WithHost(string host) =>
new Options(Duration, Expiration, SigningVersion, UrlStyle, Scheme, host, Port, BucketBoundHostname);
new Options(Duration, Expiration, SigningVersion, UrlStyle, ExplicitScheme, host, ExplicitPort, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the port.
/// </summary>
/// <param name="port">The new port. May be null.</param>
/// <returns>A new set of options with the given host.</returns>
public Options WithPort(int? port) =>
new Options(Duration, Expiration, SigningVersion, UrlStyle, Scheme, Host, port, BucketBoundHostname);
new Options(Duration, Expiration, SigningVersion, UrlStyle, ExplicitScheme, ExplicitHost, port, BucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for bucket bound domain
Expand All @@ -230,7 +257,13 @@ public sealed class Options
/// <returns>A new set of options with the given bucket bound domain and the url style set to
/// <see cref="UrlStyle.BucketBoundHostname"/>.</returns>
public Options WithBucketBoundHostname(string bucketBoundHostname) =>
new Options(Duration, Expiration, SigningVersion, UrlStyle.BucketBoundHostname, Scheme, Host, Port, bucketBoundHostname);
new Options(Duration, Expiration, SigningVersion, UrlStyle.BucketBoundHostname, ExplicitScheme, ExplicitHost, ExplicitPort, bucketBoundHostname, DefaultOptionsOverrides);

/// <summary>
/// Returns a new set of options with the same values as this one except for the default options overrides.
/// </summary>
internal Options WithDefaultOptionsOverrides(DefaultOptionsOverrides defaultOptionsOverrides) =>
new Options(Duration, Expiration, SigningVersion, UrlStyle, ExplicitScheme, ExplicitHost, ExplicitPort, BucketBoundHostname, defaultOptionsOverrides);
}
}
}

0 comments on commit 4cc8131

Please sign in to comment.