Skip to content

Commit cf06ec7

Browse files
feat: Add credential type parameter to CredentialFactory
1 parent ac025e1 commit cf06ec7

File tree

2 files changed

+222
-12
lines changed

2 files changed

+222
-12
lines changed

Src/Support/Google.Apis.Auth.Tests/OAuth2/GoogleCredentialTests.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,105 @@ public async Task UniverseDomain_DifferentCustomInRequestAndCredential()
846846
Assert.Null(request.Headers.Authorization?.Parameter);
847847
}
848848

849+
[Theory]
850+
[MemberData(nameof(CredentialFactory_Success_Data))]
851+
public void FromFile_WithType(string json, string credentialType, Type expectedType)
852+
{
853+
var tempFile = Path.GetTempFileName();
854+
try
855+
{
856+
File.WriteAllText(tempFile, json);
857+
var credential = CredentialFactory.FromFile(tempFile, credentialType);
858+
Assert.IsType(expectedType, credential.UnderlyingCredential);
859+
}
860+
finally
861+
{
862+
File.Delete(tempFile);
863+
}
864+
}
865+
866+
[Theory]
867+
[MemberData(nameof(CredentialFactory_Failure_Data))]
868+
public void FromFile_WithType_Failure(string json, string credentialType, string expectedMessage)
869+
{
870+
var tempFile = Path.GetTempFileName();
871+
try
872+
{
873+
File.WriteAllText(tempFile, json);
874+
var ex = Assert.Throws<InvalidOperationException>(() => CredentialFactory.FromFile(tempFile, credentialType));
875+
Assert.Equal(expectedMessage, ex.Message);
876+
}
877+
finally
878+
{
879+
File.Delete(tempFile);
880+
}
881+
}
882+
883+
[Theory]
884+
[MemberData(nameof(CredentialFactory_Success_Data))]
885+
public void FromJson_WithType(string json, string credentialType, Type expectedType)
886+
{
887+
var credential = CredentialFactory.FromJson(json, credentialType);
888+
Assert.IsType(expectedType, credential.UnderlyingCredential);
889+
}
890+
891+
[Theory]
892+
[MemberData(nameof(CredentialFactory_Failure_Data))]
893+
public void FromJson_WithType_Failure(string json, string credentialType, string expectedMessage)
894+
{
895+
var ex = Assert.Throws<InvalidOperationException>(() => CredentialFactory.FromJson(json, credentialType));
896+
Assert.Equal(expectedMessage, ex.Message);
897+
}
898+
899+
[Theory]
900+
[MemberData(nameof(CredentialFactory_Success_Data))]
901+
public async Task FromFileAsync_WithType(string json, string credentialType, Type expectedType)
902+
{
903+
var tempFile = Path.GetTempFileName();
904+
try
905+
{
906+
File.WriteAllText(tempFile, json);
907+
var credential = await CredentialFactory.FromFileAsync(tempFile, credentialType, CancellationToken.None);
908+
Assert.IsType(expectedType, credential.UnderlyingCredential);
909+
}
910+
finally
911+
{
912+
File.Delete(tempFile);
913+
}
914+
}
915+
916+
[Theory]
917+
[MemberData(nameof(CredentialFactory_Failure_Data))]
918+
public async Task FromFileAsync_WithType_Failure(string json, string credentialType, string expectedMessage)
919+
{
920+
var tempFile = Path.GetTempFileName();
921+
try
922+
{
923+
File.WriteAllText(tempFile, json);
924+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => CredentialFactory.FromFileAsync(tempFile, credentialType, CancellationToken.None));
925+
Assert.Equal(expectedMessage, ex.Message);
926+
}
927+
finally
928+
{
929+
File.Delete(tempFile);
930+
}
931+
}
932+
933+
public static TheoryData<string, string, Type> CredentialFactory_Success_Data() =>
934+
new TheoryData<string, string, Type>
935+
{
936+
{ FakeUserCredentialFileContents, JsonCredentialParameters.AuthorizedUserCredentialType, typeof(UserCredential) },
937+
{ FakeServiceAccountCredentialFileContents, JsonCredentialParameters.ServiceAccountCredentialType, typeof(ServiceAccountCredential) }
938+
};
939+
940+
public static TheoryData<string, string, string> CredentialFactory_Failure_Data() =>
941+
new TheoryData<string, string, string>
942+
{
943+
{ FakeUserCredentialFileContents, JsonCredentialParameters.ServiceAccountCredentialType, "The credential type authorized_user is not compatible with the requested type service_account" },
944+
{ FakeUserCredentialFileContents, "invalid_type", "The credential type authorized_user is not compatible with the requested type invalid_type" },
945+
{ "invalid_json", "any_type", "Error deserializing JSON credential data." }
946+
};
947+
849948
[Fact]
850949
public void FromStream_WrapsDeserializationException_WithBadJson()
851950
{

Src/Support/Google.Apis.Auth/OAuth2/CredentialFactory.cs

Lines changed: 123 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ limitations under the License.
1616

1717
using System;
1818
using System.IO;
19-
using System.Linq;
2019
using System.Threading;
2120
using System.Threading.Tasks;
2221

@@ -50,6 +49,19 @@ public static async Task<T> FromFileAsync<T>(string credentialPath, Cancellation
5049
return await FromStreamAsync<T>(fileStream, cancellationToken).ConfigureAwait(false);
5150
}
5251

52+
/// <summary>
53+
/// Creates a credential of the specified type from a file that contains JSON credential data.
54+
/// </summary>
55+
/// <param name="credentialPath">The path to the credential file.</param>
56+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
57+
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
58+
/// <returns>The created credential.</returns>
59+
public static async Task<GoogleCredential> FromFileAsync(string credentialPath, string credentialType, CancellationToken cancellationToken)
60+
{
61+
using FileStream fileStream = File.OpenRead(credentialPath);
62+
return await FromStreamAsync(fileStream, credentialType, cancellationToken).ConfigureAwait(false);
63+
}
64+
5365
/// <summary>
5466
/// Creates a credential of the specified type from a file that contains JSON credential data.
5567
/// </summary>
@@ -62,6 +74,18 @@ public static T FromFile<T>(string credentialPath)
6274
return FromStream<T>(fileStream);
6375
}
6476

77+
/// <summary>
78+
/// Creates a credential of the specified type from a file that contains JSON credential data.
79+
/// </summary>
80+
/// <param name="credentialPath">The path to the credential file.</param>
81+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
82+
/// <returns>The created credential.</returns>
83+
public static GoogleCredential FromFile(string credentialPath, string credentialType)
84+
{
85+
using FileStream fileStream = File.OpenRead(credentialPath);
86+
return FromStream(fileStream, credentialType);
87+
}
88+
6589
/// <summary>
6690
/// Creates a credential of the specified type from a stream that contains JSON credential data.
6791
/// </summary>
@@ -83,6 +107,27 @@ public static async Task<T> FromStreamAsync<T>(Stream stream, CancellationToken
83107
return FromJsonParameters<T>(jsonCredentialParameters);
84108
}
85109

110+
/// <summary>
111+
/// Creates a credential of the specified type from a stream that contains JSON credential data.
112+
/// </summary>
113+
/// <param name="stream">The stream that contains the JSON credential data.</param>
114+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
115+
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
116+
/// <returns>The created credential.</returns>
117+
private static async Task<GoogleCredential> FromStreamAsync(Stream stream, string credentialType, CancellationToken cancellationToken)
118+
{
119+
JsonCredentialParameters jsonCredentialParameters;
120+
try
121+
{
122+
jsonCredentialParameters = await NewtonsoftJsonSerializer.Instance.DeserializeAsync<JsonCredentialParameters>(stream, cancellationToken).ConfigureAwait(false);
123+
}
124+
catch (Exception e)
125+
{
126+
throw new InvalidOperationException(JsonDeserializationErrorMessage, e);
127+
}
128+
return FromJsonParameters(jsonCredentialParameters, credentialType);
129+
}
130+
86131
/// <summary>
87132
/// Creates a credential of the specified type from a stream that contains JSON credential data.
88133
/// </summary>
@@ -103,6 +148,26 @@ public static T FromStream<T>(Stream stream)
103148
return FromJsonParameters<T>(jsonCredentialParameters);
104149
}
105150

151+
/// <summary>
152+
/// Creates a credential of the specified type from a stream that contains JSON credential data.
153+
/// </summary>
154+
/// <param name="stream">The stream that contains the JSON credential data.</param>
155+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
156+
/// <returns>The created credential.</returns>
157+
private static GoogleCredential FromStream(Stream stream, string credentialType)
158+
{
159+
JsonCredentialParameters jsonCredentialParameters;
160+
try
161+
{
162+
jsonCredentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(stream);
163+
}
164+
catch (Exception e)
165+
{
166+
throw new InvalidOperationException(JsonDeserializationErrorMessage, e);
167+
}
168+
return FromJsonParameters(jsonCredentialParameters, credentialType);
169+
}
170+
106171
/// <summary>
107172
/// Creates a credential of the specified type from a string that contains JSON credential data.
108173
/// </summary>
@@ -123,6 +188,26 @@ public static T FromJson<T>(string json)
123188
return FromJsonParameters<T>(jsonCredentialParameters);
124189
}
125190

191+
/// <summary>
192+
/// Creates a credential of the specified type from a string that contains JSON credential data.
193+
/// </summary>
194+
/// <param name="json">The string that contains the JSON credential data.</param>
195+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
196+
/// <returns>The created credential.</returns>
197+
public static GoogleCredential FromJson(string json, string credentialType)
198+
{
199+
JsonCredentialParameters jsonCredentialParameters;
200+
try
201+
{
202+
jsonCredentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(json);
203+
}
204+
catch (Exception e)
205+
{
206+
throw new InvalidOperationException(JsonDeserializationErrorMessage, e);
207+
}
208+
return FromJsonParameters(jsonCredentialParameters, credentialType);
209+
}
210+
126211
/// <summary>
127212
/// Creates a credential of the specified type from JSON credential parameters.
128213
/// </summary>
@@ -136,27 +221,53 @@ public static T FromJson<T>(string json)
136221
/// </exception>
137222
internal static T FromJsonParameters<T>(JsonCredentialParameters credentialParameters)
138223
{
139-
if (CreateCredential(credentialParameters, typeof(T)) is T credentialAsT)
224+
credentialParameters.ThrowIfNull(nameof(credentialParameters));
225+
if (FromJsonParameters(credentialParameters, typeof(T)) is T credentialAsT)
140226
{
141227
return credentialAsT;
142228
}
143229

144230
throw new InvalidOperationException(
145231
$"Found incompatible credential types, '{credentialParameters.Type}' and '{typeof(T).FullName}, even though a check"
146232
+ " should have already taken place. We should never reach here, there's a bug in the code.");
233+
}
147234

148-
static IGoogleCredential CreateCredential(JsonCredentialParameters credentialParameters, Type targetCredentialType) =>
149-
credentialParameters.ThrowIfNull(nameof(credentialParameters)).Type switch
150-
{
151-
JsonCredentialParameters.AuthorizedUserCredentialType => CreateUserCredentialFromParameters(credentialParameters, targetCredentialType),
152-
JsonCredentialParameters.ServiceAccountCredentialType => CreateServiceAccountCredentialFromParameters(credentialParameters, targetCredentialType),
153-
JsonCredentialParameters.ExternalAccountCredentialType => CreateExternalCredentialFromParameters(credentialParameters, targetCredentialType),
154-
JsonCredentialParameters.ImpersonatedServiceAccountCredentialType => CreateImpersonatedServiceAccountCredentialFromParameters(credentialParameters, targetCredentialType),
155-
JsonCredentialParameters.ExternalAccountAuthorizedUserCredentialType => CreateExternalAccountAuthorizedUserCredentialFromParameters(credentialParameters, targetCredentialType),
156-
_ => throw new InvalidOperationException($"Error creating credential from JSON or JSON parameters. Unrecognized credential type {credentialParameters.Type}."),
157-
};
235+
/// <summary>
236+
/// Creates a credential of the specified type from JSON credential parameters.
237+
/// </summary>
238+
/// <param name="credentialParameters">The JSON credential parameters.</param>
239+
/// <param name="credentialType">The type of credential to be loaded. Valid strings can be found in <see cref="JsonCredentialParameters"/>.</param>
240+
/// <returns>The created credential.</returns>
241+
/// <exception cref="InvalidOperationException">
242+
/// Thrown if the <paramref name="credentialType"/> is unrecognized,
243+
/// or if the credential data is incompatible with the requested type.
244+
/// </exception>
245+
private static GoogleCredential FromJsonParameters(JsonCredentialParameters credentialParameters, string credentialType)
246+
{
247+
credentialParameters.ThrowIfNull(nameof(credentialParameters));
248+
if (credentialParameters.Type != credentialType)
249+
{
250+
throw new InvalidOperationException($"The credential type {credentialParameters.Type} is not compatible with the requested type {credentialType}");
251+
}
252+
253+
// Type checking has already occurred so target type may be set to the generic IGoogleCredential which all credentials implement.
254+
Type targetType = typeof(IGoogleCredential);
255+
var rawCredentialType = FromJsonParameters(credentialParameters, targetType);
256+
257+
return rawCredentialType.ToGoogleCredential();
158258
}
159259

260+
private static IGoogleCredential FromJsonParameters(JsonCredentialParameters credentialParameters, Type targetCredentialType) =>
261+
credentialParameters.ThrowIfNull(nameof(credentialParameters)).Type switch
262+
{
263+
JsonCredentialParameters.AuthorizedUserCredentialType => CreateUserCredentialFromParameters(credentialParameters, targetCredentialType),
264+
JsonCredentialParameters.ServiceAccountCredentialType => CreateServiceAccountCredentialFromParameters(credentialParameters, targetCredentialType),
265+
JsonCredentialParameters.ExternalAccountCredentialType => CreateExternalCredentialFromParameters(credentialParameters, targetCredentialType),
266+
JsonCredentialParameters.ImpersonatedServiceAccountCredentialType => CreateImpersonatedServiceAccountCredentialFromParameters(credentialParameters, targetCredentialType),
267+
JsonCredentialParameters.ExternalAccountAuthorizedUserCredentialType => CreateExternalAccountAuthorizedUserCredentialFromParameters(credentialParameters, targetCredentialType),
268+
_ => throw new InvalidOperationException($"Error creating credential from JSON or JSON parameters. Unrecognized credential type {credentialParameters.Type}.")
269+
};
270+
160271
/// <summary>
161272
/// Validates actual type can be cast to target type.
162273
/// </summary>

0 commit comments

Comments
 (0)