aspnetcore/src/Microsoft.AspNet.Identity/SignInManager.cs

451 lines
18 KiB
C#

// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// Interface that manages SignIn operations for a user
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class SignInManager<TUser> where TUser : class
{
public SignInManager(UserManager<TUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<TUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor = null,
ILogger<SignInManager<TUser>> logger = null)
{
if (userManager == null)
{
throw new ArgumentNullException(nameof(userManager));
}
if (contextAccessor == null || contextAccessor.HttpContext == null)
{
throw new ArgumentNullException(nameof(contextAccessor));
}
if (claimsFactory == null)
{
throw new ArgumentNullException(nameof(claimsFactory));
}
UserManager = userManager;
Context = contextAccessor.HttpContext;
ClaimsFactory = claimsFactory;
Options = optionsAccessor?.Options ?? new IdentityOptions();
Logger = logger ?? new Logger<SignInManager<TUser>>(new LoggerFactory());
}
public UserManager<TUser> UserManager { get; private set; }
public HttpContext Context { get; private set; }
public IUserClaimsPrincipalFactory<TUser> ClaimsFactory { get; private set; }
public IdentityOptions Options { get; private set; }
public ILogger<SignInManager<TUser>> Logger { get; set; }
// Should this be a func?
public virtual async Task<ClaimsPrincipal> CreateUserPrincipalAsync(TUser user)
{
return await ClaimsFactory.CreateAsync(user);
}
public virtual async Task<bool> CanSignInAsync(TUser user)
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user)))
{
return await LogResultAsync(false, user);
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
return await LogResultAsync(false, user);
}
return await LogResultAsync(true, user);
}
public virtual async Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
// Review: should we guard against CreateUserPrincipal returning null?
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
Context.Response.SignIn(IdentityOptions.ApplicationCookieAuthenticationScheme,
userPrincipal,
new AuthenticationProperties() { IsPersistent = isPersistent });
}
public virtual void SignOut()
{
Context.Response.SignOut(IdentityOptions.ApplicationCookieAuthenticationScheme);
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationScheme);
Context.Response.SignOut(IdentityOptions.TwoFactorUserIdCookieAuthenticationScheme);
}
private async Task<bool> IsLockedOut(TUser user)
{
return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user);
}
private async Task<SignInResult> PreSignInCheck(TUser user)
{
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
if (await IsLockedOut(user))
{
return SignInResult.LockedOut;
}
return null;
}
private Task ResetLockout(TUser user)
{
if (UserManager.SupportsUserLockout)
{
return UserManager.ResetAccessFailedCountAsync(user);
}
return Task.FromResult(0);
}
/// <summary>
/// Validates that the claims identity has a security stamp matching the users
/// Returns the user if it matches, null otherwise
/// </summary>
/// <param name="identity"></param>
/// <param name="userId"></param>
/// <returns></returns>
public virtual async Task<TUser> ValidateSecurityStampAsync(ClaimsPrincipal principal, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null && UserManager.SupportsUserSecurityStamp)
{
var securityStamp =
principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType);
if (securityStamp == await UserManager.GetSecurityStampAsync(user))
{
return user;
}
}
return null;
}
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool shouldLockout)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user);
if (error != null)
{
return await LogResultAsync(error, user);
}
if (await IsLockedOut(user))
{
return await LogResultAsync(SignInResult.LockedOut, user);
}
if (await UserManager.CheckPasswordAsync(user, password))
{
await ResetLockout(user);
return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent), user);
}
if (UserManager.SupportsUserLockout && shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
await UserManager.AccessFailedAsync(user);
if (await UserManager.IsLockedOutAsync(user))
{
return await LogResultAsync(SignInResult.LockedOut, user);
}
}
return await LogResultAsync(SignInResult.Failed, user);
}
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool shouldLockout)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, shouldLockout);
}
private static ClaimsIdentity CreateIdentity(TwoFactorAuthenticationInfo info)
{
if (info == null)
{
return null;
}
var identity = new ClaimsIdentity(IdentityOptions.TwoFactorUserIdCookieAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, info.UserId));
if (info.LoginProvider != null)
{
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, info.LoginProvider));
}
return identity;
}
public virtual async Task<bool> SendTwoFactorCodeAsync(string provider)
{
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
if (twoFactorInfo == null || twoFactorInfo.UserId == null)
{
return false;
}
var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId);
if (user == null)
{
return false;
}
var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider);
await UserManager.NotifyTwoFactorTokenAsync(user, provider, token);
return await LogResultAsync(true, user);
}
public virtual async Task<bool> IsTwoFactorClientRememberedAsync(TUser user)
{
var userId = await UserManager.GetUserIdAsync(user);
var result =
await Context.AuthenticateAsync(IdentityOptions.TwoFactorRememberMeCookieAuthenticationScheme);
return (result?.Principal != null && result.Principal.FindFirstValue(ClaimTypes.Name) == userId);
}
public virtual async Task RememberTwoFactorClientAsync(TUser user)
{
var userId = await UserManager.GetUserIdAsync(user);
var rememberBrowserIdentity = new ClaimsIdentity(IdentityOptions.TwoFactorRememberMeCookieAuthenticationType);
rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.Name, userId));
Context.Response.SignIn(IdentityOptions.TwoFactorRememberMeCookieAuthenticationScheme,
new ClaimsPrincipal(rememberBrowserIdentity),
new AuthenticationProperties { IsPersistent = true });
}
public virtual Task ForgetTwoFactorClientAsync()
{
Context.Response.SignOut(IdentityOptions.TwoFactorRememberMeCookieAuthenticationScheme);
return Task.FromResult(0);
}
public virtual async Task<SignInResult> TwoFactorSignInAsync(string provider, string code, bool isPersistent,
bool rememberClient)
{
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
if (twoFactorInfo == null || twoFactorInfo.UserId == null)
{
return SignInResult.Failed;
}
var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId);
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return await LogResultAsync(error, user);
}
if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await ResetLockout(user);
// Cleanup external cookie
if (twoFactorInfo.LoginProvider != null)
{
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationScheme);
}
await SignInAsync(user, isPersistent, twoFactorInfo.LoginProvider);
if (rememberClient)
{
await RememberTwoFactorClientAsync(user);
}
await UserManager.ResetAccessFailedCountAsync(user);
await SignInAsync(user, isPersistent);
return await LogResultAsync(SignInResult.Success, user);
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user);
return await LogResultAsync(SignInResult.Failed, user);
}
/// <summary>
/// Returns the user who has started the two factor authentication process
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual async Task<TUser> GetTwoFactorAuthenticationUserAsync()
{
var info = await RetrieveTwoFactorInfoAsync();
if (info == null)
{
return null;
}
return await UserManager.FindByIdAsync(info.UserId);
}
public virtual async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent)
{
var user = await UserManager.FindByLoginAsync(loginProvider, providerKey);
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return await LogResultAsync(error, user);
}
return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, loginProvider), user);
}
private const string LoginProviderKey = "LoginProvider";
private const string XsrfKey = "XsrfId";
public virtual IEnumerable<AuthenticationDescription> GetExternalAuthenticationSchemes()
{
return Context.GetAuthenticationSchemes().Where(d => !string.IsNullOrEmpty(d.Caption));
}
public virtual async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(IdentityOptions.ExternalCookieAuthenticationScheme);
if (auth == null || auth.Principal == null || auth.Properties.Dictionary == null || !auth.Properties.Dictionary.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!auth.Properties.Dictionary.ContainsKey(XsrfKey))
{
return null;
}
var userId = auth.Properties.Dictionary[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = auth.Properties.Dictionary[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
return new ExternalLoginInfo(auth.Principal, provider, providerKey, auth.Description.Caption);
}
public virtual AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null)
{
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Dictionary[LoginProviderKey] = provider;
if (userId != null)
{
properties.Dictionary[XsrfKey] = userId;
}
return properties;
}
private async Task<SignInResult> SignInOrTwoFactorAsync(TUser user, bool isPersistent, string loginProvider = null)
{
if (UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0)
{
if (!await IsTwoFactorClientRememberedAsync(user))
{
// Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user);
Context.Response.SignIn(IdentityOptions.TwoFactorUserIdCookieAuthenticationScheme, StoreTwoFactorInfo(userId, loginProvider));
return SignInResult.TwoFactorRequired;
}
}
// Cleanup external cookie
if (loginProvider != null)
{
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationScheme);
}
await SignInAsync(user, isPersistent, loginProvider);
return SignInResult.Success;
}
private async Task<TwoFactorAuthenticationInfo> RetrieveTwoFactorInfoAsync()
{
var result = await Context.AuthenticateAsync(IdentityOptions.TwoFactorUserIdCookieAuthenticationScheme);
if (result?.Principal != null)
{
return new TwoFactorAuthenticationInfo
{
UserId = result.Principal.FindFirstValue(ClaimTypes.Name),
LoginProvider = result.Principal.FindFirstValue(ClaimTypes.AuthenticationMethod)
};
}
return null;
}
/// <summary>
/// Log boolean result for user and return result
/// </summary>
/// <param name="result"></param>
/// <param name="user"></param>
/// <param name="methodName"></param>
/// <returns></returns>
protected async virtual Task<bool> LogResultAsync(bool result, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
{
Logger.WriteInformation(Resources.FormatLoggingSigninResult(Resources.FormatLoggingResultMessage(methodName,
await UserManager.GetUserIdAsync(user)), result));
return result;
}
/// <summary>
/// Log SignInStatus for user and return SignInStatus
/// </summary>
/// <param name="status"></param>
/// <param name="user"></param>
/// <param name="methodName"></param>
/// <returns></returns>
protected async virtual Task<SignInResult> LogResultAsync(SignInResult status, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
{
status.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await UserManager.GetUserIdAsync(user)));
return status;
}
internal static ClaimsPrincipal StoreTwoFactorInfo(string userId, string loginProvider)
{
var identity = new ClaimsIdentity(IdentityOptions.TwoFactorUserIdCookieAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userId));
if (loginProvider != null)
{
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider));
}
return new ClaimsPrincipal(identity);
}
internal class TwoFactorAuthenticationInfo
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
}
}
}