Skip to content
New issue

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

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

Already on GitHub? # to your account

feat(auth): mfaInfo to userhandling so that mfa attributes can be updated via updateuser and set while creating the user. #409

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public async Task GetUser()
public async Task UpdateUser()
{
var original = await this.userBuilder.CreateUserAsync(new UserRecordArgs());
var updateArgs = TemporaryUserBuilder.RandomUserRecordArgs();
var updateArgs = TemporaryUserBuilder.RandomUserRecordArgsWithMfa();
updateArgs.Uid = original.Uid;
updateArgs.EmailVerified = true;

Expand All @@ -253,6 +253,12 @@ public async Task UpdateUser()
Assert.Null(user.UserMetaData.LastSignInTimestamp);
Assert.Equal(2, user.ProviderData.Length);
Assert.Empty(user.CustomClaims);
Assert.Equal(updateArgs.Mfa[0].DisplayName, user.Mfa[0].DisplayName);
Assert.Equal(updateArgs.Mfa[0].PhoneInfo, user.Mfa[0].PhoneInfo);
Assert.Equal(updateArgs.Mfa[0].MfaFactorId, user.Mfa[0].MfaFactorId);
// Check that the enrolled at Timespan is within one second of the set time.
// The range, is because the google cloud API rounds the enrolledAt date by some milliseconds.
Assert.InRange<TimeSpan>(updateArgs.Mfa[0].EnrolledAt - user.Mfa[0].EnrolledAt, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1));
}

[Fact]
Expand Down Expand Up @@ -658,6 +664,17 @@ public async Task SignInWithEmailLink()
Assert.True(user.EmailVerified);
}

[Fact]
public async Task CreateUserWithMfa()
{
var user = await this.userBuilder.CreateRandomUserMithMfaAsync();
var userGetData = await this.Auth.GetUserAsync(user.Uid);

Assert.NotNull(userGetData);
Assert.NotNull(userGetData.Mfa);
await this.Auth.DeleteUserAsync(user.Uid);
}

private async Task<FirebaseToken> AssertValidIdTokenAsync(
string idToken, bool checkRevoked = false)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Threading;
using System.Threading.Tasks;
using FirebaseAdmin.Auth;
Expand Down Expand Up @@ -56,11 +57,33 @@ public static UserRecordArgs RandomUserRecordArgs()
};
}

public static UserRecordArgs RandomUserRecordArgsWithMfa()
{
var userArgs = RandomUserRecordArgs();
userArgs.EmailVerified = true;
userArgs.Mfa = new List<MfaEnrollmentArgs>()
{
new MfaEnrollmentArgs
{
PhoneInfo = userArgs.PhoneNumber,
DisplayName = "test factor",
EnrolledAt = DateTime.UtcNow,
MfaFactorId = MfaFactorIdType.Phone,
},
};
return userArgs;
}

public async Task<UserRecord> CreateRandomUserAsync()
{
return await this.CreateUserAsync(RandomUserRecordArgs());
}

public async Task<UserRecord> CreateRandomUserMithMfaAsync()
{
return await this.CreateUserAsync(RandomUserRecordArgsWithMfa());
}

public async Task<UserRecord> CreateUserAsync(UserRecordArgs args)
{
// Make sure we never create more than 1000 users in a single instance.
Expand Down
19 changes: 19 additions & 0 deletions FirebaseAdmin/FirebaseAdmin.Snippets/FirebaseAuthSnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ internal static async Task CreateUserAsync()
DisplayName = "John Doe",
PhotoUrl = "http://www.example.com/12345678/photo.png",
Disabled = false,
Mfa = new List<MfaEnrollmentArgs>
{
new MfaEnrollmentArgs
{
PhoneInfo = "+11234567890",
MfaFactorId = MfaFactorIdType.Phone,
DisplayName = "John Does personal phone",
},
},
};
UserRecord userRecord = await FirebaseAuth.DefaultInstance.CreateUserAsync(args);
// See the UserRecord reference doc for the contents of userRecord.
Expand Down Expand Up @@ -458,6 +467,16 @@ internal static async Task UpdateUserAsync(string uid)
DisplayName = "Jane Doe",
PhotoUrl = "http://www.example.com/12345678/photo.png",
Disabled = true,
Mfa = new List<MfaEnrollmentArgs>()
{
new MfaEnrollmentArgs()
{
PhoneInfo = "+11234567890",
MfaFactorId = MfaFactorIdType.Phone,
MfaEnrollmentId = "CoolId",
EnrolledAt = DateTime.UtcNow,
},
},
};
UserRecord userRecord = await FirebaseAuth.DefaultInstance.UpdateUserAsync(args);
// See the UserRecord reference doc for the contents of userRecord.
Expand Down
78 changes: 78 additions & 0 deletions FirebaseAdmin/FirebaseAdmin.Tests/Auth/MfaEnrollmentTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using FirebaseAdmin.Auth;
using FirebaseAdmin.Auth.Users;
using Xunit;

namespace FirebaseAdmin.Tests.Auth
{
public class MfaEnrollmentTest
{
[Fact]
public void NullResponse()
{
Assert.Throws<ArgumentNullException>(() => new MfaEnrollment(null));
}

[Fact]
public void EmptyUid()
{
Assert.Throws<ArgumentException>(() => new MfaEnrollment(new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = string.Empty,
}));
}

[Fact]
public void NoInfo()
{
Assert.Throws<ArgumentException>(() => new MfaEnrollment(new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "testId",
PhoneInfo = null,
TotpInfo = null,
}));
}

[Fact]
public void ConflictingInfo()
{
Assert.Throws<ArgumentException>(() => new MfaEnrollment(new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "testId",
PhoneInfo = "+10987654321",
TotpInfo = new(),
}));
}

[Fact]
public void ValidPhoneFactor()
{
var enrollment = new MfaEnrollment(new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "testId",
PhoneInfo = "+10987654321",
EnrolledAt = DateTime.Parse("2014-10-03T15:01:23Z"),
});

Assert.Equal("testId", enrollment.MfaEnrollmentId);
Assert.Equal("+10987654321", enrollment.PhoneInfo);
Assert.Equal(DateTime.Parse("2014-10-03T15:01:23Z"), enrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Phone, enrollment.MfaFactorId);
}

[Fact]
public void ValidTotpFactor()
{
var enrollment = new MfaEnrollment(new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "testId",
TotpInfo = new(),
EnrolledAt = DateTime.Parse("2014-10-03T15:01:23Z"),
});

Assert.Equal("testId", enrollment.MfaEnrollmentId);
Assert.Equal(DateTime.Parse("2014 - 10 - 03T15:01:23Z"), enrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Totp, enrollment.MfaFactorId);
}
}
}
48 changes: 48 additions & 0 deletions FirebaseAdmin/FirebaseAdmin.Tests/Auth/UserRecordTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ public void AllProperties()
PhoneNumber = "+10987654321",
},
},
Mfa = new List<GetAccountInfoResponse.MfaEnrollment>()
{
new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "mfa1",
DisplayName = "SecondFactor",
PhoneInfo = "*********321",
EnrolledAt = DateTime.Parse("2014-10-03T15:01:23Z"),
},
new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "mfa2",
DisplayName = "SecondSecondFactor",
PhoneInfo = "*********322",
EnrolledAt = DateTime.Parse("2014-10-03T15:01:23Z"),
},
new GetAccountInfoResponse.MfaEnrollment()
{
MfaEnrollmentId = "totp",
DisplayName = "totp",
TotpInfo = new(),
EnrolledAt = DateTime.Parse("2014-10-03T15:01:23Z"),
},
},
};
var user = new UserRecord(response);

Expand Down Expand Up @@ -141,6 +165,30 @@ public void AllProperties()
Assert.Equal("+10987654321", provider.PhoneNumber);
Assert.Equal("https://other.com/user.png", provider.PhotoUrl);

var mfaEnrollment = user.Mfa[0];
Assert.Equal("mfa1", mfaEnrollment.MfaEnrollmentId);
Assert.Equal("SecondFactor", mfaEnrollment.DisplayName);
Assert.Equal("*********321", mfaEnrollment.PhoneInfo);
Assert.Equal(DateTime.Parse("2014-10-03T15:01:23Z"), mfaEnrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Phone, mfaEnrollment.MfaFactorId);
Assert.Null(mfaEnrollment.UnobfuscatedPhoneInfo);

mfaEnrollment = user.Mfa[1];
Assert.Equal("mfa2", mfaEnrollment.MfaEnrollmentId);
Assert.Equal("SecondSecondFactor", mfaEnrollment.DisplayName);
Assert.Equal("*********322", mfaEnrollment.PhoneInfo);
Assert.Equal(DateTime.Parse("2014-10-03T15:01:23Z"), mfaEnrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Phone, mfaEnrollment.MfaFactorId);
Assert.Null(mfaEnrollment.UnobfuscatedPhoneInfo);

mfaEnrollment = user.Mfa[2];
Assert.Equal("totp", mfaEnrollment.MfaEnrollmentId);
Assert.Equal("totp", mfaEnrollment.DisplayName);
Assert.Equal(DateTime.Parse("2014-10-03T15:01:23Z"), mfaEnrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Totp, mfaEnrollment.MfaFactorId);
Assert.Null(mfaEnrollment.UnobfuscatedPhoneInfo);
Assert.Null(mfaEnrollment.PhoneInfo);

var metadata = user.UserMetaData;
Assert.NotNull(metadata);
Assert.Equal(UserRecord.UnixEpoch.AddMilliseconds(100), metadata.CreationTimestamp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public async Task GetUserById(TestConfig config)
Assert.Empty(userRecord.ProviderData);
Assert.Null(userRecord.UserMetaData.CreationTimestamp);
Assert.Null(userRecord.UserMetaData.LastSignInTimestamp);
Assert.Null(userRecord.Mfa);

config.AssertRequest("accounts:lookup", handler.Requests[0]);
var request = NewtonsoftJsonSerializer.Instance
Expand Down Expand Up @@ -113,6 +114,20 @@ public async Task GetUserByIdWithProperties(TestConfig config)
],
""createdAt"": 100,
""lastLoginAt"": 150,
""mfaInfo"": [
{
""mfaEnrollmentId"": ""test"",
""displayName"": ""test name"",
""enrolledAt"": ""2014-10-02T15:01:23Z"",
""phoneInfo"": ""+10987654321""
},
{
""mfaEnrollmentId"": ""test2"",
""displayName"": ""test name2"",
""enrolledAt"": ""2014-10-03T15:01:23Z"",
""totpInfo"": {}
},
],
}";
var handler = new MockMessageHandler()
{
Expand Down Expand Up @@ -157,6 +172,19 @@ public async Task GetUserByIdWithProperties(TestConfig config)
Assert.Equal("+10987654321", provider.PhoneNumber);
Assert.Equal("https://other.com/user.png", provider.PhotoUrl);

var enrollment = userRecord.Mfa[0];
Assert.Equal("test", enrollment.MfaEnrollmentId);
Assert.Equal("test name", enrollment.DisplayName);
Assert.Equal("+10987654321", enrollment.PhoneInfo);
Assert.Equal(DateTime.Parse("2014-10-02T15:01:23Z").ToUniversalTime(), enrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Phone, enrollment.MfaFactorId);

enrollment = userRecord.Mfa[1];
Assert.Equal("test2", enrollment.MfaEnrollmentId);
Assert.Equal("test name2", enrollment.DisplayName);
Assert.Equal(DateTime.Parse("2014-10-03T15:01:23Z").ToUniversalTime(), enrollment.EnrolledAt);
Assert.Equal(MfaFactorIdType.Totp, enrollment.MfaFactorId);

var metadata = userRecord.UserMetaData;
Assert.NotNull(metadata);
Assert.Equal(UserRecord.UnixEpoch.AddMilliseconds(100), metadata.CreationTimestamp);
Expand Down Expand Up @@ -227,6 +255,7 @@ public async Task GetUserByEmail(TestConfig config)
Assert.Empty(userRecord.ProviderData);
Assert.Null(userRecord.UserMetaData.CreationTimestamp);
Assert.Null(userRecord.UserMetaData.LastSignInTimestamp);
Assert.Null(userRecord.Mfa);

config.AssertRequest("accounts:lookup", handler.Requests[0]);
var request = NewtonsoftJsonSerializer.Instance
Expand Down Expand Up @@ -296,6 +325,7 @@ public async Task GetUserByPhoneNumber(TestConfig config)
Assert.Empty(userRecord.ProviderData);
Assert.Null(userRecord.UserMetaData.CreationTimestamp);
Assert.Null(userRecord.UserMetaData.LastSignInTimestamp);
Assert.Null(userRecord.Mfa);

config.AssertRequest("accounts:lookup", handler.Requests[0]);
var request = NewtonsoftJsonSerializer.Instance
Expand Down Expand Up @@ -850,7 +880,7 @@ public async Task CreateUser(TestConfig config)
Assert.Equal(2, handler.Requests.Count);
config.AssertRequest("accounts", handler.Requests[0]);
config.AssertRequest("accounts:lookup", handler.Requests[1]);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.Requests[0].Body);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JToken>(handler.Requests[0].Body);
Assert.Empty(request);
}

Expand Down Expand Up @@ -881,7 +911,7 @@ public async Task CreateUserWithArgs(TestConfig config)
Assert.Equal(2, handler.Requests.Count);
config.AssertRequest("accounts", handler.Requests[0]);
config.AssertRequest("accounts:lookup", handler.Requests[1]);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.Requests[0].Body);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JToken>(handler.Requests[0].Body);
Assert.True((bool)request["disabled"]);
Assert.Equal("Test User", request["displayName"]);
Assert.Equal("user@example.com", request["email"]);
Expand Down Expand Up @@ -915,7 +945,7 @@ public async Task CreateUserWithExplicitDefaults(TestConfig config)

Assert.Equal("user1", user.Uid);
Assert.Equal(config.TenantId, user.TenantId);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.Requests[0].Body);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JToken>(handler.Requests[0].Body);
Assert.Equal(2, handler.Requests.Count);
config.AssertRequest("accounts", handler.Requests[0]);
config.AssertRequest("accounts:lookup", handler.Requests[1]);
Expand Down Expand Up @@ -1125,7 +1155,7 @@ public async Task UpdateUser(TestConfig config)
Assert.Equal(2, handler.Requests.Count);
config.AssertRequest("accounts:update", handler.Requests[0]);
config.AssertRequest("accounts:lookup", handler.Requests[1]);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.Requests[0].Body);
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JToken>(handler.Requests[0].Body);
Assert.Equal("user1", request["localId"]);
Assert.True((bool)request["disableUser"]);
Assert.Equal("Test User", request["displayName"]);
Expand All @@ -1135,7 +1165,7 @@ public async Task UpdateUser(TestConfig config)
Assert.Equal("+1234567890", request["phoneNumber"]);
Assert.Equal("https://example.com/user.png", request["photoUrl"]);

var claims = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>((string)request["customAttributes"]);
var claims = NewtonsoftJsonSerializer.Instance.Deserialize<JToken>((string)request["customAttributes"]);
Assert.True((bool)claims["admin"]);
Assert.Equal(4L, claims["level"]);
Assert.Equal("gold", claims["package"]);
Expand Down
Loading