Introducing AntiForgery System :
- Config is cleaned up to not have IAdditionalDataProvider and SuppressIdentityChecks. - Added a DefaultClaimUidExtractor which looks for NameIdentifier and if not present serializes entire claims. - Added HtmlHelper. - AntiForgery now returns an AntiForgeryTokenSet which represents a tuple of cookie and form tokens.
This commit is contained in:
parent
d61915149c
commit
f26cc51e2e
|
|
@ -0,0 +1,98 @@
|
|||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the anti-forgery system, which provides protection against
|
||||
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
|
||||
/// </summary>
|
||||
public sealed class AntiForgery
|
||||
{
|
||||
private static readonly string _purpose = "Microsoft.AspNet.Mvc.AntiXsrf.AntiForgeryToken.v1";
|
||||
private readonly AntiForgeryWorker _worker;
|
||||
|
||||
public AntiForgery([NotNull] IClaimUidExtractor claimUidExtractor,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] IAntiForgeryAdditionalDataProvider additionalDataProvider)
|
||||
{
|
||||
// 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 TokenProvider(config, claimUidExtractor, additionalDataProvider);
|
||||
_worker = new AntiForgeryWorker(serializer, config, tokenStore, tokenProvider, tokenProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an anti-forgery token for this request. This token can
|
||||
/// be validated by calling the Validate() method.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||
/// <returns>An HTML string corresponding to an <input type="hidden">
|
||||
/// element. This element should be put inside a <form>.</returns>
|
||||
/// <remarks>
|
||||
/// This method has a side effect:
|
||||
/// A response cookie is set if there is no valid cookie associated with the request.
|
||||
/// </remarks>
|
||||
public HtmlString GetHtml([NotNull] HttpContext context)
|
||||
{
|
||||
TagBuilder builder = _worker.GetFormInputElement(context);
|
||||
return builder.ToHtmlString(TagRenderMode.SelfClosing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an anti-forgery token pair (cookie and form token) for this request.
|
||||
/// This method is similar to GetHtml(HttpContext context), but this method gives the caller control
|
||||
/// over how to persist the returned values. To validate these tokens, call the
|
||||
/// appropriate overload of Validate.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||
/// <param name="oldCookieToken">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.</param>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public AntiForgeryTokenSet GetTokens([NotNull] HttpContext context, string oldCookieToken)
|
||||
{
|
||||
// Will contain a new cookie value if the old cookie token
|
||||
// was null or invalid. If this value is non-null when the method completes, the caller
|
||||
// must persist this value in the form of a response cookie, and the existing cookie value
|
||||
// should be discarded. If this value is null when the method completes, the existing
|
||||
// cookie value was valid and needn't be modified.
|
||||
return _worker.GetTokens(context, oldCookieToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an anti-forgery token that was supplied for this request.
|
||||
/// The anti-forgery token may be generated by calling GetHtml(HttpContext context).
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||
public async Task ValidateAsync([NotNull] HttpContext context)
|
||||
{
|
||||
await _worker.ValidateAsync(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an anti-forgery token pair that was generated by the GetTokens method.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||
/// <param name="cookieToken">The token that was supplied in the request cookie.</param>
|
||||
/// <param name="formToken">The token that was supplied in the request form body.</param>
|
||||
public void Validate([NotNull] HttpContext context, string cookieToken, string formToken)
|
||||
{
|
||||
_worker.Validate(context, cookieToken, formToken);
|
||||
}
|
||||
|
||||
public void Validate([NotNull] HttpContext context, AntiForgeryTokenSet antiForgeryTokenSet)
|
||||
{
|
||||
Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides programmatic configuration for the anti-forgery token system.
|
||||
/// </summary>
|
||||
public static class AntiForgeryConfig
|
||||
{
|
||||
internal const string AntiForgeryTokenFieldName = "__RequestVerificationToken";
|
||||
private static string _cookieName;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the cookie that is used by the anti-forgery
|
||||
/// system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If an explicit name is not provided, the system will automatically
|
||||
/// generate a name.
|
||||
/// </remarks>
|
||||
public static string CookieName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cookieName == null)
|
||||
{
|
||||
_cookieName = GetAntiForgeryCookieName();
|
||||
}
|
||||
return _cookieName;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cookieName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static bool RequireSsl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static bool SuppressXFrameOptionsHeader
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// TODO: Replace the stub.
|
||||
private static string GetAntiForgeryCookieName()
|
||||
{
|
||||
return AntiForgeryTokenFieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public sealed class AntiForgeryConfigWrapper : IAntiForgeryConfig
|
||||
{
|
||||
public string CookieName
|
||||
{
|
||||
get { return AntiForgeryConfig.CookieName; }
|
||||
}
|
||||
|
||||
public string FormFieldName
|
||||
{
|
||||
get { return AntiForgeryConfig.AntiForgeryTokenFieldName; }
|
||||
}
|
||||
|
||||
public bool RequireSSL
|
||||
{
|
||||
get { return AntiForgeryConfig.RequireSsl; }
|
||||
}
|
||||
|
||||
public bool SuppressXFrameOptionsHeader
|
||||
{
|
||||
get { return AntiForgeryConfig.SuppressXFrameOptionsHeader; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
internal sealed class AntiForgeryToken
|
||||
{
|
||||
internal const int SecurityTokenBitLength = 128;
|
||||
internal const int ClaimUidBitLength = 256;
|
||||
|
||||
private string _additionalData = string.Empty;
|
||||
private string _username = string.Empty;
|
||||
private BinaryBlob _securityToken;
|
||||
|
||||
public string AdditionalData
|
||||
{
|
||||
get { return _additionalData; }
|
||||
set
|
||||
{
|
||||
_additionalData = value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
set
|
||||
{
|
||||
_username = value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
internal sealed class AntiForgeryTokenSerializer : IAntiForgeryTokenSerializer
|
||||
{
|
||||
private readonly IDataProtector _cryptoSystem;
|
||||
private const byte TokenVersion = 0x01;
|
||||
|
||||
internal AntiForgeryTokenSerializer([NotNull] IDataProtector cryptoSystem)
|
||||
{
|
||||
_cryptoSystem = cryptoSystem;
|
||||
}
|
||||
|
||||
public AntiForgeryToken Deserialize(string serializedToken)
|
||||
{
|
||||
Exception innerException = null;
|
||||
try
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream(UrlTokenDecode(serializedToken)))
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
AntiForgeryToken token = DeserializeImpl(reader);
|
||||
if (token != null)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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, innerException);
|
||||
}
|
||||
|
||||
/* 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
|
||||
var embeddedVersion = reader.ReadByte();
|
||||
if (embeddedVersion != TokenVersion)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var deserializedToken = new AntiForgeryToken();
|
||||
var 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)
|
||||
{
|
||||
var 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 (var stream = new MemoryStream())
|
||||
{
|
||||
using (var 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string UrlTokenEncode(byte[] input)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
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);
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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([NotNull] IAntiForgeryConfig config,
|
||||
[NotNull] 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 async Task<AntiForgeryToken> GetFormTokenAsync(HttpContext httpContext)
|
||||
{
|
||||
var form = await httpContext.Request.GetFormAsync();
|
||||
string value = form[_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);
|
||||
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.
|
||||
if (_config.RequireSSL)
|
||||
{
|
||||
options.Secure = true;
|
||||
}
|
||||
|
||||
httpContext.Response.Cookies.Append(_config.CookieName, serializedToken, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
|
||||
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([NotNull] IAntiForgeryTokenSerializer serializer,
|
||||
[NotNull] IAntiForgeryConfig config,
|
||||
[NotNull] ITokenStore tokenStore,
|
||||
[NotNull] ITokenGenerator generator,
|
||||
[NotNull] 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(Resources.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 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 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 <form>. This method has a side effect: it may set a response
|
||||
// cookie.
|
||||
public TagBuilder GetFormInputElement([NotNull] HttpContext httpContext)
|
||||
{
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
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.
|
||||
_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" });
|
||||
}
|
||||
|
||||
// <input type="hidden" name="__AntiForgeryToken" value="..." />
|
||||
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 AntiForgeryTokenSet GetTokens([NotNull] HttpContext httpContext, string serializedOldCookieToken)
|
||||
{
|
||||
CheckSSLConfig(httpContext);
|
||||
AntiForgeryToken oldCookieToken = DeserializeTokenNoThrow(serializedOldCookieToken);
|
||||
var tokenSet = GetTokens(httpContext, oldCookieToken);
|
||||
|
||||
var serializedNewCookieToken = Serialize(tokenSet.CookieToken);
|
||||
var serializedFormToken = Serialize(tokenSet.FormToken);
|
||||
return new AntiForgeryTokenSet(serializedFormToken, serializedNewCookieToken);
|
||||
}
|
||||
|
||||
private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken)
|
||||
{
|
||||
AntiForgeryToken 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));
|
||||
|
||||
AntiForgeryToken formToken = _generator.
|
||||
GenerateFormToken(httpContext,
|
||||
ExtractIdentity(httpContext),
|
||||
oldCookieToken);
|
||||
|
||||
return new AntiForgeryTokenSetInternal()
|
||||
{
|
||||
CookieToken = newCookieToken,
|
||||
FormToken = formToken
|
||||
};
|
||||
}
|
||||
|
||||
private string Serialize(AntiForgeryToken token)
|
||||
{
|
||||
return (token != null) ? _serializer.Serialize(token) : null;
|
||||
}
|
||||
|
||||
// [ ENTRY POINT ]
|
||||
// Given an HttpContext, validates that the anti-XSRF tokens contained
|
||||
// in the cookies & form are OK for this request.
|
||||
public async Task ValidateAsync([NotNull] HttpContext httpContext)
|
||||
{
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
// Extract cookie & form tokens
|
||||
var cookieToken = _tokenStore.GetCookieToken(httpContext);
|
||||
var formToken = await _tokenStore.GetFormTokenAsync(httpContext);
|
||||
|
||||
// Validate
|
||||
_validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken);
|
||||
}
|
||||
|
||||
// [ ENTRY POINT ]
|
||||
// Given the serialized string representations of a cookie & form token,
|
||||
// validates that the pair is OK for this request.
|
||||
public void Validate([NotNull] HttpContext httpContext, string cookieToken, string formToken)
|
||||
{
|
||||
CheckSSLConfig(httpContext);
|
||||
|
||||
// Extract cookie & form tokens
|
||||
var deserializedCookieToken = DeserializeToken(cookieToken);
|
||||
var deserializedFormToken = DeserializeToken(formToken);
|
||||
|
||||
// Validate
|
||||
_validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), deserializedCookieToken, deserializedFormToken);
|
||||
}
|
||||
|
||||
private class AntiForgeryTokenSetInternal
|
||||
{
|
||||
public AntiForgeryToken FormToken { get; set; }
|
||||
|
||||
public AntiForgeryToken CookieToken { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// Represents a binary blob (token) that contains random data.
|
||||
// Useful for binary data inside a serialized stream.
|
||||
[DebuggerDisplay("{DebuggerString}")]
|
||||
internal sealed class BinaryBlob : IEquatable<BinaryBlob>
|
||||
{
|
||||
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<byte>(data));
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// Can extract unique identifers for a claims-based identity
|
||||
public class DefaultClaimUidExtractor : IClaimUidExtractor
|
||||
{
|
||||
public string ExtractClaimUid(ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
if (claimsIdentity == null || !claimsIdentity.IsAuthenticated)
|
||||
{
|
||||
// Skip anonymous users
|
||||
return null;
|
||||
}
|
||||
|
||||
var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity);
|
||||
byte[] claimUidBytes = ComputeSHA256(uniqueIdentifierParameters);
|
||||
return Convert.ToBase64String(claimUidBytes);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier,
|
||||
nameIdentifierClaim.Value
|
||||
};
|
||||
}
|
||||
|
||||
// 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<string>();
|
||||
foreach (var claim in claims)
|
||||
{
|
||||
identifierParameters.Add(claim.Type);
|
||||
identifierParameters.Add(claim.Value);
|
||||
}
|
||||
|
||||
return identifierParameters;
|
||||
}
|
||||
|
||||
private static byte[] ComputeSHA256(IEnumerable<string> parameters)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var bw = new BinaryWriter(ms))
|
||||
{
|
||||
foreach (string parameter in parameters)
|
||||
{
|
||||
bw.Write(parameter); // also writes the length as a prefix; unambiguous
|
||||
}
|
||||
|
||||
bw.Flush();
|
||||
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] retVal = sha256.ComputeHash(ms.ToArray(), 0, checked((int)ms.Length));
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The anti-forgery system already embeds the client's username within the
|
||||
/// generated tokens. This interface provides and consumes <em>supplemental</em>
|
||||
/// data. If an incoming anti-forgery token contains supplemental data but no
|
||||
/// additional data provider is configured, the supplemental data will not be
|
||||
/// validated.
|
||||
/// </remarks>
|
||||
public interface IAntiForgeryAdditionalDataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides additional data to be stored for the anti-forgery tokens generated
|
||||
/// during this request.
|
||||
/// </summary>
|
||||
/// <param name="context">Information about the current request.</param>
|
||||
/// <returns>Supplemental data to embed within the anti-forgery token.</returns>
|
||||
string GetAdditionalData(HttpContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Validates additional data that was embedded inside an incoming anti-forgery
|
||||
/// token.
|
||||
/// </summary>
|
||||
/// <param name="context">Information about the current request.</param>
|
||||
/// <param name="additionalData">Supplemental data that was embedded within the token.</param>
|
||||
/// <returns>True if the data is valid; false if the data is invalid.</returns>
|
||||
bool ValidateAdditionalData(HttpContext context, string additionalData);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// Provides configuration information about the anti-forgery system.
|
||||
public interface IAntiForgeryConfig
|
||||
{
|
||||
// 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 X-FRAME-OPTIONS header.
|
||||
bool SuppressXFrameOptionsHeader { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// Can extract unique identifers for a claims-based identity
|
||||
public interface IClaimUidExtractor
|
||||
{
|
||||
string ExtractClaimUid(ClaimsIdentity identity);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using System.Security.Claims;
|
||||
|
||||
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, ClaimsIdentity identity, AntiForgeryToken cookieToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// Provides an abstraction around how tokens are persisted and retrieved for a request
|
||||
internal interface ITokenStore
|
||||
{
|
||||
AntiForgeryToken GetCookieToken(HttpContext httpContext);
|
||||
Task<AntiForgeryToken> GetFormTokenAsync(HttpContext httpContext);
|
||||
void SaveCookieToken(HttpContext httpContext, AntiForgeryToken token);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using System.Security.Claims;
|
||||
|
||||
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, ClaimsIdentity identity, AntiForgeryToken cookieToken, AntiForgeryToken formToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
internal sealed class TokenProvider : ITokenValidator, ITokenGenerator
|
||||
{
|
||||
private readonly IClaimUidExtractor _claimUidExtractor;
|
||||
private readonly IAntiForgeryConfig _config;
|
||||
private readonly IAntiForgeryAdditionalDataProvider _additionalDataProvider;
|
||||
|
||||
internal TokenProvider(IAntiForgeryConfig config,
|
||||
IClaimUidExtractor claimUidExtractor,
|
||||
IAntiForgeryAdditionalDataProvider additionalDataProvider)
|
||||
{
|
||||
_config = config;
|
||||
_claimUidExtractor = claimUidExtractor;
|
||||
_additionalDataProvider = additionalDataProvider;
|
||||
}
|
||||
|
||||
public AntiForgeryToken GenerateCookieToken()
|
||||
{
|
||||
return new AntiForgeryToken()
|
||||
{
|
||||
// SecurityToken will be populated automatically.
|
||||
IsSessionToken = true
|
||||
};
|
||||
}
|
||||
|
||||
public AntiForgeryToken GenerateFormToken(HttpContext httpContext,
|
||||
ClaimsIdentity identity,
|
||||
AntiForgeryToken cookieToken)
|
||||
{
|
||||
Contract.Assert(IsCookieTokenValid(cookieToken));
|
||||
|
||||
var formToken = new AntiForgeryToken()
|
||||
{
|
||||
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)
|
||||
{
|
||||
formToken.Username = identity.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// populate AdditionalData
|
||||
if (_additionalDataProvider != null)
|
||||
{
|
||||
formToken.AdditionalData = _additionalDataProvider.GetAdditionalData(httpContext);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
return formToken;
|
||||
}
|
||||
|
||||
public bool IsCookieTokenValid(AntiForgeryToken cookieToken)
|
||||
{
|
||||
return (cookieToken != null && cookieToken.IsSessionToken);
|
||||
}
|
||||
|
||||
public void ValidateTokens(HttpContext httpContext, ClaimsIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
|
||||
{
|
||||
// Were the tokens even present at all?
|
||||
if (sessionToken == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatAntiForgeryToken_CookieMissing(_config.CookieName));
|
||||
}
|
||||
if (fieldToken == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatAntiForgeryToken_FormFieldMissing(_config.FormFieldName));
|
||||
}
|
||||
|
||||
// Do the tokens have the correct format?
|
||||
if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
|
||||
{
|
||||
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))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.AntiForgeryToken_SecurityTokenMismatch);
|
||||
}
|
||||
|
||||
// Is the incoming token meant for the current user?
|
||||
string currentUsername = string.Empty;
|
||||
BinaryBlob currentClaimUid = null;
|
||||
|
||||
if (identity != null && identity.IsAuthenticated)
|
||||
{
|
||||
currentClaimUid = GetClaimUidBlob(_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))
|
||||
{
|
||||
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 (_additionalDataProvider != null && !_additionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.AntiForgeryToken_AdditionalDataCheckFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryBlob GetClaimUidBlob(string base64ClaimUid)
|
||||
{
|
||||
if (base64ClaimUid == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,25 @@
|
|||
<Compile Include="ActionResults\RedirectToActionResult.cs" />
|
||||
<Compile Include="ActionResults\RedirectToRouteResult.cs" />
|
||||
<Compile Include="ActionResults\ViewResult.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgery.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryConfig.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryConfigWrapper.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryToken.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryTokenSerializer.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryTokenSet.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryTokenStore.cs" />
|
||||
<Compile Include="AntiForgery\AntiForgeryWorker.cs" />
|
||||
<Compile Include="AntiForgery\BinaryBlob.cs" />
|
||||
<Compile Include="AntiForgery\DefaultClaimUidExtractor.cs" />
|
||||
<Compile Include="AntiForgery\DefaultAntiForgeryAdditionalDataProvider.cs" />
|
||||
<Compile Include="AntiForgery\IAntiForgeryAdditionalDataProvider.cs" />
|
||||
<Compile Include="AntiForgery\IAntiForgeryConfig.cs" />
|
||||
<Compile Include="AntiForgery\IAntiForgeryTokenSerializer.cs" />
|
||||
<Compile Include="AntiForgery\IClaimUidExtractor.cs" />
|
||||
<Compile Include="AntiForgery\ITokenGenerator.cs" />
|
||||
<Compile Include="AntiForgery\ITokenStore.cs" />
|
||||
<Compile Include="AntiForgery\ITokenValidator.cs" />
|
||||
<Compile Include="AntiForgery\TokenValidator.cs" />
|
||||
<Compile Include="Areas\AreaAttribute.cs" />
|
||||
<Compile Include="Areas\ReflectedRouteConstraintsActionDescriptorProvider.cs" />
|
||||
<Compile Include="BodyParameterInfo.cs" />
|
||||
|
|
|
|||
|
|
@ -10,6 +10,54 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string TokenValidator_AuthenticatedUserWithoutUsername
|
||||
{
|
||||
get { return GetString("TokenValidator_AuthenticatedUserWithoutUsername"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatTokenValidator_AuthenticatedUserWithoutUsername(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TokenValidator_AuthenticatedUserWithoutUsername"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A claim of type '{0}' was not present on the provided ClaimsIdentity.
|
||||
/// </summary>
|
||||
internal static string ClaimUidExtractor_ClaimNotPresent
|
||||
{
|
||||
get { return GetString("ClaimUidExtractor_ClaimNotPresent"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A claim of type '{0}' was not present on the provided ClaimsIdentity.
|
||||
/// </summary>
|
||||
internal static string FormatClaimUidExtractor_ClaimNotPresent(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ClaimUidExtractor_ClaimNotPresent"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string ClaimUidExtractor_DefaultClaimsNotPresent
|
||||
{
|
||||
get { return GetString("ClaimUidExtractor_DefaultClaimsNotPresent"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatClaimUidExtractor_DefaultClaimsNotPresent()
|
||||
{
|
||||
return GetString("ClaimUidExtractor_DefaultClaimsNotPresent");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
|
|
@ -42,6 +90,150 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token failed a custom data check.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_AdditionalDataCheckFailed
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_AdditionalDataCheckFailed"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token failed a custom data check.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_AdditionalDataCheckFailed()
|
||||
{
|
||||
return GetString("AntiForgeryToken_AdditionalDataCheckFailed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token was meant for a different claims-based user than the current user.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_ClaimUidMismatch
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_ClaimUidMismatch"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token was meant for a different claims-based user than the current user.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_ClaimUidMismatch()
|
||||
{
|
||||
return GetString("AntiForgeryToken_ClaimUidMismatch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required anti-forgery cookie "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_CookieMissing
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_CookieMissing"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required anti-forgery cookie "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_CookieMissing(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_CookieMissing"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_DeserializationFailed
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_DeserializationFailed"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_DeserializationFailed()
|
||||
{
|
||||
return GetString("AntiForgeryToken_DeserializationFailed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required anti-forgery form field "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_FormFieldMissing
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_FormFieldMissing"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The required anti-forgery form field "{0}" is not present.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_FormFieldMissing(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_FormFieldMissing"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The anti-forgery cookie token and form field token do not match.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_SecurityTokenMismatch
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_SecurityTokenMismatch"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The anti-forgery cookie token and form field token do not match.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_SecurityTokenMismatch()
|
||||
{
|
||||
return GetString("AntiForgeryToken_SecurityTokenMismatch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_TokensSwapped
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_TokensSwapped"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_TokensSwapped(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_TokensSwapped"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token was meant for user "{0}", but the current user is "{1}".
|
||||
/// </summary>
|
||||
internal static string AntiForgeryToken_UsernameMismatch
|
||||
{
|
||||
get { return GetString("AntiForgeryToken_UsernameMismatch"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided anti-forgery token was meant for user "{0}", but the current user is "{1}".
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryToken_UsernameMismatch(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("AntiForgeryToken_UsernameMismatch"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.
|
||||
/// </summary>
|
||||
internal static string AntiForgeryWorker_RequireSSL
|
||||
{
|
||||
get { return GetString("AntiForgeryWorker_RequireSSL"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.
|
||||
/// </summary>
|
||||
internal static string FormatAntiForgeryWorker_RequireSSL()
|
||||
{
|
||||
return GetString("AntiForgeryWorker_RequireSSL");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
private readonly IUrlHelper _urlHelper;
|
||||
private readonly IViewEngine _viewEngine;
|
||||
private readonly AntiForgery _antiForgeryInstance;
|
||||
|
||||
private ViewContext _viewContext;
|
||||
|
||||
|
|
@ -39,11 +40,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
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 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
ViewContext = viewContext;
|
||||
}
|
||||
|
||||
public HtmlString AntiForgeryToken()
|
||||
{
|
||||
return _antiForgeryInstance.GetHtml(ViewContext.HttpContext);
|
||||
}
|
||||
|
||||
public MvcForm BeginForm(string actionName, string controllerName, object routeValues, FormMethod method,
|
||||
object htmlAttributes)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,14 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
string fragment,
|
||||
object routeValues,
|
||||
object htmlAttributes);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hidden form field (anti-forgery token) that is validated when the form is submitted.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The generated form field (anti-forgery token).
|
||||
/// </returns>
|
||||
HtmlString AntiForgeryToken();
|
||||
|
||||
/// <summary>
|
||||
/// Writes an opening <form> tag to the response. When the user submits the form,
|
||||
|
|
|
|||
|
|
@ -117,12 +117,48 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AntiForgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
|
||||
<value>The provided anti-forgery token failed a custom data check.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_ClaimUidMismatch" xml:space="preserve">
|
||||
<value>The provided anti-forgery token was meant for a different claims-based user than the current user.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_CookieMissing" xml:space="preserve">
|
||||
<value>The required anti-forgery cookie "{0}" is not present.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_DeserializationFailed" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_FormFieldMissing" xml:space="preserve">
|
||||
<value>The required anti-forgery form field "{0}" is not present.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_SecurityTokenMismatch" xml:space="preserve">
|
||||
<value>The anti-forgery cookie token and form field token do not match.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_TokensSwapped" xml:space="preserve">
|
||||
<value>Validation of the provided anti-forgery token failed. The cookie "{0}" and the form field "{1}" were swapped.</value>
|
||||
</data>
|
||||
<data name="AntiForgeryToken_UsernameMismatch" xml:space="preserve">
|
||||
<value>The provided anti-forgery token was meant for user "{0}", but the current user is "{1}".</value>
|
||||
</data>
|
||||
<data name="AntiForgeryWorker_RequireSSL" xml:space="preserve">
|
||||
<value>The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.</value>
|
||||
</data>
|
||||
<data name="ActionExecutor_WrappedTaskInstance" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="ActionExecutor_UnexpectedTaskInstance" xml:space="preserve">
|
||||
<value>The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.</value>
|
||||
</data>
|
||||
<!--<data name="ClaimUidExtractor_DefaultClaimsNotPresent" xml:space="preserve">
|
||||
<value>A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' 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.</value>
|
||||
</data>-->
|
||||
<data name="ClaimUidExtractor_ClaimNotPresent" xml:space="preserve">
|
||||
<value>A claim of type '{0}' was not present on the provided ClaimsIdentity.</value>
|
||||
</data>
|
||||
<data name="TokenValidator_AuthenticatedUserWithoutUsername" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="ReflectedActionFilterEndPoint_UnexpectedActionDescriptor" xml:space="preserve">
|
||||
<value>The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"Microsoft.AspNet.Security": "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": {
|
||||
"net45": {},
|
||||
|
|
@ -37,6 +38,9 @@
|
|||
"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",
|
||||
"System.Threading.Tasks": "4.0.10.0"
|
||||
|
|
|
|||
|
|
@ -47,4 +47,4 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNet.Mvc.Razor;
|
|||
using Microsoft.AspNet.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Security.Authorization;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
|
|
@ -78,6 +79,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Transient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||
|
||||
yield return describe.Transient<IAuthorizationService, DefaultAuthorizationService>();
|
||||
yield return describe.Singleton<IClaimUidExtractor, DefaultClaimUidExtractor>();
|
||||
yield return describe.Singleton<AntiForgery, AntiForgery>();
|
||||
yield return describe.Singleton<IAntiForgeryAdditionalDataProvider, DefaultAntiForgeryAdditionalDataProvider>();
|
||||
|
||||
yield return
|
||||
describe.Describe(
|
||||
|
|
|
|||
Loading…
Reference in New Issue