Browse Source

Allow returning custom challenge/sign-in/sign-out parameters via AuthenticationProperties.Parameters

pull/925/head
Kévin Chalet 6 years ago
parent
commit
fd63da76df
  1. 1
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  2. 3
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  3. 75
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  4. 2
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  5. 2
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  6. 2
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  7. 201
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs
  8. 32
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
  9. 34
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

1
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

3
src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

@ -186,7 +186,8 @@ namespace OpenIddict.Abstractions
= new Dictionary<string, OpenIddictParameter>(StringComparer.Ordinal);
/// <summary>
/// Adds a parameter.
/// Adds a parameter. Note: if a parameter with the
/// same name was already added, this method has no effect.
/// </summary>
/// <param name="name">The parameter name.</param>
/// <param name="value">The parameter value.</param>

75
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -43,7 +43,18 @@ namespace OpenIddict.Server.AspNetCore
/*
* Challenge processing:
*/
AttachHostChallengeError.Descriptor)
AttachHostChallengeError.Descriptor,
AttachHostParameters<ProcessChallengeContext>.Descriptor,
/*
* Sign-in processing:
*/
AttachHostParameters<ProcessSignInContext>.Descriptor,
/*
* Sign-out processing:
*/
AttachHostParameters<ProcessSignOutContext>.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Device.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
@ -328,6 +339,66 @@ namespace OpenIddict.Server.AspNetCore
}
}
/// <summary>
/// Contains the logic responsible of attaching custom parameters stored in the ASP.NET Core authentication properties.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class AttachHostParameters<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<AttachHostParameters<TContext>>()
.SetOrder(int.MaxValue - 150_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName);
if (properties == null)
{
return default;
}
foreach (var parameter in properties.Parameters)
{
// Note: AddParameter() is used to ensure existing parameters are not overriden.
context.Response.AddParameter(parameter.Key, parameter.Value switch
{
OpenIddictParameter value => value,
JsonElement value => value,
bool value => value,
int value => value,
long value => value,
string value => value,
string[] value => value,
_ => throw new InvalidOperationException(new StringBuilder()
.Append("Only strings, booleans, integers, arrays of strings and instances of type ")
.Append("'OpenIddictParameter' or 'JsonElement' can be returned as custom parameters.")
.ToString())
});
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of extracting OpenID Connect requests from GET HTTP requests.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
@ -801,7 +872,7 @@ namespace OpenIddict.Server.AspNetCore
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP response code header.
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class AttachHttpResponseCode<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext

2
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -804,7 +804,7 @@ namespace OpenIddict.Server.Owin
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP response code header.
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachHttpResponseCode<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext

2
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs

@ -277,7 +277,7 @@ namespace OpenIddict.Validation.AspNetCore
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP response code header.
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class AttachHttpResponseCode<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext

2
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs

@ -279,7 +279,7 @@ namespace OpenIddict.Validation.Owin
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP response code header.
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachHttpResponseCode<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext

201
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

@ -27,6 +27,77 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
{
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests
{
[Fact]
public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.True((bool) response["boolean_parameter"]);
Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind);
Assert.Equal(42, (long) response["integer_parameter"]);
Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind);
Assert.Equal("Bob l'Eponge", (string) response["string_parameter"]);
Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind);
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]) response["array_parameter"]);
Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind);
Assert.Equal("value", (string) response["object_parameter"]?["parameter"]);
Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind);
}
[Fact]
public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal("custom_error", response.Error);
Assert.Equal("custom_error_description", response.ErrorDescription);
Assert.Equal("custom_error_uri", response.ErrorUri);
}
[Theory]
[InlineData("/", OpenIddictServerEndpointType.Unknown)]
[InlineData("/connect", OpenIddictServerEndpointType.Unknown)]
@ -165,7 +236,7 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
[InlineData("/connect/revoke")]
[InlineData("/connect/token")]
[InlineData("/connect/userinfo")]
public async Task HandleRequestAsync_RejectsInsecureHttpRequests(string address)
public async Task ProcessRequest_RejectsInsecureHttpRequests(string address)
{
// Arrange
var client = CreateClient(options =>
@ -255,6 +326,76 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
Assert.Equal("Bob le Magnifique", (string) response["name"]);
}
[Fact]
public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/signin/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/signin/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.True((bool) response["boolean_parameter"]);
Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind);
Assert.Equal(42, (long) response["integer_parameter"]);
Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind);
Assert.Equal("Bob l'Eponge", (string) response["string_parameter"]);
Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind);
Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]) response["array_parameter"]);
Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind);
Assert.Equal("value", (string) response["object_parameter"]?["parameter"]);
Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind);
}
[Fact]
public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetLogoutEndpointUris("/signout/custom");
options.AddEventHandler<HandleLogoutRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/signout/custom", new OpenIddictRequest
{
PostLogoutRedirectUri = "http://www.fabrikam.com/path",
State = "af0ifjsldkj"
});
// Assert
Assert.True((bool) response["boolean_parameter"]);
Assert.Equal(42, (long) response["integer_parameter"]);
Assert.Equal("Bob l'Eponge", (string) response["string_parameter"]);
}
protected override OpenIddictServerIntegrationTestClient CreateClient(Action<OpenIddictServerBuilder> configuration = null)
{
var builder = new WebHostBuilder();
@ -305,12 +446,49 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
return;
}
else if (context.Request.Path == "/signin/custom")
{
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(Claims.Subject, "Bob le Bricoleur");
var principal = new ClaimsPrincipal(identity);
var properties = new AuthenticationProperties(
items: new Dictionary<string, string>(),
parameters: new Dictionary<string, object>
{
["boolean_parameter"] = true,
["integer_parameter"] = 42,
["string_parameter"] = "Bob l'Eponge",
["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
});
await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal, properties);
return;
}
else if (context.Request.Path == "/signout")
{
await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return;
}
else if (context.Request.Path == "/signout/custom")
{
var properties = new AuthenticationProperties(
items: new Dictionary<string, string>(),
parameters: new Dictionary<string, object>
{
["boolean_parameter"] = true,
["integer_parameter"] = 42,
["string_parameter"] = "Bob l'Eponge"
});
await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties);
return;
}
else if (context.Request.Path == "/challenge")
{
await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
@ -319,12 +497,21 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
else if (context.Request.Path == "/challenge/custom")
{
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri"
});
var properties = new AuthenticationProperties(
items: new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description",
[OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri"
},
parameters: new Dictionary<string, object>
{
["boolean_parameter"] = true,
["integer_parameter"] = 42,
["string_parameter"] = "Bob l'Eponge",
["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
});
await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties);
return;

32
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

@ -931,38 +931,6 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Null(response.ErrorUri);
}
[Fact]
public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal("custom_error", response.Error);
Assert.Equal("custom_error_description", response.ErrorDescription);
Assert.Equal("custom_error_uri", response.ErrorUri);
}
[Theory]
[InlineData("custom_error", null, null)]
[InlineData("custom_error", "custom_description", null)]

34
test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

@ -26,6 +26,38 @@ namespace OpenIddict.Server.Owin.FunctionalTests
{
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests
{
[Fact]
public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/challenge/custom");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal("custom_error", response.Error);
Assert.Equal("custom_error_description", response.ErrorDescription);
Assert.Equal("custom_error_uri", response.ErrorUri);
}
[Theory]
[InlineData("/", OpenIddictServerEndpointType.Unknown)]
[InlineData("/connect", OpenIddictServerEndpointType.Unknown)]
@ -164,7 +196,7 @@ namespace OpenIddict.Server.Owin.FunctionalTests
[InlineData("/connect/revoke")]
[InlineData("/connect/token")]
[InlineData("/connect/userinfo")]
public async Task HandleRequestAsync_RejectsInsecureHttpRequests(string address)
public async Task ProcessRequest_RejectsInsecureHttpRequests(string address)
{
// Arrange
var client = CreateClient(options =>

Loading…
Cancel
Save