-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCacheKeyComputer.cs
More file actions
142 lines (120 loc) · 4.36 KB
/
Copy pathCacheKeyComputer.cs
File metadata and controls
142 lines (120 loc) · 4.36 KB
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
using System.Text;
using System.Text.Json;
namespace HttpClientCache;
public class CacheKeyComputer : ICacheKeyComputer
{
private const char Separator = '\x1E';
private const char Null = '\x00';
[ThreadStatic]
private static StringBuilder? _stringBuilder;
public bool RequireJwtToken { get; set; } = false;
public string? ComputeKey(HttpRequestMessage request, Variation variation)
{
var builder = _stringBuilder ??= new();
try
{
var url = request.RequestUri!;
builder.Append(request.Method.Method.ToLowerInvariant());
builder.Append(Separator);
builder.Append(url.Scheme.ToLowerInvariant());
builder.Append(Separator);
builder.Append(url.Host.ToLowerInvariant());
builder.Append(Separator);
builder.Append(url.Port);
builder.Append(Separator);
builder.Append(url.PathAndQuery);
builder.Append(Separator);
if (variation.CacheType == CacheType.Private)
{
var userId = GetUserId(request);
if (userId is null)
{
return null;
}
builder.Append(userId);
}
else
{
builder.Append(Null);
}
foreach (var headerName in variation.NormalizedVaryHeaders)
{
builder.Append(Separator).Append(headerName).Append('=');
if (request.Headers.TryGetValues(headerName, out var headerValues))
{
var headerValuesArray = headerValues.ToArray();
Array.Sort(headerValuesArray, StringComparer.Ordinal);
builder.Append(headerValuesArray[0]);
for (var i = 1; i < headerValuesArray.Length; i++)
{
builder.Append(',');
builder.Append(headerValuesArray[i]);
}
}
else
{
builder.Append(Null);
}
}
return builder.ToString();
}
finally
{
builder.Clear();
}
}
protected virtual string? GetUserId(HttpRequestMessage request)
{
if (!request.Headers.TryGetValues("Authorization", out var headerValues))
{
return null;
}
var headerValue = headerValues.First();
if (headerValue.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
var accessToken = headerValue.Substring(7);
if (JwtDecoder.TryDecodePayload(accessToken, out var payloadDocument))
{
// A valid JWT was supplied. Use the identifier derived from its claims even if
// that identifier is null (e.g. neither "sub" nor "client_id" is present) so that
// such a token is treated consistently rather than falling back to the raw value.
using (payloadDocument)
{
return GetUserId(payloadDocument.RootElement);
}
}
}
if (RequireJwtToken)
{
return null;
}
// Fallback to using the Authorization header value directly
// This is not ideal if the token value recycles often, but it is fine if the token is stable (e.g. an api key)
return headerValue;
}
/// <summary>
/// Derive a stable user identifier from a decoded JWT payload. The default implementation uses
/// the <c>sub</c> claim, falling back to <c>client_id</c>. Override to use different claims.
/// </summary>
/// <param name="jwtPayload">
/// The decoded JWT payload. This value is only valid for the duration of the call.
/// </param>
protected virtual string? GetUserId(JsonElement jwtPayload)
{
if (
jwtPayload.TryGetProperty("sub", out var sub)
&& sub.ValueKind == JsonValueKind.String
)
{
return "sub:" + sub.GetString();
}
if (
jwtPayload.TryGetProperty("client_id", out var clientId)
&& clientId.ValueKind == JsonValueKind.String
)
{
return "client_id:" + clientId.GetString();
}
return null;
}
}