From 17eccd2613dfdcf067d5f11656acf410b0cb25e5 Mon Sep 17 00:00:00 2001 From: johnmcavinue Date: Wed, 25 May 2016 15:49:09 +0100 Subject: [PATCH 1/9] Added an event for token revocation --- source/Core/Core.csproj | 1 + .../Connect/RevocationEndpointController.cs | 4 +- .../Authentication/TokenRevokedDetails.cs | 48 +++++++++++++++++++ source/Core/Events/Base/EventConstants.cs | 2 + .../Extensions/IEventServiceExtensions.cs | 17 +++++++ source/Core/Resources/Events.Designer.cs | 9 ++++ source/Core/Resources/Events.resx | 3 ++ 7 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 source/Core/Events/Authentication/TokenRevokedDetails.cs diff --git a/source/Core/Core.csproj b/source/Core/Core.csproj index 02111cece..d98940785 100644 --- a/source/Core/Core.csproj +++ b/source/Core/Core.csproj @@ -146,6 +146,7 @@ + diff --git a/source/Core/Endpoints/Connect/RevocationEndpointController.cs b/source/Core/Endpoints/Connect/RevocationEndpointController.cs index 904a8482b..d3a7694d5 100644 --- a/source/Core/Endpoints/Connect/RevocationEndpointController.cs +++ b/source/Core/Endpoints/Connect/RevocationEndpointController.cs @@ -120,12 +120,13 @@ public async Task ProcessAsync(Client client, NameValueCollec private async Task RevokeAccessTokenAsync(string handle, Client client) { var token = await _tokenHandles.GetAsync(handle); - + if (token != null) { if (token.ClientId == client.ClientId) { await _tokenHandles.RemoveAsync(handle); + await _events.RaiseTokenRevokedEventAsync(token.SubjectId, handle, Constants.TokenTypeHints.AccessToken); } else { @@ -152,6 +153,7 @@ private async Task RevokeRefreshTokenAsync(string handle, Client client) { await _refreshTokens.RevokeAsync(token.SubjectId, token.ClientId); await _tokenHandles.RevokeAsync(token.SubjectId, token.ClientId); + await _events.RaiseTokenRevokedEventAsync(token.SubjectId, handle, Constants.TokenTypeHints.RefreshToken); } else { diff --git a/source/Core/Events/Authentication/TokenRevokedDetails.cs b/source/Core/Events/Authentication/TokenRevokedDetails.cs new file mode 100644 index 000000000..e1e03fa3a --- /dev/null +++ b/source/Core/Events/Authentication/TokenRevokedDetails.cs @@ -0,0 +1,48 @@ +/* + * Copyright 2014, 2015 Dominick Baier, Brock Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using IdentityServer3.Core.Models; + +namespace IdentityServer3.Core.Events +{ + /// + /// Event details for token revocation event + /// + public class TokenRevokedDetails + { + /// + /// Gets or sets the token. + /// + /// + /// The token that was revoked. + /// + public string Token { get; set; } + + /// + /// Gets or sets the token toke. + /// + /// + /// The type of token that was revoked. Access token or Refresh. + /// + public string TokenType { get; set; } + + /// + /// Gets or sets the subject Id + /// + /// + public string SubjectId { get; set; } + } +} \ No newline at end of file diff --git a/source/Core/Events/Base/EventConstants.cs b/source/Core/Events/Base/EventConstants.cs index f1327bdd6..4bb449f00 100644 --- a/source/Core/Events/Base/EventConstants.cs +++ b/source/Core/Events/Base/EventConstants.cs @@ -70,6 +70,8 @@ public static class Ids public const int Logout = AuthenticationEventsStart + 30; + public const int TokenRevoked = AuthenticationEventsStart + 35; + public const int PartialLogin = AuthenticationEventsStart + 40; public const int PartialLoginComplete = AuthenticationEventsStart + 41; diff --git a/source/Core/Extensions/IEventServiceExtensions.cs b/source/Core/Extensions/IEventServiceExtensions.cs index e4f6144f6..d9ffb2b69 100644 --- a/source/Core/Extensions/IEventServiceExtensions.cs +++ b/source/Core/Extensions/IEventServiceExtensions.cs @@ -406,6 +406,23 @@ public static async Task RaiseSuccessfulRefreshTokenRefreshEventAsync(this IEven await events.RaiseEventAsync(evt); } + public static async Task RaiseTokenRevokedEventAsync(this IEventService events, string subjectId, string token, string tokenType) + { + var evt = new Event( + EventConstants.Categories.Authentication, + Resources.Events.TokenRevoked, + EventTypes.Success, + EventConstants.Ids.TokenRevoked, + new TokenRevokedDetails() + { + SubjectId = subjectId, + Token = token, + TokenType = tokenType + }); + + await events.RaiseEventAsync(evt); + } + public static async Task RaiseUnhandledExceptionEventAsync(this IEventService events, Exception exception) { var evt = new Event( diff --git a/source/Core/Resources/Events.Designer.cs b/source/Core/Resources/Events.Designer.cs index f95283af6..08e81a101 100644 --- a/source/Core/Resources/Events.Designer.cs +++ b/source/Core/Resources/Events.Designer.cs @@ -185,5 +185,14 @@ public static string ResourceOwnerFlowLoginSuccess { return ResourceManager.GetString("ResourceOwnerFlowLoginSuccess", resourceCulture); } } + + /// + /// Looks up a localized string similar to Token Revoked Event. + /// + public static string TokenRevoked { + get { + return ResourceManager.GetString("TokenRevoked", resourceCulture); + } + } } } diff --git a/source/Core/Resources/Events.resx b/source/Core/Resources/Events.resx index 98e09615a..347547720 100644 --- a/source/Core/Resources/Events.resx +++ b/source/Core/Resources/Events.resx @@ -159,4 +159,7 @@ Resource Owner Password Flow Login Success + + Token Revoked Event + \ No newline at end of file From 2a123b5b80d40c4ec8795888af6b33df051d53f8 Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Wed, 24 Aug 2016 14:40:34 +0200 Subject: [PATCH 2/9] update version to 2.6 --- default.ps1 | 2 +- source/VersionAssemblyInfo.cs | Bin 226 -> 226 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/default.ps1 b/default.ps1 index 0a5667934..1e7785b14 100644 --- a/default.ps1 +++ b/default.ps1 @@ -11,7 +11,7 @@ properties { $nuget_path = "$base_directory\nuget.exe" $buildNumber = 0; - $version = "2.5.2.0" + $version = "2.6.0.0" $preRelease = $null } diff --git a/source/VersionAssemblyInfo.cs b/source/VersionAssemblyInfo.cs index c81cdd9a21b16c52bc169269244b84dc148790ea..f92f276528753b54c43e46051b9ee908d080e7af 100644 GIT binary patch delta 32 kcmaFF_=s^r8Kc?6@^BF|20aD?FjivFWQb+pW#D1}0FGM*C;$Ke delta 32 mcmaFF_=s^r8KddM@^BGT20aENAU0r7V$fuWW#DDtVgLY+P6j9d From dccd094e2a63961429781dabc429dfb55023f6b5 Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Wed, 24 Aug 2016 14:41:18 +0200 Subject: [PATCH 3/9] add POCO support to token response generator --- source/Core/Results/TokenResult.cs | 11 ++++++++--- .../Extensions/CustomTokenResponseGenerator.cs | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/source/Core/Results/TokenResult.cs b/source/Core/Results/TokenResult.cs index f9b2ba46c..09398eaf1 100644 --- a/source/Core/Results/TokenResult.cs +++ b/source/Core/Results/TokenResult.cs @@ -22,7 +22,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -76,13 +75,19 @@ private HttpResponseMessage Execute() throw new Exception("Item does already exist - cannot add it via a custom entry: " + item.Key); } - jobject.Add(new JProperty(item.Key, item.Value)); + if (item.Value.GetType().IsClass) + { + jobject.Add(new JProperty(item.Key, JToken.FromObject(item.Value))); + } + else + { + jobject.Add(new JProperty(item.Key, item.Value)); + } } } var response = new HttpResponseMessage(HttpStatusCode.OK) { - //Content = new ObjectContent(jobject, new JsonMediaTypeFormatter()) Content = new StringContent(jobject.ToString(Formatting.None), Encoding.UTF8, "application/json") }; diff --git a/source/Host.Configuration/Extensions/CustomTokenResponseGenerator.cs b/source/Host.Configuration/Extensions/CustomTokenResponseGenerator.cs index d141df4d1..83dae1d04 100644 --- a/source/Host.Configuration/Extensions/CustomTokenResponseGenerator.cs +++ b/source/Host.Configuration/Extensions/CustomTokenResponseGenerator.cs @@ -10,8 +10,16 @@ class CustomTokenResponseGenerator : ICustomTokenResponseGenerator public Task GenerateAsync(ValidatedTokenRequest request, TokenResponse response) { response.Custom.Add("custom_field", "custom data"); + response.Custom.Add("custom_complex_field", new ResponsePoco { SomeString = "foo", SomeInt = 42 }); + return Task.FromResult(response); } } + + class ResponsePoco + { + public string SomeString { get; set; } + public int SomeInt { get; set; } + } } \ No newline at end of file From 8967046376001ea92af41564ac3cc543ca56d48b Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Thu, 25 Aug 2016 07:37:58 +0200 Subject: [PATCH 4/9] fix custom discovery logic --- .../Endpoints/Connect/DiscoveryEndpointController.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/Core/Endpoints/Connect/DiscoveryEndpointController.cs b/source/Core/Endpoints/Connect/DiscoveryEndpointController.cs index 45e6d1051..c6e23f314 100644 --- a/source/Core/Endpoints/Connect/DiscoveryEndpointController.cs +++ b/source/Core/Endpoints/Connect/DiscoveryEndpointController.cs @@ -210,7 +210,14 @@ public async Task GetConfiguration() throw new Exception("Item does already exist - cannot add it via a custom entry: " + item.Key); } - jobject.Add(new JProperty(item.Key, item.Value)); + if (item.Value.GetType().IsClass) + { + jobject.Add(new JProperty(item.Key, JToken.FromObject(item.Value))); + } + else + { + jobject.Add(new JProperty(item.Key, item.Value)); + } } } From 022b463b73a8bc2488f282f038e1bab4c80da365 Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Thu, 25 Aug 2016 07:49:37 +0200 Subject: [PATCH 5/9] Obfuscate tokens in events (closes #3165) --- .../Extensions/IEventServiceExtensions.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/source/Core/Extensions/IEventServiceExtensions.cs b/source/Core/Extensions/IEventServiceExtensions.cs index 7fa7bc77f..88371506e 100644 --- a/source/Core/Extensions/IEventServiceExtensions.cs +++ b/source/Core/Extensions/IEventServiceExtensions.cs @@ -416,7 +416,7 @@ public static async Task RaiseTokenRevokedEventAsync(this IEventService events, new TokenRevokedDetails() { SubjectId = subjectId, - Token = token, + Token = ObfuscateToken(token), TokenType = tokenType }); @@ -495,12 +495,6 @@ public static async Task RaiseFailureEndpointEventAsync(this IEventService event public static async Task RaiseSuccessfulIntrospectionEndpointEventAsync(this IEventService events, string token, string tokenStatus, string scopeName) { - string last4chars = "****"; - if (token.IsPresent() && token.Length > 4) - { - last4chars = token.Substring(token.Length - 4); - } - var evt = new Event( EventConstants.Categories.Endpoints, "Introspection endpoint success", @@ -508,7 +502,7 @@ public static async Task RaiseSuccessfulIntrospectionEndpointEventAsync(this IEv EventConstants.Ids.IntrospectionEndpointSuccess, new IntrospectionEndpointDetail { - Token = "***" + last4chars, + Token = ObfuscateToken(token), TokenStatus = tokenStatus, ScopeName = scopeName }); @@ -518,12 +512,6 @@ public static async Task RaiseSuccessfulIntrospectionEndpointEventAsync(this IEv public static async Task RaiseFailureIntrospectionEndpointEventAsync(this IEventService events, string error, string token, string scopeName) { - string last4chars = "****"; - if (token.IsPresent() && token.Length > 4) - { - last4chars = token.Substring(token.Length - 4); - } - var evt = new Event( EventConstants.Categories.Endpoints, "Introspection endpoint failure", @@ -531,7 +519,7 @@ public static async Task RaiseFailureIntrospectionEndpointEventAsync(this IEvent EventConstants.Ids.IntrospectionEndpointFailure, new IntrospectionEndpointDetail { - Token = "***" + last4chars, + Token = ObfuscateToken(token), ScopeName = scopeName }, error); @@ -676,5 +664,16 @@ private static async Task RaiseEventAsync(this IEventService events, Event await events.RaiseAsync(evt); } + + private static string ObfuscateToken(string token) + { + string last4chars = "****"; + if (token.IsPresent() && token.Length > 4) + { + last4chars = token.Substring(token.Length - 4); + } + + return "****" + last4chars; + } } } \ No newline at end of file From 7e0c242e65d58a381f70383318fc841d406236a7 Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Thu, 25 Aug 2016 08:06:17 +0200 Subject: [PATCH 6/9] enable events on token client tests code path --- source/Tests/UnitTests/TokenClients/Setup/Startup.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Tests/UnitTests/TokenClients/Setup/Startup.cs b/source/Tests/UnitTests/TokenClients/Setup/Startup.cs index 381de8fd4..341b39e2f 100644 --- a/source/Tests/UnitTests/TokenClients/Setup/Startup.cs +++ b/source/Tests/UnitTests/TokenClients/Setup/Startup.cs @@ -21,6 +21,14 @@ public static AppBuilder Create() app.UseIdentityServer(new IdentityServerOptions { + EventsOptions = new EventsOptions + { + RaiseErrorEvents = true, + RaiseFailureEvents = true, + RaiseInformationEvents = true, + RaiseSuccessEvents = true + }, + IssuerUri = "https://idsrv3", SigningCertificate = TestCert.Load(), From 8ec1b63e708c07da26750179fc18439b7c9dc18d Mon Sep 17 00:00:00 2001 From: Brock Allen Date: Thu, 1 Sep 2016 11:28:06 -0400 Subject: [PATCH 7/9] add test to show bug in reading form post from custom user service --- source/Tests/UnitTests/Core.Tests.csproj | 1 + .../AuthenticationControllerTests.cs | 254 +++++++++--------- .../Endpoints/Setup/IdSvrHostTestBase.cs | 12 +- .../Endpoints/Setup/MockUserService.cs | 87 ++++++ 4 files changed, 230 insertions(+), 124 deletions(-) create mode 100644 source/Tests/UnitTests/Endpoints/Setup/MockUserService.cs diff --git a/source/Tests/UnitTests/Core.Tests.csproj b/source/Tests/UnitTests/Core.Tests.csproj index 7f71a0387..2b0d361e5 100644 --- a/source/Tests/UnitTests/Core.Tests.csproj +++ b/source/Tests/UnitTests/Core.Tests.csproj @@ -194,6 +194,7 @@ + diff --git a/source/Tests/UnitTests/Endpoints/AuthenticationControllerTests.cs b/source/Tests/UnitTests/Endpoints/AuthenticationControllerTests.cs index 41ad81d93..99d1e8aa7 100644 --- a/source/Tests/UnitTests/Endpoints/AuthenticationControllerTests.cs +++ b/source/Tests/UnitTests/Endpoints/AuthenticationControllerTests.cs @@ -148,6 +148,33 @@ public void GetLogin_EnableLoginHintFalse_UsernameIsNotPopulatedFromLoginHint() model.Username.Should().BeNull(); ; } + [Fact] + public void PostToLogin_UserServiceReadsOwinRequestBody_Should_Read_Custom_Data() + { + var msg = new SignInMessage(); + msg.LoginHint = "test"; + + var resp = GetLoginPage(msg); + var model = resp.GetModel(); + + string customParam = null; + mockUserService.OnAuthenticateLocal = async ctx => + { + var owin = new OwinContext(mockUserService.OwinEnvironmentService.Environment); + var form = await owin.Request.ReadFormAsync(); + customParam = form["CustomParam"]; + }; + + var data = new + { + username = "alice", + password = "password", + CustomParam = "some_value" + }; + resp = PostForm(model.LoginUrl, data); + customParam.Should().Be("some_value"); + } + [Fact] public void PostToLogin_SignInMessageHasLoginHint_UsernameShouldBeUsernamePosted() { @@ -205,12 +232,11 @@ public void GetLogin_PreAuthenticateReturnsNull_ShowsLoginPage() [Fact] public void GetLogin_PreAuthenticateReturnsError_ShowsErrorPage() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx=>{ - ctx.AuthenticateResult = new AuthenticateResult("SomeError"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("SomeError"); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.AssertPage("error"); @@ -221,13 +247,12 @@ public void GetLogin_PreAuthenticateReturnsError_ShowsErrorPage() [Fact] public void GetLogin_PreAuthenticateReturnsErrorAndShowLoginPageOnErrorResultIsSet_ShowsLoginPageWithError() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx => { - ctx.AuthenticateResult = new AuthenticateResult("SomeError"); - ctx.ShowLoginPageOnErrorResult = true; - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("SomeError"); + ctx.ShowLoginPageOnErrorResult = true; + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.AssertPage("login"); @@ -238,13 +263,11 @@ public void GetLogin_PreAuthenticateReturnsErrorAndShowLoginPageOnErrorResultIsS [Fact] public void GetLogin_PreAuthenticateReturnsFullLogin_IssuesLoginCookie() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult(IdentityServerPrincipal.Create("sub", "name")); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult(IdentityServerPrincipal.Create("sub", "name")); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.AssertCookie(Constants.PrimaryAuthenticationType); @@ -253,12 +276,11 @@ public void GetLogin_PreAuthenticateReturnsFullLogin_IssuesLoginCookie() [Fact] public void GetLogin_PreAuthenticateReturnsFullLogin_RedirectsToReturnUrl() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx=>{ - ctx.AuthenticateResult = new AuthenticateResult(IdentityServerPrincipal.Create("sub", "name")); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult(IdentityServerPrincipal.Create("sub", "name")); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.StatusCode.Should().Be(HttpStatusCode.Found); @@ -268,13 +290,11 @@ public void GetLogin_PreAuthenticateReturnsFullLogin_RedirectsToReturnUrl() [Fact] public void GetLogin_PreAuthenticateReturnsParialLogin_IssuesPartialLoginCookie() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.AssertCookie(Constants.PartialSignInAuthenticationType); @@ -283,13 +303,11 @@ public void GetLogin_PreAuthenticateReturnsParialLogin_IssuesPartialLoginCookie( [Fact] public void GetLogin_PreAuthenticateReturnsParialLogin_IssuesRedirect() { - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.StatusCode.Should().Be(HttpStatusCode.Found); @@ -321,13 +339,11 @@ public void GetLogin_PublicHostNameConfigured_PreAuthenticateReturnsParialLogin_ }; Init(); - mockUserService - .Setup(x => x.PreAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnPreAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; var resp = GetLoginPage(); resp.StatusCode.Should().Be(HttpStatusCode.Found); @@ -550,12 +566,11 @@ public void PostToLogin_InvalidPassword_ShowErrorPage() [Fact] public void PostToLogin_UserServiceReturnsError_ShowErrorPage() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("bad stuff"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("bad stuff"); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -567,8 +582,11 @@ public void PostToLogin_UserServiceReturnsError_ShowErrorPage() [Fact] public void PostToLogin_UserServiceReturnsNull_ShowErrorPage() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Returns(Task.FromResult((AuthenticateResult)null)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = null; + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -580,12 +598,11 @@ public void PostToLogin_UserServiceReturnsNull_ShowErrorPage() [Fact] public void PostToLogin_UserServiceReturnsParialLogin_IssuesPartialLoginCookie() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -595,12 +612,11 @@ public void PostToLogin_UserServiceReturnsParialLogin_IssuesPartialLoginCookie() [Fact] public void PostToLogin_UserServiceReturnsParialLogin_IssuesRedirect() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -658,12 +674,11 @@ public void PostToLogin_CookieOptions_AllowRememberMeIsTrue_IsPersistentIsTrue_C [Fact] public void PostToLogin_CookieOptionsIsPersistentIsTrueButResponseIsPartialLogin_DoesNotIssuePersistentCookie() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; options.AuthenticationOptions.CookieOptions.IsPersistent = true; GetLoginPage(); @@ -678,31 +693,31 @@ public void PostToLogin_PostAuthenticate_IsCalled() { GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); - mockUserService.Verify(x => x.PostAuthenticateAsync(It.IsAny())); + mockUserService.PostAuthenticateWasCalled.Should().BeTrue(); } [Fact] public void PostToLogin_PostAuthenticate_is_not_called_for_partial_logins() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("~/partial", "123", "foo", Enumerable.Empty()); - }).Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("~/partial", "123", "foo", Enumerable.Empty()); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); - mockUserService.Verify(x => x.PostAuthenticateAsync(It.IsAny()), Times.Never()); + mockUserService.PostAuthenticateWasCalled.Should().BeFalse(); } [Fact] public void PostToLogin_PostAuthenticate_returns_error_and_error_page_is_rendered_and_user_is_not_logged_in() { - mockUserService.Setup(x => x.PostAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("some error"); - }).Returns(Task.FromResult(0)); + mockUserService.OnPostAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("some error"); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -715,11 +730,11 @@ public void PostToLogin_PostAuthenticate_returns_error_and_error_page_is_rendere [Fact] public void PostToLogin_PostAuthenticate_returns_partial_login_and_user_is_not_logged_in() { - mockUserService.Setup(x => x.PostAuthenticateAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("~/foo", "123", "bob"); - }).Returns(Task.FromResult(0)); + mockUserService.OnPostAuthenticate = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("~/foo", "123", "bob"); + return Task.FromResult(0); + }; GetLoginPage(); var resp = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -732,12 +747,11 @@ public void PostToLogin_PostAuthenticate_returns_partial_login_and_user_is_not_l [Fact] public void ResumeLoginFromRedirect_WithPartialCookie_IssuesFullLoginCookie() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; GetLoginPage(); var resp1 = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -749,12 +763,11 @@ public void ResumeLoginFromRedirect_WithPartialCookie_IssuesFullLoginCookie() [Fact] public void ResumeLoginFromRedirect_WithPartialCookie_IssuesRedirectToAuthorizationPage() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; GetLoginPage(); var resp1 = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -768,12 +781,11 @@ public void ResumeLoginFromRedirect_WithPartialCookie_IssuesRedirectToAuthorizat [Fact] public void ResumeLoginFromRedirect_WithoutPartialCookie_ShowsError() { - mockUserService.Setup(x => x.AuthenticateLocalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateLocal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("/foo", "tempsub", "tempname"); + return Task.FromResult(0); + }; GetLoginPage(); var resp1 = PostForm(GetLoginUrl(), new LoginCredentials { Username = "alice", Password = "alice" }); @@ -864,7 +876,7 @@ public void PostToLogout_SignOutMessagePassed_RequireSignOutPromptSet_LogoutPage public void PostToLogout_AnonymousUser_DoesNotInvokeUserServiceSignOut() { var resp = PostForm(Constants.RoutePaths.Logout, (string)null); - this.mockUserService.Verify(x => x.SignOutAsync(It.IsAny()), Times.Never()); + this.mockUserService.SignOutWasCalled.Should().BeFalse(); } [Fact] @@ -873,7 +885,7 @@ public void PostToLogout_AuthenticatedUser_InvokesUserServiceSignOut() Login(); var resp = PostForm(Constants.RoutePaths.Logout, (string)null); - this.mockUserService.Verify(x => x.SignOutAsync(It.IsAny())); + this.mockUserService.SignOutWasCalled.Should().BeTrue(); } [Fact] @@ -1003,12 +1015,11 @@ public void LoginExternalCallback_WithValidSubjectClaim_RedirectsToAuthorizeEndp [Fact] public void LoginExternalCallback_UserServiceReturnsError_ShowsError() { - mockUserService.Setup(x => x.AuthenticateExternalAsync(It.IsAny())) - .Callback(ctx => - { - ctx.AuthenticateResult = new AuthenticateResult("foo bad"); - }) - .Returns(Task.FromResult(0)); + mockUserService.OnAuthenticateExternal = ctx => + { + ctx.AuthenticateResult = new AuthenticateResult("foo bad"); + return Task.FromResult(0); + }; var msg = new SignInMessage(); msg.IdP = "Google"; @@ -1029,9 +1040,12 @@ public void LoginExternalCallback_UserServiceReturnsError_ShowsError() [Fact] public void LoginExternalCallback_UserServiceReturnsNull_ShowError() { - mockUserService.Setup(x => x.AuthenticateExternalAsync(It.IsAny())) - .Returns(Task.FromResult((AuthenticateResult)null)); - + mockUserService.OnAuthenticateExternal = ctx => + { + ctx.AuthenticateResult = null; + return Task.FromResult(0); + }; + var msg = new SignInMessage(); msg.IdP = "Google"; msg.ReturnUrl = Url("authorize"); @@ -1063,7 +1077,7 @@ public void LoginExternalCallback_UserIsAnonymous_NoSubjectIsPassedToUserService Get(Constants.RoutePaths.LoginExternalCallback); - mockUserService.Verify(x => x.AuthenticateExternalAsync(It.IsAny())); + mockUserService.AuthenticateExternalWasCalled.Should().BeTrue(); } [Fact] diff --git a/source/Tests/UnitTests/Endpoints/Setup/IdSvrHostTestBase.cs b/source/Tests/UnitTests/Endpoints/Setup/IdSvrHostTestBase.cs index e564fe4d6..aab3753ed 100644 --- a/source/Tests/UnitTests/Endpoints/Setup/IdSvrHostTestBase.cs +++ b/source/Tests/UnitTests/Endpoints/Setup/IdSvrHostTestBase.cs @@ -22,6 +22,7 @@ using IdentityServer3.Core.Services; using IdentityServer3.Core.Services.InMemory; using IdentityServer3.Core.ViewModels; +using IdentityServer3.Tests.Endpoints.Setup; using Microsoft.Owin; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.Google; @@ -46,7 +47,7 @@ public class IdSvrHostTestBase protected IDataProtector protector; protected TicketDataFormat ticketFormatter; - protected Mock mockUserService; + protected MockUserService mockUserService; protected IdentityServerOptions options; protected IAppBuilder appBuilder; @@ -81,9 +82,12 @@ protected void Init() { appBuilder = app; - mockUserService = new Mock(TestUsers.Get()); - mockUserService.CallBase = true; - factory.UserService = new Registration((resolver) => mockUserService.Object); + mockUserService = new MockUserService(TestUsers.Get()); + factory.UserService = new Registration(resolver=> + { + mockUserService.OwinEnvironmentService = resolver.Resolve(); + return mockUserService; + }); options = TestIdentityServerOptions.Create(); options.Factory = factory; diff --git a/source/Tests/UnitTests/Endpoints/Setup/MockUserService.cs b/source/Tests/UnitTests/Endpoints/Setup/MockUserService.cs new file mode 100644 index 000000000..643fb54d3 --- /dev/null +++ b/source/Tests/UnitTests/Endpoints/Setup/MockUserService.cs @@ -0,0 +1,87 @@ +using IdentityServer3.Core.Services; +using IdentityServer3.Core.Services.InMemory; +using Microsoft.Owin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IdentityServer3.Core.Models; + +namespace IdentityServer3.Tests.Endpoints.Setup +{ + public class MockUserService : InMemoryUserService + { + public Func OnPreAuthenticate { get; set; } + public Func OnAuthenticateLocal { get; set; } + public Func OnAuthenticateExternal { get; set; } + public Func OnPostAuthenticate { get; set; } + + public bool AuthenticateExternalWasCalled { get; set; } + public bool PostAuthenticateWasCalled { get; set; } + public bool SignOutWasCalled { get; set; } + + public OwinEnvironmentService OwinEnvironmentService { get; set; } + + public MockUserService(List users) : base(users) + { + } + + public async override Task PreAuthenticateAsync(PreAuthenticationContext context) + { + if (OnPreAuthenticate != null) + { + await OnPreAuthenticate(context); + } + else + { + await base.PreAuthenticateAsync(context); + } + } + + public async override Task AuthenticateLocalAsync(LocalAuthenticationContext context) + { + if (OnAuthenticateLocal != null) + { + await OnAuthenticateLocal(context); + } + else + { + await base.AuthenticateLocalAsync(context); + } + } + + public async override Task AuthenticateExternalAsync(ExternalAuthenticationContext context) + { + AuthenticateExternalWasCalled = true; + + if (OnAuthenticateExternal != null) + { + await OnAuthenticateExternal(context); + } + else + { + await base.AuthenticateExternalAsync(context); + } + } + + public async override Task PostAuthenticateAsync(PostAuthenticationContext context) + { + PostAuthenticateWasCalled = true; + if (OnPostAuthenticate != null) + { + await OnPostAuthenticate(context); + } + else + { + await base.PostAuthenticateAsync(context); + } + } + + public override Task SignOutAsync(SignOutContext context) + { + SignOutWasCalled = true; + return base.SignOutAsync(context); + } + } +} From 3498eae2fdc226c43843f9a3ade76881ce72e60b Mon Sep 17 00:00:00 2001 From: Brock Allen Date: Thu, 1 Sep 2016 11:28:29 -0400 Subject: [PATCH 8/9] fix bug where custom user service can't read owin post body --- .../Hosting/ValidateAntiForgeryTokenAttribute.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/Core/Configuration/Hosting/ValidateAntiForgeryTokenAttribute.cs b/source/Core/Configuration/Hosting/ValidateAntiForgeryTokenAttribute.cs index 52159b5ab..53716786a 100644 --- a/source/Core/Configuration/Hosting/ValidateAntiForgeryTokenAttribute.cs +++ b/source/Core/Configuration/Hosting/ValidateAntiForgeryTokenAttribute.cs @@ -58,6 +58,17 @@ private static async Task ValidateTokens(HttpActionContext actionContext) actionContext.Request.Content.IsFormData(); if (success) { + // ReadAsByteArrayAsync buffers the request body stream + // so Web API will re-use that later for model binding + // unfortunately the stream pointer is at the end, but + // in our anti-forgery logic we use our internal ReadRequestFormAsync + // API to read the body, which has the side effect of resetting + // the stream pointer to the begining. subsequet calls to + // read the form body will then succeed (e.g. via OwinContext) + // this is all rather unfortunate that web api prevents others + // from re-reading the form, but this sequence of code allow it. #lame + var bytes = await actionContext.Request.Content.ReadAsByteArrayAsync(); + var antiForgeryToken = env.ResolveDependency(); success = await antiForgeryToken.IsTokenValid(); } From 6c59ba566a18213c5fbc823f1df3db389e3bb595 Mon Sep 17 00:00:00 2001 From: Dominick Baier Date: Thu, 1 Sep 2016 17:39:22 +0200 Subject: [PATCH 9/9] going back to 2.5.3 --- default.ps1 | 2 +- source/VersionAssemblyInfo.cs | Bin 226 -> 226 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/default.ps1 b/default.ps1 index 1e7785b14..c4ab00645 100644 --- a/default.ps1 +++ b/default.ps1 @@ -11,7 +11,7 @@ properties { $nuget_path = "$base_directory\nuget.exe" $buildNumber = 0; - $version = "2.6.0.0" + $version = "2.5.3.0" $preRelease = $null } diff --git a/source/VersionAssemblyInfo.cs b/source/VersionAssemblyInfo.cs index f92f276528753b54c43e46051b9ee908d080e7af..a64ba0f8fb9f885443a7554b170cb1c2768b7fb9 100644 GIT binary patch delta 32 mcmaFF_=s^r8KddM@^BGT20aF2AU0r7V$fuWW#DDtVgLY+UIr-u delta 32 kcmaFF_=s^r8Kc?6@^BF|20aD?FjivFWQb+pW#D1}0FGM*C;$Ke