/
TokenValidator.cs
142 lines (122 loc) · 5.79 KB
/
TokenValidator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 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.Contracts;
using System.Globalization;
using System.Security.Principal;
using System.Web.Mvc;
using System.Web.WebPages.Resources;
namespace System.Web.Helpers.AntiXsrf
{
internal sealed class TokenValidator : ITokenValidator
{
private readonly IClaimUidExtractor _claimUidExtractor;
private readonly IAntiForgeryConfig _config;
internal TokenValidator(IAntiForgeryConfig config, IClaimUidExtractor claimUidExtractor)
{
_config = config;
_claimUidExtractor = claimUidExtractor;
}
public AntiForgeryToken GenerateCookieToken()
{
return new AntiForgeryToken()
{
// SecurityToken will be populated automatically.
IsSessionToken = true
};
}
public AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken)
{
Contract.Assert(IsCookieTokenValid(cookieToken));
AntiForgeryToken formToken = new AntiForgeryToken()
{
SecurityToken = cookieToken.SecurityToken,
IsSessionToken = false
};
bool requireAuthenticatedUserHeuristicChecks = false;
// populate Username and ClaimUid
if (identity != null && identity.IsAuthenticated)
{
if (!_config.SuppressIdentityHeuristicChecks)
{
// If the user is authenticated and heuristic checks are not suppressed,
// then Username, ClaimUid, or AdditionalData must be set.
requireAuthenticatedUserHeuristicChecks = true;
}
formToken.ClaimUid = _claimUidExtractor.ExtractClaimUid(identity);
if (formToken.ClaimUid == null)
{
formToken.Username = identity.Name;
}
}
// populate AdditionalData
if (_config.AdditionalDataProvider != null)
{
formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext);
}
if (requireAuthenticatedUserHeuristicChecks
&& String.IsNullOrEmpty(formToken.Username)
&& formToken.ClaimUid == null
&& String.IsNullOrEmpty(formToken.AdditionalData))
{
// Application says user is authenticated, but we have no identifier for the user.
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
WebPageResources.TokenValidator_AuthenticatedUserWithoutUsername, identity.GetType()));
}
return formToken;
}
public bool IsCookieTokenValid(AntiForgeryToken cookieToken)
{
return (cookieToken != null && cookieToken.IsSessionToken);
}
public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
{
// Were the tokens even present at all?
if (sessionToken == null)
{
throw HttpAntiForgeryException.CreateCookieMissingException(_config.CookieName);
}
if (fieldToken == null)
{
throw HttpAntiForgeryException.CreateFormFieldMissingException(_config.FormFieldName);
}
// Do the tokens have the correct format?
if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
{
throw HttpAntiForgeryException.CreateTokensSwappedException(_config.CookieName, _config.FormFieldName);
}
// Are the security tokens embedded in each incoming token identical?
if (!Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
{
throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
}
// Is the incoming token meant for the current user?
string currentUsername = String.Empty;
BinaryBlob currentClaimUid = null;
if (identity != null && identity.IsAuthenticated)
{
currentClaimUid = _claimUidExtractor.ExtractClaimUid(identity);
if (currentClaimUid == null)
{
currentUsername = identity.Name ?? String.Empty;
}
}
// OpenID and other similar authentication schemes use URIs for the username.
// These should be treated as case-sensitive.
bool useCaseSensitiveUsernameComparison = currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|| currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
{
throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername);
}
if (!Equals(fieldToken.ClaimUid, currentClaimUid))
{
throw HttpAntiForgeryException.CreateClaimUidMismatchException();
}
// Is the AdditionalData valid?
if (_config.AdditionalDataProvider != null && !_config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
{
throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
}
}
}
}