Skip to content

Commit

Permalink
Implement getUserByProviderUid.
Browse files Browse the repository at this point in the history
  • Loading branch information
nrsim committed Feb 3, 2020
1 parent d672e23 commit e3ef8da
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 25 deletions.
19 changes: 18 additions & 1 deletion FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -276,6 +284,15 @@ public async Task GetUserNonExistingEmail()
Assert.Equal(AuthErrorCode.UserNotFound, exception.AuthErrorCode);
}

[Fact]
public async Task GetUserNonExistingProviderUid()
{
var exception = await Assert.ThrowsAsync<FirebaseAuthException>(
async () => await FirebaseAuth.DefaultInstance.GetUserByProviderUidAsync("google.com", "non_existing_user"));

Assert.Equal(AuthErrorCode.UserNotFound, exception.AuthErrorCode);
}

[Fact]
public async Task UpdateUserNonExistingUid()
{
Expand Down
106 changes: 106 additions & 0 deletions FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,112 @@ public async Task GetUserByPhoneNumberEmpty()
await Assert.ThrowsAsync<ArgumentException>(() => 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<Dictionary<string, object>>(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<Dictionary<string, object>>(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", "[email protected]");

Assert.Equal("user1", userRecord.Uid);

var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(handler.LastRequestBody);
Assert.Equal(new JArray("[email protected]"), 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<FirebaseAuthException>(
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<ArgumentException>(() => auth.GetUserByProviderUidAsync("google.com", null));
await Assert.ThrowsAsync<ArgumentException>(() => auth.GetUserByProviderUidAsync(null, "google_uid"));
}

[Fact]
public async Task GetUserByProviderUidEmpty()
{
var auth = this.CreateFirebaseAuth(new MockMessageHandler());
await Assert.ThrowsAsync<ArgumentException>(() => auth.GetUserByProviderUidAsync("google.com", string.Empty));
await Assert.ThrowsAsync<ArgumentException>(() => auth.GetUserByProviderUidAsync(string.Empty, "google_uid"));
}

[Fact]
public async Task ListUsers()
{
Expand Down
43 changes: 43 additions & 0 deletions FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,49 @@ public async Task<UserRecord> GetUserByPhoneNumberAsync(
.ConfigureAwait(false);
}

/// <summary>
/// Gets a <see cref="UserRecord"/> object containing information about the user identified by
/// <paramref name="providerId"/> and <paramref name="providerUid"/>.
/// </summary>
/// <param name="providerId">Identifier for the given provider, for example,
/// "google.com" for the Google provider.</param>
/// <param name="providerUid">The user identifier with the given provider.</param>
/// <returns>A task that completes with a <see cref="UserRecord"/> representing
/// a user with the specified provider user identifier.</returns>
/// <exception cref="ArgumentException">If the provider identifier is null or empty,
/// or if the provider user identifier is empty.</exception>
/// <exception cref="FirebaseAuthException">If a user cannot be found with the specified
/// provider user identifier.</exception>
public async Task<UserRecord> GetUserByProviderUidAsync(string providerId, string providerUid)
{
return await this.GetUserByProviderUidAsync(providerId, providerUid, default(CancellationToken))
.ConfigureAwait(false);
}

/// <summary>
/// Gets a <see cref="UserRecord"/> object containing information about the user identified by
/// <paramref name="providerId"/> and <paramref name="providerUid"/>.
/// </summary>
/// <param name="providerId">Identifier for the given provider, for example,
/// "google.com" for the Google provider.</param>
/// <param name="providerUid">The user identifier with the given provider.</param>
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
/// operation.</param>
/// <returns>A task that completes with a <see cref="UserRecord"/> representing
/// a user with the specified provider user identifier.</returns>
/// <exception cref="ArgumentException">If the provider identifier is null or empty,
/// or if the provider user identifier is empty.</exception>
/// <exception cref="FirebaseAuthException">If a user cannot be found with the specified
/// provider user identifier.</exception>
public async Task<UserRecord> GetUserByProviderUidAsync(
string providerId, string providerUid, CancellationToken cancellationToken)
{
var userManager = this.IfNotDeleted(() => this.userManager.Value);

return await userManager.GetUserByProviderUidAsync(providerId, providerUid, cancellationToken)
.ConfigureAwait(false);
}

/// <summary>
/// Updates an existing user account with the attributes contained in the specified <see cref="UserRecordArgs"/>.
/// The <see cref="UserRecordArgs.Uid"/> property must be specified.
Expand Down
83 changes: 59 additions & 24 deletions FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ internal async Task<UserRecord> 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);
Expand All @@ -127,7 +127,56 @@ internal async Task<UserRecord> GetUserByEmailAsync(
var query = new UserQuery()
{
Field = "email",
Value = email,
Value = new string[] { email },
Description = "email: " + email,
};
return await this.GetUserAsync(query, cancellationToken)
.ConfigureAwait(false);
}

/// <summary>
/// Gets the user data corresponding to the given provider user identifer.
/// </summary>
/// <param name="providerId">Identifier for the given provider, for example,
/// "google.com" for the Google provider.</param>
/// <param name="providerUid">The user identifier with the given provider.</param>
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
/// operation.</param>
/// <returns>A record of user with the queried provider user identifier if one exists.</returns>
internal async Task<UserRecord> 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<string, object>()
{
{ "rawId", providerUid },
{ "providerId", providerId },
};

var query = new UserQuery()
{
Field = "federatedUserId",
Value = new Dictionary<string, object>[] { federatedUserId },
Description = "providerId: " + providerId + ", providerUid: " + providerUid,
};
return await this.GetUserAsync(query, cancellationToken)
.ConfigureAwait(false);
Expand All @@ -151,8 +200,8 @@ internal async Task<UserRecord> 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);
Expand Down Expand Up @@ -301,37 +350,23 @@ internal sealed class Args
/// <summary>
/// Represents a query that can be executed against the Firebase Auth service to retrieve user records.
/// A query mainly consists of a <see cref="UserQuery.Field"/> and a <see cref="UserQuery.Value"/> (e.g.
/// <c>Field = localId</c> and <c>Value = alice</c>). Additionally, a query may also specify a more
/// human-readable <see cref="UserQuery.Label"/> for the field, which will appear on any error messages
/// <c>Field = localId</c> and <c>Value = alice</c>). Additionally, a query also specifies a more
/// human-readable <see cref="UserQuery.Description"/> for the key-value, which will appear on any error messages
/// produced by the query.
/// </summary>
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<string, object> Build()
{
return new Dictionary<string, object>()
{
{ this.Field, new string[] { this.Value } },
{ this.Field, this.Value },
};
}
}
Expand Down

0 comments on commit e3ef8da

Please sign in to comment.