Add support for Authentication Tokens to Identity
This commit is contained in:
parent
c8849685cf
commit
82863e9444
|
|
@ -172,6 +172,9 @@ namespace IdentitySample.Controllers
|
||||||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
|
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
|
// Update any authentication tokens if login succeeded
|
||||||
|
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
|
||||||
|
|
||||||
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
|
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +191,7 @@ namespace IdentitySample.Controllers
|
||||||
// If the user does not have an account, then ask the user to create an account.
|
// If the user does not have an account, then ask the user to create an account.
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
ViewData["LoginProvider"] = info.LoginProvider;
|
ViewData["LoginProvider"] = info.LoginProvider;
|
||||||
var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
|
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||||
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
|
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +220,10 @@ namespace IdentitySample.Controllers
|
||||||
{
|
{
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||||
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
|
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
|
||||||
|
|
||||||
|
// Update any authentication tokens as well
|
||||||
|
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
|
||||||
|
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<IdentityUserRole<TKey>> UserRoles { get; set; }
|
public DbSet<IdentityUserRole<TKey>> UserRoles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<IdentityUserToken<TKey>> UserTokens { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
|
/// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -207,6 +212,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
|
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
|
||||||
b.ToTable("AspNetUserLogins");
|
b.ToTable("AspNetUserLogins");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Entity<IdentityUserToken<TKey>>(b =>
|
||||||
|
{
|
||||||
|
b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name });
|
||||||
|
b.ToTable("AspNetUserTokens");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an authentication token for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">The type of the primary key used for users.</typeparam>
|
||||||
|
public class IdentityUserToken<TKey> where TKey : IEquatable<TKey>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the primary key of the user that the token belongs to.
|
||||||
|
/// </summary>
|
||||||
|
public virtual TKey UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LoginProvider this token is from.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string LoginProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the token.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the token value.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,7 +63,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
IUserLockoutStore<TUser>,
|
IUserLockoutStore<TUser>,
|
||||||
IUserPhoneNumberStore<TUser>,
|
IUserPhoneNumberStore<TUser>,
|
||||||
IQueryableUserStore<TUser>,
|
IQueryableUserStore<TUser>,
|
||||||
IUserTwoFactorStore<TUser>
|
IUserTwoFactorStore<TUser>,
|
||||||
|
IUserAuthenticationTokenStore<TUser>
|
||||||
where TUser : IdentityUser<TKey>
|
where TUser : IdentityUser<TKey>
|
||||||
where TRole : IdentityRole<TKey>
|
where TRole : IdentityRole<TKey>
|
||||||
where TContext : DbContext
|
where TContext : DbContext
|
||||||
|
|
@ -523,6 +524,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
private DbSet<IdentityUserClaim<TKey>> UserClaims { get { return Context.Set<IdentityUserClaim<TKey>>(); } }
|
private DbSet<IdentityUserClaim<TKey>> UserClaims { get { return Context.Set<IdentityUserClaim<TKey>>(); } }
|
||||||
private DbSet<IdentityUserRole<TKey>> UserRoles { get { return Context.Set<IdentityUserRole<TKey>>(); } }
|
private DbSet<IdentityUserRole<TKey>> UserRoles { get { return Context.Set<IdentityUserRole<TKey>>(); } }
|
||||||
private DbSet<IdentityUserLogin<TKey>> UserLogins { get { return Context.Set<IdentityUserLogin<TKey>>(); } }
|
private DbSet<IdentityUserLogin<TKey>> UserLogins { get { return Context.Set<IdentityUserLogin<TKey>>(); } }
|
||||||
|
private DbSet<IdentityUserToken<TKey>> UserTokens { get { return Context.Set<IdentityUserToken<TKey>>(); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the claims associated with the specified <paramref name="user"/> as an asynchronous operation.
|
/// Get the claims associated with the specified <paramref name="user"/> as an asynchronous operation.
|
||||||
|
|
@ -653,7 +655,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
LoginProvider = login.LoginProvider,
|
LoginProvider = login.LoginProvider,
|
||||||
ProviderDisplayName = login.ProviderDisplayName
|
ProviderDisplayName = login.ProviderDisplayName
|
||||||
};
|
};
|
||||||
// TODO: fixup so we don't have to update both
|
|
||||||
UserLogins.Add(l);
|
UserLogins.Add(l);
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1196,5 +1197,69 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||||
}
|
}
|
||||||
return new List<TUser>();
|
return new List<TUser>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task<IdentityUserToken<TKey>> FindToken(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var userId = user.Id;
|
||||||
|
return UserTokens.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.Name == name, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <inheritdoc>
|
||||||
|
public virtual async Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = await FindToken(user, loginProvider, name, cancellationToken);
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
UserTokens.Add(new IdentityUserToken<TKey>
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
LoginProvider = loginProvider,
|
||||||
|
Name = name,
|
||||||
|
Value = value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
var userId = user.Id;
|
||||||
|
var entry = await UserTokens.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.Name == name, cancellationToken);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
UserTokens.Remove(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
var entry = await FindToken(user, loginProvider, name, cancellationToken);
|
||||||
|
return entry?.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// Provides protection and validation of identity tokens.
|
/// Provides protection and validation of identity tokens.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TUser">The type used to represent a user.</typeparam>
|
/// <typeparam name="TUser">The type used to represent a user.</typeparam>
|
||||||
public class DataProtectorTokenProvider<TUser> : IUserTokenProvider<TUser> where TUser : class
|
public class DataProtectorTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser> where TUser : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DataProtectorTokenProvider{TUser}"/> class.
|
/// Initializes a new instance of the <see cref="DataProtectorTokenProvider{TUser}"/> class.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity
|
namespace Microsoft.AspNetCore.Identity
|
||||||
{
|
{
|
||||||
|
|
@ -13,20 +15,22 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="ExternalLoginInfo"/>
|
/// Creates a new instance of <see cref="ExternalLoginInfo"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="externalPrincipal">The <see cref="ClaimsPrincipal"/> to associate with this login.</param>
|
/// <param name="principal">The <see cref="ClaimsPrincipal"/> to associate with this login.</param>
|
||||||
/// <param name="loginProvider">The provider associated with this login information.</param>
|
/// <param name="loginProvider">The provider associated with this login information.</param>
|
||||||
/// <param name="providerKey">The unique identifier for this user provided by the login provider.</param>
|
/// <param name="providerKey">The unique identifier for this user provided by the login provider.</param>
|
||||||
/// <param name="displayName">The display name for this user provided by the login provider.</param>
|
/// <param name="displayName">The display name for this user provided by the login provider.</param>
|
||||||
public ExternalLoginInfo(ClaimsPrincipal externalPrincipal, string loginProvider, string providerKey,
|
public ExternalLoginInfo(ClaimsPrincipal principal, string loginProvider, string providerKey,
|
||||||
string displayName) : base(loginProvider, providerKey, displayName)
|
string displayName) : base(loginProvider, providerKey, displayName)
|
||||||
{
|
{
|
||||||
ExternalPrincipal = externalPrincipal;
|
Principal = principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ClaimsPrincipal"/> associated with this login.
|
/// Gets or sets the <see cref="ClaimsPrincipal"/> associated with this login.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The <see cref="ClaimsPrincipal"/> associated with this login.</value>
|
/// <value>The <see cref="ClaimsPrincipal"/> associated with this login.</value>
|
||||||
public ClaimsPrincipal ExternalPrincipal { get; set; }
|
public ClaimsPrincipal Principal { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<AuthenticationToken> AuthenticationTokens { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Identity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an abstraction to store a user's authentication tokens.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||||
|
public interface IUserAuthenticationTokenStore<TUser> : IUserStore<TUser> where TUser : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the token value for a particular user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||||
|
/// <param name="name">The name of the token.</param>
|
||||||
|
/// <param name="value">The value of the token.</param>
|
||||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||||
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a token for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||||
|
/// <param name="name">The name of the token.</param>
|
||||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||||
|
Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the token value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||||
|
/// <param name="name">The name of the token.</param>
|
||||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||||
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
|
Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,10 +31,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// <param name="loginProvider">The login provide whose information should be removed.</param>
|
/// <param name="loginProvider">The login provide whose information should be removed.</param>
|
||||||
/// <param name="providerKey">The key given by the external login provider for the specified user.</param>
|
/// <param name="providerKey">The key given by the external login provider for the specified user.</param>
|
||||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||||
/// <returns>
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||||
/// The <see cref="Task"/> that contains a flag the result of the asynchronous removing operation. The flag will be true if
|
|
||||||
/// the login information was existed and removed, otherwise false.
|
|
||||||
/// </returns>
|
|
||||||
Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken);
|
Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ using System.Threading.Tasks;
|
||||||
namespace Microsoft.AspNetCore.Identity
|
namespace Microsoft.AspNetCore.Identity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides an abstraction for token generators.
|
/// Provides an abstraction for two factor token generators.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||||
public interface IUserTokenProvider<TUser> where TUser : class
|
public interface IUserTwoFactorTokenProvider<TUser> where TUser : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a token for the specified <paramref name="user"/> and <paramref name="purpose"/>.
|
/// Generates a token for the specified <paramref name="user"/> and <paramref name="purpose"/>.
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the heck even if it was
|
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the heck even if it was
|
||||||
/// for the same user.
|
/// for the same user.
|
||||||
///
|
///
|
||||||
/// Implementations of <see cref="IUserTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
/// Implementations of <see cref="IUserTwoFactorTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||||
/// help with token separation.
|
/// help with token separation.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user);
|
Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user);
|
||||||
|
|
@ -133,11 +133,11 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// Adds a token provider for the <seealso cref="UserType"/>.
|
/// Adds a token provider for the <seealso cref="UserType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="providerName">The name of the provider to add.</param>
|
/// <param name="providerName">The name of the provider to add.</param>
|
||||||
/// <param name="provider">The type of the <see cref="IUserTokenProvider{TUser}"/> to add.</param>
|
/// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{TUser}"/> to add.</param>
|
||||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||||
public virtual IdentityBuilder AddTokenProvider(string providerName, Type provider)
|
public virtual IdentityBuilder AddTokenProvider(string providerName, Type provider)
|
||||||
{
|
{
|
||||||
if (!typeof(IUserTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
|
if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.FormatInvalidManagerType(provider.Name, "IUserTokenProvider", UserType.Name));
|
throw new InvalidOperationException(Resources.FormatInvalidManagerType(provider.Name, "IUserTokenProvider", UserType.Name));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Authentication;
|
using Microsoft.AspNetCore.Http.Authentication;
|
||||||
|
|
@ -460,8 +461,43 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// REVIEW: fix this wrap
|
return new ExternalLoginInfo(auth.Principal, provider, providerKey, new AuthenticationDescription(auth.Description).DisplayName)
|
||||||
return new ExternalLoginInfo(auth.Principal, provider, providerKey, new AuthenticationDescription(auth.Description).DisplayName);
|
{
|
||||||
|
AuthenticationTokens = new AuthenticationProperties(auth.Properties).GetTokens()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores any authentication tokens found in the external authentication cookie into the associated user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="externalLogin">The information from the external login provider.</param>
|
||||||
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
|
||||||
|
public virtual async Task<IdentityResult> UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin)
|
||||||
|
{
|
||||||
|
if (externalLogin == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(externalLogin));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (externalLogin.AuthenticationTokens != null && externalLogin.AuthenticationTokens.Any())
|
||||||
|
{
|
||||||
|
var user = await UserManager.FindByLoginAsync(externalLogin.LoginProvider, externalLogin.ProviderKey);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return IdentityResult.Failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var token in externalLogin.AuthenticationTokens)
|
||||||
|
{
|
||||||
|
var result = await UserManager.SetAuthenticationTokenAsync(user, externalLogin.LoginProvider, token.Name, token.Value);
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IdentityResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// Represents a token provider that generates time based codes using the user's security stamp.
|
/// Represents a token provider that generates time based codes using the user's security stamp.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||||
public abstract class TotpSecurityStampBasedTokenProvider<TUser> : IUserTokenProvider<TUser>
|
public abstract class TotpSecurityStampBasedTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser>
|
||||||
where TUser : class
|
where TUser : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the check even if it was
|
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the check even if it was
|
||||||
/// for the same user.
|
/// for the same user.
|
||||||
///
|
///
|
||||||
/// Implementations of <see cref="IUserTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
/// Implementations of <see cref="IUserTwoFactorTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||||
/// help with token separation.
|
/// help with token separation.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
protected const string ResetPasswordTokenPurpose = "ResetPassword";
|
protected const string ResetPasswordTokenPurpose = "ResetPassword";
|
||||||
protected const string ConfirmEmailTokenPurpose = "EmailConfirmation";
|
protected const string ConfirmEmailTokenPurpose = "EmailConfirmation";
|
||||||
|
|
||||||
private readonly Dictionary<string, IUserTokenProvider<TUser>> _tokenProviders =
|
private readonly Dictionary<string, IUserTwoFactorTokenProvider<TUser>> _tokenProviders =
|
||||||
new Dictionary<string, IUserTokenProvider<TUser>>();
|
new Dictionary<string, IUserTwoFactorTokenProvider<TUser>>();
|
||||||
|
|
||||||
private TimeSpan _defaultLockout = TimeSpan.Zero;
|
private TimeSpan _defaultLockout = TimeSpan.Zero;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
_context = services.GetService<IHttpContextAccessor>()?.HttpContext;
|
_context = services.GetService<IHttpContextAccessor>()?.HttpContext;
|
||||||
foreach (var providerName in Options.Tokens.ProviderMap.Keys)
|
foreach (var providerName in Options.Tokens.ProviderMap.Keys)
|
||||||
{
|
{
|
||||||
var provider = services.GetRequiredService(Options.Tokens.ProviderMap[providerName].ProviderType) as IUserTokenProvider<TUser>;
|
var provider = services.GetRequiredService(Options.Tokens.ProviderMap[providerName].ProviderType) as IUserTwoFactorTokenProvider<TUser>;
|
||||||
if (provider != null)
|
if (provider != null)
|
||||||
{
|
{
|
||||||
RegisterTokenProvider(providerName, provider);
|
RegisterTokenProvider(providerName, provider);
|
||||||
|
|
@ -122,6 +122,21 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
|
|
||||||
internal IdentityOptions Options { get; set; }
|
internal IdentityOptions Options { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a flag indicating whether the backing user store supports authentication tokens.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// true if the backing user store supports authentication tokens, otherwise false.
|
||||||
|
/// </value>
|
||||||
|
public virtual bool SupportsUserAuthenticationTokens
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
return Store is IUserAuthenticationTokenStore<TUser>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a flag indicating whether the backing user store supports two factor authentication.
|
/// Gets a flag indicating whether the backing user store supports two factor authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1601,7 +1616,7 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="providerName">The name of the provider to register.</param>
|
/// <param name="providerName">The name of the provider to register.</param>
|
||||||
/// <param name="provider">The provider to register.</param>
|
/// <param name="provider">The provider to register.</param>
|
||||||
public virtual void RegisterTokenProvider(string providerName, IUserTokenProvider<TUser> provider)
|
public virtual void RegisterTokenProvider(string providerName, IUserTwoFactorTokenProvider<TUser> provider)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
|
|
@ -1953,6 +1968,92 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return store.GetUsersInRoleAsync(NormalizeKey(roleName), CancellationToken);
|
return store.GetUsersInRoleAsync(NormalizeKey(roleName), CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an authentication token for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||||
|
/// <param name="tokenName">The name of the token.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual Task<string> GetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
|
||||||
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
var store = GetTokenStore();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
if (loginProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(loginProvider));
|
||||||
|
}
|
||||||
|
if (tokenName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tokenName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.GetTokenAsync(user, loginProvider, tokenName, CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets an authentication token for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||||
|
/// <param name="tokenName">The name of the token.</param>
|
||||||
|
/// <param name="tokenValue">The value of the token.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual async Task<IdentityResult> SetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName, string tokenValue)
|
||||||
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
var store = GetTokenStore();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
if (loginProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(loginProvider));
|
||||||
|
}
|
||||||
|
if (tokenName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tokenName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// REVIEW: should updating any tokens affect the security stamp?
|
||||||
|
await store.SetTokenAsync(user, loginProvider, tokenName, tokenValue, CancellationToken);
|
||||||
|
return await UpdateUserAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove an authentication token for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||||
|
/// <param name="tokenName">The name of the token.</param>
|
||||||
|
/// <returns>Whether a token was removed.</returns>
|
||||||
|
public virtual async Task<IdentityResult> RemoveAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
|
||||||
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
var store = GetTokenStore();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
if (loginProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(loginProvider));
|
||||||
|
}
|
||||||
|
if (tokenName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tokenName));
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.RemoveTokenAsync(user, loginProvider, tokenName, CancellationToken);
|
||||||
|
return await UpdateUserAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the unmanaged resources used by the role manager and optionally releases the managed resources.
|
/// Releases the unmanaged resources used by the role manager and optionally releases the managed resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1966,7 +2067,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserFactorStore methods
|
|
||||||
internal IUserTwoFactorStore<TUser> GetUserTwoFactorStore()
|
internal IUserTwoFactorStore<TUser> GetUserTwoFactorStore()
|
||||||
{
|
{
|
||||||
var cast = Store as IUserTwoFactorStore<TUser>;
|
var cast = Store as IUserTwoFactorStore<TUser>;
|
||||||
|
|
@ -1977,7 +2077,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return cast;
|
return cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserLockoutStore methods
|
|
||||||
internal IUserLockoutStore<TUser> GetUserLockoutStore()
|
internal IUserLockoutStore<TUser> GetUserLockoutStore()
|
||||||
{
|
{
|
||||||
var cast = Store as IUserLockoutStore<TUser>;
|
var cast = Store as IUserLockoutStore<TUser>;
|
||||||
|
|
@ -1988,7 +2087,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return cast;
|
return cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserEmailStore methods
|
|
||||||
internal IUserEmailStore<TUser> GetEmailStore(bool throwOnFail = true)
|
internal IUserEmailStore<TUser> GetEmailStore(bool throwOnFail = true)
|
||||||
{
|
{
|
||||||
var cast = Store as IUserEmailStore<TUser>;
|
var cast = Store as IUserEmailStore<TUser>;
|
||||||
|
|
@ -1999,7 +2097,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return cast;
|
return cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserPhoneNumberStore methods
|
|
||||||
internal IUserPhoneNumberStore<TUser> GetPhoneNumberStore()
|
internal IUserPhoneNumberStore<TUser> GetPhoneNumberStore()
|
||||||
{
|
{
|
||||||
var cast = Store as IUserPhoneNumberStore<TUser>;
|
var cast = Store as IUserPhoneNumberStore<TUser>;
|
||||||
|
|
@ -2010,7 +2107,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return cast;
|
return cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two factor APIS
|
|
||||||
internal async Task<byte[]> CreateSecurityTokenAsync(TUser user)
|
internal async Task<byte[]> CreateSecurityTokenAsync(TUser user)
|
||||||
{
|
{
|
||||||
return Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user));
|
return Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user));
|
||||||
|
|
@ -2137,11 +2233,6 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return IdentityResult.Success;
|
return IdentityResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate user and update. Called by other UserManager methods
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<IdentityResult> UpdateUserAsync(TUser user)
|
private async Task<IdentityResult> UpdateUserAsync(TUser user)
|
||||||
{
|
{
|
||||||
var result = await ValidateUserInternal(user);
|
var result = await ValidateUserInternal(user);
|
||||||
|
|
@ -2154,7 +2245,16 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
return await Store.UpdateAsync(user, CancellationToken);
|
return await Store.UpdateAsync(user, CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUserPasswordStore methods
|
private IUserAuthenticationTokenStore<TUser> GetTokenStore()
|
||||||
|
{
|
||||||
|
var cast = Store as IUserAuthenticationTokenStore<TUser>;
|
||||||
|
if (cast == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Resources.StoreNotIUserAuthenticationTokenStore");
|
||||||
|
}
|
||||||
|
return cast;
|
||||||
|
}
|
||||||
|
|
||||||
private IUserPasswordStore<TUser> GetPasswordStore()
|
private IUserPasswordStore<TUser> GetPasswordStore()
|
||||||
{
|
{
|
||||||
var cast = Store as IUserPasswordStore<TUser>;
|
var cast = Store as IUserPasswordStore<TUser>;
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
||||||
Assert.True(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"));
|
Assert.True(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"));
|
||||||
Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
|
Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
|
||||||
Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
|
Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
|
||||||
|
Assert.True(VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"));
|
||||||
|
|
||||||
VerifyIndex(db, "AspNetRoles", "RoleNameIndex");
|
VerifyIndex(db, "AspNetRoles", "RoleNameIndex");
|
||||||
VerifyIndex(db, "AspNetUsers", "UserNameIndex");
|
VerifyIndex(db, "AspNetUsers", "UserNameIndex");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Builder.Internal;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Identity.Test;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Identity.InMemory.Test
|
||||||
|
{
|
||||||
|
public class ControllerTest
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task VerifyAccountControllerSignIn(bool isPersistent)
|
||||||
|
{
|
||||||
|
var context = new Mock<HttpContext>();
|
||||||
|
var auth = new Mock<AuthenticationManager>();
|
||||||
|
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
||||||
|
auth.Setup(a => a.SignInAsync(new IdentityCookieOptions().ApplicationCookieAuthenticationScheme,
|
||||||
|
It.IsAny<ClaimsPrincipal>(),
|
||||||
|
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
|
||||||
|
// REVIEW: is persistant mocking broken
|
||||||
|
//It.Is<AuthenticationProperties>(v => v.IsPersistent == isPersistent))).Returns(Task.FromResult(0)).Verifiable();
|
||||||
|
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||||
|
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddSingleton(contextAccessor.Object);
|
||||||
|
services.AddIdentity<TestUser, TestRole>();
|
||||||
|
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
||||||
|
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
||||||
|
|
||||||
|
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
||||||
|
app.UseCookieAuthentication();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var user = new TestUser
|
||||||
|
{
|
||||||
|
UserName = "Yolo"
|
||||||
|
};
|
||||||
|
const string password = "Yol0Sw@g!";
|
||||||
|
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
||||||
|
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
||||||
|
|
||||||
|
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
|
||||||
|
|
||||||
|
var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.Succeeded);
|
||||||
|
context.VerifyAll();
|
||||||
|
auth.VerifyAll();
|
||||||
|
contextAccessor.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task VerifyAccountControllerExternalLoginWithTokensFlow()
|
||||||
|
{
|
||||||
|
// Setup the external cookie like it would look from a real OAuth2
|
||||||
|
var externalId = "<externalId>";
|
||||||
|
var authScheme = "<authScheme>";
|
||||||
|
var externalIdentity = new ClaimsIdentity();
|
||||||
|
externalIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, externalId));
|
||||||
|
var externalPrincipal = new ClaimsPrincipal(externalIdentity);
|
||||||
|
var externalLogin = new ExternalLoginInfo(externalPrincipal, authScheme, externalId, "displayname")
|
||||||
|
{
|
||||||
|
AuthenticationTokens = new[] {
|
||||||
|
new AuthenticationToken { Name = "refresh_token", Value = "refresh" },
|
||||||
|
new AuthenticationToken { Name = "access_token", Value = "access" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var auth = new Mock<AuthenticationManager>();
|
||||||
|
auth.Setup(a => a.AuthenticateAsync(It.IsAny<AuthenticateContext>())).Returns(Task.FromResult(0));
|
||||||
|
var context = new Mock<HttpContext>();
|
||||||
|
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
||||||
|
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||||
|
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddSingleton(contextAccessor.Object);
|
||||||
|
services.AddIdentity<TestUser, TestRole>();
|
||||||
|
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
||||||
|
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
||||||
|
|
||||||
|
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
||||||
|
app.UseCookieAuthentication();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var user = new TestUser
|
||||||
|
{
|
||||||
|
UserName = "Yolo"
|
||||||
|
};
|
||||||
|
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
||||||
|
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
||||||
|
|
||||||
|
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user));
|
||||||
|
IdentityResultAssert.IsSuccess(await userManager.AddLoginAsync(user, new UserLoginInfo(authScheme, externalId, "whatever")));
|
||||||
|
IdentityResultAssert.IsSuccess(await signInManager.UpdateExternalAuthenticationTokensAsync(externalLogin));
|
||||||
|
Assert.Equal("refresh", await userManager.GetAuthenticationTokenAsync(user, authScheme, "refresh_token"));
|
||||||
|
Assert.Equal("access", await userManager.GetAuthenticationTokenAsync(user, authScheme, "access_token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Builder.Internal;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Identity.Test;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Identity.InMemory.Test
|
|
||||||
{
|
|
||||||
public class HttpSignInTest
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(true)]
|
|
||||||
[InlineData(false)]
|
|
||||||
public async Task VerifyAccountControllerSignIn(bool isPersistent)
|
|
||||||
{
|
|
||||||
var context = new Mock<HttpContext>();
|
|
||||||
var auth = new Mock<AuthenticationManager>();
|
|
||||||
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
|
||||||
auth.Setup(a => a.SignInAsync(new IdentityCookieOptions().ApplicationCookieAuthenticationScheme,
|
|
||||||
It.IsAny<ClaimsPrincipal>(),
|
|
||||||
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
|
|
||||||
// REVIEW: is persistant mocking broken
|
|
||||||
//It.Is<AuthenticationProperties>(v => v.IsPersistent == isPersistent))).Returns(Task.FromResult(0)).Verifiable();
|
|
||||||
var contextAccessor = new Mock<IHttpContextAccessor>();
|
|
||||||
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddLogging();
|
|
||||||
services.AddSingleton(contextAccessor.Object);
|
|
||||||
services.AddIdentity<TestUser, TestRole>();
|
|
||||||
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
|
||||||
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
|
||||||
|
|
||||||
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
|
||||||
app.UseCookieAuthentication();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var user = new TestUser
|
|
||||||
{
|
|
||||||
UserName = "Yolo"
|
|
||||||
};
|
|
||||||
const string password = "Yol0Sw@g!";
|
|
||||||
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
|
||||||
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
|
||||||
|
|
||||||
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
|
|
||||||
|
|
||||||
var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result.Succeeded);
|
|
||||||
context.VerifyAll();
|
|
||||||
auth.VerifyAll();
|
|
||||||
contextAccessor.VerifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
||||||
IQueryableUserStore<TUser>,
|
IQueryableUserStore<TUser>,
|
||||||
IUserTwoFactorStore<TUser>,
|
IUserTwoFactorStore<TUser>,
|
||||||
IQueryableRoleStore<TRole>,
|
IQueryableRoleStore<TRole>,
|
||||||
IRoleClaimStore<TRole>
|
IRoleClaimStore<TRole>,
|
||||||
|
IUserAuthenticationTokenStore<TUser>
|
||||||
where TRole : TestRole
|
where TRole : TestRole
|
||||||
where TUser : TestUser
|
where TUser : TestUser
|
||||||
{
|
{
|
||||||
|
|
@ -499,6 +500,54 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tokenEntity =
|
||||||
|
user.Tokens.SingleOrDefault(
|
||||||
|
l =>
|
||||||
|
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||||
|
l.UserId == user.Id);
|
||||||
|
if (tokenEntity != null)
|
||||||
|
{
|
||||||
|
tokenEntity.TokenValue = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user.Tokens.Add(new TestUserToken
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
LoginProvider = loginProvider,
|
||||||
|
TokenName = name,
|
||||||
|
TokenValue = value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tokenEntity =
|
||||||
|
user.Tokens.SingleOrDefault(
|
||||||
|
l =>
|
||||||
|
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||||
|
l.UserId == user.Id);
|
||||||
|
if (tokenEntity != null)
|
||||||
|
{
|
||||||
|
user.Tokens.Remove(tokenEntity);
|
||||||
|
}
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tokenEntity =
|
||||||
|
user.Tokens.SingleOrDefault(
|
||||||
|
l =>
|
||||||
|
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||||
|
l.UserId == user.Id);
|
||||||
|
return Task.FromResult(tokenEntity?.TokenValue);
|
||||||
|
}
|
||||||
|
|
||||||
public IQueryable<TRole> Roles
|
public IQueryable<TRole> Roles
|
||||||
{
|
{
|
||||||
get { return _roles.Values.AsQueryable(); }
|
get { return _roles.Values.AsQueryable(); }
|
||||||
|
|
|
||||||
|
|
@ -1133,7 +1133,7 @@ namespace Microsoft.AspNetCore.Identity.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOpTokenProvider : IUserTokenProvider<TestUser>
|
private class NoOpTokenProvider : IUserTwoFactorTokenProvider<TestUser>
|
||||||
{
|
{
|
||||||
public string Name { get; } = "Noop";
|
public string Name { get; } = "Noop";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,17 +89,9 @@ namespace Microsoft.AspNetCore.Identity.Test
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual int AccessFailedCount { get; set; }
|
public virtual int AccessFailedCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigation property for users in the role
|
|
||||||
/// </summary>
|
|
||||||
public virtual ICollection<TestUserRole<TKey>> Roles { get; private set; } = new List<TestUserRole<TKey>>();
|
public virtual ICollection<TestUserRole<TKey>> Roles { get; private set; } = new List<TestUserRole<TKey>>();
|
||||||
/// <summary>
|
|
||||||
/// Navigation property for users claims
|
|
||||||
/// </summary>
|
|
||||||
public virtual ICollection<TestUserClaim<TKey>> Claims { get; private set; } = new List<TestUserClaim<TKey>>();
|
public virtual ICollection<TestUserClaim<TKey>> Claims { get; private set; } = new List<TestUserClaim<TKey>>();
|
||||||
/// <summary>
|
|
||||||
/// Navigation property for users logins
|
|
||||||
/// </summary>
|
|
||||||
public virtual ICollection<TestUserLogin<TKey>> Logins { get; private set; } = new List<TestUserLogin<TKey>>();
|
public virtual ICollection<TestUserLogin<TKey>> Logins { get; private set; } = new List<TestUserLogin<TKey>>();
|
||||||
|
public virtual ICollection<TestUserToken<TKey>> Tokens { get; private set; } = new List<TestUserToken<TKey>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Identity.Test
|
||||||
|
{
|
||||||
|
public class TestUserToken : TestUserToken<string> { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity type for a user's login (i.e. facebook, google)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey"></typeparam>
|
||||||
|
public class TestUserToken<TKey> where TKey : IEquatable<TKey>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The login provider for the login (i.e. facebook, google)
|
||||||
|
/// </summary>
|
||||||
|
public virtual string LoginProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key representing the login for the provider
|
||||||
|
/// </summary>
|
||||||
|
public virtual string TokenName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name for the login
|
||||||
|
/// </summary>
|
||||||
|
public virtual string TokenValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User Id for the user who owns this login
|
||||||
|
/// </summary>
|
||||||
|
public virtual TKey UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -815,7 +815,7 @@ namespace Microsoft.AspNetCore.Identity.Test
|
||||||
Assert.False(await manager.IsEmailConfirmedAsync(user));
|
Assert.False(await manager.IsEmailConfirmedAsync(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StaticTokenProvider : IUserTokenProvider<TUser>
|
private class StaticTokenProvider : IUserTwoFactorTokenProvider<TUser>
|
||||||
{
|
{
|
||||||
public async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
public async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
||||||
{
|
{
|
||||||
|
|
@ -1909,6 +1909,30 @@ namespace Microsoft.AspNetCore.Identity.Test
|
||||||
Assert.True(!factors.Any());
|
Assert.True(!factors.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanGetSetUpdateAndRemoveUserToken()
|
||||||
|
{
|
||||||
|
if (ShouldSkipDbTests())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var manager = CreateManager();
|
||||||
|
var user = CreateTestUser();
|
||||||
|
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
|
||||||
|
Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value"));
|
||||||
|
Assert.Equal("value", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
|
||||||
|
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value2"));
|
||||||
|
Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
|
||||||
|
IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "whatevs", "name"));
|
||||||
|
Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
|
||||||
|
IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanGetValidTwoFactor()
|
public async Task CanGetValidTwoFactor()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue