// 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.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.DataProtection;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc
{
///
/// Provides access to the anti-forgery system, which provides protection against
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
///
public sealed class AntiForgery
{
private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1";
private readonly AntiForgeryWorker _worker;
public AntiForgery([NotNull] IClaimUidExtractor claimUidExtractor,
[NotNull] IDataProtectionProvider dataProtectionProvider,
[NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider,
[NotNull] IOptions antiforgeryOptions,
[NotNull] IHtmlEncoder htmlEncoder,
[NotNull] IOptions dataProtectionOptions)
{
var config = antiforgeryOptions.Options;
var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty;
config.CookieName = config.CookieName ?? ComputeCookieName(applicationId);
var serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose));
var tokenStore = new AntiForgeryTokenStore(config, serializer);
var tokenProvider = new AntiForgeryTokenProvider(config, claimUidExtractor, additionalDataProvider);
_worker = new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider, htmlEncoder);
}
///
/// Generates an anti-forgery token for this request. This token can
/// be validated by calling the Validate() method.
///
/// The HTTP context associated with the current call.
/// An HTML string corresponding to an <input type="hidden">
/// element. This element should be put inside a <form>.
///
/// This method has a side effect:
/// A response cookie is set if there is no valid cookie associated with the request.
///
public TagBuilder GetHtml([NotNull] HttpContext context)
{
var builder = _worker.GetFormInputElement(context);
return builder;
}
///
/// Generates an anti-forgery token pair (cookie and form token) for this request.
/// This method is similar to GetHtml(HttpContext context), but this method gives the caller control
/// over how to persist the returned values. To validate these tokens, call the
/// appropriate overload of Validate.
///
/// The HTTP context associated with the current call.
/// The anti-forgery token - if any - that already existed
/// for this request. May be null. The anti-forgery system will try to reuse this cookie
/// value when generating a matching form token.
///
/// Unlike the GetHtml(HttpContext context) method, this method has no side effect. The caller
/// is responsible for setting the response cookie and injecting the returned
/// form token as appropriate.
///
public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCookieToken)
{
// Will contain a new cookie value if the old cookie token
// was null or invalid. If this value is non-null when the method completes, the caller
// must persist this value in the form of a response cookie, and the existing cookie value
// should be discarded. If this value is null when the method completes, the existing
// cookie value was valid and needn't be modified.
return _worker.GetTokens(context, oldCookieToken);
}
///
/// Validates an anti-forgery token that was supplied for this request.
/// The anti-forgery token may be generated by calling GetHtml(HttpContext context).
///
/// The HTTP context associated with the current call.
public async Task ValidateAsync([NotNull] HttpContext context)
{
await _worker.ValidateAsync(context);
}
///
/// Validates an anti-forgery token pair that was generated by the GetTokens method.
///
/// The HTTP context associated with the current call.
/// The token that was supplied in the request cookie.
/// The token that was supplied in the request form body.
public void Validate([NotNull] HttpContext context, string cookieToken, string formToken)
{
_worker.Validate(context, cookieToken, formToken);
}
///
/// Validates an anti-forgery token pair that was generated by the GetTokens method.
///
/// The HTTP context associated with the current call.
/// The anti-forgery token pair (cookie and form token) for this request.
///
public void Validate([NotNull] HttpContext context, AntiForgeryTokenSet antiForgeryTokenSet)
{
Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken);
}
///
/// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers.
///
/// The HTTP context associated with the current call.
public void SetCookieTokenAndHeader([NotNull] HttpContext context)
{
_worker.SetCookieTokenAndHeader(context);
}
private string ComputeCookieName(string applicationId)
{
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId));
var subHash = hash.Take(8).ToArray();
return WebEncoders.Base64UrlEncode(subHash);
}
}
}
}