diff --git a/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs b/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs index ab2d2b7c31..b2c2838643 100644 --- a/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs +++ b/src/Microsoft.AspNet.Antiforgery/Antiforgery.cs @@ -2,14 +2,9 @@ // 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.Diagnostics; using System.Threading.Tasks; -using Microsoft.AspNet.Antiforgery.Internal; -using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; -using Microsoft.AspNet.WebUtilities; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; @@ -20,27 +15,26 @@ namespace Microsoft.AspNet.Antiforgery /// Provides access to the anti-forgery system, which provides protection against /// Cross-site Request Forgery (XSRF, also called CSRF) attacks. /// - public sealed class Antiforgery + public class Antiforgery { - private static readonly string _purpose = "Microsoft.AspNet.Antiforgery.AntiforgeryToken.v1"; - private readonly AntiforgeryWorker _worker; + private readonly IHtmlEncoder _htmlEncoder; + private readonly AntiforgeryOptions _options; + private readonly IAntiforgeryTokenGenerator _tokenGenerator; + private readonly IAntiforgeryTokenSerializer _tokenSerializer; + private readonly IAntiforgeryTokenStore _tokenStore; public Antiforgery( - [NotNull] IClaimUidExtractor claimUidExtractor, - [NotNull] IDataProtectionProvider dataProtectionProvider, - [NotNull] IAntiforgeryAdditionalDataProvider additionalDataProvider, - [NotNull] IOptions AntiforgeryOptionsAccessor, - [NotNull] IHtmlEncoder htmlEncoder, - [NotNull] IOptions dataProtectionOptions) + IOptions antiforgeryOptionsAccessor, + IAntiforgeryTokenGenerator tokenGenerator, + IAntiforgeryTokenSerializer tokenSerializer, + IAntiforgeryTokenStore tokenStore, + IHtmlEncoder htmlEncoder) { - var AntiforgeryOptions = AntiforgeryOptionsAccessor.Options; - var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty; - AntiforgeryOptions.CookieName = AntiforgeryOptions.CookieName ?? ComputeCookieName(applicationId); - - var serializer = new AntiforgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); - var tokenStore = new AntiforgeryTokenStore(AntiforgeryOptions, serializer); - var tokenProvider = new AntiforgeryTokenProvider(AntiforgeryOptions, claimUidExtractor, additionalDataProvider); - _worker = new AntiforgeryWorker(serializer, AntiforgeryOptions, tokenStore, tokenProvider, tokenProvider, htmlEncoder); + _options = antiforgeryOptionsAccessor.Options; + _tokenGenerator = tokenGenerator; + _tokenSerializer = tokenSerializer; + _tokenStore = tokenStore; + _htmlEncoder = htmlEncoder; } /// @@ -56,8 +50,21 @@ namespace Microsoft.AspNet.Antiforgery /// public string GetHtml([NotNull] HttpContext context) { - var html = _worker.GetFormInputElement(context); - return html; + CheckSSLConfig(context); + + var cookieToken = GetCookieTokenDoesNotThrow(context); + var tokenSet = GetTokens(context, cookieToken); + cookieToken = tokenSet.CookieToken; + var formToken = tokenSet.FormToken; + + SaveCookieTokenAndHeader(context, cookieToken); + + var inputTag = string.Format( + "", + _htmlEncoder.HtmlEncode(_options.FormFieldName), + _htmlEncoder.HtmlEncode("hidden"), + _htmlEncoder.HtmlEncode(_tokenSerializer.Serialize(formToken))); + return inputTag; } /// @@ -82,7 +89,14 @@ namespace Microsoft.AspNet.Antiforgery // 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); + CheckSSLConfig(context); + + var deserializedcookieToken = DeserializeTokenDoesNotThrow(oldCookieToken); + var tokenSet = GetTokens(context, deserializedcookieToken); + + var serializedCookieToken = Serialize(tokenSet.CookieToken); + var serializedFormToken = Serialize(tokenSet.FormToken); + return new AntiforgeryTokenSet(serializedFormToken, serializedCookieToken); } /// @@ -92,7 +106,14 @@ namespace Microsoft.AspNet.Antiforgery /// The HTTP context associated with the current call. public async Task ValidateAsync([NotNull] HttpContext context) { - await _worker.ValidateAsync(context); + CheckSSLConfig(context); + + // Extract cookie & form tokens + var cookieToken = _tokenStore.GetCookieToken(context); + var formToken = await _tokenStore.GetFormTokenAsync(context); + + // Validate + _tokenGenerator.ValidateTokens(context, cookieToken, formToken); } /// @@ -103,7 +124,17 @@ namespace Microsoft.AspNet.Antiforgery /// 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); + CheckSSLConfig(context); + + // Extract cookie & form tokens + var deserializedCookieToken = DeserializeToken(cookieToken); + var deserializedFormToken = DeserializeToken(formToken); + + // Validate + _tokenGenerator.ValidateTokens( + context, + deserializedCookieToken, + deserializedFormToken); } /// @@ -123,17 +154,117 @@ namespace Microsoft.AspNet.Antiforgery /// The HTTP context associated with the current call. public void SetCookieTokenAndHeader([NotNull] HttpContext context) { - _worker.SetCookieTokenAndHeader(context); + CheckSSLConfig(context); + + var cookieToken = GetCookieTokenDoesNotThrow(context); + cookieToken = ValidateAndGenerateNewCookieToken(cookieToken); + + SaveCookieTokenAndHeader(context, cookieToken); } - private string ComputeCookieName(string applicationId) + // This method returns null if oldCookieToken is valid. + private AntiforgeryToken ValidateAndGenerateNewCookieToken(AntiforgeryToken cookieToken) { - using (var sha256 = SHA256.Create()) + if (!_tokenGenerator.IsCookieTokenValid(cookieToken)) { - var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId)); - var subHash = hash.Take(8).ToArray(); - return WebEncoders.Base64UrlEncode(subHash); + // 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( + [NotNull] HttpContext httpContext, + AntiforgeryToken cookieToken) + { + if (cookieToken != null) + { + // Persist the new cookie if it is not null. + _tokenStore.SaveCookieToken(httpContext, 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. + httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); + } + } + + private void CheckSSLConfig(HttpContext httpContext) + { + if (_options.RequireSSL && !httpContext.Request.IsHttps) + { + throw new InvalidOperationException(Resources.AntiforgeryWorker_RequireSSL); + } + } + + private AntiforgeryToken DeserializeToken(string serializedToken) + { + return (!string.IsNullOrEmpty(serializedToken)) + ? _tokenSerializer.Deserialize(serializedToken) + : null; + } + + private AntiforgeryToken DeserializeTokenDoesNotThrow(string serializedToken) + { + try + { + return DeserializeToken(serializedToken); + } + catch + { + // ignore failures since we'll just generate a new token + return null; + } + } + + private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext) + { + try + { + return _tokenStore.GetCookieToken(httpContext); + } + catch + { + // ignore failures since we'll just generate a new token + return null; + } + } + + private AntiforgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiforgeryToken cookieToken) + { + var newCookieToken = ValidateAndGenerateNewCookieToken(cookieToken); + if (newCookieToken != null) + { + cookieToken = newCookieToken; + } + var formToken = _tokenGenerator.GenerateFormToken( + httpContext, + cookieToken); + + return new AntiforgeryTokenSetInternal() + { + // Note : The new cookie would be null if the old cookie is valid. + CookieToken = newCookieToken, + FormToken = formToken + }; + } + + private string Serialize(AntiforgeryToken token) + { + return (token != null) ? _tokenSerializer.Serialize(token) : null; + } + + private class AntiforgeryTokenSetInternal + { + public AntiforgeryToken FormToken { get; set; } + + public AntiforgeryToken CookieToken { get; set; } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs index 6906c02643..d8db3df2cb 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptions.cs @@ -12,9 +12,6 @@ namespace Microsoft.AspNet.Antiforgery { private const string AntiforgeryTokenFieldName = "__RequestVerificationToken"; - private string _cookieName; - private string _formFieldName = AntiforgeryTokenFieldName; - /// /// Specifies the name of the cookie that is used by the anti-forgery /// system. @@ -23,47 +20,19 @@ namespace Microsoft.AspNet.Antiforgery /// If an explicit name is not provided, the system will automatically /// generate a name. /// - public string CookieName - { - get - { - return _cookieName; - } - - [param: NotNull] - set - { - _cookieName = value; - } - } + public string CookieName { get; [param: NotNull] set; } /// /// Specifies the name of the anti-forgery token field that is used by the anti-forgery system. /// - public string FormFieldName - { - get - { - return _formFieldName; - } - - [param: NotNull] - set - { - _formFieldName = value; - } - } + public string FormFieldName { get; [param: NotNull] set; } = AntiforgeryTokenFieldName; /// /// Specifies whether SSL is required for the anti-forgery system /// to operate. If this setting is 'true' and a non-SSL request /// comes into the system, all anti-forgery APIs will fail. /// - public bool RequireSSL - { - get; - set; - } + public bool RequireSSL { get; set; } /// /// Specifies whether to suppress the generation of X-Frame-Options header @@ -71,10 +40,6 @@ namespace Microsoft.AspNet.Antiforgery /// header is generated with the value SAMEORIGIN. If this setting is 'true', /// the X-Frame-Options header will not be generated for the response. /// - public bool SuppressXFrameOptionsHeader - { - get; - set; - } + public bool SuppressXFrameOptionsHeader { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptionsSetup.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptionsSetup.cs new file mode 100644 index 0000000000..76030662ad --- /dev/null +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryOptionsSetup.cs @@ -0,0 +1,41 @@ +// 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.Linq; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNet.DataProtection; +using Microsoft.AspNet.WebUtilities; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Antiforgery +{ + public class AntiforgeryOptionsSetup : ConfigureOptions + { + public AntiforgeryOptionsSetup(IOptions dataProtectionOptionsAccessor) + : base((options) => ConfigureOptions(options, dataProtectionOptionsAccessor.Options)) + { + // We want this to run after any user setups to compute a default name if needed. + Order = 10000; + } + + public static void ConfigureOptions(AntiforgeryOptions options, DataProtectionOptions dataProtectionOptions) + { + if (options.CookieName == null) + { + var applicationId = dataProtectionOptions.ApplicationDiscriminator ?? string.Empty; + options.CookieName = ComputeCookieName(applicationId); + } + } + + private static 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); + } + } + } +} diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenGenerator.cs similarity index 89% rename from src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs rename to src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenGenerator.cs index cdb02b986f..493e4a3495 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenProvider.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenGenerator.cs @@ -5,21 +5,22 @@ using System; using System.Diagnostics; using System.Security.Claims; using Microsoft.AspNet.Http; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Antiforgery { - public sealed class AntiforgeryTokenProvider : IAntiforgeryTokenValidator, IAntiforgeryTokenGenerator + public class AntiforgeryTokenGenerator : IAntiforgeryTokenGenerator { private readonly IClaimUidExtractor _claimUidExtractor; - private readonly AntiforgeryOptions _config; + private readonly AntiforgeryOptions _options; private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider; - internal AntiforgeryTokenProvider( - AntiforgeryOptions config, + public AntiforgeryTokenGenerator( + IOptions optionsAccessor, IClaimUidExtractor claimUidExtractor, IAntiforgeryAdditionalDataProvider additionalDataProvider) { - _config = config; + _options = optionsAccessor.Options; _claimUidExtractor = claimUidExtractor; _additionalDataProvider = additionalDataProvider; } @@ -33,9 +34,9 @@ namespace Microsoft.AspNet.Antiforgery }; } - public AntiforgeryToken GenerateFormToken(HttpContext httpContext, - ClaimsIdentity identity, - AntiforgeryToken cookieToken) + public AntiforgeryToken GenerateFormToken( + HttpContext httpContext, + AntiforgeryToken cookieToken) { Debug.Assert(IsCookieTokenValid(cookieToken)); @@ -46,6 +47,7 @@ namespace Microsoft.AspNet.Antiforgery }; var isIdentityAuthenticated = false; + var identity = httpContext.User?.Identity as ClaimsIdentity; // populate Username and ClaimUid if (identity != null && identity.IsAuthenticated) @@ -84,7 +86,6 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens( HttpContext httpContext, - ClaimsIdentity identity, AntiforgeryToken sessionToken, AntiforgeryToken fieldToken) { @@ -92,19 +93,19 @@ namespace Microsoft.AspNet.Antiforgery if (sessionToken == null) { throw new InvalidOperationException( - Resources.FormatAntiforgeryToken_CookieMissing(_config.CookieName)); + Resources.FormatAntiforgeryToken_CookieMissing(_options.CookieName)); } if (fieldToken == null) { throw new InvalidOperationException( - Resources.FormatAntiforgeryToken_FormFieldMissing(_config.FormFieldName)); + Resources.FormatAntiforgeryToken_FormFieldMissing(_options.FormFieldName)); } // Do the tokens have the correct format? if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken) { throw new InvalidOperationException( - Resources.FormatAntiforgeryToken_TokensSwapped(_config.CookieName, _config.FormFieldName)); + Resources.FormatAntiforgeryToken_TokensSwapped(_options.CookieName, _options.FormFieldName)); } // Are the security tokens embedded in each incoming token identical? @@ -117,6 +118,7 @@ namespace Microsoft.AspNet.Antiforgery var currentUsername = string.Empty; BinaryBlob currentClaimUid = null; + var identity = httpContext.User?.Identity as ClaimsIdentity; if (identity != null && identity.IsAuthenticated) { currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs index 9022f77b13..f3d79844f7 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenSerializer.cs @@ -9,14 +9,16 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Antiforgery { - public sealed class AntiforgeryTokenSerializer : IAntiforgeryTokenSerializer + public class AntiforgeryTokenSerializer : IAntiforgeryTokenSerializer { + private static readonly string Purpose = "Microsoft.AspNet.Antiforgery.AntiforgeryToken.v1"; + private readonly IDataProtector _cryptoSystem; private const byte TokenVersion = 0x01; - public AntiforgeryTokenSerializer([NotNull] IDataProtector cryptoSystem) + public AntiforgeryTokenSerializer([NotNull] IDataProtectionProvider provider) { - _cryptoSystem = cryptoSystem; + _cryptoSystem = provider.CreateProtector(Purpose); } public AntiforgeryToken Deserialize(string serializedToken) diff --git a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs index e288c1c786..ecf17ad0a7 100644 --- a/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Antiforgery/AntiforgeryTokenStore.cs @@ -6,74 +6,77 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Antiforgery { // Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form - public sealed class AntiforgeryTokenStore : IAntiforgeryTokenStore + public class AntiforgeryTokenStore : IAntiforgeryTokenStore { - private readonly AntiforgeryOptions _config; - private readonly IAntiforgeryTokenSerializer _serializer; + private readonly AntiforgeryOptions _options; + private readonly IAntiforgeryTokenSerializer _tokenSerializer; - public AntiforgeryTokenStore([NotNull] AntiforgeryOptions config, - [NotNull] IAntiforgeryTokenSerializer serializer) + public AntiforgeryTokenStore( + [NotNull] IOptions optionsAccessor, + [NotNull] IAntiforgeryTokenSerializer tokenSerializer) { - _config = config; - _serializer = serializer; + _options = optionsAccessor.Options; + _tokenSerializer = tokenSerializer; } public AntiforgeryToken GetCookieToken(HttpContext httpContext) { - var contextAccessor = - httpContext.RequestServices.GetRequiredService(); + var services = httpContext.RequestServices; + var contextAccessor = services.GetRequiredService(); if (contextAccessor.Value != null) { return contextAccessor.Value.CookieToken; } - var requestCookie = httpContext.Request.Cookies[_config.CookieName]; + var requestCookie = httpContext.Request.Cookies[_options.CookieName]; if (string.IsNullOrEmpty(requestCookie)) { // unable to find the cookie. return null; } - return _serializer.Deserialize(requestCookie); + return _tokenSerializer.Deserialize(requestCookie); } public async Task GetFormTokenAsync(HttpContext httpContext) { var form = await httpContext.Request.ReadFormAsync(); - var value = form[_config.FormFieldName]; + var value = form[_options.FormFieldName]; if (string.IsNullOrEmpty(value)) { // did not exist return null; } - return _serializer.Deserialize(value); + return _tokenSerializer.Deserialize(value); } public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token) { // Add the cookie to the request based context. // This is useful if the cookie needs to be reloaded in the context of the same request. - var contextAccessor = - httpContext.RequestServices.GetRequiredService(); + + var services = httpContext.RequestServices; + var contextAccessor = services.GetRequiredService(); Debug.Assert(contextAccessor.Value == null, "AntiforgeryContext should be set only once per request."); contextAccessor.Value = new AntiforgeryContext() { CookieToken = token }; - var serializedToken = _serializer.Serialize(token); + var serializedToken = _tokenSerializer.Serialize(token); var options = new CookieOptions() { HttpOnly = true }; - // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default + // Note: don't use "newCookie.Secure = _options.RequireSSL;" since the default // value of newCookie.Secure is poulated out of band. - if (_config.RequireSSL) + if (_options.RequireSSL) { options.Secure = true; } - httpContext.Response.Cookies.Append(_config.CookieName, serializedToken, options); + httpContext.Response.Cookies.Append(_options.CookieName, serializedToken, options); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs index 87d936dec3..8ca4510bf7 100644 --- a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs +++ b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenGenerator.cs @@ -6,7 +6,9 @@ using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Antiforgery { - // Provides configuration information about the anti-forgery system. + /// + /// Generates and validates antiforgery tokens. + /// public interface IAntiforgeryTokenGenerator { // Generates a new random cookie token. @@ -16,7 +18,16 @@ namespace Microsoft.AspNet.Antiforgery // The incoming cookie token must be valid. AntiforgeryToken GenerateFormToken( HttpContext httpContext, - ClaimsIdentity identity, AntiforgeryToken cookieToken); + + // Determines whether an existing cookie token is valid (well-formed). + // If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken(). + bool IsCookieTokenValid(AntiforgeryToken cookieToken); + + // Validates a (cookie, form) token pair. + void ValidateTokens( + HttpContext httpContext, + AntiforgeryToken cookieToken, + AntiforgeryToken formToken); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs b/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs deleted file mode 100644 index ed75a9de45..0000000000 --- a/src/Microsoft.AspNet.Antiforgery/IAntiforgeryTokenValidator.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.Security.Claims; -using Microsoft.AspNet.Http; - -namespace Microsoft.AspNet.Antiforgery -{ - // Provides an abstraction around something that can validate anti-XSRF tokens - public interface IAntiforgeryTokenValidator - { - // Determines whether an existing cookie token is valid (well-formed). - // If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken(). - bool IsCookieTokenValid(AntiforgeryToken cookieToken); - - // Validates a (cookie, form) token pair. - void ValidateTokens( - HttpContext httpContext, - ClaimsIdentity identity, - AntiforgeryToken cookieToken, - AntiforgeryToken formToken); - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs b/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs deleted file mode 100644 index ac4dd9e0b8..0000000000 --- a/src/Microsoft.AspNet.Antiforgery/Internal/AntiforgeryWorker.cs +++ /dev/null @@ -1,251 +0,0 @@ -// 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.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.Framework.Internal; -using Microsoft.Framework.WebEncoders; - -namespace Microsoft.AspNet.Antiforgery.Internal -{ - public sealed class AntiforgeryWorker - { - private readonly AntiforgeryOptions _config; - private readonly IAntiforgeryTokenSerializer _serializer; - private readonly IAntiforgeryTokenStore _tokenStore; - private readonly IAntiforgeryTokenValidator _validator; - private readonly IAntiforgeryTokenGenerator _generator; - private readonly IHtmlEncoder _htmlEncoder; - - public AntiforgeryWorker( - [NotNull] IAntiforgeryTokenSerializer serializer, - [NotNull] AntiforgeryOptions config, - [NotNull] IAntiforgeryTokenStore tokenStore, - [NotNull] IAntiforgeryTokenGenerator generator, - [NotNull] IAntiforgeryTokenValidator validator, - [NotNull] IHtmlEncoder htmlEncoder) - { - _serializer = serializer; - _config = config; - _tokenStore = tokenStore; - _generator = generator; - _validator = validator; - _htmlEncoder = htmlEncoder; - } - - private void CheckSSLConfig(HttpContext httpContext) - { - if (_config.RequireSSL && !httpContext.Request.IsHttps) - { - throw new InvalidOperationException(Resources.AntiforgeryWorker_RequireSSL); - } - } - - private AntiforgeryToken DeserializeToken(string serializedToken) - { - return (!string.IsNullOrEmpty(serializedToken)) - ? _serializer.Deserialize(serializedToken) - : null; - } - - private AntiforgeryToken DeserializeTokenDoesNotThrow(string serializedToken) - { - try - { - return DeserializeToken(serializedToken); - } - catch - { - // ignore failures since we'll just generate a new token - return null; - } - } - - private static ClaimsIdentity ExtractIdentity(HttpContext httpContext) - { - if (httpContext != null) - { - var user = httpContext.User; - - if (user != null) - { - // We only support ClaimsIdentity. - return user.Identity as ClaimsIdentity; - } - } - - return null; - } - - private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext) - { - try - { - return _tokenStore.GetCookieToken(httpContext); - } - catch - { - // ignore failures since we'll just generate a new token - return null; - } - } - - // [ ENTRY POINT ] - // Generates an anti-XSRF token pair for the current user. The return - // value is the hidden input form element that should be rendered in - // the
. This method has a side effect: it may set a response - // cookie. - public string GetFormInputElement([NotNull] HttpContext httpContext) - { - CheckSSLConfig(httpContext); - - var cookieToken = GetCookieTokenDoesNotThrow(httpContext); - var tokenSet = GetTokens(httpContext, cookieToken); - cookieToken = tokenSet.CookieToken; - var formToken = tokenSet.FormToken; - - SaveCookieTokenAndHeader(httpContext, cookieToken); - - var inputTag = string.Format( - "", - _htmlEncoder.HtmlEncode(_config.FormFieldName), - _htmlEncoder.HtmlEncode("hidden"), - _htmlEncoder.HtmlEncode(_serializer.Serialize(formToken))); - return inputTag; - } - - // [ ENTRY POINT ] - // Generates a (cookie, form) serialized token pair for the current user. - // The caller may specify an existing cookie value if one exists. If the - // 'new cookie value' out param is non-null, the caller *must* persist - // the new value to cookie storage since the original value was null or - // invalid. This method is side-effect free. - public AntiforgeryTokenSet GetTokens([NotNull] HttpContext httpContext, string cookieToken) - { - CheckSSLConfig(httpContext); - var deSerializedcookieToken = DeserializeTokenDoesNotThrow(cookieToken); - var tokenSet = GetTokens(httpContext, deSerializedcookieToken); - - var serializedCookieToken = Serialize(tokenSet.CookieToken); - var serializedFormToken = Serialize(tokenSet.FormToken); - return new AntiforgeryTokenSet(serializedFormToken, serializedCookieToken); - } - - private AntiforgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiforgeryToken cookieToken) - { - var newCookieToken = ValidateAndGenerateNewCookieToken(cookieToken); - if (newCookieToken != null) - { - cookieToken = newCookieToken; - } - var formToken = _generator.GenerateFormToken( - httpContext, - ExtractIdentity(httpContext), - cookieToken); - - return new AntiforgeryTokenSetInternal() - { - // Note : The new cookie would be null if the old cookie is valid. - CookieToken = newCookieToken, - FormToken = formToken - }; - } - - private string Serialize(AntiforgeryToken token) - { - return (token != null) ? _serializer.Serialize(token) : null; - } - - // [ ENTRY POINT ] - // Given an HttpContext, validates that the anti-XSRF tokens contained - // in the cookies & form are OK for this request. - public async Task ValidateAsync([NotNull] HttpContext httpContext) - { - CheckSSLConfig(httpContext); - - // Extract cookie & form tokens - var cookieToken = _tokenStore.GetCookieToken(httpContext); - var formToken = await _tokenStore.GetFormTokenAsync(httpContext); - - // Validate - _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken); - } - - // [ ENTRY POINT ] - // Given the serialized string representations of a cookie & form token, - // validates that the pair is OK for this request. - public void Validate([NotNull] HttpContext httpContext, string cookieToken, string formToken) - { - CheckSSLConfig(httpContext); - - // Extract cookie & form tokens - var deserializedCookieToken = DeserializeToken(cookieToken); - var deserializedFormToken = DeserializeToken(formToken); - - // Validate - _validator.ValidateTokens( - httpContext, - ExtractIdentity(httpContext), - deserializedCookieToken, - deserializedFormToken); - } - - - /// - /// 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 httpContext) - { - CheckSSLConfig(httpContext); - - var cookieToken = GetCookieTokenDoesNotThrow(httpContext); - cookieToken = ValidateAndGenerateNewCookieToken(cookieToken); - - SaveCookieTokenAndHeader(httpContext, cookieToken); - } - - // This method returns null if oldCookieToken is valid. - private AntiforgeryToken ValidateAndGenerateNewCookieToken(AntiforgeryToken cookieToken) - { - if (!_validator.IsCookieTokenValid(cookieToken)) - { - // Need to make sure we're always operating with a good cookie token. - var newCookieToken = _generator.GenerateCookieToken(); - Debug.Assert(_validator.IsCookieTokenValid(newCookieToken)); - return newCookieToken; - } - - return null; - } - - private void SaveCookieTokenAndHeader( - [NotNull] HttpContext httpContext, - AntiforgeryToken cookieToken) - { - if (cookieToken != null) - { - // Persist the new cookie if it is not null. - _tokenStore.SaveCookieToken(httpContext, cookieToken); - } - - if (!_config.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. - httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); - } - } - - private class AntiforgeryTokenSetInternal - { - public AntiforgeryToken FormToken { get; set; } - - public AntiforgeryToken CookieToken { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Antiforgery/ServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Antiforgery/ServiceCollectionExtensions.cs index 22ad5f2202..f6dcee30e1 100644 --- a/src/Microsoft.AspNet.Antiforgery/ServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Antiforgery/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNet.Antiforgery; using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; namespace Microsoft.Framework.DependencyInjection { @@ -14,11 +15,17 @@ namespace Microsoft.Framework.DependencyInjection services.AddDataProtection(); services.AddWebEncoders(); - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor.Singleton()); - services.TryAdd(ServiceDescriptor.Scoped()); - services.TryAdd( - ServiceDescriptor.Singleton()); + // Don't overwrite any options setups that a user may have added. + services.TryAddEnumerable( + ServiceDescriptor.Transient, AntiforgeryOptionsSetup>()); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); return services; } diff --git a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryOptionsSetupTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryOptionsSetupTest.cs new file mode 100644 index 0000000000..68bd517548 --- /dev/null +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryOptionsSetupTest.cs @@ -0,0 +1,58 @@ +// 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 Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; +using Xunit; + +namespace Microsoft.AspNet.Antiforgery +{ + public class AntiforgeryOptionsSetupTest + { + [Theory] + [InlineData("HelloWorldApp", "tGmK82_ckDw")] + [InlineData("TodoCalendar", "7mK1hBEBwYs")] + public void AntiforgeryOptionsSetup_SetsDefaultCookieName_BasedOnApplicationId( + string applicationId, + string expectedCookieName) + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAntiforgery(); + serviceCollection.ConfigureDataProtection(o => o.SetApplicationName(applicationId)); + + var services = serviceCollection.BuildServiceProvider(); + var options = services.GetRequiredService>(); + + // Act + var cookieName = options.Options.CookieName; + + // Assert + Assert.Equal(expectedCookieName, cookieName); + } + + [Fact] + public void AntiforgeryOptionsSetup_UserOptionsSetup_CanSetCookieName() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAntiforgery(); + serviceCollection.ConfigureDataProtection(o => o.SetApplicationName("HelloWorldApp")); + + serviceCollection.Configure(o => + { + Assert.Null(o.CookieName); + o.CookieName = "antiforgery"; + }, order: 9999); + + var services = serviceCollection.BuildServiceProvider(); + var options = services.GetRequiredService>(); + + // Act + var cookieName = options.Options.CookieName; + + // Assert + Assert.Equal("antiforgery", cookieName); + } + } +} diff --git a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryWorkerTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTest.cs similarity index 58% rename from test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryWorkerTest.cs rename to test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTest.cs index 899a1f0225..6b1e4c5368 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryWorkerTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTest.cs @@ -1,160 +1,127 @@ // 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. -#if DNX451 - using System; -using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Antiforgery.Internal; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; +using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders.Testing; +#if DNX451 using Moq; +#endif using Xunit; namespace Microsoft.AspNet.Antiforgery { - public class AntiforgeryWorkerTest + public class AntiforgeryTest { - [Fact] public async Task ChecksSSL_ValidateAsync_Throws() { // Arrange - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.Request.IsHttps) - .Returns(false); + var httpContext = new DefaultHttpContext(); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { RequireSSL = true }; - var worker = new AntiforgeryWorker( - config: config, - serializer: null, - tokenStore: null, - generator: null, - validator: null, - htmlEncoder: new CommonTestEncoder()); + var antiforgery = GetAntiforgery(options); - // Act & assert - var ex = - await - Assert.ThrowsAsync( - async () => await worker.ValidateAsync(mockHttpContext.Object)); + // Act & Assert + var exception = await Assert.ThrowsAsync( + async () => await antiforgery.ValidateAsync(httpContext)); Assert.Equal( @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + "but the current request is not an SSL request.", - ex.Message); + exception.Message); } [Fact] public void ChecksSSL_Validate_Throws() { // Arrange - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.Request.IsHttps) - .Returns(false); + var httpContext = new DefaultHttpContext(); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { RequireSSL = true }; - var worker = new AntiforgeryWorker( - config: config, - serializer: null, - tokenStore: null, - generator: null, - validator: null, - htmlEncoder: new CommonTestEncoder()); + var antiforgery = GetAntiforgery(options); - // Act & assert - var ex = Assert.Throws( - () => worker.Validate(mockHttpContext.Object, cookieToken: null, formToken: null)); - Assert.Equal( - @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + - "but the current request is not an SSL request.", - ex.Message); - } - - [Fact] - public void ChecksSSL_GetFormInputElement_Throws() - { - // Arrange - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.Request.IsHttps) - .Returns(false); - - var config = new AntiforgeryOptions() - { - RequireSSL = true - }; - - var worker = new AntiforgeryWorker( - config: config, - serializer: null, - tokenStore: null, - generator: null, - validator: null, - htmlEncoder: new CommonTestEncoder()); - - // Act & assert - var ex = Assert.Throws(() => worker.GetFormInputElement(mockHttpContext.Object)); + // Act & Assert + var exception = Assert.Throws( + () => antiforgery.Validate(httpContext, cookieToken: null, formToken: null)); Assert.Equal( @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + "but the current request is not an SSL request.", - ex.Message); + exception.Message); + } + + [Fact] + public void ChecksSSL_GetHtml_Throws() + { + // Arrange + var httpContext = new DefaultHttpContext(); + + var options = new AntiforgeryOptions() + { + RequireSSL = true + }; + + var antiforgery = GetAntiforgery(options); + + // Act & Assert + var exception = Assert.Throws( + () => antiforgery.GetHtml(httpContext)); + Assert.Equal( + @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + + "but the current request is not an SSL request.", + exception.Message); } [Fact] public void ChecksSSL_GetTokens_Throws() { // Arrange - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.Request.IsHttps) - .Returns(false); + var httpContext = new DefaultHttpContext(); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { RequireSSL = true }; - var worker = new AntiforgeryWorker( - config: config, - serializer: null, - tokenStore: null, - generator: null, - validator: null, - htmlEncoder: new CommonTestEncoder()); + var antiforgery = GetAntiforgery(options); - // Act & assert - var ex = Assert.Throws(() => - worker.GetTokens(mockHttpContext.Object, "cookie-token")); + // Act & Assert + var exception = Assert.Throws( + () => antiforgery.GetTokens(httpContext, "dkfkfkf")); Assert.Equal( - @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + - "but the current request is not an SSL request.", - ex.Message); + @"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " + + "but the current request is not an SSL request.", + exception.Message); } +#if DNX451 + [Fact] public void GetFormInputElement_ExistingInvalidCookieToken_GeneratesANewCookieAndAnAntiforgeryToken() { // Arrange - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { FormFieldName = "form-field-name" }; // Make sure the existing cookie is invalid. - var context = GetAntiforgeryWorkerContext(config, isOldCookieValid: false); - var worker = GetAntiforgeryWorker(context); + var context = CreateMockContext(options, isOldCookieValid: false); + var antiforgery = GetAntiforgery(context); // Act - var inputElement = worker.GetFormInputElement(context.HttpContext.Object); + var inputElement = antiforgery.GetHtml(context.HttpContext); // Assert Assert.Equal( @@ -168,25 +135,25 @@ namespace Microsoft.AspNet.Antiforgery public void GetFormInputElement_ExistingInvalidCookieToken_SwallowsExceptions() { // Arrange - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { FormFieldName = "form-field-name" }; // Make sure the existing cookie is invalid. - var context = GetAntiforgeryWorkerContext(config, isOldCookieValid: false); - var worker = GetAntiforgeryWorker(context); + var context = CreateMockContext(options, isOldCookieValid: false); + var antiforgery = GetAntiforgery(context); // This will cause the cookieToken to be null. - context.TokenStore.Setup(o => o.GetCookieToken(context.HttpContext.Object)) + context.TokenStore.Setup(o => o.GetCookieToken(context.HttpContext)) .Throws(new Exception("should be swallowed")); // Setup so that the null cookie token returned is treated as invalid. - context.TokenValidator.Setup(o => o.IsCookieTokenValid(null)) + context.TokenGenerator.Setup(o => o.IsCookieTokenValid(null)) .Returns(false); // Act - var inputElement = worker.GetFormInputElement(context.HttpContext.Object); + var inputElement = antiforgery.GetHtml(context.HttpContext); // Assert Assert.Equal( @@ -206,11 +173,11 @@ namespace Microsoft.AspNet.Antiforgery }; // Make sure the existing cookie is valid and use the same cookie for the mock Token Provider. - var context = GetAntiforgeryWorkerContext(options, useOldCookie: true, isOldCookieValid: true); - var worker = GetAntiforgeryWorker(context); + var context = CreateMockContext(options, useOldCookie: true, isOldCookieValid: true); + var antiforgery = GetAntiforgery(context); // Act - var inputElement = worker.GetFormInputElement(context.HttpContext.Object); + var inputElement = antiforgery.GetHtml(context.HttpContext); // Assert Assert.Equal( @@ -231,14 +198,14 @@ namespace Microsoft.AspNet.Antiforgery }; // Genreate a new cookie. - var context = GetAntiforgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false); - var worker = GetAntiforgeryWorker(context); + var context = CreateMockContext(options, useOldCookie: false, isOldCookieValid: false); + var antiforgery = GetAntiforgery(context); // Act - var inputElement = worker.GetFormInputElement(context.HttpContext.Object); + var inputElement = antiforgery.GetHtml(context.HttpContext); // Assert - string xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"]; + string xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"]; Assert.Equal(expectedHeaderValue, xFrameOptions); } @@ -247,14 +214,14 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange // Genreate a new cookie. - var context = GetAntiforgeryWorkerContext( + var context = CreateMockContext( new AntiforgeryOptions(), useOldCookie: false, isOldCookieValid: false); - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act - var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token"); + var tokenset = antiforgery.GetTokens(context.HttpContext, "serialized-old-cookie-token"); // Assert Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken); @@ -266,7 +233,7 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange // Make sure the existing cookie is invalid. - var context = GetAntiforgeryWorkerContext( + var context = CreateMockContext( new AntiforgeryOptions(), useOldCookie: false, isOldCookieValid: false); @@ -276,12 +243,12 @@ namespace Microsoft.AspNet.Antiforgery .Throws(new Exception("should be swallowed")); // Setup so that the null cookie token returned is treated as invalid. - context.TokenValidator.Setup(o => o.IsCookieTokenValid(null)) + context.TokenGenerator.Setup(o => o.IsCookieTokenValid(null)) .Returns(false); - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act - var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token"); + var tokenset = antiforgery.GetTokens(context.HttpContext, "serialized-old-cookie-token"); // Assert Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken); @@ -292,15 +259,15 @@ namespace Microsoft.AspNet.Antiforgery public void GetTokens_ExistingValidCookieToken_GeneratesANewFormToken() { // Arrange - var context = GetAntiforgeryWorkerContext( + var context = CreateMockContext( new AntiforgeryOptions(), useOldCookie: true, isOldCookieValid: true); context.TokenStore = null; - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act - var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token"); + var tokenset = antiforgery.GetTokens(context.HttpContext, "serialized-old-cookie-token"); // Assert Assert.Null(tokenset.CookieToken); @@ -311,95 +278,88 @@ namespace Microsoft.AspNet.Antiforgery public void Validate_FromInvalidStrings_Throws() { // Arrange - var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions()); + var context = CreateMockContext(new AntiforgeryOptions()); context.TokenSerializer.Setup(o => o.Deserialize("cookie-token")) .Returns(context.TestTokenSet.OldCookieToken); context.TokenSerializer.Setup(o => o.Deserialize("form-token")) .Returns(context.TestTokenSet.FormToken); - context.TokenValidator.Setup(o => o.ValidateTokens( - context.HttpContext.Object, - context.HttpContext.Object.User.Identity as ClaimsIdentity, + context.TokenGenerator.Setup(o => o.ValidateTokens( + context.HttpContext, context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) .Throws(new InvalidOperationException("my-message")); context.TokenStore = null; - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act & assert - var ex = - Assert.Throws( - () => worker.Validate(context.HttpContext.Object, "cookie-token", "form-token")); - Assert.Equal("my-message", ex.Message); + var exception = Assert.Throws( + () => antiforgery.Validate(context.HttpContext, "cookie-token", "form-token")); + Assert.Equal("my-message", exception.Message); } [Fact] public void Validate_FromValidStrings_TokensValidatedSuccessfully() { // Arrange - var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions()); + var context = CreateMockContext(new AntiforgeryOptions()); context.TokenSerializer.Setup(o => o.Deserialize("cookie-token")) .Returns(context.TestTokenSet.OldCookieToken); context.TokenSerializer.Setup(o => o.Deserialize("form-token")) .Returns(context.TestTokenSet.FormToken); - context.TokenValidator.Setup(o => o.ValidateTokens( - context.HttpContext.Object, - context.HttpContext.Object.User.Identity as ClaimsIdentity, + context.TokenGenerator.Setup(o => o.ValidateTokens( + context.HttpContext, context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) .Verifiable(); context.TokenStore = null; - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act - worker.Validate(context.HttpContext.Object, "cookie-token", "form-token"); + antiforgery.Validate(context.HttpContext, "cookie-token", "form-token"); // Assert - context.TokenValidator.Verify(); + context.TokenGenerator.Verify(); } [Fact] public async Task Validate_FromStore_Failure() { // Arrange - var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions()); + var context = CreateMockContext(new AntiforgeryOptions()); - context.TokenValidator.Setup(o => o.ValidateTokens( - context.HttpContext.Object, - context.HttpContext.Object.User.Identity as ClaimsIdentity, + context.TokenGenerator.Setup(o => o.ValidateTokens( + context.HttpContext, context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) .Throws(new InvalidOperationException("my-message")); context.TokenSerializer = null; - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act & assert - var ex = - await - Assert.ThrowsAsync( - async () => await worker.ValidateAsync(context.HttpContext.Object)); - Assert.Equal("my-message", ex.Message); + var exception = await Assert.ThrowsAsync( + async () => await antiforgery.ValidateAsync(context.HttpContext)); + Assert.Equal("my-message", exception.Message); } [Fact] public async Task Validate_FromStore_Success() { // Arrange - var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions()); + var context = CreateMockContext(new AntiforgeryOptions()); - context.TokenValidator.Setup(o => o.ValidateTokens( - context.HttpContext.Object, - context.HttpContext.Object.User.Identity as ClaimsIdentity, + context.TokenGenerator.Setup(o => o.ValidateTokens( + context.HttpContext, context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken)) .Verifiable(); context.TokenSerializer = null; - var worker = GetAntiforgeryWorker(context); + var antiforgery = GetAntiforgery(context); // Act - await worker.ValidateAsync(context.HttpContext.Object); + await antiforgery.ValidateAsync(context.HttpContext); // Assert - context.TokenValidator.Verify(); + context.TokenGenerator.Verify(); } [Theory] @@ -416,45 +376,55 @@ namespace Microsoft.AspNet.Antiforgery }; // Genreate a new cookie. - var context = GetAntiforgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false); - var worker = GetAntiforgeryWorker(context); + var context = CreateMockContext(options, useOldCookie: false, isOldCookieValid: false); + var antiforgery = GetAntiforgery(context); // Act - worker.SetCookieTokenAndHeader(context.HttpContext.Object); + antiforgery.SetCookieTokenAndHeader(context.HttpContext); // Assert - var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"]; + var xFrameOptions = context.HttpContext.Response.Headers["X-Frame-Options"]; Assert.Equal(expectedHeaderValue, xFrameOptions); } - private AntiforgeryWorker GetAntiforgeryWorker(AntiforgeryWorkerContext context) - { - return new AntiforgeryWorker( - config: context.Options, - serializer: context.TokenSerializer != null ? context.TokenSerializer.Object : null, - tokenStore: context.TokenStore != null ? context.TokenStore.Object : null, - generator: context.TokenGenerator != null ? context.TokenGenerator.Object : null, - validator: context.TokenValidator != null ? context.TokenValidator.Object : null, - htmlEncoder: new CommonTestEncoder()); - } +#endif - private Mock GetHttpContext(bool setupResponse = true) + private Antiforgery GetAntiforgery( + AntiforgeryOptions options = null, + IAntiforgeryTokenGenerator tokenGenerator = null, + IAntiforgeryTokenSerializer tokenSerializer = null, + IAntiforgeryTokenStore tokenStore = null) { - var identity = new ClaimsIdentity("some-auth"); - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.User) - .Returns(new ClaimsPrincipal(identity)); - - if (setupResponse) + var optionsManager = new TestOptionsManager(); + if (options != null) { - var mockResponse = new Mock(); - mockResponse.Setup(r => r.Headers) - .Returns(new HeaderDictionary(new Dictionary())); - mockHttpContext.Setup(o => o.Response) - .Returns(mockResponse.Object); + optionsManager.Options = options; } - return mockHttpContext; + return new Antiforgery( + antiforgeryOptionsAccessor: optionsManager, + tokenGenerator: tokenGenerator, + tokenSerializer: tokenSerializer, + tokenStore: tokenStore, + htmlEncoder: new CommonTestEncoder()); + } + + private HttpContext GetHttpContext() + { + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity("some-auth")); + return httpContext; + } + +#if DNX451 + + private Antiforgery GetAntiforgery(AntiforgeryMockContext context) + { + return GetAntiforgery( + context.Options, + context.TokenGenerator?.Object, + context.TokenSerializer?.Object, + context.TokenStore?.Object); } private Mock GetTokenStore( @@ -495,6 +465,49 @@ namespace Microsoft.AspNet.Antiforgery return mockSerializer; } + private AntiforgeryMockContext CreateMockContext( + AntiforgeryOptions options, + bool useOldCookie = false, + bool isOldCookieValid = true) + { + // Arrange + var httpContext = GetHttpContext(); + var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true); + + var mockSerializer = GetTokenSerializer(testTokenSet); + + var mockTokenStore = GetTokenStore(httpContext, testTokenSet); + + var mockGenerator = new Mock(MockBehavior.Strict); + mockGenerator + .Setup(o => o.GenerateFormToken( + httpContext, + useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken)) + .Returns(testTokenSet.FormToken); + + mockGenerator + .Setup(o => o.GenerateCookieToken()) + .Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken); + + mockGenerator + .Setup(o => o.IsCookieTokenValid(testTokenSet.OldCookieToken)) + .Returns(isOldCookieValid); + + mockGenerator + .Setup(o => o.IsCookieTokenValid(testTokenSet.NewCookieToken)) + .Returns(!isOldCookieValid); + + return new AntiforgeryMockContext() + { + Options = options, + HttpContext = httpContext, + TokenGenerator = mockGenerator, + TokenSerializer = mockSerializer, + TokenStore = mockTokenStore, + TestTokenSet = testTokenSet + }; + } + private TestTokenSet GetTokenSet(bool isOldCookieTokenSessionToken = true, bool isNewCookieSessionToken = true) { return new TestTokenSet() @@ -505,79 +518,46 @@ namespace Microsoft.AspNet.Antiforgery }; } - private AntiforgeryWorkerContext GetAntiforgeryWorkerContext( - AntiforgeryOptions config, - bool useOldCookie = false, - bool isOldCookieValid = true) - { - // Arrange - var mockHttpContext = GetHttpContext(); - var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true); - - var mockSerializer = GetTokenSerializer(testTokenSet); - - var mockTokenStore = GetTokenStore(mockHttpContext.Object, testTokenSet); - - var mockGenerator = new Mock(MockBehavior.Strict); - mockGenerator - .Setup(o => o.GenerateFormToken( - mockHttpContext.Object, - mockHttpContext.Object.User.Identity as ClaimsIdentity, - useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken)) - .Returns(testTokenSet.FormToken); - - mockGenerator - .Setup(o => o.GenerateCookieToken()) - .Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken); - - var mockValidator = new Mock(MockBehavior.Strict); - mockValidator - .Setup(o => o.IsCookieTokenValid(testTokenSet.OldCookieToken)) - .Returns(isOldCookieValid); - - mockValidator - .Setup(o => o.IsCookieTokenValid(testTokenSet.NewCookieToken)) - .Returns(!isOldCookieValid); - - return new AntiforgeryWorkerContext() - { - Options = config, - HttpContext = mockHttpContext, - TokenGenerator = mockGenerator, - TokenValidator = mockValidator, - TokenSerializer = mockSerializer, - TokenStore = mockTokenStore, - TestTokenSet = testTokenSet - }; - } - private class TestTokenSet { public AntiforgeryToken FormToken { get; set; } + public string FormTokenString { get; set; } + public AntiforgeryToken OldCookieToken { get; set; } + public string OldCookieTokenString { get; set; } + public AntiforgeryToken NewCookieToken { get; set; } + public string NewCookieTokenString { get; set; } } - private class AntiforgeryWorkerContext + private class AntiforgeryMockContext { public AntiforgeryOptions Options { get; set; } public TestTokenSet TestTokenSet { get; set; } - public Mock HttpContext { get; set; } + public HttpContext HttpContext { get; set; } public Mock TokenGenerator { get; set; } - public Mock TokenValidator { get; set; } - public Mock TokenStore { get; set; } public Mock TokenSerializer { get; set; } } - } -} -#endif \ No newline at end of file +#endif + + private class TestOptionsManager : IOptions + { + public AntiforgeryOptions Options { get; set; } = new AntiforgeryOptions(); + + public AntiforgeryOptions GetNamedOptions(string name) + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Antiforgery.Test/TokenProviderTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenGeneratorTest.cs similarity index 72% rename from test/Microsoft.AspNet.Antiforgery.Test/TokenProviderTest.cs rename to test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenGeneratorTest.cs index ea3955511f..8cf4a67cbb 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/TokenProviderTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenGeneratorTest.cs @@ -4,7 +4,7 @@ using System; using System.Security.Claims; using System.Security.Cryptography; -using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Internal; #if DNX451 using Moq; #endif @@ -12,44 +12,40 @@ using Xunit; namespace Microsoft.AspNet.Antiforgery { - public class TokenProviderTest + public class AntiforgeryTokenGeneratorProviderTest { [Fact] public void GenerateCookieToken() { // Arrange - var tokenProvider = new AntiforgeryTokenProvider( - config: null, - claimUidExtractor: null, - additionalDataProvider: null); + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), + claimUidExtractor: null, + additionalDataProvider: null); // Act - var retVal = tokenProvider.GenerateCookieToken(); + var token = tokenProvider.GenerateCookieToken(); // Assert - Assert.NotNull(retVal); + Assert.NotNull(token); } -#if DNX451 [Fact] public void GenerateFormToken_AnonymousUser() { // Arrange var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; - var httpContext = new Mock().Object; - var mockIdentity = new Mock(); - mockIdentity.Setup(o => o.IsAuthenticated) - .Returns(false); + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + Assert.False(httpContext.User.Identity.IsAuthenticated); - var config = new AntiforgeryOptions(); - - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken); + var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); @@ -60,6 +56,8 @@ namespace Microsoft.AspNet.Antiforgery Assert.Empty(fieldToken.AdditionalData); } +#if DNX451 + [Fact] public void GenerateFormToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData() { @@ -69,20 +67,20 @@ namespace Microsoft.AspNet.Antiforgery IsSessionToken = true }; - var httpContext = new Mock().Object; - ClaimsIdentity identity = new MyAuthenticatedIdentityWithoutUsername(); - var config = new AntiforgeryOptions(); - IClaimUidExtractor claimUidExtractor = new Mock().Object; + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername()); - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var options = new AntiforgeryOptions(); + var claimUidExtractor = new Mock().Object; + + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: claimUidExtractor, additionalDataProvider: null); // Act & assert - var ex = - Assert.Throws( - () => tokenProvider.GenerateFormToken(httpContext, identity, cookieToken)); + var exception = Assert.Throws( + () => tokenProvider.GenerateFormToken(httpContext, cookieToken)); Assert.Equal( "The provided identity of type " + $"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " + @@ -91,7 +89,7 @@ namespace Microsoft.AspNet.Antiforgery "If it is not possible to provide a unique Name for this identity, " + "consider extending IAdditionalDataProvider by overriding the DefaultAdditionalDataProvider " + "or a custom type that can provide some form of unique identifier for the current user.", - ex.Message); + exception.Message); } [Fact] @@ -99,23 +97,23 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; - var httpContext = new Mock().Object; - ClaimsIdentity identity = new MyAuthenticatedIdentityWithoutUsername(); + + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new MyAuthenticatedIdentityWithoutUsername()); var mockAdditionalDataProvider = new Mock(); mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext)) .Returns("additional-data"); - var config = new AntiforgeryOptions(); - IClaimUidExtractor claimUidExtractor = new Mock().Object; + var claimUidExtractor = new Mock().Object; - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: claimUidExtractor, additionalDataProvider: mockAdditionalDataProvider.Object); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, identity, cookieToken); + var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); @@ -131,10 +129,10 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; - var httpContext = new Mock().Object; - var identity = GetAuthenticatedIdentity("some-identity"); - var config = new AntiforgeryOptions(); + var identity = GetAuthenticatedIdentity("some-identity"); + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(identity); byte[] data = new byte[256 / 8]; using (var rng = RandomNumberGenerator.Create()) @@ -148,13 +146,13 @@ namespace Microsoft.AspNet.Antiforgery mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)) .Returns(base64ClaimUId); - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: mockClaimUidExtractor.Object, additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, identity, cookieToken); + var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); @@ -171,23 +169,24 @@ namespace Microsoft.AspNet.Antiforgery // Arrange var cookieToken = new AntiforgeryToken() { IsSessionToken = true }; - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var mockIdentity = new Mock(); mockIdentity.Setup(o => o.IsAuthenticated) .Returns(true); mockIdentity.Setup(o => o.Name) .Returns("my-username"); - var config = new AntiforgeryOptions(); - IClaimUidExtractor claimUidExtractor = new Mock().Object; + httpContext.User = new ClaimsPrincipal(mockIdentity.Object); + + var claimUidExtractor = new Mock().Object; - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: claimUidExtractor, additionalDataProvider: null); // Act - var fieldToken = tokenProvider.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken); + var fieldToken = tokenProvider.GenerateFormToken(httpContext, cookieToken); // Assert Assert.NotNull(fieldToken); @@ -208,8 +207,8 @@ namespace Microsoft.AspNet.Antiforgery IsSessionToken = false }; - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: null); @@ -225,16 +224,16 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange AntiforgeryToken cookieToken = null; - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: null); // Act - bool retVal = tokenProvider.IsCookieTokenValid(cookieToken); + var isValid = tokenProvider.IsCookieTokenValid(cookieToken); // Assert - Assert.False(retVal); + Assert.False(isValid); } [Fact] @@ -246,41 +245,42 @@ namespace Microsoft.AspNet.Antiforgery IsSessionToken = true }; - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: null); // Act - bool retVal = tokenProvider.IsCookieTokenValid(cookieToken); + var isValid = tokenProvider.IsCookieTokenValid(cookieToken); // Assert - Assert.True(retVal); + Assert.True(isValid); } -#if DNX451 + [Fact] public void ValidateTokens_SessionTokenMissing() { // Arrange - var httpContext = new Mock().Object; - ClaimsIdentity identity = new Mock().Object; - AntiforgeryToken sessionToken = null; + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = "my-cookie-name" }; - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(options), claimUidExtractor: null, additionalDataProvider: null); // Act & assert var ex = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, null, fieldtoken)); Assert.Equal(@"The required anti-forgery cookie ""my-cookie-name"" is not present.", ex.Message); } @@ -288,25 +288,25 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_FieldTokenMissing() { // Arrange - var httpContext = new Mock().Object; - ClaimsIdentity identity = new Mock().Object; - var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; - AntiforgeryToken fieldtoken = null; + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); - var config = new AntiforgeryOptions() + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; + + var options = new AntiforgeryOptions() { FormFieldName = "my-form-field-name" }; - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(options), claimUidExtractor: null, additionalDataProvider: null); // Act & assert var ex = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, sessionToken, null)); Assert.Equal(@"The required anti-forgery form field ""my-form-field-name"" is not present.", ex.Message); } @@ -314,26 +314,27 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_FieldAndSessionTokensSwapped() { // Arrange - var httpContext = new Mock().Object; - ClaimsIdentity identity = new Mock().Object; + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = "my-cookie-name", FormFieldName = "my-form-field-name" }; - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(options), claimUidExtractor: null, additionalDataProvider: null); // Act & assert var ex1 = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, fieldtoken, fieldtoken)); + () => tokenProvider.ValidateTokens(httpContext, fieldtoken, fieldtoken)); Assert.Equal( "Validation of the provided anti-forgery token failed. " + @"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", @@ -341,7 +342,7 @@ namespace Microsoft.AspNet.Antiforgery var ex2 = Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, sessionToken)); + () => tokenProvider.ValidateTokens(httpContext, sessionToken, sessionToken)); Assert.Equal( "Validation of the provided anti-forgery token failed. " + @"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.", @@ -352,23 +353,27 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_FieldAndSessionTokensHaveDifferentSecurityKeys() { // Arrange - var httpContext = new Mock().Object; - ClaimsIdentity identity = new Mock().Object; + var httpContext = new DefaultHttpContext(); + httpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { IsSessionToken = false }; - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: null); - // Act & assert - var ex = - Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); - Assert.Equal(@"The anti-forgery cookie token and form field token do not match.", ex.Message); + // Act & Assert + var exception = Assert.Throws( + () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + Assert.Equal( + @"The anti-forgery cookie token and form field token do not match.", + exception.Message); } +#if DNX451 + [Theory] [InlineData("the-user", "the-other-user")] [InlineData("http://example.com/uri-casing", "http://example.com/URI-casing")] @@ -376,8 +381,10 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_UsernameMismatch(string identityUsername, string embeddedUsername) { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = GetAuthenticatedIdentity(identityUsername); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -390,26 +397,28 @@ namespace Microsoft.AspNet.Antiforgery mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)) .Returns((string)null); - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: mockClaimUidExtractor.Object, additionalDataProvider: null); - // Act & assert - var ex = - Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); + // Act & Assert + var exception = Assert.Throws( + () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); Assert.Equal( @"The provided anti-forgery token was meant for user """ + embeddedUsername + - @""", but the current user is """ + identityUsername + @""".", ex.Message); + @""", but the current user is """ + identityUsername + @""".", + exception.Message); } [Fact] public void ValidateTokens_ClaimUidMismatch() { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = GetAuthenticatedIdentity("the-user"); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -423,26 +432,27 @@ namespace Microsoft.AspNet.Antiforgery mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)) .Returns(Convert.ToBase64String(differentToken.GetData())); - var tokenProvider = new AntiforgeryTokenProvider( - config: null, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: mockClaimUidExtractor.Object, additionalDataProvider: null); // Act & assert - var ex = - Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); + var exception = Assert.Throws( + () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); Assert.Equal( @"The provided anti-forgery token was meant for a different claims-based user than the current user.", - ex.Message); + exception.Message); } [Fact] public void ValidateTokens_AdditionalDataRejected() { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = new ClaimsIdentity(); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -456,25 +466,25 @@ namespace Microsoft.AspNet.Antiforgery mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")) .Returns(false); - var config = new AntiforgeryOptions(); - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: mockAdditionalDataProvider.Object); // Act & assert - var ex = - Assert.Throws( - () => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken)); - Assert.Equal(@"The provided anti-forgery token failed a custom data check.", ex.Message); + var exception = Assert.Throws( + () => tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken)); + Assert.Equal(@"The provided anti-forgery token failed a custom data check.", exception.Message); } [Fact] public void ValidateTokens_Success_AnonymousUser() { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = new ClaimsIdentity(); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -488,14 +498,13 @@ namespace Microsoft.AspNet.Antiforgery mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")) .Returns(true); - var config = new AntiforgeryOptions(); - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: null, additionalDataProvider: mockAdditionalDataProvider.Object); // Act - tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! @@ -505,8 +514,10 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_Success_AuthenticatedUserWithUsername() { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = GetAuthenticatedIdentity("the-user"); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -520,14 +531,13 @@ namespace Microsoft.AspNet.Antiforgery mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data")) .Returns(true); - var config = new AntiforgeryOptions(); - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: new Mock().Object, additionalDataProvider: mockAdditionalDataProvider.Object); // Act - tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! @@ -537,8 +547,10 @@ namespace Microsoft.AspNet.Antiforgery public void ValidateTokens_Success_ClaimsBasedUser() { // Arrange - var httpContext = new Mock().Object; + var httpContext = new DefaultHttpContext(); var identity = GetAuthenticatedIdentity("the-user"); + httpContext.User = new ClaimsPrincipal(identity); + var sessionToken = new AntiforgeryToken() { IsSessionToken = true }; var fieldtoken = new AntiforgeryToken() { @@ -551,15 +563,13 @@ namespace Microsoft.AspNet.Antiforgery mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity)) .Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData())); - var config = new AntiforgeryOptions(); - - var tokenProvider = new AntiforgeryTokenProvider( - config: config, + var tokenProvider = new AntiforgeryTokenGenerator( + optionsAccessor: new TestOptionsManager(), claimUidExtractor: mockClaimUidExtractor.Object, additionalDataProvider: null); // Act - tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken); + tokenProvider.ValidateTokens(httpContext, sessionToken, fieldtoken); // Assert // Nothing to assert - if we got this far, success! diff --git a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenSerializerTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenSerializerTest.cs index dae7de6a17..31fc024721 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenSerializerTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenSerializerTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Antiforgery { public class AntiforgeryTokenSerializerTest { - private static readonly Mock _dataProtector = GetDataProtector(); + private static readonly Mock _dataProtector = GetDataProtector(); private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 }); private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C }); private const byte _salt = 0x05; @@ -138,7 +138,7 @@ namespace Microsoft.AspNet.Antiforgery _dataProtector.Verify(); } - private static Mock GetDataProtector() + private static Mock GetDataProtector() { var mockCryptoSystem = new Mock(); mockCryptoSystem.Setup(o => o.Protect(It.IsAny())) @@ -147,7 +147,12 @@ namespace Microsoft.AspNet.Antiforgery mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny())) .Returns(UnProtect) .Verifiable(); - return mockCryptoSystem; + + var provider = new Mock(); + provider + .Setup(p => p.CreateProtector(It.IsAny())) + .Returns(mockCryptoSystem.Object); + return provider; } private static byte[] Protect(byte[] data) diff --git a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenStoreTest.cs b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenStoreTest.cs index b8e45bc0e1..1753b8ce0e 100644 --- a/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenStoreTest.cs +++ b/test/Microsoft.AspNet.Antiforgery.Test/AntiforgeryTokenStoreTest.cs @@ -32,14 +32,14 @@ namespace Microsoft.AspNet.Antiforgery var contextAccessor = new AntiforgeryContextAccessor(); mockHttpContext.SetupGet(o => o.RequestServices) .Returns(GetServiceProvider(contextAccessor)); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = _cookieName }; var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: null); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: null); // Act var token = tokenStore.GetCookieToken(mockHttpContext.Object); @@ -67,14 +67,14 @@ namespace Microsoft.AspNet.Antiforgery // add a cookie explicitly. var cookie = new AntiforgeryToken(); contextAccessor.Value = new AntiforgeryContext() { CookieToken = cookie }; - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = _cookieName }; var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: null); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: null); // Act var token = tokenStore.GetCookieToken(mockHttpContext.Object); @@ -89,14 +89,14 @@ namespace Microsoft.AspNet.Antiforgery // Arrange var mockHttpContext = GetMockHttpContext(_cookieName, string.Empty); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = _cookieName }; var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: null); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: null); // Act var token = tokenStore.GetCookieToken(mockHttpContext); @@ -110,10 +110,6 @@ namespace Microsoft.AspNet.Antiforgery { // Arrange var mockHttpContext = GetMockHttpContext(_cookieName, "invalid-value"); - var config = new AntiforgeryOptions() - { - CookieName = _cookieName - }; var expectedException = new InvalidOperationException("some exception"); var mockSerializer = new Mock(); @@ -121,9 +117,14 @@ namespace Microsoft.AspNet.Antiforgery .Setup(o => o.Deserialize("invalid-value")) .Throws(expectedException); + var options = new AntiforgeryOptions() + { + CookieName = _cookieName + }; + var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: mockSerializer.Object); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: mockSerializer.Object); // Act & assert var ex = Assert.Throws(() => tokenStore.GetCookieToken(mockHttpContext)); @@ -137,19 +138,19 @@ namespace Microsoft.AspNet.Antiforgery var expectedToken = new AntiforgeryToken(); var mockHttpContext = GetMockHttpContext(_cookieName, "valid-value"); - var config = new AntiforgeryOptions() - { - CookieName = _cookieName - }; - var mockSerializer = new Mock(); mockSerializer .Setup(o => o.Deserialize("valid-value")) .Returns(expectedToken); + var options = new AntiforgeryOptions() + { + CookieName = _cookieName + }; + var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: mockSerializer.Object); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: mockSerializer.Object); // Act AntiforgeryToken retVal = tokenStore.GetCookieToken(mockHttpContext); @@ -170,14 +171,15 @@ namespace Microsoft.AspNet.Antiforgery .Returns(Task.FromResult(formCollection.Object)); mockHttpContext.Setup(o => o.Request) .Returns(requestContext.Object); - var config = new AntiforgeryOptions() + + var options = new AntiforgeryOptions() { - FormFieldName = "form-field-name" + FormFieldName = "form-field-name", }; var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: null); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: null); // Act var token = await tokenStore.GetFormTokenAsync(mockHttpContext.Object); @@ -201,26 +203,24 @@ namespace Microsoft.AspNet.Antiforgery mockHttpContext.Setup(o => o.Request) .Returns(requestContext.Object); - var config = new AntiforgeryOptions() - { - FormFieldName = "form-field-name" - }; - var expectedException = new InvalidOperationException("some exception"); var mockSerializer = new Mock(); mockSerializer.Setup(o => o.Deserialize("invalid-value")) .Throws(expectedException); + var options = new AntiforgeryOptions() + { + FormFieldName = "form-field-name", + }; + var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: mockSerializer.Object); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: mockSerializer.Object); // Act & assert - var ex = - await - Assert.ThrowsAsync( + var exception = await Assert.ThrowsAsync( async () => await tokenStore.GetFormTokenAsync(mockHttpContext.Object)); - Assert.Same(expectedException, ex); + Assert.Same(expectedException, exception); } [Fact] @@ -239,18 +239,18 @@ namespace Microsoft.AspNet.Antiforgery mockHttpContext.Setup(o => o.Request) .Returns(requestContext.Object); - var config = new AntiforgeryOptions() - { - FormFieldName = "form-field-name" - }; - var mockSerializer = new Mock(); mockSerializer.Setup(o => o.Deserialize("valid-value")) .Returns(expectedToken); + var options = new AntiforgeryOptions() + { + FormFieldName = "form-field-name", + }; + var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: mockSerializer.Object); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: mockSerializer.Object); // Act var retVal = await tokenStore.GetFormTokenAsync(mockHttpContext.Object); @@ -283,15 +283,15 @@ namespace Microsoft.AspNet.Antiforgery mockSerializer.Setup(o => o.Serialize(token)) .Returns("serialized-value"); - var config = new AntiforgeryOptions() + var options = new AntiforgeryOptions() { CookieName = _cookieName, RequireSSL = requireSsl }; var tokenStore = new AntiforgeryTokenStore( - config: config, - serializer: mockSerializer.Object); + optionsAccessor: new TestOptionsManager(options), + tokenSerializer: mockSerializer.Object); // Act tokenStore.SaveCookieToken(mockHttpContext.Object, token); diff --git a/test/Microsoft.AspNet.Antiforgery.Test/TestOptionsManager.cs b/test/Microsoft.AspNet.Antiforgery.Test/TestOptionsManager.cs new file mode 100644 index 0000000000..2a74d9764e --- /dev/null +++ b/test/Microsoft.AspNet.Antiforgery.Test/TestOptionsManager.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Antiforgery +{ + public class TestOptionsManager : IOptions + { + public TestOptionsManager() + { + } + + public TestOptionsManager(AntiforgeryOptions options) + { + Options = options; + } + + public AntiforgeryOptions Options { get; set; } = new AntiforgeryOptions(); + + public AntiforgeryOptions GetNamedOptions(string name) + { + throw new NotImplementedException(); + } + } +}