Skip to content

Commit 3649f08

Browse files
fix(Spanner.Data): Specify credential type when loading from file.
1 parent f6be971 commit 3649f08

5 files changed

Lines changed: 87 additions & 21 deletions

File tree

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/SpannerClientCreationOptionsTest.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ public async Task CredentialFileRelative()
131131
Assert.NotNull(await options.CreateSpannerClientAsync(new Spanner.V1.SpannerSettings()));
132132
}
133133

134+
[Fact]
135+
public async Task CredentialFile_WithWrongCredentialType_Fails()
136+
{
137+
var builder = new SpannerConnectionStringBuilder("CredentialFile=SpannerEF-8dfc036f6000.json;CredentialType=authorized_user");
138+
var options = new SpannerClientCreationOptions(builder);
139+
await Assert.ThrowsAsync<InvalidOperationException>(() => options.CreateSpannerClientAsync(new Spanner.V1.SpannerSettings()));
140+
}
141+
142+
[Fact]
143+
public async Task CredentialFile_WithCorrectCredentialType_Succeeds()
144+
{
145+
var builder = new SpannerConnectionStringBuilder("CredentialFile=SpannerEF-8dfc036f6000.json;CredentialType=service_account");
146+
var options = new SpannerClientCreationOptions(builder);
147+
Assert.NotNull(await options.CreateSpannerClientAsync(new Spanner.V1.SpannerSettings()));
148+
}
149+
134150
[Fact]
135151
public async Task CredentialFileP12Error()
136152
{

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/SpannerConnectionStringBuilderTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,5 +373,33 @@ public void IsolationLevelIsConvertedToEnum()
373373

374374
Assert.Equal(System.Data.IsolationLevel.RepeatableRead, builder.IsolationLevel);
375375
}
376+
377+
[Fact]
378+
public void CredentialType_Default()
379+
{
380+
var builder = new SpannerConnectionStringBuilder();
381+
Assert.Equal(JsonCredentialParameters.ServiceAccountCredentialType, builder.CredentialType);
382+
}
383+
384+
[Fact]
385+
public void CredentialType_Explicit()
386+
{
387+
var builder = new SpannerConnectionStringBuilder("CredentialType=authorized_user");
388+
Assert.Equal("authorized_user", builder.CredentialType);
389+
builder.CredentialType = "service_account";
390+
Assert.Equal("service_account", builder.CredentialType);
391+
// DbConnectionStringBuilder lower-cases keywords, annoyingly.
392+
Assert.Equal("credentialtype=service_account", builder.ToString());
393+
}
394+
395+
[Fact]
396+
public void CredentialType_NullSet_RestoresDefault()
397+
{
398+
var builder = new SpannerConnectionStringBuilder();
399+
builder.CredentialType = "authorized_user";
400+
Assert.Equal("authorized_user", builder.CredentialType);
401+
builder.CredentialType = null;
402+
Assert.Equal(JsonCredentialParameters.ServiceAccountCredentialType, builder.CredentialType);
403+
}
376404
}
377405
}

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerClientCreationOptions.cs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using Google.Api.Gax.Grpc.Gcp;
16+
using Google.Apis.Auth.OAuth2;
1617
using Google.Cloud.Spanner.Admin.Database.V1;
1718
using Google.Cloud.Spanner.V1;
1819
using System;
@@ -32,16 +33,17 @@ internal sealed class SpannerClientCreationOptions : IEquatable<SpannerClientCre
3233

3334
internal bool UsesEmulator { get; }
3435

36+
private readonly string _credentialFile;
37+
private readonly string _credentialType;
38+
private readonly Lazy<GoogleCredential> _effectiveGoogleCredential;
39+
3540
internal SpannerClientCreationOptions(SpannerConnectionStringBuilder builder)
3641
{
3742
var clientBuilder = new SpannerClientBuilder
3843
{
3944
EmulatorDetection = builder.EmulatorDetection,
4045
EnvironmentVariableProvider = builder.EnvironmentVariableProvider,
4146
Endpoint = builder.ContainsKey(nameof(builder.Host)) || builder.ContainsKey(nameof(builder.Port)) ? builder.EndPoint : null,
42-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
43-
CredentialsPath = builder.CredentialFile == "" ? null: builder.CredentialFile,
44-
#pragma warning restore CS0618
4547
ChannelCredentials = builder.CredentialOverride,
4648
GoogleCredential = builder.GoogleCredential,
4749
AffinityChannelPoolConfiguration = new ChannelPoolConfig
@@ -59,6 +61,11 @@ internal SpannerClientCreationOptions(SpannerConnectionStringBuilder builder)
5961

6062
UsesEmulator = emulatorBuilder is not null;
6163
ClientBuilder = emulatorBuilder ?? clientBuilder;
64+
_credentialFile = builder.CredentialFile;
65+
_credentialType = builder.CredentialType;
66+
// Use Lazy to avoid throwing in the constructor if the credential file is missing.
67+
_effectiveGoogleCredential = new Lazy<GoogleCredential>(() =>
68+
ClientBuilder.GoogleCredential ?? (string.IsNullOrEmpty(_credentialFile) ? null : CredentialFactory.FromFile(_credentialFile, _credentialType)));
6269
}
6370

6471
internal Task<SpannerClient> CreateSpannerClientAsync(SpannerSettings settings) =>
@@ -68,11 +75,8 @@ internal Task<SpannerClient> CreateSpannerClientAsync(SpannerSettings settings)
6875
// Note we don't copy emulator detection properties because we already took care
6976
// of emulator detection on the constructor.
7077
Endpoint = ClientBuilder.Endpoint,
71-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
72-
CredentialsPath = ClientBuilder.CredentialsPath,
73-
#pragma warning disable CS0618
7478
ChannelCredentials = ClientBuilder.ChannelCredentials,
75-
GoogleCredential = ClientBuilder.GoogleCredential,
79+
GoogleCredential = _effectiveGoogleCredential.Value,
7680
AffinityChannelPoolConfiguration = ClientBuilder.AffinityChannelPoolConfiguration,
7781
LeaderRoutingEnabled = ClientBuilder.LeaderRoutingEnabled,
7882
DirectedReadOptions = ClientBuilder.DirectedReadOptions,
@@ -88,11 +92,8 @@ internal DatabaseAdminClientBuilder CreateDatabaseAdminClientBuilder()
8892
// Note we don't copy emulator detection properties because we already took care
8993
// of emulator detection on the constructor.
9094
Endpoint = ClientBuilder.Endpoint,
91-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
92-
CredentialsPath = ClientBuilder.CredentialsPath,
93-
#pragma warning restore CS0618
9495
ChannelCredentials = ClientBuilder.ChannelCredentials,
95-
GoogleCredential = ClientBuilder.GoogleCredential,
96+
GoogleCredential = _effectiveGoogleCredential.Value,
9697
// If we ever have settings of our own, we need to merge those with these.
9798
Settings = CreateDatabaseAdminSettings(),
9899
UniverseDomain = ClientBuilder.UniverseDomain,
@@ -113,11 +114,10 @@ other is not null &&
113114
UsesEmulator == other.UsesEmulator &&
114115
// TODO: Consider overriding ClientBuilderBase and SpannerClientBuilder Equals, etc.
115116
Equals(ClientBuilder.Endpoint, other.ClientBuilder.Endpoint) &&
116-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
117-
Equals(ClientBuilder.CredentialsPath, other.ClientBuilder.CredentialsPath) &&
118-
#pragma warning restore CS0618
119117
Equals(ClientBuilder.ChannelCredentials, other.ClientBuilder.ChannelCredentials) &&
120118
Equals(ClientBuilder.GoogleCredential, other.ClientBuilder.GoogleCredential) &&
119+
_credentialFile == other._credentialFile &&
120+
_credentialType == other._credentialType &&
121121
Equals(ClientBuilder.AffinityChannelPoolConfiguration, other.ClientBuilder.AffinityChannelPoolConfiguration) &&
122122
ClientBuilder.LeaderRoutingEnabled == other.ClientBuilder.LeaderRoutingEnabled &&
123123
Equals(ClientBuilder.DirectedReadOptions, other.ClientBuilder.DirectedReadOptions) &&
@@ -129,11 +129,10 @@ public override int GetHashCode()
129129
{
130130
int hash = 31;
131131
hash = hash * 23 + (ClientBuilder.Endpoint?.GetHashCode() ?? 0);
132-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
133-
hash = hash * 23 + (ClientBuilder.CredentialsPath?.GetHashCode() ?? 0);
134-
#pragma warning restore CS0618
135132
hash = hash * 23 + (ClientBuilder.ChannelCredentials?.GetHashCode() ?? 0);
136133
hash = hash * 23 + (ClientBuilder.GoogleCredential?.GetHashCode() ?? 0);
134+
hash = hash * 23 + (_credentialFile?.GetHashCode() ?? 0);
135+
hash = hash * 23 + (_credentialType?.GetHashCode() ?? 0);
137136
hash = hash * 23 + UsesEmulator.GetHashCode();
138137
hash = hash * 23 + (ClientBuilder.AffinityChannelPoolConfiguration?.GetHashCode() ?? 0);
139138
hash = hash * 23 + (ClientBuilder.LeaderRoutingEnabled.GetHashCode());
@@ -150,12 +149,14 @@ public override int GetHashCode()
150149
public override string ToString()
151150
{
152151
var builder = new StringBuilder($"EndPoint: {ClientBuilder.Endpoint ?? "Default"}");
153-
#pragma warning disable CS0618 // Temporarily disable warnings for obsolete methods. See b/453009677 for more details.
154-
if (!string.IsNullOrEmpty(ClientBuilder.CredentialsPath))
152+
if (!string.IsNullOrEmpty(_credentialFile))
153+
{
154+
builder.Append($"; CredentialsFile: {_credentialFile}");
155+
}
156+
if (!string.IsNullOrEmpty(_credentialType))
155157
{
156-
builder.Append($"; CredentialsFile: {ClientBuilder.CredentialsPath}");
158+
builder.Append($"; CredentialType: {_credentialType}");
157159
}
158-
#pragma warning restore CS0618
159160
if (ClientBuilder.ChannelCredentials is not null)
160161
{
161162
builder.Append($"; CredentialsOverride: True");

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerConnectionStringBuilder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public sealed class SpannerConnectionStringBuilder : DbConnectionStringBuilder
5151
internal const int DefaultMaxConcurrentStreamsLowWatermark = 20;
5252

5353
private const string CredentialFileKeyword = "CredentialFile";
54+
private const string CredentialTypeKeyword = "CredentialType";
5455
private const string DataSourceKeyword = "Data Source";
5556
private const string UseClrDefaultForNullKeyword = "UseClrDefaultForNull";
5657
private const string EnableGetSchemaTableKeyword = "EnableGetSchemaTable";
@@ -94,6 +95,17 @@ public string CredentialFile
9495
set => this[CredentialFileKeyword] = value;
9596
}
9697

98+
/// <summary>
99+
/// The expected type of the credential provided via <see cref="CredentialFile"/>.
100+
/// Accepted values can be found in <see cref="JsonCredentialParameters"/>.
101+
/// Defaults to <see cref="JsonCredentialParameters.ServiceAccountCredentialType"/>.
102+
/// </summary>
103+
public string CredentialType
104+
{
105+
get => GetValueOrDefault(CredentialTypeKeyword, JsonCredentialParameters.ServiceAccountCredentialType);
106+
set => this[CredentialTypeKeyword] = value;
107+
}
108+
97109
/// <summary>
98110
/// Option to change between the default handling of null database values (return <see cref="DBNull.Value">DBNull.Value</see>) or
99111
/// the non-standard handling (return the default value for whatever type is requested).

apis/Google.Cloud.Spanner.Data/docs/connection_string.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ Example:
2525

2626
- CredentialFile=/path/to/credentials.json
2727

28+
## CredentialType
29+
30+
The expected type of the credential provided via `CredentialFile`.
31+
Defaults to `service_account`.
32+
33+
Example:
34+
35+
- CredentialType=authorized_user
36+
2837
## EnableGetSchemaTable
2938

3039
When set to true (and when targeting .NET 4.5 or .NET Standard 2.0;

0 commit comments

Comments
 (0)