Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): Implement getUserByProviderUid. #142

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: How about calling these provider and providerUid so that they are not off by just one letter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
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