// 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.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.DataProtection; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity { /// /// Provides protection and validation of identity tokens. /// /// The type used to represent a user. public class DataProtectorTokenProvider : IUserTokenProvider where TUser : class { /// /// Initializes a new instance of the class. /// /// The system data protection provider. /// The configured . public DataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions options) { if (dataProtectionProvider == null) { throw new ArgumentNullException(nameof(dataProtectionProvider)); } Options = options?.Options ?? new DataProtectionTokenProviderOptions(); // Use the Name as the purpose which should usually be distinct from others Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider"); } /// /// Gets the for this instance. /// /// /// The for this instance. /// protected DataProtectionTokenProviderOptions Options { get; private set; } /// /// Gets the for this instance. /// /// /// The for this instance. /// protected IDataProtector Protector { get; private set; } /// /// Gets the name of this instance. /// /// /// The name of this instance. /// public string Name { get { return Options.Name; } } /// /// Generates a protected token for the specified . /// /// The purpose the token will be used for. /// The to retrieve user properties from. /// The the token will be generated from. /// A to observe while waiting for the tasks to complete. /// A that contains the protected token. public virtual async Task GenerateAsync(string purpose, UserManager manager, TUser user) { if (user == null) { throw new ArgumentNullException("user"); } var ms = new MemoryStream(); var userId = await manager.GetUserIdAsync(user); using (var writer = ms.CreateWriter()) { writer.Write(DateTimeOffset.UtcNow); writer.Write(userId); writer.Write(purpose ?? ""); string stamp = null; if (manager.SupportsUserSecurityStamp) { stamp = await manager.GetSecurityStampAsync(user); } writer.Write(stamp ?? ""); } var protectedBytes = Protector.Protect(ms.ToArray()); return Convert.ToBase64String(protectedBytes); } /// /// Validates the protected for the specified and . /// /// The purpose the token was be used for. /// The token to validate. /// The to retrieve user properties from. /// The the token was generated for. /// A that is true if the token is valid, otherwise false. public virtual async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) { try { var unprotectedData = Protector.Unprotect(Convert.FromBase64String(token)); var ms = new MemoryStream(unprotectedData); using (var reader = ms.CreateReader()) { var creationTime = reader.ReadDateTimeOffset(); var expirationTime = creationTime + Options.TokenLifespan; if (expirationTime < DateTimeOffset.UtcNow) { return false; } var userId = reader.ReadString(); var actualUserId = await manager.GetUserIdAsync(user); if (userId != actualUserId) { return false; } var purp = reader.ReadString(); if (!string.Equals(purp, purpose)) { return false; } var stamp = reader.ReadString(); if (reader.PeekChar() != -1) { return false; } if (manager.SupportsUserSecurityStamp) { return stamp == await manager.GetSecurityStampAsync(user); } return stamp == ""; } } // ReSharper disable once EmptyGeneralCatchClause catch { // Do not leak exception } return false; } /// /// Returns a indicating whether a token generated by this instance /// can be used as a Two Factor Authentication token. /// /// The to retrieve user properties from. /// The the token was generated for. /// True if a token generated by this instance can be used as a Two Factor Authentication token, otherwise false. /// This method will always return false for instances of . public virtual Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) { return Task.FromResult(false); } /// /// Creates a notification task for A based on the supplied . /// /// The token to generate notifications for.. /// The to retrieve user properties from. /// The the token was generated for. /// A that represents the started task. public virtual Task NotifyAsync(string token, UserManager manager, TUser user) { return Task.FromResult(0); } } /// /// Utility extensions to streams /// internal static class StreamExtensions { internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); public static BinaryReader CreateReader(this Stream stream) { return new BinaryReader(stream, DefaultEncoding, true); } public static BinaryWriter CreateWriter(this Stream stream) { return new BinaryWriter(stream, DefaultEncoding, true); } public static DateTimeOffset ReadDateTimeOffset(this BinaryReader reader) { return new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); } public static void Write(this BinaryWriter writer, DateTimeOffset value) { writer.Write(value.UtcTicks); } } }