diff --git a/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs b/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs new file mode 100644 index 0000000000..eb5219894e --- /dev/null +++ b/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs @@ -0,0 +1,80 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity +{ + /// + /// TokenProvider that generates tokens from the user's security stamp and notifies a user via their email + /// + /// + public class EmailTokenProvider : TotpSecurityStampBasedTokenProvider + where TUser : class + { + private string _body; + private string _subject; + + /// + /// Email subject used when a token notification is received + /// + public string Subject + { + get { return _subject ?? string.Empty; } + set { _subject = value; } + } + + /// + /// Format string which will be used for the email body, it will be passed the token for the first parameter + /// + public string BodyFormat + { + get { return _body ?? "{0}"; } + set { _body = value; } + } + + /// + /// True if the user has an email set + /// + /// + /// + /// + public override async Task IsValidProviderForUserAsync(UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + var email = await manager.GetEmailAsync(user, cancellationToken); + return !String.IsNullOrWhiteSpace(email) && await manager.IsEmailConfirmedAsync(user, cancellationToken); + } + + /// + /// Returns the email of the user for entropy in the token + /// + /// + /// + /// + /// + public override async Task GetUserModifierAsync(string purpose, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + var email = await manager.GetEmailAsync(user, cancellationToken); + return "Email:" + purpose + ":" + email; + } + + /// + /// Notifies the user with a token via email using the Subject and BodyFormat + /// + /// + /// + /// + /// + public override Task NotifyAsync(string token, UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + return manager.SendEmailAsync(user, Subject, String.Format(CultureInfo.CurrentCulture, BodyFormat, token), cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index d418832fbb..ee7363ba94 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -26,6 +26,7 @@ + @@ -57,6 +58,8 @@ + + diff --git a/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs b/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs new file mode 100644 index 0000000000..100c51586d --- /dev/null +++ b/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs @@ -0,0 +1,78 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity +{ + /// + /// TokenProvider that generates tokens from the user's security stamp and notifies a user via their phone number + /// + /// + public class PhoneNumberTokenProvider : TotpSecurityStampBasedTokenProvider + where TUser : class + { + private string _body; + + /// + /// Message contents which should contain a format string which the token will be the only argument + /// + public string MessageFormat + { + get { return _body ?? "{0}"; } + set { _body = value; } + } + + /// + /// Returns true if the user has a phone number set + /// + /// + /// + /// + public override async Task IsValidProviderForUserAsync(UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + var phoneNumber = await manager.GetPhoneNumberAsync(user, cancellationToken); + return !String.IsNullOrWhiteSpace(phoneNumber) && await manager.IsPhoneNumberConfirmedAsync(user, cancellationToken); + } + + /// + /// Returns the phone number of the user for entropy in the token + /// + /// + /// + /// + /// + public override async Task GetUserModifierAsync(string purpose, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + var phoneNumber = await manager.GetPhoneNumberAsync(user, cancellationToken); + return "PhoneNumber:" + purpose + ":" + phoneNumber; + } + + /// + /// Notifies the user with a token via SMS using the MessageFormat + /// + /// + /// + /// + /// + public override Task NotifyAsync(string token, UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + return manager.SendSmsAsync(user, String.Format(CultureInfo.CurrentCulture, MessageFormat, token), cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs b/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs index 07a60be7d7..2a491f505a 100644 --- a/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs +++ b/src/Microsoft.AspNet.Identity/Rfc6238AuthenticationService.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if NET45 - using System; using System.Diagnostics; using System.Net; @@ -17,7 +15,7 @@ namespace Microsoft.AspNet.Identity public SecurityToken(byte[] data) { - _data = (byte[]) data.Clone(); + _data = (byte[])data.Clone(); } internal byte[] GetDataNoClone() @@ -39,10 +37,10 @@ namespace Microsoft.AspNet.Identity // See https://tools.ietf.org/html/rfc4226 // We can add an optional modifier - var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long) timestepNumber)); + var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); - // GenerateAsync DT string + // Generate DT string var offset = hash[hash.Length - 1] & 0xf; Debug.Assert(offset + 4 < hash.Length); var binaryCode = (hash[offset] & 0x7f) << 24 @@ -50,7 +48,7 @@ namespace Microsoft.AspNet.Identity | (hash[offset + 2] & 0xff) << 8 | (hash[offset + 3] & 0xff); - return binaryCode%mod; + return binaryCode % mod; } private static byte[] ApplyModifier(byte[] input, string modifier) @@ -71,7 +69,7 @@ namespace Microsoft.AspNet.Identity private static ulong GetCurrentTimeStepNumber() { var delta = DateTime.UtcNow - _unixEpoch; - return (ulong) (delta.Ticks/_timestep.Ticks); + return (ulong)(delta.Ticks / _timestep.Ticks); } public static int GenerateCode(SecurityToken securityToken, string modifier = null) @@ -102,7 +100,7 @@ namespace Microsoft.AspNet.Identity { for (var i = -2; i <= 2; i++) { - var computedTotp = ComputeTotp(hashAlgorithm, (ulong) ((long) currentTimeStep + i), modifier); + var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); if (computedTotp == code) { return true; @@ -115,4 +113,3 @@ namespace Microsoft.AspNet.Identity } } } -#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/TotpSecurityStampBasedTokenProvider.cs b/src/Microsoft.AspNet.Identity/TotpSecurityStampBasedTokenProvider.cs new file mode 100644 index 0000000000..c032693585 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/TotpSecurityStampBasedTokenProvider.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity +{ + /// + /// TokenProvider that generates time based codes using the user's security stamp + /// + /// + /// + public class TotpSecurityStampBasedTokenProvider : IUserTokenProvider + where TUser : class + { + /// + /// This token provider does not notify the user by default + /// + /// + /// + /// + /// + public virtual Task NotifyAsync(string token, UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } + + /// + /// Returns true if the provider can generate tokens for the user, by default this is equal to + /// manager.SupportsUserSecurityStamp + /// + /// + /// + /// + public virtual Task IsValidProviderForUserAsync(UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + return Task.FromResult(manager.SupportsUserSecurityStamp); + } + + /// + /// Generate a token for the user using their security stamp + /// + /// + /// + /// + /// + public virtual async Task GenerateAsync(string purpose, UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + var token = await manager.CreateSecurityTokenAsync(user); + var modifier = await GetUserModifierAsync(purpose, manager, user); + return Rfc6238AuthenticationService.GenerateCode(token, modifier).ToString("D6", CultureInfo.InvariantCulture); + } + + /// + /// Validate the token for the user + /// + /// + /// + /// + /// + /// + public virtual async Task ValidateAsync(string purpose, string token, UserManager manager, + TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + int code; + if (!Int32.TryParse(token, out code)) + { + return false; + } + var securityToken = await manager.CreateSecurityTokenAsync(user); + var modifier = await GetUserModifierAsync(purpose, manager, user); + return securityToken != null && Rfc6238AuthenticationService.ValidateCode(securityToken, code, modifier); + } + + /// + /// Used for entropy in the token, uses the user.Id by default + /// + /// + /// + /// + /// + public virtual async Task GetUserModifierAsync(string purpose, UserManager manager, TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (manager == null) + { + throw new ArgumentNullException("manager"); + } + string userId = await manager.GetUserIdAsync(user); + return "Totp:" + purpose + ":" + userId; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 23163f10e7..0176171209 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -1291,8 +1291,7 @@ namespace Microsoft.AspNet.Identity // Two factor APIS -#if NET45 - internal async Task CreateSecurityToken(TUser user) + internal async Task CreateSecurityTokenAsync(TUser user) { return new SecurityToken(Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user))); @@ -1308,10 +1307,9 @@ namespace Microsoft.AspNet.Identity { ThrowIfDisposed(); return - Rfc6238AuthenticationService.GenerateCode(await CreateSecurityToken(user), phoneNumber) + Rfc6238AuthenticationService.GenerateCode(await CreateSecurityTokenAsync(user), phoneNumber) .ToString(CultureInfo.InvariantCulture); } -#endif /// /// Verify a phone number code for a specific user and phone number @@ -1323,14 +1321,12 @@ namespace Microsoft.AspNet.Identity public virtual async Task VerifyChangePhoneNumberTokenAsync(TUser user, string token, string phoneNumber) { ThrowIfDisposed(); -#if NET45 - var securityToken = await CreateSecurityToken(user); + var securityToken = await CreateSecurityTokenAsync(user); int code; if (securityToken != null && Int32.TryParse(token, out code)) { return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber); } -#endif return false; } diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json index 77ab50fdc2..f9c78d70fc 100644 --- a/src/Microsoft.AspNet.Identity/project.json +++ b/src/Microsoft.AspNet.Identity/project.json @@ -18,6 +18,7 @@ "System.Linq": "4.0.0.0", "System.Linq.Expressions": "4.0.0.0", "System.Linq.Queryable": "4.0.0.0", + "System.Net.Primitives": "4.0.10.0", "System.Reflection": "4.0.10.0", "System.Reflection.Extensions": "4.0.0.0", "System.Resources.ResourceManager": "4.0.0.0", @@ -25,7 +26,10 @@ "System.Runtime.Extensions": "4.0.10.0", "System.Security.Principal": "4.0.0.0", "System.Security.Cryptography.DeriveBytes": "4.0.0.0", + "System.Security.Cryptography.Hashing": "4.0.0.0", + "System.Security.Cryptography.Hashing.Algorithms": "4.0.0.0", "System.Text.Encoding": "4.0.20.0", + "System.Text.Encoding.Extensions": "4.0.10.0", "System.Threading.Tasks": "4.0.10.0" } } diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTestBase.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTestBase.cs index 173010894a..066dc80bac 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTestBase.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTestBase.cs @@ -1011,10 +1011,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test var role = new ApplicationRole(); Assert.Null(await manager.FindByIdAsync(role.Id.ToString())); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - //Assert.Equal(role, await manager.FindByIdAsync(role.Id.ToString())); - var fetch = await manager.FindByIdAsync(role.Id.ToString()); - Assert.Equal(role.Id, fetch.Id); - Assert.Equal(role.Name, fetch.Name); + Assert.Equal(role, await manager.FindByIdAsync(role.Id.ToString())); } [Fact] @@ -1025,10 +1022,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test Assert.Null(await manager.FindByNameAsync(role.Name)); Assert.False(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - var fetch = await manager.FindByNameAsync(role.Name); - //Assert.Equal(role, fetch); - Assert.Equal(role.Id, fetch.Id); - Assert.Equal(role.Name, fetch.Name); + Assert.Equal(role, await manager.FindByNameAsync(role.Name)); } [Fact] @@ -1299,7 +1293,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test Assert.NotEqual(stamp, user.SecurityStamp); } -#if NET45 [Fact] public async Task CanChangePhoneNumber() { @@ -1346,65 +1339,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); } -#endif - - private class EmailTokenProvider : IUserTokenProvider - { - public Task GenerateAsync(string purpose, UserManager manager, ApplicationUser user, CancellationToken token) - { - return Task.FromResult(MakeToken(purpose)); - } - - public Task ValidateAsync(string purpose, string token, UserManager manager, - ApplicationUser user, CancellationToken cancellationToken) - { - return Task.FromResult(token == MakeToken(purpose)); - } - - public Task NotifyAsync(string token, UserManager manager, ApplicationUser user, CancellationToken cancellationToken) - { - return manager.SendEmailAsync(user, token, token); - } - - public async Task IsValidProviderForUserAsync(UserManager manager, ApplicationUser user, CancellationToken token) - { - return !string.IsNullOrEmpty(await manager.GetEmailAsync(user)); - } - - private static string MakeToken(string purpose) - { - return "email:" + purpose; - } - } - - private class SmsTokenProvider : IUserTokenProvider - { - public Task GenerateAsync(string purpose, UserManager manager, ApplicationUser user, CancellationToken token) - { - return Task.FromResult(MakeToken(purpose)); - } - - public Task ValidateAsync(string purpose, string token, UserManager manager, - ApplicationUser user, CancellationToken cancellationToken) - { - return Task.FromResult(token == MakeToken(purpose)); - } - - public Task NotifyAsync(string token, UserManager manager, ApplicationUser user, CancellationToken cancellationToken) - { - return manager.SendSmsAsync(user, token); - } - - public async Task IsValidProviderForUserAsync(UserManager manager, ApplicationUser user, CancellationToken token) - { - return !string.IsNullOrEmpty(await manager.GetPhoneNumberAsync(user)); - } - - private static string MakeToken(string purpose) - { - return "sms:" + purpose; - } - } [Fact] public async Task CanEmailTwoFactorToken() @@ -1413,8 +1347,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test var messageService = new TestMessageService(); manager.EmailService = messageService; const string factorId = "EmailCode"; - manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); + var subject = "Subject"; + manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider { Subject = subject }); var user = new ApplicationUser { Email = "foo@foo.com" }; + user.EmailConfirmed = true; const string password = "password"; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); var stamp = user.SecurityStamp; @@ -1422,9 +1358,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); Assert.NotNull(token); Assert.Null(messageService.Message); + Assert.True(await manager.IsEmailConfirmedAsync(user)); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); - Assert.Equal(token, messageService.Message.Subject); + Assert.Equal(subject, messageService.Message.Subject); Assert.Equal(token, messageService.Message.Body); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } @@ -1441,49 +1378,48 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test "No IUserTwoFactorProvider for 'Bogus' is registered."); } + [Fact] + public async Task EmailTokenFactorWithFormatTest() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.EmailService = messageService; + const string factorId = "EmailCode"; + manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider + { + Subject = "Security Code", + BodyFormat = "Your code is: {0}" + }); + var user = new ApplicationUser { Email = "foo@foo.com" }; + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal("Security Code", messageService.Message.Subject); + Assert.Equal("Your code is: " + token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + } - //[Fact] - //public async Task EmailTokenFactorWithFormatTest() - //{ - // var manager = CreateManager(); - // var messageService = new TestMessageService(); - // manager.EmailService = messageService; - // const string factorId = "EmailCode"; - // manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider - // { - // Subject = "Security Code", - // BodyFormat = "Your code is: {0}" - // }); - // var user = new ApplicationUser("EmailCodeTest") { Email = "foo@foo.com" }; - // const string password = "password"; - // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // Assert.Null(messageService.Message); - // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); - // Assert.NotNull(messageService.Message); - // Assert.Equal("Security Code", messageService.Message.Subject); - // Assert.Equal("Your code is: " + token, messageService.Message.Body); - // Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} - - //[Fact] - //public async Task EmailFactorFailsAfterSecurityStampChangeTest() - //{ - // var manager = CreateManager(); - // const string factorId = "EmailCode"; - // manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); - // var user = new ApplicationUser("EmailCodeTest") { Email = "foo@foo.com" }; - // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} + [Fact] + public async Task EmailFactorFailsAfterSecurityStampChangeTest() + { + var manager = CreateManager(); + const string factorId = "EmailCode"; + manager.RegisterTwoFactorProvider(factorId, new EmailTokenProvider()); + var user = new ApplicationUser { Email = "foo@foo.com" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + } [Fact] public async Task EnableTwoFactorChangesSecurityStamp() @@ -1532,7 +1468,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test var messageService = new TestMessageService(); manager.SmsService = messageService; const string factorId = "PhoneCode"; - manager.RegisterTwoFactorProvider(factorId, new SmsTokenProvider()); + manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider()); var user = new ApplicationUser { PhoneNumber = "4251234567" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var stamp = user.SecurityStamp; @@ -1546,29 +1482,29 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } - //[Fact] - //public async Task PhoneTokenFactorFormatTest() - //{ - // var manager = CreateManager(); - // var messageService = new TestMessageService(); - // manager.SmsService = messageService; - // const string factorId = "PhoneCode"; - // manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider - // { - // MessageFormat = "Your code is: {0}" - // }); - // var user = new ApplicationUser("PhoneCodeTest") { PhoneNumber = "4251234567" }; - // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // Assert.Null(messageService.Message); - // IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); - // Assert.NotNull(messageService.Message); - // Assert.Equal("Your code is: " + token, messageService.Message.Body); - // Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} + [Fact] + public async Task PhoneTokenFactorFormatTest() + { + var manager = CreateManager(); + var messageService = new TestMessageService(); + manager.SmsService = messageService; + const string factorId = "PhoneCode"; + manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider + { + MessageFormat = "Your code is: {0}" + }); + var user = new ApplicationUser { PhoneNumber = "4251234567" }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + Assert.Null(messageService.Message); + IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); + Assert.NotNull(messageService.Message); + Assert.Equal("Your code is: " + token, messageService.Message.Body); + Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + } [Fact] public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() @@ -1599,8 +1535,8 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test public async Task CanGetValidTwoFactor() { var manager = CreateManager(); - manager.RegisterTwoFactorProvider("phone", new SmsTokenProvider()); - manager.RegisterTwoFactorProvider("email", new EmailTokenProvider()); + manager.RegisterTwoFactorProvider("phone", new PhoneNumberTokenProvider()); + manager.RegisterTwoFactorProvider("email", new EmailTokenProvider()); var user = new ApplicationUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1609,11 +1545,22 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); factors = await manager.GetValidTwoFactorProvidersAsync(user); Assert.NotNull(factors); + Assert.True(factors.Count() == 0); + user.PhoneNumberConfirmed = true; + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); Assert.True(factors.Count() == 1); Assert.Equal("phone", factors[0]); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); factors = await manager.GetValidTwoFactorProvidersAsync(user); Assert.NotNull(factors); + Assert.True(factors.Count() == 1); + Assert.Equal("phone", factors[0]); + user.EmailConfirmed = true; + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); Assert.True(factors.Count() == 2); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1622,29 +1569,29 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test Assert.Equal("phone", factors[0]); } - //[Fact] - //public async Task PhoneFactorFailsAfterSecurityStampChangeTest() - //{ - // var manager = CreateManager(); - // var factorId = "PhoneCode"; - // manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider()); - // var user = new ApplicationUser("PhoneCodeTest"); - // user.PhoneNumber = "4251234567"; - // IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - // var stamp = user.SecurityStamp; - // Assert.NotNull(stamp); - // var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - // Assert.NotNull(token); - // IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - // Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - //} + [Fact] + public async Task PhoneFactorFailsAfterSecurityStampChangeTest() + { + var manager = CreateManager(); + var factorId = "PhoneCode"; + manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider()); + var user = new ApplicationUser(); + user.PhoneNumber = "4251234567"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = user.SecurityStamp; + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + } [Fact] public async Task VerifyTokenFromWrongTokenProviderFails() { var manager = CreateManager(); - manager.RegisterTwoFactorProvider("PhoneCode", new SmsTokenProvider()); - manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider()); + manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider()); + manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider()); var user = new ApplicationUser { PhoneNumber = "4251234567" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var token = await manager.GenerateTwoFactorTokenAsync(user, "PhoneCode"); @@ -1657,7 +1604,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test { var manager = CreateManager(); const string factorId = "PhoneCode"; - manager.RegisterTwoFactorProvider(factorId, new SmsTokenProvider()); + manager.RegisterTwoFactorProvider(factorId, new PhoneNumberTokenProvider()); var user = new ApplicationUser { PhoneNumber = "4251234567" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, "bogus"));