Skip to content

Commit

Permalink
(DO NOT MERGE) Prototype of API key support in GAX
Browse files Browse the repository at this point in the history
The intention is to make it easy for APIs which *do* support API
keys to expose that, without providing misleading properties to
other client builders.
  • Loading branch information
jskeet committed Oct 25, 2023
1 parent 3a37991 commit 2adacca
Showing 1 changed file with 41 additions and 5 deletions.
46 changes: 41 additions & 5 deletions Google.Api.Gax.Grpc/ClientBuilderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ protected EmulatorDetection EmulatorDetection
set => _emulatorDetection = GaxPreconditions.CheckEnumValue(value, nameof(value));
}

/// <summary>
/// An API key to use as an alternative to a full credential.
/// </summary>
/// <remarks>
/// This is protected as not all APIs support API keys. APIs which support API keys
/// should declare a new public property (also called ApiKey) in the concrete client builder class,
/// and ensure they call <see cref="GetEffectiveSettings{T}(T)"/> to potentially specify the API key header
/// via CallSettings.
/// </remarks>
protected string ApiKey { get; set; }

/// <summary>
/// The GCP project ID that should be used for quota and billing purposes.
/// May be null.
Expand Down Expand Up @@ -198,6 +209,30 @@ protected void CopySettingsForEmulator(ClientBuilderBase<TClient> source)
Logger = source.Logger;
}

/// <summary>
/// Returns the effective settings for this builder, taking into account API keys and any other properties
/// which may require additional settings (typically via <see cref="ServiceSettingsBase.CallSettings"/>).
/// </summary>
/// <remarks>This method only needs to be called if the concrete builder type knows that the settings may
/// need to be modified (e.g. if the API supports API keys). It should typically be called as
/// <c>GetEffectiveSettings(Settings?.Clone())</c>.</remarks>
/// <typeparam name="T">The concrete settings type, derived from <see cref="ServiceSettingsBase"/>, with a
/// parameterless constructor that can be used to construct a new default instance.</typeparam>
/// <param name="settings">A clone of the existing settings specified in the concrete builder type. May be null.</param>
/// <returns>The appropriate effective settings for this builder, or null if no settings have been
/// provided and no other properties require additional settings. Note that clone operations are provided
/// on a per-concrete-type basis, so this method must accept already-cloned settings.</returns>
protected T GetEffectiveSettings<T>(T settings) where T : ServiceSettingsBase, new()
{
if (ApiKey is null)
{
return settings;
}
settings ??= new T();
settings.CallSettings = settings.CallSettings.WithHeader("X-Goog-Api-Key", ApiKey);
return settings;
}

/// <summary>
/// Validates that the builder is in a consistent state for building. For example, it's invalid to call
/// <see cref="Build()"/> on an instance which has both JSON credentials and a credentials path specified.
Expand All @@ -211,14 +246,14 @@ protected virtual void Validate()
ChannelCredentials, CredentialsPath, JsonCredentials, Scopes, Endpoint, TokenAccessMethod, GoogleCredential, Credential);

ValidateAtMostOneNotNull("Only one source of credentials can be specified",
ChannelCredentials, CredentialsPath, JsonCredentials, TokenAccessMethod, GoogleCredential, Credential);
ChannelCredentials, CredentialsPath, JsonCredentials, TokenAccessMethod, GoogleCredential, Credential, ApiKey);

ValidateOptionExcludesOthers("Scopes are not relevant when a token access method, channel credentials or ICredential are supplied", Scopes,
TokenAccessMethod, ChannelCredentials, Credential);
ValidateOptionExcludesOthers("Scopes are not relevant when a token access method, channel credentials, ICredential or ApiKey are supplied", Scopes,
TokenAccessMethod, ChannelCredentials, Credential, ApiKey);
#pragma warning restore CS0618 // Type or member is obsolete

ValidateOptionExcludesOthers($"{nameof(QuotaProject)} cannot be specified if a {nameof(CallInvoker)}, {nameof(ChannelCredentials)} or {nameof(Credential)} is specified", QuotaProject,
CallInvoker, ChannelCredentials, Credential);
ValidateOptionExcludesOthers($"{nameof(QuotaProject)} cannot be specified if a {nameof(CallInvoker)}, {nameof(ChannelCredentials)}, {nameof(Credential)} or {nameof(ApiKey)} is specified", QuotaProject,
CallInvoker, ChannelCredentials, Credential, ApiKey);
}

/// <summary>
Expand Down Expand Up @@ -424,6 +459,7 @@ protected async virtual Task<ChannelCredentials> GetChannelCredentialsAsync(Canc
/// </summary>
private ChannelCredentials MaybeGetSimpleChannelCredentials() =>
ChannelCredentials ?? Credential?.ToChannelCredentials() ??
(ApiKey is not null ? ChannelCredentials.SecureSsl : null) ??
#pragma warning disable CS0618 // Type or member is obsolete
(TokenAccessMethod is not null ? new DelegatedTokenAccess(TokenAccessMethod, QuotaProject).ToChannelCredentials() : null);
#pragma warning restore CS0618 // Type or member is obsolete
Expand Down

0 comments on commit 2adacca

Please sign in to comment.