21 changed files with 2723 additions and 16 deletions
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0;net472</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' "> |
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server.AspNetCore\OpenIddict.Server.AspNetCore.csproj" /> |
|||
<ProjectReference Include="..\OpenIddict.Server.IntegrationTests\OpenIddict.Server.IntegrationTests.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' "> |
|||
<Reference Include="System.Net.Http" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,58 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.FunctionalTests; |
|||
using Xunit; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Server.AspNetCore.FunctionalTests |
|||
{ |
|||
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests |
|||
{ |
|||
[Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] |
|||
public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() |
|||
{ |
|||
// Arrange
|
|||
var client = CreateClient(options => options.EnableDegradedMode()); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" |
|||
}); |
|||
|
|||
// Assert
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal("The 'request_id' parameter is not supported.", response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() |
|||
{ |
|||
// Arrange
|
|||
var client = CreateClient(options => |
|||
{ |
|||
options.Services.AddDistributedMemoryCache(); |
|||
|
|||
options.UseAspNetCore() |
|||
.EnableAuthorizationEndpointCaching(); |
|||
}); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" |
|||
}); |
|||
|
|||
// Assert
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal("The specified 'request_id' parameter is invalid.", response.ErrorDescription); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Security.Claims; |
|||
using System.Text.Json; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.TestHost; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.FunctionalTests; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Server.AspNetCore.FunctionalTests |
|||
{ |
|||
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests |
|||
{ |
|||
protected override OpenIddictServerIntegrationTestClient CreateClient(Action<OpenIddictServerBuilder> configuration = null) |
|||
{ |
|||
var builder = new WebHostBuilder(); |
|||
|
|||
builder.UseEnvironment("Testing"); |
|||
|
|||
builder.ConfigureServices(ConfigureServices); |
|||
builder.ConfigureServices(services => |
|||
{ |
|||
services.AddOpenIddict() |
|||
.AddServer(options => |
|||
{ |
|||
// Disable the transport security requirement during testing.
|
|||
options.UseAspNetCore() |
|||
.DisableTransportSecurityRequirement(); |
|||
|
|||
configuration?.Invoke(options); |
|||
}); |
|||
}); |
|||
|
|||
builder.Configure(app => |
|||
{ |
|||
app.Use(next => async context => |
|||
{ |
|||
await next(context); |
|||
|
|||
var feature = context.Features.Get<OpenIddictServerAspNetCoreFeature>(); |
|||
var response = feature?.Transaction.GetProperty<object>("custom_response"); |
|||
if (response != null) |
|||
{ |
|||
context.Response.ContentType = "application/json"; |
|||
await context.Response.WriteAsync(JsonSerializer.Serialize(response)); |
|||
} |
|||
}); |
|||
|
|||
app.UseAuthentication(); |
|||
|
|||
app.Use(next => context => |
|||
{ |
|||
if (context.Request.Path == "/invalid-signin") |
|||
{ |
|||
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); |
|||
|
|||
var principal = new ClaimsPrincipal(identity); |
|||
|
|||
return context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal); |
|||
} |
|||
|
|||
else if (context.Request.Path == "/invalid-signout") |
|||
{ |
|||
return context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
else if (context.Request.Path == "/invalid-challenge") |
|||
{ |
|||
return context.ChallengeAsync( |
|||
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
new AuthenticationProperties()); |
|||
} |
|||
|
|||
else if (context.Request.Path == "/invalid-authenticate") |
|||
{ |
|||
return context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
return next(context); |
|||
}); |
|||
|
|||
app.Run(context => |
|||
{ |
|||
context.Response.ContentType = "application/json"; |
|||
return context.Response.WriteAsync(JsonSerializer.Serialize(new |
|||
{ |
|||
name = "Bob le Magnifique" |
|||
})); |
|||
}); |
|||
}); |
|||
|
|||
var server = new TestServer(builder); |
|||
return new OpenIddictServerIntegrationTestClient(server.CreateClient()); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0;net472</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Certificate.cer" /> |
|||
<EmbeddedResource Include="Certificate.pfx" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server\OpenIddict.Server.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="AngleSharp" Version="$(AngleSharpVersion)" /> |
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="Moq" Version="$(MoqVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' "> |
|||
<Reference Include="System.Net.Http" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,458 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using AngleSharp.Html.Parser; |
|||
using Microsoft.Extensions.Primitives; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace OpenIddict.Server.FunctionalTests |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes methods that allow sending OpenID Connect
|
|||
/// requests and extracting the corresponding responses.
|
|||
/// </summary>
|
|||
public class OpenIddictServerIntegrationTestClient |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the OpenID Connect client.
|
|||
/// </summary>
|
|||
public OpenIddictServerIntegrationTestClient() |
|||
: this(new HttpClient()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the OpenID Connect client.
|
|||
/// </summary>
|
|||
/// <param name="client">The HTTP client used to communicate with the OpenID Connect server.</param>
|
|||
public OpenIddictServerIntegrationTestClient(HttpClient client) |
|||
: this(client, new HtmlParser()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the OpenID Connect client.
|
|||
/// </summary>
|
|||
/// <param name="client">The HTTP client used to communicate with the OpenID Connect server.</param>
|
|||
/// <param name="parser">The HTML parser used to parse the responses returned by the OpenID Connect server.</param>
|
|||
public OpenIddictServerIntegrationTestClient(HttpClient client, HtmlParser parser) |
|||
{ |
|||
if (client == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(client)); |
|||
} |
|||
|
|||
if (parser == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(parser)); |
|||
} |
|||
|
|||
HttpClient = client; |
|||
HtmlParser = parser; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying HTTP client used to
|
|||
/// communicate with the OpenID Connect server.
|
|||
/// </summary>
|
|||
public HttpClient HttpClient { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying HTML parser used to parse the
|
|||
/// responses returned by the OpenID Connect server.
|
|||
/// </summary>
|
|||
public HtmlParser HtmlParser { get; } |
|||
|
|||
/// <summary>
|
|||
/// Sends an empty OpenID Connect request to the given endpoint using GET
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> GetAsync(string uri) |
|||
=> GetAsync(uri, new OpenIddictRequest()); |
|||
|
|||
/// <summary>
|
|||
/// Sends an empty OpenID Connect request to the given endpoint using GET
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> GetAsync(Uri uri) |
|||
=> GetAsync(uri, new OpenIddictRequest()); |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint using GET
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> GetAsync(string uri, OpenIddictRequest request) |
|||
{ |
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(uri)) |
|||
{ |
|||
throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); |
|||
} |
|||
|
|||
return GetAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint using GET
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> GetAsync(Uri uri, OpenIddictRequest request) |
|||
=> SendAsync(HttpMethod.Get, uri, request); |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint using POST
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> PostAsync(string uri, OpenIddictRequest request) |
|||
{ |
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(uri)) |
|||
{ |
|||
throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); |
|||
} |
|||
|
|||
return PostAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint using POST
|
|||
/// and converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> PostAsync(Uri uri, OpenIddictRequest request) |
|||
=> SendAsync(HttpMethod.Post, uri, request); |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint and
|
|||
/// converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="method">The HTTP method used to send the OpenID Connect request.</param>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> SendAsync(string method, string uri, OpenIddictRequest request) |
|||
{ |
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(method)) |
|||
{ |
|||
throw new ArgumentException("The HTTP method cannot be null or empty.", nameof(method)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(uri)) |
|||
{ |
|||
throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); |
|||
} |
|||
|
|||
return SendAsync(new HttpMethod(method), uri, request); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint and
|
|||
/// converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="method">The HTTP method used to send the OpenID Connect request.</param>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public Task<OpenIddictResponse> SendAsync(HttpMethod method, string uri, OpenIddictRequest request) |
|||
{ |
|||
if (method == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(method)); |
|||
} |
|||
|
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(uri)) |
|||
{ |
|||
throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); |
|||
} |
|||
|
|||
return SendAsync(method, new Uri(uri, UriKind.RelativeOrAbsolute), request); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sends a generic OpenID Connect request to the given endpoint and
|
|||
/// converts the returned response to an OpenID Connect response.
|
|||
/// </summary>
|
|||
/// <param name="method">The HTTP method used to send the OpenID Connect request.</param>
|
|||
/// <param name="uri">The endpoint to which the request is sent.</param>
|
|||
/// <param name="request">The OpenID Connect request to send.</param>
|
|||
/// <returns>The OpenID Connect response returned by the server.</returns>
|
|||
public virtual async Task<OpenIddictResponse> SendAsync(HttpMethod method, Uri uri, OpenIddictRequest request) |
|||
{ |
|||
if (method == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(method)); |
|||
} |
|||
|
|||
if (uri == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(uri)); |
|||
} |
|||
|
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (HttpClient.BaseAddress == null && !uri.IsAbsoluteUri) |
|||
{ |
|||
throw new ArgumentException("The address cannot be a relative URI when no base address " + |
|||
"is associated with the HTTP client.", nameof(uri)); |
|||
} |
|||
|
|||
return await GetResponseAsync(await HttpClient.SendAsync(CreateRequestMessage(request, method, uri))); |
|||
} |
|||
|
|||
private HttpRequestMessage CreateRequestMessage(OpenIddictRequest request, HttpMethod method, Uri uri) |
|||
{ |
|||
// Note: a dictionary is deliberately not used here to allow multiple parameters with the
|
|||
// same name to be specified. While initially not allowed by the core OAuth2 specification,
|
|||
// this is required for derived drafts like the OAuth2 token exchange specification.
|
|||
var parameters = new List<KeyValuePair<string, string>>(); |
|||
|
|||
foreach (var parameter in request.GetParameters()) |
|||
{ |
|||
// If the parameter is null or empty, send an empty value.
|
|||
if (OpenIddictParameter.IsNullOrEmpty(parameter.Value)) |
|||
{ |
|||
parameters.Add(new KeyValuePair<string, string>(parameter.Key, string.Empty)); |
|||
|
|||
continue; |
|||
} |
|||
|
|||
var values = (string[]) parameter.Value; |
|||
if (values == null || values.Length == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var value in values) |
|||
{ |
|||
parameters.Add(new KeyValuePair<string, string>(parameter.Key, value)); |
|||
} |
|||
} |
|||
|
|||
if (method == HttpMethod.Get && parameters.Count != 0) |
|||
{ |
|||
var builder = new StringBuilder(); |
|||
|
|||
foreach (var parameter in parameters) |
|||
{ |
|||
if (builder.Length != 0) |
|||
{ |
|||
builder.Append('&'); |
|||
} |
|||
|
|||
builder.Append(UrlEncoder.Default.Encode(parameter.Key)); |
|||
builder.Append('='); |
|||
builder.Append(UrlEncoder.Default.Encode(parameter.Value)); |
|||
} |
|||
|
|||
if (!uri.IsAbsoluteUri) |
|||
{ |
|||
uri = new Uri(HttpClient.BaseAddress, uri); |
|||
} |
|||
|
|||
uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; |
|||
} |
|||
|
|||
var message = new HttpRequestMessage(method, uri); |
|||
|
|||
if (method != HttpMethod.Get) |
|||
{ |
|||
message.Content = new FormUrlEncodedContent(parameters); |
|||
} |
|||
|
|||
return message; |
|||
} |
|||
|
|||
private async Task<OpenIddictResponse> GetResponseAsync(HttpResponseMessage message) |
|||
{ |
|||
if (message.Headers.Location != null) |
|||
{ |
|||
var payload = message.Headers.Location.Fragment; |
|||
if (string.IsNullOrEmpty(payload)) |
|||
{ |
|||
payload = message.Headers.Location.Query; |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(payload)) |
|||
{ |
|||
return new OpenIddictResponse(); |
|||
} |
|||
|
|||
string UnescapeDataString(string value) |
|||
{ |
|||
if (string.IsNullOrEmpty(value)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return Uri.UnescapeDataString(value.Replace("+", "%20")); |
|||
} |
|||
|
|||
// Note: a dictionary is deliberately not used here to allow multiple parameters with the
|
|||
// same name to be retrieved. While initially not allowed by the core OAuth2 specification,
|
|||
// this is required for derived drafts like the OAuth2 token exchange specification.
|
|||
var parameters = new List<KeyValuePair<string, string>>(); |
|||
|
|||
foreach (var element in new StringTokenizer(payload, OpenIddictConstants.Separators.Ampersand)) |
|||
{ |
|||
var segment = element; |
|||
if (segment.Length == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Always skip the first char (# or ?).
|
|||
if (segment.Offset == 0) |
|||
{ |
|||
segment = segment.Subsegment(1, segment.Length - 1); |
|||
} |
|||
|
|||
var index = segment.IndexOf('='); |
|||
if (index == -1) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var name = UnescapeDataString(segment.Substring(0, index)); |
|||
if (string.IsNullOrEmpty(name)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var value = UnescapeDataString(segment.Substring(index + 1, segment.Length - (index + 1))); |
|||
|
|||
parameters.Add(new KeyValuePair<string, string>(name, value)); |
|||
} |
|||
|
|||
return new OpenIddictResponse( |
|||
from parameter in parameters |
|||
group parameter by parameter.Key into grouping |
|||
let values = grouping.Select(parameter => parameter.Value) |
|||
select new KeyValuePair<string, StringValues>(grouping.Key, values.ToArray())); |
|||
} |
|||
|
|||
else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
using var stream = await message.Content.ReadAsStreamAsync(); |
|||
|
|||
return await JsonSerializer.DeserializeAsync<OpenIddictResponse>(stream); |
|||
} |
|||
|
|||
else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/html", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
using var stream = await message.Content.ReadAsStreamAsync(); |
|||
using var document = await HtmlParser.ParseDocumentAsync(stream); |
|||
|
|||
// Note: a dictionary is deliberately not used here to allow multiple parameters with the
|
|||
// same name to be retrieved. While initially not allowed by the core OAuth2 specification,
|
|||
// this is required for derived drafts like the OAuth2 token exchange specification.
|
|||
var parameters = new List<KeyValuePair<string, string>>(); |
|||
|
|||
foreach (var element in document.Body.GetElementsByTagName("input")) |
|||
{ |
|||
var name = element.GetAttribute("name"); |
|||
if (string.IsNullOrEmpty(name)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var value = element.GetAttribute("value"); |
|||
|
|||
parameters.Add(new KeyValuePair<string, string>(name, value)); |
|||
} |
|||
|
|||
return new OpenIddictResponse( |
|||
from parameter in parameters |
|||
group parameter by parameter.Key into grouping |
|||
let values = grouping.Select(parameter => parameter.Value) |
|||
select new KeyValuePair<string, StringValues>(grouping.Key, values.ToArray())); |
|||
} |
|||
|
|||
else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/plain", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
using (var stream = await message.Content.ReadAsStreamAsync()) |
|||
using (var reader = new StreamReader(stream)) |
|||
{ |
|||
// Note: a dictionary is deliberately not used here to allow multiple parameters with the
|
|||
// same name to be retrieved. While initially not allowed by the core OAuth2 specification,
|
|||
// this is required for derived drafts like the OAuth2 token exchange specification.
|
|||
var parameters = new List<KeyValuePair<string, string>>(); |
|||
|
|||
for (var line = await reader.ReadLineAsync(); line != null; line = await reader.ReadLineAsync()) |
|||
{ |
|||
var index = line.IndexOf(':'); |
|||
if (index == -1) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var name = line.Substring(0, index); |
|||
if (string.IsNullOrEmpty(name)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var value = line.Substring(index + 1); |
|||
|
|||
parameters.Add(new KeyValuePair<string, string>(name, value)); |
|||
} |
|||
|
|||
return new OpenIddictResponse( |
|||
from parameter in parameters |
|||
group parameter by parameter.Key into grouping |
|||
let values = grouping.Select(parameter => parameter.Value) |
|||
select new KeyValuePair<string, StringValues>(grouping.Key, values.ToArray())); |
|||
} |
|||
} |
|||
|
|||
return new OpenIddictResponse(); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,170 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Security.Claims; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Moq; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Core; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
|
|||
namespace OpenIddict.Server.FunctionalTests |
|||
{ |
|||
public abstract partial class OpenIddictServerIntegrationTests |
|||
{ |
|||
protected virtual void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddOpenIddict() |
|||
.AddCore(options => |
|||
{ |
|||
options.SetDefaultApplicationEntity<OpenIddictApplication>() |
|||
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>() |
|||
.SetDefaultScopeEntity<OpenIddictScope>() |
|||
.SetDefaultTokenEntity<OpenIddictToken>(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager()) |
|||
.AddSingleton(CreateAuthorizationManager()) |
|||
.AddSingleton(CreateScopeManager()) |
|||
.AddSingleton(CreateTokenManager()); |
|||
}) |
|||
|
|||
.AddServer(options => |
|||
{ |
|||
// Enable the tested endpoints.
|
|||
options.SetAuthorizationEndpointUris("/connect/authorize") |
|||
.SetConfigurationEndpointUris("/.well-known/openid-configuration") |
|||
.SetCryptographyEndpointUris("/.well-known/jwks") |
|||
.SetIntrospectionEndpointUris("/connect/introspect") |
|||
.SetLogoutEndpointUris("/connect/logout") |
|||
.SetRevocationEndpointUris("/connect/revoke") |
|||
.SetTokenEndpointUris("/connect/token") |
|||
.SetUserinfoEndpointUris("/connect/userinfo"); |
|||
|
|||
options.AllowAuthorizationCodeFlow() |
|||
.AllowClientCredentialsFlow() |
|||
.AllowImplicitFlow() |
|||
.AllowPasswordFlow() |
|||
.AllowRefreshTokenFlow(); |
|||
|
|||
// Accept anonymous clients by default.
|
|||
options.AcceptAnonymousClients(); |
|||
|
|||
// Disable permission enforcement by default.
|
|||
options.IgnoreEndpointPermissions() |
|||
.IgnoreGrantTypePermissions() |
|||
.IgnoreScopePermissions(); |
|||
|
|||
options.AddSigningCertificate( |
|||
assembly: typeof(OpenIddictServerIntegrationTests).Assembly, |
|||
resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", |
|||
password: "Owin.Security.OpenIdConnect.Server"); |
|||
|
|||
options.AddEncryptionCertificate( |
|||
assembly: typeof(OpenIddictServerIntegrationTests).Assembly, |
|||
resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", |
|||
password: "Owin.Security.OpenIdConnect.Server"); |
|||
|
|||
options.AddEventHandler<ValidateAuthorizationRequestContext>(builder => |
|||
builder.UseInlineHandler(context => default)); |
|||
|
|||
options.AddEventHandler<ValidateIntrospectionRequestContext>(builder => |
|||
builder.UseInlineHandler(context => default)); |
|||
|
|||
options.AddEventHandler<ValidateLogoutRequestContext>(builder => |
|||
builder.UseInlineHandler(context => default)); |
|||
|
|||
options.AddEventHandler<ValidateRevocationRequestContext>(builder => |
|||
builder.UseInlineHandler(context => default)); |
|||
|
|||
options.AddEventHandler<ValidateTokenRequestContext>(builder => |
|||
builder.UseInlineHandler(context => default)); |
|||
|
|||
options.AddEventHandler<HandleAuthorizationRequestContext>(builder => |
|||
{ |
|||
builder.UseInlineHandler(context => |
|||
{ |
|||
var identity = new ClaimsIdentity("Bearer"); |
|||
identity.AddClaim(Claims.Subject, "Bob le Magnifique"); |
|||
|
|||
context.Principal = new ClaimsPrincipal(identity); |
|||
context.HandleAuthentication(); |
|||
|
|||
return default; |
|||
}); |
|||
|
|||
builder.SetOrder(int.MaxValue); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
protected abstract OpenIddictServerIntegrationTestClient CreateClient(Action<OpenIddictServerBuilder> configuration = null); |
|||
|
|||
protected OpenIddictApplicationManager<OpenIddictApplication> CreateApplicationManager( |
|||
Action<Mock<OpenIddictApplicationManager<OpenIddictApplication>>> configuration = null) |
|||
{ |
|||
var manager = new Mock<OpenIddictApplicationManager<OpenIddictApplication>>( |
|||
Mock.Of<IOpenIddictApplicationCache<OpenIddictApplication>>(), |
|||
Mock.Of<IOpenIddictApplicationStoreResolver>(), |
|||
Mock.Of<ILogger<OpenIddictApplicationManager<OpenIddictApplication>>>(), |
|||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|||
|
|||
configuration?.Invoke(manager); |
|||
|
|||
return manager.Object; |
|||
} |
|||
|
|||
protected OpenIddictAuthorizationManager<OpenIddictAuthorization> CreateAuthorizationManager( |
|||
Action<Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>> configuration = null) |
|||
{ |
|||
var manager = new Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>( |
|||
Mock.Of<IOpenIddictAuthorizationCache<OpenIddictAuthorization>>(), |
|||
Mock.Of<IOpenIddictAuthorizationStoreResolver>(), |
|||
Mock.Of<ILogger<OpenIddictAuthorizationManager<OpenIddictAuthorization>>>(), |
|||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|||
|
|||
configuration?.Invoke(manager); |
|||
|
|||
return manager.Object; |
|||
} |
|||
|
|||
protected OpenIddictScopeManager<OpenIddictScope> CreateScopeManager( |
|||
Action<Mock<OpenIddictScopeManager<OpenIddictScope>>> configuration = null) |
|||
{ |
|||
var manager = new Mock<OpenIddictScopeManager<OpenIddictScope>>( |
|||
Mock.Of<IOpenIddictScopeCache<OpenIddictScope>>(), |
|||
Mock.Of<IOpenIddictScopeStoreResolver>(), |
|||
Mock.Of<ILogger<OpenIddictScopeManager<OpenIddictScope>>>(), |
|||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|||
|
|||
configuration?.Invoke(manager); |
|||
|
|||
return manager.Object; |
|||
} |
|||
|
|||
protected OpenIddictTokenManager<OpenIddictToken> CreateTokenManager( |
|||
Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> configuration = null) |
|||
{ |
|||
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>( |
|||
Mock.Of<IOpenIddictTokenCache<OpenIddictToken>>(), |
|||
Mock.Of<IOpenIddictTokenStoreResolver>(), |
|||
Mock.Of<ILogger<OpenIddictTokenManager<OpenIddictToken>>>(), |
|||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|||
|
|||
configuration?.Invoke(manager); |
|||
|
|||
return manager.Object; |
|||
} |
|||
|
|||
public class OpenIddictApplication { } |
|||
public class OpenIddictAuthorization { } |
|||
public class OpenIddictScope { } |
|||
public class OpenIddictToken { } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net472</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server.Owin\OpenIddict.Server.Owin.csproj" /> |
|||
<ProjectReference Include="..\OpenIddict.Server.IntegrationTests\OpenIddict.Server.IntegrationTests.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Owin.Testing" Version="$(OwinVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' "> |
|||
<Reference Include="System.Net.Http" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,58 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.FunctionalTests; |
|||
using Xunit; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Server.Owin.FunctionalTests |
|||
{ |
|||
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests |
|||
{ |
|||
[Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] |
|||
public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() |
|||
{ |
|||
// Arrange
|
|||
var client = CreateClient(options => options.EnableDegradedMode()); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" |
|||
}); |
|||
|
|||
// Assert
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal("The 'request_id' parameter is not supported.", response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() |
|||
{ |
|||
// Arrange
|
|||
var client = CreateClient(options => |
|||
{ |
|||
options.Services.AddDistributedMemoryCache(); |
|||
|
|||
options.UseOwin() |
|||
.EnableAuthorizationEndpointCaching(); |
|||
}); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" |
|||
}); |
|||
|
|||
// Assert
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal("The specified 'request_id' parameter is invalid.", response.ErrorDescription); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Security.Claims; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Owin; |
|||
using Microsoft.Owin.Testing; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.FunctionalTests; |
|||
using Owin; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Server.Owin.FunctionalTests |
|||
{ |
|||
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests |
|||
{ |
|||
protected override OpenIddictServerIntegrationTestClient CreateClient(Action<OpenIddictServerBuilder> configuration = null) |
|||
{ |
|||
var services = new ServiceCollection(); |
|||
ConfigureServices(services); |
|||
|
|||
services.AddOpenIddict() |
|||
.AddServer(options => |
|||
{ |
|||
// Disable the transport security requirement during testing.
|
|||
options.UseOwin() |
|||
.DisableTransportSecurityRequirement(); |
|||
|
|||
configuration?.Invoke(options); |
|||
}); |
|||
|
|||
var provider = services.BuildServiceProvider(); |
|||
|
|||
var server = TestServer.Create(app => |
|||
{ |
|||
app.Use(async (context, next) => |
|||
{ |
|||
using var scope = provider.CreateScope(); |
|||
|
|||
context.Set(typeof(IServiceProvider).FullName, scope.ServiceProvider); |
|||
|
|||
try |
|||
{ |
|||
await next(); |
|||
} |
|||
|
|||
finally |
|||
{ |
|||
context.Environment.Remove(typeof(IServiceProvider).FullName); |
|||
} |
|||
}); |
|||
|
|||
app.Use(async (context, next) => |
|||
{ |
|||
await next(); |
|||
|
|||
var transaction = context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName); |
|||
var response = transaction?.GetProperty<object>("custom_response"); |
|||
if (response != null) |
|||
{ |
|||
context.Response.ContentType = "application/json"; |
|||
await context.Response.WriteAsync(JsonSerializer.Serialize(response)); |
|||
} |
|||
}); |
|||
|
|||
app.UseOpenIddictServer(); |
|||
|
|||
app.Use((context, next) => |
|||
{ |
|||
if (context.Request.Path == new PathString("/invalid-signin")) |
|||
{ |
|||
var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType); |
|||
identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); |
|||
|
|||
context.Authentication.SignIn(identity); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
else if (context.Request.Path == new PathString("/invalid-signout")) |
|||
{ |
|||
context.Authentication.SignOut(OpenIddictServerOwinDefaults.AuthenticationType); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
else if (context.Request.Path == new PathString("/invalid-challenge")) |
|||
{ |
|||
context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
else if (context.Request.Path == new PathString("/invalid-authenticate")) |
|||
{ |
|||
return context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); |
|||
} |
|||
|
|||
return next(); |
|||
}); |
|||
|
|||
app.Run(context => |
|||
{ |
|||
context.Response.ContentType = "application/json"; |
|||
return context.Response.WriteAsync(JsonSerializer.Serialize(new |
|||
{ |
|||
name = "Bob le Magnifique" |
|||
})); |
|||
}); |
|||
}); |
|||
|
|||
return new OpenIddictServerIntegrationTestClient(server.HttpClient); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue