diff --git a/rakefile.rb b/rakefile.rb index b28124bac1..c39a3c0146 100644 --- a/rakefile.rb +++ b/rakefile.rb @@ -55,7 +55,7 @@ desc "Compile solution file for Mono" xbuild :compilemono => [:assembly_info] do |xb| xb.solution = SOLUTION_FILE - xb.properties = { :configuration => CONFIGURATIONMONO, "TargetFrameworkProfile" => "", "TargetFrameworkVersion" => "v4.0" } + xb.properties = { :configuration => CONFIGURATIONMONO, "TargetFrameworkProfile" => "", "TargetFrameworkVersion" => "v4.5" } end desc "Gathers output files and copies them to the output folder" diff --git a/src/Nancy.Authentication.Basic.Tests/BasicAuthenticationFixture.cs b/src/Nancy.Authentication.Basic.Tests/BasicAuthenticationFixture.cs index 49f0e77db3..83e95f63c6 100644 --- a/src/Nancy.Authentication.Basic.Tests/BasicAuthenticationFixture.cs +++ b/src/Nancy.Authentication.Basic.Tests/BasicAuthenticationFixture.cs @@ -2,13 +2,13 @@ { using System; using System.Collections.Generic; + using System.Security.Claims; using System.Text; using System.Threading; using FakeItEasy; using Nancy.Bootstrapper; - using Nancy.Security; using Nancy.Tests; using Nancy.Tests.Fakes; @@ -252,7 +252,7 @@ public void Should_set_user_in_context_with_valid_username_in_auth_header() var fakePipelines = new Pipelines(); var validator = A.Fake(); - var fakeUser = A.Fake(); + var fakeUser = A.Fake(); A.CallTo(() => validator.Validate("foo", "bar")).Returns(fakeUser); var cfg = new BasicAuthenticationConfiguration(validator, "realm"); diff --git a/src/Nancy.Authentication.Basic/IUserValidator.cs b/src/Nancy.Authentication.Basic/IUserValidator.cs index 4149bc27e7..1670670319 100644 --- a/src/Nancy.Authentication.Basic/IUserValidator.cs +++ b/src/Nancy.Authentication.Basic/IUserValidator.cs @@ -1,18 +1,18 @@ namespace Nancy.Authentication.Basic { - using Nancy.Security; + using System.Security.Claims; /// - /// Provides a way to validate the username and password - /// - public interface IUserValidator - { - /// - /// Validates the username and password - /// - /// Username - /// Password - /// A value representing the authenticated user, null if the user was not authenticated. - IUserIdentity Validate(string username, string password); - } -} + /// Provides a way to validate the username and password + /// + public interface IUserValidator + { + /// + /// Validates the username and password + /// + /// Username + /// Password + /// A value representing the authenticated user, null if the user was not authenticated. + ClaimsPrincipal Validate(string username, string password); + } +} \ No newline at end of file diff --git a/src/Nancy.Authentication.Forms.Tests/Fakes/FakeUserIdentity.cs b/src/Nancy.Authentication.Forms.Tests/Fakes/FakeUserIdentity.cs deleted file mode 100644 index 55538f7cde..0000000000 --- a/src/Nancy.Authentication.Forms.Tests/Fakes/FakeUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Authentication.Forms.Tests.Fakes -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class FakeUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Forms.Tests/FormsAuthenticationFixture.cs b/src/Nancy.Authentication.Forms.Tests/FormsAuthenticationFixture.cs index cc4830ab78..05a71fc12d 100644 --- a/src/Nancy.Authentication.Forms.Tests/FormsAuthenticationFixture.cs +++ b/src/Nancy.Authentication.Forms.Tests/FormsAuthenticationFixture.cs @@ -2,11 +2,12 @@ namespace Nancy.Authentication.Forms.Tests { using System; using System.Linq; + using System.Security.Claims; + using System.Security.Principal; using System.Threading; using FakeItEasy; - using Nancy.Authentication.Forms.Tests.Fakes; using Nancy.Bootstrapper; using Nancy.Cryptography; using Nancy.Helpers; @@ -379,7 +380,7 @@ public void Should_have_expired_empty_authentication_cookie_in_logout_response_w var result = FormsAuthentication.LogOutResponse(); // Then - var cookie = result.Cookies.Where(c => c.Name == FormsAuthentication.FormsAuthenticationCookieName).First(); + var cookie = result.Cookies.First(c => c.Name == FormsAuthentication.FormsAuthenticationCookieName); cookie.Value.ShouldBeEmpty(); cookie.Expires.ShouldNotBeNull(); (cookie.Expires < DateTime.Now).ShouldBeTrue(); @@ -405,7 +406,7 @@ public void Should_set_user_in_context_with_valid_cookie() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity {UserName = "Bob"}; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); @@ -421,7 +422,7 @@ public void Should_not_set_user_in_context_with_empty_cookie() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity {UserName = "Bob"}; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); @@ -437,7 +438,7 @@ public void Should_not_set_user_in_context_with_invalid_hmac() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity { UserName = "Bob" }; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); @@ -453,7 +454,7 @@ public void Should_not_set_user_in_context_with_empty_hmac() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity { UserName = "Bob" }; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); @@ -469,7 +470,7 @@ public void Should_not_set_user_in_context_with_no_hmac() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity { UserName = "Bob" }; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); @@ -485,7 +486,7 @@ public void Should_not_set_username_in_context_with_broken_encryption_data() { var fakePipelines = new Pipelines(); var fakeMapper = A.Fake(); - var fakeUser = new FakeUserIdentity { UserName = "Bob" }; + var fakeUser = new ClaimsPrincipal(new GenericIdentity("Bob")); A.CallTo(() => fakeMapper.GetUserFromIdentifier(this.userGuid, this.context)).Returns(fakeUser); this.config.UserMapper = fakeMapper; FormsAuthentication.Enable(fakePipelines, this.config); diff --git a/src/Nancy.Authentication.Forms.Tests/Nancy.Authentication.Forms.Tests.csproj b/src/Nancy.Authentication.Forms.Tests/Nancy.Authentication.Forms.Tests.csproj index c852977671..56e083b132 100644 --- a/src/Nancy.Authentication.Forms.Tests/Nancy.Authentication.Forms.Tests.csproj +++ b/src/Nancy.Authentication.Forms.Tests/Nancy.Authentication.Forms.Tests.csproj @@ -173,7 +173,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Nancy.Authentication.Forms/IUserMapper.cs b/src/Nancy.Authentication.Forms/IUserMapper.cs index c0a66e6cb6..6757580a7e 100644 --- a/src/Nancy.Authentication.Forms/IUserMapper.cs +++ b/src/Nancy.Authentication.Forms/IUserMapper.cs @@ -1,8 +1,7 @@ namespace Nancy.Authentication.Forms { using System; - - using Nancy.Security; + using System.Security.Claims; /// /// Provides a mapping between forms auth guid identifiers and @@ -16,6 +15,6 @@ public interface IUserMapper /// User identifier /// The current NancyFx context /// Matching populated IUserIdentity object, or empty - IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context); + ClaimsPrincipal GetUserFromIdentifier(Guid identifier, NancyContext context); } } \ No newline at end of file diff --git a/src/Nancy.Authentication.Stateless/StatelessAuthenticationConfiguration.cs b/src/Nancy.Authentication.Stateless/StatelessAuthenticationConfiguration.cs index a1f1af6fe8..0a7341f1ac 100644 --- a/src/Nancy.Authentication.Stateless/StatelessAuthenticationConfiguration.cs +++ b/src/Nancy.Authentication.Stateless/StatelessAuthenticationConfiguration.cs @@ -1,22 +1,21 @@ namespace Nancy.Authentication.Stateless { using System; - - using Nancy.Security; + using System.Security.Claims; /// /// Configuration options for stateless authentication /// public class StatelessAuthenticationConfiguration { - internal Func GetUserIdentity; + internal readonly Func GetUserIdentity; /// /// Initializes a new instance of the class. /// - public StatelessAuthenticationConfiguration(Func getUserIdentity) + public StatelessAuthenticationConfiguration(Func getUserIdentity) { - GetUserIdentity = getUserIdentity; + this.GetUserIdentity = getUserIdentity; } /// diff --git a/src/Nancy.Authentication.Token.Tests/Nancy.Authentication.Token.Tests.csproj b/src/Nancy.Authentication.Token.Tests/Nancy.Authentication.Token.Tests.csproj deleted file mode 100644 index f053bdb288..0000000000 --- a/src/Nancy.Authentication.Token.Tests/Nancy.Authentication.Token.Tests.csproj +++ /dev/null @@ -1,117 +0,0 @@ - - - - - Debug - AnyCPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20} - Library - Properties - Nancy.Authentication.Token.Tests - Nancy.Authentication.Token.Tests - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - true - bin\MonoDebug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - bin\MonoRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - - False - ..\packages\FakeItEasy.1.19.0\lib\net40\FakeItEasy.dll - - - - - - - - - - False - ..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - False - ..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - - - - - Fakes\FakeRequest.cs - - - ShouldExtensions.cs - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - - - - - {97fa024a-f6ed-4086-bcc1-1a51be63474c} - Nancy.Authentication.Token - - - {D79203C0-B672-4751-9C95-C3AB7D3FEFBE} - Nancy.Testing - - - {34576216-0dca-4b0f-a0dc-9075e75a676f} - Nancy - - - - - \ No newline at end of file diff --git a/src/Nancy.Authentication.Token.Tests/Storage/FileSystemTokenKeyStoreFixture.cs b/src/Nancy.Authentication.Token.Tests/Storage/FileSystemTokenKeyStoreFixture.cs deleted file mode 100644 index d047db4c09..0000000000 --- a/src/Nancy.Authentication.Token.Tests/Storage/FileSystemTokenKeyStoreFixture.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Nancy.Authentication.Token.Tests.Storage -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - - using Nancy.Authentication.Token.Storage; - using Nancy.Testing.Fakes; - - using Xunit; - - public class FileSystemTokenKeyStoreFixture - { - [Fact] - public void Should_store_keys_in_file() - { - // Given - var keyStore = GetKeyStore(); - try - { - var keys = new Dictionary - { - { DateTime.UtcNow, Encoding.UTF8.GetBytes("fake encryption key") } - }; - - // When - keyStore.Store(keys); - - // Then - Assert.True(File.Exists(keyStore.FilePath)); - } - finally - { - keyStore.Purge(); - } - } - - [Fact] - public void Should_retrieve_keys_from_file() - { - // Given - var keyStore = GetKeyStore(); - try - { - var keys = new Dictionary - { - { DateTime.UtcNow, Encoding.UTF8.GetBytes("fake encryption key") } - }; - - keyStore.Store(keys); - - // When - var retrievedKeys = keyStore.Retrieve(); - - // Then - Assert.True(Encoding.UTF8.GetString(retrievedKeys.Values.First()) == "fake encryption key"); - } - finally - { - keyStore.Purge(); - } - } - - private static FileSystemTokenKeyStore GetKeyStore() - { - var rootPathProvider = new FakeRootPathProvider(); - return new FileSystemTokenKeyStore(rootPathProvider); - } - } -} diff --git a/src/Nancy.Authentication.Token.Tests/TokenAuthenticationConfigurationFixture.cs b/src/Nancy.Authentication.Token.Tests/TokenAuthenticationConfigurationFixture.cs deleted file mode 100644 index 65999ee00a..0000000000 --- a/src/Nancy.Authentication.Token.Tests/TokenAuthenticationConfigurationFixture.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Nancy.Authentication.Token.Tests -{ - using System; - - using Nancy.Tests; - - using Xunit; - - public class TokenAuthenticationConfigurationFixture - { - [Fact] - public void Should_throw_with_null_tokenizer() - { - var result = Record.Exception(() => new TokenAuthenticationConfiguration(null)); - - result.ShouldBeOfType(typeof (ArgumentException)); - } - } -} diff --git a/src/Nancy.Authentication.Token.Tests/TokenAuthenticationFixture.cs b/src/Nancy.Authentication.Token.Tests/TokenAuthenticationFixture.cs deleted file mode 100644 index 4e39b760f9..0000000000 --- a/src/Nancy.Authentication.Token.Tests/TokenAuthenticationFixture.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace Nancy.Authentication.Token.Tests -{ - using System; - using System.Collections.Generic; - using System.Threading; - - using FakeItEasy; - - using Nancy.Bootstrapper; - using Nancy.Security; - using Nancy.Tests; - using Nancy.Tests.Fakes; - - using Xunit; - - public class TokenAuthenticationFixture - { - private readonly TokenAuthenticationConfiguration config; - private readonly IPipelines hooks; - - public TokenAuthenticationFixture() - { - this.config = new TokenAuthenticationConfiguration(A.Fake()); - this.hooks = new Pipelines(); - TokenAuthentication.Enable(this.hooks, this.config); - } - - [Fact] - public void Should_add_a_pre_hook_in_application_when_enabled() - { - // Given - var pipelines = A.Fake(); - - // When - TokenAuthentication.Enable(pipelines, this.config); - - // Then - A.CallTo(() => pipelines.BeforeRequest.AddItemToStartOfPipeline(A>.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Should_add_both_token_and_requires_auth_pre_hook_in_module_when_enabled() - { - // Given - var module = new FakeModule(); - - // When - TokenAuthentication.Enable(module, this.config); - - // Then - module.Before.PipelineDelegates.ShouldHaveCount(2); - } - - [Fact] - public void Should_throw_with_null_config_passed_to_enable_with_application() - { - // Given, When - var result = Record.Exception(() => TokenAuthentication.Enable(A.Fake(), null)); - - // Then - result.ShouldBeOfType(typeof(ArgumentNullException)); - } - - [Fact] - public void Should_throw_with_null_config_passed_to_enable_with_module() - { - // Given, When - var result = Record.Exception(() => TokenAuthentication.Enable(new FakeModule(), null)); - - // Then - result.ShouldBeOfType(typeof(ArgumentNullException)); - } - - [Fact] - public void Pre_request_hook_should_not_set_auth_details_with_no_auth_headers() - { - // Given - var context = new NancyContext() - { - Request = new FakeRequest("GET", "/") - }; - - // When - var result = this.hooks.BeforeRequest.Invoke(context, new CancellationToken()); - - // Then - result.Result.ShouldBeNull(); - context.CurrentUser.ShouldBeNull(); - } - - [Fact] - public void Pre_request_hook_should_not_set_auth_details_when_invalid_scheme_in_auth_header() - { - // Given - var context = CreateContextWithHeader( - "Authorization", new[] { "FooScheme" + " " + "A-FAKE-TOKEN" }); - - // When - var result = this.hooks.BeforeRequest.Invoke(context, new CancellationToken()); - - // Then - result.Result.ShouldBeNull(); - context.CurrentUser.ShouldBeNull(); - } - - [Fact] - public void Pre_request_hook_should_call_tokenizer_with_token_in_auth_header() - { - // Given - var context = CreateContextWithHeader( - "Authorization", new[] { "Token" + " " + "mytoken" }); - - // When - this.hooks.BeforeRequest.Invoke(context, new CancellationToken()); - - // Then - A.CallTo(() => config.Tokenizer.Detokenize("mytoken", context, A.Ignored)).MustHaveHappened(); - } - - [Fact] - public void Should_set_user_in_context_with_valid_username_in_auth_header() - { - // Given - var fakePipelines = new Pipelines(); - - var context = CreateContextWithHeader( - "Authorization", new[] { "Token" + " " + "mytoken" }); - - var tokenizer = A.Fake(); - var fakeUser = A.Fake(); - A.CallTo(() => tokenizer.Detokenize("mytoken", context, A.Ignored)).Returns(fakeUser); - - var cfg = new TokenAuthenticationConfiguration(tokenizer); - - TokenAuthentication.Enable(fakePipelines, cfg); - - // When - fakePipelines.BeforeRequest.Invoke(context, new CancellationToken()); - - // Then - context.CurrentUser.ShouldBeSameAs(fakeUser); - } - - private static NancyContext CreateContextWithHeader(string name, IEnumerable values) - { - var header = new Dictionary> - { - { name, values } - }; - - return new NancyContext() - { - Request = new FakeRequest("GET", "/", header) - }; - } - - class FakeModule : NancyModule - { - public FakeModule() - { - this.After = new AfterPipeline(); - this.Before = new BeforePipeline(); - this.OnError = new ErrorPipeline(); - } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token.Tests/TokenizerFixture.cs b/src/Nancy.Authentication.Token.Tests/TokenizerFixture.cs deleted file mode 100644 index b708c0b8c0..0000000000 --- a/src/Nancy.Authentication.Token.Tests/TokenizerFixture.cs +++ /dev/null @@ -1,340 +0,0 @@ -namespace Nancy.Authentication.Token.Tests -{ - using System; - using System.Collections.Generic; - using System.Text; - using System.Threading; - - using FakeItEasy; - - using Nancy.Authentication.Token.Storage; - using Nancy.Security; - using Nancy.Tests; - using Nancy.Tests.Fakes; - - using Xunit; - - public class TokenizerFixture - { - private readonly NancyContext context; - private readonly FakeRequest request; - - public TokenizerFixture() - { - context = new NancyContext(); - request = new FakeRequest("GET", "/", - new Dictionary> - { - {"User-Agent", new[] {"a fake user agent"}} - }); - context.Request = request; - } - - [Fact] - public void Should_throw_argument_exception_if_token_expiration_exceeds_key_expiration() - { - var result = Record.Exception(() => - { - CreateTokenizer(cfg => cfg.TokenExpiration(() => TimeSpan.FromDays(8))); - }); - - result.ShouldBeOfType(); - } - - [Fact] - public void Should_throw_argument_exception_if_key_expiration_is_less_than_token_expiration() - { - var result = Record.Exception(() => - { - CreateTokenizer(cfg => cfg.KeyExpiration(() => TimeSpan.FromTicks(1))); - }); - - result.ShouldBeOfType(); - } - - [Fact] - public void Should_be_able_to_create_token_from_user_identity() - { - var tokenizer = CreateTokenizer(); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - token.ShouldNotBeNull(); - } - - [Fact] - public void Should_be_able_to_extract_user_identity_from_token() - { - var tokenizer = CreateTokenizer(); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - var detokenizedIdentity = tokenizer.Detokenize(token, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldNotBeNull(); - - detokenizedIdentity.UserName.ShouldEqual("joe"); - - detokenizedIdentity.Claims.ShouldEqualSequence(new[] { "claim1", "claim2" }); - } - - [Fact] - public void Should_not_be_able_to_extract_user_identity_from_modified_token() - { - var tokenizer = CreateTokenizer(); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - var parts = token.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries); - var bytes = Convert.FromBase64String(parts[0]); - - var tweak = new List(bytes); - tweak.Add(Encoding.UTF8.GetBytes("X")[0]); - - var badToken = Convert.ToBase64String(tweak.ToArray()) + ":" + parts[1]; - - var detokenizedIdentity = tokenizer.Detokenize(badToken, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldBeNull(); - } - - [Fact] - public void Should_be_able_to_extract_user_identity_from_token_with_extra_items() - { - var tokenizer = CreateTokenizer(); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - var detokenizedIdentity = tokenizer.Detokenize(token, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldNotBeNull(); - - detokenizedIdentity.UserName.ShouldEqual("joe"); - - detokenizedIdentity.Claims.ShouldEqualSequence(new[] { "claim1", "claim2" }); - } - - [Fact] - public void Should_fail_to_detokenize_when_additional_items_do_not_match() - { - var tokenizer = CreateTokenizer(); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - var badRequest = new FakeRequest("GET", "/", - new Dictionary> - { - {"User-Agent", new[] {"uh oh! no matchey!"}} - }); - var badContext = new NancyContext - { - Request = badRequest - }; - - var detokenizedIdentity = tokenizer.Detokenize(token, badContext, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldBeNull(); - } - - [Fact] - public void Should_expire_token_when_expiration_has_lapsed() - { - var tokenizer = CreateTokenizer(cfg => cfg.TokenExpiration(() => TimeSpan.FromMilliseconds(10))); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - Thread.Sleep(20); - - var detokenizedIdentity = tokenizer.Detokenize(token, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldBeNull(); - } - - [Fact] - public void Should_not_expire_token_when_key_expiration_has_lapsed_but_token_expiration_has_not() - { - var tokenizer = CreateTokenizer(cfg => - { - cfg.TokenExpiration(() => TimeSpan.FromMilliseconds(50)); - cfg.KeyExpiration(() => TimeSpan.FromMilliseconds(100)); - }); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - tokenizer.Tokenize(identity, context); // prime the pump to generate a key - - Thread.Sleep(75); // key is 75% to its expiration - - var token = tokenizer.Tokenize(identity, context); - - Thread.Sleep(25); // key is now expired but should not be purged until token expiration lapses - - var detokenizedIdentity = tokenizer.Detokenize(token, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldNotBeNull(); - } - - [Fact] - public void Should_generate_new_token_after_previous_key_has_expired() - { - var tokenizer = CreateTokenizer(cfg => - { - cfg.TokenExpiration(() => TimeSpan.FromMilliseconds(50)); - cfg.KeyExpiration(() => TimeSpan.FromMilliseconds(100)); - cfg.TokenStamp(() => new DateTime(2014, 1, 1)); - }); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); // prime the pump to generate a key - - Thread.Sleep(120); // expire the key - - var secondToken = tokenizer.Tokenize(identity, context); - - token.ShouldNotEqual(secondToken); - } - - [Fact] - public void Should_expire_token_when_key_expiration_has_lapsed() - { - var tokenizer = CreateTokenizer(cfg => - { - cfg.TokenExpiration(() => TimeSpan.FromMilliseconds(10)); - cfg.KeyExpiration(() => TimeSpan.FromMilliseconds(20)); - }); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - var token = tokenizer.Tokenize(identity, context); - - Thread.Sleep(30); - - var detokenizedIdentity = tokenizer.Detokenize(token, this.context, new DefaultUserIdentityResolver()); - - detokenizedIdentity.ShouldBeNull(); - } - - [Fact] - public void Should_retrieve_keys_from_store_when_tokenizer_is_created() - { - var keyCache = A.Fake(); - - CreateTokenizer(cfg => cfg.WithKeyCache(keyCache)); - - A.CallTo(() => keyCache.Retrieve()).MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Should_store_keys_when_the_first_token_is_tokenized() - { - var keyCache = A.Fake(); - - var tokenizer = CreateTokenizer(cfg => cfg.WithKeyCache(keyCache)); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - tokenizer.Tokenize(identity, this.context); - - A.CallTo(() => keyCache.Store(A>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); - } - - [Fact] - public void Should_store_keys_when_a_key_is_purged() - { - var keyCache = A.Fake(); - - var tokenizer = CreateTokenizer(cfg => - { - cfg.TokenExpiration(() => TimeSpan.FromMilliseconds(1)); - cfg.KeyExpiration(() => TimeSpan.FromMilliseconds(2)); - cfg.WithKeyCache(keyCache); - }); - - var identity = new FakeUserIdentity - { - UserName = "joe", - Claims = new[] { "claim1", "claim2" } - }; - - tokenizer.Tokenize(identity, context); - - Thread.Sleep(5); - - tokenizer.Tokenize(identity, context); - - A.CallTo(() => keyCache.Store(A>.Ignored)).MustHaveHappened(Repeated.AtLeast.Once); - } - - private Tokenizer CreateTokenizer(Action configuration = null) - { - var tokenizer = new Tokenizer(cfg => - { - cfg.WithKeyCache(new InMemoryTokenKeyStore()); - - if (configuration != null) - { - configuration(cfg); - } - }); - return tokenizer; - } - - public class FakeUserIdentity : IUserIdentity - { - public string UserName { get; set; } - public IEnumerable Claims { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token.Tests/app.config b/src/Nancy.Authentication.Token.Tests/app.config deleted file mode 100644 index b7a7ef1660..0000000000 --- a/src/Nancy.Authentication.Token.Tests/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Nancy.Authentication.Token.Tests/packages.config b/src/Nancy.Authentication.Token.Tests/packages.config deleted file mode 100644 index 6d5474b3ed..0000000000 --- a/src/Nancy.Authentication.Token.Tests/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/DefaultUserIdentityResolver.cs b/src/Nancy.Authentication.Token/DefaultUserIdentityResolver.cs deleted file mode 100644 index 28930d7207..0000000000 --- a/src/Nancy.Authentication.Token/DefaultUserIdentityResolver.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using System.Collections.Generic; - - using Nancy.Security; - - /// - /// The default user identity resolver. - /// This creates a plain user identity based on username and claims. - /// - public class DefaultUserIdentityResolver : IUserIdentityResolver - { - /// - /// Gets the from username and claims. - /// - /// The username. - /// The claims. - /// Current . - /// A populated , or null - public IUserIdentity GetUser(string userName, IEnumerable claims, NancyContext context) - { - return new TokenUserIdentity(userName, claims); - } - - private class TokenUserIdentity : IUserIdentity - { - public TokenUserIdentity(string userName, IEnumerable claims) - { - this.UserName = userName; - this.Claims = claims; - } - - public string UserName { get; private set; } - - public IEnumerable Claims { get; private set; } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/ITokenizer.cs b/src/Nancy.Authentication.Token/ITokenizer.cs deleted file mode 100644 index 2d6655d940..0000000000 --- a/src/Nancy.Authentication.Token/ITokenizer.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using Nancy.Security; - - /// - /// Encodes and decodes authorization tokens. - /// - public interface ITokenizer - { - /// - /// Create a token from a - /// - /// The user identity from which to create a token. - /// Current . - /// The generated token. - string Tokenize(IUserIdentity userIdentity, NancyContext context); - - /// - /// Create a from a token - /// - /// The token from which to create a user identity. - /// Current . - /// The user identity resolver. - /// The detokenized user identity. - IUserIdentity Detokenize(string token, NancyContext context, IUserIdentityResolver userIdentityResolver); - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/IUserIdentityResolver.cs b/src/Nancy.Authentication.Token/IUserIdentityResolver.cs deleted file mode 100644 index 1067922477..0000000000 --- a/src/Nancy.Authentication.Token/IUserIdentityResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using System.Collections.Generic; - - using Nancy.Security; - - /// - /// Provides a mapping between username and an . - /// - public interface IUserIdentityResolver - { - /// - /// Gets the from username and claims. - /// - /// The username. - /// The claims. - /// Current . - /// A populated , or null - IUserIdentity GetUser(string userName, IEnumerable claims, NancyContext context); - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/Nancy.Authentication.Token.csproj b/src/Nancy.Authentication.Token/Nancy.Authentication.Token.csproj deleted file mode 100644 index bf6935e3b8..0000000000 --- a/src/Nancy.Authentication.Token/Nancy.Authentication.Token.csproj +++ /dev/null @@ -1,104 +0,0 @@ - - - - - Debug - AnyCPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C} - Library - Properties - Nancy.Authentication.Token - Nancy.Authentication.Token - v4.5 - 512 - ..\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Nancy.Authentication.Token.XML - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Nancy.Authentication.Token.XML - false - - - true - bin\MonoDebug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - bin\MonoDebug\Nancy.Authentication.Token.XML - false - - - bin\MonoRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - bin\MonoRelease\Nancy.Authentication.Token.XML - false - - - - - - - - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - - - - - - - {34576216-0dca-4b0f-a0dc-9075e75a676f} - Nancy - - - - - - - - - \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/Properties/Internals.cs b/src/Nancy.Authentication.Token/Properties/Internals.cs deleted file mode 100644 index 312935b597..0000000000 --- a/src/Nancy.Authentication.Token/Properties/Internals.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Nancy.Authentication.Token.Tests")] \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/README.md b/src/Nancy.Authentication.Token/README.md deleted file mode 100644 index 09b5a1c16a..0000000000 --- a/src/Nancy.Authentication.Token/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Meet Nancy Token Authentication - -The Nancy.Authentication.Token project was built for use by heterogeneous clients (iOS apps, Android apps, Angular SPA apps, etc.) that all communicate with the same back-end Nancy application. - -## Rationale - -Token authentication and authorization was built with the following requirements: - -* No Cookies (since not all client apps are web browsers) -* Avoid retrieving users and permissions from a backend data store once the user has been authenticated/authorized -* Allow client apps to store a token containing the current user's credentials for resubmission on subsequent requests (after first authenticating) -* Prevent rogue clients from simply generating their own spoofed credentials by incorporating a one-way hashing algorithm that ensures the token has not been tampered with -* Use server side keys for token hash generation with a configurable key expiration interval -* Use file system storage of server-side token generation private keys to allow keys to survive an application restart or an app pool recycle. Note: an "in memory" option is available primarily for testing, but could be used in a situation where expiring all user sessions on an application restart is acceptable behavior. - -Token Authentication can be wired up in a simliar fashion to other available forms of Nancy authentication. - -```csharp -public class Bootstrapper : DefaultNancyBootstrapper -{ - protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) - { - TokenAuthentication.Enable(pipelines, new TokenAuthenticationConfiguration(container.Resolve())); - } -} -``` - -You will need to provide your own form of initial user authentication. This can use your own custom implementation that queries -from a database, from an AD store, from a webservice, or any other form you choose. It could also use another form of Nancy authentication (Basic with an IUserValidator implementation -for example). - -Tokens are generated from an `IUserIdentity` and a `NancyContext` by an implementation of `ITokenizer`. The -default implementation is named `Tokenizer` and provides some configuration options. By default, it generates a token -that includes the following components: - -* User name -* Pipe separated list of user claims -* UTC now in ticks -* The client's "User-Agent" http header value (required) - -It is recommended that you configure the Tokenizer to use an additional piece of information that can uniquely identify -the client device. - -The following code shows an example of how you can perform the initial user authorization and return the generated token to the client. - -```csharp -public class AuthModule : NancyModule -{ - public AuthModule(ITokenizer tokenizer) - : base("/auth") - { - Post["/"] = x => - { - var userName = (string)this.Request.Form.UserName; - var password = (string)this.Request.Form.Password; - - var userIdentity = UserDatabase.ValidateUser(userName, password); - - if (userIdentity == null) - { - return HttpStatusCode.Unauthorized; - } - - var token = tokenizer.Tokenize(userIdentity, Context); - - return new - { - Token = token, - }; - }; - - Get["/validation"] = _ => - { - this.RequiresAuthentication(); - return "Yay! You are authenticated!"; - }; - - Get["/admin"] = _ => - { - this.RequiresAuthentication(); - this.RequiresClaims(new[] { "admin" }); - return "Yay! You are authorized!"; - }; - } -} -``` - -## Contributors - -Nancy.Authentication.Token was originally created by the crack development team at [Lotpath](http://lotpath.com) ([Lotpath on github](http://github.com/Lotpath)). diff --git a/src/Nancy.Authentication.Token/Storage/FileSystemTokenKeyStore.cs b/src/Nancy.Authentication.Token/Storage/FileSystemTokenKeyStore.cs deleted file mode 100644 index ce40ae3ed1..0000000000 --- a/src/Nancy.Authentication.Token/Storage/FileSystemTokenKeyStore.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace Nancy.Authentication.Token.Storage -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Runtime.Serialization.Formatters.Binary; - - /// - /// Stores encryption keys in the file system - /// - public class FileSystemTokenKeyStore : ITokenKeyStore - { - private IRootPathProvider rootPathProvider; - - private BinaryFormatter binaryFormatter; - - private static object syncLock = new object(); - - /// - /// Creates a new - /// - public FileSystemTokenKeyStore() - : this(new DefaultRootPathProvider()) - { - } - - /// - /// Creates a new - /// - /// - public FileSystemTokenKeyStore(IRootPathProvider rootPathProvider) - { - this.rootPathProvider = rootPathProvider; - this.binaryFormatter = new BinaryFormatter(); - } - - /// - /// Retrieves encryption keys. - /// - /// Keys - public IDictionary Retrieve() - { - lock (syncLock) - { - if (!File.Exists(FilePath)) - { - return new Dictionary(); - } - - using (var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None)) - { - return (Dictionary)binaryFormatter.Deserialize(fileStream); - } - } - } - - /// - /// Stores encyrption keys. - /// - /// Keys - public void Store(IDictionary keys) - { - lock (syncLock) - { - if (!Directory.Exists(StorageLocation)) - { - Directory.CreateDirectory(StorageLocation); - } - - var keyChain = new Dictionary(keys); - - using (var fileStream = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) - { - binaryFormatter.Serialize(fileStream, keyChain); - } - } - } - - /// - /// Purges encryption keys - /// - public void Purge() - { - if (File.Exists(FilePath)) - { - File.Delete(FilePath); - } - if (Directory.Exists(StorageLocation)) - { - Directory.Delete(StorageLocation); - } - } - - /// - /// The location where token keys are stored - /// - public string FilePath - { - get - { - return Path.Combine(StorageLocation, "keyChain.bin"); - } - } - - private string StorageLocation - { - get - { - return Path.Combine(rootPathProvider.GetRootPath(), "keyStore"); - } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/Storage/ITokenKeyStore.cs b/src/Nancy.Authentication.Token/Storage/ITokenKeyStore.cs deleted file mode 100644 index 1cb92e4f2c..0000000000 --- a/src/Nancy.Authentication.Token/Storage/ITokenKeyStore.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Nancy.Authentication.Token.Storage -{ - using System; - using System.Collections.Generic; - - /// - /// Stores and retrieves encryption keys - /// - public interface ITokenKeyStore - { - /// - /// Retrieves encryption keys - /// - /// Keys - IDictionary Retrieve(); - - /// - /// Stores encryption keys - /// - /// Keys - void Store(IDictionary keys); - - /// - /// Purges encryption keys - /// - void Purge(); - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/Storage/InMemoryTokenKeyStore.cs b/src/Nancy.Authentication.Token/Storage/InMemoryTokenKeyStore.cs deleted file mode 100644 index 170eaa0226..0000000000 --- a/src/Nancy.Authentication.Token/Storage/InMemoryTokenKeyStore.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Nancy.Authentication.Token.Storage -{ - using System; - using System.Collections.Generic; - - /// - /// In in memory implementation of . Useful for testing or scenarios - /// where encryption keys do not need to persist across application restarts (due to updates, app pool - /// expiration, etc.) - /// - public class InMemoryTokenKeyStore : ITokenKeyStore - { - private IDictionary keys; - - /// - /// Retrieves encryption keys - /// - /// Keys - public IDictionary Retrieve() - { - return new Dictionary(this.keys ?? new Dictionary()); - } - - /// - /// Stores encryption keys - /// - /// Keys - public void Store(IDictionary keys) - { - this.keys = new Dictionary(keys); - } - - /// - /// Purges encryption keys - /// - public void Purge() - { - this.keys = new Dictionary(); - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/TokenAuthentication.cs b/src/Nancy.Authentication.Token/TokenAuthentication.cs deleted file mode 100644 index 040c27e008..0000000000 --- a/src/Nancy.Authentication.Token/TokenAuthentication.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using System; - - using Nancy.Bootstrapper; - using Nancy.Security; - - /// - /// Nancy Token authentication implementation - /// - public static class TokenAuthentication - { - private const string Scheme = "Token"; - - /// - /// Enables Token authentication for the application - /// - /// Pipelines to add handlers to (usually "this") - /// Forms authentication configuration - public static void Enable(IPipelines pipelines, TokenAuthenticationConfiguration configuration) - { - if (pipelines == null) - { - throw new ArgumentNullException("pipelines"); - } - - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } - - pipelines.BeforeRequest.AddItemToStartOfPipeline(GetCredentialRetrievalHook(configuration)); - } - - /// - /// Enables Token authentication for a module - /// - /// Module to add handlers to (usually "this") - /// Forms authentication configuration - public static void Enable(INancyModule module, TokenAuthenticationConfiguration configuration) - { - if (module == null) - { - throw new ArgumentNullException("module"); - } - - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } - - module.RequiresAuthentication(); - module.Before.AddItemToStartOfPipeline(GetCredentialRetrievalHook(configuration)); - } - - /// - /// Gets the pre request hook for loading the authenticated user's details - /// from the auth header. - /// - /// Token authentication configuration to use - /// Pre request hook delegate - private static Func GetCredentialRetrievalHook(TokenAuthenticationConfiguration configuration) - { - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } - - return context => - { - RetrieveCredentials(context, configuration); - return null; - }; - } - - private static void RetrieveCredentials(NancyContext context, TokenAuthenticationConfiguration configuration) - { - var token = ExtractTokenFromHeader(context.Request); - if (token == null) - { - return; - } - - var user = configuration.Tokenizer.Detokenize(token, context, configuration.UserIdentityResolver); - if (user != null) - { - context.CurrentUser = user; - } - } - - private static string ExtractTokenFromHeader(Request request) - { - var authorization = request.Headers.Authorization; - - if (string.IsNullOrEmpty(authorization)) - { - return null; - } - - if (!authorization.StartsWith(Scheme)) - { - return null; - } - - try - { - var encodedToken = authorization.Substring(Scheme.Length).Trim(); - return String.IsNullOrWhiteSpace(encodedToken) ? null : encodedToken; - } - catch (FormatException) - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/TokenAuthenticationConfiguration.cs b/src/Nancy.Authentication.Token/TokenAuthenticationConfiguration.cs deleted file mode 100644 index 8ddc4f107b..0000000000 --- a/src/Nancy.Authentication.Token/TokenAuthenticationConfiguration.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using System; - - /// - /// Configuration options for token authentication - /// - public class TokenAuthenticationConfiguration - { - /// - /// Initializes a new instance of the class. - /// - /// A valid instance of class - /// The user identity resolver. - public TokenAuthenticationConfiguration(ITokenizer tokenizer, IUserIdentityResolver userIdentityResolver = null) - { - if (tokenizer == null) - { - throw new ArgumentNullException("tokenizer"); - } - - this.Tokenizer = tokenizer; - this.UserIdentityResolver = userIdentityResolver ?? new DefaultUserIdentityResolver(); - } - - /// - /// Gets the token validator - /// - public ITokenizer Tokenizer { get; private set; } - - /// - /// Gets or sets the user identity resolver - /// - public IUserIdentityResolver UserIdentityResolver { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Authentication.Token/Tokenizer.cs b/src/Nancy.Authentication.Token/Tokenizer.cs deleted file mode 100644 index 062ab003c2..0000000000 --- a/src/Nancy.Authentication.Token/Tokenizer.cs +++ /dev/null @@ -1,413 +0,0 @@ -namespace Nancy.Authentication.Token -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - - using Nancy.Authentication.Token.Storage; - using Nancy.ErrorHandling; - using Nancy.Security; - - /// - /// Default implementation of - /// - public class Tokenizer : ITokenizer - { - private readonly TokenValidator validator; - private ITokenKeyStore keyStore = new FileSystemTokenKeyStore(); - private Encoding encoding = Encoding.UTF8; - private string claimsDelimiter = "|"; - private string hashDelimiter = ":"; - private string itemDelimiter = Environment.NewLine; - private Func tokenStamp = () => DateTime.UtcNow; - private Func now = () => DateTime.UtcNow; - private Func tokenExpiration = () => TimeSpan.FromDays(1); - private Func keyExpiration = () => TimeSpan.FromDays(7); - - private Func[] additionalItems = - { - ctx => ctx.Request.Headers.UserAgent - }; - - /// - /// Initializes a new instance of the class. - /// - public Tokenizer() - : this(null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration that should be used by the tokenizer. - public Tokenizer(Action configuration) - { - if (configuration != null) - { - var configurator = new TokenizerConfigurator(this); - configuration.Invoke(configurator); - } - var keyRing = new TokenKeyRing(this); - this.validator = new TokenValidator(keyRing); - } - - /// - /// Creates a token from a . - /// - /// The user identity from which to create a token. - /// Current . - /// The generated token. - public string Tokenize(IUserIdentity userIdentity, NancyContext context) - { - var items = new List - { - userIdentity.UserName, - string.Join(this.claimsDelimiter, userIdentity.Claims), - this.tokenStamp().Ticks.ToString(CultureInfo.InvariantCulture) - }; - - if (this.additionalItems != null) - { - foreach (var item in this.additionalItems.Select(additionalItem => additionalItem(context))) - { - if (string.IsNullOrWhiteSpace(item)) - { - throw new RouteExecutionEarlyExitException(new Response { StatusCode = HttpStatusCode.Unauthorized }); - } - items.Add(item); - } - } - - var message = string.Join(this.itemDelimiter, items); - var token = CreateToken(message); - return token; - } - - /// - /// Creates a from a token. - /// - /// The token from which to create a user identity. - /// Current . - /// The user identity resolver. - /// The detokenized user identity. - public IUserIdentity Detokenize(string token, NancyContext context, IUserIdentityResolver userIdentityResolver) - { - var tokenComponents = token.Split(new[] { this.hashDelimiter }, StringSplitOptions.None); - if (tokenComponents.Length != 2) - { - return null; - } - - var messagebytes = Convert.FromBase64String(tokenComponents[0]); - var hash = Convert.FromBase64String(tokenComponents[1]); - - if (!this.validator.IsValid(messagebytes, hash)) - { - return null; - } - - var items = this.encoding.GetString(messagebytes).Split(new[] { this.itemDelimiter }, StringSplitOptions.None); - - if (this.additionalItems != null) - { - var additionalItemCount = additionalItems.Count(); - for (var i = 0; i < additionalItemCount; i++) - { - var tokenizedValue = items[i + 3]; - var currentValue = additionalItems.ElementAt(i)(context); - if (tokenizedValue != currentValue) - { - // todo: may need to log here as this probably indicates hacking - return null; - } - } - } - - var generatedOn = new DateTime(long.Parse(items[2])); - - if (tokenStamp() - generatedOn > tokenExpiration()) - { - return null; - } - - var userName = items[0]; - var claims = items[1].Split(new[] { this.claimsDelimiter }, StringSplitOptions.None); - - return userIdentityResolver.GetUser(userName, claims, context); - } - - private string CreateToken(string message) - { - var messagebytes = this.encoding.GetBytes(message); - var hash = this.validator.CreateHash(messagebytes); - return Convert.ToBase64String(messagebytes) + this.hashDelimiter + Convert.ToBase64String(hash); - } - - /// - /// Provides an API for configuring a instance. - /// - public class TokenizerConfigurator - { - private readonly Tokenizer tokenizer; - - /// - /// Initializes a new instance of the class. - /// - /// - public TokenizerConfigurator(Tokenizer tokenizer) - { - this.tokenizer = tokenizer; - } - - /// - /// Sets the token key store used by the tokenizer - /// - /// - /// A reference to the current - public TokenizerConfigurator WithKeyCache(ITokenKeyStore store) - { - this.tokenizer.keyStore = store; - return this; - } - - /// - /// Sets the encoding used by the tokenizer - /// - /// - /// A reference to the current - public TokenizerConfigurator Encoding(Encoding encoding) - { - this.tokenizer.encoding = encoding; - return this; - } - - /// - /// Sets the delimiter between document and its hash - /// - /// - /// A reference to the current - public TokenizerConfigurator HashDelimiter(string hashDelimiter) - { - this.tokenizer.hashDelimiter = hashDelimiter; - return this; - } - - /// - /// Sets the delimiter between each item to be tokenized - /// - /// - /// A reference to the current - public TokenizerConfigurator ItemDelimiter(string itemDelimiter) - { - this.tokenizer.itemDelimiter = itemDelimiter; - return this; - } - - /// - /// Sets the delimiter between each claim - /// - /// - /// A reference to the current - public TokenizerConfigurator ClaimsDelimiter(string claimsDelimiter) - { - this.tokenizer.claimsDelimiter = claimsDelimiter; - return this; - } - - /// - /// Sets the token expiration interval. An expired token will cause a user to become unauthorized (logged out). - /// Suggested value is 1 day (which is also the default). - /// - /// - /// A reference to the current - public TokenizerConfigurator TokenExpiration(Func expiration) - { - this.tokenizer.tokenExpiration = expiration; - - if (this.tokenizer.tokenExpiration() >= this.tokenizer.keyExpiration()) - { - throw new ArgumentException("Token expiration must be less than key expiration", "expiration"); - } - - return this; - } - - /// - /// Sets the key expiration interval. Must be longer than the value. - /// When keys expire, they are scheduled to purge once any tokens they have been used to generate have expired. - /// Suggested range is 2 to 14 days. The default is 7 days. - /// - /// - /// A reference to the current - public TokenizerConfigurator KeyExpiration(Func expiration) - { - this.tokenizer.keyExpiration = expiration; - - if (this.tokenizer.tokenExpiration() >= this.tokenizer.keyExpiration()) - { - throw new ArgumentException("Key expiration must be greater than token expiration", "expiration"); - } - - return this; - } - - /// - /// Sets the token-generated-at timestamp - /// - /// - /// A reference to the current - public TokenizerConfigurator TokenStamp(Func tokenStamp) - { - this.tokenizer.tokenStamp = tokenStamp; - return this; - } - - /// - /// Sets the current date/time. - /// - /// - /// A reference to the current - public TokenizerConfigurator Now(Func now) - { - this.tokenizer.now = now; - return this; - } - - /// - /// Sets any additional items to be included when tokenizing. The default includes Request.Headers.UserAgent. - /// - /// - /// A reference to the current - public TokenizerConfigurator AdditionalItems(params Func[] additionalItems) - { - this.tokenizer.additionalItems = additionalItems; - return this; - } - } - - private class TokenValidator - { - private readonly TokenKeyRing keyRing; - - internal TokenValidator(TokenKeyRing keyRing) - { - this.keyRing = keyRing; - } - - public bool IsValid(byte[] message, byte[] hash) - { - return this.keyRing - .AllKeys() - .Select(key => GenerateHash(key, message)) - .Any(hash.SequenceEqual); - } - - public byte[] CreateHash(byte[] message) - { - var key = this.keyRing - .NonExpiredKeys() - .First(); - - return GenerateHash(key, message); - } - - private byte[] GenerateHash(byte[] key, byte[] message) - { - using (var hmac = new HMACSHA256(key)) - { - return hmac.ComputeHash(message); - } - } - } - - private class TokenKeyRing - { - private readonly Tokenizer tokenizer; - - private readonly IDictionary keys; - - internal TokenKeyRing(Tokenizer tokenizer) - { - this.tokenizer = tokenizer; - keys = this.tokenizer.keyStore.Retrieve(); - } - - public IEnumerable AllKeys() - { - return this.Keys(true); - } - - public IEnumerable NonExpiredKeys() - { - return this.Keys(false); - } - - private IEnumerable Keys(bool includeExpired) - { - var entriesToPurge = new List(); - var validKeys = new List(); - - foreach (var entry in this.keys.OrderByDescending(x => x.Key)) - { - if (IsReadyToPurge(entry)) - { - entriesToPurge.Add(entry.Key); - } - else if (!IsExpired(entry) || includeExpired) - { - validKeys.Add(entry.Value); - } - } - - var shouldStore = false; - - foreach (var entry in entriesToPurge) - { - this.keys.Remove(entry); - shouldStore = true; - } - - if (validKeys.Count == 0) - { - var key = CreateKey(); - this.keys[this.tokenizer.now()] = key; - validKeys.Add(key); - shouldStore = true; - } - - if (shouldStore) - { - this.tokenizer.keyStore.Store(keys); - } - - return validKeys; - } - - private bool IsReadyToPurge(KeyValuePair entry) - { - return this.tokenizer.now() - entry.Key > (this.tokenizer.keyExpiration() + this.tokenizer.tokenExpiration()); - } - - private bool IsExpired(KeyValuePair entry) - { - return this.tokenizer.now() - entry.Key > this.tokenizer.keyExpiration(); - } - - private byte[] CreateKey() - { - var secretKey = new byte[64]; - - using (var rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(secretKey); - } - - return secretKey; - } - } - } -} diff --git a/src/Nancy.Authentication.Token/nancy.authentication.token.nuspec b/src/Nancy.Authentication.Token/nancy.authentication.token.nuspec deleted file mode 100644 index 8a1fb8c3d4..0000000000 --- a/src/Nancy.Authentication.Token/nancy.authentication.token.nuspec +++ /dev/null @@ -1,26 +0,0 @@ - - - - Nancy.Authentication.Token - 0.0.0 - Andreas Håkansson, Steven Robbins and contributors - false - A token based authentication provider for Nancy. - Nancy is a lightweight web framework for the .Net platform, inspired by Sinatra. Nancy aim at delivering a low ceremony approach to building light, fast web applications. - en-US - Andreas Håkansson, Steven Robbins and contributors - http://nancyfx.org/nancy-nuget.png - https://github.com/NancyFx/Nancy/blob/master/license.txt - http://nancyfx.org - - - - Nancy Token Authentication - - - - - - - - \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Basic/DemoUserIdentity.cs b/src/Nancy.Demo.Authentication.Basic/DemoUserIdentity.cs deleted file mode 100644 index 8c952c7161..0000000000 --- a/src/Nancy.Demo.Authentication.Basic/DemoUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Demo.Authentication.Basic -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class DemoUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Basic/Nancy.Demo.Authentication.Basic.csproj b/src/Nancy.Demo.Authentication.Basic/Nancy.Demo.Authentication.Basic.csproj index 8fe9928377..96920c136c 100644 --- a/src/Nancy.Demo.Authentication.Basic/Nancy.Demo.Authentication.Basic.csproj +++ b/src/Nancy.Demo.Authentication.Basic/Nancy.Demo.Authentication.Basic.csproj @@ -114,7 +114,6 @@ - diff --git a/src/Nancy.Demo.Authentication.Basic/SecureModule.cs b/src/Nancy.Demo.Authentication.Basic/SecureModule.cs index 86bee7f6c5..44cd404c45 100644 --- a/src/Nancy.Demo.Authentication.Basic/SecureModule.cs +++ b/src/Nancy.Demo.Authentication.Basic/SecureModule.cs @@ -8,10 +8,7 @@ public SecureModule() : base("/secure") { this.RequiresAuthentication(); - Get["/"] = x => - { - return "Hello " + this.Context.CurrentUser.UserName; - }; + Get["/"] = x => "Hello " + this.Context.CurrentUser.Identity.Name; } } } diff --git a/src/Nancy.Demo.Authentication.Basic/UserValidator.cs b/src/Nancy.Demo.Authentication.Basic/UserValidator.cs index 4a3e85b7c6..78d41c4d4d 100644 --- a/src/Nancy.Demo.Authentication.Basic/UserValidator.cs +++ b/src/Nancy.Demo.Authentication.Basic/UserValidator.cs @@ -1,15 +1,17 @@ namespace Nancy.Demo.Authentication.Basic { + using System.Security.Claims; + using System.Security.Principal; + using Nancy.Authentication.Basic; - using Nancy.Security; public class UserValidator : IUserValidator { - public IUserIdentity Validate(string username, string password) + public ClaimsPrincipal Validate(string username, string password) { if (username == "demo" && password == "demo") { - return new DemoUserIdentity { UserName = username }; + return new ClaimsPrincipal(new GenericIdentity(username)); } // Not recognised => anonymous. diff --git a/src/Nancy.Demo.Authentication.Forms/DemoUserIdentity.cs b/src/Nancy.Demo.Authentication.Forms/DemoUserIdentity.cs deleted file mode 100644 index 6dbac58ae3..0000000000 --- a/src/Nancy.Demo.Authentication.Forms/DemoUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Demo.Authentication.Forms -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class DemoUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Forms/Nancy.Demo.Authentication.Forms.csproj b/src/Nancy.Demo.Authentication.Forms/Nancy.Demo.Authentication.Forms.csproj index 8732479b79..139091e247 100644 --- a/src/Nancy.Demo.Authentication.Forms/Nancy.Demo.Authentication.Forms.csproj +++ b/src/Nancy.Demo.Authentication.Forms/Nancy.Demo.Authentication.Forms.csproj @@ -126,7 +126,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Nancy.Demo.Authentication.Forms/PartlySecureModule.cs b/src/Nancy.Demo.Authentication.Forms/PartlySecureModule.cs index 799422611e..8818fb8bef 100644 --- a/src/Nancy.Demo.Authentication.Forms/PartlySecureModule.cs +++ b/src/Nancy.Demo.Authentication.Forms/PartlySecureModule.cs @@ -13,7 +13,7 @@ public PartlySecureModule() Get["/secured"] = x => { this.RequiresAuthentication(); - var model = new UserModel(this.Context.CurrentUser.UserName); + var model = new UserModel(this.Context.CurrentUser.Identity.Name); return View["secure.cshtml", model]; }; } diff --git a/src/Nancy.Demo.Authentication.Forms/SecureModule.cs b/src/Nancy.Demo.Authentication.Forms/SecureModule.cs index 057095646b..a334288ad7 100644 --- a/src/Nancy.Demo.Authentication.Forms/SecureModule.cs +++ b/src/Nancy.Demo.Authentication.Forms/SecureModule.cs @@ -10,7 +10,7 @@ public SecureModule() : base("/secure") this.RequiresAuthentication(); Get["/"] = x => { - var model = new UserModel(this.Context.CurrentUser.UserName); + var model = new UserModel(this.Context.CurrentUser.Identity.Name); return View["secure.cshtml", model]; }; } diff --git a/src/Nancy.Demo.Authentication.Forms/UserDatabase.cs b/src/Nancy.Demo.Authentication.Forms/UserDatabase.cs index fedd7247f1..42b0cc8d17 100644 --- a/src/Nancy.Demo.Authentication.Forms/UserDatabase.cs +++ b/src/Nancy.Demo.Authentication.Forms/UserDatabase.cs @@ -3,9 +3,10 @@ namespace Nancy.Demo.Authentication.Forms using System; using System.Collections.Generic; using System.Linq; + using System.Security.Claims; + using System.Security.Principal; using Nancy.Authentication.Forms; - using Nancy.Security; public class UserDatabase : IUserMapper { @@ -17,18 +18,18 @@ static UserDatabase() users.Add(new Tuple("user", "password", new Guid("56E1E49E-B7E8-4EEA-8459-7A906AC4D4C0"))); } - public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) + public ClaimsPrincipal GetUserFromIdentifier(Guid identifier, NancyContext context) { - var userRecord = users.Where(u => u.Item3 == identifier).FirstOrDefault(); + var userRecord = users.FirstOrDefault(u => u.Item3 == identifier); return userRecord == null ? null - : new DemoUserIdentity {UserName = userRecord.Item1}; + : new ClaimsPrincipal(new GenericIdentity(userRecord.Item1)); } public static Guid? ValidateUser(string username, string password) { - var userRecord = users.Where(u => u.Item1 == username && u.Item2 == password).FirstOrDefault(); + var userRecord = users.FirstOrDefault(u => u.Item1 == username && u.Item2 == password); if (userRecord == null) { diff --git a/src/Nancy.Demo.Authentication.Stateless/DemoUserIdentity.cs b/src/Nancy.Demo.Authentication.Stateless/DemoUserIdentity.cs deleted file mode 100644 index 703a41cb88..0000000000 --- a/src/Nancy.Demo.Authentication.Stateless/DemoUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Demo.Authentication.Stateless -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class DemoUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Stateless/Nancy.Demo.Authentication.Stateless.csproj b/src/Nancy.Demo.Authentication.Stateless/Nancy.Demo.Authentication.Stateless.csproj index d3e00cf0de..6d8e853e3a 100644 --- a/src/Nancy.Demo.Authentication.Stateless/Nancy.Demo.Authentication.Stateless.csproj +++ b/src/Nancy.Demo.Authentication.Stateless/Nancy.Demo.Authentication.Stateless.csproj @@ -106,7 +106,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Nancy.Demo.Authentication.Stateless/RootModule.cs b/src/Nancy.Demo.Authentication.Stateless/RootModule.cs index d46d4a1b0f..7661b28c3d 100644 --- a/src/Nancy.Demo.Authentication.Stateless/RootModule.cs +++ b/src/Nancy.Demo.Authentication.Stateless/RootModule.cs @@ -4,8 +4,12 @@ public class RootModule : NancyModule { public RootModule() { - Get["/"] = _ => this.Response.AsText("This is a REST API. It is in another VS project to demonstrate how a common REST API might behave when accessing it from another website or application. To see how a website can access this API, run the Nancy.Demo.Authentication.Stateless.Website project (in the same Nancy solution)."); + Get["/"] = _ => this.Response.AsText("This is a REST API. It is in another VS project to " + + "demonstrate how a common REST API might behave when " + + "accessing it from another website or application. To " + + "see how a website can access this API, run the " + + "Nancy.Demo.Authentication.Stateless.Website project " + + "(in the same Nancy solution)."); } - } } \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Stateless/SecureModule.cs b/src/Nancy.Demo.Authentication.Stateless/SecureModule.cs index 2ce501c2a6..3a1ab5e499 100644 --- a/src/Nancy.Demo.Authentication.Stateless/SecureModule.cs +++ b/src/Nancy.Demo.Authentication.Stateless/SecureModule.cs @@ -14,10 +14,10 @@ public SecureModule() Get["secure"] = x => { //Context.CurrentUser was set by StatelessAuthentication earlier in the pipeline - var identity = (DemoUserIdentity)this.Context.CurrentUser; + var identity = this.Context.CurrentUser; //return the secure information in a json response - var userModel = new UserModel(identity.UserName); + var userModel = new UserModel(identity.Identity.Name); return this.Response.AsJson(new { SecureContent = "here's some secure content that you can only see if you provide a correct apiKey", diff --git a/src/Nancy.Demo.Authentication.Stateless/UserDatabase.cs b/src/Nancy.Demo.Authentication.Stateless/UserDatabase.cs index ec3831a237..2798bb70e3 100644 --- a/src/Nancy.Demo.Authentication.Stateless/UserDatabase.cs +++ b/src/Nancy.Demo.Authentication.Stateless/UserDatabase.cs @@ -3,8 +3,8 @@ namespace Nancy.Demo.Authentication.Stateless using System; using System.Collections.Generic; using System.Linq; - - using Nancy.Security; + using System.Security.Claims; + using System.Security.Principal; public class UserDatabase { @@ -17,7 +17,7 @@ static UserDatabase() Users.Add(new Tuple("user", "password")); } - public static IUserIdentity GetUserFromApiKey(string apiKey) + public static ClaimsPrincipal GetUserFromApiKey(string apiKey) { var activeKey = ActiveApiKeys.FirstOrDefault(x => x.Item2 == apiKey); @@ -27,7 +27,7 @@ public static IUserIdentity GetUserFromApiKey(string apiKey) } var userRecord = Users.First(u => u.Item1 == activeKey.Item1); - return new DemoUserIdentity {UserName = userRecord.Item1}; + return new ClaimsPrincipal(new GenericIdentity(userRecord.Item1, "stateless")); } public static string ValidateUser(string username, string password) diff --git a/src/Nancy.Demo.Authentication.Token.TestingDemo/LoginFixture.cs b/src/Nancy.Demo.Authentication.Token.TestingDemo/LoginFixture.cs deleted file mode 100644 index f83af7f801..0000000000 --- a/src/Nancy.Demo.Authentication.Token.TestingDemo/LoginFixture.cs +++ /dev/null @@ -1,112 +0,0 @@ -namespace Nancy.Demo.Authentication.Token.TestingDemo -{ - using Nancy.Testing; - using Nancy.Tests; - - using Xunit; - - public class LoginFixture - { - private readonly Browser browser; - - public LoginFixture() - { - var bootstrapper = new TestBootstrapper(); - this.browser = new Browser(bootstrapper); - } - - [Fact] - public void Should_return_generated_token_for_valid_user_credentials() - { - // Given, When - var response = this.browser.Post("/auth/", (with) => - { - with.HttpRequest(); - with.Accept("application/json"); - with.Header("User-Agent", "Nancy Browser"); - with.FormValue("UserName", "demo"); - with.FormValue("Password", "demo"); - }); - - // Then - response.Body.DeserializeJson().ShouldNotBeNull(); - } - - [Fact] - public void Should_return_unauthorized_for_invalid_user_credentials() - { - // Given, When - var response = this.browser.Post("/auth/", (with) => - { - with.HttpRequest(); - with.Accept("application/json"); - with.Header("User-Agent", "Nancy Browser"); - with.FormValue("UserName", "bad"); - with.FormValue("Password", "boy"); - }); - - // Then - response.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_when_not_authenticated() - { - // Given, When - var response = this.browser.Get("/auth/validation/", (with) => - { - with.HttpRequest(); - }); - - // Then - response.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_forbidden_when_not_authorized() - { - // Given, When - var response = this.browser.Post("/auth/", (with) => - { - with.HttpRequest(); - with.Accept("application/json"); - with.Header("User-Agent", "Nancy Browser"); - with.FormValue("UserName", "nonadmin"); - with.FormValue("Password", "nonadmin"); - }); - - var token = response.Body.DeserializeJson().Token; - - var secondResponse = response.Then.Get("/auth/admin/", with => - { - with.HttpRequest(); - with.Header("User-Agent", "Nancy Browser"); - with.Header("Authorization", "Token " + token); - }); - - // Then - secondResponse.StatusCode.ShouldEqual(HttpStatusCode.Forbidden); - } - - [Fact] - public void Should_return_unauthorized_without_user_agent() - { - // Given, When - var response = this.browser.Post("/auth/", (with) => - { - with.HttpRequest(); - with.Accept("application/json"); - with.FormValue("UserName", "demo"); - with.FormValue("Password", "demo"); - }); - - // Then - response.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - public class AuthResponse - { - public string Token { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token.TestingDemo/Nancy.Demo.Authentication.Token.TestingDemo.csproj b/src/Nancy.Demo.Authentication.Token.TestingDemo/Nancy.Demo.Authentication.Token.TestingDemo.csproj deleted file mode 100644 index 67fe8b01e0..0000000000 --- a/src/Nancy.Demo.Authentication.Token.TestingDemo/Nancy.Demo.Authentication.Token.TestingDemo.csproj +++ /dev/null @@ -1,115 +0,0 @@ - - - - - Debug - AnyCPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9} - Library - Properties - Nancy.Demo.Authentication.Token.TestingDemo - Nancy.Demo.Authentication.Token.TestingDemo - v4.5 - 512 - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\ - TRACE - prompt - 4 - false - - - true - bin\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - - - - - - - - - False - ..\packages\xunit.1.9.1\lib\net20\xunit.dll - - - False - ..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll - - - - - ShouldExtensions.cs - - - Properties\SharedAssemblyInfo.cs - - - - - - - {97FA024A-F6ED-4086-BCC1-1A51BE63474C} - Nancy.Authentication.Token - - - {35460aa4-b94a-4b64-9418-7243ec3d2f01} - Nancy.Demo.Authentication.Token - - - {d79203c0-b672-4751-9c95-c3ab7d3fefbe} - Nancy.Testing - - - {2c6f51df-015c-4db6-b44c-0e5e4f25e2a9} - Nancy.ViewEngines.Razor - - - {34576216-0dca-4b0f-a0dc-9075e75a676f} - Nancy - - - - - - - - \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token.TestingDemo/TestBootstrapper.cs b/src/Nancy.Demo.Authentication.Token.TestingDemo/TestBootstrapper.cs deleted file mode 100644 index ace6cb1cc6..0000000000 --- a/src/Nancy.Demo.Authentication.Token.TestingDemo/TestBootstrapper.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Nancy.Demo.Authentication.Token.TestingDemo -{ - using System; - using System.IO; - - using Nancy.Authentication.Token; - using Nancy.Authentication.Token.Storage; - using Nancy.Testing; - using Nancy.Testing.Fakes; - using Nancy.TinyIoc; - - public class TestBootstrapper : TokenAuthBootstrapper - { - protected override void ConfigureApplicationContainer(TinyIoCContainer container) - { - container.Register(new Tokenizer(cfg => cfg.WithKeyCache(new InMemoryTokenKeyStore()))); - } - - protected override IRootPathProvider RootPathProvider - { - get - { - var assemblyFilePath = - new Uri(typeof(TokenAuthBootstrapper).Assembly.CodeBase).LocalPath; - - var assemblyPath = - Path.GetDirectoryName(assemblyFilePath); - - var rootPath = - PathHelper.GetParent(assemblyPath, 2); - - rootPath = - Path.Combine(rootPath, @"Nancy.Demo.Authentication.Token"); - - FakeRootPathProvider.RootPath = rootPath; - - return new FakeRootPathProvider(); - } - } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token.TestingDemo/packages.config b/src/Nancy.Demo.Authentication.Token.TestingDemo/packages.config deleted file mode 100644 index beaa94de20..0000000000 --- a/src/Nancy.Demo.Authentication.Token.TestingDemo/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/AuthModule.cs b/src/Nancy.Demo.Authentication.Token/AuthModule.cs deleted file mode 100644 index 9ab2aeb47a..0000000000 --- a/src/Nancy.Demo.Authentication.Token/AuthModule.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Nancy.Demo.Authentication.Token -{ - using Nancy.Authentication.Token; - using Nancy.Security; - - public class AuthModule : NancyModule - { - public AuthModule(ITokenizer tokenizer) - : base("/auth") - { - Post["/"] = x => - { - var userName = (string)this.Request.Form.UserName; - var password = (string)this.Request.Form.Password; - - var userIdentity = UserDatabase.ValidateUser(userName, password); - - if (userIdentity == null) - { - return HttpStatusCode.Unauthorized; - } - - var token = tokenizer.Tokenize(userIdentity, Context); - - return new - { - Token = token, - }; - }; - - Get["/validation"] = _ => - { - this.RequiresAuthentication(); - return "Yay! You are authenticated!"; - }; - - Get["/admin"] = _ => - { - this.RequiresAuthentication(); - this.RequiresClaims(new[] { "admin" }); - return "Yay! You are authorized!"; - }; - } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/DemoUserIdentity.cs b/src/Nancy.Demo.Authentication.Token/DemoUserIdentity.cs deleted file mode 100644 index f8be8595b4..0000000000 --- a/src/Nancy.Demo.Authentication.Token/DemoUserIdentity.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Nancy.Demo.Authentication.Token -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class DemoUserIdentity : IUserIdentity - { - public string UserName { get; set; } - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/Nancy.Demo.Authentication.Token.csproj b/src/Nancy.Demo.Authentication.Token/Nancy.Demo.Authentication.Token.csproj deleted file mode 100644 index 6657ca240c..0000000000 --- a/src/Nancy.Demo.Authentication.Token/Nancy.Demo.Authentication.Token.csproj +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Debug - AnyCPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01} - Exe - Properties - Nancy.Demo.Authentication.Token - Nancy.Demo.Authentication.Token - v4.5 - 512 - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - true - bin\MonoDebug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - bin\MonoRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - - - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - - - - - {97fa024a-f6ed-4086-bcc1-1a51be63474c} - Nancy.Authentication.Token - - - {aa7f66eb-ec2c-47de-855f-30b3e6ef2134} - Nancy.Hosting.Self - - - {34576216-0dca-4b0f-a0dc-9075e75a676f} - Nancy - - - - - \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/Program.cs b/src/Nancy.Demo.Authentication.Token/Program.cs deleted file mode 100644 index 21893aa7cc..0000000000 --- a/src/Nancy.Demo.Authentication.Token/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Nancy.Demo.Authentication.Token -{ - using System; - - using Nancy.Hosting.Self; - - class Program - { - static void Main(string[] args) - { - var uri = - new Uri("http://localhost:3579"); - - using (var host = new NancyHost(uri)) - { - host.Start(); - - Console.WriteLine("Your application is running on " + uri); - Console.WriteLine("Press any [Enter] to close the host."); - Console.ReadLine(); - } - } - } -} diff --git a/src/Nancy.Demo.Authentication.Token/TokenAuthBootstrapper.cs b/src/Nancy.Demo.Authentication.Token/TokenAuthBootstrapper.cs deleted file mode 100644 index 1bf0be96d3..0000000000 --- a/src/Nancy.Demo.Authentication.Token/TokenAuthBootstrapper.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Nancy.Demo.Authentication.Token -{ - using Nancy.Authentication.Token; - using Nancy.Bootstrapper; - using Nancy.TinyIoc; - - public class TokenAuthBootstrapper : DefaultNancyBootstrapper - { - protected override void ConfigureApplicationContainer(TinyIoCContainer container) - { - container.Register(new Tokenizer()); - // Example options for specifying additional values for token generation - - //container.Register(new Tokenizer(cfg => - // cfg.AdditionalItems( - // ctx => - // ctx.Request.Headers["X-Custom-Header"].FirstOrDefault(), - // ctx => ctx.Request.Query.extraValue))); - } - - protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) - { - TokenAuthentication.Enable(pipelines, new TokenAuthenticationConfiguration(container.Resolve())); - } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/UserDatabase.cs b/src/Nancy.Demo.Authentication.Token/UserDatabase.cs deleted file mode 100644 index 59e7008819..0000000000 --- a/src/Nancy.Demo.Authentication.Token/UserDatabase.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Nancy.Demo.Authentication.Token -{ - using System; - using System.Collections.Generic; - using System.Linq; - - using Nancy.Security; - - public class UserDatabase - { - private static readonly List> Users = new List>(); - private static readonly Dictionary> Claims = new Dictionary>(); - - static UserDatabase() - { - Users.Add(new Tuple("demo", "demo")); - Claims.Add("demo", new List { "demo", "admin" }); - - Users.Add(new Tuple("nonadmin", "nonadmin")); - Claims.Add("nonadmin", new List { "demo", }); - } - - public static IUserIdentity ValidateUser(string userName, string password) - { - var user = Users.FirstOrDefault(x => x.Item1 == userName && x.Item2 == password); - if (user == null) - { - return null; - } - - var claims = Claims[user.Item1]; - return new DemoUserIdentity {UserName = user.Item1, Claims = claims}; - } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication.Token/app.config b/src/Nancy.Demo.Authentication.Token/app.config deleted file mode 100644 index b7a7ef1660..0000000000 --- a/src/Nancy.Demo.Authentication.Token/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Nancy.Demo.Authentication/AnotherVerySecureModule.cs b/src/Nancy.Demo.Authentication/AnotherVerySecureModule.cs index 08d8530af1..f8dc41b730 100644 --- a/src/Nancy.Demo.Authentication/AnotherVerySecureModule.cs +++ b/src/Nancy.Demo.Authentication/AnotherVerySecureModule.cs @@ -1,5 +1,7 @@ namespace Nancy.Demo.Authentication { + using System.Security.Claims; + using Nancy.Demo.Authentication.Models; using Nancy.Security; @@ -10,11 +12,11 @@ public class AnotherVerySecureModule : NancyModule { public AnotherVerySecureModule() : base("/superSecure") { - this.RequiresClaims(new[] { "SuperSecure" }); + this.RequiresClaims(c => c.Type == ClaimTypes.Role && c.Value == "SuperSecure"); Get["/"] = x => { - var model = new UserModel(this.Context.CurrentUser.UserName); + var model = new UserModel(this.Context.CurrentUser.Identity.Name); return View["superSecure.cshtml", model]; }; } diff --git a/src/Nancy.Demo.Authentication/AuthenticationBootstrapper.cs b/src/Nancy.Demo.Authentication/AuthenticationBootstrapper.cs index 8502e51a08..e504218e2d 100644 --- a/src/Nancy.Demo.Authentication/AuthenticationBootstrapper.cs +++ b/src/Nancy.Demo.Authentication/AuthenticationBootstrapper.cs @@ -2,6 +2,7 @@ namespace Nancy.Demo.Authentication { using System; using System.Collections.Generic; + using System.Security.Claims; using Nancy.Bootstrapper; using Nancy.Responses; @@ -14,7 +15,7 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline base.ApplicationStartup(container, pipelines); // In reality you would use a pre-built authentication/claims provider - pipelines.BeforeRequest += (ctx) => + pipelines.BeforeRequest += ctx => { // World's-worse-authentication (TM) // Pull the username out of the querystring if it exists @@ -23,17 +24,13 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline if (username.HasValue) { - ctx.CurrentUser = new DemoUserIdentity - { - UserName = username.ToString(), - Claims = BuildClaims(username.ToString()) - }; + ctx.CurrentUser = new ClaimsPrincipal(new ClaimsIdentity(BuildClaims(username), "querystring")); } return null; }; - pipelines.AfterRequest += (ctx) => + pipelines.AfterRequest += ctx => { // If status code comes back as Unauthorized then // forward the user to the login page @@ -49,14 +46,14 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline /// /// Current username /// IEnumerable of claims - private static IEnumerable BuildClaims(string userName) + private static IEnumerable BuildClaims(string userName) { - var claims = new List(); + var claims = new List(); // Only bob can have access to SuperSecure if (String.Equals(userName, "bob", StringComparison.InvariantCultureIgnoreCase)) { - claims.Add("SuperSecure"); + claims.Add(new Claim(ClaimTypes.Role, "SuperSecure")); } return claims; diff --git a/src/Nancy.Demo.Authentication/DemoUserIdentity.cs b/src/Nancy.Demo.Authentication/DemoUserIdentity.cs deleted file mode 100644 index f98f01798a..0000000000 --- a/src/Nancy.Demo.Authentication/DemoUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Demo.Authentication -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class DemoUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Demo.Authentication/Nancy.Demo.Authentication.csproj b/src/Nancy.Demo.Authentication/Nancy.Demo.Authentication.csproj index b368c4fe69..21d2994a15 100644 --- a/src/Nancy.Demo.Authentication/Nancy.Demo.Authentication.csproj +++ b/src/Nancy.Demo.Authentication/Nancy.Demo.Authentication.csproj @@ -127,11 +127,9 @@ - - diff --git a/src/Nancy.Demo.Authentication/SecureModule.cs b/src/Nancy.Demo.Authentication/SecureModule.cs index e2f371ee58..6091c047fc 100644 --- a/src/Nancy.Demo.Authentication/SecureModule.cs +++ b/src/Nancy.Demo.Authentication/SecureModule.cs @@ -10,7 +10,7 @@ public SecureModule() : base("/secure") this.RequiresAuthentication(); Get["/"] = x => { - var model = new UserModel(this.Context.CurrentUser.UserName); + var model = new UserModel(this.Context.CurrentUser.Identity.Name); return View["secure.cshtml", model]; }; } diff --git a/src/Nancy.Demo.Authentication/VerySecureModule.cs b/src/Nancy.Demo.Authentication/VerySecureModule.cs deleted file mode 100644 index 793695318f..0000000000 --- a/src/Nancy.Demo.Authentication/VerySecureModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Nancy.Demo.Authentication -{ - using System.Linq; - - using Nancy.Demo.Authentication.Models; - using Nancy.Security; - - /// - /// A module that only people with SuperSecure clearance are allowed to access - /// - public class VerySecureModule : NancyModule - { - public VerySecureModule() : base("/superSecureToo") - { - this.RequiresValidatedClaims(c => c.Contains("SuperSecure")); - - Get["/"] = x => { - var model = new UserModel(this.Context.CurrentUser.UserName); - return View["superSecure.cshtml", model]; - }; - } - } -} \ No newline at end of file diff --git a/src/Nancy.Owin.Tests/Nancy.Owin.Tests.csproj b/src/Nancy.Owin.Tests/Nancy.Owin.Tests.csproj index 460be86061..671518e246 100644 --- a/src/Nancy.Owin.Tests/Nancy.Owin.Tests.csproj +++ b/src/Nancy.Owin.Tests/Nancy.Owin.Tests.csproj @@ -98,9 +98,6 @@ Properties\SharedAssemblyInfo.cs - - - diff --git a/src/Nancy.Owin.Tests/NancyOptionsFixture.cs b/src/Nancy.Owin.Tests/NancyOptionsFixture.cs deleted file mode 100644 index 63b81e88de..0000000000 --- a/src/Nancy.Owin.Tests/NancyOptionsFixture.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Nancy.Owin.Tests -{ - using Nancy.Tests; - - using Xunit; - - public class NancyOptionsFixture - { - private readonly NancyOptions nancyOptions; - - public NancyOptionsFixture() - { - this.nancyOptions = new NancyOptions(); - } - - [Fact] - public void Bootstrapper_should_not_be_null() - { - this.nancyOptions.Bootstrapper.ShouldNotBeNull(); - } - - [Fact] - public void PerformPassThrough_should_not_be_null() - { - this.nancyOptions.PerformPassThrough.ShouldNotBeNull(); - } - - [Fact] - public void PerformPassThrough_delegate_should_return_false() - { - this.nancyOptions.PerformPassThrough(new NancyContext()).ShouldBeFalse(); - } - } -} \ No newline at end of file diff --git a/src/Nancy.Tests.Functional/Modules/PerRouteAuthModule.cs b/src/Nancy.Tests.Functional/Modules/PerRouteAuthModule.cs index 386343b0b0..f0b53e7a70 100644 --- a/src/Nancy.Tests.Functional/Modules/PerRouteAuthModule.cs +++ b/src/Nancy.Tests.Functional/Modules/PerRouteAuthModule.cs @@ -1,7 +1,5 @@ namespace Nancy.Tests.Functional.Modules { - using System.Linq; - using Nancy.Security; public class PerRouteAuthModule : NancyModule @@ -19,21 +17,14 @@ public PerRouteAuthModule() Get["/requiresclaims"] = _ => { - this.RequiresClaims(new[] { "test", "test2" }); + this.RequiresClaims(c => c.Type == "test", c => c.Type == "test2"); return 200; }; Get["/requiresanyclaims"] = _ => { - this.RequiresAnyClaim(new[] { "test", "test2" }); - - return 200; - }; - - Get["/requiresvalidatedclaims"] = _ => - { - this.RequiresValidatedClaims(c => c.Contains("test")); + this.RequiresAnyClaim(c => c.Type == "test", c => c.Type == "test2"); return 200; }; diff --git a/src/Nancy.Tests.Functional/Tests/PerRouteAuthFixture.cs b/src/Nancy.Tests.Functional/Tests/PerRouteAuthFixture.cs index 4dbfa1932d..c3c554f017 100644 --- a/src/Nancy.Tests.Functional/Tests/PerRouteAuthFixture.cs +++ b/src/Nancy.Tests.Functional/Tests/PerRouteAuthFixture.cs @@ -1,13 +1,12 @@ namespace Nancy.Tests.Functional.Tests { - using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; - using Nancy.Security; using Nancy.Testing; using Nancy.Tests.Functional.Modules; using Xunit; - using Xunit.Extensions; public class PerRouteAuthFixture { @@ -36,7 +35,7 @@ public void Should_deny_if_claims_wrong() { var browser = new Browser(with => { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test2")); + with.RequestStartup((t, p, c) => c.CurrentUser = CreateFakeUser("test2")); with.Module(); }); @@ -50,7 +49,7 @@ public void Should_allow_if_claims_correct() { var browser = new Browser(with => { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test", "test2")); + with.RequestStartup((t, p, c) => c.CurrentUser = CreateFakeUser("test", "test2")); with.Module(); }); @@ -59,42 +58,12 @@ public void Should_allow_if_claims_correct() Assert.Equal(HttpStatusCode.OK, result.StatusCode); } - [Theory] - [PropertyData("Claims")] - public void Should_allow_if_claims_correct_case_insensitively(params string[] claims) - { - var browser = new Browser(with => - { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser(claims)); - with.Module(); - }); - - var result = browser.Get("/requiresclaims"); - - Assert.Equal(HttpStatusCode.OK, result.StatusCode); - } - - public static IEnumerable Claims - { - get - { - yield return new object[] { new[] { "TEST", "TEST2" } }; - yield return new object[] { new[] { "TEST", "test2" } }; - yield return new object[] { new[] { "test", "TEST2" } }; - yield return new object[] { new[] { "test", "test2" } }; - yield return new object[] { new[] { "Test", "Test2" } }; - yield return new object[] { new[] { "TesT", "TesT2" } }; - yield return new object[] { new[] { "TEsT", "TEsT2" } }; - yield return new object[] { new[] { "TeSt", "TeSt2" } }; - } - } - [Fact] public void Should_deny_if_anyclaims_not_found() { var browser = new Browser(with => { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test3")); + with.RequestStartup((t, p, c) => c.CurrentUser = CreateFakeUser("test3")); with.Module(); }); @@ -108,7 +77,7 @@ public void Should_allow_if_anyclaim_found() { var browser = new Browser(with => { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test2")); + with.RequestStartup((t, p, c) => c.CurrentUser = CreateFakeUser("test2")); with.Module(); }); @@ -117,45 +86,12 @@ public void Should_allow_if_anyclaim_found() Assert.Equal(HttpStatusCode.OK, result.StatusCode); } - [Fact] - public void Should_deny_if_validated_claims_fails() - { - var browser = new Browser(with => - { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test2")); - with.Module(); - }); - - var result = browser.Get("/requiresvalidatedclaims"); - - Assert.Equal(HttpStatusCode.Forbidden, result.StatusCode); - } - - [Fact] - public void Should_allow_if_validated_claims_passes() - { - var browser = new Browser(with => - { - with.RequestStartup((t, p, c) => c.CurrentUser = new FakeUser("test")); - with.Module(); - }); - - var result = browser.Get("/requiresvalidatedclaims"); - - Assert.Equal(HttpStatusCode.OK, result.StatusCode); - } - } - - public class FakeUser : IUserIdentity - { - public string UserName { get; private set; } - - public IEnumerable Claims { get; private set; } - - public FakeUser(params string[] claims) + private static ClaimsPrincipal CreateFakeUser(params string[] claimTypes) { - this.UserName = "Bob"; - this.Claims = claims; + var claims = claimTypes.Select(claimType => new Claim(claimType, string.Empty)).ToList(); + claims.Add(new Claim(ClaimTypes.NameIdentifier, "user")); + + return new ClaimsPrincipal(new ClaimsIdentity(claims, "test")); } } } \ No newline at end of file diff --git a/src/Nancy.Tests/Fakes/FakeHookedModule.cs b/src/Nancy.Tests/Fakes/FakeHookedModule.cs index a17420006b..fccf7943a9 100644 --- a/src/Nancy.Tests/Fakes/FakeHookedModule.cs +++ b/src/Nancy.Tests/Fakes/FakeHookedModule.cs @@ -8,5 +8,4 @@ public FakeHookedModule(BeforePipeline before = null, AfterPipeline after = null this.After = after; } } - } \ No newline at end of file diff --git a/src/Nancy.Tests/Fakes/FakeUserIdentity.cs b/src/Nancy.Tests/Fakes/FakeUserIdentity.cs deleted file mode 100644 index 6e66448a81..0000000000 --- a/src/Nancy.Tests/Fakes/FakeUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Nancy.Tests.Fakes -{ - using System.Collections.Generic; - - using Nancy.Security; - - public class FakeUserIdentity : IUserIdentity - { - public string UserName { get; set; } - - public IEnumerable Claims { get; set; } - } -} \ No newline at end of file diff --git a/src/Nancy.Tests/Nancy.Tests.csproj b/src/Nancy.Tests/Nancy.Tests.csproj index d75283ed16..6544f92cf5 100644 --- a/src/Nancy.Tests/Nancy.Tests.csproj +++ b/src/Nancy.Tests/Nancy.Tests.csproj @@ -128,7 +128,6 @@ - @@ -201,10 +200,13 @@ + + + @@ -239,7 +241,7 @@ - + diff --git a/src/Nancy.Owin.Tests/NancyMiddlewareFixture.cs b/src/Nancy.Tests/Unit/NancyMiddlewareFixture.cs similarity index 82% rename from src/Nancy.Owin.Tests/NancyMiddlewareFixture.cs rename to src/Nancy.Tests/Unit/NancyMiddlewareFixture.cs index 473bc5079c..777ca8f64d 100644 --- a/src/Nancy.Owin.Tests/NancyMiddlewareFixture.cs +++ b/src/Nancy.Tests/Unit/NancyMiddlewareFixture.cs @@ -1,17 +1,20 @@ -namespace Nancy.Owin.Tests +namespace Nancy.Tests.Unit { using System; using System.Collections.Generic; using System.IO; using System.Linq; + using System.Security.Claims; + using System.Security.Principal; using System.Text; using System.Threading; + using System.Threading.Tasks; using FakeItEasy; using Nancy.Bootstrapper; using Nancy.Helpers; - using Nancy.Tests; + using Nancy.Owin; using Xunit; @@ -187,6 +190,7 @@ public void Should_append_setcookie_headers() var fakeResponse = new Response { StatusCode = HttpStatusCode.OK }; fakeResponse.WithCookie("test", "testvalue"); var fakeContext = new NancyContext { Response = fakeResponse }; + this.SetupFakeNancyCompleteCallback(fakeContext); //When @@ -198,6 +202,42 @@ public void Should_append_setcookie_headers() (respHeaders["Set-Cookie"][1] == "test=testvalue; path=/").ShouldBeTrue(); } + [Fact] + public async Task Should_flow_katana_user() + { + // Given + IPrincipal user = new ClaimsPrincipal(new GenericIdentity("testuser")); + this.environment.Add("server.User", user); + + var fakeResponse = new Response { StatusCode = HttpStatusCode.OK, Contents = s => { } }; + var fakeContext = new NancyContext { Response = fakeResponse }; + this.SetupFakeNancyCompleteCallback(fakeContext); + + // When + await this.host.Invoke(this.environment); + + // Then + fakeContext.CurrentUser.ShouldEqual(user); + } + + [Fact] + public async Task Should_flow_owin_user() + { + // Given + var user = new ClaimsPrincipal(new GenericIdentity("testuser")); + this.environment.Add("owin.RequestUser", user); + + var fakeResponse = new Response { StatusCode = HttpStatusCode.OK, Contents = s => { } }; + var fakeContext = new NancyContext { Response = fakeResponse }; + this.SetupFakeNancyCompleteCallback(fakeContext); + + // When + await this.host.Invoke(this.environment); + + // Then + fakeContext.CurrentUser.ShouldEqual(user); + } + /// /// Sets the fake nancy engine to execute the complete callback with the given context /// @@ -208,6 +248,7 @@ private void SetupFakeNancyCompleteCallback(NancyContext context) A.Ignored, A>.Ignored, A.Ignored)) + .Invokes((Request _, Func preRequest, CancellationToken __) => preRequest(context)) .Returns(TaskHelpers.GetCompletedTask(context)); } diff --git a/src/Nancy.Owin.Tests/NancyOptionsExtensionsFixture.cs b/src/Nancy.Tests/Unit/NancyOptionsExtensionsFixture.cs similarity index 89% rename from src/Nancy.Owin.Tests/NancyOptionsExtensionsFixture.cs rename to src/Nancy.Tests/Unit/NancyOptionsExtensionsFixture.cs index 36d4481b94..22c24a3e9f 100644 --- a/src/Nancy.Owin.Tests/NancyOptionsExtensionsFixture.cs +++ b/src/Nancy.Tests/Unit/NancyOptionsExtensionsFixture.cs @@ -1,6 +1,6 @@ -namespace Nancy.Owin.Tests +namespace Nancy.Tests.Unit { - using Nancy.Tests; + using Nancy.Owin; using Xunit; diff --git a/src/Nancy.Tests/Unit/NancyOptionsFixture.cs b/src/Nancy.Tests/Unit/NancyOptionsFixture.cs new file mode 100644 index 0000000000..6aa2dcd20e --- /dev/null +++ b/src/Nancy.Tests/Unit/NancyOptionsFixture.cs @@ -0,0 +1,58 @@ +namespace Nancy.Tests.Unit +{ + using Nancy.Bootstrapper; + using Nancy.Owin; + + using Xunit; + + public class NancyOptionsFixture + { + private readonly NancyOptions nancyOptions; + + public NancyOptionsFixture() + { + this.nancyOptions = new NancyOptions(); + } + + [Fact] + public void Bootstrapper_should_use_locator_if_not_specified() + { + // Given + var bootstrapper = new DefaultNancyBootstrapper(); + NancyBootstrapperLocator.Bootstrapper = bootstrapper; + + //When + //Then + this.nancyOptions.Bootstrapper.ShouldNotBeNull(); + this.nancyOptions.Bootstrapper.ShouldBeSameAs(bootstrapper); + } + + [Fact] + public void Bootstrapper_should_use_chosen_bootstrapper_if_specified() + { + // Given + var bootstrapper = new DefaultNancyBootstrapper(); + var specificBootstrapper = new DefaultNancyBootstrapper(); + NancyBootstrapperLocator.Bootstrapper = bootstrapper; + + //When + this.nancyOptions.Bootstrapper = specificBootstrapper; + + //Then + this.nancyOptions.Bootstrapper.ShouldNotBeNull(); + this.nancyOptions.Bootstrapper.ShouldBeSameAs(specificBootstrapper); + } + + [Fact] + public void PerformPassThrough_should_not_be_null() + { + this.nancyOptions.PerformPassThrough.ShouldNotBeNull(); + } + + [Fact] + public void PerformPassThrough_delegate_should_return_false() + { + this.nancyOptions.PerformPassThrough(new NancyContext()).ShouldBeFalse(); + } + } +} \ No newline at end of file diff --git a/src/Nancy.Tests/Unit/Security/UserIdentityExtensionsFixture.cs b/src/Nancy.Tests/Unit/Security/ClaimsPrincipalExtensionsFixture.cs similarity index 51% rename from src/Nancy.Tests/Unit/Security/UserIdentityExtensionsFixture.cs rename to src/Nancy.Tests/Unit/Security/ClaimsPrincipalExtensionsFixture.cs index e4d486b6a4..d9de20d7be 100644 --- a/src/Nancy.Tests/Unit/Security/UserIdentityExtensionsFixture.cs +++ b/src/Nancy.Tests/Unit/Security/ClaimsPrincipalExtensionsFixture.cs @@ -2,19 +2,20 @@ namespace Nancy.Tests.Unit.Security { using System; using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; using Nancy.Security; - using Nancy.Tests.Fakes; using Xunit; - public class UserIdentityExtensionsFixture + public class ClaimsPrincipalExtensionsFixture { [Fact] public void Should_return_false_for_authentication_if_the_user_is_null() { // Given - IUserIdentity user = null; + ClaimsPrincipal user = null; // When var result = user.IsAuthenticated(); @@ -24,10 +25,10 @@ public void Should_return_false_for_authentication_if_the_user_is_null() } [Fact] - public void Should_return_false_for_authentication_if_the_username_is_null() + public void Should_return_false_for_authentication_if_the_identity_is_anonymous() { // Given - IUserIdentity user = GetFakeUser(null); + ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity()); // When var result = user.IsAuthenticated(); @@ -36,68 +37,15 @@ public void Should_return_false_for_authentication_if_the_username_is_null() result.ShouldBeFalse(); } - [Fact] - public void Should_return_false_for_authentication_if_the_username_is_empty() - { - // Given - IUserIdentity user = GetFakeUser(""); - - // When - var result = user.IsAuthenticated(); - - // Then - result.ShouldBeFalse(); - } - - [Fact] - public void Should_return_false_for_authentication_if_the_username_is_whitespace() - { - // Given - IUserIdentity user = GetFakeUser(" \r\n "); - - // When - var result = user.IsAuthenticated(); - - // Then - result.ShouldBeFalse(); - } - - [Fact] - public void Should_return_true_for_authentication_if_username_is_set() - { - // Given - IUserIdentity user = GetFakeUser("Fake"); - - // When - var result = user.IsAuthenticated(); - - // Then - result.ShouldBeTrue(); - } - - [Fact] - public void Should_return_false_for_required_claim_if_the_user_is_null() - { - // Given - IUserIdentity user = null; - var requiredClaim = "not-present-claim"; - - // When - var result = user.HasClaim(requiredClaim); - - // Then - result.ShouldBeFalse(); - } - [Fact] public void Should_return_false_for_required_claim_if_the_claims_are_null() { // Given - IUserIdentity user = GetFakeUser("Fake"); + ClaimsPrincipal user = GetFakeUser("Fake"); var requiredClaim = "not-present-claim"; // When - var result = user.HasClaim(requiredClaim); + var result = user.HasClaim(c => c.Type == requiredClaim); // Then result.ShouldBeFalse(); @@ -107,11 +55,11 @@ public void Should_return_false_for_required_claim_if_the_claims_are_null() public void Should_return_false_for_required_claim_if_the_user_does_not_have_claim() { // Given - IUserIdentity user = GetFakeUser("Fake", new [] { "present-claim" }) ; + ClaimsPrincipal user = GetFakeUser("Fake", new Claim("present-claim", string.Empty)); var requiredClaim = "not-present-claim"; // When - var result = user.HasClaim(requiredClaim); + var result = user.HasClaim(c => c.Type == requiredClaim); // Then result.ShouldBeFalse(); @@ -121,11 +69,11 @@ public void Should_return_false_for_required_claim_if_the_user_does_not_have_cla public void Should_return_true_for_required_claim_if_the_user_does_have_claim() { // Given - IUserIdentity user = GetFakeUser("Fake", new [] { "present-claim" }) ; + ClaimsPrincipal user = GetFakeUser("Fake", new Claim("present-claim", string.Empty)); var requiredClaim = "present-claim"; // When - var result = user.HasClaim(requiredClaim); + var result = user.HasClaim(c => c.Type == requiredClaim); // Then result.ShouldBeTrue(); @@ -135,11 +83,11 @@ public void Should_return_true_for_required_claim_if_the_user_does_have_claim() public void Should_return_false_for_required_claims_if_the_user_is_null() { // Given - IUserIdentity user = null; + ClaimsPrincipal user = null; var requiredClaims = new[] { "not-present-claim1", "not-present-claim2" }; // When - var result = user.HasClaims(requiredClaims); + var result = user.HasClaims(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeFalse(); @@ -149,11 +97,11 @@ public void Should_return_false_for_required_claims_if_the_user_is_null() public void Should_return_false_for_required_claims_if_the_claims_are_null() { // Given - IUserIdentity user = GetFakeUser("Fake"); + ClaimsPrincipal user = GetFakeUser("Fake"); var requiredClaims = new[] { "not-present-claim1", "not-present-claim2" }; // When - var result = user.HasClaims(requiredClaims); + var result = user.HasClaims(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeFalse(); @@ -163,8 +111,15 @@ public void Should_return_false_for_required_claims_if_the_claims_are_null() public void Should_return_false_for_required_claims_if_the_user_does_not_have_all_claims() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); - var requiredClaims = new[] { "present-claim1", "not-present-claim1" }; + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); + var requiredClaims = new Predicate[] + { + c => c.Type == "present-claim1", + c => c.Type == "not-present-claim1" + }; // When var result = user.HasClaims(requiredClaims); @@ -177,8 +132,15 @@ public void Should_return_false_for_required_claims_if_the_user_does_not_have_al public void Should_return_true_for_required_claims_if_the_user_does_have_all_claims() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); - var requiredClaims = new[] { "present-claim1", "present-claim2" }; + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); + var requiredClaims = new Predicate[] + { + c => c.Type == "present-claim1", + c => c.Type == "present-claim2" + }; // When var result = user.HasClaims(requiredClaims); @@ -191,11 +153,11 @@ public void Should_return_true_for_required_claims_if_the_user_does_have_all_cla public void Should_return_false_for_any_required_claim_if_the_user_is_null() { // Given - IUserIdentity user = null; + ClaimsPrincipal user = null; var requiredClaims = new[] { "not-present-claim1", "not-present-claim2" }; // When - var result = user.HasAnyClaim(requiredClaims); + var result = user.HasAnyClaim(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeFalse(); @@ -205,11 +167,11 @@ public void Should_return_false_for_any_required_claim_if_the_user_is_null() public void Should_return_false_for_any_required_claim_if_the_claims_are_null() { // Given - IUserIdentity user = GetFakeUser("Fake"); + ClaimsPrincipal user = GetFakeUser("Fake"); var requiredClaims = new[] { "not-present-claim1", "not-present-claim2" }; // When - var result = user.HasAnyClaim(requiredClaims); + var result = user.HasAnyClaim(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeFalse(); @@ -219,11 +181,14 @@ public void Should_return_false_for_any_required_claim_if_the_claims_are_null() public void Should_return_false_for_any_required_claim_if_the_user_does_not_have_any_claim() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); var requiredClaims = new[] { "not-present-claim1", "not-present-claim2" }; // When - var result = user.HasAnyClaim(requiredClaims); + var result = user.HasAnyClaim(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeFalse(); @@ -233,11 +198,14 @@ public void Should_return_false_for_any_required_claim_if_the_user_does_not_have public void Should_return_true_for_any_required_claim_if_the_user_does_have_any_of_claim() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); var requiredClaims = new[] { "present-claim1", "not-present-claim1" }; // When - var result = user.HasAnyClaim(requiredClaims); + var result = user.HasAnyClaim(c => requiredClaims.Contains(c.Type)); // Then result.ShouldBeTrue(); @@ -247,22 +215,8 @@ public void Should_return_true_for_any_required_claim_if_the_user_does_have_any_ public void Should_return_false_for_valid_claim_if_the_user_is_null() { // Given - IUserIdentity user = null; - Func, bool> isValid = claims => true; - - // When - var result = user.HasValidClaims(isValid); - - // Then - result.ShouldBeFalse(); - } - - [Fact] - public void Should_return_false_for_valid_claim_if_claims_are_null() - { - // Given - IUserIdentity user = GetFakeUser("Fake"); - Func, bool> isValid = claims => true; + ClaimsPrincipal user = null; + Func, bool> isValid = claims => true; // When var result = user.HasValidClaims(isValid); @@ -275,8 +229,11 @@ public void Should_return_false_for_valid_claim_if_claims_are_null() public void Should_return_false_for_valid_claim_if_the_validation_fails() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); - Func, bool> isValid = claims => false; + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); + Func, bool> isValid = claims => false; // When var result = user.HasValidClaims(isValid); @@ -289,8 +246,11 @@ public void Should_return_false_for_valid_claim_if_the_validation_fails() public void Should_return_true_for_valid_claim_if_the_validation_succeeds() { // Given - IUserIdentity user = GetFakeUser("Fake", new[] { "present-claim1", "present-claim2", "present-claim3" }); - Func, bool> isValid = claims => true; + ClaimsPrincipal user = GetFakeUser("Fake", + new Claim("present-claim1", string.Empty), + new Claim("present-claim2", string.Empty), + new Claim("present-claim3", string.Empty)); + Func, bool> isValid = claims => true; // When var result = user.HasValidClaims(isValid); @@ -303,11 +263,10 @@ public void Should_return_true_for_valid_claim_if_the_validation_succeeds() public void Should_call_validation_with_users_claims() { // Given - IEnumerable userClaims = new string[0]; - IUserIdentity user = GetFakeUser("Fake", userClaims); + ClaimsPrincipal user = GetFakeUser("Fake"); - IEnumerable validatedClaims = null; - Func, bool> isValid = claims => + IEnumerable validatedClaims = null; + Func, bool> isValid = claims => { // store passed claims for testing assertion validatedClaims = claims; @@ -318,16 +277,15 @@ public void Should_call_validation_with_users_claims() user.HasValidClaims(isValid); // Then - validatedClaims.ShouldBeSameAs(userClaims); + validatedClaims.ShouldEqualSequence(user.Claims); } - private static IUserIdentity GetFakeUser(string userName, IEnumerable claims = null) + private static ClaimsPrincipal GetFakeUser(string userName, params Claim[] claims) { - var ret = new FakeUserIdentity(); - ret.UserName = userName; - ret.Claims = claims; - - return ret; + var claimsList = (claims ?? Enumerable.Empty()).ToList(); + claimsList.Add(new Claim(ClaimTypes.NameIdentifier, userName)); + + return new ClaimsPrincipal(new ClaimsIdentity(claimsList)); } } } \ No newline at end of file diff --git a/src/Nancy.Tests/Unit/Security/ModuleSecurityFixture.cs b/src/Nancy.Tests/Unit/Security/ModuleSecurityFixture.cs index adb41252b4..ba78ee1baf 100644 --- a/src/Nancy.Tests/Unit/Security/ModuleSecurityFixture.cs +++ b/src/Nancy.Tests/Unit/Security/ModuleSecurityFixture.cs @@ -2,7 +2,8 @@ namespace Nancy.Tests.Unit.Security { using System; - using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; using System.Threading; using FakeItEasy; @@ -30,23 +31,13 @@ public void Should_add_two_items_to_the_end_of_the_begin_pipeline_when_RequiresC { var module = new FakeHookedModule(A.Fake()); - module.RequiresClaims(new[] { string.Empty }); + module.RequiresClaims(_ => true); A.CallTo(() => module.Before.AddItemToEndOfPipeline(A>.Ignored)).MustHaveHappened(Repeated.Exactly.Twice); } [Fact] - public void Should_add_two_items_to_the_end_of_the_begin_pipeline_when_RequiresValidatedClaims_enabled() - { - var module = new FakeHookedModule(A.Fake()); - - module.RequiresValidatedClaims(c => false); - - A.CallTo(() => module.Before.AddItemToEndOfPipeline(A>.Ignored)).MustHaveHappened(Repeated.Exactly.Twice); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresAuthentication_enabled_and_no_username() + public void Should_return_unauthorized_response_with_RequiresAuthentication_enabled_and_no_user() { var module = new FakeHookedModule(new BeforePipeline()); module.RequiresAuthentication(); @@ -58,15 +49,15 @@ public void Should_return_unauthorized_response_with_RequiresAuthentication_enab } [Fact] - public void Should_return_unauthorized_response_with_RequiresAuthentication_enabled_and_blank_username() + public void Should_return_unauthorized_response_with_RequiresAuthentication_enabled_and_no_identity() { var module = new FakeHookedModule(new BeforePipeline()); module.RequiresAuthentication(); var context = new NancyContext - { - CurrentUser = GetFakeUser(String.Empty) - }; + { + CurrentUser = new ClaimsPrincipal() + }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -75,114 +66,33 @@ public void Should_return_unauthorized_response_with_RequiresAuthentication_enab } [Fact] - public void Should_return_null_with_RequiresAuthentication_enabled_and_username_provided() + public void Should_return_null_with_RequiresAuthentication_enabled_and_user_provided() { var module = new FakeHookedModule(new BeforePipeline()); module.RequiresAuthentication(); - var context = new NancyContext - { - CurrentUser = GetFakeUser("Bob") - }; - - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldBeNull(); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresClaims_enabled_and_no_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { string.Empty }); - - var result = module.Before.Invoke(new NancyContext(), new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresClaims_enabled_and_blank_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { string.Empty }); - var context = new NancyContext - { - CurrentUser = GetFakeUser(String.Empty) - }; - - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresAnyClaim_enabled_and_no_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresAnyClaim(new[] { string.Empty }); - - var result = module.Before.Invoke(new NancyContext(), new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresAnyClaim_enabled_and_blank_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresAnyClaim(new[] { string.Empty }); var context = new NancyContext { - CurrentUser = GetFakeUser(String.Empty) + CurrentUser = GetFakeUser("Bob") }; var result = module.Before.Invoke(context, new CancellationToken()); - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresValidatedClaims_enabled_and_no_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresValidatedClaims(c => false); - - var result = module.Before.Invoke(new NancyContext(), new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); - } - - [Fact] - public void Should_return_unauthorized_response_with_RequiresValidatedClaims_enabled_and_blank_username() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresValidatedClaims(c => false); - var context = new NancyContext - { - CurrentUser = GetFakeUser(String.Empty) - }; - - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Unauthorized); + result.Result.ShouldBeNull(); } [Fact] public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_nonmatching_claims() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { "Claim1" }); + module.RequiresClaims(c => c.Type == "Claim1"); var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new string[] {"Claim2", "Claim3"}) - }; + { + CurrentUser = GetFakeUser( + "username", + new Claim("Claim2", string.Empty), + new Claim("Claim3", string.Empty)) + }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -194,11 +104,11 @@ public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_non public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_claims_key_missing() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { "Claim1" }); + module.RequiresClaims(c => c.Type == "Claim1"); var context = new NancyContext - { - CurrentUser = GetFakeUser("username") - }; + { + CurrentUser = GetFakeUser("username") + }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -210,11 +120,13 @@ public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_cla public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_not_all_claims_met() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { "Claim1", "Claim2" }); + module.RequiresClaims(c => c.Type == "Claim1", c => c.Type == "Claim2"); var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new[] {"Claim2"}) - }; + { + CurrentUser = GetFakeUser( + "username", + new Claim("Claim2", string.Empty)) + }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -226,11 +138,14 @@ public void Should_return_forbidden_response_with_RequiresClaims_enabled_but_not public void Should_return_null_with_RequiresClaims_and_all_claims_met() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresClaims(new[] { "Claim1", "Claim2" }); + module.RequiresClaims(c => c.Type == "Claim1", c => c.Type == "Claim2"); var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new[] {"Claim1", "Claim2", "Claim3"}) - }; + { + CurrentUser = GetFakeUser("username", + new Claim("Claim1", string.Empty), + new Claim("Claim2", string.Empty), + new Claim("Claim3", string.Empty)) + }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -242,10 +157,13 @@ public void Should_return_null_with_RequiresClaims_and_all_claims_met() public void Should_return_forbidden_response_with_RequiresAnyClaim_enabled_but_nonmatching_claims() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresAnyClaim(new[] { "Claim1" }); + module.RequiresAnyClaim(c => c.Type == "Claim1"); var context = new NancyContext { - CurrentUser = GetFakeUser("username", new string[] { "Claim2", "Claim3" }) + CurrentUser = GetFakeUser( + "username", + new Claim("Claim2", string.Empty), + new Claim("Claim3", string.Empty)) }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -258,7 +176,7 @@ public void Should_return_forbidden_response_with_RequiresAnyClaim_enabled_but_n public void Should_return_forbidden_response_with_RequiresAnyClaim_enabled_but_claims_key_missing() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresAnyClaim(new[] { "Claim1" }); + module.RequiresAnyClaim(c => c.Type == "Claim1"); var context = new NancyContext { CurrentUser = GetFakeUser("username") @@ -274,10 +192,13 @@ public void Should_return_forbidden_response_with_RequiresAnyClaim_enabled_but_c public void Should_return_null_with_RequiresAnyClaim_and_any_claim_met() { var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresAnyClaim(new[] { "Claim1", "Claim4" }); + module.RequiresAnyClaim(c => c.Type == "Claim1", c => c.Type == "Claim4"); var context = new NancyContext { - CurrentUser = GetFakeUser("username", new[] { "Claim1", "Claim2", "Claim3" }) + CurrentUser = GetFakeUser("username", + new Claim("Claim1", string.Empty), + new Claim("Claim2", string.Empty), + new Claim("Claim3", string.Empty)) }; var result = module.Before.Invoke(context, new CancellationToken()); @@ -285,75 +206,6 @@ public void Should_return_null_with_RequiresAnyClaim_and_any_claim_met() result.Result.ShouldBeNull(); } - [Fact] - public void Should_return_forbidden_response_with_RequiresValidatedClaims_enabled_but_claims_missing() - { - var module = new FakeHookedModule(new BeforePipeline()); - module.RequiresValidatedClaims(s => true); - var context = new NancyContext - { - CurrentUser = GetFakeUser("username") - }; - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Forbidden); - } - - [Fact] - public void Should_call_IsValid_delegate_with_RequiresValidatedClaims_and_valid_username() - { - bool called = false; - var module = new FakeHookedModule(new BeforePipeline()); - var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new[] {"Claim1", "Claim2", "Claim3"}) - }; - - module.RequiresValidatedClaims(s => - { - called = true; - return true; - }); - - module.Before.Invoke(context, new CancellationToken()); - - called.ShouldEqual(true); - } - - [Fact] - public void Should_return_null_with_RequiresValidatedClaims_and_IsValid_returns_true() - { - var module = new FakeHookedModule(new BeforePipeline()); - var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new[] {"Claim1", "Claim2", "Claim3"}) - }; - - module.RequiresValidatedClaims(s => true); - - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldBeNull(); - } - - [Fact] - public void Should_return_forbidden_response_with_RequiresValidatedClaims_and_IsValid_returns_false() - { - var module = new FakeHookedModule(new BeforePipeline()); - var context = new NancyContext - { - CurrentUser = GetFakeUser("username", new[] {"Claim1", "Claim2", "Claim3"}) - }; - - module.RequiresValidatedClaims(s => false); - - var result = module.Before.Invoke(context, new CancellationToken()); - - result.Result.ShouldNotBeNull(); - result.Result.StatusCode.ShouldEqual(HttpStatusCode.Forbidden); - } - [Fact] public void Should_return_redirect_response_when_request_url_is_non_secure_method_is_get_and_requires_https() { @@ -361,9 +213,9 @@ public void Should_return_redirect_response_when_request_url_is_non_secure_metho var module = new FakeHookedModule(new BeforePipeline()); var url = GetFakeUrl(false); var context = new NancyContext - { - Request = new Request("GET", url) - }; + { + Request = new Request("GET", url) + }; module.RequiresHttps(); @@ -411,9 +263,9 @@ public void Should_return_forbidden_response_when_request_url_is_non_secure_meth var module = new FakeHookedModule(new BeforePipeline()); var url = GetFakeUrl(false); var context = new NancyContext - { - Request = new Request("POST", url) - }; + { + Request = new Request("POST", url) + }; module.RequiresHttps(); @@ -432,9 +284,9 @@ public void Should_return_forbidden_response_when_request_url_is_non_secure_meth var module = new FakeHookedModule(new BeforePipeline()); var url = GetFakeUrl(false); var context = new NancyContext - { - Request = new Request("DELETE", url) - }; + { + Request = new Request("DELETE", url) + }; module.RequiresHttps(); @@ -528,26 +380,25 @@ public void Should_return_null_response_when_request_url_is_secure_method_is_pos result.Result.ShouldBeNull(); } - private static IUserIdentity GetFakeUser(string userName, IEnumerable claims = null) + private static ClaimsPrincipal GetFakeUser(string userName, params Claim[] claims) { - var ret = new FakeUserIdentity(); - ret.UserName = userName; - ret.Claims = claims; - - return ret; + var claimsList = claims.ToList(); + claimsList.Add(new Claim(ClaimTypes.NameIdentifier, userName)); + + return new ClaimsPrincipal(new ClaimsIdentity(claimsList, "test")); } private static Url GetFakeUrl(bool https) { return new Url - { - BasePath = null, - HostName = "localhost", - Path = "/", - Port = 80, - Query = string.Empty, - Scheme = https ? "https" : "http" - }; + { + BasePath = null, + HostName = "localhost", + Path = "/", + Port = 80, + Query = string.Empty, + Scheme = https ? "https" : "http" + }; } } } \ No newline at end of file diff --git a/src/Nancy.ViewEngines.Razor/HtmlHelpers.cs b/src/Nancy.ViewEngines.Razor/HtmlHelpers.cs index 73e574ee52..8a357f5586 100644 --- a/src/Nancy.ViewEngines.Razor/HtmlHelpers.cs +++ b/src/Nancy.ViewEngines.Razor/HtmlHelpers.cs @@ -2,8 +2,7 @@ { using System; using System.IO; - - using Nancy.Security; + using System.Security.Claims; /// /// Helpers to generate html content. @@ -107,7 +106,7 @@ public string CurrentLocale /// /// Returns current authenticated user name /// - public IUserIdentity CurrentUser + public ClaimsPrincipal CurrentUser { get { return this.RenderContext.Context.CurrentUser; } } diff --git a/src/Nancy.sln b/src/Nancy.sln index 57ade39450..abd08f977a 100644 --- a/src/Nancy.sln +++ b/src/Nancy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E944109B-0B7A-4ADE-8602-004CEFA5897D}" EndProject @@ -123,14 +123,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Owin.Tests", "Nancy.O EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.ConstraintRouting", "Nancy.Demo.ConstraintRouting\Nancy.Demo.ConstraintRouting.csproj", "{972C2D45-49B6-4109-9A3A-0C8BC9225B95}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Authentication.Token.Tests", "Nancy.Authentication.Token.Tests\Nancy.Authentication.Token.Tests.csproj", "{3C131D45-AF1D-4659-8B26-A9F55EED0D20}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Authentication.Token", "Nancy.Authentication.Token\Nancy.Authentication.Token.csproj", "{97FA024A-F6ED-4086-BCC1-1A51BE63474C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Authentication.Token", "Nancy.Demo.Authentication.Token\Nancy.Demo.Authentication.Token.csproj", "{35460AA4-B94A-4B64-9418-7243EC3D2F01}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Demo.Authentication.Token.TestingDemo", "Nancy.Demo.Authentication.Token.TestingDemo\Nancy.Demo.Authentication.Token.TestingDemo.csproj", "{9121DA01-7BFD-49F0-9937-0D3E7875ADB9}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Metadata.Modules", "Nancy.Metadata.Modules\Nancy.Metadata.Modules.csproj", "{DC8AAA8B-7FD0-47C4-9413-3CC94088BCC4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.Metadata.Modules.Tests", "Nancy.Metadata.Modules.Tests\Nancy.Metadata.Modules.Tests.csproj", "{508A3A68-2012-4E62-925F-2F3083A013FC}" @@ -815,54 +807,6 @@ Global {972C2D45-49B6-4109-9A3A-0C8BC9225B95}.Release|Any CPU.ActiveCfg = Release|Any CPU {972C2D45-49B6-4109-9A3A-0C8BC9225B95}.Release|Any CPU.Build.0 = Release|Any CPU {972C2D45-49B6-4109-9A3A-0C8BC9225B95}.Release|x86.ActiveCfg = Release|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Debug|x86.ActiveCfg = Debug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoDebug|x86.ActiveCfg = Debug|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.MonoRelease|x86.ActiveCfg = Release|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Release|Any CPU.Build.0 = Release|Any CPU - {3C131D45-AF1D-4659-8B26-A9F55EED0D20}.Release|x86.ActiveCfg = Release|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Debug|x86.ActiveCfg = Debug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoDebug|x86.ActiveCfg = Debug|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.MonoRelease|x86.ActiveCfg = Release|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Release|Any CPU.Build.0 = Release|Any CPU - {97FA024A-F6ED-4086-BCC1-1A51BE63474C}.Release|x86.ActiveCfg = Release|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Debug|x86.ActiveCfg = Debug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoDebug|x86.ActiveCfg = Debug|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.MonoRelease|x86.ActiveCfg = Release|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Release|Any CPU.Build.0 = Release|Any CPU - {35460AA4-B94A-4B64-9418-7243EC3D2F01}.Release|x86.ActiveCfg = Release|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Debug|x86.ActiveCfg = Debug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoDebug|Any CPU.ActiveCfg = MonoDebug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoDebug|Any CPU.Build.0 = MonoDebug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoDebug|x86.ActiveCfg = Debug|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoRelease|Any CPU.ActiveCfg = MonoRelease|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoRelease|Any CPU.Build.0 = MonoRelease|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.MonoRelease|x86.ActiveCfg = Release|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Release|Any CPU.Build.0 = Release|Any CPU - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9}.Release|x86.ActiveCfg = Release|Any CPU {DC8AAA8B-7FD0-47C4-9413-3CC94088BCC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC8AAA8B-7FD0-47C4-9413-3CC94088BCC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {DC8AAA8B-7FD0-47C4-9413-3CC94088BCC4}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -948,10 +892,6 @@ Global {864AF449-0902-44FC-BEEE-06BA9A6F4A8F} = {E944109B-0B7A-4ADE-8602-004CEFA5897D} {34DDDA42-4041-4F92-87A6-F0E8CE12C7E3} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3} {972C2D45-49B6-4109-9A3A-0C8BC9225B95} = {4A24657F-9695-437B-9702-2541ED280628} - {3C131D45-AF1D-4659-8B26-A9F55EED0D20} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3} - {97FA024A-F6ED-4086-BCC1-1A51BE63474C} = {E944109B-0B7A-4ADE-8602-004CEFA5897D} - {35460AA4-B94A-4B64-9418-7243EC3D2F01} = {4A24657F-9695-437B-9702-2541ED280628} - {9121DA01-7BFD-49F0-9937-0D3E7875ADB9} = {4A24657F-9695-437B-9702-2541ED280628} {DC8AAA8B-7FD0-47C4-9413-3CC94088BCC4} = {E944109B-0B7A-4ADE-8602-004CEFA5897D} {508A3A68-2012-4E62-925F-2F3083A013FC} = {A427F9F8-0A6F-4EEA-837F-FCDAB6E7D4B3} EndGlobalSection diff --git a/src/Nancy/Nancy.csproj b/src/Nancy/Nancy.csproj index d0b21cbb33..c4d7d971fa 100644 --- a/src/Nancy/Nancy.csproj +++ b/src/Nancy/Nancy.csproj @@ -337,7 +337,6 @@ - @@ -434,7 +433,7 @@ - + diff --git a/src/Nancy/NancyContext.cs b/src/Nancy/NancyContext.cs index 796bdee729..906b29c15e 100644 --- a/src/Nancy/NancyContext.cs +++ b/src/Nancy/NancyContext.cs @@ -5,11 +5,11 @@ namespace Nancy using System.Collections.Generic; using System.Globalization; using System.Linq; + using System.Security.Claims; using Nancy.Diagnostics; using Nancy.Responses.Negotiation; using Nancy.Routing; - using Nancy.Security; using Nancy.Validation; /// @@ -74,7 +74,7 @@ public Request Request /// /// Gets or sets the current user /// - public IUserIdentity CurrentUser { get; set; } + public ClaimsPrincipal CurrentUser { get; set; } /// /// Diagnostic request tracing diff --git a/src/Nancy/NancyEngineExtensions.cs b/src/Nancy/NancyEngineExtensions.cs index 6d22727292..0749772446 100644 --- a/src/Nancy/NancyEngineExtensions.cs +++ b/src/Nancy/NancyEngineExtensions.cs @@ -65,8 +65,6 @@ public static void HandleRequest( nancyEngine .HandleRequest(request, preRequest, cancellationToken) .WhenCompleted(t => onComplete(t.Result), t => onError(t.Exception)); - - //this.HandleRequest(request, null, onComplete, onError); } /// diff --git a/src/Nancy/Owin/NancyMiddleware.cs b/src/Nancy/Owin/NancyMiddleware.cs index cb4b0b9eca..f0e0e922a1 100644 --- a/src/Nancy/Owin/NancyMiddleware.cs +++ b/src/Nancy/Owin/NancyMiddleware.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Net; + using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -66,6 +67,7 @@ public static MidFunc UseNancy(NancyOptions options = null) var owinRequestBody = Get(environment, "owin.RequestBody"); var owinCallCancelled = Get(environment, "owin.CallCancelled"); var owinRequestHost = GetHeader(owinRequestHeaders, "Host") ?? Dns.GetHostName(); + var owinUser = GetUser(environment); byte[] certificate = null; if (options.EnableClientCertificates) @@ -92,7 +94,7 @@ public static MidFunc UseNancy(NancyOptions options = null) engine.HandleRequest( nancyRequest, - StoreEnvironment(environment), + StoreEnvironment(environment, owinUser), RequestComplete(environment, options.PerformPassThrough, next, tcs), RequestErrored(tcs), owinCallCancelled); @@ -101,6 +103,8 @@ public static MidFunc UseNancy(NancyOptions options = null) }; } + + /// /// Gets a delegate to handle converting a nancy response /// to the format required by OWIN and signals that the we are @@ -188,6 +192,23 @@ private static string GetHeader(IDictionary headers, string ke return headers.TryGetValue(key, out value) && value != null ? string.Join(",", value.ToArray()) : null; } + private static ClaimsPrincipal GetUser(IDictionary environment) + { + // OWIN 1.1 + object user; + if (environment.TryGetValue("owin.RequestUser", out user)) + { + return user as ClaimsPrincipal; + } + + // check for Katana User + if (environment.TryGetValue("server.User", out user)) + { + return user as ClaimsPrincipal; + } + return null; + } + private static long ExpectedLength(IDictionary headers) { var header = GetHeader(headers, "Content-Length"); @@ -241,14 +262,15 @@ private static Url CreateUrl( } /// - /// Gets a delegate to store the OWIN environment into the NancyContext + /// Gets a delegate to store the OWIN environment and flow the user into the NancyContext /// /// OWIN Environment /// Delegate - private static Func StoreEnvironment(IDictionary environment) + private static Func StoreEnvironment(IDictionary environment, ClaimsPrincipal user) { return context => { + context.CurrentUser = user; environment["nancy.NancyContext"] = context; context.Items[RequestEnvironmentKey] = environment; return context; diff --git a/src/Nancy/Security/UserIdentityExtensions.cs b/src/Nancy/Security/ClaimsPrincipalExtensions.cs similarity index 52% rename from src/Nancy/Security/UserIdentityExtensions.cs rename to src/Nancy/Security/ClaimsPrincipalExtensions.cs index e87f2bc3d6..c7c615b575 100644 --- a/src/Nancy/Security/UserIdentityExtensions.cs +++ b/src/Nancy/Security/ClaimsPrincipalExtensions.cs @@ -3,36 +3,21 @@ using System; using System.Collections.Generic; using System.Linq; + using System.Security.Claims; /// /// Extension methods for working with IUserIdentity. /// - public static class UserIdentityExtensions + public static class ClaimsPrincipalExtensions { /// /// Tests if the user is authenticated. /// /// User to be verified /// True if the user is authenticated, false otherwise - public static bool IsAuthenticated(this IUserIdentity user) + public static bool IsAuthenticated(this ClaimsPrincipal user) { - return - user != null - && !String.IsNullOrWhiteSpace(user.UserName); - } - - /// - /// Tests if the user has a required claim. - /// - /// User to be verified - /// Claim the user needs to have - /// True if the user has the required claim, false otherwise - public static bool HasClaim(this IUserIdentity user, string requiredClaim) - { - return - user != null - && user.Claims != null - && user.Claims.Contains(requiredClaim, StringComparer.OrdinalIgnoreCase); + return user != null && user.Identity != null && user.Identity.IsAuthenticated; } /// @@ -41,12 +26,9 @@ public static bool HasClaim(this IUserIdentity user, string requiredClaim) /// User to be verified /// Claims the user needs to have /// True if the user has all of the required claims, false otherwise - public static bool HasClaims(this IUserIdentity user, IEnumerable requiredClaims) + public static bool HasClaims(this ClaimsPrincipal user, params Predicate[] requiredClaims) { - return - user != null - && user.Claims != null - && !requiredClaims.Any(claim => !user.Claims.Contains(claim, StringComparer.OrdinalIgnoreCase)); + return user != null && requiredClaims.All(user.HasClaim); } /// @@ -55,12 +37,9 @@ public static bool HasClaims(this IUserIdentity user, IEnumerable requir /// User to be verified /// Claims the user needs to have at least one of /// True if the user has at least one of the required claims, false otherwise - public static bool HasAnyClaim(this IUserIdentity user, IEnumerable requiredClaims) + public static bool HasAnyClaim(this ClaimsPrincipal user, params Predicate[] requiredClaims) { - return - user != null - && user.Claims != null - && requiredClaims.Any(claim => user.Claims.Contains(claim, StringComparer.OrdinalIgnoreCase)); + return user != null && requiredClaims.Any(user.HasClaim); } /// @@ -70,7 +49,7 @@ public static bool HasAnyClaim(this IUserIdentity user, IEnumerable requ /// Validation function to be called with the authenticated /// users claims /// True if the user does pass the supplied validation function, false otherwise - public static bool HasValidClaims(this IUserIdentity user, Func, bool> isValid) + public static bool HasValidClaims(this ClaimsPrincipal user, Func, bool> isValid) { return user != null diff --git a/src/Nancy/Security/IUserIdentity.cs b/src/Nancy/Security/IUserIdentity.cs deleted file mode 100644 index 0d2cd50f4b..0000000000 --- a/src/Nancy/Security/IUserIdentity.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Nancy.Security -{ - using System.Collections.Generic; - - /// - /// Defines the core functionality of an identity - /// - public interface IUserIdentity - { - /// - /// The username of the authenticated user. - /// - /// A containing the username. - string UserName { get; } - - /// - /// The claims of the authenticated user. - /// - /// An , containing the claims. - IEnumerable Claims { get; } - } -} diff --git a/src/Nancy/Security/ModuleSecurity.cs b/src/Nancy/Security/ModuleSecurity.cs index a3285a41e2..a0381fae9d 100644 --- a/src/Nancy/Security/ModuleSecurity.cs +++ b/src/Nancy/Security/ModuleSecurity.cs @@ -1,7 +1,7 @@ namespace Nancy.Security { using System; - using System.Collections.Generic; + using System.Security.Claims; using Nancy.Extensions; using Nancy.Responses; @@ -25,7 +25,7 @@ public static void RequiresAuthentication(this INancyModule module) /// /// Module to enable /// Claim(s) required - public static void RequiresClaims(this INancyModule module, IEnumerable requiredClaims) + public static void RequiresClaims(this INancyModule module, params Predicate[] requiredClaims) { module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication"); module.AddBeforeHookOrExecute(SecurityHooks.RequiresClaims(requiredClaims), "Requires Claims"); @@ -36,23 +36,12 @@ public static void RequiresClaims(this INancyModule module, IEnumerable /// /// Module to enable /// Claim(s) required - public static void RequiresAnyClaim(this INancyModule module, IEnumerable requiredClaims) + public static void RequiresAnyClaim(this INancyModule module, params Predicate[] requiredClaims) { module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication"); module.AddBeforeHookOrExecute(SecurityHooks.RequiresAnyClaim(requiredClaims), "Requires Any Claim"); } - /// - /// This module requires claims to be validated - /// - /// Module to enable - /// Claims validator - public static void RequiresValidatedClaims(this INancyModule module, Func, bool> isValid) - { - module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication"); - module.AddBeforeHookOrExecute(SecurityHooks.RequiresValidatedClaims(isValid), "Requires Validated Claim"); - } - /// /// This module requires https. /// diff --git a/src/Nancy/Security/SecurityHooks.cs b/src/Nancy/Security/SecurityHooks.cs index 8cdc0cbeb3..7fc982ff5c 100644 --- a/src/Nancy/Security/SecurityHooks.cs +++ b/src/Nancy/Security/SecurityHooks.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Security.Claims; using Nancy.Responses; @@ -28,7 +29,7 @@ public static Func RequiresAuthentication() /// Claims the authenticated user needs to have /// Hook that returns an Unauthorized response if the user is not /// authenticated or does not have the required claims, null otherwise - public static Func RequiresClaims(IEnumerable claims) + public static Func RequiresClaims(params Predicate[] claims) { return ForbiddenIfNot(ctx => ctx.CurrentUser.HasClaims(claims)); } @@ -42,7 +43,7 @@ public static Func RequiresClaims(IEnumerable cl /// Hook that returns an Unauthorized response if the user is not /// authenticated or does not have at least one of the required claims, null /// otherwise - public static Func RequiresAnyClaim(IEnumerable claims) + public static Func RequiresAnyClaim(params Predicate[] claims) { return ForbiddenIfNot(ctx => ctx.CurrentUser.HasAnyClaim(claims)); } @@ -57,7 +58,7 @@ public static Func RequiresAnyClaim(IEnumerable /// Hook that returns an Unauthorized response if the user is not /// authenticated or does not pass the supplied validation function, null /// otherwise - public static Func RequiresValidatedClaims(Func, bool> isValid) + public static Func RequiresValidatedClaims(Func, bool> isValid) { return ForbiddenIfNot(ctx => ctx.CurrentUser.HasValidClaims(isValid)); } @@ -105,19 +106,6 @@ private static Func HttpStatusCodeIfNot(HttpStatusCode s }; } - /// - /// Creates a hook to be used in a pipeline before a route handler to ensure that - /// the resource is served over HTTPS - /// - /// if the user should be redirected to HTTPS (no port number) if the incoming request was made using HTTP, otherwise if should be returned. - /// Hook that returns a RedirectResponse with the Url scheme set to HTTPS, - /// or a Response with a Forbidden HTTP status code if redirect is false or the method is not GET, - /// null otherwise - public static Func RequiresHttps(bool redirect) - { - return RequiresHttps(redirect, null); - } - /// /// Creates a hook to be used in a pipeline before a route handler to ensure that /// the resource is served over HTTPS @@ -127,7 +115,7 @@ public static Func RequiresHttps(bool redirect) /// Hook that returns a with the Url scheme set to HTTPS, /// or a with a status code if redirect is false or the method is not GET, /// null otherwise - public static Func RequiresHttps(bool redirect, int? httpsPort) + public static Func RequiresHttps(bool redirect, int? httpsPort = null) { return (ctx) => {