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): 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("[email protected]", 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