Browse Source
Add a new OpenIddict.Server.SystemNetHttp.Tests project with 23 unit tests covering the CIMD application manager, context, and handler filter. Add 8 integration tests in the base server integration tests for ValidateClientId CIMD branching and discovery metadata advertisement.pull/2416/head
6 changed files with 856 additions and 0 deletions
@ -0,0 +1,250 @@ |
|||
/* |
|||
* 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 Microsoft.Extensions.DependencyInjection; |
|||
using Moq; |
|||
using Xunit; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
using static OpenIddict.Server.OpenIddictServerHandlers; |
|||
|
|||
namespace OpenIddict.Server.IntegrationTests; |
|||
|
|||
public abstract partial class OpenIddictServerIntegrationTests |
|||
{ |
|||
[Fact] |
|||
public async Task HandleConfigurationRequest_AdvertisesCimdSupport_WhenEnabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.GetAsync("/.well-known/openid-configuration"); |
|||
|
|||
// Assert
|
|||
Assert.True((bool?) response[Metadata.ClientIdMetadataDocumentSupported]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task HandleConfigurationRequest_DoesNotAdvertiseCimdSupport_WhenDisabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(); |
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.GetAsync("/.well-known/openid-configuration"); |
|||
|
|||
// Assert
|
|||
Assert.Null(response[Metadata.ClientIdMetadataDocumentSupported]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_RejectsUrlClientId_WhenCimdDisabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "https://example.com/client", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert — CIMD is disabled, so URL client_id is just treated as unknown client
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_SetsTransactionFlag_WhenCimdEnabledAndClientIdIsHttpsUrl() |
|||
{ |
|||
// Arrange
|
|||
var flagWasSet = false; |
|||
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
|
|||
// Add an inline handler that runs after ValidateClientId to inspect the transaction flag.
|
|||
options.AddEventHandler<ProcessAuthenticationContext>(builder => |
|||
{ |
|||
builder.UseInlineHandler(context => |
|||
{ |
|||
if (context.Transaction.Properties.TryGetValue( |
|||
".ClientIdMetadataDocumentFetchRequired", out var value) && |
|||
value is true) |
|||
{ |
|||
flagWasSet = true; |
|||
} |
|||
|
|||
// Reject to stop further processing (we don't have CIMD HTTP infrastructure here).
|
|||
context.Reject( |
|||
error: Errors.InvalidClient, |
|||
description: "Test completed."); |
|||
|
|||
return ValueTask.CompletedTask; |
|||
}); |
|||
|
|||
builder.SetOrder(ValidateClientId.Descriptor.Order + 1); |
|||
}); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "https://example.com/client", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert
|
|||
Assert.True(flagWasSet); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_RejectsHttpUrl_WhenCimdEnabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("http://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "http://example.com/client", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert — HTTP URL should be rejected even with CIMD enabled (requires HTTPS)
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_RejectsUrlWithFragment_WhenCimdEnabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("https://example.com/client#fragment", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "https://example.com/client#fragment", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert — URL with fragment should be rejected
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_RejectsUrlWithUserInfo_WhenCimdEnabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("https://user:pass@example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "https://user:pass@example.com/client", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert — URL with userinfo should be rejected
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ValidateAuthorizationRequest_RejectsRootPathUrl_WhenCimdEnabled() |
|||
{ |
|||
// Arrange
|
|||
await using var server = await CreateServerAsync(options => |
|||
{ |
|||
options.EnableClientIdMetadataDocumentSupport(); |
|||
|
|||
options.Services.AddSingleton(CreateApplicationManager(mock => |
|||
{ |
|||
mock.Setup(manager => manager.FindByClientIdAsync("https://example.com/", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((OpenIddictApplication?) null); |
|||
})); |
|||
}); |
|||
|
|||
await using var client = await server.CreateClientAsync(); |
|||
|
|||
// Act
|
|||
var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest |
|||
{ |
|||
ClientId = "https://example.com/", |
|||
RedirectUri = "http://www.fabrikam.com/path", |
|||
ResponseType = ResponseTypes.Code |
|||
}); |
|||
|
|||
// Assert — Root path URL should be rejected
|
|||
Assert.Equal(Errors.InvalidRequest, response.Error); |
|||
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>net472;net48;$(NetCoreTargetFrameworks)</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="MartinCostello.Logging.XUnit" /> |
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" /> |
|||
<PackageReference Include="Moq" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server.SystemNetHttp\OpenIddict.Server.SystemNetHttp.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' "> |
|||
<Reference Include="System.Net.Http" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Using Include="OpenIddict.Abstractions" /> |
|||
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" /> |
|||
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,468 @@ |
|||
/* |
|||
* 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.Collections.Immutable; |
|||
using System.Text.Json; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Moq; |
|||
using OpenIddict.Core; |
|||
using OpenIddict.Server.SystemNetHttp; |
|||
using Xunit; |
|||
|
|||
namespace OpenIddict.Server.SystemNetHttp.Tests; |
|||
|
|||
public class OpenIddictServerSystemNetHttpApplicationManagerTests |
|||
{ |
|||
[Fact] |
|||
public async Task FindByClientIdAsync_ReturnsBaseResult_WhenPreRegisteredClientExists() |
|||
{ |
|||
// Arrange
|
|||
var application = new TestApplication(); |
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync(application); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext(); |
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("Fabrikam"); |
|||
|
|||
// Assert
|
|||
Assert.Same(application, result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_ReturnsNull_WhenNoCimdContext() |
|||
{ |
|||
// Arrange
|
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((TestApplication?) null); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext(); |
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.Null(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_ReturnsNull_WhenCimdClientIdDoesNotMatch() |
|||
{ |
|||
// Arrange
|
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("https://example.com/client-a", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((TestApplication?) null); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext |
|||
{ |
|||
ClientId = "https://example.com/client-b", |
|||
MetadataDocument = CreateMetadataDocument() |
|||
}; |
|||
|
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client-a"); |
|||
|
|||
// Assert
|
|||
Assert.Null(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizesVirtualApp_WhenCimdContextPopulated() |
|||
{ |
|||
// Arrange
|
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((TestApplication?) null); |
|||
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"client_id": "https://example.com/client", |
|||
"client_name": "Test Client" |
|||
} |
|||
""");
|
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext |
|||
{ |
|||
ClientId = "https://example.com/client", |
|||
MetadataDocument = document |
|||
}; |
|||
|
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetClientIdAsync(result, "https://example.com/client", It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_ReturnsCachedVirtualApp_OnSecondCall() |
|||
{ |
|||
// Arrange
|
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((TestApplication?) null); |
|||
|
|||
using var document = CreateMetadataDocument(); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext |
|||
{ |
|||
ClientId = "https://example.com/client", |
|||
MetadataDocument = document |
|||
}; |
|||
|
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var first = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
var second = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(first); |
|||
Assert.Same(first, second); |
|||
store.Verify(s => s.InstantiateAsync(It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectClientType() |
|||
{ |
|||
// Arrange
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetClientTypeAsync(result, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectApplicationType() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"application_type": "native" |
|||
} |
|||
""");
|
|||
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetApplicationTypeAsync(result, "native", It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_DefaultsToWebApplicationType() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("{}"); |
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetApplicationTypeAsync(result, ApplicationTypes.Web, It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectDisplayName() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"client_name": "My Cool App" |
|||
} |
|||
""");
|
|||
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetDisplayNameAsync(result, "My Cool App", It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectRedirectUris() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"redirect_uris": ["https://example.com/callback", "https://example.com/callback2"] |
|||
} |
|||
""");
|
|||
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetRedirectUrisAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(uris => |
|||
uris.Length == 2 && |
|||
uris[0] == "https://example.com/callback" && |
|||
uris[1] == "https://example.com/callback2"), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectGrantTypePermissions() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"grant_types": ["authorization_code", "refresh_token"] |
|||
} |
|||
""");
|
|||
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.GrantTypes.AuthorizationCode) && |
|||
perms.Contains(Permissions.GrantTypes.RefreshToken)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_DefaultsToAuthorizationCodeGrant() |
|||
{ |
|||
// Arrange — no grant_types in metadata
|
|||
using var document = CreateMetadataDocument("{}"); |
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.GrantTypes.AuthorizationCode)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasCorrectResponseTypePermissions() |
|||
{ |
|||
// Arrange
|
|||
using var document = CreateMetadataDocument("""
|
|||
{ |
|||
"response_types": ["code", "code id_token"] |
|||
} |
|||
""");
|
|||
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.ResponseTypes.Code) && |
|||
perms.Contains(Permissions.ResponseTypes.CodeIdToken)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_DefaultsToCodeResponseType() |
|||
{ |
|||
// Arrange — no response_types in metadata
|
|||
using var document = CreateMetadataDocument("{}"); |
|||
var (manager, store, _) = CreateManagerWithCimdContext(document); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.ResponseTypes.Code)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_RequiresPkce() |
|||
{ |
|||
// Arrange
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetRequirementsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(reqs => |
|||
reqs.Contains(Requirements.Features.ProofKeyForCodeExchange)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GetIdAsync_ReturnsNull_ForVirtualApplication() |
|||
{ |
|||
// Arrange
|
|||
var (manager, _, cimdContext) = CreateManagerWithCimdContext(); |
|||
|
|||
var virtualApp = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
Assert.NotNull(virtualApp); |
|||
|
|||
// Act
|
|||
var id = await manager.GetIdAsync(virtualApp); |
|||
|
|||
// Assert
|
|||
Assert.Null(id); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GetIdAsync_DelegatesToBase_ForNonVirtualApplication() |
|||
{ |
|||
// Arrange
|
|||
var regularApp = new TestApplication(); |
|||
var store = CreateStore(); |
|||
store.Setup(s => s.GetIdAsync(regularApp, It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync("some-id"); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext(); |
|||
var manager = CreateManager(store, cimdContext); |
|||
|
|||
// Act
|
|||
var id = await manager.GetIdAsync(regularApp); |
|||
|
|||
// Assert
|
|||
Assert.Equal("some-id", id); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasEndpointPermissions() |
|||
{ |
|||
// Arrange
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.Endpoints.Authorization) && |
|||
perms.Contains(Permissions.Endpoints.Token)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindByClientIdAsync_SynthesizedApp_HasScopePermissions() |
|||
{ |
|||
// Arrange
|
|||
var (manager, store, _) = CreateManagerWithCimdContext(); |
|||
|
|||
// Act
|
|||
var result = await manager.FindByClientIdAsync("https://example.com/client"); |
|||
|
|||
// Assert
|
|||
Assert.NotNull(result); |
|||
store.Verify(s => s.SetPermissionsAsync( |
|||
result, |
|||
It.Is<ImmutableArray<string>>(perms => |
|||
perms.Contains(Permissions.Scopes.Email) && |
|||
perms.Contains(Permissions.Scopes.Profile) && |
|||
perms.Contains(Permissions.Scopes.Address) && |
|||
perms.Contains(Permissions.Scopes.Phone) && |
|||
perms.Contains(Permissions.Scopes.Roles)), |
|||
It.IsAny<CancellationToken>()), Times.Once()); |
|||
} |
|||
|
|||
private static Mock<IOpenIddictApplicationStore<TestApplication>> CreateStore() |
|||
{ |
|||
var store = new Mock<IOpenIddictApplicationStore<TestApplication>>(); |
|||
store.Setup(s => s.InstantiateAsync(It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync(() => new TestApplication()); |
|||
return store; |
|||
} |
|||
|
|||
private static OpenIddictServerSystemNetHttpApplicationManager<TestApplication> CreateManager( |
|||
Mock<IOpenIddictApplicationStore<TestApplication>> store, |
|||
OpenIddictServerSystemNetHttpCimdContext cimdContext) |
|||
{ |
|||
var cache = Mock.Of<IOpenIddictApplicationCache<TestApplication>>(); |
|||
var logger = NullLogger<OpenIddictApplicationManager<TestApplication>>.Instance; |
|||
|
|||
var coreOptions = new OpenIddictCoreOptions |
|||
{ |
|||
DisableEntityCaching = true, |
|||
DisableAdditionalFiltering = true |
|||
}; |
|||
|
|||
var optionsMonitor = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>( |
|||
m => m.CurrentValue == coreOptions); |
|||
|
|||
return new OpenIddictServerSystemNetHttpApplicationManager<TestApplication>( |
|||
cache, logger, optionsMonitor, store.Object, cimdContext); |
|||
} |
|||
|
|||
private static (OpenIddictServerSystemNetHttpApplicationManager<TestApplication> Manager, |
|||
Mock<IOpenIddictApplicationStore<TestApplication>> Store, |
|||
OpenIddictServerSystemNetHttpCimdContext CimdContext) |
|||
CreateManagerWithCimdContext(JsonDocument? document = null) |
|||
{ |
|||
var store = CreateStore(); |
|||
store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny<CancellationToken>())) |
|||
.ReturnsAsync((TestApplication?) null); |
|||
|
|||
var cimdContext = new OpenIddictServerSystemNetHttpCimdContext |
|||
{ |
|||
ClientId = "https://example.com/client", |
|||
MetadataDocument = document ?? CreateMetadataDocument() |
|||
}; |
|||
|
|||
var manager = CreateManager(store, cimdContext); |
|||
return (manager, store, cimdContext); |
|||
} |
|||
|
|||
private static JsonDocument CreateMetadataDocument(string? json = null) |
|||
=> JsonDocument.Parse(json ?? """{"client_id": "https://example.com/client"}""");
|
|||
|
|||
public class TestApplication { } |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/* |
|||
* 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.Text.Json; |
|||
using OpenIddict.Server.SystemNetHttp; |
|||
using Xunit; |
|||
|
|||
namespace OpenIddict.Server.SystemNetHttp.Tests; |
|||
|
|||
public class OpenIddictServerSystemNetHttpCimdContextTests |
|||
{ |
|||
[Fact] |
|||
public void Properties_DefaultToNull() |
|||
{ |
|||
// Arrange
|
|||
var context = new OpenIddictServerSystemNetHttpCimdContext(); |
|||
|
|||
// Assert
|
|||
Assert.Null(context.ClientId); |
|||
Assert.Null(context.MetadataDocument); |
|||
Assert.Null(context.VirtualApplication); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Properties_CanBeSetAndRead() |
|||
{ |
|||
// Arrange
|
|||
var context = new OpenIddictServerSystemNetHttpCimdContext(); |
|||
using var document = JsonDocument.Parse("""{"client_name": "Test"}"""); |
|||
var virtualApp = new object(); |
|||
|
|||
// Act
|
|||
context.ClientId = "https://example.com/client"; |
|||
context.MetadataDocument = document; |
|||
context.VirtualApplication = virtualApp; |
|||
|
|||
// Assert
|
|||
Assert.Equal("https://example.com/client", context.ClientId); |
|||
Assert.Same(document, context.MetadataDocument); |
|||
Assert.Same(virtualApp, context.VirtualApplication); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/* |
|||
* 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 Microsoft.Extensions.Logging; |
|||
using Moq; |
|||
using OpenIddict.Server.SystemNetHttp; |
|||
using Xunit; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
using static OpenIddict.Server.SystemNetHttp.OpenIddictServerSystemNetHttpHandlerFilters; |
|||
|
|||
namespace OpenIddict.Server.SystemNetHttp.Tests; |
|||
|
|||
public class OpenIddictServerSystemNetHttpHandlerFilterTests |
|||
{ |
|||
[Fact] |
|||
public async Task IsActiveAsync_ReturnsTrue_WhenCimdEnabled() |
|||
{ |
|||
// Arrange
|
|||
var filter = new RequireClientIdMetadataDocumentSupportEnabled(); |
|||
var context = CreateBaseContext(enableCimd: true); |
|||
|
|||
// Act
|
|||
var result = await filter.IsActiveAsync(context); |
|||
|
|||
// Assert
|
|||
Assert.True(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task IsActiveAsync_ReturnsFalse_WhenCimdDisabled() |
|||
{ |
|||
// Arrange
|
|||
var filter = new RequireClientIdMetadataDocumentSupportEnabled(); |
|||
var context = CreateBaseContext(enableCimd: false); |
|||
|
|||
// Act
|
|||
var result = await filter.IsActiveAsync(context); |
|||
|
|||
// Assert
|
|||
Assert.False(result); |
|||
} |
|||
|
|||
private static HandleConfigurationRequestContext CreateBaseContext(bool enableCimd) |
|||
{ |
|||
var options = new OpenIddictServerOptions |
|||
{ |
|||
EnableClientIdMetadataDocumentSupport = enableCimd |
|||
}; |
|||
|
|||
var transaction = new OpenIddictServerTransaction |
|||
{ |
|||
Options = options, |
|||
Logger = Mock.Of<ILogger>() |
|||
}; |
|||
|
|||
return new HandleConfigurationRequestContext(transaction) |
|||
{ |
|||
Issuer = new Uri("https://localhost/") |
|||
}; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue