From 314b0d8175a6600b174f277488a4eb95ffddc7c7 Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Mon, 31 Mar 2014 12:58:40 -0700 Subject: [PATCH 1/7] AntiForgery Interfaces. Also contains some code ported over. This Commit is only for review purpose. --- .../AntiForgery/AntiForgery.cs | 111 +++++++++++ .../AntiForgery/AntiForgeryConfig.cs | 100 ++++++++++ .../AntiForgery/AntiForgeryConfigWrapper.cs | 40 ++++ .../AntiForgery/AntiForgeryToken.cs | 62 ++++++ .../AntiForgery/AntiForgeryTokenSerializer.cs | 28 +++ .../AntiForgery/AntiForgeryTokenStore.cs | 61 ++++++ .../AntiForgery/AntiForgeryWorker.cs | 187 ++++++++++++++++++ .../AntiForgery/BinaryBlob.cs | 116 +++++++++++ .../AntiForgery/DefaultClaimUidExtractor.cs | 82 ++++++++ .../IAntiForgeryAdditionalDataProvider.cs | 37 ++++ .../AntiForgery/IAntiForgeryConfig.cs | 26 +++ .../IAntiForgeryTokenSerializer.cs | 11 ++ .../AntiForgery/IClaimUidExtractor.cs | 12 ++ .../AntiForgery/ITokenGenerator.cs | 17 ++ .../AntiForgery/ITokenStore.cs | 13 ++ .../AntiForgery/ITokenValidator.cs | 16 ++ .../AntiForgery/TokenValidator.cs | 154 +++++++++++++++ src/Microsoft.AspNet.Mvc.Core/project.json | 2 + 18 files changed, 1075 insertions(+) create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs new file mode 100644 index 0000000000..9a1ca61b48 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides access to the anti-forgery system, which provides protection against + /// Cross-site Request Forgery (XSRF, also called CSRF) attacks. + /// + public class AntiForgery + { + private static readonly AntiForgeryWorker _worker = CreateSingletonAntiForgeryWorker(); + private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1" ; + + private static AntiForgeryWorker CreateSingletonAntiForgeryWorker() + { + // initialize the dependency chain + IAntiForgeryConfig config = new AntiForgeryConfigWrapper(); + + // TODO populate the IDataProtectionProvider using DI. + IDataProtectionProvider dataProtectionProvider = DataProtectionProvider.CreateNew(); + IAntiForgeryTokenSerializer serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); + ITokenStore tokenStore = new AntiForgeryTokenStore(config, serializer); + IClaimUidExtractor claimUidExtractor = new DefaultClaimUidExtractor(config); + var tokenProvider = new TokenValidator(config, claimUidExtractor); + + return new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider); + } + + /// + /// Generates an anti-forgery token for this request. This token can + /// be validated by calling the Validate() method. + /// + /// An HTML string corresponding to an <input type="hidden"> + /// element. This element should be put inside a <form>. + /// + /// This method has a side effect: it may set a response cookie. + /// + public static HtmlString GetHtml(HttpContext context) + { + TagBuilder retVal = _worker.GetFormInputElement(context); + return retVal.ToHtmlString(TagRenderMode.SelfClosing); + } + + /// + /// Generates an anti-forgery token pair (cookie and form token) for this request. + /// This method is similar to GetHtml(), but this method gives the caller control + /// over how to persist the returned values. To validate these tokens, call the + /// appropriate overload of Validate. + /// + /// The anti-forgery token - if any - that already existed + /// for this request. May be null. The anti-forgery system will try to reuse this cookie + /// value when generating a matching form token. + /// Will contain a new cookie value if the old cookie token + /// was null or invalid. If this value is non-null when the method completes, the caller + /// must persist this value in the form of a response cookie, and the existing cookie value + /// should be discarded. If this value is null when the method completes, the existing + /// cookie value was valid and needn't be modified. + /// The value that should be stored in the <form>. The caller + /// should take care not to accidentally swap the cookie and form tokens. + /// + /// Unlike the GetHtml() method, this method has no side effect. The caller + /// is responsible for setting the response cookie and injecting the returned + /// form token as appropriate. + /// + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", + Justification = "Method is intended for advanced audiences.")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", + Justification = "Method is intended for advanced audiences.")] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static void GetTokens(HttpContext context, string oldCookieToken, out string newCookieToken, out string formToken) + { + _worker.GetTokens(context, oldCookieToken, out newCookieToken, out formToken); + } + + /// + /// Validates an anti-forgery token that was supplied for this request. + /// The anti-forgery token may be generated by calling GetHtml(). + /// + /// + /// Throws an HttpAntiForgeryException if validation fails. + /// + public static void Validate(HttpContext context) + { + _worker.Validate(context); + } + + /// + /// Validates an anti-forgery token pair that was generated by the GetTokens method. + /// + /// The token that was supplied in the request cookie. + /// The token that was supplied in the request form body. + /// + /// Throws an HttpAntiForgeryException if validation fails. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static void Validate(HttpContext context, string cookieToken, string formToken) + { + _worker.Validate(context, cookieToken, formToken); + } + } +} + diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs new file mode 100644 index 0000000000..822183f491 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.ComponentModel; +using System.Text; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides programmatic configuration for the anti-forgery token system. + /// + public static class AntiForgeryConfig + { + internal const string AntiForgeryTokenFieldName = "__RequestVerificationToken"; + + private static string _cookieName; + private static string _uniqueClaimTypeIdentifier; + + /// + /// Specifies an object that can provide additional data to put into all + /// generated tokens and that can validate additional data in incoming + /// tokens. + /// + public static IAntiForgeryAdditionalDataProvider AdditionalDataProvider + { + get; + set; + } + + /// + /// Specifies the name of the cookie that is used by the anti-forgery + /// system. + /// + /// + /// If an explicit name is not provided, the system will automatically + /// generate a name. + /// + public static string CookieName + { + get + { + if (_cookieName == null) + { + _cookieName = GetAntiForgeryCookieName(); + } + return _cookieName; + } + set + { + _cookieName = value; + } + } + + /// + /// 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 static bool RequireSsl + { + get; + set; + } + + /// + /// Specifies whether to suppress the generation of X-Frame-Options header + /// which is used to prevent ClickJacking. By default, the X-Frame-Options + /// 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 static bool SuppressXFrameOptionsHeader + { + get; + set; + } + + /// + /// Specifies whether the anti-forgery system should skip checking + /// for conditions that might indicate misuse of the system. Please + /// use caution when setting this switch, as improper use could open + /// security holes in the application. + /// + /// + /// Setting this switch will disable several checks, including: + /// - Identity.IsAuthenticated = true without Identity.Name being set + /// - special-casing claims-based identities + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool SuppressIdentityHeuristicChecks + { + get; + set; + } + + // TODO: Replace the stub. + private static string GetAntiForgeryCookieName() + { + return AntiForgeryTokenFieldName; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs new file mode 100644 index 0000000000..bbcd90fee4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc +{ + internal sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig + { + public IAntiForgeryAdditionalDataProvider AdditionalDataProvider + { + get + { + return AntiForgeryConfig.AdditionalDataProvider; + } + } + + public string CookieName + { + get { return AntiForgeryConfig.CookieName; } + } + + public string FormFieldName + { + get { return AntiForgeryConfig.AntiForgeryTokenFieldName; } + } + + public bool RequireSSL + { + get { return AntiForgeryConfig.RequireSsl; } + } + + public bool SuppressIdentityHeuristicChecks + { + get { return AntiForgeryConfig.SuppressIdentityHeuristicChecks; } + } + + public bool SuppressXFrameOptionsHeader + { + get { return AntiForgeryConfig.SuppressXFrameOptionsHeader; } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs new file mode 100644 index 0000000000..0785cd2a6d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc +{ + public class AntiForgeryToken + { + internal const int SecurityTokenBitLength = 128; + internal const int ClaimUidBitLength = 256; + + private string _additionalData; + private BinaryBlob _securityToken; + private string _username; + + public string AdditionalData + { + get + { + return _additionalData ?? String.Empty; + } + set + { + _additionalData = value; + } + } + + public BinaryBlob ClaimUid { get; set; } + + public bool IsSessionToken { get; set; } + + public BinaryBlob SecurityToken + { + get + { + if (_securityToken == null) + { + _securityToken = new BinaryBlob(SecurityTokenBitLength); + } + return _securityToken; + } + set + { + _securityToken = value; + } + } + + public string Username + { + get + { + return _username ?? String.Empty; + } + set + { + _username = value; + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs new file mode 100644 index 0000000000..1d2fbbd4c5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Mvc +{ + // TODO: Stub :Replace with actual implementation + internal sealed class AntiForgeryTokenSerializer : IAntiForgeryTokenSerializer + { + private readonly IDataProtector _cryptoSystem; + + internal AntiForgeryTokenSerializer(IDataProtector cryptoSystem) + { + _cryptoSystem = cryptoSystem; + } + + public AntiForgeryToken Deserialize(string serializedToken) + { + throw new NotImplementedException(); + } + + public string Serialize(AntiForgeryToken token) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs new file mode 100644 index 0000000000..2822a15cd1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Mvc +{ + // Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form + internal sealed class AntiForgeryTokenStore : ITokenStore + { + private readonly IAntiForgeryConfig _config; + private readonly IAntiForgeryTokenSerializer _serializer; + + internal AntiForgeryTokenStore(IAntiForgeryConfig config, IAntiForgeryTokenSerializer serializer) + { + _config = config; + _serializer = serializer; + } + + public AntiForgeryToken GetCookieToken(HttpContext httpContext) + { + var cookie = httpContext.Request.Cookies[_config.CookieName]; + if (String.IsNullOrEmpty(cookie)) + { + // did not exist + return null; + } + + return _serializer.Deserialize(cookie); + } + + public AntiForgeryToken GetFormToken(HttpContext httpContext) + { + // TODO: Add proper exception handling. + string value = httpContext.Request.GetFormAsync().Result[_config.FormFieldName]; + if (String.IsNullOrEmpty(value)) + { + // did not exist + return null; + } + + return _serializer.Deserialize(value); + } + + public void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token) + { + string serializedToken = _serializer.Serialize(token); + CookieOptions options = new CookieOptions() { HttpOnly = true }; + + // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default + // value of newCookie.Secure is automatically populated from the + // config element. + if (_config.RequireSSL) + { + options.Secure = true; + } + + httpContext.Response.Cookies.Append(_config.CookieName, serializedToken, options); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs new file mode 100644 index 0000000000..f61a9f8d0d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Rendering; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; + +namespace Microsoft.AspNet.Mvc +{ + internal sealed class AntiForgeryWorker + { + private readonly IAntiForgeryConfig _config; + private readonly IAntiForgeryTokenSerializer _serializer; + private readonly ITokenStore _tokenStore; + private readonly ITokenValidator _validator; + private readonly ITokenGenerator _generator; + + internal AntiForgeryWorker(IAntiForgeryTokenSerializer serializer, IAntiForgeryConfig config, ITokenStore tokenStore, ITokenGenerator generator, ITokenValidator validator) + { + _serializer = serializer; + _config = config; + _tokenStore = tokenStore; + _generator = generator; + _validator = validator; + } + + private void CheckSSLConfig(HttpContext httpContext) + { + if (_config.RequireSSL && !httpContext.Request.IsSecure) + { + throw new InvalidOperationException("AntiForgeryWorker_RequireSSL"); + } + } + + private AntiForgeryToken DeserializeToken(string serializedToken) + { + return (!String.IsNullOrEmpty(serializedToken)) + ? _serializer.Deserialize(serializedToken) + : null; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Caller will just regenerate token in case of failure.")] + private AntiForgeryToken DeserializeTokenNoThrow(string serializedToken) + { + try + { + return DeserializeToken(serializedToken); + } + catch + { + // ignore failures since we'll just generate a new token + return null; + } + } + + private static IIdentity ExtractIdentity(HttpContext httpContext) + { + if (httpContext != null) + { + var user = httpContext.User; + if (user != null) + { + return user.Identity; + } + } + + return null; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Caller will just regenerate token in case of failure.")] + private AntiForgeryToken GetCookieTokenNoThrow(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 TagBuilder GetFormInputElement(HttpContext httpContext) + { + CheckSSLConfig(httpContext); + + AntiForgeryToken oldCookieToken = GetCookieTokenNoThrow(httpContext); + AntiForgeryToken newCookieToken, formToken; + GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken); + + if (newCookieToken != null) + { + // If a new cookie was generated, persist it. + _tokenStore.SaveCookieToken(httpContext, newCookieToken); + } + + 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.Add("X-Frame-Options", new[] { "SAMEORIGIN" }); + } + + // + var retVal = new TagBuilder("input"); + retVal.Attributes["type"] = "hidden"; + retVal.Attributes["name"] = _config.FormFieldName; + retVal.Attributes["value"] = _serializer.Serialize(formToken); + return retVal; + } + + // [ 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 void GetTokens(HttpContext httpContext, string serializedOldCookieToken, out string serializedNewCookieToken, out string serializedFormToken) + { + CheckSSLConfig(httpContext); + + AntiForgeryToken oldCookieToken = DeserializeTokenNoThrow(serializedOldCookieToken); + AntiForgeryToken newCookieToken, formToken; + GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken); + + serializedNewCookieToken = Serialize(newCookieToken); + serializedFormToken = Serialize(formToken); + } + + private void GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken, out AntiForgeryToken newCookieToken, out AntiForgeryToken formToken) + { + newCookieToken = null; + if (!_validator.IsCookieTokenValid(oldCookieToken)) + { + // Need to make sure we're always operating with a good cookie token. + oldCookieToken = newCookieToken = _generator.GenerateCookieToken(); + } + + Contract.Assert(_validator.IsCookieTokenValid(oldCookieToken)); + formToken = _generator.GenerateFormToken(httpContext, ExtractIdentity(httpContext), oldCookieToken); + } + + 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 void Validate(HttpContext httpContext) + { + CheckSSLConfig(httpContext); + + // Extract cookie & form tokens + AntiForgeryToken cookieToken = _tokenStore.GetCookieToken(httpContext); + AntiForgeryToken formToken = _tokenStore.GetFormToken(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(HttpContext httpContext, string cookieToken, string formToken) + { + CheckSSLConfig(httpContext); + + // Extract cookie & form tokens + AntiForgeryToken deserializedCookieToken = DeserializeToken(cookieToken); + AntiForgeryToken deserializedFormToken = DeserializeToken(formToken); + + // Validate + _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), deserializedCookieToken, deserializedFormToken); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs new file mode 100644 index 0000000000..970f7cd0cd --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Mvc +{ + // Represents a binary blob (token) that contains random data. + // Useful for binary data inside a serialized stream. + [DebuggerDisplay("{DebuggerString}")] + public sealed class BinaryBlob : IEquatable + { + private readonly byte[] _data; + + // Generates a new token using a specified bit length. + public BinaryBlob(int bitLength) + : this(bitLength, GenerateNewToken(bitLength)) + { + } + + // Generates a token using an existing binary value. + public BinaryBlob(int bitLength, byte[] data) + { + if (bitLength < 32 || bitLength % 8 != 0) + { + throw new ArgumentOutOfRangeException("bitLength"); + } + if (data == null || data.Length != bitLength / 8) + { + throw new ArgumentOutOfRangeException("data"); + } + + _data = data; + } + + public int BitLength + { + get + { + return checked(_data.Length * 8); + } + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by debugger.")] + private string DebuggerString + { + get + { + StringBuilder sb = new StringBuilder("0x", 2 + (_data.Length * 2)); + for (int i = 0; i < _data.Length; i++) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", _data[i]); + } + return sb.ToString(); + } + } + + public override bool Equals(object obj) + { + return Equals(obj as BinaryBlob); + } + + public bool Equals(BinaryBlob other) + { + if (other == null) + { + return false; + } + + Contract.Assert(this._data.Length == other._data.Length); + return AreByteArraysEqual(this._data, other._data); + } + + public byte[] GetData() + { + return _data; + } + + public override int GetHashCode() + { + // Since data should contain uniformly-distributed entropy, the + // first 32 bits can serve as the hash code. + Contract.Assert(_data != null && _data.Length >= (32 / 8)); + return BitConverter.ToInt32(_data, 0); + } + + private static byte[] GenerateNewToken(int bitLength) + { + byte[] data = new byte[bitLength / 8]; + CryptRand.FillBuffer(new ArraySegment(data)); + return data; + } + + private static bool AreByteArraysEqual(byte[] a, byte[] b) + { + if (a == null || b == null || a.Length != b.Length) + { + return false; + } + + bool areEqual = true; + for (int i = 0; i < a.Length; i++) + { + areEqual &= (a[i] == b[i]); + } + return areEqual; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs new file mode 100644 index 0000000000..8d49e0bf65 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Security.Principal; + +namespace Microsoft.AspNet.Mvc +{ + // Can extract unique identifers for a claims-based identity + public class DefaultClaimUidExtractor : IClaimUidExtractor + { + private const string NameIdentifierClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; + + private readonly ClaimsIdentityConverter _claimsIdentityConverter; + private readonly IAntiForgeryConfig _config; + + public DefaultClaimUidExtractor(IAntiForgeryConfig config) + { + _config = config; + } + + public byte[] ExtractClaimUid(IIdentity identity) + { + if (identity == null || !identity.IsAuthenticated || _config.SuppressIdentityHeuristicChecks) + { + // Skip anonymous users + // Skip when claims-based checks are disabled + return null; + } + + var claimsIdentity = identity as ClaimsIdentity; + if (claimsIdentity == null) + { + // not a claims-based identity + return null; + } + + string[] uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity); + byte[] claimUidBytes = ComputeSHA256(uniqueIdentifierParameters); + return claimUidBytes; + } + + private static string[] GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity) + { + // TODO: We need to select a single claim based on the Authentication Type of the claim. + var claims = claimsIdentity.Claims; + + // TODO: Need to check with vittorio for acs. + // For a correctly configured ACS consumer, this tuple will uniquely + // identify a user of the application. We assume that a well-behaved + // identity provider will never assign the same name identifier to multiple + // users within its security realm, and we assume that ACS has been + // configured so that each identity provider has a unique 'identityProvider' + // claim. + // By default, we look for 'nameIdentifier' claim. + Claim nameIdentifierClaim = claims.SingleOrDefault(claim => String.Equals(NameIdentifierClaimType, claim.ValueType, StringComparison.Ordinal)); + if (nameIdentifierClaim == null || String.IsNullOrEmpty(nameIdentifierClaim.Value)) + { + // TODO: Update the exception message. + throw new InvalidOperationException("DefaultClaimsNotPresent"); + } + + return new string[] + { + NameIdentifierClaimType, + nameIdentifierClaim.Value + }; + } + + + private static byte[] ComputeSHA256(IList parameters) + { + // TODO: find the right api to compute the hash. + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs new file mode 100644 index 0000000000..a2ac6ca5d2 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Filters; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Allows providing or validating additional custom data for anti-forgery tokens. + /// For example, the developer could use this to supply a nonce when the token is + /// generated, then he could validate the nonce when the token is validated. + /// + /// + /// The anti-forgery system already embeds the client's username within the + /// generated tokens. This interface provides and consumes supplemental + /// data. If an incoming anti-forgery token contains supplemental data but no + /// additional data provider is configured, the supplemental data will not be + /// validated. + /// + public interface IAntiForgeryAdditionalDataProvider + { + /// + /// Provides additional data to be stored for the anti-forgery tokens generated + /// during this request. + /// + /// Information about the current request. + /// Supplemental data to embed within the anti-forgery token. + string GetAdditionalData(HttpContext context); + + /// + /// Validates additional data that was embedded inside an incoming anti-forgery + /// token. + /// + /// Information about the current request. + /// Supplemental data that was embedded within the token. + /// True if the data is valid; false if the data is invalid. + bool ValidateAdditionalData(HttpContext context, string additionalData); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs new file mode 100644 index 0000000000..d5022ace20 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNet.Mvc.Filters; + +namespace Microsoft.AspNet.Mvc +{ + // Provides configuration information about the anti-forgery system. + public interface IAntiForgeryConfig + { + // Provides additional data to go into the tokens. + IAntiForgeryAdditionalDataProvider AdditionalDataProvider { get; } + + // Name of the cookie to use. + string CookieName { get; } + + // Name of the form field to use. + string FormFieldName { get; } + + // Whether SSL is mandatory for this request. + bool RequireSSL { get; } + + // Skip ClaimsIdentity & related logic. + bool SuppressIdentityHeuristicChecks { get; } + + // Skip X-FRAME-OPTIONS header. + bool SuppressXFrameOptionsHeader { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs new file mode 100644 index 0000000000..1d7ce0e26b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNet.Mvc.Filters; + +namespace Microsoft.AspNet.Mvc +{ + // Abstracts out the serialization process for an anti-forgery token + internal interface IAntiForgeryTokenSerializer + { + AntiForgeryToken Deserialize(string serializedToken); + string Serialize(AntiForgeryToken token); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs new file mode 100644 index 0000000000..695032a6b7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Security.Principal; + +namespace Microsoft.AspNet.Mvc +{ + // Can extract unique identifers for a claims-based identity + public interface IClaimUidExtractor + { + byte[] ExtractClaimUid(IIdentity identity); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs new file mode 100644 index 0000000000..9726f8e40d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs @@ -0,0 +1,17 @@ +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Filters; + +namespace Microsoft.AspNet.Mvc +{ + // Provides configuration information about the anti-forgery system. + internal interface ITokenGenerator + { + // Generates a new random cookie token. + AntiForgeryToken GenerateCookieToken(); + + // Given a cookie token, generates a corresponding form token. + // The incoming cookie token must be valid. + AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs new file mode 100644 index 0000000000..7a73df8cfe --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Filters; + +namespace Microsoft.AspNet.Mvc +{ + // Provides an abstraction around how tokens are persisted and retrieved for a request + internal interface ITokenStore + { + AntiForgeryToken GetCookieToken(HttpContext httpContext); + AntiForgeryToken GetFormToken(HttpContext httpContext); + void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs new file mode 100644 index 0000000000..7c606dd5cd --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNet.Abstractions; +using System.Security.Principal; + +namespace Microsoft.AspNet.Mvc +{ + // Provides an abstraction around something that can validate anti-XSRF tokens + internal interface ITokenValidator + { + // 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, IIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs new file mode 100644 index 0000000000..267dd01461 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Mvc +{ + internal sealed class TokenValidator : ITokenValidator, ITokenGenerator + { + private readonly IClaimUidExtractor _claimUidExtractor; + private readonly IAntiForgeryConfig _config; + + internal TokenValidator(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor) + { + _config = config; + _claimUidExtractor = claimUidExtractor; + } + + public AntiForgeryToken GenerateCookieToken() + { + return new AntiForgeryToken() + { + // SecurityToken will be populated automatically. + IsSessionToken = true + }; + } + + public AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken) + { + Contract.Assert(IsCookieTokenValid(cookieToken)); + + AntiForgeryToken formToken = new AntiForgeryToken() + { + SecurityToken = cookieToken.SecurityToken, + IsSessionToken = false + }; + + bool requireAuthenticatedUserHeuristicChecks = false; + // populate Username and ClaimUid + if (identity != null && identity.IsAuthenticated) + { + if (!_config.SuppressIdentityHeuristicChecks) + { + // If the user is authenticated and heuristic checks are not suppressed, + // then Username, ClaimUid, or AdditionalData must be set. + requireAuthenticatedUserHeuristicChecks = true; + } + + formToken.ClaimUid = GetClaimUid(_claimUidExtractor.ExtractClaimUid(identity)); + if (formToken.ClaimUid == null) + { + formToken.Username = identity.Name; + } + } + + // populate AdditionalData + if (_config.AdditionalDataProvider != null) + { + formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext); + } + + if (requireAuthenticatedUserHeuristicChecks + && String.IsNullOrEmpty(formToken.Username) + && formToken.ClaimUid == null + && String.IsNullOrEmpty(formToken.AdditionalData)) + { + // TODO: Throw properException + // Application says user is authenticated, but we have no identifier for the user. + throw new InvalidOperationException("TokenValidator_AuthenticatedUserWithoutUsername"); + } + + return formToken; + } + + public bool IsCookieTokenValid(AntiForgeryToken cookieToken) + { + return (cookieToken != null && cookieToken.IsSessionToken); + } + + // TODO: Sweep the exceptions. + public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken) + { + // Were the tokens even present at all? + if (sessionToken == null) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateCookieMissingException(_config.CookieName); + } + if (fieldToken == null) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateFormFieldMissingException(_config.FormFieldName); + } + + // Do the tokens have the correct format? + if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateTokensSwappedException(_config.CookieName, _config.FormFieldName); + } + + // Are the security tokens embedded in each incoming token identical? + if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken)) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateSecurityTokenMismatchException(); + } + + // Is the incoming token meant for the current user? + string currentUsername = String.Empty; + BinaryBlob currentClaimUid = null; + + if (identity != null && identity.IsAuthenticated) + { + currentClaimUid = GetClaimUid( _claimUidExtractor.ExtractClaimUid(identity)); + if (currentClaimUid == null) + { + currentUsername = identity.Name ?? String.Empty; + } + } + + // OpenID and other similar authentication schemes use URIs for the username. + // These should be treated as case-sensitive. + bool useCaseSensitiveUsernameComparison = currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase); + + if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername); + } + if (!Equals(fieldToken.ClaimUid, currentClaimUid)) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateClaimUidMismatchException(); + } + + // Is the AdditionalData valid? + if (_config.AdditionalDataProvider != null && !_config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData)) + { + // TODO: Throw properException + //throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException(); + } + } + + private static BinaryBlob GetClaimUid(byte[] claimUidBytes) + { + return new BinaryBlob(256, claimUidBytes); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json index 30481e9172..e0e743114d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/project.json +++ b/src/Microsoft.AspNet.Mvc.Core/project.json @@ -8,6 +8,7 @@ "Common": "", "Microsoft.AspNet.Mvc.ModelBinding": "", "Microsoft.Net.Runtime.Interfaces": "0.1-alpha-*" + "Microsoft.AspNet.Security.DataProtection" : "0.1-alpha-*" }, "configurations": { "net45": {}, @@ -33,6 +34,7 @@ "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Principal": "4.0.0.0", "System.Text.Encoding": "4.0.20.0", "System.Threading": "4.0.0.0", "System.Threading.Tasks": "4.0.10.0" From 3895dc2fdb6e53cd39ee5c736bbc440b6354934a Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Wed, 16 Apr 2014 12:25:56 -0700 Subject: [PATCH 2/7] Adding Concerete Implementations, addressing TODOs. Remaining: 1. Generate the AntiForgeryCookieName. 2. Update the ClaimsUidExtractor. --- .../AntiForgery/AntiForgery.cs | 81 +++----- .../AntiForgery/AntiForgeryConfig.cs | 8 +- .../AntiForgery/AntiForgeryConfigWrapper.cs | 5 +- .../AntiForgery/AntiForgeryToken.cs | 8 +- .../AntiForgery/AntiForgeryTokenSerializer.cs | 175 +++++++++++++++- .../AntiForgery/AntiForgeryTokenSet.cs | 28 +++ .../AntiForgery/AntiForgeryTokenStore.cs | 4 +- .../AntiForgery/AntiForgeryWorker.cs | 18 +- .../AntiForgery/BinaryBlob.cs | 8 +- .../AntiForgery/DefaultClaimUidExtractor.cs | 32 ++- .../IAntiForgeryAdditionalDataProvider.cs | 3 +- .../AntiForgery/IAntiForgeryConfig.cs | 6 +- .../IAntiForgeryTokenSerializer.cs | 6 +- .../AntiForgery/IClaimUidExtractor.cs | 4 +- .../AntiForgery/ITokenGenerator.cs | 3 +- .../AntiForgery/ITokenStore.cs | 3 +- .../AntiForgery/ITokenValidator.cs | 4 +- .../AntiForgery/TokenValidator.cs | 32 +-- .../Microsoft.AspNet.Mvc.Core.kproj | 19 ++ .../Properties/Resources.Designer.cs | 192 ++++++++++++++++++ .../Rendering/Html/HtmlHelper.cs | 10 +- .../Rendering/Html/HtmlHelperOfT.cs | 5 +- .../Rendering/IHtmlHelperOfT.cs | 3 +- src/Microsoft.AspNet.Mvc.Core/Resources.resx | 36 ++++ src/Microsoft.AspNet.Mvc.Core/project.json | 4 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 5 + 26 files changed, 560 insertions(+), 142 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index 9a1ca61b48..ddbc204841 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -1,10 +1,5 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System; + using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Security.Claims; -using System.Security.Principal; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Security.DataProtection; @@ -15,36 +10,31 @@ namespace Microsoft.AspNet.Mvc /// Provides access to the anti-forgery system, which provides protection against /// Cross-site Request Forgery (XSRF, also called CSRF) attacks. /// - public class AntiForgery + public sealed class AntiForgery { - private static readonly AntiForgeryWorker _worker = CreateSingletonAntiForgeryWorker(); - private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1" ; + private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1"; + private readonly AntiForgeryWorker _worker; - private static AntiForgeryWorker CreateSingletonAntiForgeryWorker() + public AntiForgery(IAntiForgeryConfig config, + IClaimUidExtractor claimUidExtractor) { - // initialize the dependency chain - IAntiForgeryConfig config = new AntiForgeryConfigWrapper(); - - // TODO populate the IDataProtectionProvider using DI. - IDataProtectionProvider dataProtectionProvider = DataProtectionProvider.CreateNew(); - IAntiForgeryTokenSerializer serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); - ITokenStore tokenStore = new AntiForgeryTokenStore(config, serializer); - IClaimUidExtractor claimUidExtractor = new DefaultClaimUidExtractor(config); + var serializer = new AntiForgeryTokenSerializer(DataProtectionProvider.CreateNew().CreateProtector(_purpose)); + var tokenStore = new AntiForgeryTokenStore(config, serializer); var tokenProvider = new TokenValidator(config, claimUidExtractor); - - return new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider); + _worker = new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider); } /// /// Generates an anti-forgery token for this request. This token can /// be validated by calling the Validate() method. /// + /// The http context associated with the current call. /// An HTML string corresponding to an <input type="hidden"> /// element. This element should be put inside a <form>. /// /// This method has a side effect: it may set a response cookie. /// - public static HtmlString GetHtml(HttpContext context) + public HtmlString GetHtml(HttpContext context) { TagBuilder retVal = _worker.GetFormInputElement(context); return retVal.ToHtmlString(TagRenderMode.SelfClosing); @@ -52,43 +42,34 @@ public static HtmlString GetHtml(HttpContext context) /// /// Generates an anti-forgery token pair (cookie and form token) for this request. - /// This method is similar to GetHtml(), but this method gives the caller control + /// This method is similar to GetHtml(HttpContext context), but this method gives the caller control /// over how to persist the returned values. To validate these tokens, call the /// appropriate overload of Validate. /// + /// The http context associated with the current call. /// The anti-forgery token - if any - that already existed /// for this request. May be null. The anti-forgery system will try to reuse this cookie /// value when generating a matching form token. - /// Will contain a new cookie value if the old cookie token - /// was null or invalid. If this value is non-null when the method completes, the caller - /// must persist this value in the form of a response cookie, and the existing cookie value - /// should be discarded. If this value is null when the method completes, the existing - /// cookie value was valid and needn't be modified. - /// The value that should be stored in the <form>. The caller - /// should take care not to accidentally swap the cookie and form tokens. - /// - /// Unlike the GetHtml() method, this method has no side effect. The caller - /// is responsible for setting the response cookie and injecting the returned - /// form token as appropriate. /// - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", - Justification = "Method is intended for advanced audiences.")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", - Justification = "Method is intended for advanced audiences.")] - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static void GetTokens(HttpContext context, string oldCookieToken, out string newCookieToken, out string formToken) + public AntiForgeryTokenSet GetTokens(HttpContext context, string oldCookieToken) { + // Will contain a new cookie value if the old cookie token + // was null or invalid. If this value is non-null when the method completes, the caller + // must persist this value in the form of a response cookie, and the existing cookie value + // should be discarded. If this value is null when the method completes, the existing + // cookie value was valid and needn't be modified. + string newCookieToken; + string formToken; _worker.GetTokens(context, oldCookieToken, out newCookieToken, out formToken); + return new AntiForgeryTokenSet(formToken, newCookieToken); } /// /// Validates an anti-forgery token that was supplied for this request. /// The anti-forgery token may be generated by calling GetHtml(). /// - /// - /// Throws an HttpAntiForgeryException if validation fails. - /// - public static void Validate(HttpContext context) + /// The http context associated with the current call. + public void Validate(HttpContext context) { _worker.Validate(context); } @@ -96,16 +77,18 @@ public static void Validate(HttpContext context) /// /// Validates an anti-forgery token pair that was generated by the GetTokens method. /// + /// The http context associated with the current call. /// The token that was supplied in the request cookie. /// The token that was supplied in the request form body. - /// - /// Throws an HttpAntiForgeryException if validation fails. - /// [EditorBrowsable(EditorBrowsableState.Advanced)] - public static void Validate(HttpContext context, string cookieToken, string formToken) + public void Validate(HttpContext context, string cookieToken, string formToken) { _worker.Validate(context, cookieToken, formToken); } - } -} + public void Validate(HttpContext context, AntiForgeryTokenSet antiforgeryTokenSet) + { + Validate(context, antiforgeryTokenSet.CookieToken, antiforgeryTokenSet.FormToken); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs index 822183f491..dabe08c54c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs @@ -1,7 +1,5 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - + using System.ComponentModel; -using System.Text; namespace Microsoft.AspNet.Mvc { @@ -64,7 +62,7 @@ public static bool RequireSsl /// /// Specifies whether to suppress the generation of X-Frame-Options header /// which is used to prevent ClickJacking. By default, the X-Frame-Options - /// header is generated with the value SAMEORIGIN. If this setting is 'true', + /// 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 static bool SuppressXFrameOptionsHeader @@ -97,4 +95,4 @@ private static string GetAntiForgeryCookieName() return AntiForgeryTokenFieldName; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs index bbcd90fee4..9bed9bf5f7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - + namespace Microsoft.AspNet.Mvc { - internal sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig + public sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig { public IAntiForgeryAdditionalDataProvider AdditionalDataProvider { diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs index 0785cd2a6d..fadbdebb00 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { - public class AntiForgeryToken + internal sealed class AntiForgeryToken { internal const int SecurityTokenBitLength = 128; internal const int ClaimUidBitLength = 256; @@ -59,4 +55,4 @@ public string Username } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs index 1d2fbbd4c5..a2f9792903 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs @@ -1,14 +1,16 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - + using System; +using System.IO; +using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Security.DataProtection; +using System.Text; namespace Microsoft.AspNet.Mvc { - // TODO: Stub :Replace with actual implementation internal sealed class AntiForgeryTokenSerializer : IAntiForgeryTokenSerializer { private readonly IDataProtector _cryptoSystem; + private const byte TokenVersion = 0x01; internal AntiForgeryTokenSerializer(IDataProtector cryptoSystem) { @@ -17,12 +19,173 @@ internal AntiForgeryTokenSerializer(IDataProtector cryptoSystem) public AntiForgeryToken Deserialize(string serializedToken) { - throw new NotImplementedException(); + try + { + using (MemoryStream stream = new MemoryStream(UrlTokenDecode(serializedToken))) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + AntiForgeryToken token = DeserializeImpl(reader); + if (token != null) + { + return token; + } + } + } + } + catch + { + // swallow all exceptions - homogenize error if something went wrong + } + + // TODO: Return proper exception here. + // if we reached this point, something went wrong deserializing + // throw HttpAntiForgeryException.CreateDeserializationFailedException(); + throw new InvalidOperationException(Resources.AntiForgeryToken_DeserializationFailed); + } + + /* The serialized format of the anti-XSRF token is as follows: + * Version: 1 byte integer + * SecurityToken: 16 byte binary blob + * IsSessionToken: 1 byte Boolean + * [if IsSessionToken = true] + * +- IsClaimsBased: 1 byte Boolean + * | [if IsClaimsBased = true] + * | `- ClaimUid: 32 byte binary blob + * | [if IsClaimsBased = false] + * | `- Username: UTF-8 string with 7-bit integer length prefix + * `- AdditionalData: UTF-8 string with 7-bit integer length prefix + */ + private static AntiForgeryToken DeserializeImpl(BinaryReader reader) + { + // we can only consume tokens of the same serialized version that we generate + byte embeddedVersion = reader.ReadByte(); + if (embeddedVersion != TokenVersion) + { + return null; + } + + AntiForgeryToken deserializedToken = new AntiForgeryToken(); + byte[] securityTokenBytes = reader.ReadBytes(AntiForgeryToken.SecurityTokenBitLength / 8); + deserializedToken.SecurityToken = new BinaryBlob(AntiForgeryToken.SecurityTokenBitLength, securityTokenBytes); + deserializedToken.IsSessionToken = reader.ReadBoolean(); + + if (!deserializedToken.IsSessionToken) + { + bool isClaimsBased = reader.ReadBoolean(); + if (isClaimsBased) + { + byte[] claimUidBytes = reader.ReadBytes(AntiForgeryToken.ClaimUidBitLength / 8); + deserializedToken.ClaimUid = new BinaryBlob(AntiForgeryToken.ClaimUidBitLength, claimUidBytes); + } + else + { + deserializedToken.Username = reader.ReadString(); + } + + deserializedToken.AdditionalData = reader.ReadString(); + } + + // if there's still unconsumed data in the stream, fail + if (reader.BaseStream.ReadByte() != -1) + { + return null; + } + + // success + return deserializedToken; + } + + public string Serialize([NotNull] AntiForgeryToken token) + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(TokenVersion); + writer.Write(token.SecurityToken.GetData()); + writer.Write(token.IsSessionToken); + + if (!token.IsSessionToken) + { + if (token.ClaimUid != null) + { + writer.Write(true /* isClaimsBased */); + writer.Write(token.ClaimUid.GetData()); + } + else + { + writer.Write(false /* isClaimsBased */); + writer.Write(token.Username); + } + + writer.Write(token.AdditionalData); + } + + writer.Flush(); + return UrlTokenEncode(_cryptoSystem.Protect(stream.ToArray())); + } + } } - public string Serialize(AntiForgeryToken token) + // TODO: This is temporary replacement for HttpServerUtility.UrlTokenEncode. + // This will be removed when webutils has this. + private string UrlTokenEncode(byte[] input) { - throw new NotImplementedException(); + var base64String = Convert.ToBase64String(input); + if (string.IsNullOrEmpty(base64String)) + { + return string.Empty; + } + + var sb = new StringBuilder(); + for (int i = 0; i < base64String.Length; i++) + { + switch (base64String[i]) + { + case '+': + sb.Append('-'); + break; + case '/': + sb.Append('_'); + break; + case '=': + sb.Append('.'); + break; + default: + sb.Append(base64String[i]); + break; + } + } + + return sb.ToString(); + } + + // TODO: This is temporary replacement for HttpServerUtility.UrlTokenDecode. + // This will be removed when webutils has this. + private byte[] UrlTokenDecode(string input) + { + var sb = new StringBuilder(); + for (int i = 0; i < input.Length; i++) + { + switch (input[i]) + { + case '-': + sb.Append('+'); + break; + case '_': + sb.Append('/'); + break; + case '.': + sb.Append('='); + break; + default: + sb.Append(input[i]); + break; + } + } + + return Convert.FromBase64String(sb.ToString()); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs new file mode 100644 index 0000000000..46013b033d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc +{ + public class AntiForgeryTokenSet + { + public AntiForgeryTokenSet(string formToken, string cookieToken) + { + if (string.IsNullOrEmpty(formToken)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, formToken); + } + + if (string.IsNullOrEmpty(cookieToken)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, cookieToken); + } + + FormToken = formToken; + CookieToken = cookieToken; + } + + public string FormToken { get; private set; } + + public string CookieToken { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index 2822a15cd1..88f6e4c2b8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -1,5 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - + using System; using Microsoft.AspNet.Abstractions; @@ -31,7 +30,6 @@ public AntiForgeryToken GetCookieToken(HttpContext httpContext) public AntiForgeryToken GetFormToken(HttpContext httpContext) { - // TODO: Add proper exception handling. string value = httpContext.Request.GetFormAsync().Result[_config.FormFieldName]; if (String.IsNullOrEmpty(value)) { diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index f61a9f8d0d..41d88ec77d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -1,11 +1,12 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; -using Microsoft.AspNet.Abstractions; -using Microsoft.AspNet.Mvc.Rendering; + using System; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; using System.Security.Principal; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.Rendering; +using System.Security.Claims; namespace Microsoft.AspNet.Mvc { @@ -30,7 +31,7 @@ private void CheckSSLConfig(HttpContext httpContext) { if (_config.RequireSSL && !httpContext.Request.IsSecure) { - throw new InvalidOperationException("AntiForgeryWorker_RequireSSL"); + throw new InvalidOperationException(Resources.AntiForgeryWorker_RequireSSL); } } @@ -59,10 +60,11 @@ private static IIdentity ExtractIdentity(HttpContext httpContext) { if (httpContext != null) { - var user = httpContext.User; + ClaimsPrincipal user = httpContext.User; + if (user != null) { - return user.Identity; + return user.Identity; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs index 970f7cd0cd..a26b7d1185 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; -using System.Linq; -using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; using Microsoft.AspNet.Security.DataProtection; namespace Microsoft.AspNet.Mvc @@ -15,7 +11,7 @@ namespace Microsoft.AspNet.Mvc // Represents a binary blob (token) that contains random data. // Useful for binary data inside a serialized stream. [DebuggerDisplay("{DebuggerString}")] - public sealed class BinaryBlob : IEquatable + internal sealed class BinaryBlob : IEquatable { private readonly byte[] _data; @@ -113,4 +109,4 @@ private static bool AreByteArraysEqual(byte[] a, byte[] b) return areEqual; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs index 8d49e0bf65..70e3855a7a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs @@ -1,8 +1,5 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System; +using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Security.Claims; @@ -16,7 +13,6 @@ public class DefaultClaimUidExtractor : IClaimUidExtractor { private const string NameIdentifierClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; - private readonly ClaimsIdentityConverter _claimsIdentityConverter; private readonly IAntiForgeryConfig _config; public DefaultClaimUidExtractor(IAntiForgeryConfig config) @@ -58,11 +54,11 @@ private static string[] GetUniqueIdentifierParameters(ClaimsIdentity claimsIdent // configured so that each identity provider has a unique 'identityProvider' // claim. // By default, we look for 'nameIdentifier' claim. - Claim nameIdentifierClaim = claims.SingleOrDefault(claim => String.Equals(NameIdentifierClaimType, claim.ValueType, StringComparison.Ordinal)); + Claim nameIdentifierClaim = claims.SingleOrDefault(claim => String.Equals(NameIdentifierClaimType, claim.Type, StringComparison.Ordinal)); if (nameIdentifierClaim == null || String.IsNullOrEmpty(nameIdentifierClaim.Value)) { - // TODO: Update the exception message. - throw new InvalidOperationException("DefaultClaimsNotPresent"); + // TODO: The exception message would be decided based on the claim types we choose to expose. + throw new InvalidOperationException("Resources.ClaimUidExtractor_DefaultClaimsNotPresent"); } return new string[] @@ -72,11 +68,25 @@ private static string[] GetUniqueIdentifierParameters(ClaimsIdentity claimsIdent }; } - private static byte[] ComputeSHA256(IList parameters) { - // TODO: find the right api to compute the hash. - throw new NotImplementedException(); + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter bw = new BinaryWriter(ms)) + { + foreach (string parameter in parameters) + { + bw.Write(parameter); // also writes the length as a prefix; unambiguous + } + bw.Flush(); + + using (SHA256 sha256 = SHA256.Create()) + { + byte[] retVal = sha256.ComputeHash(ms.ToArray(), 0, checked((int)ms.Length)); + return retVal; + } + } + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs index a2ac6ca5d2..49999d8e65 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryAdditionalDataProvider.cs @@ -1,5 +1,4 @@ using Microsoft.AspNet.Abstractions; -using Microsoft.AspNet.Mvc.Filters; namespace Microsoft.AspNet.Mvc { @@ -34,4 +33,4 @@ public interface IAntiForgeryAdditionalDataProvider /// True if the data is valid; false if the data is invalid. bool ValidateAdditionalData(HttpContext context, string additionalData); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs index d5022ace20..138851a151 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNet.Mvc.Filters; - -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc { // Provides configuration information about the anti-forgery system. public interface IAntiForgeryConfig @@ -23,4 +21,4 @@ public interface IAntiForgeryConfig // Skip X-FRAME-OPTIONS header. bool SuppressXFrameOptionsHeader { get; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs index 1d7ce0e26b..eface2f87a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryTokenSerializer.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNet.Mvc.Filters; - -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc { // Abstracts out the serialization process for an anti-forgery token internal interface IAntiForgeryTokenSerializer @@ -8,4 +6,4 @@ internal interface IAntiForgeryTokenSerializer AntiForgeryToken Deserialize(string serializedToken); string Serialize(AntiForgeryToken token); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs index 695032a6b7..22b2452deb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs @@ -1,6 +1,4 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System.Security.Principal; +using System.Security.Principal; namespace Microsoft.AspNet.Mvc { diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs index 9726f8e40d..cc6146ea07 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs @@ -1,6 +1,5 @@ using System.Security.Principal; using Microsoft.AspNet.Abstractions; -using Microsoft.AspNet.Mvc.Filters; namespace Microsoft.AspNet.Mvc { @@ -14,4 +13,4 @@ internal interface ITokenGenerator // The incoming cookie token must be valid. AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs index 7a73df8cfe..e1b6c07ec1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs @@ -1,5 +1,4 @@ using Microsoft.AspNet.Abstractions; -using Microsoft.AspNet.Mvc.Filters; namespace Microsoft.AspNet.Mvc { @@ -10,4 +9,4 @@ internal interface ITokenStore AntiForgeryToken GetFormToken(HttpContext httpContext); void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token); } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs index 7c606dd5cd..bd7aa52397 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNet.Abstractions; -using System.Security.Principal; +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Mvc { diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs index 267dd01461..453bba25d7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs @@ -1,10 +1,8 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System; +using System; using System.Diagnostics.Contracts; -using System.Globalization; using System.Security.Principal; using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Mvc.Core; namespace Microsoft.AspNet.Mvc { @@ -67,9 +65,8 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity ide && formToken.ClaimUid == null && String.IsNullOrEmpty(formToken.AdditionalData)) { - // TODO: Throw properException // Application says user is authenticated, but we have no identifier for the user. - throw new InvalidOperationException("TokenValidator_AuthenticatedUserWithoutUsername"); + throw new InvalidOperationException(Resources.FormatTokenValidator_AuthenticatedUserWithoutUsername(identity.GetType())); } return formToken; @@ -86,27 +83,23 @@ public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForg // Were the tokens even present at all? if (sessionToken == null) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateCookieMissingException(_config.CookieName); + throw new InvalidOperationException(Resources.FormatAntiForgeryToken_CookieMissing(_config.CookieName)); } if (fieldToken == null) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateFormFieldMissingException(_config.FormFieldName); + throw new InvalidOperationException(Resources.FormatAntiForgeryToken_FormFieldMissing(_config.FormFieldName)); } // Do the tokens have the correct format? if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateTokensSwappedException(_config.CookieName, _config.FormFieldName); + throw new InvalidOperationException(Resources.FormatAntiForgeryToken_TokensSwapped(_config.CookieName, _config.FormFieldName)); } // Are the security tokens embedded in each incoming token identical? if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken)) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateSecurityTokenMismatchException(); + throw new InvalidOperationException(Resources.AntiForgeryToken_SecurityTokenMismatch); } // Is the incoming token meant for the current user? @@ -115,7 +108,7 @@ public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForg if (identity != null && identity.IsAuthenticated) { - currentClaimUid = GetClaimUid( _claimUidExtractor.ExtractClaimUid(identity)); + currentClaimUid = GetClaimUid(_claimUidExtractor.ExtractClaimUid(identity)); if (currentClaimUid == null) { currentUsername = identity.Name ?? String.Empty; @@ -129,20 +122,17 @@ public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForg if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername); + throw new InvalidOperationException(Resources.FormatAntiForgeryToken_UsernameMismatch(fieldToken.Username, currentUsername)); } if (!Equals(fieldToken.ClaimUid, currentClaimUid)) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateClaimUidMismatchException(); + throw new InvalidOperationException(Resources.AntiForgeryToken_ClaimUidMismatch); } // Is the AdditionalData valid? if (_config.AdditionalDataProvider != null && !_config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData)) { - // TODO: Throw properException - //throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException(); + throw new InvalidOperationException(Resources.AntiForgeryToken_AdditionalDataCheckFailed); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 8b94db3a49..66128439f9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -40,6 +40,24 @@ + + + + + + + + + + + + + + + + + + @@ -112,6 +130,7 @@ + diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index f6b97780d3..f658bfe7f8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -10,6 +10,54 @@ internal static class Resources private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + /// + internal static string TokenValidator_AuthenticatedUserWithoutUsername + { + get { return GetString("TokenValidator_AuthenticatedUserWithoutUsername"); } + } + + /// + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + /// + internal static string FormatTokenValidator_AuthenticatedUserWithoutUsername(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TokenValidator_AuthenticatedUserWithoutUsername"), p0); + } + + /// + /// A claim of type '{0}' was not present on the provided ClaimsIdentity. + /// + internal static string ClaimUidExtractor_ClaimNotPresent + { + get { return GetString("ClaimUidExtractor_ClaimNotPresent"); } + } + + /// + /// A claim of type '{0}' was not present on the provided ClaimsIdentity. + /// + internal static string FormatClaimUidExtractor_ClaimNotPresent(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ClaimUidExtractor_ClaimNotPresent"), p0); + } + + /// + /// A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier. + /// + internal static string ClaimUidExtractor_DefaultClaimsNotPresent + { + get { return GetString("ClaimUidExtractor_DefaultClaimsNotPresent"); } + } + + /// + /// A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier. + /// + internal static string FormatClaimUidExtractor_DefaultClaimsNotPresent() + { + return GetString("ClaimUidExtractor_DefaultClaimsNotPresent"); + } + /// /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. /// @@ -42,6 +90,150 @@ internal static string FormatActionExecutor_UnexpectedTaskInstance(object p0, ob return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1); } + /// + /// The provided anti-forgery token failed a custom data check. + /// + internal static string AntiForgeryToken_AdditionalDataCheckFailed + { + get { return GetString("AntiForgeryToken_AdditionalDataCheckFailed"); } + } + + /// + /// The provided anti-forgery token failed a custom data check. + /// + internal static string FormatAntiForgeryToken_AdditionalDataCheckFailed() + { + return GetString("AntiForgeryToken_AdditionalDataCheckFailed"); + } + + /// + /// The provided anti-forgery token was meant for a different claims-based user than the current user. + /// + internal static string AntiForgeryToken_ClaimUidMismatch + { + get { return GetString("AntiForgeryToken_ClaimUidMismatch"); } + } + + /// + /// The provided anti-forgery token was meant for a different claims-based user than the current user. + /// + internal static string FormatAntiForgeryToken_ClaimUidMismatch() + { + return GetString("AntiForgeryToken_ClaimUidMismatch"); + } + + /// + /// The required anti-forgery cookie "{0}" is not present. + /// + internal static string AntiForgeryToken_CookieMissing + { + get { return GetString("AntiForgeryToken_CookieMissing"); } + } + + /// + /// The required anti-forgery cookie "{0}" is not present. + /// + internal static string FormatAntiForgeryToken_CookieMissing(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_CookieMissing"), p0); + } + + /// + /// The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster. + /// + internal static string AntiForgeryToken_DeserializationFailed + { + get { return GetString("AntiForgeryToken_DeserializationFailed"); } + } + + /// + /// The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster. + /// + internal static string FormatAntiForgeryToken_DeserializationFailed() + { + return GetString("AntiForgeryToken_DeserializationFailed"); + } + + /// + /// The required anti-forgery form field "{0}" is not present. + /// + internal static string AntiForgeryToken_FormFieldMissing + { + get { return GetString("AntiForgeryToken_FormFieldMissing"); } + } + + /// + /// The required anti-forgery form field "{0}" is not present. + /// + internal static string FormatAntiForgeryToken_FormFieldMissing(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_FormFieldMissing"), p0); + } + + /// + /// The anti-forgery cookie token and form field token do not match. + /// + internal static string AntiForgeryToken_SecurityTokenMismatch + { + get { return GetString("AntiForgeryToken_SecurityTokenMismatch"); } + } + + /// + /// The anti-forgery cookie token and form field token do not match. + /// + internal static string FormatAntiForgeryToken_SecurityTokenMismatch() + { + return GetString("AntiForgeryToken_SecurityTokenMismatch"); + } + + /// + /// Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped. + /// + internal static string AntiForgeryToken_TokensSwapped + { + get { return GetString("AntiForgeryToken_TokensSwapped"); } + } + + /// + /// Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped. + /// + internal static string FormatAntiForgeryToken_TokensSwapped(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_TokensSwapped"), p0, p1); + } + + /// + /// The provided anti-forgery token was meant for user "{0}", but the current user is "{1}". + /// + internal static string AntiForgeryToken_UsernameMismatch + { + get { return GetString("AntiForgeryToken_UsernameMismatch"); } + } + + /// + /// The provided anti-forgery token was meant for user "{0}", but the current user is "{1}". + /// + internal static string FormatAntiForgeryToken_UsernameMismatch(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_UsernameMismatch"), p0, p1); + } + + /// + /// The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request. + /// + internal static string AntiForgeryWorker_RequireSSL + { + get { return GetString("AntiForgeryWorker_RequireSSL"); } + } + + /// + /// The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request. + /// + internal static string FormatAntiForgeryWorker_RequireSSL() + { + return GetString("AntiForgeryWorker_RequireSSL"); + } + /// /// The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs index 72f3938a12..f5df17d95b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs @@ -30,6 +30,7 @@ public class HtmlHelper : ICanHasViewContext private readonly IUrlHelper _urlHelper; private readonly IViewEngine _viewEngine; + private readonly AntiForgery _antiForgeryInstance; private ViewContext _viewContext; @@ -39,11 +40,13 @@ public class HtmlHelper : ICanHasViewContext public HtmlHelper( [NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider, - [NotNull] IUrlHelper urlHelper) + [NotNull] IUrlHelper urlHelper, + [NotNull] AntiForgery antiForgeryInstance) { _viewEngine = viewEngine; MetadataProvider = metadataProvider; _urlHelper = urlHelper; + _antiForgeryInstance = antiForgeryInstance; // Underscores are fine characters in id's. IdAttributeDotReplacement = "_"; @@ -158,6 +161,11 @@ public virtual void Contextualize([NotNull] ViewContext viewContext) ViewContext = viewContext; } + public HtmlString AntiForgeryToken() + { + return _antiForgeryInstance.GetHtml(ViewContext.HttpContext); + } + public MvcForm BeginForm(string actionName, string controllerName, object routeValues, FormMethod method, object htmlAttributes) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs index c2af9e14c2..5f1e5c431f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs @@ -15,8 +15,9 @@ public class HtmlHelper : HtmlHelper, IHtmlHelper public HtmlHelper( [NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider, - [NotNull] IUrlHelper urlHelper) - : base(viewEngine, metadataProvider, urlHelper) + [NotNull] IUrlHelper urlHelper, + [NotNull] AntiForgery antiForgeryInstance) + : base(viewEngine, metadataProvider, urlHelper, antiForgeryInstance) { } diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs index 07d11c07af..f6f0a2472a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs @@ -61,7 +61,8 @@ HtmlString ActionLink( string fragment, object routeValues, object htmlAttributes); - + + HtmlString AntiForgeryToken(); /// /// Writes an opening tag to the response. When the user submits the form, diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 2f6151a79f..6a349538c1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -117,12 +117,48 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The provided anti-forgery token failed a custom data check. + + + The provided anti-forgery token was meant for a different claims-based user than the current user. + + + The required anti-forgery cookie "{0}" is not present. + + + The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster. + + + The required anti-forgery form field "{0}" is not present. + + + The anti-forgery cookie token and form field token do not match. + + + Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped. + + + The provided anti-forgery token was meant for user "{0}", but the current user is "{1}". + + + The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request. + The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + + + A claim of type '{0}' was not present on the provided ClaimsIdentity. + + + The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors. diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json index e0e743114d..85b6e3fd28 100644 --- a/src/Microsoft.AspNet.Mvc.Core/project.json +++ b/src/Microsoft.AspNet.Mvc.Core/project.json @@ -7,7 +7,7 @@ "Microsoft.AspNet.Routing": "0.1-alpha-*", "Common": "", "Microsoft.AspNet.Mvc.ModelBinding": "", - "Microsoft.Net.Runtime.Interfaces": "0.1-alpha-*" + "Microsoft.Net.Runtime.Interfaces": "0.1-alpha-*", "Microsoft.AspNet.Security.DataProtection" : "0.1-alpha-*" }, "configurations": { @@ -34,6 +34,8 @@ "System.Runtime": "4.0.20.0", "System.Runtime.Extensions": "4.0.10.0", "System.Runtime.InteropServices": "4.0.20.0", + "System.Security.Cryptography": "4.0.0.0", + "System.Security.Cryptography.HashAlgorithms.SHA2": "4.0.0.0", "System.Security.Principal": "4.0.0.0", "System.Text.Encoding": "4.0.20.0", "System.Threading": "4.0.0.0", diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 36557a98d8..cf9de9c508 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -8,6 +8,7 @@ using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor.Compilation; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Security.DataProtection; namespace Microsoft.AspNet.Mvc { @@ -76,6 +77,10 @@ public static IEnumerable GetDefaultServices(IConfiguration yield return describe.Transient(); yield return describe.Transient(); + yield return describe.Singleton(); + yield return describe.Singleton(); + yield return describe.Singleton(); + yield return describe.Describe( typeof(INestedProviderManager<>), From fb446949df21be08bdd8c9e819c76b268826f0d4 Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Fri, 18 Apr 2014 13:54:43 -0700 Subject: [PATCH 3/7] DI related changes to the interface, adding the fallback for extracting the ClaimUid. --- .../AntiForgery/AntiForgery.cs | 16 +++-- .../AntiForgery/AntiForgeryConfig.cs | 41 ++--------- .../AntiForgery/AntiForgeryConfigWrapper.cs | 16 +---- .../AntiForgery/AntiForgeryTokenStore.cs | 11 +-- .../AntiForgery/AntiForgeryWorker.cs | 13 ++-- .../AntiForgery/BinaryBlob.cs | 2 + ...efaultAntiForgeryAdditionalDataProvider.cs | 18 +++++ .../AntiForgery/DefaultClaimUidExtractor.cs | 70 +++++++------------ .../AntiForgery/IAntiForgeryConfig.cs | 6 -- .../AntiForgery/IClaimUidExtractor.cs | 5 +- .../AntiForgery/ITokenGenerator.cs | 3 +- .../AntiForgery/ITokenStore.cs | 5 +- .../AntiForgery/ITokenValidator.cs | 3 +- .../{TokenValidator.cs => TokenProvider.cs} | 56 +++++++-------- .../Microsoft.AspNet.Mvc.Core.kproj | 1 + .../DataMemberModelValidatorProvider.cs | 2 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 2 +- 17 files changed, 117 insertions(+), 153 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultAntiForgeryAdditionalDataProvider.cs rename src/Microsoft.AspNet.Mvc.Core/AntiForgery/{TokenValidator.cs => TokenProvider.cs} (65%) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index ddbc204841..b779a0a222 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -3,6 +3,7 @@ using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Security.DataProtection; +using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { @@ -15,12 +16,15 @@ public sealed class AntiForgery private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1"; private readonly AntiForgeryWorker _worker; - public AntiForgery(IAntiForgeryConfig config, - IClaimUidExtractor claimUidExtractor) + public AntiForgery(IClaimUidExtractor claimUidExtractor, + IDataProtectionProvider dataProtectionProvider, + IAntiForgeryAdditionalDataProvider additionalDataProvider) { - var serializer = new AntiForgeryTokenSerializer(DataProtectionProvider.CreateNew().CreateProtector(_purpose)); + // TODO: This is temporary till we figure out how to flow configs using DI. + var config = new AntiForgeryConfigWrapper(); + var serializer = new AntiForgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose)); var tokenStore = new AntiForgeryTokenStore(config, serializer); - var tokenProvider = new TokenValidator(config, claimUidExtractor); + var tokenProvider = new TokenProvider(config, claimUidExtractor, additionalDataProvider); _worker = new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider); } @@ -69,9 +73,9 @@ public AntiForgeryTokenSet GetTokens(HttpContext context, string oldCookieToken) /// The anti-forgery token may be generated by calling GetHtml(). /// /// The http context associated with the current call. - public void Validate(HttpContext context) + public async Task ValidateAsync(HttpContext context) { - _worker.Validate(context); + await _worker.ValidateAsync(context); } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs index dabe08c54c..7e3a3c8342 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs @@ -1,7 +1,4 @@ - -using System.ComponentModel; - -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc { /// /// Provides programmatic configuration for the anti-forgery token system. @@ -9,20 +6,8 @@ namespace Microsoft.AspNet.Mvc public static class AntiForgeryConfig { internal const string AntiForgeryTokenFieldName = "__RequestVerificationToken"; - + private const string AntiForgeryCookieTokenName = "__RequestVerificationCookieToken"; private static string _cookieName; - private static string _uniqueClaimTypeIdentifier; - - /// - /// Specifies an object that can provide additional data to put into all - /// generated tokens and that can validate additional data in incoming - /// tokens. - /// - public static IAntiForgeryAdditionalDataProvider AdditionalDataProvider - { - get; - set; - } /// /// Specifies the name of the cookie that is used by the anti-forgery @@ -71,28 +56,10 @@ public static bool SuppressXFrameOptionsHeader set; } - /// - /// Specifies whether the anti-forgery system should skip checking - /// for conditions that might indicate misuse of the system. Please - /// use caution when setting this switch, as improper use could open - /// security holes in the application. - /// - /// - /// Setting this switch will disable several checks, including: - /// - Identity.IsAuthenticated = true without Identity.Name being set - /// - special-casing claims-based identities - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool SuppressIdentityHeuristicChecks - { - get; - set; - } - - // TODO: Replace the stub. + // TODO: Replace the stub. private static string GetAntiForgeryCookieName() { - return AntiForgeryTokenFieldName; + return AntiForgeryCookieTokenName; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs index 9bed9bf5f7..57dd2b4f7b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfigWrapper.cs @@ -1,16 +1,7 @@ - -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc { public sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig { - public IAntiForgeryAdditionalDataProvider AdditionalDataProvider - { - get - { - return AntiForgeryConfig.AdditionalDataProvider; - } - } - public string CookieName { get { return AntiForgeryConfig.CookieName; } @@ -26,11 +17,6 @@ public bool RequireSSL get { return AntiForgeryConfig.RequireSsl; } } - public bool SuppressIdentityHeuristicChecks - { - get { return AntiForgeryConfig.SuppressIdentityHeuristicChecks; } - } - public bool SuppressXFrameOptionsHeader { get { return AntiForgeryConfig.SuppressXFrameOptionsHeader; } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index 88f6e4c2b8..f38c8a8dc1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -1,6 +1,7 @@  using System; using Microsoft.AspNet.Abstractions; +using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { @@ -28,10 +29,11 @@ public AntiForgeryToken GetCookieToken(HttpContext httpContext) return _serializer.Deserialize(cookie); } - public AntiForgeryToken GetFormToken(HttpContext httpContext) + public async Task GetFormTokenAsync(HttpContext httpContext) { - string value = httpContext.Request.GetFormAsync().Result[_config.FormFieldName]; - if (String.IsNullOrEmpty(value)) + var form = await httpContext.Request.GetFormAsync(); + string value = form[_config.FormFieldName]; + if (string.IsNullOrEmpty(value)) { // did not exist return null; @@ -46,8 +48,7 @@ public void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token) CookieOptions options = new CookieOptions() { HttpOnly = true }; // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default - // value of newCookie.Secure is automatically populated from the - // config element. + // value of newCookie.Secure is poulated out of band. if (_config.RequireSSL) { options.Secure = true; diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index 41d88ec77d..c820d3f9ad 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -7,6 +7,7 @@ using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Rendering; using System.Security.Claims; +using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { @@ -37,7 +38,7 @@ private void CheckSSLConfig(HttpContext httpContext) private AntiForgeryToken DeserializeToken(string serializedToken) { - return (!String.IsNullOrEmpty(serializedToken)) + return (!string.IsNullOrEmpty(serializedToken)) ? _serializer.Deserialize(serializedToken) : null; } @@ -56,7 +57,7 @@ private AntiForgeryToken DeserializeTokenNoThrow(string serializedToken) } } - private static IIdentity ExtractIdentity(HttpContext httpContext) + private static ClaimsIdentity ExtractIdentity(HttpContext httpContext) { if (httpContext != null) { @@ -64,7 +65,9 @@ private static IIdentity ExtractIdentity(HttpContext httpContext) if (user != null) { - return user.Identity; + // We only support ClaimsIdentity. + // Todo remove this once httpContext.User moves to ClaimsIdentity. + return user.Identity as ClaimsIdentity; } } @@ -159,13 +162,13 @@ private string Serialize(AntiForgeryToken token) // [ ENTRY POINT ] // Given an HttpContext, validates that the anti-XSRF tokens contained // in the cookies & form are OK for this request. - public void Validate(HttpContext httpContext) + public async Task ValidateAsync(HttpContext httpContext) { CheckSSLConfig(httpContext); // Extract cookie & form tokens AntiForgeryToken cookieToken = _tokenStore.GetCookieToken(httpContext); - AntiForgeryToken formToken = _tokenStore.GetFormToken(httpContext); + AntiForgeryToken formToken = await _tokenStore.GetFormTokenAsync(httpContext); // Validate _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken); diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs index a26b7d1185..75f695189f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Text; using Microsoft.AspNet.Security.DataProtection; +using System.Runtime.CompilerServices; namespace Microsoft.AspNet.Mvc { @@ -94,6 +95,7 @@ private static byte[] GenerateNewToken(int bitLength) return data; } + [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static bool AreByteArraysEqual(byte[] a, byte[] b) { if (a == null || b == null || a.Length != b.Length) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultAntiForgeryAdditionalDataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultAntiForgeryAdditionalDataProvider.cs new file mode 100644 index 0000000000..bb8b3ecb1a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultAntiForgeryAdditionalDataProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Mvc +{ + public class DefaultAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider + { + public virtual string GetAdditionalData(HttpContext context) + { + return string.Empty; + } + + public virtual bool ValidateAdditionalData(HttpContext context, string additionalData) + { + // Default implementation does not understand anything but empty data. + return string.IsNullOrEmpty(additionalData); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs index 70e3855a7a..8e4ff2fea8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs @@ -4,71 +4,54 @@ using System.Linq; using System.Security.Claims; using System.Security.Cryptography; -using System.Security.Principal; namespace Microsoft.AspNet.Mvc { // Can extract unique identifers for a claims-based identity public class DefaultClaimUidExtractor : IClaimUidExtractor { - private const string NameIdentifierClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; - - private readonly IAntiForgeryConfig _config; - - public DefaultClaimUidExtractor(IAntiForgeryConfig config) + public string ExtractClaimUid(ClaimsIdentity claimsIdentity) { - _config = config; - } - - public byte[] ExtractClaimUid(IIdentity identity) - { - if (identity == null || !identity.IsAuthenticated || _config.SuppressIdentityHeuristicChecks) + if (claimsIdentity == null || !claimsIdentity.IsAuthenticated) { // Skip anonymous users - // Skip when claims-based checks are disabled - return null; - } - - var claimsIdentity = identity as ClaimsIdentity; - if (claimsIdentity == null) - { - // not a claims-based identity return null; } - string[] uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity); + var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity); byte[] claimUidBytes = ComputeSHA256(uniqueIdentifierParameters); - return claimUidBytes; + return Convert.ToBase64String(claimUidBytes); } - private static string[] GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity) + private static IEnumerable GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity) { - // TODO: We need to select a single claim based on the Authentication Type of the claim. - var claims = claimsIdentity.Claims; - - // TODO: Need to check with vittorio for acs. - // For a correctly configured ACS consumer, this tuple will uniquely - // identify a user of the application. We assume that a well-behaved - // identity provider will never assign the same name identifier to multiple - // users within its security realm, and we assume that ACS has been - // configured so that each identity provider has a unique 'identityProvider' - // claim. - // By default, we look for 'nameIdentifier' claim. - Claim nameIdentifierClaim = claims.SingleOrDefault(claim => String.Equals(NameIdentifierClaimType, claim.Type, StringComparison.Ordinal)); - if (nameIdentifierClaim == null || String.IsNullOrEmpty(nameIdentifierClaim.Value)) + // TODO: Need to enable support for special casing acs identities. + var nameIdentifierClaim = claimsIdentity.FindFirst(claim => + String.Equals(ClaimTypes.NameIdentifier, + claim.Type, StringComparison.Ordinal)); + if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value)) { - // TODO: The exception message would be decided based on the claim types we choose to expose. - throw new InvalidOperationException("Resources.ClaimUidExtractor_DefaultClaimsNotPresent"); + return new string[] + { + ClaimTypes.NameIdentifier, + nameIdentifierClaim.Value + }; } - return new string[] + // We Do not understand this claimsIdentity, fallback on serializing the entire claims Identity. + var claims = claimsIdentity.Claims.ToList(); + claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal)); + var identifierParameters = new List(); + foreach (var claim in claims) { - NameIdentifierClaimType, - nameIdentifierClaim.Value - }; + identifierParameters.Add(claim.Type); + identifierParameters.Add(claim.Value); + } + + return identifierParameters; } - private static byte[] ComputeSHA256(IList parameters) + private static byte[] ComputeSHA256(IEnumerable parameters) { using (MemoryStream ms = new MemoryStream()) { @@ -78,6 +61,7 @@ private static byte[] ComputeSHA256(IList parameters) { bw.Write(parameter); // also writes the length as a prefix; unambiguous } + bw.Flush(); using (SHA256 sha256 = SHA256.Create()) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs index 138851a151..14bc3e8b38 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IAntiForgeryConfig.cs @@ -3,9 +3,6 @@ // Provides configuration information about the anti-forgery system. public interface IAntiForgeryConfig { - // Provides additional data to go into the tokens. - IAntiForgeryAdditionalDataProvider AdditionalDataProvider { get; } - // Name of the cookie to use. string CookieName { get; } @@ -15,9 +12,6 @@ public interface IAntiForgeryConfig // Whether SSL is mandatory for this request. bool RequireSSL { get; } - // Skip ClaimsIdentity & related logic. - bool SuppressIdentityHeuristicChecks { get; } - // Skip X-FRAME-OPTIONS header. bool SuppressXFrameOptionsHeader { get; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs index 22b2452deb..d5414593eb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/IClaimUidExtractor.cs @@ -1,10 +1,11 @@ -using System.Security.Principal; +using System.Security.Claims; +using System.Security.Principal; namespace Microsoft.AspNet.Mvc { // Can extract unique identifers for a claims-based identity public interface IClaimUidExtractor { - byte[] ExtractClaimUid(IIdentity identity); + string ExtractClaimUid(ClaimsIdentity identity); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs index cc6146ea07..3e23fe63c6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenGenerator.cs @@ -1,5 +1,6 @@ using System.Security.Principal; using Microsoft.AspNet.Abstractions; +using System.Security.Claims; namespace Microsoft.AspNet.Mvc { @@ -11,6 +12,6 @@ internal interface ITokenGenerator // Given a cookie token, generates a corresponding form token. // The incoming cookie token must be valid. - AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken); + AntiForgeryToken GenerateFormToken(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken cookieToken); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs index e1b6c07ec1..6026a579e7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenStore.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNet.Abstractions; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Mvc { @@ -6,7 +7,7 @@ namespace Microsoft.AspNet.Mvc internal interface ITokenStore { AntiForgeryToken GetCookieToken(HttpContext httpContext); - AntiForgeryToken GetFormToken(HttpContext httpContext); + Task GetFormTokenAsync(HttpContext httpContext); void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs index bd7aa52397..1146691523 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/ITokenValidator.cs @@ -1,5 +1,6 @@ using System.Security.Principal; using Microsoft.AspNet.Abstractions; +using System.Security.Claims; namespace Microsoft.AspNet.Mvc { @@ -11,6 +12,6 @@ internal interface ITokenValidator bool IsCookieTokenValid(AntiForgeryToken cookieToken); // Validates a (cookie, form) token pair. - void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken); + void ValidateTokens(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs similarity index 65% rename from src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs rename to src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs index 453bba25d7..2de41f7ae8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenValidator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs @@ -3,18 +3,21 @@ using System.Security.Principal; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Core; +using System.Security.Claims; namespace Microsoft.AspNet.Mvc { - internal sealed class TokenValidator : ITokenValidator, ITokenGenerator + internal sealed class TokenProvider : ITokenValidator, ITokenGenerator { private readonly IClaimUidExtractor _claimUidExtractor; private readonly IAntiForgeryConfig _config; + private readonly IAntiForgeryAdditionalDataProvider _additionalDataProvider; - internal TokenValidator(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor) + internal TokenProvider(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor, IAntiForgeryAdditionalDataProvider additionalDataProvider) { _config = config; _claimUidExtractor = claimUidExtractor; + _additionalDataProvider = additionalDataProvider; } public AntiForgeryToken GenerateCookieToken() @@ -26,7 +29,7 @@ public AntiForgeryToken GenerateCookieToken() }; } - public AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity identity, AntiForgeryToken cookieToken) + public AntiForgeryToken GenerateFormToken(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken cookieToken) { Contract.Assert(IsCookieTokenValid(cookieToken)); @@ -35,19 +38,11 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity ide SecurityToken = cookieToken.SecurityToken, IsSessionToken = false }; - - bool requireAuthenticatedUserHeuristicChecks = false; + // populate Username and ClaimUid if (identity != null && identity.IsAuthenticated) { - if (!_config.SuppressIdentityHeuristicChecks) - { - // If the user is authenticated and heuristic checks are not suppressed, - // then Username, ClaimUid, or AdditionalData must be set. - requireAuthenticatedUserHeuristicChecks = true; - } - - formToken.ClaimUid = GetClaimUid(_claimUidExtractor.ExtractClaimUid(identity)); + formToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); if (formToken.ClaimUid == null) { formToken.Username = identity.Name; @@ -55,15 +50,14 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, IIdentity ide } // populate AdditionalData - if (_config.AdditionalDataProvider != null) + if (_additionalDataProvider != null) { - formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext); + formToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext); } - if (requireAuthenticatedUserHeuristicChecks - && String.IsNullOrEmpty(formToken.Username) + if (string.IsNullOrEmpty(formToken.Username) && formToken.ClaimUid == null - && String.IsNullOrEmpty(formToken.AdditionalData)) + && string.IsNullOrEmpty(formToken.AdditionalData)) { // Application says user is authenticated, but we have no identifier for the user. throw new InvalidOperationException(Resources.FormatTokenValidator_AuthenticatedUserWithoutUsername(identity.GetType())); @@ -77,8 +71,7 @@ public bool IsCookieTokenValid(AntiForgeryToken cookieToken) return (cookieToken != null && cookieToken.IsSessionToken); } - // TODO: Sweep the exceptions. - public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken) + public void ValidateTokens(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken) { // Were the tokens even present at all? if (sessionToken == null) @@ -103,15 +96,15 @@ public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForg } // Is the incoming token meant for the current user? - string currentUsername = String.Empty; + string currentUsername = string.Empty; BinaryBlob currentClaimUid = null; if (identity != null && identity.IsAuthenticated) { - currentClaimUid = GetClaimUid(_claimUidExtractor.ExtractClaimUid(identity)); + currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); if (currentClaimUid == null) { - currentUsername = identity.Name ?? String.Empty; + currentUsername = identity.Name ?? string.Empty; } } @@ -120,25 +113,32 @@ public void ValidateTokens(HttpContext httpContext, IIdentity identity, AntiForg bool useCaseSensitiveUsernameComparison = currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase); - if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(fieldToken.Username, + currentUsername, + (useCaseSensitiveUsernameComparison) ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase)) { - throw new InvalidOperationException(Resources.FormatAntiForgeryToken_UsernameMismatch(fieldToken.Username, currentUsername)); + throw new InvalidOperationException(Resources. + FormatAntiForgeryToken_UsernameMismatch(fieldToken.Username, + currentUsername)); } + if (!Equals(fieldToken.ClaimUid, currentClaimUid)) { throw new InvalidOperationException(Resources.AntiForgeryToken_ClaimUidMismatch); } // Is the AdditionalData valid? - if (_config.AdditionalDataProvider != null && !_config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData)) + if (_additionalDataProvider != null && !_additionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData)) { throw new InvalidOperationException(Resources.AntiForgeryToken_AdditionalDataCheckFailed); } } - private static BinaryBlob GetClaimUid(byte[] claimUidBytes) + private static BinaryBlob GetClaimUidBlob(string base64ClaimUid) { - return new BinaryBlob(256, claimUidBytes); + return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid)); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 66128439f9..2325f7c1ac 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -50,6 +50,7 @@ + diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataMemberModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataMemberModelValidatorProvider.cs index 5784518422..cb6c7631d6 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataMemberModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataMemberModelValidatorProvider.cs @@ -47,4 +47,4 @@ internal static bool IsRequiredDataMember(Type containerType, IEnumerable GetDefaultServices(IConfiguration yield return describe.Transient(); yield return describe.Singleton(); - yield return describe.Singleton(); yield return describe.Singleton(); + yield return describe.Singleton(); yield return describe.Describe( From 2b0d4721d9309082ad3eb07e5eefb065f58923be Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Fri, 18 Apr 2014 15:32:10 -0700 Subject: [PATCH 4/7] Responding to Comments. --- .../AntiForgery/AntiForgery.cs | 43 ++++++------- .../AntiForgery/AntiForgeryConfig.cs | 3 +- .../AntiForgery/AntiForgeryTokenSerializer.cs | 2 +- .../AntiForgery/AntiForgeryTokenStore.cs | 3 +- .../AntiForgery/AntiForgeryWorker.cs | 62 ++++++++++++------- .../AntiForgery/TokenProvider.cs | 25 ++++++-- .../Properties/Resources.Designer.cs | 4 +- .../Rendering/IHtmlHelperOfT.cs | 6 ++ src/Microsoft.AspNet.Mvc.Core/Resources.resx | 2 +- 9 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index b779a0a222..208163ce98 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -1,9 +1,8 @@ - -using System.ComponentModel; +using System.ComponentModel; +using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Security.DataProtection; -using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { @@ -16,9 +15,9 @@ public sealed class AntiForgery private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1"; private readonly AntiForgeryWorker _worker; - public AntiForgery(IClaimUidExtractor claimUidExtractor, - IDataProtectionProvider dataProtectionProvider, - IAntiForgeryAdditionalDataProvider additionalDataProvider) + public AntiForgery([NotNull] IClaimUidExtractor claimUidExtractor, + [NotNull] IDataProtectionProvider dataProtectionProvider, + [NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider) { // TODO: This is temporary till we figure out how to flow configs using DI. var config = new AntiForgeryConfigWrapper(); @@ -32,16 +31,16 @@ public AntiForgery(IClaimUidExtractor claimUidExtractor, /// Generates an anti-forgery token for this request. This token can /// be validated by calling the Validate() method. /// - /// The http context associated with the current call. + /// The HTTP context associated with the current call. /// An HTML string corresponding to an <input type="hidden"> /// element. This element should be put inside a <form>. /// - /// This method has a side effect: it may set a response cookie. + /// This method has a side effect: A response cookie is set if there is no valid cookie associated with the request. /// - public HtmlString GetHtml(HttpContext context) + public HtmlString GetHtml([NotNull] HttpContext context) { - TagBuilder retVal = _worker.GetFormInputElement(context); - return retVal.ToHtmlString(TagRenderMode.SelfClosing); + TagBuilder builder = _worker.GetFormInputElement(context); + return builder.ToHtmlString(TagRenderMode.SelfClosing); } /// @@ -50,30 +49,27 @@ public HtmlString GetHtml(HttpContext context) /// over how to persist the returned values. To validate these tokens, call the /// appropriate overload of Validate. /// - /// The http context associated with the current call. + /// The HTTP context associated with the current call. /// The anti-forgery token - if any - that already existed /// for this request. May be null. The anti-forgery system will try to reuse this cookie /// value when generating a matching form token. /// - public AntiForgeryTokenSet GetTokens(HttpContext context, string oldCookieToken) + public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCookieToken) { // Will contain a new cookie value if the old cookie token // was null or invalid. If this value is non-null when the method completes, the caller // must persist this value in the form of a response cookie, and the existing cookie value // should be discarded. If this value is null when the method completes, the existing // cookie value was valid and needn't be modified. - string newCookieToken; - string formToken; - _worker.GetTokens(context, oldCookieToken, out newCookieToken, out formToken); - return new AntiForgeryTokenSet(formToken, newCookieToken); + return _worker.GetTokens(context, oldCookieToken); } /// /// Validates an anti-forgery token that was supplied for this request. /// The anti-forgery token may be generated by calling GetHtml(). /// - /// The http context associated with the current call. - public async Task ValidateAsync(HttpContext context) + /// The HTTP context associated with the current call. + public async Task ValidateAsync([NotNull] HttpContext context) { await _worker.ValidateAsync(context); } @@ -81,18 +77,17 @@ public async Task ValidateAsync(HttpContext context) /// /// Validates an anti-forgery token pair that was generated by the GetTokens method. /// - /// The http context associated with the current call. + /// The HTTP context associated with the current call. /// The token that was supplied in the request cookie. /// The token that was supplied in the request form body. - [EditorBrowsable(EditorBrowsableState.Advanced)] - public void Validate(HttpContext context, string cookieToken, string formToken) + public void Validate([NotNull] HttpContext context, string cookieToken, string formToken) { _worker.Validate(context, cookieToken, formToken); } - public void Validate(HttpContext context, AntiForgeryTokenSet antiforgeryTokenSet) + public void Validate([NotNull] HttpContext context, AntiForgeryTokenSet antiForgeryTokenSet) { - Validate(context, antiforgeryTokenSet.CookieToken, antiforgeryTokenSet.FormToken); + Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs index 7e3a3c8342..454a7c4cbe 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryConfig.cs @@ -6,7 +6,6 @@ public static class AntiForgeryConfig { internal const string AntiForgeryTokenFieldName = "__RequestVerificationToken"; - private const string AntiForgeryCookieTokenName = "__RequestVerificationCookieToken"; private static string _cookieName; /// @@ -59,7 +58,7 @@ public static bool SuppressXFrameOptionsHeader // TODO: Replace the stub. private static string GetAntiForgeryCookieName() { - return AntiForgeryCookieTokenName; + return AntiForgeryTokenFieldName; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs index a2f9792903..89d9e07e41 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs @@ -12,7 +12,7 @@ internal sealed class AntiForgeryTokenSerializer : IAntiForgeryTokenSerializer private readonly IDataProtector _cryptoSystem; private const byte TokenVersion = 0x01; - internal AntiForgeryTokenSerializer(IDataProtector cryptoSystem) + internal AntiForgeryTokenSerializer([NotNull] IDataProtector cryptoSystem) { _cryptoSystem = cryptoSystem; } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index f38c8a8dc1..2afbf48111 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -11,7 +11,8 @@ internal sealed class AntiForgeryTokenStore : ITokenStore private readonly IAntiForgeryConfig _config; private readonly IAntiForgeryTokenSerializer _serializer; - internal AntiForgeryTokenStore(IAntiForgeryConfig config, IAntiForgeryTokenSerializer serializer) + internal AntiForgeryTokenStore([NotNull] IAntiForgeryConfig config, + [NotNull] IAntiForgeryTokenSerializer serializer) { _config = config; _serializer = serializer; diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index c820d3f9ad..cfa9cef6a0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -2,12 +2,11 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; -using System.Security.Principal; +using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Rendering; -using System.Security.Claims; -using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc { @@ -19,7 +18,11 @@ internal sealed class AntiForgeryWorker private readonly ITokenValidator _validator; private readonly ITokenGenerator _generator; - internal AntiForgeryWorker(IAntiForgeryTokenSerializer serializer, IAntiForgeryConfig config, ITokenStore tokenStore, ITokenGenerator generator, ITokenValidator validator) + internal AntiForgeryWorker([NotNull] IAntiForgeryTokenSerializer serializer, + [NotNull] IAntiForgeryConfig config, + [NotNull] ITokenStore tokenStore, + [NotNull] ITokenGenerator generator, + [NotNull] ITokenValidator validator) { _serializer = serializer; _config = config; @@ -62,12 +65,12 @@ private static ClaimsIdentity ExtractIdentity(HttpContext httpContext) if (httpContext != null) { ClaimsPrincipal user = httpContext.User; - + if (user != null) { // We only support ClaimsIdentity. // Todo remove this once httpContext.User moves to ClaimsIdentity. - return user.Identity as ClaimsIdentity; + return user.Identity as ClaimsIdentity; } } @@ -93,14 +96,14 @@ private AntiForgeryToken GetCookieTokenNoThrow(HttpContext httpContext) // 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 TagBuilder GetFormInputElement(HttpContext httpContext) + public TagBuilder GetFormInputElement([NotNull] HttpContext httpContext) { CheckSSLConfig(httpContext); - AntiForgeryToken oldCookieToken = GetCookieTokenNoThrow(httpContext); - AntiForgeryToken newCookieToken, formToken; - GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken); - + var oldCookieToken = GetCookieTokenNoThrow(httpContext); + var tokenSet = GetTokens(httpContext, oldCookieToken); + var newCookieToken = tokenSet.CookieToken; + var formToken = tokenSet.FormToken; if (newCookieToken != null) { // If a new cookie was generated, persist it. @@ -129,21 +132,21 @@ public TagBuilder GetFormInputElement(HttpContext httpContext) // '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 void GetTokens(HttpContext httpContext, string serializedOldCookieToken, out string serializedNewCookieToken, out string serializedFormToken) + public AntiForgeryTokenSet GetTokens([NotNull] HttpContext httpContext, string serializedOldCookieToken) { CheckSSLConfig(httpContext); - AntiForgeryToken oldCookieToken = DeserializeTokenNoThrow(serializedOldCookieToken); AntiForgeryToken newCookieToken, formToken; - GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken); + var tokenSet = GetTokens(httpContext, oldCookieToken); - serializedNewCookieToken = Serialize(newCookieToken); - serializedFormToken = Serialize(formToken); + var serializedNewCookieToken = Serialize(tokenSet.CookieToken); + var serializedFormToken = Serialize(tokenSet.FormToken); + return new AntiForgeryTokenSet(serializedFormToken, serializedNewCookieToken); } - private void GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken, out AntiForgeryToken newCookieToken, out AntiForgeryToken formToken) + private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken) { - newCookieToken = null; + AntiForgeryToken newCookieToken = null; if (!_validator.IsCookieTokenValid(oldCookieToken)) { // Need to make sure we're always operating with a good cookie token. @@ -151,7 +154,17 @@ private void GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken, } Contract.Assert(_validator.IsCookieTokenValid(oldCookieToken)); - formToken = _generator.GenerateFormToken(httpContext, ExtractIdentity(httpContext), oldCookieToken); + + AntiForgeryToken formToken = _generator. + GenerateFormToken(httpContext, + ExtractIdentity(httpContext), + oldCookieToken); + + return new AntiForgeryTokenSetInternal() + { + CookieToken = newCookieToken, + FormToken = formToken + }; } private string Serialize(AntiForgeryToken token) @@ -162,7 +175,7 @@ private string Serialize(AntiForgeryToken token) // [ 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(HttpContext httpContext) + public async Task ValidateAsync([NotNull] HttpContext httpContext) { CheckSSLConfig(httpContext); @@ -177,7 +190,7 @@ public async Task ValidateAsync(HttpContext httpContext) // [ ENTRY POINT ] // Given the serialized string representations of a cookie & form token, // validates that the pair is OK for this request. - public void Validate(HttpContext httpContext, string cookieToken, string formToken) + public void Validate([NotNull] HttpContext httpContext, string cookieToken, string formToken) { CheckSSLConfig(httpContext); @@ -188,5 +201,12 @@ public void Validate(HttpContext httpContext, string cookieToken, string formTok // Validate _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), deserializedCookieToken, deserializedFormToken); } + + 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.Mvc.Core/AntiForgery/TokenProvider.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs index 2de41f7ae8..4d00c9d64d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs @@ -13,7 +13,9 @@ internal sealed class TokenProvider : ITokenValidator, ITokenGenerator private readonly IAntiForgeryConfig _config; private readonly IAntiForgeryAdditionalDataProvider _additionalDataProvider; - internal TokenProvider(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor, IAntiForgeryAdditionalDataProvider additionalDataProvider) + internal TokenProvider(IAntiForgeryConfig config, + IClaimUidExtractor claimUidExtractor, + IAntiForgeryAdditionalDataProvider additionalDataProvider) { _config = config; _claimUidExtractor = claimUidExtractor; @@ -29,7 +31,9 @@ public AntiForgeryToken GenerateCookieToken() }; } - public AntiForgeryToken GenerateFormToken(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken cookieToken) + public AntiForgeryToken GenerateFormToken(HttpContext httpContext, + ClaimsIdentity identity, + AntiForgeryToken cookieToken) { Contract.Assert(IsCookieTokenValid(cookieToken)); @@ -38,10 +42,13 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, ClaimsIdentit SecurityToken = cookieToken.SecurityToken, IsSessionToken = false }; - + + bool isIdentityAuthenticated = false; + // populate Username and ClaimUid if (identity != null && identity.IsAuthenticated) { + isIdentityAuthenticated = true; formToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity)); if (formToken.ClaimUid == null) { @@ -55,12 +62,15 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, ClaimsIdentit formToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext); } - if (string.IsNullOrEmpty(formToken.Username) + if (isIdentityAuthenticated + && string.IsNullOrEmpty(formToken.Username) && formToken.ClaimUid == null && string.IsNullOrEmpty(formToken.AdditionalData)) { // Application says user is authenticated, but we have no identifier for the user. - throw new InvalidOperationException(Resources.FormatTokenValidator_AuthenticatedUserWithoutUsername(identity.GetType())); + throw new InvalidOperationException( + Resources. + FormatTokenValidator_AuthenticatedUserWithoutUsername(identity.GetType())); } return formToken; @@ -138,6 +148,11 @@ public void ValidateTokens(HttpContext httpContext, ClaimsIdentity identity, Ant private static BinaryBlob GetClaimUidBlob(string base64ClaimUid) { + if (base64ClaimUid == null) + { + return null; + } + return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid)); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index f658bfe7f8..3608028c9b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -11,7 +11,7 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); /// - /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. 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. /// internal static string TokenValidator_AuthenticatedUserWithoutUsername { @@ -19,7 +19,7 @@ internal static string TokenValidator_AuthenticatedUserWithoutUsername } /// - /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + /// The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. 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. /// internal static string FormatTokenValidator_AuthenticatedUserWithoutUsername(object p0) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs index f6f0a2472a..3419627605 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs @@ -62,6 +62,12 @@ HtmlString ActionLink( object routeValues, object htmlAttributes); + /// + /// Generates a hidden form field (anti-forgery token) that is validated when the form is submitted. + /// + /// + /// The generated form field (anti-forgery token). + /// HtmlString AntiForgeryToken(); /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 6a349538c1..f7e38e0ced 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -157,7 +157,7 @@ A claim of type '{0}' was not present on the provided ClaimsIdentity. - The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user. + The provided identity of type '{0}' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. 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. The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors. From c1431534c0acff6c43a8b0592b34f6513f5a776f Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Mon, 21 Apr 2014 18:48:27 -0700 Subject: [PATCH 5/7] adding a comment --- src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs index 75f695189f..d43c06f496 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/BinaryBlob.cs @@ -95,6 +95,8 @@ private static byte[] GenerateNewToken(int bitLength) return data; } + // Need to mark it with NoInlining and NoOptimization attributes to ensure that the + // operation runs in constant time. [MethodImplAttribute(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static bool AreByteArraysEqual(byte[] a, byte[] b) { From 20fdbe25a7603c73b5b68e8c27f6c8b90ed0857d Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Tue, 22 Apr 2014 15:24:16 -0700 Subject: [PATCH 6/7] Responding to comments and removing null check for CookieToken --- .../AntiForgery/AntiForgery.cs | 6 +++++- .../AntiForgery/AntiForgeryToken.cs | 18 ++++++------------ .../AntiForgery/AntiForgeryTokenSerializer.cs | 6 ------ .../AntiForgery/AntiForgeryTokenSet.cs | 8 +++----- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index 208163ce98..7378a4d253 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -53,6 +53,10 @@ public HtmlString GetHtml([NotNull] HttpContext context) /// The anti-forgery token - if any - that already existed /// for this request. May be null. The anti-forgery system will try to reuse this cookie /// value when generating a matching form token. + /// + /// Unlike the GetHtml(HttpContext context) method, this method has no side effect. The caller + /// is responsible for setting the response cookie and injecting the returned + /// form token as appropriate. /// public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCookieToken) { @@ -66,7 +70,7 @@ public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCo /// /// Validates an anti-forgery token that was supplied for this request. - /// The anti-forgery token may be generated by calling GetHtml(). + /// The anti-forgery token may be generated by calling GetHtml(HttpContext context). /// /// The HTTP context associated with the current call. public async Task ValidateAsync([NotNull] HttpContext context) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs index fadbdebb00..b1bb1b58a3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs @@ -7,19 +7,16 @@ internal sealed class AntiForgeryToken internal const int SecurityTokenBitLength = 128; internal const int ClaimUidBitLength = 256; - private string _additionalData; + private string _additionalData = string.Empty; + private string _username = string.Empty; private BinaryBlob _securityToken; - private string _username; public string AdditionalData { - get - { - return _additionalData ?? String.Empty; - } + get { return _additionalData; } set { - _additionalData = value; + _additionalData = value ?? string.Empty; } } @@ -45,13 +42,10 @@ public BinaryBlob SecurityToken public string Username { - get - { - return _username ?? String.Empty; - } + get { return _username; } set { - _username = value; + _username = value ?? string.Empty; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs index 89d9e07e41..036431637a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs @@ -38,9 +38,7 @@ public AntiForgeryToken Deserialize(string serializedToken) // swallow all exceptions - homogenize error if something went wrong } - // TODO: Return proper exception here. // if we reached this point, something went wrong deserializing - // throw HttpAntiForgeryException.CreateDeserializationFailedException(); throw new InvalidOperationException(Resources.AntiForgeryToken_DeserializationFailed); } @@ -128,8 +126,6 @@ public string Serialize([NotNull] AntiForgeryToken token) } } - // TODO: This is temporary replacement for HttpServerUtility.UrlTokenEncode. - // This will be removed when webutils has this. private string UrlTokenEncode(byte[] input) { var base64String = Convert.ToBase64String(input); @@ -161,8 +157,6 @@ private string UrlTokenEncode(byte[] input) return sb.ToString(); } - // TODO: This is temporary replacement for HttpServerUtility.UrlTokenDecode. - // This will be removed when webutils has this. private byte[] UrlTokenDecode(string input) { var sb = new StringBuilder(); diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs index 46013b033d..867847e251 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSet.cs @@ -12,17 +12,15 @@ public AntiForgeryTokenSet(string formToken, string cookieToken) throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, formToken); } - if (string.IsNullOrEmpty(cookieToken)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, cookieToken); - } - FormToken = formToken; CookieToken = cookieToken; } public string FormToken { get; private set; } + // The cookie token is allowed to be null. + // This would be the case when the old cookie token is still valid. + // In such cases a call to GetTokens would return a token set with null cookie token. public string CookieToken { get; private set; } } } \ No newline at end of file From 08a1c5069f22fc3f1294b99ae73eaa128da3b664 Mon Sep 17 00:00:00 2001 From: harshgMSFT Date: Wed, 23 Apr 2014 14:41:08 -0700 Subject: [PATCH 7/7] Responding to comments : var changes --- .../AntiForgery/AntiForgery.cs | 3 ++- .../AntiForgery/AntiForgeryTokenSerializer.cs | 18 ++++++++++-------- .../AntiForgery/AntiForgeryTokenStore.cs | 2 +- .../AntiForgery/AntiForgeryWorker.cs | 8 ++++---- .../AntiForgery/DefaultClaimUidExtractor.cs | 6 +++--- .../AntiForgery/TokenProvider.cs | 2 +- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index 7378a4d253..ca7ff68dea 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -35,7 +35,8 @@ public AntiForgery([NotNull] IClaimUidExtractor claimUidExtractor, /// An HTML string corresponding to an <input type="hidden"> /// element. This element should be put inside a <form>. /// - /// This method has a side effect: A response cookie is set if there is no valid cookie associated with the request. + /// This method has a side effect: + /// A response cookie is set if there is no valid cookie associated with the request. /// public HtmlString GetHtml([NotNull] HttpContext context) { diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs index 036431637a..d64b6e3e7b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenSerializer.cs @@ -19,6 +19,7 @@ internal AntiForgeryTokenSerializer([NotNull] IDataProtector cryptoSystem) public AntiForgeryToken Deserialize(string serializedToken) { + Exception innerException = null; try { using (MemoryStream stream = new MemoryStream(UrlTokenDecode(serializedToken))) @@ -33,13 +34,14 @@ public AntiForgeryToken Deserialize(string serializedToken) } } } - catch + catch(Exception ex) { // swallow all exceptions - homogenize error if something went wrong + innerException = ex; } // if we reached this point, something went wrong deserializing - throw new InvalidOperationException(Resources.AntiForgeryToken_DeserializationFailed); + throw new InvalidOperationException(Resources.AntiForgeryToken_DeserializationFailed, innerException); } /* The serialized format of the anti-XSRF token is as follows: @@ -57,14 +59,14 @@ public AntiForgeryToken Deserialize(string serializedToken) private static AntiForgeryToken DeserializeImpl(BinaryReader reader) { // we can only consume tokens of the same serialized version that we generate - byte embeddedVersion = reader.ReadByte(); + var embeddedVersion = reader.ReadByte(); if (embeddedVersion != TokenVersion) { return null; } - AntiForgeryToken deserializedToken = new AntiForgeryToken(); - byte[] securityTokenBytes = reader.ReadBytes(AntiForgeryToken.SecurityTokenBitLength / 8); + var deserializedToken = new AntiForgeryToken(); + var securityTokenBytes = reader.ReadBytes(AntiForgeryToken.SecurityTokenBitLength / 8); deserializedToken.SecurityToken = new BinaryBlob(AntiForgeryToken.SecurityTokenBitLength, securityTokenBytes); deserializedToken.IsSessionToken = reader.ReadBoolean(); @@ -73,7 +75,7 @@ private static AntiForgeryToken DeserializeImpl(BinaryReader reader) bool isClaimsBased = reader.ReadBoolean(); if (isClaimsBased) { - byte[] claimUidBytes = reader.ReadBytes(AntiForgeryToken.ClaimUidBitLength / 8); + var claimUidBytes = reader.ReadBytes(AntiForgeryToken.ClaimUidBitLength / 8); deserializedToken.ClaimUid = new BinaryBlob(AntiForgeryToken.ClaimUidBitLength, claimUidBytes); } else @@ -96,9 +98,9 @@ private static AntiForgeryToken DeserializeImpl(BinaryReader reader) public string Serialize([NotNull] AntiForgeryToken token) { - using (MemoryStream stream = new MemoryStream()) + using (var stream = new MemoryStream()) { - using (BinaryWriter writer = new BinaryWriter(stream)) + using (var writer = new BinaryWriter(stream)) { writer.Write(TokenVersion); writer.Write(token.SecurityToken.GetData()); diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index 2afbf48111..90c39c95c8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -46,7 +46,7 @@ public async Task GetFormTokenAsync(HttpContext httpContext) public void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token) { string serializedToken = _serializer.Serialize(token); - CookieOptions options = new CookieOptions() { HttpOnly = true }; + var options = new CookieOptions() { HttpOnly = true }; // Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default // value of newCookie.Secure is poulated out of band. diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index cfa9cef6a0..437b548815 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -180,8 +180,8 @@ public async Task ValidateAsync([NotNull] HttpContext httpContext) CheckSSLConfig(httpContext); // Extract cookie & form tokens - AntiForgeryToken cookieToken = _tokenStore.GetCookieToken(httpContext); - AntiForgeryToken formToken = await _tokenStore.GetFormTokenAsync(httpContext); + var cookieToken = _tokenStore.GetCookieToken(httpContext); + var formToken = await _tokenStore.GetFormTokenAsync(httpContext); // Validate _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken); @@ -195,8 +195,8 @@ public void Validate([NotNull] HttpContext httpContext, string cookieToken, stri CheckSSLConfig(httpContext); // Extract cookie & form tokens - AntiForgeryToken deserializedCookieToken = DeserializeToken(cookieToken); - AntiForgeryToken deserializedFormToken = DeserializeToken(formToken); + var deserializedCookieToken = DeserializeToken(cookieToken); + var deserializedFormToken = DeserializeToken(formToken); // Validate _validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), deserializedCookieToken, deserializedFormToken); diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs index 8e4ff2fea8..f640030c1f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/DefaultClaimUidExtractor.cs @@ -53,9 +53,9 @@ private static IEnumerable GetUniqueIdentifierParameters(ClaimsIdentity private static byte[] ComputeSHA256(IEnumerable parameters) { - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - using (BinaryWriter bw = new BinaryWriter(ms)) + using (var bw = new BinaryWriter(ms)) { foreach (string parameter in parameters) { @@ -64,7 +64,7 @@ private static byte[] ComputeSHA256(IEnumerable parameters) bw.Flush(); - using (SHA256 sha256 = SHA256.Create()) + using (var sha256 = SHA256.Create()) { byte[] retVal = sha256.ComputeHash(ms.ToArray(), 0, checked((int)ms.Length)); return retVal; diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs index 4d00c9d64d..4e65ab6507 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/TokenProvider.cs @@ -37,7 +37,7 @@ public AntiForgeryToken GenerateFormToken(HttpContext httpContext, { Contract.Assert(IsCookieTokenValid(cookieToken)); - AntiForgeryToken formToken = new AntiForgeryToken() + var formToken = new AntiForgeryToken() { SecurityToken = cookieToken.SecurityToken, IsSessionToken = false