166 lines
7.0 KiB
C#
166 lines
7.0 KiB
C#
// 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;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Microsoft.AspNetCore.Identity
|
|
{
|
|
/// <summary>
|
|
/// Provides default implementation of validation functions for security stamps.
|
|
/// </summary>
|
|
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
|
public class SecurityStampValidator<TUser> : ISecurityStampValidator where TUser : class
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of <see cref="SecurityStampValidator{TUser}"/>.
|
|
/// </summary>
|
|
/// <param name="options">Used to access the <see cref="IdentityOptions"/>.</param>
|
|
/// <param name="signInManager">The <see cref="SignInManager{TUser}"/>.</param>
|
|
/// <param name="clock">The system clock.</param>
|
|
public SecurityStampValidator(IOptions<SecurityStampValidatorOptions> options, SignInManager<TUser> signInManager, ISystemClock clock)
|
|
{
|
|
if (options == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(options));
|
|
}
|
|
if (signInManager == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(signInManager));
|
|
}
|
|
SignInManager = signInManager;
|
|
Options = options.Value;
|
|
Clock = clock;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SignInManager.
|
|
/// </summary>
|
|
public SignInManager<TUser> SignInManager { get; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="SecurityStampValidatorOptions"/>.
|
|
/// </summary>
|
|
public SecurityStampValidatorOptions Options { get; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="ISystemClock"/>.
|
|
/// </summary>
|
|
public ISystemClock Clock { get; }
|
|
|
|
/// <summary>
|
|
/// Called when the security stamp has been verified.
|
|
/// </summary>
|
|
/// <param name="user">The user who has been verified.</param>
|
|
/// <param name="context">The <see cref="CookieValidatePrincipalContext"/>.</param>
|
|
/// <returns>A task.</returns>
|
|
protected virtual async Task SecurityStampVerified(TUser user, CookieValidatePrincipalContext context)
|
|
{
|
|
var newPrincipal = await SignInManager.CreateUserPrincipalAsync(user);
|
|
|
|
if (Options.OnRefreshingPrincipal != null)
|
|
{
|
|
var replaceContext = new SecurityStampRefreshingPrincipalContext
|
|
{
|
|
CurrentPrincipal = context.Principal,
|
|
NewPrincipal = newPrincipal
|
|
};
|
|
|
|
// Note: a null principal is allowed and results in a failed authentication.
|
|
await Options.OnRefreshingPrincipal(replaceContext);
|
|
newPrincipal = replaceContext.NewPrincipal;
|
|
}
|
|
|
|
// REVIEW: note we lost login authentication method
|
|
context.ReplacePrincipal(newPrincipal);
|
|
context.ShouldRenew = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the principal's security stamp, returns the matching user if successful
|
|
/// </summary>
|
|
/// <param name="principal">The principal to verify.</param>
|
|
/// <returns>The verified user or null if verification fails.</returns>
|
|
protected virtual Task<TUser> VerifySecurityStamp(ClaimsPrincipal principal)
|
|
=> SignInManager.ValidateSecurityStampAsync(principal);
|
|
|
|
/// <summary>
|
|
/// Validates a security stamp of an identity as an asynchronous operation, and rebuilds the identity if the validation succeeds, otherwise rejects
|
|
/// the identity.
|
|
/// </summary>
|
|
/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>
|
|
/// and <see cref="Http.Authentication.AuthenticationProperties"/> to validate.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous validation operation.</returns>
|
|
public virtual async Task ValidateAsync(CookieValidatePrincipalContext context)
|
|
{
|
|
var currentUtc = DateTimeOffset.UtcNow;
|
|
if (context.Options != null && Clock != null)
|
|
{
|
|
currentUtc = Clock.UtcNow;
|
|
}
|
|
var issuedUtc = context.Properties.IssuedUtc;
|
|
|
|
// Only validate if enough time has elapsed
|
|
var validate = (issuedUtc == null);
|
|
if (issuedUtc != null)
|
|
{
|
|
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
|
validate = timeElapsed > Options.ValidationInterval;
|
|
}
|
|
if (validate)
|
|
{
|
|
var user = await VerifySecurityStamp(context.Principal);
|
|
if (user != null)
|
|
{
|
|
await SecurityStampVerified(user, context);
|
|
}
|
|
else
|
|
{
|
|
context.RejectPrincipal();
|
|
await SignInManager.SignOutAsync();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Static helper class used to configure a CookieAuthenticationNotifications to validate a cookie against a user's security
|
|
/// stamp.
|
|
/// </summary>
|
|
public static class SecurityStampValidator
|
|
{
|
|
/// <summary>
|
|
/// Validates a principal against a user's stored security stamp.
|
|
/// </summary>
|
|
/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>
|
|
/// and <see cref="AuthenticationProperties"/> to validate.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous validation operation.</returns>
|
|
public static Task ValidatePrincipalAsync(CookieValidatePrincipalContext context)
|
|
=> ValidateAsync<ISecurityStampValidator>(context);
|
|
|
|
/// <summary>
|
|
/// Used to validate the <see cref="IdentityConstants.TwoFactorUserIdScheme"/> and
|
|
/// <see cref="IdentityConstants.TwoFactorRememberMeScheme"/> cookies against the user's
|
|
/// stored security stamp.
|
|
/// </summary>
|
|
/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>
|
|
/// and <see cref="AuthenticationProperties"/> to validate.</param>
|
|
/// <returns></returns>
|
|
|
|
public static Task ValidateAsync<TValidator>(CookieValidatePrincipalContext context) where TValidator : ISecurityStampValidator
|
|
{
|
|
if (context.HttpContext.RequestServices == null)
|
|
{
|
|
throw new InvalidOperationException("RequestServices is null.");
|
|
}
|
|
|
|
var validator = context.HttpContext.RequestServices.GetRequiredService<TValidator>();
|
|
return validator.ValidateAsync(context);
|
|
}
|
|
}
|
|
} |