diff --git a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs index 6352c159..7b93f608 100644 --- a/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs @@ -225,7 +225,15 @@ public async Task UserLifecycle() user = await FirebaseAuth.DefaultInstance.GetUserByEmailAsync(randomUser.Email); Assert.Equal(uid, user.Uid); - // Disable user and remove properties + // Get user by phone provider uid + user = await FirebaseAuth.DefaultInstance.GetUserByProviderUidAsync("phone", randomUser.PhoneNumber); + Assert.Equal(uid, user.Uid); + + // Get user by email provider uid + user = await FirebaseAuth.DefaultInstance.GetUserByProviderUidAsync("email", randomUser.Email); + Assert.Equal(uid, user.Uid); + + // Disable user and remove properties var disableArgs = new UserRecordArgs() { Uid = uid, @@ -276,6 +284,15 @@ public async Task GetUserNonExistingEmail() Assert.Equal(AuthErrorCode.UserNotFound, exception.AuthErrorCode); } + [Fact] + public async Task GetUserNonExistingProviderUid() + { + var exception = await Assert.ThrowsAsync( + async () => await FirebaseAuth.DefaultInstance.GetUserByProviderUidAsync("google.com", "non_existing_user")); + + Assert.Equal(AuthErrorCode.UserNotFound, exception.AuthErrorCode); + } + [Fact] public async Task UpdateUserNonExistingUid() { diff --git a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs index c8d06450..d69b1f81 100644 --- a/FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs +++ b/FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs @@ -326,6 +326,112 @@ public async Task GetUserByPhoneNumberEmpty() await Assert.ThrowsAsync(() => auth.GetUserByPhoneNumberAsync(string.Empty)); } + [Fact] + public async Task GetUserByProviderUid() + { + var handler = new MockMessageHandler() + { + Response = GetUserResponse, + }; + var auth = this.CreateFirebaseAuth(handler); + + var userRecord = await auth.GetUserByProviderUidAsync("google.com", "google_uid"); + + Assert.Equal("user1", userRecord.Uid); + Assert.Null(userRecord.DisplayName); + Assert.Null(userRecord.Email); + Assert.Null(userRecord.PhoneNumber); + Assert.Null(userRecord.PhotoUrl); + Assert.Equal("firebase", userRecord.ProviderId); + Assert.False(userRecord.Disabled); + Assert.False(userRecord.EmailVerified); + Assert.Equal(UserRecord.UnixEpoch, userRecord.TokensValidAfterTimestamp); + Assert.Empty(userRecord.CustomClaims); + Assert.Empty(userRecord.ProviderData); + Assert.Null(userRecord.UserMetaData.CreationTimestamp); + Assert.Null(userRecord.UserMetaData.LastSignInTimestamp); + + var request = NewtonsoftJsonSerializer.Instance.Deserialize>(handler.LastRequestBody); + JObject expectedFederatedUserId = new JObject(); + expectedFederatedUserId.Add("rawId", "google_uid"); + expectedFederatedUserId.Add("providerId", "google.com"); + Assert.Equal(new JArray(expectedFederatedUserId), request["federatedUserId"]); + this.AssertClientVersion(handler.LastRequestHeaders); + } + + [Fact] + public async Task GetUserByProviderUidWithPhoneProvider() + { + var handler = new MockMessageHandler() + { + Response = GetUserResponse, + }; + var auth = this.CreateFirebaseAuth(handler); + + var userRecord = await auth.GetUserByProviderUidAsync("phone", "+1234567890"); + + Assert.Equal("user1", userRecord.Uid); + + var request = NewtonsoftJsonSerializer.Instance.Deserialize>(handler.LastRequestBody); + Assert.Equal(new JArray("+1234567890"), request["phoneNumber"]); + Assert.False(request.ContainsKey("federatedUserId")); + this.AssertClientVersion(handler.LastRequestHeaders); + } + + [Fact] + public async Task GetUserByProviderUidWithEmailProvider() + { + var handler = new MockMessageHandler() + { + Response = GetUserResponse, + }; + var auth = this.CreateFirebaseAuth(handler); + + var userRecord = await auth.GetUserByProviderUidAsync("email", "user@example.com"); + + Assert.Equal("user1", userRecord.Uid); + + var request = NewtonsoftJsonSerializer.Instance.Deserialize>(handler.LastRequestBody); + Assert.Equal(new JArray("user@example.com"), request["email"]); + Assert.False(request.ContainsKey("federatedUserId")); + this.AssertClientVersion(handler.LastRequestHeaders); + } + + [Fact] + public async Task GetUserByProviderUidUserNotFound() + { + var handler = new MockMessageHandler() + { + Response = @"{""users"": []}", + }; + var auth = this.CreateFirebaseAuth(handler); + + var exception = await Assert.ThrowsAsync( + async () => await auth.GetUserByProviderUidAsync("google.com", "google_uid")); + + Assert.Equal(ErrorCode.NotFound, exception.ErrorCode); + Assert.Equal(AuthErrorCode.UserNotFound, exception.AuthErrorCode); + Assert.Equal("Failed to get user with providerId: google.com, providerUid: google_uid", exception.Message); + Assert.NotNull(exception.HttpResponse); + Assert.Null(exception.InnerException); + } + + [Fact] + public async Task GetUserByProviderUidNull() + { + var auth = this.CreateFirebaseAuth(new MockMessageHandler()); + await Assert.ThrowsAsync(() => auth.GetUserByProviderUidAsync("google.com", null)); + await Assert.ThrowsAsync(() => auth.GetUserByProviderUidAsync(null, "google_uid")); + } + + [Fact] + public async Task GetUserByProviderUidEmpty() + { + var auth = this.CreateFirebaseAuth(new MockMessageHandler()); + await Assert.ThrowsAsync(() => auth.GetUserByProviderUidAsync("google.com", string.Empty)); + await Assert.ThrowsAsync(() => auth.GetUserByProviderUidAsync(string.Empty, "google_uid")); + } + [Fact] public async Task ListUsers() { diff --git a/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs b/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs index de8440ed..74410622 100644 --- a/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs +++ b/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs @@ -413,6 +413,49 @@ public async Task GetUserByPhoneNumberAsync( .ConfigureAwait(false); } + /// + /// Gets a object containing information about the user identified by + /// and . + /// + /// Identifier for the given provider, for example, + /// "google.com" for the Google provider. + /// The user identifier with the given provider. + /// A task that completes with a representing + /// a user with the specified provider user identifier. + /// If the provider identifier is null or empty, + /// or if the provider user identifier is empty. + /// If a user cannot be found with the specified + /// provider user identifier. + public async Task GetUserByProviderUidAsync(string providerId, string providerUid) + { + return await this.GetUserByProviderUidAsync(providerId, providerUid, default(CancellationToken)) + .ConfigureAwait(false); + } + + /// + /// Gets a object containing information about the user identified by + /// and . + /// + /// Identifier for the given provider, for example, + /// "google.com" for the Google provider. + /// The user identifier with the given provider. + /// A cancellation token to monitor the asynchronous + /// operation. + /// A task that completes with a representing + /// a user with the specified provider user identifier. + /// If the provider identifier is null or empty, + /// or if the provider user identifier is empty. + /// If a user cannot be found with the specified + /// provider user identifier. + public async Task GetUserByProviderUidAsync( + string providerId, string providerUid, CancellationToken cancellationToken) + { + var userManager = this.IfNotDeleted(() => this.userManager.Value); + + return await userManager.GetUserByProviderUidAsync(providerId, providerUid, cancellationToken) + .ConfigureAwait(false); + } + /// /// Updates an existing user account with the attributes contained in the specified . /// The property must be specified. diff --git a/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs b/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs index af42c459..99b6a297 100644 --- a/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs +++ b/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs @@ -102,8 +102,8 @@ internal async Task GetUserByIdAsync( var query = new UserQuery() { Field = "localId", - Value = uid, - Label = "uid", + Value = new string[] { uid }, + Description = "uid: " + uid, }; return await this.GetUserAsync(query, cancellationToken) .ConfigureAwait(false); @@ -127,7 +127,56 @@ internal async Task GetUserByEmailAsync( var query = new UserQuery() { Field = "email", - Value = email, + Value = new string[] { email }, + Description = "email: " + email, + }; + return await this.GetUserAsync(query, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets the user data corresponding to the given provider user identifer. + /// + /// Identifier for the given provider, for example, + /// "google.com" for the Google provider. + /// The user identifier with the given provider. + /// A cancellation token to monitor the asynchronous + /// operation. + /// A record of user with the queried provider user identifier if one exists. + internal async Task GetUserByProviderUidAsync( + string providerId, string providerUid, CancellationToken cancellationToken = default(CancellationToken)) + { + if (string.IsNullOrEmpty(providerId)) + { + throw new ArgumentException("providerId cannot be null or empty."); + } + + if (string.IsNullOrEmpty(providerUid)) + { + throw new ArgumentException("providerUid cannot be null or empty."); + } + + if (providerId.Equals("phone")) + { + return await this.GetUserByPhoneNumberAsync(providerUid, cancellationToken); + } + + if (providerId.Equals("email")) + { + return await this.GetUserByEmailAsync(providerUid, cancellationToken); + } + + var federatedUserId = new Dictionary() + { + { "rawId", providerUid }, + { "providerId", providerId }, + }; + + var query = new UserQuery() + { + Field = "federatedUserId", + Value = new Dictionary[] { federatedUserId }, + Description = "providerId: " + providerId + ", providerUid: " + providerUid, }; return await this.GetUserAsync(query, cancellationToken) .ConfigureAwait(false); @@ -151,8 +200,8 @@ internal async Task GetUserByPhoneNumberAsync( var query = new UserQuery() { Field = "phoneNumber", - Value = phoneNumber, - Label = "phone number", + Value = new string[] { phoneNumber }, + Description = "phone number: " + phoneNumber, }; return await this.GetUserAsync(query, cancellationToken) .ConfigureAwait(false); @@ -301,37 +350,23 @@ internal sealed class Args /// /// Represents a query that can be executed against the Firebase Auth service to retrieve user records. /// A query mainly consists of a and a (e.g. - /// Field = localId and Value = alice). Additionally, a query may also specify a more - /// human-readable for the field, which will appear on any error messages + /// Field = localId and Value = alice). Additionally, a query also specifies a more + /// human-readable for the key-value, which will appear on any error messages /// produced by the query. /// private class UserQuery { internal string Field { get; set; } - internal string Value { get; set; } - - internal string Label { get; set; } + internal object Value { get; set; } - internal string Description - { - get - { - var label = this.Label; - if (string.IsNullOrEmpty(label)) - { - label = this.Field; - } - - return $"{label}: {this.Value}"; - } - } + internal string Description { get; set; } internal Dictionary Build() { return new Dictionary() { - { this.Field, new string[] { this.Value } }, + { this.Field, this.Value }, }; } }