// 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);
}
}
}