// 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.Http; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; namespace Microsoft.AspNet.Antiforgery { /// /// Provides access to the antiforgery system, which provides protection against /// Cross-site Request Forgery (XSRF, also called CSRF) attacks. /// public class DefaultAntiforgery : IAntiforgery { private readonly IHtmlEncoder _htmlEncoder; private readonly AntiforgeryOptions _options; private readonly IAntiforgeryTokenGenerator _tokenGenerator; private readonly IAntiforgeryTokenSerializer _tokenSerializer; private readonly IAntiforgeryTokenStore _tokenStore; public DefaultAntiforgery( IOptions antiforgeryOptionsAccessor, IAntiforgeryTokenGenerator tokenGenerator, IAntiforgeryTokenSerializer tokenSerializer, IAntiforgeryTokenStore tokenStore, IHtmlEncoder htmlEncoder) { _options = antiforgeryOptionsAccessor.Value; _tokenGenerator = tokenGenerator; _tokenSerializer = tokenSerializer; _tokenStore = tokenStore; _htmlEncoder = htmlEncoder; } /// public string GetHtml(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } CheckSSLConfig(context); var tokenSet = GetAndStoreTokens(context); var inputTag = string.Format( "", _htmlEncoder.HtmlEncode(_options.FormFieldName), _htmlEncoder.HtmlEncode("hidden"), _htmlEncoder.HtmlEncode(tokenSet.FormToken)); return inputTag; } /// public AntiforgeryTokenSet GetAndStoreTokens(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } CheckSSLConfig(context); var tokenSet = GetTokensInternal(context); SaveCookieTokenAndHeader(context, tokenSet.CookieToken); return Serialize(tokenSet); } /// public AntiforgeryTokenSet GetTokens(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } CheckSSLConfig(context); var tokenSet = GetTokensInternal(context); return Serialize(tokenSet); } /// 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); } /// 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.FormToken)) { throw new ArgumentException( Resources.Antiforgery_FormToken_MustBeProvided_Generic, nameof(antiforgeryTokenSet)); } // Extract cookie & form tokens var deserializedCookieToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.CookieToken); var deserializedFormToken = _tokenSerializer.Deserialize(antiforgeryTokenSet.FormToken); // Validate _tokenGenerator.ValidateTokens( context, deserializedCookieToken, deserializedFormToken); } /// 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 formToken = _tokenGenerator.GenerateFormToken( context, cookieToken); return new AntiforgeryTokenSetInternal() { // Note : The new cookie would be null if the old cookie is valid. CookieToken = newCookieToken, FormToken = formToken }; } private AntiforgeryTokenSet Serialize(AntiforgeryTokenSetInternal tokenSet) { return new AntiforgeryTokenSet( tokenSet.FormToken != null ? _tokenSerializer.Serialize(tokenSet.FormToken) : null, tokenSet.CookieToken != null ? _tokenSerializer.Serialize(tokenSet.CookieToken) : null); } private class AntiforgeryTokenSetInternal { public AntiforgeryToken FormToken { get; set; } public AntiforgeryToken CookieToken { get; set; } } } }