This repository has been archived by the owner on Jan 24, 2021. It is now read-only.
/
DiagnosticsHook.cs
257 lines (203 loc) · 10.7 KB
/
DiagnosticsHook.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
namespace Nancy.Diagnostics
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Bootstrapper;
using Cookies;
using Cryptography;
using Helpers;
using ModelBinding;
using Responses;
using Responses.Negotiation;
using Routing;
public static class DiagnosticsHook
{
private const string PipelineKey = "__Diagnostics";
public static void Enable(DiagnosticsConfiguration diagnosticsConfiguration, IPipelines pipelines, IEnumerable<IDiagnosticsProvider> providers, IRootPathProvider rootPathProvider, IEnumerable<ISerializer> serializers, IRequestTracing requestTracing, NancyInternalConfiguration configuration, IModelBinderLocator modelBinderLocator, IEnumerable<IResponseProcessor> responseProcessors)
{
var keyGenerator = new DefaultModuleKeyGenerator();
var diagnosticsModuleCatalog = new DiagnosticsModuleCatalog(keyGenerator, providers, rootPathProvider, requestTracing, configuration, diagnosticsConfiguration);
var diagnosticsRouteCache = new RouteCache(diagnosticsModuleCatalog, keyGenerator, new DefaultNancyContextFactory(), new DefaultRouteSegmentExtractor(), new DefaultRouteDescriptionProvider());
var diagnosticsRouteResolver = new DefaultRouteResolver(
diagnosticsModuleCatalog,
new DefaultRoutePatternMatcher(),
new DiagnosticsModuleBuilder(rootPathProvider, serializers, modelBinderLocator),
diagnosticsRouteCache,
responseProcessors);
var serializer = new DefaultObjectSerializer();
pipelines.BeforeRequest.AddItemToStartOfPipeline(
new PipelineItem<Func<NancyContext, Response>>(
PipelineKey,
ctx =>
{
if (!ctx.ControlPanelEnabled)
{
return null;
}
if (!ctx.Request.Path.StartsWith(diagnosticsConfiguration.Path, StringComparison.OrdinalIgnoreCase))
{
return null;
}
var resourcePrefix =
string.Concat(diagnosticsConfiguration.Path, "/Resources/");
if (ctx.Request.Path.StartsWith(resourcePrefix, StringComparison.OrdinalIgnoreCase))
{
var resourceNamespace = "Nancy.Diagnostics.Resources";
var path = Path.GetDirectoryName(ctx.Request.Url.Path.Replace(resourcePrefix, string.Empty)) ?? string.Empty;
if (!string.IsNullOrEmpty(path))
{
resourceNamespace += string.Format(".{0}", path.Replace('\\', '.'));
}
return new EmbeddedFileResponse(
typeof(DiagnosticsHook).Assembly,
resourceNamespace,
Path.GetFileName(ctx.Request.Url.Path));
}
return diagnosticsConfiguration.Valid
? ExecuteDiagnostics(ctx, diagnosticsRouteResolver, diagnosticsConfiguration, serializer)
: GetDiagnosticsHelpView(ctx);
}));
}
public static void Disable(IPipelines pipelines)
{
pipelines.BeforeRequest.RemoveByName(PipelineKey);
}
private static Response GetDiagnosticsHelpView(NancyContext ctx)
{
return (StaticConfiguration.IsRunningDebug)
? new DiagnosticsViewRenderer(ctx)["help"]
: HttpStatusCode.NotFound;
}
private static Response GetDiagnosticsLoginView(NancyContext ctx)
{
var renderer = new DiagnosticsViewRenderer(ctx);
return renderer["login"];
}
private static Response ExecuteDiagnostics(NancyContext ctx, IRouteResolver routeResolver, DiagnosticsConfiguration diagnosticsConfiguration, DefaultObjectSerializer serializer)
{
var session = GetSession(ctx, diagnosticsConfiguration, serializer);
ctx.Request.Url.BasePath =
string.Concat(ctx.Request.Url.BasePath, diagnosticsConfiguration.Path);
ctx.Request.Url.Path =
ctx.Request.Url.Path.Substring(diagnosticsConfiguration.Path.Length);
if (ctx.Request.Url.Path.Length.Equals(0))
{
ctx.Request.Url.Path = "/";
}
if (session == null)
{
var view = GetDiagnosticsLoginView(ctx);
view.AddCookie(
new NancyCookie(diagnosticsConfiguration.CookieName, String.Empty, true) { Expires = DateTime.Now.AddDays(-1) });
return view;
}
var resolveResult = routeResolver.Resolve(ctx);
ctx.Parameters = resolveResult.Item2;
var resolveResultPreReq = resolveResult.Item3;
var resolveResultPostReq = resolveResult.Item4;
ExecuteRoutePreReq(ctx, resolveResultPreReq);
if (ctx.Response == null)
{
ctx.Response = resolveResult.Item1.Invoke(resolveResult.Item2);
}
if (ctx.Request.Method.ToUpperInvariant() == "HEAD")
{
ctx.Response = new HeadResponse(ctx.Response);
}
if (resolveResultPostReq != null)
{
resolveResultPostReq.Invoke(ctx);
}
AddUpdateSessionCookie(session, ctx, diagnosticsConfiguration, serializer);
return ctx.Response;
}
private static void AddUpdateSessionCookie(DiagnosticsSession session, NancyContext context, DiagnosticsConfiguration diagnosticsConfiguration, DefaultObjectSerializer serializer)
{
if (context.Response == null)
{
return;
}
session.Expiry = DateTime.Now.AddMinutes(diagnosticsConfiguration.SlidingTimeout);
var serializedSession = serializer.Serialize(session);
var encryptedSession = diagnosticsConfiguration.CryptographyConfiguration.EncryptionProvider.Encrypt(serializedSession);
var hmacBytes = diagnosticsConfiguration.CryptographyConfiguration.HmacProvider.GenerateHmac(encryptedSession);
var hmacString = Convert.ToBase64String(hmacBytes);
var cookie = new NancyCookie(diagnosticsConfiguration.CookieName, String.Format("{1}{0}", encryptedSession, hmacString), true);
context.Response.AddCookie(cookie);
}
private static DiagnosticsSession GetSession(NancyContext context, DiagnosticsConfiguration diagnosticsConfiguration, DefaultObjectSerializer serializer)
{
if (context.Request == null)
{
return null;
}
if (IsLoginRequest(context, diagnosticsConfiguration))
{
return ProcessLogin(context, diagnosticsConfiguration, serializer);
}
if (!context.Request.Cookies.ContainsKey(diagnosticsConfiguration.CookieName))
{
return null;
}
var encryptedValue = HttpUtility.UrlDecode(context.Request.Cookies[diagnosticsConfiguration.CookieName]);
var hmacStringLength = Base64Helpers.GetBase64Length(diagnosticsConfiguration.CryptographyConfiguration.HmacProvider.HmacLength);
var encryptedSession = encryptedValue.Substring(hmacStringLength);
var hmacString = encryptedValue.Substring(0, hmacStringLength);
var hmacBytes = Convert.FromBase64String(hmacString);
var newHmac = diagnosticsConfiguration.CryptographyConfiguration.HmacProvider.GenerateHmac(encryptedSession);
var hmacValid = HmacComparer.Compare(newHmac, hmacBytes, diagnosticsConfiguration.CryptographyConfiguration.HmacProvider.HmacLength);
if (!hmacValid)
{
return null;
}
var decryptedValue = diagnosticsConfiguration.CryptographyConfiguration.EncryptionProvider.Decrypt(encryptedSession);
var session = serializer.Deserialize(decryptedValue) as DiagnosticsSession;
if (session == null || session.Expiry < DateTime.Now || !SessionPasswordValid(session, diagnosticsConfiguration.Password))
{
return null;
}
return session;
}
private static bool SessionPasswordValid(DiagnosticsSession session, string realPassword)
{
var newHash = DiagnosticsSession.GenerateSaltedHash(realPassword, session.Salt);
return (newHash.Length == session.Hash.Length && newHash.SequenceEqual(session.Hash));
}
private static DiagnosticsSession ProcessLogin(NancyContext context, DiagnosticsConfiguration diagnosticsConfiguration, DefaultObjectSerializer serializer)
{
string password = context.Request.Form.Password;
if (!string.Equals(password, diagnosticsConfiguration.Password, StringComparison.Ordinal))
{
return null;
}
var salt = DiagnosticsSession.GenerateRandomSalt();
var hash = DiagnosticsSession.GenerateSaltedHash(password, salt);
var session = new DiagnosticsSession
{
Hash = hash,
Salt = salt,
Expiry = DateTime.Now.AddMinutes(diagnosticsConfiguration.SlidingTimeout)
};
return session;
}
private static bool IsLoginRequest(NancyContext context, DiagnosticsConfiguration diagnosticsConfiguration)
{
return context.Request.Method == "POST" &&
context.Request.Path.TrimEnd(new[] { '/' }) == diagnosticsConfiguration.Path;
}
private static void ExecuteRoutePreReq(NancyContext context, Func<NancyContext, Response> resolveResultPreReq)
{
if (resolveResultPreReq == null)
{
return;
}
var resolveResultPreReqResponse = resolveResultPreReq.Invoke(context);
if (resolveResultPreReqResponse != null)
{
context.Response = resolveResultPreReqResponse;
}
}
}
}