From 371afd23030ddb088ed3617c53630963c03b9c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 6 May 2021 17:31:19 +0200 Subject: [PATCH] Update the ASP.NET Core/OWIN server/validation hosts to populate AuthenticationProperties.IssuedUtc/ExpiresUtc --- .../OpenIddictServerAspNetCoreHandler.cs | 7 +- .../OpenIddictServerOwinHandler.cs | 6 +- .../OpenIddictValidationAspNetCoreHandler.cs | 7 +- .../OpenIddictValidationOwinHandler.cs | 6 +- ...nIddictServerAspNetCoreIntegrationTests.cs | 124 +++++++++++++++++- .../OpenIddictServerOwinIntegrationTests.cs | 124 +++++++++++++++++- 6 files changed, 262 insertions(+), 12 deletions(-) diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs index 2d214c47..536c2fbc 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs @@ -161,7 +161,12 @@ namespace OpenIddict.Server.AspNetCore // Store the token to allow any OWIN/Katana component (e.g a controller) // to retrieve it (e.g to make an API request to another application). - var properties = new AuthenticationProperties(); + var properties = new AuthenticationProperties + { + ExpiresUtc = context.Principal.GetExpirationDate(), + IssuedUtc = context.Principal.GetCreationDate() + }; + properties.StoreTokens(new[] { new AuthenticationToken diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs index ca5bc475..e015ab32 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs @@ -176,7 +176,11 @@ namespace OpenIddict.Server.Owin var properties = new AuthenticationProperties(new Dictionary { [context.Principal.GetTokenType()!] = context.Token - }); + }) + { + ExpiresUtc = context.Principal.GetExpirationDate(), + IssuedUtc = context.Principal.GetCreationDate() + }; return new AuthenticationTicket((ClaimsIdentity) context.Principal.Identity, properties); } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs index 1835ecd8..262a6268 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs @@ -159,7 +159,12 @@ namespace OpenIddict.Validation.AspNetCore // Store the token to allow any ASP.NET Core component (e.g a controller) // to retrieve it (e.g to make an API request to another application). - var properties = new AuthenticationProperties(); + var properties = new AuthenticationProperties + { + ExpiresUtc = context.Principal.GetExpirationDate(), + IssuedUtc = context.Principal.GetCreationDate() + }; + properties.StoreTokens(new[] { new AuthenticationToken diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs index 67c3885b..497cedff 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs @@ -173,7 +173,11 @@ namespace OpenIddict.Validation.Owin var properties = new AuthenticationProperties(new Dictionary { [context.Principal.GetTokenType()!] = context.Token - }); + }) + { + ExpiresUtc = context.Principal.GetExpirationDate(), + IssuedUtc = context.Principal.GetCreationDate() + }; return new AuthenticationTicket((ClaimsIdentity) context.Principal.Identity, properties); } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index e4c44603..1b2d9133 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs @@ -26,6 +26,7 @@ using Xunit.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers; using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers; using SR = OpenIddict.Abstractions.OpenIddictResources; namespace OpenIddict.Server.AspNetCore.IntegrationTests @@ -37,6 +38,107 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests { } + [Fact] + public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); + } + + [Fact] + public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); + } + [Fact] public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() { @@ -601,11 +703,25 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests return; } + var claims = result.Principal.Claims.GroupBy(claim => claim.Type) + .Select(group => new KeyValuePair( + group.Key, group.Select(claim => claim.Value).ToArray())); + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); + return; + } + + else if (context.Request.Path == "/authenticate/properties") + { + var result = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (result?.Properties is null) + { + return; + } + context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize( - new OpenIddictResponse(result.Principal.Claims.GroupBy(claim => claim.Type) - .Select(group => new KeyValuePair( - group.Key, group.Select(claim => claim.Value).ToArray()))))); + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Items))); return; } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index ad6b31e5..066d179e 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs @@ -23,6 +23,7 @@ using Xunit; using Xunit.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlers; using SR = OpenIddict.Abstractions.OpenIddictResources; @@ -35,6 +36,107 @@ namespace OpenIddict.Server.Owin.IntegrationTests { } + [Fact] + public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); + } + + [Fact] + public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(TokenTypeHints.AccessToken, context.TokenType); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); + } + [Fact] public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties() { @@ -423,11 +525,25 @@ namespace OpenIddict.Server.Owin.IntegrationTests return; } + var claims = result.Identity.Claims.GroupBy(claim => claim.Type) + .Select(group => new KeyValuePair( + group.Key, group.Select(claim => claim.Value).ToArray())); + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); + return; + } + + else if (context.Request.Path == new PathString("/authenticate/properties")) + { + var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); + if (result?.Properties is null) + { + return; + } + context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize( - new OpenIddictResponse(result.Identity.Claims.GroupBy(claim => claim.Type) - .Select(group => new KeyValuePair( - group.Key, group.Select(claim => claim.Value).ToArray()))!))); + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Dictionary))); return; }