diff --git a/src/Nancy.Tests/Nancy.Tests.csproj b/src/Nancy.Tests/Nancy.Tests.csproj index 192368c89c..2c0858585d 100644 --- a/src/Nancy.Tests/Nancy.Tests.csproj +++ b/src/Nancy.Tests/Nancy.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Nancy.Tests/Unit/Diagnostics/CustomInteractiveDiagnosticsFixture.cs b/src/Nancy.Tests/Unit/Diagnostics/CustomInteractiveDiagnosticsFixture.cs new file mode 100644 index 0000000000..224f5ee9d3 --- /dev/null +++ b/src/Nancy.Tests/Unit/Diagnostics/CustomInteractiveDiagnosticsFixture.cs @@ -0,0 +1,138 @@ +namespace Nancy.Tests.Unit.Diagnostics +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Fakes; + using FakeItEasy; + using Nancy.Bootstrapper; + using Nancy.Cookies; + using Nancy.Cryptography; + using Nancy.Culture; + using Nancy.Diagnostics; + using Nancy.ModelBinding; + using Nancy.Responses.Negotiation; + using Nancy.Testing; + using Xunit; + + public class CustomInteractiveDiagnosticsHookFixture + { + private const string DiagsCookieName = "__ncd"; + + private readonly CryptographyConfiguration cryptoConfig; + + private readonly IObjectSerializer objectSerializer; + + public CustomInteractiveDiagnosticsHookFixture() + { + this.cryptoConfig = CryptographyConfiguration.Default; + this.objectSerializer = new DefaultObjectSerializer(); + } + + private class FakeDiagnostics : IDiagnostics + { + private readonly DiagnosticsConfiguration diagnosticsConfiguration; + private readonly IEnumerable diagnosticProviders; + private readonly IRootPathProvider rootPathProvider; + private readonly IEnumerable serializers; + private readonly IRequestTracing requestTracing; + private readonly NancyInternalConfiguration configuration; + private readonly IModelBinderLocator modelBinderLocator; + private readonly IEnumerable responseProcessors; + private readonly ICultureService cultureService; + + public FakeDiagnostics(DiagnosticsConfiguration diagnosticsConfiguration, IEnumerable diagnosticProviders, IRootPathProvider rootPathProvider, IEnumerable serializers, IRequestTracing requestTracing, NancyInternalConfiguration configuration, IModelBinderLocator modelBinderLocator, IEnumerable responseProcessors, ICultureService cultureService) + { + this.diagnosticsConfiguration = diagnosticsConfiguration; + this.diagnosticProviders = (new IDiagnosticsProvider[] { new FakeDiagnosticsProvider() }).ToArray(); + this.rootPathProvider = rootPathProvider; + this.serializers = serializers; + this.requestTracing = requestTracing; + this.configuration = configuration; + this.modelBinderLocator = modelBinderLocator; + this.responseProcessors = responseProcessors; + this.cultureService = cultureService; + } + + public void Initialize(IPipelines pipelines) + { + DiagnosticsHook.Enable(this.diagnosticsConfiguration, pipelines, this.diagnosticProviders, this.rootPathProvider, this.serializers, this.requestTracing, this.configuration, this.modelBinderLocator, this.responseProcessors, this.cultureService); + } + } + + private class FakeDiagnosticsProvider : IDiagnosticsProvider + { + public string Name + { + get { return "Fake testing provider"; } + } + + public string Description + { + get { return "Fake testing provider"; } + } + + public object DiagnosticObject + { + get { return this; } + } + } + + [Fact] + public void Should_return_main_page_with_valid_auth_cookie() + { + // Given + var diagsConfig = new DiagnosticsConfiguration { Password = "password", CryptographyConfiguration = this.cryptoConfig }; + + var bootstrapper = new ConfigurableBootstrapper(with => + { + with.EnableAutoRegistration(); + with.DiagnosticsConfiguration(diagsConfig); + with.Diagnostics(); + }); + + var browser = new Browser(bootstrapper); + + // When + var result = browser.Get(diagsConfig.Path + "/interactive/providers/", with => + { + with.Cookie(DiagsCookieName, this.GetSessionCookieValue("password")); + }); + + // Then should see our fake provider and not the default testing provider + result.Body.AsString().ShouldContain("Fake testing provider"); + result.Body.AsString().ShouldNotContain("Testing Diagnostic Provider"); + } + + private string GetSessionCookieValue(string password, DateTime? expiry = null) + { + var salt = DiagnosticsSession.GenerateRandomSalt(); + var hash = DiagnosticsSession.GenerateSaltedHash(password, salt); + var session = new DiagnosticsSession + { + Hash = hash, + Salt = salt, + Expiry = expiry.HasValue ? expiry.Value : DateTime.Now.AddMinutes(15), + }; + + var serializedSession = this.objectSerializer.Serialize(session); + + var encryptedSession = this.cryptoConfig.EncryptionProvider.Encrypt(serializedSession); + var hmacBytes = this.cryptoConfig.HmacProvider.GenerateHmac(encryptedSession); + var hmacString = Convert.ToBase64String(hmacBytes); + + return String.Format("{1}{0}", encryptedSession, hmacString); + } + + private DiagnosticsSession DecodeCookie(INancyCookie nancyCookie) + { + var cookieValue = nancyCookie.Value; + var hmacStringLength = Base64Helpers.GetBase64Length(this.cryptoConfig.HmacProvider.HmacLength); + var encryptedSession = cookieValue.Substring(hmacStringLength); + var decrypted = this.cryptoConfig.EncryptionProvider.Decrypt(encryptedSession); + + return this.objectSerializer.Deserialize(decrypted) as DiagnosticsSession; + } + } +} diff --git a/src/Nancy.Tests/Unit/Diagnostics/DiagnosticsHookFixture.cs b/src/Nancy.Tests/Unit/Diagnostics/DiagnosticsHookFixture.cs index 8d0541cb63..c94540d61e 100644 --- a/src/Nancy.Tests/Unit/Diagnostics/DiagnosticsHookFixture.cs +++ b/src/Nancy.Tests/Unit/Diagnostics/DiagnosticsHookFixture.cs @@ -29,7 +29,8 @@ public void Should_return_info_page_if_password_null() // Given var diagsConfig = new DiagnosticsConfiguration { Password = null, CryptographyConfiguration = this.cryptoConfig }; - var bootstrapper = new ConfigurableBootstrapper(with =>{ + var bootstrapper = new ConfigurableBootstrapper(with => + { with.EnableAutoRegistration(); with.DiagnosticsConfiguration(diagsConfig); with.Diagnostics(); @@ -244,6 +245,32 @@ public void Should_use_rolling_expiry_for_auth_cookie() .Expiry.ShouldNotEqual(expiryDate); } + [Fact] + public void Should_return_diagnostic_example() + { + // Given no custom interactive diagnostic providers + var diagsConfig = new DiagnosticsConfiguration { Password = "password", CryptographyConfiguration = this.cryptoConfig }; + + var bootstrapper = new ConfigurableBootstrapper(with => + { + with.EnableAutoRegistration(); + with.DiagnosticsConfiguration(diagsConfig); + with.Diagnostics(); + }); + + var browser = new Browser(bootstrapper); + + // When querying the list of interactive providers + var result = browser.Get(diagsConfig.Path + "/interactive/providers/", with => + { + with.Cookie(DiagsCookieName, this.GetSessionCookieValue("password")); + }); + + // Then we should see the fake testing provider and not the Nancy provided testing example + result.Body.AsString().ShouldNotContain("Fake testing provider"); + result.Body.AsString().Contains("Testing Diagnostic Provider"); + } + private string GetSessionCookieValue(string password, DateTime? expiry = null) { var salt = DiagnosticsSession.GenerateRandomSalt(); @@ -270,7 +297,7 @@ private DiagnosticsSession DecodeCookie(INancyCookie nancyCookie) var hmacStringLength = Base64Helpers.GetBase64Length(this.cryptoConfig.HmacProvider.HmacLength); var encryptedSession = cookieValue.Substring(hmacStringLength); var decrypted = this.cryptoConfig.EncryptionProvider.Decrypt(encryptedSession); - + return this.objectSerializer.Deserialize(decrypted) as DiagnosticsSession; } } diff --git a/src/Nancy/Diagnostics/InteractiveDiagnostics.cs b/src/Nancy/Diagnostics/InteractiveDiagnostics.cs index fe5d70ebe6..29c1ad00a1 100644 --- a/src/Nancy/Diagnostics/InteractiveDiagnostics.cs +++ b/src/Nancy/Diagnostics/InteractiveDiagnostics.cs @@ -15,7 +15,23 @@ public class InteractiveDiagnostics : IInteractiveDiagnostics public InteractiveDiagnostics(IEnumerable providers) { - this.providers = providers.ToArray(); + var customProvidersAvailable = providers.Any(provider => + { + Type providerType = provider.GetType(); + + return providerType != typeof(Nancy.Diagnostics.TestingDiagnosticProvider) & + providerType != typeof(Nancy.Routing.DefaultRouteCacheProvider); + }); + + if (customProvidersAvailable) + { + // Exclude only the TestingDiagnosticProvider + this.providers = providers.Where(provider => provider.GetType() != typeof(Nancy.Diagnostics.TestingDiagnosticProvider)).ToArray(); + } + else + { + this.providers = providers.ToArray(); + } this.BuildAvailableDiagnostics(); }