Add Email and Phone TOTP providers

Now that HMACSHA1 is available on K
This commit is contained in:
Hao Kung 2014-07-23 11:57:14 -07:00
parent 661464004b
commit 076ea0385f
8 changed files with 386 additions and 173 deletions

View File

@ -0,0 +1,80 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// TokenProvider that generates tokens from the user's security stamp and notifies a user via their email
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class EmailTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser>
where TUser : class
{
private string _body;
private string _subject;
/// <summary>
/// Email subject used when a token notification is received
/// </summary>
public string Subject
{
get { return _subject ?? string.Empty; }
set { _subject = value; }
}
/// <summary>
/// Format string which will be used for the email body, it will be passed the token for the first parameter
/// </summary>
public string BodyFormat
{
get { return _body ?? "{0}"; }
set { _body = value; }
}
/// <summary>
/// True if the user has an email set
/// </summary>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override async Task<bool> IsValidProviderForUserAsync(UserManager<TUser> manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
var email = await manager.GetEmailAsync(user, cancellationToken);
return !String.IsNullOrWhiteSpace(email) && await manager.IsEmailConfirmedAsync(user, cancellationToken);
}
/// <summary>
/// Returns the email of the user for entropy in the token
/// </summary>
/// <param name="purpose"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager,
TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
var email = await manager.GetEmailAsync(user, cancellationToken);
return "Email:" + purpose + ":" + email;
}
/// <summary>
/// Notifies the user with a token via email using the Subject and BodyFormat
/// </summary>
/// <param name="token"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override Task NotifyAsync(string token, UserManager<TUser> 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);
}
}
}

View File

@ -26,6 +26,7 @@
<Compile Include="ClaimsIdentityOptions.cs" />
<Compile Include="Crypto.cs" />
<Compile Include="DefaultAuthenticationTypes.cs" />
<Compile Include="PhoneNumberTokenProvider.cs" />
<Compile Include="IAuthenticationManager.cs" />
<Compile Include="IClaimsIdentityFactory.cs" />
<Compile Include="IdentityBuilder.cs" />
@ -57,6 +58,8 @@
<Compile Include="IUserRoleStore.cs" />
<Compile Include="IUserSecurityStampStore.cs" />
<Compile Include="IUserStore.cs" />
<Compile Include="EmailTokenProvider.cs" />
<Compile Include="TotpSecurityStampBasedTokenProvider.cs" />
<Compile Include="IUserTokenProvider.cs" />
<Compile Include="IUserTwoFactorStore.cs" />
<Compile Include="IUserValidator.cs" />

View File

@ -0,0 +1,78 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// TokenProvider that generates tokens from the user's security stamp and notifies a user via their phone number
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class PhoneNumberTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser>
where TUser : class
{
private string _body;
/// <summary>
/// Message contents which should contain a format string which the token will be the only argument
/// </summary>
public string MessageFormat
{
get { return _body ?? "{0}"; }
set { _body = value; }
}
/// <summary>
/// Returns true if the user has a phone number set
/// </summary>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override async Task<bool> IsValidProviderForUserAsync(UserManager<TUser> 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);
}
/// <summary>
/// Returns the phone number of the user for entropy in the token
/// </summary>
/// <param name="purpose"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> 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;
}
/// <summary>
/// Notifies the user with a token via SMS using the MessageFormat
/// </summary>
/// <param name="token"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public override Task NotifyAsync(string token, UserManager<TUser> 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);
}
}
}

View File

@ -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

View File

@ -0,0 +1,108 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// TokenProvider that generates time based codes using the user's security stamp
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public class TotpSecurityStampBasedTokenProvider<TUser> : IUserTokenProvider<TUser>
where TUser : class
{
/// <summary>
/// This token provider does not notify the user by default
/// </summary>
/// <param name="token"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public virtual Task NotifyAsync(string token, UserManager<TUser> manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
/// <summary>
/// Returns true if the provider can generate tokens for the user, by default this is equal to
/// manager.SupportsUserSecurityStamp
/// </summary>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public virtual Task<bool> IsValidProviderForUserAsync(UserManager<TUser> manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return Task.FromResult(manager.SupportsUserSecurityStamp);
}
/// <summary>
/// Generate a token for the user using their security stamp
/// </summary>
/// <param name="purpose"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> 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);
}
/// <summary>
/// Validate the token for the user
/// </summary>
/// <param name="purpose"></param>
/// <param name="token"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> 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);
}
/// <summary>
/// Used for entropy in the token, uses the user.Id by default
/// </summary>
/// <param name="purpose"></param>
/// <param name="manager"></param>
/// <param name="user"></param>
/// <returns></returns>
public virtual async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
string userId = await manager.GetUserIdAsync(user);
return "Totp:" + purpose + ":" + userId;
}
}
}

View File

@ -1291,8 +1291,7 @@ namespace Microsoft.AspNet.Identity
// Two factor APIS
#if NET45
internal async Task<SecurityToken> CreateSecurityToken(TUser user)
internal async Task<SecurityToken> 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
/// <summary>
/// Verify a phone number code for a specific user and phone number
@ -1323,14 +1321,12 @@ namespace Microsoft.AspNet.Identity
public virtual async Task<bool> 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;
}

View File

@ -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"
}
}

View File

@ -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<ApplicationUser>
{
public Task<string> GenerateAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user, CancellationToken token)
{
return Task.FromResult(MakeToken(purpose));
}
public Task<bool> ValidateAsync(string purpose, string token, UserManager<ApplicationUser> manager,
ApplicationUser user, CancellationToken cancellationToken)
{
return Task.FromResult(token == MakeToken(purpose));
}
public Task NotifyAsync(string token, UserManager<ApplicationUser> manager, ApplicationUser user, CancellationToken cancellationToken)
{
return manager.SendEmailAsync(user, token, token);
}
public async Task<bool> IsValidProviderForUserAsync(UserManager<ApplicationUser> 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<ApplicationUser>
{
public Task<string> GenerateAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user, CancellationToken token)
{
return Task.FromResult(MakeToken(purpose));
}
public Task<bool> ValidateAsync(string purpose, string token, UserManager<ApplicationUser> manager,
ApplicationUser user, CancellationToken cancellationToken)
{
return Task.FromResult(token == MakeToken(purpose));
}
public Task NotifyAsync(string token, UserManager<ApplicationUser> manager, ApplicationUser user, CancellationToken cancellationToken)
{
return manager.SendSmsAsync(user, token);
}
public async Task<bool> IsValidProviderForUserAsync(UserManager<ApplicationUser> 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<ApplicationUser> { 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<ApplicationUser>
{
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<ApplicationUser>
// {
// 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<ApplicationUser>());
// 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<ApplicationUser>());
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<ApplicationUser>());
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<ApplicationUser>
// {
// 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<ApplicationUser>
{
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<ApplicationUser>());
manager.RegisterTwoFactorProvider("email", new EmailTokenProvider<ApplicationUser>());
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<ApplicationUser>());
// 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<ApplicationUser>());
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<ApplicationUser>());
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>());
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<ApplicationUser>());
var user = new ApplicationUser { PhoneNumber = "4251234567" };
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, "bogus"));