Code dump of Antiforgery code
This commit is contained in:
parent
68ee820b5d
commit
3dc2663c35
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Antiforgery.Internal;
|
||||||
|
using Microsoft.AspNet.DataProtection;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.WebUtilities;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
using Microsoft.Framework.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <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.Antiforgery.AntiforgeryToken.v1";
|
||||||
|
private readonly AntiforgeryWorker _worker;
|
||||||
|
|
||||||
|
public Antiforgery(
|
||||||
|
[NotNull] IClaimUidExtractor claimUidExtractor,
|
||||||
|
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||||
|
[NotNull] IAntiforgeryAdditionalDataProvider additionalDataProvider,
|
||||||
|
[NotNull] IOptions<AntiforgeryOptions> AntiforgeryOptionsAccessor,
|
||||||
|
[NotNull] IHtmlEncoder htmlEncoder,
|
||||||
|
[NotNull] IOptions<DataProtectionOptions> dataProtectionOptions)
|
||||||
|
{
|
||||||
|
var AntiforgeryOptions = AntiforgeryOptionsAccessor.Options;
|
||||||
|
var applicationId = dataProtectionOptions.Options.ApplicationDiscriminator ?? string.Empty;
|
||||||
|
AntiforgeryOptions.CookieName = AntiforgeryOptions.CookieName ?? ComputeCookieName(applicationId);
|
||||||
|
|
||||||
|
var serializer = new AntiforgeryTokenSerializer(dataProtectionProvider.CreateProtector(_purpose));
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(AntiforgeryOptions, serializer);
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(AntiforgeryOptions, claimUidExtractor, additionalDataProvider);
|
||||||
|
_worker = new AntiforgeryWorker(serializer, AntiforgeryOptions, tokenStore, tokenProvider, tokenProvider, htmlEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 string GetHtml([NotNull] HttpContext context)
|
||||||
|
{
|
||||||
|
var html = _worker.GetFormInputElement(context);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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="AntiforgeryTokenSet">The anti-forgery token pair (cookie and form token) for this request.
|
||||||
|
/// </param>
|
||||||
|
public void Validate([NotNull] HttpContext context, AntiforgeryTokenSet AntiforgeryTokenSet)
|
||||||
|
{
|
||||||
|
Validate(context, AntiforgeryTokenSet.CookieToken, AntiforgeryTokenSet.FormToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||||
|
public void SetCookieTokenAndHeader([NotNull] HttpContext context)
|
||||||
|
{
|
||||||
|
_worker.SetCookieTokenAndHeader(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ComputeCookieName(string applicationId)
|
||||||
|
{
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(applicationId));
|
||||||
|
var subHash = hash.Take(8).ToArray();
|
||||||
|
return WebEncoders.Base64UrlEncode(subHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used as a per request state.
|
||||||
|
/// </summary>
|
||||||
|
public class AntiforgeryContext
|
||||||
|
{
|
||||||
|
public AntiforgeryToken CookieToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class AntiforgeryContextAccessor : IAntiforgeryContextAccessor
|
||||||
|
{
|
||||||
|
public AntiforgeryContext Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides programmatic configuration for the anti-forgery token system.
|
||||||
|
/// </summary>
|
||||||
|
public class AntiforgeryOptions
|
||||||
|
{
|
||||||
|
private const string AntiforgeryTokenFieldName = "__RequestVerificationToken";
|
||||||
|
|
||||||
|
private string _cookieName;
|
||||||
|
private string _formFieldName = AntiforgeryTokenFieldName;
|
||||||
|
|
||||||
|
/// <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 string CookieName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _cookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[param: NotNull]
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_cookieName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the name of the anti-forgery token field that is used by the anti-forgery system.
|
||||||
|
/// </summary>
|
||||||
|
public string FormFieldName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _formFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[param: NotNull]
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_formFieldName = 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 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 bool SuppressXFrameOptionsHeader
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public 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,168 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public sealed class AntiforgeryTokenProvider : IAntiforgeryTokenValidator, IAntiforgeryTokenGenerator
|
||||||
|
{
|
||||||
|
private readonly IClaimUidExtractor _claimUidExtractor;
|
||||||
|
private readonly AntiforgeryOptions _config;
|
||||||
|
private readonly IAntiforgeryAdditionalDataProvider _additionalDataProvider;
|
||||||
|
|
||||||
|
internal AntiforgeryTokenProvider(
|
||||||
|
AntiforgeryOptions 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)
|
||||||
|
{
|
||||||
|
Debug.Assert(IsCookieTokenValid(cookieToken));
|
||||||
|
|
||||||
|
var formToken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = cookieToken.SecurityToken,
|
||||||
|
IsSessionToken = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var 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.FormatAntiforgeryTokenValidator_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?
|
||||||
|
var 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.
|
||||||
|
var 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.DataProtection;
|
||||||
|
using Microsoft.AspNet.WebUtilities;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public sealed class AntiforgeryTokenSerializer : IAntiforgeryTokenSerializer
|
||||||
|
{
|
||||||
|
private readonly IDataProtector _cryptoSystem;
|
||||||
|
private const byte TokenVersion = 0x01;
|
||||||
|
|
||||||
|
public AntiforgeryTokenSerializer([NotNull] IDataProtector cryptoSystem)
|
||||||
|
{
|
||||||
|
_cryptoSystem = cryptoSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntiforgeryToken Deserialize(string serializedToken)
|
||||||
|
{
|
||||||
|
Exception innerException = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tokenBytes = WebEncoders.Base64UrlDecode(serializedToken);
|
||||||
|
using (var stream = new MemoryStream(_cryptoSystem.Unprotect(tokenBytes)))
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
var 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)
|
||||||
|
{
|
||||||
|
var 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 WebEncoders.Base64UrlEncode(_cryptoSystem.Protect(stream.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The anti-forgery token pair (cookie and form token) for a request.
|
||||||
|
/// </summary>
|
||||||
|
public class AntiforgeryTokenSet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the anti-forgery token pair (cookie and form token) for a request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="formToken">The token that is supplied in the request form body.</param>
|
||||||
|
/// <param name="cookieToken">The token that is supplied in the request cookie.</param>
|
||||||
|
public AntiforgeryTokenSet(string formToken, string cookieToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(formToken))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(formToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
FormToken = formToken;
|
||||||
|
|
||||||
|
// Cookie Token is allowed to be null in the case when the old cookie is valid
|
||||||
|
// and there is no new cookieToken generated.
|
||||||
|
CookieToken = cookieToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The token that is supplied in the request form body.
|
||||||
|
/// </summary>
|
||||||
|
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,79 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Saves anti-XSRF tokens split between HttpRequest.Cookies and HttpRequest.Form
|
||||||
|
public sealed class AntiforgeryTokenStore : IAntiforgeryTokenStore
|
||||||
|
{
|
||||||
|
private readonly AntiforgeryOptions _config;
|
||||||
|
private readonly IAntiforgeryTokenSerializer _serializer;
|
||||||
|
|
||||||
|
public AntiforgeryTokenStore([NotNull] AntiforgeryOptions config,
|
||||||
|
[NotNull] IAntiforgeryTokenSerializer serializer)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_serializer = serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntiforgeryToken GetCookieToken(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var contextAccessor =
|
||||||
|
httpContext.RequestServices.GetRequiredService<IAntiforgeryContextAccessor>();
|
||||||
|
if (contextAccessor.Value != null)
|
||||||
|
{
|
||||||
|
return contextAccessor.Value.CookieToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestCookie = httpContext.Request.Cookies[_config.CookieName];
|
||||||
|
if (string.IsNullOrEmpty(requestCookie))
|
||||||
|
{
|
||||||
|
// unable to find the cookie.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _serializer.Deserialize(requestCookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AntiforgeryToken> GetFormTokenAsync(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
var form = await httpContext.Request.ReadFormAsync();
|
||||||
|
var value = form[_config.FormFieldName];
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
// did not exist
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _serializer.Deserialize(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token)
|
||||||
|
{
|
||||||
|
// Add the cookie to the request based context.
|
||||||
|
// This is useful if the cookie needs to be reloaded in the context of the same request.
|
||||||
|
var contextAccessor =
|
||||||
|
httpContext.RequestServices.GetRequiredService<IAntiforgeryContextAccessor>();
|
||||||
|
Debug.Assert(contextAccessor.Value == null, "AntiforgeryContext should be set only once per request.");
|
||||||
|
contextAccessor.Value = new AntiforgeryContext() { CookieToken = token };
|
||||||
|
|
||||||
|
var serializedToken = _serializer.Serialize(token);
|
||||||
|
var 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,117 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Represents a binary blob (token) that contains random data.
|
||||||
|
// Useful for binary data inside a serialized stream.
|
||||||
|
[DebuggerDisplay("{DebuggerString}")]
|
||||||
|
public sealed class BinaryBlob : IEquatable<BinaryBlob>
|
||||||
|
{
|
||||||
|
private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DebuggerString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder("0x", 2 + (_data.Length * 2));
|
||||||
|
for (var 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.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.
|
||||||
|
Debug.Assert(_data != null && _data.Length >= (32 / 8));
|
||||||
|
return BitConverter.ToInt32(_data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GenerateNewToken(int bitLength)
|
||||||
|
{
|
||||||
|
var data = new byte[bitLength / 8];
|
||||||
|
_randomNumberGenerator.GetBytes(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var areEqual = true;
|
||||||
|
for (var i = 0; i < a.Length; i++)
|
||||||
|
{
|
||||||
|
areEqual &= (a[i] == b[i]);
|
||||||
|
}
|
||||||
|
return areEqual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A default <see cref="IAntiforgeryAdditionalDataProvider"/> implementation.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultAntiforgeryAdditionalDataProvider : IAntiforgeryAdditionalDataProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual string GetAdditionalData(HttpContext context)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual bool ValidateAdditionalData(HttpContext context, string additionalData)
|
||||||
|
{
|
||||||
|
// Default implementation does not understand anything but empty data.
|
||||||
|
return string.IsNullOrEmpty(additionalData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of <see cref="IClaimUidExtractor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultClaimUidExtractor : IClaimUidExtractor
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ExtractClaimUid(ClaimsIdentity claimsIdentity)
|
||||||
|
{
|
||||||
|
if (claimsIdentity == null || !claimsIdentity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
// Skip anonymous users
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity);
|
||||||
|
var claimUidBytes = ComputeSHA256(uniqueIdentifierParameters);
|
||||||
|
return Convert.ToBase64String(claimUidBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing
|
||||||
|
internal static IEnumerable<string> GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity)
|
||||||
|
{
|
||||||
|
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 stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
foreach (string parameter in parameters)
|
||||||
|
{
|
||||||
|
writer.Write(parameter); // also writes the length as a prefix; unambiguous
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
var bytes = sha256.ComputeHash(stream.ToArray(), 0, checked((int)stream.Length));
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <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,10 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public interface IAntiforgeryContextAccessor
|
||||||
|
{
|
||||||
|
AntiforgeryContext Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Provides configuration information about the anti-forgery system.
|
||||||
|
public interface IAntiforgeryTokenGenerator
|
||||||
|
{
|
||||||
|
// 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,12 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Abstracts out the serialization process for an anti-forgery token
|
||||||
|
public interface IAntiforgeryTokenSerializer
|
||||||
|
{
|
||||||
|
AntiforgeryToken Deserialize(string serializedToken);
|
||||||
|
string Serialize(AntiforgeryToken token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Provides an abstraction around how tokens are persisted and retrieved for a request
|
||||||
|
public interface IAntiforgeryTokenStore
|
||||||
|
{
|
||||||
|
AntiforgeryToken GetCookieToken(HttpContext httpContext);
|
||||||
|
Task<AntiforgeryToken> GetFormTokenAsync(HttpContext httpContext);
|
||||||
|
void SaveCookieToken(HttpContext httpContext, AntiforgeryToken token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
// Provides an abstraction around something that can validate anti-XSRF tokens
|
||||||
|
public interface IAntiforgeryTokenValidator
|
||||||
|
{
|
||||||
|
// Determines whether an existing cookie token is valid (well-formed).
|
||||||
|
// If it is not, the caller must call GenerateCookieToken() before calling GenerateFormToken().
|
||||||
|
bool IsCookieTokenValid(AntiforgeryToken cookieToken);
|
||||||
|
|
||||||
|
// Validates a (cookie, form) token pair.
|
||||||
|
void ValidateTokens(
|
||||||
|
HttpContext httpContext,
|
||||||
|
ClaimsIdentity identity,
|
||||||
|
AntiforgeryToken cookieToken,
|
||||||
|
AntiforgeryToken formToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This interface can extract unique identifers for a claims-based identity.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClaimUidExtractor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts claims identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identity">The <see cref="ClaimsIdentity"/>.</param>
|
||||||
|
/// <returns>The claims identifier.</returns>
|
||||||
|
string ExtractClaimUid(ClaimsIdentity identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
using Microsoft.Framework.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery.Internal
|
||||||
|
{
|
||||||
|
public sealed class AntiforgeryWorker
|
||||||
|
{
|
||||||
|
private readonly AntiforgeryOptions _config;
|
||||||
|
private readonly IAntiforgeryTokenSerializer _serializer;
|
||||||
|
private readonly IAntiforgeryTokenStore _tokenStore;
|
||||||
|
private readonly IAntiforgeryTokenValidator _validator;
|
||||||
|
private readonly IAntiforgeryTokenGenerator _generator;
|
||||||
|
private readonly IHtmlEncoder _htmlEncoder;
|
||||||
|
|
||||||
|
public AntiforgeryWorker(
|
||||||
|
[NotNull] IAntiforgeryTokenSerializer serializer,
|
||||||
|
[NotNull] AntiforgeryOptions config,
|
||||||
|
[NotNull] IAntiforgeryTokenStore tokenStore,
|
||||||
|
[NotNull] IAntiforgeryTokenGenerator generator,
|
||||||
|
[NotNull] IAntiforgeryTokenValidator validator,
|
||||||
|
[NotNull] IHtmlEncoder htmlEncoder)
|
||||||
|
{
|
||||||
|
_serializer = serializer;
|
||||||
|
_config = config;
|
||||||
|
_tokenStore = tokenStore;
|
||||||
|
_generator = generator;
|
||||||
|
_validator = validator;
|
||||||
|
_htmlEncoder = htmlEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckSSLConfig(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (_config.RequireSSL && !httpContext.Request.IsHttps)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.AntiforgeryWorker_RequireSSL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryToken DeserializeToken(string serializedToken)
|
||||||
|
{
|
||||||
|
return (!string.IsNullOrEmpty(serializedToken))
|
||||||
|
? _serializer.Deserialize(serializedToken)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryToken DeserializeTokenDoesNotThrow(string serializedToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return DeserializeToken(serializedToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore failures since we'll just generate a new token
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClaimsIdentity ExtractIdentity(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
if (httpContext != null)
|
||||||
|
{
|
||||||
|
var user = httpContext.User;
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
// We only support ClaimsIdentity.
|
||||||
|
return user.Identity as ClaimsIdentity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryToken GetCookieTokenDoesNotThrow(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _tokenStore.GetCookieToken(httpContext);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore failures since we'll just generate a new token
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ENTRY POINT ]
|
||||||
|
// Generates an anti-XSRF token pair for the current user. The return
|
||||||
|
// value is the hidden input form element that should be rendered in
|
||||||
|
// the <form>. This method has a side effect: it may set a response
|
||||||
|
// cookie.
|
||||||
|
public string GetFormInputElement([NotNull] HttpContext httpContext)
|
||||||
|
{
|
||||||
|
CheckSSLConfig(httpContext);
|
||||||
|
|
||||||
|
var cookieToken = GetCookieTokenDoesNotThrow(httpContext);
|
||||||
|
var tokenSet = GetTokens(httpContext, cookieToken);
|
||||||
|
cookieToken = tokenSet.CookieToken;
|
||||||
|
var formToken = tokenSet.FormToken;
|
||||||
|
|
||||||
|
SaveCookieTokenAndHeader(httpContext, cookieToken);
|
||||||
|
|
||||||
|
var inputTag = string.Format(
|
||||||
|
"<input name=\"{0}\" type=\"{1}\" value=\"{2}\" />",
|
||||||
|
_htmlEncoder.HtmlEncode(_config.FormFieldName),
|
||||||
|
_htmlEncoder.HtmlEncode("hidden"),
|
||||||
|
_htmlEncoder.HtmlEncode(_serializer.Serialize(formToken)));
|
||||||
|
return inputTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ENTRY POINT ]
|
||||||
|
// Generates a (cookie, form) serialized token pair for the current user.
|
||||||
|
// The caller may specify an existing cookie value if one exists. If the
|
||||||
|
// 'new cookie value' out param is non-null, the caller *must* persist
|
||||||
|
// the new value to cookie storage since the original value was null or
|
||||||
|
// invalid. This method is side-effect free.
|
||||||
|
public AntiforgeryTokenSet GetTokens([NotNull] HttpContext httpContext, string cookieToken)
|
||||||
|
{
|
||||||
|
CheckSSLConfig(httpContext);
|
||||||
|
var deSerializedcookieToken = DeserializeTokenDoesNotThrow(cookieToken);
|
||||||
|
var tokenSet = GetTokens(httpContext, deSerializedcookieToken);
|
||||||
|
|
||||||
|
var serializedCookieToken = Serialize(tokenSet.CookieToken);
|
||||||
|
var serializedFormToken = Serialize(tokenSet.FormToken);
|
||||||
|
return new AntiforgeryTokenSet(serializedFormToken, serializedCookieToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiforgeryToken cookieToken)
|
||||||
|
{
|
||||||
|
var newCookieToken = ValidateAndGenerateNewCookieToken(cookieToken);
|
||||||
|
if (newCookieToken != null)
|
||||||
|
{
|
||||||
|
cookieToken = newCookieToken;
|
||||||
|
}
|
||||||
|
var formToken = _generator.GenerateFormToken(
|
||||||
|
httpContext,
|
||||||
|
ExtractIdentity(httpContext),
|
||||||
|
cookieToken);
|
||||||
|
|
||||||
|
return new AntiforgeryTokenSetInternal()
|
||||||
|
{
|
||||||
|
// Note : The new cookie would be null if the old cookie is valid.
|
||||||
|
CookieToken = newCookieToken,
|
||||||
|
FormToken = formToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Serialize(AntiforgeryToken token)
|
||||||
|
{
|
||||||
|
return (token != null) ? _serializer.Serialize(token) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ENTRY POINT ]
|
||||||
|
// Given an HttpContext, validates that the anti-XSRF tokens contained
|
||||||
|
// in the cookies & form are OK for this request.
|
||||||
|
public async Task ValidateAsync([NotNull] HttpContext httpContext)
|
||||||
|
{
|
||||||
|
CheckSSLConfig(httpContext);
|
||||||
|
|
||||||
|
// Extract cookie & form tokens
|
||||||
|
var cookieToken = _tokenStore.GetCookieToken(httpContext);
|
||||||
|
var formToken = await _tokenStore.GetFormTokenAsync(httpContext);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), cookieToken, formToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ ENTRY POINT ]
|
||||||
|
// Given the serialized string representations of a cookie & form token,
|
||||||
|
// validates that the pair is OK for this request.
|
||||||
|
public void Validate([NotNull] HttpContext httpContext, string cookieToken, string formToken)
|
||||||
|
{
|
||||||
|
CheckSSLConfig(httpContext);
|
||||||
|
|
||||||
|
// Extract cookie & form tokens
|
||||||
|
var deserializedCookieToken = DeserializeToken(cookieToken);
|
||||||
|
var deserializedFormToken = DeserializeToken(formToken);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_validator.ValidateTokens(
|
||||||
|
httpContext,
|
||||||
|
ExtractIdentity(httpContext),
|
||||||
|
deserializedCookieToken,
|
||||||
|
deserializedFormToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The HTTP context associated with the current call.</param>
|
||||||
|
public void SetCookieTokenAndHeader([NotNull] HttpContext httpContext)
|
||||||
|
{
|
||||||
|
CheckSSLConfig(httpContext);
|
||||||
|
|
||||||
|
var cookieToken = GetCookieTokenDoesNotThrow(httpContext);
|
||||||
|
cookieToken = ValidateAndGenerateNewCookieToken(cookieToken);
|
||||||
|
|
||||||
|
SaveCookieTokenAndHeader(httpContext, cookieToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method returns null if oldCookieToken is valid.
|
||||||
|
private AntiforgeryToken ValidateAndGenerateNewCookieToken(AntiforgeryToken cookieToken)
|
||||||
|
{
|
||||||
|
if (!_validator.IsCookieTokenValid(cookieToken))
|
||||||
|
{
|
||||||
|
// Need to make sure we're always operating with a good cookie token.
|
||||||
|
var newCookieToken = _generator.GenerateCookieToken();
|
||||||
|
Debug.Assert(_validator.IsCookieTokenValid(newCookieToken));
|
||||||
|
return newCookieToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveCookieTokenAndHeader(
|
||||||
|
[NotNull] HttpContext httpContext,
|
||||||
|
AntiforgeryToken cookieToken)
|
||||||
|
{
|
||||||
|
if (cookieToken != null)
|
||||||
|
{
|
||||||
|
// Persist the new cookie if it is not null.
|
||||||
|
_tokenStore.SaveCookieToken(httpContext, cookieToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_config.SuppressXFrameOptionsHeader)
|
||||||
|
{
|
||||||
|
// Adding X-Frame-Options header to prevent ClickJacking. See
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
|
||||||
|
// for more information.
|
||||||
|
httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AntiforgeryTokenSetInternal
|
||||||
|
{
|
||||||
|
public AntiforgeryToken FormToken { get; set; }
|
||||||
|
|
||||||
|
public AntiforgeryToken CookieToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNet.Antiforgery.Test")]
|
||||||
|
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
// <auto-generated />
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
internal static class Resources
|
||||||
|
{
|
||||||
|
private static readonly ResourceManager _resourceManager
|
||||||
|
= new ResourceManager("Microsoft.AspNet.Antiforgery.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 AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername
|
||||||
|
{
|
||||||
|
get { return GetString("AntiforgeryTokenValidator_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 FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("AntiforgeryTokenValidator_AuthenticatedUserWithoutUsername"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
internal static string AntiforgeryToken_DeserializationFailed
|
||||||
|
{
|
||||||
|
get { return GetString("AntiforgeryToken_DeserializationFailed"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The anti-forgery token could not be decrypted.
|
||||||
|
/// </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 AntiforgeryOptions.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 AntiforgeryOptions.RequireSsl = true, but the current request is not an SSL request.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatAntiforgeryWorker_RequireSSL()
|
||||||
|
{
|
||||||
|
return GetString("AntiforgeryWorker_RequireSSL");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ArgumentCannotBeNullOrEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("ArgumentCannotBeNullOrEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value cannot be null or empty.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatArgumentCannotBeNullOrEmpty()
|
||||||
|
{
|
||||||
|
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
{
|
||||||
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.Assert(value != null);
|
||||||
|
|
||||||
|
if (formatterNames != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < formatterNames.Length; i++)
|
||||||
|
{
|
||||||
|
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AntiforgeryTokenValidator_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="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.</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 AntiforgeryOptions.RequireSsl = true, but the current request is not an SSL request.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
|
<value>Value cannot be null or empty.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -1,22 +1,30 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
"description": "",
|
"description": "",
|
||||||
"authors": [ "" ],
|
"authors": [ "" ],
|
||||||
"tags": [ "" ],
|
"tags": [ "" ],
|
||||||
"projectUrl": "",
|
"projectUrl": "",
|
||||||
"licenseUrl": "",
|
"licenseUrl": "",
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
},
|
"Microsoft.AspNet.DataProtection": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Http.Abstractions": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.DependencyInjection.Abstractions": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" },
|
||||||
|
"Microsoft.Framework.OptionsModel": "1.0.0-*"
|
||||||
|
},
|
||||||
|
|
||||||
"frameworks" : {
|
"frameworks" : {
|
||||||
"dnx451": { },
|
"dnx451": { },
|
||||||
"dnxcore50" : {
|
"dnxcore50" : {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Collections": "4.0.10-beta-22807",
|
"System.Collections": "4.0.10-beta-*",
|
||||||
"System.Linq": "4.0.0-beta-22807",
|
"System.Linq": "4.0.0-beta-*",
|
||||||
"System.Threading": "4.0.10-beta-22807",
|
"System.Security.Cryptography.RandomNumberGenerator": "4.0.0-beta-*",
|
||||||
"Microsoft.CSharp": "4.0.0-beta-22807"
|
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*",
|
||||||
|
"System.Threading": "4.0.10-beta-*",
|
||||||
|
"Microsoft.CSharp": "4.0.0-beta-*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.DataProtection;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class AntiforgeryTokenSerializerTest
|
||||||
|
{
|
||||||
|
private static readonly Mock<IDataProtector> _dataProtector = GetDataProtector();
|
||||||
|
private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 });
|
||||||
|
private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C });
|
||||||
|
private const byte _salt = 0x05;
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(
|
||||||
|
"01" // Version
|
||||||
|
+ "705EEDCC7D42F1D6B3B9" // SecurityToken
|
||||||
|
// (WRONG!) Stream ends too early
|
||||||
|
)]
|
||||||
|
[InlineData(
|
||||||
|
"01" // Version
|
||||||
|
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
+ "01" // IsSessionToken
|
||||||
|
+ "00" // (WRONG!) Too much data in stream
|
||||||
|
)]
|
||||||
|
[InlineData(
|
||||||
|
"02" // (WRONG! - must be 0x01) Version
|
||||||
|
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
+ "01" // IsSessionToken
|
||||||
|
)]
|
||||||
|
[InlineData(
|
||||||
|
"01" // Version
|
||||||
|
+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
+ "00" // IsSessionToken
|
||||||
|
+ "00" // IsClaimsBased
|
||||||
|
+ "05" // Username length header
|
||||||
|
+ "0000" // (WRONG!) Too little data in stream
|
||||||
|
)]
|
||||||
|
public void Deserialize_BadToken_Throws(string serializedToken)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testSerializer = new AntiforgeryTokenSerializer(_dataProtector.Object);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => testSerializer.Deserialize(serializedToken));
|
||||||
|
Assert.Equal(@"The anti-forgery token could not be decrypted.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialize_FieldToken_WithClaimUid_TokenRoundTripSuccessful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testSerializer = new AntiforgeryTokenSerializer(_dataProtector.Object);
|
||||||
|
|
||||||
|
//"01" // Version
|
||||||
|
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
//+ "00" // IsSessionToken
|
||||||
|
//+ "01" // IsClaimsBased
|
||||||
|
//+ "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid
|
||||||
|
//+ "05" // AdditionalData length header
|
||||||
|
//+ "E282AC3437"; // AdditionalData ("€47") as UTF8
|
||||||
|
var token = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = _securityToken,
|
||||||
|
IsSessionToken = false,
|
||||||
|
ClaimUid = _claimUid,
|
||||||
|
AdditionalData = "€47"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualSerializedData = testSerializer.Serialize(token);
|
||||||
|
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertTokensEqual(token, deserializedToken);
|
||||||
|
_dataProtector.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialize_FieldToken_WithUsername_TokenRoundTripSuccessful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testSerializer = new AntiforgeryTokenSerializer(_dataProtector.Object);
|
||||||
|
|
||||||
|
//"01" // Version
|
||||||
|
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
//+ "00" // IsSessionToken
|
||||||
|
//+ "00" // IsClaimsBased
|
||||||
|
//+ "08" // Username length header
|
||||||
|
//+ "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8
|
||||||
|
//+ "05" // AdditionalData length header
|
||||||
|
//+ "E282AC3437"; // AdditionalData ("€47") as UTF8
|
||||||
|
var token = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = _securityToken,
|
||||||
|
IsSessionToken = false,
|
||||||
|
Username = "Jérôme",
|
||||||
|
AdditionalData = "€47"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualSerializedData = testSerializer.Serialize(token);
|
||||||
|
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertTokensEqual(token, deserializedToken);
|
||||||
|
_dataProtector.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialize_SessionToken_TokenRoundTripSuccessful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testSerializer = new AntiforgeryTokenSerializer(_dataProtector.Object);
|
||||||
|
|
||||||
|
//"01" // Version
|
||||||
|
//+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken
|
||||||
|
//+ "01"; // IsSessionToken
|
||||||
|
var token = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = _securityToken,
|
||||||
|
IsSessionToken = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
string actualSerializedData = testSerializer.Serialize(token);
|
||||||
|
var deserializedToken = testSerializer.Deserialize(actualSerializedData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertTokensEqual(token, deserializedToken);
|
||||||
|
_dataProtector.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mock<IDataProtector> GetDataProtector()
|
||||||
|
{
|
||||||
|
var mockCryptoSystem = new Mock<IDataProtector>();
|
||||||
|
mockCryptoSystem.Setup(o => o.Protect(It.IsAny<byte[]>()))
|
||||||
|
.Returns<byte[]>(Protect)
|
||||||
|
.Verifiable();
|
||||||
|
mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny<byte[]>()))
|
||||||
|
.Returns<byte[]>(UnProtect)
|
||||||
|
.Verifiable();
|
||||||
|
return mockCryptoSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] Protect(byte[] data)
|
||||||
|
{
|
||||||
|
var input = new List<byte>(data);
|
||||||
|
input.Add(_salt);
|
||||||
|
return input.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] UnProtect(byte[] data)
|
||||||
|
{
|
||||||
|
var salt = data[data.Length - 1];
|
||||||
|
if (salt != _salt)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid salt value in data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Take(data.Length - 1).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertTokensEqual(AntiforgeryToken expected, AntiforgeryToken actual)
|
||||||
|
{
|
||||||
|
Assert.NotNull(expected);
|
||||||
|
Assert.NotNull(actual);
|
||||||
|
Assert.Equal(expected.AdditionalData, actual.AdditionalData);
|
||||||
|
Assert.Equal(expected.ClaimUid, actual.ClaimUid);
|
||||||
|
Assert.Equal(expected.IsSessionToken, actual.IsSessionToken);
|
||||||
|
Assert.Equal(expected.SecurityToken, actual.SecurityToken);
|
||||||
|
Assert.Equal(expected.Username, actual.Username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,428 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class AntiforgeryTokenStoreTest
|
||||||
|
{
|
||||||
|
private readonly string _cookieName = "cookie-name";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCookieToken_CookieDoesNotExist_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestCookies = new Mock<IReadableStringCollection>();
|
||||||
|
requestCookies
|
||||||
|
.Setup(o => o.Get(It.IsAny<string>()))
|
||||||
|
.Returns(string.Empty);
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext
|
||||||
|
.Setup(o => o.Request.Cookies)
|
||||||
|
.Returns(requestCookies.Object);
|
||||||
|
var contextAccessor = new AntiforgeryContextAccessor();
|
||||||
|
mockHttpContext.SetupGet(o => o.RequestServices)
|
||||||
|
.Returns(GetServiceProvider(contextAccessor));
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var token = tokenStore.GetCookieToken(mockHttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCookieToken_CookieIsMissingInRequest_LooksUpCookieInAntiforgeryContext()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestCookies = new Mock<IReadableStringCollection>();
|
||||||
|
requestCookies
|
||||||
|
.Setup(o => o.Get(It.IsAny<string>()))
|
||||||
|
.Returns(string.Empty);
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext
|
||||||
|
.Setup(o => o.Request.Cookies)
|
||||||
|
.Returns(requestCookies.Object);
|
||||||
|
var contextAccessor = new AntiforgeryContextAccessor();
|
||||||
|
mockHttpContext.SetupGet(o => o.RequestServices)
|
||||||
|
.Returns(GetServiceProvider(contextAccessor));
|
||||||
|
|
||||||
|
// add a cookie explicitly.
|
||||||
|
var cookie = new AntiforgeryToken();
|
||||||
|
contextAccessor.Value = new AntiforgeryContext() { CookieToken = cookie };
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var token = tokenStore.GetCookieToken(mockHttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(cookie, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCookieToken_CookieIsEmpty_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = GetMockHttpContext(_cookieName, string.Empty);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var token = tokenStore.GetCookieToken(mockHttpContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCookieToken_CookieIsInvalid_PropagatesException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = GetMockHttpContext(_cookieName, "invalid-value");
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedException = new InvalidOperationException("some exception");
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
|
||||||
|
mockSerializer
|
||||||
|
.Setup(o => o.Deserialize("invalid-value"))
|
||||||
|
.Throws(expectedException);
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: mockSerializer.Object);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => tokenStore.GetCookieToken(mockHttpContext));
|
||||||
|
Assert.Same(expectedException, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCookieToken_CookieIsValid_ReturnsToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedToken = new AntiforgeryToken();
|
||||||
|
var mockHttpContext = GetMockHttpContext(_cookieName, "valid-value");
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
|
||||||
|
mockSerializer
|
||||||
|
.Setup(o => o.Deserialize("valid-value"))
|
||||||
|
.Returns(expectedToken);
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: mockSerializer.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AntiforgeryToken retVal = tokenStore.GetCookieToken(mockHttpContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(expectedToken, retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFormToken_FormFieldIsEmpty_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
var requestContext = new Mock<HttpRequest>();
|
||||||
|
var formCollection = new Mock<IFormCollection>();
|
||||||
|
formCollection.Setup(f => f["form-field-name"]).Returns(string.Empty);
|
||||||
|
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None))
|
||||||
|
.Returns(Task.FromResult(formCollection.Object));
|
||||||
|
mockHttpContext.Setup(o => o.Request)
|
||||||
|
.Returns(requestContext.Object);
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var token = await tokenStore.GetFormTokenAsync(mockHttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFormToken_FormFieldIsInvalid_PropagatesException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formCollection = new Mock<IFormCollection>();
|
||||||
|
formCollection.Setup(f => f["form-field-name"]).Returns("invalid-value");
|
||||||
|
|
||||||
|
var requestContext = new Mock<HttpRequest>();
|
||||||
|
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None))
|
||||||
|
.Returns(Task.FromResult(formCollection.Object));
|
||||||
|
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request)
|
||||||
|
.Returns(requestContext.Object);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedException = new InvalidOperationException("some exception");
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
|
||||||
|
mockSerializer.Setup(o => o.Deserialize("invalid-value"))
|
||||||
|
.Throws(expectedException);
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: mockSerializer.Object);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
await
|
||||||
|
Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await tokenStore.GetFormTokenAsync(mockHttpContext.Object));
|
||||||
|
Assert.Same(expectedException, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFormToken_FormFieldIsValid_ReturnsToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedToken = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
var requestContext = new Mock<HttpRequest>();
|
||||||
|
var formCollection = new Mock<IFormCollection>();
|
||||||
|
formCollection.Setup(f => f["form-field-name"]).Returns("valid-value");
|
||||||
|
requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None))
|
||||||
|
.Returns(Task.FromResult(formCollection.Object));
|
||||||
|
mockHttpContext.Setup(o => o.Request)
|
||||||
|
.Returns(requestContext.Object);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
|
||||||
|
mockSerializer.Setup(o => o.Deserialize("valid-value"))
|
||||||
|
.Returns(expectedToken);
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: mockSerializer.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retVal = await tokenStore.GetFormTokenAsync(mockHttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(expectedToken, retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, true)]
|
||||||
|
[InlineData(false, null)]
|
||||||
|
public void SaveCookieToken(bool requireSsl, bool? expectedCookieSecureFlag)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
var mockCookies = new Mock<IResponseCookies>();
|
||||||
|
|
||||||
|
bool defaultCookieSecureValue = expectedCookieSecureFlag ?? false; // pulled from config; set by ctor
|
||||||
|
var cookies = new MockResponseCookieCollection();
|
||||||
|
|
||||||
|
cookies.Count = 0;
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Response.Cookies)
|
||||||
|
.Returns(cookies);
|
||||||
|
var contextAccessor = new AntiforgeryContextAccessor();
|
||||||
|
mockHttpContext.SetupGet(o => o.RequestServices)
|
||||||
|
.Returns(GetServiceProvider(contextAccessor));
|
||||||
|
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>();
|
||||||
|
mockSerializer.Setup(o => o.Serialize(token))
|
||||||
|
.Returns("serialized-value");
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = _cookieName,
|
||||||
|
RequireSSL = requireSsl
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenStore = new AntiforgeryTokenStore(
|
||||||
|
config: config,
|
||||||
|
serializer: mockSerializer.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tokenStore.SaveCookieToken(mockHttpContext.Object, token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, cookies.Count);
|
||||||
|
Assert.NotNull(contextAccessor.Value.CookieToken);
|
||||||
|
Assert.NotNull(cookies);
|
||||||
|
Assert.Equal(_cookieName, cookies.Key);
|
||||||
|
Assert.Equal("serialized-value", cookies.Value);
|
||||||
|
Assert.True(cookies.Options.HttpOnly);
|
||||||
|
Assert.Equal(defaultCookieSecureValue, cookies.Options.Secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpContext GetMockHttpContext(string cookieName, string cookieValue)
|
||||||
|
{
|
||||||
|
var requestCookies = new MockCookieCollection(new Dictionary<string, string>() { { cookieName, cookieValue } });
|
||||||
|
|
||||||
|
var request = new Mock<HttpRequest>();
|
||||||
|
request.Setup(o => o.Cookies)
|
||||||
|
.Returns(requestCookies);
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request)
|
||||||
|
.Returns(request.Object);
|
||||||
|
|
||||||
|
var contextAccessor = new AntiforgeryContextAccessor();
|
||||||
|
mockHttpContext.SetupGet(o => o.RequestServices)
|
||||||
|
.Returns(GetServiceProvider(contextAccessor));
|
||||||
|
|
||||||
|
return mockHttpContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceProvider GetServiceProvider(IAntiforgeryContextAccessor contextAccessor)
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddInstance<IAntiforgeryContextAccessor>(contextAccessor);
|
||||||
|
return serviceCollection.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockResponseCookieCollection : IResponseCookies
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public CookieOptions Options { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
|
||||||
|
public void Append(string key, string value, CookieOptions options)
|
||||||
|
{
|
||||||
|
this.Key = key;
|
||||||
|
this.Value = value;
|
||||||
|
this.Options = options;
|
||||||
|
this.Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Append(string key, string value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(string key, CookieOptions options)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(string key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockCookieCollection : IReadableStringCollection
|
||||||
|
{
|
||||||
|
private Dictionary<string, string> _dictionary;
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _dictionary.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICollection<string> Keys
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _dictionary.Keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MockCookieCollection(Dictionary<string, string> dictionary)
|
||||||
|
{
|
||||||
|
_dictionary = dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MockCookieCollection GetDummyInstance(string key, string value)
|
||||||
|
{
|
||||||
|
return new MockCookieCollection(new Dictionary<string, string>() { { key, value } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Get(string key)
|
||||||
|
{
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<string> GetValues(string key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return _dictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string this[string key]
|
||||||
|
{
|
||||||
|
get { return _dictionary[key]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class AntiforgeryTokenTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AdditionalDataProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act & assert - 1
|
||||||
|
Assert.Equal("", token.AdditionalData);
|
||||||
|
|
||||||
|
// Act & assert - 2
|
||||||
|
token.AdditionalData = "additional data";
|
||||||
|
Assert.Equal("additional data", token.AdditionalData);
|
||||||
|
|
||||||
|
// Act & assert - 3
|
||||||
|
token.AdditionalData = null;
|
||||||
|
Assert.Equal("", token.AdditionalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClaimUidProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act & assert - 1
|
||||||
|
Assert.Null(token.ClaimUid);
|
||||||
|
|
||||||
|
// Act & assert - 2
|
||||||
|
BinaryBlob blob = new BinaryBlob(32);
|
||||||
|
token.ClaimUid = blob;
|
||||||
|
Assert.Equal(blob, token.ClaimUid);
|
||||||
|
|
||||||
|
// Act & assert - 3
|
||||||
|
token.ClaimUid = null;
|
||||||
|
Assert.Null(token.ClaimUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSessionTokenProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act & assert - 1
|
||||||
|
Assert.False(token.IsSessionToken);
|
||||||
|
|
||||||
|
// Act & assert - 2
|
||||||
|
token.IsSessionToken = true;
|
||||||
|
Assert.True(token.IsSessionToken);
|
||||||
|
|
||||||
|
// Act & assert - 3
|
||||||
|
token.IsSessionToken = false;
|
||||||
|
Assert.False(token.IsSessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UsernameProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act & assert - 1
|
||||||
|
Assert.Equal("", token.Username);
|
||||||
|
|
||||||
|
// Act & assert - 2
|
||||||
|
token.Username = "my username";
|
||||||
|
Assert.Equal("my username", token.Username);
|
||||||
|
|
||||||
|
// Act & assert - 3
|
||||||
|
token.Username = null;
|
||||||
|
Assert.Equal("", token.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SecurityTokenProperty_GetsAutopopulated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var securityToken = token.SecurityToken;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(securityToken);
|
||||||
|
Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
|
||||||
|
|
||||||
|
// check that we're not making a new one each property call
|
||||||
|
Assert.Equal(securityToken, token.SecurityToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SecurityTokenProperty_PropertySetter_DoesNotUseDefaults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var securityToken = new BinaryBlob(64);
|
||||||
|
token.SecurityToken = securityToken;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(securityToken, token.SecurityToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SecurityTokenProperty_PropertySetter_DoesNotAllowNulls()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = new AntiforgeryToken();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
token.SecurityToken = null;
|
||||||
|
var securityToken = token.SecurityToken;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(securityToken);
|
||||||
|
Assert.Equal(AntiforgeryToken.SecurityTokenBitLength, securityToken.BitLength);
|
||||||
|
|
||||||
|
// check that we're not making a new one each property call
|
||||||
|
Assert.Equal(securityToken, token.SecurityToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,583 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Antiforgery.Internal;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
|
using Microsoft.Framework.WebEncoders.Testing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class AntiforgeryWorkerTest
|
||||||
|
{
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ChecksSSL_ValidateAsync_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request.IsHttps)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
RequireSSL = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var worker = new AntiforgeryWorker(
|
||||||
|
config: config,
|
||||||
|
serializer: null,
|
||||||
|
tokenStore: null,
|
||||||
|
generator: null,
|
||||||
|
validator: null,
|
||||||
|
htmlEncoder: new CommonTestEncoder());
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
await
|
||||||
|
Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await worker.ValidateAsync(mockHttpContext.Object));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
|
||||||
|
"but the current request is not an SSL request.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChecksSSL_Validate_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request.IsHttps)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
RequireSSL = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var worker = new AntiforgeryWorker(
|
||||||
|
config: config,
|
||||||
|
serializer: null,
|
||||||
|
tokenStore: null,
|
||||||
|
generator: null,
|
||||||
|
validator: null,
|
||||||
|
htmlEncoder: new CommonTestEncoder());
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => worker.Validate(mockHttpContext.Object, cookieToken: null, formToken: null));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
|
||||||
|
"but the current request is not an SSL request.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChecksSSL_GetFormInputElement_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request.IsHttps)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
RequireSSL = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var worker = new AntiforgeryWorker(
|
||||||
|
config: config,
|
||||||
|
serializer: null,
|
||||||
|
tokenStore: null,
|
||||||
|
generator: null,
|
||||||
|
validator: null,
|
||||||
|
htmlEncoder: new CommonTestEncoder());
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => worker.GetFormInputElement(mockHttpContext.Object));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
|
||||||
|
"but the current request is not an SSL request.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChecksSSL_GetTokens_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.Request.IsHttps)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
RequireSSL = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var worker = new AntiforgeryWorker(
|
||||||
|
config: config,
|
||||||
|
serializer: null,
|
||||||
|
tokenStore: null,
|
||||||
|
generator: null,
|
||||||
|
validator: null,
|
||||||
|
htmlEncoder: new CommonTestEncoder());
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
worker.GetTokens(mockHttpContext.Object, "cookie-token"));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The anti-forgery system has the configuration value AntiforgeryOptions.RequireSsl = true, " +
|
||||||
|
"but the current request is not an SSL request.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFormInputElement_ExistingInvalidCookieToken_GeneratesANewCookieAndAnAntiforgeryToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure the existing cookie is invalid.
|
||||||
|
var context = GetAntiforgeryWorkerContext(config, isOldCookieValid: false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||||
|
@"value=""HtmlEncode[[serialized-form-token]]"" />",
|
||||||
|
inputElement);
|
||||||
|
context.TokenStore.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFormInputElement_ExistingInvalidCookieToken_SwallowsExceptions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure the existing cookie is invalid.
|
||||||
|
var context = GetAntiforgeryWorkerContext(config, isOldCookieValid: false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// This will cause the cookieToken to be null.
|
||||||
|
context.TokenStore.Setup(o => o.GetCookieToken(context.HttpContext.Object))
|
||||||
|
.Throws(new Exception("should be swallowed"));
|
||||||
|
|
||||||
|
// Setup so that the null cookie token returned is treated as invalid.
|
||||||
|
context.TokenValidator.Setup(o => o.IsCookieTokenValid(null))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||||
|
@"value=""HtmlEncode[[serialized-form-token]]"" />",
|
||||||
|
inputElement);
|
||||||
|
context.TokenStore.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFormInputElement_ExistingValidCookieToken_GeneratesAnAntiforgeryToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure the existing cookie is valid and use the same cookie for the mock Token Provider.
|
||||||
|
var context = GetAntiforgeryWorkerContext(options, useOldCookie: true, isOldCookieValid: true);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(
|
||||||
|
@"<input name=""HtmlEncode[[form-field-name]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||||
|
@"value=""HtmlEncode[[serialized-form-token]]"" />",
|
||||||
|
inputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, "SAMEORIGIN")]
|
||||||
|
[InlineData(true, null)]
|
||||||
|
public void GetFormInputElement_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
SuppressXFrameOptionsHeader = suppressXFrameOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
// Genreate a new cookie.
|
||||||
|
var context = GetAntiforgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var inputElement = worker.GetFormInputElement(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
string xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"];
|
||||||
|
Assert.Equal(expectedHeaderValue, xFrameOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTokens_ExistingInvalidCookieToken_GeneratesANewCookieTokenAndANewFormToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Genreate a new cookie.
|
||||||
|
var context = GetAntiforgeryWorkerContext(
|
||||||
|
new AntiforgeryOptions(),
|
||||||
|
useOldCookie: false,
|
||||||
|
isOldCookieValid: false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken);
|
||||||
|
Assert.Equal("serialized-form-token", tokenset.FormToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTokens_ExistingInvalidCookieToken_SwallowsExceptions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Make sure the existing cookie is invalid.
|
||||||
|
var context = GetAntiforgeryWorkerContext(
|
||||||
|
new AntiforgeryOptions(),
|
||||||
|
useOldCookie: false,
|
||||||
|
isOldCookieValid: false);
|
||||||
|
|
||||||
|
// This will cause the cookieToken to be null.
|
||||||
|
context.TokenSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token"))
|
||||||
|
.Throws(new Exception("should be swallowed"));
|
||||||
|
|
||||||
|
// Setup so that the null cookie token returned is treated as invalid.
|
||||||
|
context.TokenValidator.Setup(o => o.IsCookieTokenValid(null))
|
||||||
|
.Returns(false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("serialized-new-cookie-token", tokenset.CookieToken);
|
||||||
|
Assert.Equal("serialized-form-token", tokenset.FormToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTokens_ExistingValidCookieToken_GeneratesANewFormToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetAntiforgeryWorkerContext(
|
||||||
|
new AntiforgeryOptions(),
|
||||||
|
useOldCookie: true,
|
||||||
|
isOldCookieValid: true);
|
||||||
|
context.TokenStore = null;
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var tokenset = worker.GetTokens(context.HttpContext.Object, "serialized-old-cookie-token");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(tokenset.CookieToken);
|
||||||
|
Assert.Equal("serialized-form-token", tokenset.FormToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_FromInvalidStrings_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions());
|
||||||
|
|
||||||
|
context.TokenSerializer.Setup(o => o.Deserialize("cookie-token"))
|
||||||
|
.Returns(context.TestTokenSet.OldCookieToken);
|
||||||
|
context.TokenSerializer.Setup(o => o.Deserialize("form-token"))
|
||||||
|
.Returns(context.TestTokenSet.FormToken);
|
||||||
|
|
||||||
|
context.TokenValidator.Setup(o => o.ValidateTokens(
|
||||||
|
context.HttpContext.Object,
|
||||||
|
context.HttpContext.Object.User.Identity as ClaimsIdentity,
|
||||||
|
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
|
||||||
|
.Throws(new InvalidOperationException("my-message"));
|
||||||
|
context.TokenStore = null;
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => worker.Validate(context.HttpContext.Object, "cookie-token", "form-token"));
|
||||||
|
Assert.Equal("my-message", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_FromValidStrings_TokensValidatedSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions());
|
||||||
|
|
||||||
|
context.TokenSerializer.Setup(o => o.Deserialize("cookie-token"))
|
||||||
|
.Returns(context.TestTokenSet.OldCookieToken);
|
||||||
|
context.TokenSerializer.Setup(o => o.Deserialize("form-token"))
|
||||||
|
.Returns(context.TestTokenSet.FormToken);
|
||||||
|
|
||||||
|
context.TokenValidator.Setup(o => o.ValidateTokens(
|
||||||
|
context.HttpContext.Object,
|
||||||
|
context.HttpContext.Object.User.Identity as ClaimsIdentity,
|
||||||
|
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
|
||||||
|
.Verifiable();
|
||||||
|
context.TokenStore = null;
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
worker.Validate(context.HttpContext.Object, "cookie-token", "form-token");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.TokenValidator.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validate_FromStore_Failure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions());
|
||||||
|
|
||||||
|
context.TokenValidator.Setup(o => o.ValidateTokens(
|
||||||
|
context.HttpContext.Object,
|
||||||
|
context.HttpContext.Object.User.Identity as ClaimsIdentity,
|
||||||
|
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
|
||||||
|
.Throws(new InvalidOperationException("my-message"));
|
||||||
|
context.TokenSerializer = null;
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
await
|
||||||
|
Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
async () => await worker.ValidateAsync(context.HttpContext.Object));
|
||||||
|
Assert.Equal("my-message", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validate_FromStore_Success()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetAntiforgeryWorkerContext(new AntiforgeryOptions());
|
||||||
|
|
||||||
|
context.TokenValidator.Setup(o => o.ValidateTokens(
|
||||||
|
context.HttpContext.Object,
|
||||||
|
context.HttpContext.Object.User.Identity as ClaimsIdentity,
|
||||||
|
context.TestTokenSet.OldCookieToken, context.TestTokenSet.FormToken))
|
||||||
|
.Verifiable();
|
||||||
|
context.TokenSerializer = null;
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await worker.ValidateAsync(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.TokenValidator.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, "SAMEORIGIN")]
|
||||||
|
[InlineData(true, null)]
|
||||||
|
public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(
|
||||||
|
bool suppressXFrameOptions,
|
||||||
|
string expectedHeaderValue)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
SuppressXFrameOptionsHeader = suppressXFrameOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
// Genreate a new cookie.
|
||||||
|
var context = GetAntiforgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false);
|
||||||
|
var worker = GetAntiforgeryWorker(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
worker.SetCookieTokenAndHeader(context.HttpContext.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"];
|
||||||
|
Assert.Equal(expectedHeaderValue, xFrameOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryWorker GetAntiforgeryWorker(AntiforgeryWorkerContext context)
|
||||||
|
{
|
||||||
|
return new AntiforgeryWorker(
|
||||||
|
config: context.Options,
|
||||||
|
serializer: context.TokenSerializer != null ? context.TokenSerializer.Object : null,
|
||||||
|
tokenStore: context.TokenStore != null ? context.TokenStore.Object : null,
|
||||||
|
generator: context.TokenGenerator != null ? context.TokenGenerator.Object : null,
|
||||||
|
validator: context.TokenValidator != null ? context.TokenValidator.Object : null,
|
||||||
|
htmlEncoder: new CommonTestEncoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<HttpContext> GetHttpContext(bool setupResponse = true)
|
||||||
|
{
|
||||||
|
var identity = new ClaimsIdentity("some-auth");
|
||||||
|
var mockHttpContext = new Mock<HttpContext>();
|
||||||
|
mockHttpContext.Setup(o => o.User)
|
||||||
|
.Returns(new ClaimsPrincipal(identity));
|
||||||
|
|
||||||
|
if (setupResponse)
|
||||||
|
{
|
||||||
|
var mockResponse = new Mock<HttpResponse>();
|
||||||
|
mockResponse.Setup(r => r.Headers)
|
||||||
|
.Returns(new HeaderDictionary(new Dictionary<string, string[]>()));
|
||||||
|
mockHttpContext.Setup(o => o.Response)
|
||||||
|
.Returns(mockResponse.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockHttpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<IAntiforgeryTokenStore> GetTokenStore(
|
||||||
|
HttpContext context,
|
||||||
|
TestTokenSet testTokenSet,
|
||||||
|
bool saveNewCookie = true)
|
||||||
|
{
|
||||||
|
var oldCookieToken = testTokenSet.OldCookieToken;
|
||||||
|
var formToken = testTokenSet.FormToken;
|
||||||
|
var mockTokenStore = new Mock<IAntiforgeryTokenStore>(MockBehavior.Strict);
|
||||||
|
mockTokenStore.Setup(o => o.GetCookieToken(context))
|
||||||
|
.Returns(oldCookieToken);
|
||||||
|
mockTokenStore.Setup(o => o.GetFormTokenAsync(context))
|
||||||
|
.Returns(Task.FromResult(formToken));
|
||||||
|
|
||||||
|
if (saveNewCookie)
|
||||||
|
{
|
||||||
|
var newCookieToken = testTokenSet.NewCookieToken;
|
||||||
|
mockTokenStore.Setup(o => o.SaveCookieToken(context, newCookieToken))
|
||||||
|
.Verifiable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockTokenStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<IAntiforgeryTokenSerializer> GetTokenSerializer(TestTokenSet testTokenSet)
|
||||||
|
{
|
||||||
|
var oldCookieToken = testTokenSet.OldCookieToken;
|
||||||
|
var newCookieToken = testTokenSet.NewCookieToken;
|
||||||
|
var formToken = testTokenSet.FormToken;
|
||||||
|
var mockSerializer = new Mock<IAntiforgeryTokenSerializer>(MockBehavior.Strict);
|
||||||
|
mockSerializer.Setup(o => o.Serialize(formToken))
|
||||||
|
.Returns("serialized-form-token");
|
||||||
|
mockSerializer.Setup(o => o.Deserialize("serialized-old-cookie-token"))
|
||||||
|
.Returns(oldCookieToken);
|
||||||
|
mockSerializer.Setup(o => o.Serialize(newCookieToken))
|
||||||
|
.Returns("serialized-new-cookie-token");
|
||||||
|
return mockSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestTokenSet GetTokenSet(bool isOldCookieTokenSessionToken = true, bool isNewCookieSessionToken = true)
|
||||||
|
{
|
||||||
|
return new TestTokenSet()
|
||||||
|
{
|
||||||
|
FormToken = new AntiforgeryToken() { IsSessionToken = false },
|
||||||
|
OldCookieToken = new AntiforgeryToken() { IsSessionToken = isOldCookieTokenSessionToken },
|
||||||
|
NewCookieToken = new AntiforgeryToken() { IsSessionToken = isNewCookieSessionToken },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private AntiforgeryWorkerContext GetAntiforgeryWorkerContext(
|
||||||
|
AntiforgeryOptions config,
|
||||||
|
bool useOldCookie = false,
|
||||||
|
bool isOldCookieValid = true)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHttpContext = GetHttpContext();
|
||||||
|
var testTokenSet = GetTokenSet(isOldCookieTokenSessionToken: true, isNewCookieSessionToken: true);
|
||||||
|
|
||||||
|
var mockSerializer = GetTokenSerializer(testTokenSet);
|
||||||
|
|
||||||
|
var mockTokenStore = GetTokenStore(mockHttpContext.Object, testTokenSet);
|
||||||
|
|
||||||
|
var mockGenerator = new Mock<IAntiforgeryTokenGenerator>(MockBehavior.Strict);
|
||||||
|
mockGenerator
|
||||||
|
.Setup(o => o.GenerateFormToken(
|
||||||
|
mockHttpContext.Object,
|
||||||
|
mockHttpContext.Object.User.Identity as ClaimsIdentity,
|
||||||
|
useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken))
|
||||||
|
.Returns(testTokenSet.FormToken);
|
||||||
|
|
||||||
|
mockGenerator
|
||||||
|
.Setup(o => o.GenerateCookieToken())
|
||||||
|
.Returns(useOldCookie ? testTokenSet.OldCookieToken : testTokenSet.NewCookieToken);
|
||||||
|
|
||||||
|
var mockValidator = new Mock<IAntiforgeryTokenValidator>(MockBehavior.Strict);
|
||||||
|
mockValidator
|
||||||
|
.Setup(o => o.IsCookieTokenValid(testTokenSet.OldCookieToken))
|
||||||
|
.Returns(isOldCookieValid);
|
||||||
|
|
||||||
|
mockValidator
|
||||||
|
.Setup(o => o.IsCookieTokenValid(testTokenSet.NewCookieToken))
|
||||||
|
.Returns(!isOldCookieValid);
|
||||||
|
|
||||||
|
return new AntiforgeryWorkerContext()
|
||||||
|
{
|
||||||
|
Options = config,
|
||||||
|
HttpContext = mockHttpContext,
|
||||||
|
TokenGenerator = mockGenerator,
|
||||||
|
TokenValidator = mockValidator,
|
||||||
|
TokenSerializer = mockSerializer,
|
||||||
|
TokenStore = mockTokenStore,
|
||||||
|
TestTokenSet = testTokenSet
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTokenSet
|
||||||
|
{
|
||||||
|
public AntiforgeryToken FormToken { get; set; }
|
||||||
|
public string FormTokenString { get; set; }
|
||||||
|
public AntiforgeryToken OldCookieToken { get; set; }
|
||||||
|
public string OldCookieTokenString { get; set; }
|
||||||
|
public AntiforgeryToken NewCookieToken { get; set; }
|
||||||
|
public string NewCookieTokenString { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AntiforgeryWorkerContext
|
||||||
|
{
|
||||||
|
public AntiforgeryOptions Options { get; set; }
|
||||||
|
|
||||||
|
public TestTokenSet TestTokenSet { get; set; }
|
||||||
|
|
||||||
|
public Mock<HttpContext> HttpContext { get; set; }
|
||||||
|
|
||||||
|
public Mock<IAntiforgeryTokenGenerator> TokenGenerator { get; set; }
|
||||||
|
|
||||||
|
public Mock<IAntiforgeryTokenValidator> TokenValidator { get; set; }
|
||||||
|
|
||||||
|
public Mock<IAntiforgeryTokenStore> TokenStore { get; set; }
|
||||||
|
|
||||||
|
public Mock<IAntiforgeryTokenSerializer> TokenSerializer { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class BinaryBlobTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Ctor_BitLength()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var blob = new BinaryBlob(bitLength: 64);
|
||||||
|
var data = blob.GetData();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(64, blob.BitLength);
|
||||||
|
Assert.Equal(64 / 8, data.Length);
|
||||||
|
Assert.NotEqual(new byte[64 / 8], data); // should not be a zero-filled array
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(24)]
|
||||||
|
[InlineData(33)]
|
||||||
|
public void Ctor_BitLength_Bad(int bitLength)
|
||||||
|
{
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(bitLength));
|
||||||
|
Assert.Equal("bitLength", ex.ParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ctor_BitLength_ProducesDifferentValues()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var blobA = new BinaryBlob(bitLength: 64);
|
||||||
|
var blobB = new BinaryBlob(bitLength: 64);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(blobA.GetData(), blobB.GetData());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ctor_Data()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var blob = new BinaryBlob(32, expectedData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(32, blob.BitLength);
|
||||||
|
Assert.Equal(expectedData, blob.GetData());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData((object[])null)]
|
||||||
|
[InlineData(new byte[] { 0x01, 0x02, 0x03 })]
|
||||||
|
public void Ctor_Data_Bad(byte[] data)
|
||||||
|
{
|
||||||
|
// Act & assert
|
||||||
|
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new BinaryBlob(32, data));
|
||||||
|
Assert.Equal("data", ex.ParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Equals_DifferentData_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||||
|
object blobB = new BinaryBlob(32, new byte[] { 0x04, 0x03, 0x02, 0x01 });
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.NotEqual(blobA, blobB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Equals_NotABlob_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
object blobA = new BinaryBlob(32);
|
||||||
|
object blobB = "hello";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.NotEqual(blobA, blobB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Equals_Null_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
object blobA = new BinaryBlob(32);
|
||||||
|
object blobB = null;
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.NotEqual(blobA, blobB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Equals_SameData_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
object blobA = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||||
|
object blobB = new BinaryBlob(32, new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Equal(blobA, blobB);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetHashCodeTest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var blobData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
||||||
|
var expectedHashCode = BitConverter.ToInt32(blobData, 0);
|
||||||
|
|
||||||
|
var blob = new BinaryBlob(32, blobData);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualHashCode = blob.GetHashCode();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedHashCode, actualHashCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
#if DNX451
|
||||||
|
using Moq;
|
||||||
|
#endif
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class ClaimUidExtractorTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ExtractClaimUid_NullIdentity()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
IClaimUidExtractor extractor = new DefaultClaimUidExtractor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var claimUid = extractor.ExtractClaimUid(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(claimUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
[Fact]
|
||||||
|
public void ExtractClaimUid_Unauthenticated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
IClaimUidExtractor extractor = new DefaultClaimUidExtractor();
|
||||||
|
|
||||||
|
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||||
|
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var claimUid = extractor.ExtractClaimUid(mockIdentity.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(claimUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExtractClaimUid_ClaimsIdentity()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||||
|
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
IClaimUidExtractor extractor = new DefaultClaimUidExtractor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var claimUid = extractor.ExtractClaimUid(mockIdentity.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(claimUid);
|
||||||
|
Assert.Equal("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", claimUid);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultUniqueClaimTypes_NotPresent_SerializesAllClaimTypes()
|
||||||
|
{
|
||||||
|
var identity = new ClaimsIdentity();
|
||||||
|
identity.AddClaim(new Claim(ClaimTypes.Email, "someone@antifrogery.com"));
|
||||||
|
identity.AddClaim(new Claim(ClaimTypes.GivenName, "some"));
|
||||||
|
identity.AddClaim(new Claim(ClaimTypes.Surname, "one"));
|
||||||
|
#if DNX451
|
||||||
|
// CoreCLR doesn't support an 'empty' name
|
||||||
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, string.Empty));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var claimsIdentity = (ClaimsIdentity)identity;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var identiferParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(claimsIdentity)
|
||||||
|
.ToArray();
|
||||||
|
var claims = claimsIdentity.Claims.ToList();
|
||||||
|
claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
int index = 0;
|
||||||
|
foreach (var claim in claims)
|
||||||
|
{
|
||||||
|
Assert.Equal(identiferParameters[index++], claim.Type);
|
||||||
|
Assert.Equal(identiferParameters[index++], claim.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultUniqueClaimTypes_Present()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var identity = new ClaimsIdentity();
|
||||||
|
identity.AddClaim(new Claim("fooClaim", "fooClaimValue"));
|
||||||
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "nameIdentifierValue"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var uniqueIdentifierParameters = DefaultClaimUidExtractor.GetUniqueIdentifierParameters(identity);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new string[]
|
||||||
|
{
|
||||||
|
ClaimTypes.NameIdentifier,
|
||||||
|
"nameIdentifierValue",
|
||||||
|
}, uniqueIdentifierParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<ProjectGuid>415e83f8-6002-47e4-aa8e-cd5169c06f28</ProjectGuid>
|
<ProjectGuid>415e83f8-6002-47e4-aa8e-cd5169c06f28</ProjectGuid>
|
||||||
|
|
@ -12,9 +11,11 @@
|
||||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -0,0 +1,588 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
#if DNX451
|
||||||
|
using Moq;
|
||||||
|
#endif
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Antiforgery
|
||||||
|
{
|
||||||
|
public class TokenProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GenerateCookieToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retVal = tokenProvider.GenerateCookieToken();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
[Fact]
|
||||||
|
public void GenerateFormToken_AnonymousUser()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||||
|
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var fieldToken = tokenProvider.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(fieldToken);
|
||||||
|
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||||
|
Assert.False(fieldToken.IsSessionToken);
|
||||||
|
Assert.Empty(fieldToken.Username);
|
||||||
|
Assert.Null(fieldToken.ClaimUid);
|
||||||
|
Assert.Empty(fieldToken.AdditionalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateFormToken_AuthenticatedWithoutUsernameAndNoAdditionalData_NoAdditionalData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
IsSessionToken = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new MyAuthenticatedIdentityWithoutUsername();
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
IClaimUidExtractor claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: claimUidExtractor,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.GenerateFormToken(httpContext, identity, cookieToken));
|
||||||
|
Assert.Equal(
|
||||||
|
"The provided identity of type " +
|
||||||
|
$"'{typeof(MyAuthenticatedIdentityWithoutUsername).FullName}' " +
|
||||||
|
"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.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateFormToken_AuthenticatedWithoutUsername_WithAdditionalData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new MyAuthenticatedIdentityWithoutUsername();
|
||||||
|
|
||||||
|
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||||
|
mockAdditionalDataProvider.Setup(o => o.GetAdditionalData(httpContext))
|
||||||
|
.Returns("additional-data");
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
IClaimUidExtractor claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: claimUidExtractor,
|
||||||
|
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var fieldToken = tokenProvider.GenerateFormToken(httpContext, identity, cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(fieldToken);
|
||||||
|
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||||
|
Assert.False(fieldToken.IsSessionToken);
|
||||||
|
Assert.Empty(fieldToken.Username);
|
||||||
|
Assert.Null(fieldToken.ClaimUid);
|
||||||
|
Assert.Equal("additional-data", fieldToken.AdditionalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateFormToken_ClaimsBasedIdentity()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = GetAuthenticatedIdentity("some-identity");
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
|
||||||
|
byte[] data = new byte[256 / 8];
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
rng.GetBytes(data);
|
||||||
|
}
|
||||||
|
var base64ClaimUId = Convert.ToBase64String(data);
|
||||||
|
var expectedClaimUid = new BinaryBlob(256, data);
|
||||||
|
|
||||||
|
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||||
|
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
|
||||||
|
.Returns(base64ClaimUId);
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var fieldToken = tokenProvider.GenerateFormToken(httpContext, identity, cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(fieldToken);
|
||||||
|
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||||
|
Assert.False(fieldToken.IsSessionToken);
|
||||||
|
Assert.Equal("", fieldToken.Username);
|
||||||
|
Assert.Equal(expectedClaimUid, fieldToken.ClaimUid);
|
||||||
|
Assert.Equal("", fieldToken.AdditionalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateFormToken_RegularUserWithUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var mockIdentity = new Mock<ClaimsIdentity>();
|
||||||
|
mockIdentity.Setup(o => o.IsAuthenticated)
|
||||||
|
.Returns(true);
|
||||||
|
mockIdentity.Setup(o => o.Name)
|
||||||
|
.Returns("my-username");
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
IClaimUidExtractor claimUidExtractor = new Mock<IClaimUidExtractor>().Object;
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: claimUidExtractor,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var fieldToken = tokenProvider.GenerateFormToken(httpContext, mockIdentity.Object, cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(fieldToken);
|
||||||
|
Assert.Equal(cookieToken.SecurityToken, fieldToken.SecurityToken);
|
||||||
|
Assert.False(fieldToken.IsSessionToken);
|
||||||
|
Assert.Equal("my-username", fieldToken.Username);
|
||||||
|
Assert.Null(fieldToken.ClaimUid);
|
||||||
|
Assert.Empty(fieldToken.AdditionalData);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsCookieTokenValid_FieldToken_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
IsSessionToken = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
bool retVal = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsCookieTokenValid_NullToken_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AntiforgeryToken cookieToken = null;
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
bool retVal = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsCookieTokenValid_ValidToken_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var cookieToken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
IsSessionToken = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
bool retVal = tokenProvider.IsCookieTokenValid(cookieToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DNX451
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_SessionTokenMissing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new Mock<ClaimsIdentity>().Object;
|
||||||
|
AntiforgeryToken sessionToken = null;
|
||||||
|
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = "my-cookie-name"
|
||||||
|
};
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(@"The required anti-forgery cookie ""my-cookie-name"" is not present.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_FieldTokenMissing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new Mock<ClaimsIdentity>().Object;
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
AntiforgeryToken fieldtoken = null;
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
FormFieldName = "my-form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(@"The required anti-forgery form field ""my-form-field-name"" is not present.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_FieldAndSessionTokensSwapped()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new Mock<ClaimsIdentity>().Object;
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions()
|
||||||
|
{
|
||||||
|
CookieName = "my-cookie-name",
|
||||||
|
FormFieldName = "my-form-field-name"
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex1 =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, fieldtoken, fieldtoken));
|
||||||
|
Assert.Equal(
|
||||||
|
"Validation of the provided anti-forgery token failed. " +
|
||||||
|
@"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.",
|
||||||
|
ex1.Message);
|
||||||
|
|
||||||
|
var ex2 =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, sessionToken));
|
||||||
|
Assert.Equal(
|
||||||
|
"Validation of the provided anti-forgery token failed. " +
|
||||||
|
@"The cookie ""my-cookie-name"" and the form field ""my-form-field-name"" were swapped.",
|
||||||
|
ex2.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_FieldAndSessionTokensHaveDifferentSecurityKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
ClaimsIdentity identity = new Mock<ClaimsIdentity>().Object;
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken() { IsSessionToken = false };
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(@"The anti-forgery cookie token and form field token do not match.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("the-user", "the-other-user")]
|
||||||
|
[InlineData("http://example.com/uri-casing", "http://example.com/URI-casing")]
|
||||||
|
[InlineData("https://example.com/secure-uri-casing", "https://example.com/secure-URI-casing")]
|
||||||
|
public void ValidateTokens_UsernameMismatch(string identityUsername, string embeddedUsername)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = GetAuthenticatedIdentity(identityUsername);
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
Username = embeddedUsername,
|
||||||
|
IsSessionToken = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||||
|
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
|
||||||
|
.Returns((string)null);
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The provided anti-forgery token was meant for user """ + embeddedUsername +
|
||||||
|
@""", but the current user is """ + identityUsername + @""".", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_ClaimUidMismatch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = GetAuthenticatedIdentity("the-user");
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
IsSessionToken = false,
|
||||||
|
ClaimUid = new BinaryBlob(256)
|
||||||
|
};
|
||||||
|
|
||||||
|
var differentToken = new BinaryBlob(256);
|
||||||
|
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||||
|
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
|
||||||
|
.Returns(Convert.ToBase64String(differentToken.GetData()));
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: null,
|
||||||
|
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(
|
||||||
|
@"The provided anti-forgery token was meant for a different claims-based user than the current user.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_AdditionalDataRejected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = new ClaimsIdentity();
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
Username = String.Empty,
|
||||||
|
IsSessionToken = false,
|
||||||
|
AdditionalData = "some-additional-data"
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||||
|
mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
var ex =
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken));
|
||||||
|
Assert.Equal(@"The provided anti-forgery token failed a custom data check.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_Success_AnonymousUser()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = new ClaimsIdentity();
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
Username = String.Empty,
|
||||||
|
IsSessionToken = false,
|
||||||
|
AdditionalData = "some-additional-data"
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||||
|
mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: null,
|
||||||
|
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Nothing to assert - if we got this far, success!
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_Success_AuthenticatedUserWithUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = GetAuthenticatedIdentity("the-user");
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
Username = "THE-USER",
|
||||||
|
IsSessionToken = false,
|
||||||
|
AdditionalData = "some-additional-data"
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockAdditionalDataProvider = new Mock<IAntiforgeryAdditionalDataProvider>();
|
||||||
|
mockAdditionalDataProvider.Setup(o => o.ValidateAdditionalData(httpContext, "some-additional-data"))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: new Mock<IClaimUidExtractor>().Object,
|
||||||
|
additionalDataProvider: mockAdditionalDataProvider.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Nothing to assert - if we got this far, success!
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateTokens_Success_ClaimsBasedUser()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>().Object;
|
||||||
|
var identity = GetAuthenticatedIdentity("the-user");
|
||||||
|
var sessionToken = new AntiforgeryToken() { IsSessionToken = true };
|
||||||
|
var fieldtoken = new AntiforgeryToken()
|
||||||
|
{
|
||||||
|
SecurityToken = sessionToken.SecurityToken,
|
||||||
|
IsSessionToken = false,
|
||||||
|
ClaimUid = new BinaryBlob(256)
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
|
||||||
|
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
|
||||||
|
.Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData()));
|
||||||
|
|
||||||
|
var config = new AntiforgeryOptions();
|
||||||
|
|
||||||
|
var tokenProvider = new AntiforgeryTokenProvider(
|
||||||
|
config: config,
|
||||||
|
claimUidExtractor: mockClaimUidExtractor.Object,
|
||||||
|
additionalDataProvider: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tokenProvider.ValidateTokens(httpContext, identity, sessionToken, fieldtoken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Nothing to assert - if we got this far, success!
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private static ClaimsIdentity GetAuthenticatedIdentity(string identityUsername)
|
||||||
|
{
|
||||||
|
var claim = new Claim(ClaimsIdentity.DefaultNameClaimType, identityUsername);
|
||||||
|
return new ClaimsIdentity(new[] { claim }, "Some-Authentication");
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class MyAuthenticatedIdentityWithoutUsername : ClaimsIdentity
|
||||||
|
{
|
||||||
|
public override bool IsAuthenticated
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return String.Empty; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Http": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.WebEncoders.Testing": "1.0.0-*",
|
||||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
|
|
@ -8,7 +11,11 @@
|
||||||
"test": "xunit.runner.aspnet"
|
"test": "xunit.runner.aspnet"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"dnx451": { },
|
"dnx451": {
|
||||||
|
"dependencies": {
|
||||||
|
"Moq": "4.2.1312.1622"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dnxcore50": { }
|
"dnxcore50": { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue