257 lines
8.7 KiB
C#
257 lines
8.7 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.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Html;
|
|
using Microsoft.AspNet.Http;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Microsoft.AspNet.Antiforgery
|
|
{
|
|
/// <summary>
|
|
/// Provides access to the antiforgery system, which provides protection against
|
|
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
|
|
/// </summary>
|
|
public class DefaultAntiforgery : IAntiforgery
|
|
{
|
|
private readonly AntiforgeryOptions _options;
|
|
private readonly IAntiforgeryTokenGenerator _tokenGenerator;
|
|
private readonly IAntiforgeryTokenSerializer _tokenSerializer;
|
|
private readonly IAntiforgeryTokenStore _tokenStore;
|
|
|
|
public DefaultAntiforgery(
|
|
IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
|
|
IAntiforgeryTokenGenerator tokenGenerator,
|
|
IAntiforgeryTokenSerializer tokenSerializer,
|
|
IAntiforgeryTokenStore tokenStore)
|
|
{
|
|
_options = antiforgeryOptionsAccessor.Value;
|
|
_tokenGenerator = tokenGenerator;
|
|
_tokenSerializer = tokenSerializer;
|
|
_tokenStore = tokenStore;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IHtmlContent GetHtml(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
var tokenSet = GetAndStoreTokens(context);
|
|
|
|
// Though RequestToken normally contains only US-ASCII letters, numbers, '-', and '_', must assume the
|
|
// IAntiforgeryTokenSerializer implementation has been overridden. Similarly, users may choose a
|
|
// FormFieldName containing almost any character.
|
|
var content = new HtmlContentBuilder()
|
|
.AppendHtml("<input name=\"")
|
|
.Append(_options.FormFieldName)
|
|
.AppendHtml("\" type=\"hidden\" value=\"")
|
|
.Append(tokenSet.RequestToken)
|
|
.AppendHtml("\" />");
|
|
|
|
return content;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public AntiforgeryTokenSet GetAndStoreTokens(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
var tokenSet = GetTokensInternal(context);
|
|
if (tokenSet.IsNewCookieToken)
|
|
{
|
|
SaveCookieTokenAndHeader(context, tokenSet.CookieToken);
|
|
}
|
|
|
|
return Serialize(tokenSet);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public AntiforgeryTokenSet GetTokens(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
var tokenSet = GetTokensInternal(context);
|
|
return Serialize(tokenSet);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task ValidateRequestAsync(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
var tokens = await _tokenStore.GetRequestTokensAsync(context);
|
|
ValidateTokens(context, tokens);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ValidateTokens(HttpContext context, AntiforgeryTokenSet antiforgeryTokenSet)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
if (string.IsNullOrEmpty(antiforgeryTokenSet.CookieToken))
|
|
{
|
|
throw new ArgumentException(
|
|
Resources.Antiforgery_CookieToken_MustBeProvided_Generic,
|
|
nameof(antiforgeryTokenSet));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(antiforgeryTokenSet.RequestToken))
|
|
{
|
|
throw new ArgumentException(
|
|
Resources.Antiforgery_RequestToken_MustBeProvided_Generic,
|
|
nameof(antiforgeryTokenSet));
|
|
}
|
|
|
|
// Extract cookie & request tokens
|
|
var deserializedCookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken);
|
|
var deserializedRequestToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.RequestToken);
|
|
|
|
// Validate
|
|
_tokenGenerator.ValidateTokens(
|
|
context,
|
|
deserializedCookieToken,
|
|
deserializedRequestToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetCookieTokenAndHeader(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
CheckSSLConfig(context);
|
|
|
|
var cookieToken = GetCookieTokenDoesNotThrow(context);
|
|
cookieToken = ValidateAndGenerateNewCookieToken(cookieToken);
|
|
SaveCookieTokenAndHeader(context, cookieToken);
|
|
}
|
|
|
|
// This method returns null if oldCookieToken is valid.
|
|
private AntiforgeryToken ValidateAndGenerateNewCookieToken(AntiforgeryToken cookieToken)
|
|
{
|
|
if (!_tokenGenerator.IsCookieTokenValid(cookieToken))
|
|
{
|
|
// Need to make sure we're always operating with a good cookie token.
|
|
var newCookieToken = _tokenGenerator.GenerateCookieToken();
|
|
Debug.Assert(_tokenGenerator.IsCookieTokenValid(newCookieToken));
|
|
return newCookieToken;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void SaveCookieTokenAndHeader(
|
|
HttpContext context,
|
|
AntiforgeryToken cookieToken)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (cookieToken != null)
|
|
{
|
|
// Persist the new cookie if it is not null.
|
|
_tokenStore.SaveCookieToken(context, cookieToken);
|
|
}
|
|
|
|
if (!_options.SuppressXFrameOptionsHeader)
|
|
{
|
|
// Adding X-Frame-Options header to prevent ClickJacking. See
|
|
// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
|
|
// for more information.
|
|
context.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
|
|
}
|
|
}
|
|
|
|
private void CheckSSLConfig(HttpContext context)
|
|
{
|
|
if (_options.RequireSsl && !context.Request.IsHttps)
|
|
{
|
|
throw new InvalidOperationException(Resources.FormatAntiforgeryWorker_RequireSSL(
|
|
nameof(AntiforgeryOptions),
|
|
nameof(AntiforgeryOptions.RequireSsl),
|
|
"true"));
|
|
}
|
|
}
|
|
|
|
private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext context)
|
|
{
|
|
try
|
|
{
|
|
return _tokenStore.GetCookieToken(context);
|
|
}
|
|
catch
|
|
{
|
|
// ignore failures since we'll just generate a new token
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private AntiforgeryTokenSetInternal GetTokensInternal(HttpContext context)
|
|
{
|
|
var cookieToken = GetCookieTokenDoesNotThrow(context);
|
|
var newCookieToken = ValidateAndGenerateNewCookieToken(cookieToken);
|
|
if (newCookieToken != null)
|
|
{
|
|
cookieToken = newCookieToken;
|
|
}
|
|
var requestToken = _tokenGenerator.GenerateRequestToken(
|
|
context,
|
|
cookieToken);
|
|
|
|
return new AntiforgeryTokenSetInternal()
|
|
{
|
|
// Note : The new cookie would be null if the old cookie is valid.
|
|
CookieToken = cookieToken,
|
|
RequestToken = requestToken,
|
|
IsNewCookieToken = newCookieToken != null
|
|
};
|
|
}
|
|
|
|
private AntiforgeryTokenSet Serialize(AntiforgeryTokenSetInternal tokenSet)
|
|
{
|
|
return new AntiforgeryTokenSet(
|
|
tokenSet.RequestToken != null ? _tokenSerializer.Serialize(tokenSet.RequestToken) : null,
|
|
tokenSet.CookieToken != null ? _tokenSerializer.Serialize(tokenSet.CookieToken) : null);
|
|
}
|
|
|
|
private class AntiforgeryTokenSetInternal
|
|
{
|
|
public AntiforgeryToken RequestToken { get; set; }
|
|
|
|
public AntiforgeryToken CookieToken { get; set; }
|
|
|
|
public bool IsNewCookieToken { get; set; }
|
|
}
|
|
}
|
|
} |