// 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
{
///
/// Provides default implementation of validation functions for security stamps.
///
/// The type encapsulating a user.
public class SecurityStampValidator : ISecurityStampValidator where TUser : class
{
///
/// Creates a new instance of .
///
/// Used to access the .
/// The .
/// The system clock.
public SecurityStampValidator(IOptions options, SignInManager 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;
}
///
/// The SignInManager.
///
public SignInManager SignInManager { get; }
///
/// The .
///
public SecurityStampValidatorOptions Options { get; }
///
/// The .
///
public ISystemClock Clock { get; }
///
/// Called when the security stamp has been verified.
///
/// The user who has been verified.
/// The .
/// A task.
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;
}
///
/// Verifies the principal's security stamp, returns the matching user if successful
///
/// The principal to verify.
/// The verified user or null if verification fails.
protected virtual Task VerifySecurityStamp(ClaimsPrincipal principal)
=> SignInManager.ValidateSecurityStampAsync(principal);
///
/// Validates a security stamp of an identity as an asynchronous operation, and rebuilds the identity if the validation succeeds, otherwise rejects
/// the identity.
///
/// The context containing the
/// and to validate.
/// The that represents the asynchronous validation operation.
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();
}
}
}
}
///
/// Static helper class used to configure a CookieAuthenticationNotifications to validate a cookie against a user's security
/// stamp.
///
public static class SecurityStampValidator
{
///
/// Validates a principal against a user's stored security stamp.
///
/// The context containing the
/// and to validate.
/// The that represents the asynchronous validation operation.
public static Task ValidatePrincipalAsync(CookieValidatePrincipalContext context)
=> ValidateAsync(context);
///
/// Used to validate the and
/// cookies against the user's
/// stored security stamp.
///
/// The context containing the
/// and to validate.
///
public static Task ValidateAsync(CookieValidatePrincipalContext context) where TValidator : ISecurityStampValidator
{
if (context.HttpContext.RequestServices == null)
{
throw new InvalidOperationException("RequestServices is null.");
}
var validator = context.HttpContext.RequestServices.GetRequiredService();
return validator.ValidateAsync(context);
}
}
}