-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the ability to link/unlink a provider user id to an account.
- Loading branch information
Showing
5 changed files
with
341 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,22 +34,40 @@ namespace FirebaseAdmin.Auth.Users.Tests | |
{ | ||
public class FirebaseUserManagerTest | ||
{ | ||
public static readonly IEnumerable<object[]> TestConfigs = new List<object[]>() | ||
public static readonly TheoryData<TestConfig> TestConfigs = new TheoryData<TestConfig>() | ||
{ | ||
new object[] { TestConfig.ForFirebaseAuth() }, | ||
new object[] { TestConfig.ForTenantAwareFirebaseAuth("tenant1") }, | ||
new object[] { TestConfig.ForFirebaseAuth().WithEmulator() }, | ||
new object[] { TestConfig.ForTenantAwareFirebaseAuth("tenant1").WithEmulator() }, | ||
TestConfig.ForFirebaseAuth(), | ||
TestConfig.ForTenantAwareFirebaseAuth("tenant1"), | ||
TestConfig.ForFirebaseAuth().WithEmulator(), | ||
TestConfig.ForTenantAwareFirebaseAuth("tenant1").WithEmulator(), | ||
}; | ||
|
||
public static readonly IEnumerable<object[]> MainTenantTestConfigs = new List<object[]>() | ||
public static readonly TheoryData<TestConfig> MainTenantTestConfigs = new TheoryData<TestConfig>() | ||
{ | ||
new object[] { TestConfig.ForFirebaseAuth() }, | ||
new object[] { TestConfig.ForFirebaseAuth().WithEmulator() }, | ||
TestConfig.ForFirebaseAuth(), | ||
TestConfig.ForFirebaseAuth().WithEmulator(), | ||
}; | ||
|
||
private const string CreateUserResponse = @"{""localId"": ""user1""}"; | ||
|
||
public static TheoryData<TestConfig, string, string> UpdateUserInvalidProviderToLinkTestData | ||
{ | ||
get | ||
{ | ||
var data = new TheoryData<TestConfig, string, string>(); | ||
|
||
foreach (var testConfigObj in TestConfigs) | ||
{ | ||
var testConfig = (TestConfig)testConfigObj[0]; | ||
|
||
data.Add(testConfig, "google_user1", string.Empty); // Empty provider ID | ||
data.Add(testConfig, string.Empty, "google.com"); // Empty provider UID | ||
} | ||
|
||
return data; | ||
} | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task GetUserById(TestConfig config) | ||
|
@@ -1107,6 +1125,12 @@ public async Task UpdateUser(TestConfig config) | |
{ "package", "gold" }, | ||
}; | ||
|
||
var providerToLink = new ProviderUserInfoArgs() | ||
{ | ||
Uid = "google_user1", | ||
ProviderId = "google.com", | ||
}; | ||
|
||
var user = await auth.UpdateUserAsync(new UserRecordArgs() | ||
{ | ||
CustomClaims = customClaims, | ||
|
@@ -1118,6 +1142,7 @@ public async Task UpdateUser(TestConfig config) | |
PhoneNumber = "+1234567890", | ||
PhotoUrl = "https://example.com/user.png", | ||
Uid = "user1", | ||
ProviderToLink = providerToLink, | ||
}); | ||
|
||
Assert.Equal("user1", user.Uid); | ||
|
@@ -1135,6 +1160,13 @@ public async Task UpdateUser(TestConfig config) | |
Assert.Equal("+1234567890", request["phoneNumber"]); | ||
Assert.Equal("https://example.com/user.png", request["photoUrl"]); | ||
|
||
var expectedProviderUserInfo = new JObject | ||
{ | ||
{ "Uid", "google_user1" }, | ||
{ "ProviderId", "google.com" }, | ||
}; | ||
Assert.Equal(expectedProviderUserInfo, request["linkProviderUserInfo"]); | ||
|
||
var claims = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>((string)request["customAttributes"]); | ||
Assert.True((bool)claims["admin"]); | ||
Assert.Equal(4L, claims["level"]); | ||
|
@@ -1168,6 +1200,37 @@ public async Task UpdateUserPartial(TestConfig config) | |
Assert.True((bool)request["emailVerified"]); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task UpdateUserLinkProvider(TestConfig config) | ||
{ | ||
var handler = new MockMessageHandler() | ||
{ | ||
Response = new List<string>() { CreateUserResponse, config.GetUserResponse() }, | ||
}; | ||
var auth = config.CreateAuth(handler); | ||
|
||
var user = await auth.UpdateUserAsync(new UserRecordArgs() | ||
{ | ||
Uid = "user1", | ||
ProviderToLink = new ProviderUserInfoArgs() | ||
{ | ||
Uid = "google_user1", | ||
ProviderId = "google.com", | ||
}, | ||
}); | ||
|
||
Assert.Equal("user1", user.Uid); | ||
Assert.Equal(2, handler.Requests.Count); | ||
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.Requests[0].Body); | ||
Assert.Equal(2, request.Count); | ||
Assert.Equal("user1", request["localId"]); | ||
var expectedProviderUserInfo = new JObject(); | ||
expectedProviderUserInfo.Add("Uid", "google_user1"); | ||
expectedProviderUserInfo.Add("ProviderId", "google.com"); | ||
Assert.Equal(expectedProviderUserInfo, request["linkProviderUserInfo"]); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task UpdateUserRemoveAttributes(TestConfig config) | ||
|
@@ -1212,6 +1275,7 @@ public async Task UpdateUserRemoveProviders(TestConfig config) | |
{ | ||
PhoneNumber = null, | ||
Uid = "user1", | ||
ProvidersToDelete = new List<string>() { "google.com" }, | ||
}); | ||
|
||
Assert.Equal("user1", user.Uid); | ||
|
@@ -1223,7 +1287,7 @@ public async Task UpdateUserRemoveProviders(TestConfig config) | |
Assert.Equal(2, request.Count); | ||
Assert.Equal("user1", request["localId"]); | ||
Assert.Equal( | ||
new JArray() { "phone" }, | ||
new JArray() { "phone", "google.com" }, | ||
request["deleteProvider"]); | ||
} | ||
|
||
|
@@ -1485,6 +1549,110 @@ public async Task UpdateUserShortPassword(TestConfig config) | |
Assert.Empty(handler.Requests); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(UpdateUserInvalidProviderToLinkTestData))] | ||
public async Task UpdateUserInvalidProviderToLink(TestConfig config, string uid, string providerId) | ||
{ | ||
var handler = new MockMessageHandler() { Response = CreateUserResponse }; | ||
var auth = config.CreateAuth(handler); | ||
|
||
var args = new UserRecordArgs() | ||
{ | ||
ProviderToLink = new ProviderUserInfoArgs() | ||
{ | ||
Uid = uid, | ||
ProviderId = providerId, | ||
}, | ||
Uid = "user1", | ||
}; | ||
await Assert.ThrowsAsync<ArgumentException>( | ||
async () => await auth.UpdateUserAsync(args)); | ||
Assert.Empty(handler.Requests); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task UpdateUserInvalidEmailProviderToLink(TestConfig config) | ||
{ | ||
var handler = new MockMessageHandler() { Response = CreateUserResponse }; | ||
var auth = config.CreateAuth(handler); | ||
|
||
// Phone provider updated in 2 places in the same request | ||
var args = new UserRecordArgs() | ||
{ | ||
ProviderToLink = new ProviderUserInfoArgs() | ||
{ | ||
Uid = "[email protected]", | ||
ProviderId = "email", | ||
}, | ||
Uid = "user1", | ||
Email = "[email protected]", | ||
}; | ||
var exception = await Assert.ThrowsAsync<FirebaseAuthException>( | ||
async () => await auth.UpdateUserAsync(args)); | ||
|
||
const string expectedError = "Both UpdateRequest.Email and UpdateRequest.ProviderToLink.ProviderId='email' " + | ||
"were set. To link to the email/password provider, only specify the " + | ||
"UpdateRequest.Email field."; | ||
|
||
Assert.Equal(expectedError, exception.Message); | ||
Assert.Empty(handler.Requests); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task UpdateUserInvalidPhoneProviderToLink(TestConfig config) | ||
{ | ||
var handler = new MockMessageHandler() { Response = CreateUserResponse }; | ||
var auth = config.CreateAuth(handler); | ||
|
||
// Phone provider updated in 2 places in the same request | ||
var args = new UserRecordArgs() | ||
{ | ||
ProviderToLink = new ProviderUserInfoArgs() | ||
{ | ||
Uid = "+11234567891", | ||
ProviderId = "phone", | ||
}, | ||
Uid = "user1", | ||
PhoneNumber = "+11234567891", | ||
}; | ||
var exception = await Assert.ThrowsAsync<FirebaseAuthException>( | ||
async () => await auth.UpdateUserAsync(args)); | ||
|
||
const string expectedError = "Both UpdateRequest.PhoneNumber and UpdateRequest.ProviderToLink.ProviderId='phone'" + | ||
" were set. To link to a phone provider, only specify the " + | ||
"UpdateRequest.PhoneNumber field."; | ||
|
||
Assert.Equal(expectedError, exception.Message); | ||
Assert.Empty(handler.Requests); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public async Task UpdateUserInvalidProvidersToDelete(TestConfig config) | ||
{ | ||
var handler = new MockMessageHandler() { Response = CreateUserResponse }; | ||
var auth = config.CreateAuth(handler); | ||
|
||
// Empty provider ID | ||
var args = new UserRecordArgs() | ||
{ | ||
ProvidersToDelete = new List<string>() { "google.com", string.Empty }, | ||
Uid = "user1", | ||
}; | ||
await Assert.ThrowsAsync<ArgumentException>( | ||
async () => await auth.UpdateUserAsync(args)); | ||
Assert.Empty(handler.Requests); | ||
|
||
// Phone provider updates in two places | ||
args.PhoneNumber = null; | ||
args.ProvidersToDelete = new List<string>() { "google.com", "phone" }; | ||
await Assert.ThrowsAsync<ArgumentException>( | ||
async () => await auth.UpdateUserAsync(args)); | ||
Assert.Empty(handler.Requests); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TestConfigs))] | ||
public void EmptyNameClaims(TestConfig config) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright 2023, Google Inc. All rights reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
namespace FirebaseAdmin.Auth | ||
{ | ||
/// <summary> | ||
/// Contains metadata regarding how a user is known by a particular identity provider (IdP). | ||
/// </summary> | ||
public sealed class ProviderUserInfoArgs | ||
{ | ||
/// <summary> | ||
/// Gets or sets the user's unique ID assigned by the identity provider. | ||
/// </summary> | ||
/// <returns>a user ID string.</returns> | ||
public string Uid { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the user's display name, if available. | ||
/// </summary> | ||
/// <returns>a display name string or null.</returns> | ||
public string DisplayName { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the user's email address, if available. | ||
/// </summary> | ||
/// <returns>an email address string or null.</returns> | ||
public string Email { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the user's phone number. | ||
/// </summary> | ||
/// <returns>a phone number string or null.</returns> | ||
public string PhoneNumber { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the user's photo URL, if available. | ||
/// </summary> | ||
/// <returns>a URL string or null.</returns> | ||
public string PhotoUrl { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the ID of the identity provider. This can be a short domain name (e.g. | ||
/// google.com) or the identifier of an OpenID identity provider. | ||
/// </summary> | ||
/// <returns>an ID string that uniquely identifies the identity provider.</returns> | ||
public string ProviderId { get; set; } | ||
} | ||
} |
Oops, something went wrong.