Skip to content

Commit

Permalink
feat: Use self-signed JWTs in Google.Api.Gax.Rest
Browse files Browse the repository at this point in the history
This is a no-op by default, but allows libraries to create
ScopedCredentialProvider instances that apply self-signed JWTs to
credentials. After updating to a new version of GAX, the three
libraries in google-cloud-dotnet that use this will need trivial
changes to support it.
  • Loading branch information
jskeet committed Oct 29, 2021
1 parent a5af75b commit b52107e
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 5 deletions.
14 changes: 13 additions & 1 deletion Google.Api.Gax.Rest.Tests/ScopedCredentialProviderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void GetCredentials_EmptyScopes_NoOp()
}

[Fact]
public void GetCredentials_ScopesApplied()
public void GetCredentials_ScopesApplied_UnspecifiedUseJwts()
{
var provider = new ScopedCredentialProvider(new[] { "abc" });
var originalCredentials = CreateServiceCredentials();
Expand All @@ -54,6 +54,18 @@ public void GetCredentials_ScopesApplied()
Assert.NotSame(originalCredentials, provided);
}

[Theory, CombinatorialData]
public void GetCredentials_ScopesApplied_UseJwtWithScopesSpecified(bool useJwtWithScopes)
{
var provider = new ScopedCredentialProvider(new[] { "abc" }, useJwtWithScopes);
var originalCredentials = CreateServiceCredentials();
var provided = provider.GetCredentials(originalCredentials);
Assert.NotSame(originalCredentials, provided);
var serviceAccount = provided.UnderlyingCredential as ServiceAccountCredential;
Assert.NotNull(serviceAccount);
Assert.Equal(useJwtWithScopes, serviceAccount.UseJwtAccessWithScopes);
}

[Fact]
public async Task GetCredentialsAsync_EmptyScopes_NoOp()
{
Expand Down
30 changes: 26 additions & 4 deletions Google.Api.Gax.Rest/ScopedCredentialProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,32 @@ public sealed class ScopedCredentialProvider
/// </summary>
private readonly Lazy<Task<GoogleCredential>> _lazyScopedDefaultCredentials;
private readonly List<string> _scopes;
private readonly bool _useJwtWithScopes;

/// <summary>
/// Creates a channel pool which will apply the specified scopes to the credentials if they require any.
/// </summary>
/// <param name="scopes">The scopes to apply. Must not be null, and must not contain null references. May be empty.</param>
public ScopedCredentialProvider(IEnumerable<string> scopes)
/// <param name="useJwtWithScopes">A flag preferring use of self-signed JWTs over OAuth tokens when OAuth scopes are explicitly set.</param>
public ScopedCredentialProvider(IEnumerable<string> scopes, bool useJwtWithScopes)
{
// Always take a copy of the provided scopes, then check the copy doesn't contain any nulls.
_scopes = GaxPreconditions.CheckNotNull(scopes, nameof(scopes)).ToList();
GaxPreconditions.CheckArgument(!_scopes.Any(x => x == null), nameof(scopes), "Scopes must not contain any null references");
_useJwtWithScopes = useJwtWithScopes;
_lazyScopedDefaultCredentials = new Lazy<Task<GoogleCredential>>(() => Task.Run(CreateDefaultCredentialsUncached));
}

/// <summary>
/// Creates a channel pool which will apply the specified scopes to the credentials if they require any.
/// A provider created with this overload is equivalent to calling <see cref="ScopedCredentialProvider(IEnumerable{string}, bool)"/>
/// with a second argument of <c>false</c>.
/// </summary>
/// <param name="scopes">The scopes to apply. Must not be null, and must not contain null references. May be empty.</param>
public ScopedCredentialProvider(IEnumerable<string> scopes) : this(scopes, false)
{
}

/// <summary>
/// Returns credentials with the scopes applied if required.
/// </summary>
Expand Down Expand Up @@ -74,11 +87,20 @@ private async Task<GoogleCredential> CreateDefaultCredentialsUncached()
return ApplyScopes(credentials);
}

/// <summary>
/// Applies scopes when they're available, and potentially specifies a preference for
/// using self-signed JWTs.
/// </summary>
private GoogleCredential ApplyScopes(GoogleCredential original)
{
return original.IsCreateScopedRequired && _scopes.Count > 0
? original.CreateScoped(_scopes)
: original;
if (!original.IsCreateScopedRequired || _scopes.Count == 0)
{
return original;
}
var scoped = original.CreateScoped(_scopes);
return _useJwtWithScopes && scoped.UnderlyingCredential is ServiceAccountCredential serviceCredential
? GoogleCredential.FromServiceAccountCredential(serviceCredential.WithUseJwtAccessWithScopes(true))
: scoped;
}

// Note: this is duplicated in Google.Apis.Auth, Google.Apis.Core and Google.Api.Gax.Grpc as well so it can stay internal.
Expand Down

0 comments on commit b52107e

Please sign in to comment.