diff --git a/OpenIddict.slnx b/OpenIddict.slnx index 69b2f560..ab5d6823 100644 --- a/OpenIddict.slnx +++ b/OpenIddict.slnx @@ -97,6 +97,7 @@ + diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Cimd.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Cimd.cs new file mode 100644 index 00000000..4a311089 --- /dev/null +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Cimd.cs @@ -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())) + .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())) + .ReturnsAsync((OpenIddictApplication?) null); + })); + + // Add an inline handler that runs after ValidateClientId to inspect the transaction flag. + options.AddEventHandler(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())) + .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())) + .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())) + .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())) + .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); + } +} diff --git a/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddict.Server.SystemNetHttp.Tests.csproj b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddict.Server.SystemNetHttp.Tests.csproj new file mode 100644 index 00000000..d9adf296 --- /dev/null +++ b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddict.Server.SystemNetHttp.Tests.csproj @@ -0,0 +1,28 @@ + + + + net472;net48;$(NetCoreTargetFrameworks) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpApplicationManagerTests.cs b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpApplicationManagerTests.cs new file mode 100644 index 00000000..f70a7271 --- /dev/null +++ b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpApplicationManagerTests.cs @@ -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())) + .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())) + .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())) + .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())) + .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()), Times.Once()); + } + + [Fact] + public async Task FindByClientIdAsync_ReturnsCachedVirtualApp_OnSecondCall() + { + // Arrange + var store = CreateStore(); + store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny())) + .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()), 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()), 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()), 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()), 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()), 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>(uris => + uris.Length == 2 && + uris[0] == "https://example.com/callback" && + uris[1] == "https://example.com/callback2"), + It.IsAny()), 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>(perms => + perms.Contains(Permissions.GrantTypes.AuthorizationCode) && + perms.Contains(Permissions.GrantTypes.RefreshToken)), + It.IsAny()), 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>(perms => + perms.Contains(Permissions.GrantTypes.AuthorizationCode)), + It.IsAny()), 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>(perms => + perms.Contains(Permissions.ResponseTypes.Code) && + perms.Contains(Permissions.ResponseTypes.CodeIdToken)), + It.IsAny()), 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>(perms => + perms.Contains(Permissions.ResponseTypes.Code)), + It.IsAny()), 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>(reqs => + reqs.Contains(Requirements.Features.ProofKeyForCodeExchange)), + It.IsAny()), 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())) + .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>(perms => + perms.Contains(Permissions.Endpoints.Authorization) && + perms.Contains(Permissions.Endpoints.Token)), + It.IsAny()), 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>(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()), Times.Once()); + } + + private static Mock> CreateStore() + { + var store = new Mock>(); + store.Setup(s => s.InstantiateAsync(It.IsAny())) + .ReturnsAsync(() => new TestApplication()); + return store; + } + + private static OpenIddictServerSystemNetHttpApplicationManager CreateManager( + Mock> store, + OpenIddictServerSystemNetHttpCimdContext cimdContext) + { + var cache = Mock.Of>(); + var logger = NullLogger>.Instance; + + var coreOptions = new OpenIddictCoreOptions + { + DisableEntityCaching = true, + DisableAdditionalFiltering = true + }; + + var optionsMonitor = Mock.Of>( + m => m.CurrentValue == coreOptions); + + return new OpenIddictServerSystemNetHttpApplicationManager( + cache, logger, optionsMonitor, store.Object, cimdContext); + } + + private static (OpenIddictServerSystemNetHttpApplicationManager Manager, + Mock> Store, + OpenIddictServerSystemNetHttpCimdContext CimdContext) + CreateManagerWithCimdContext(JsonDocument? document = null) + { + var store = CreateStore(); + store.Setup(s => s.FindByClientIdAsync("https://example.com/client", It.IsAny())) + .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 { } +} diff --git a/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpCimdContextTests.cs b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpCimdContextTests.cs new file mode 100644 index 00000000..61e4a2bf --- /dev/null +++ b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpCimdContextTests.cs @@ -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); + } +} diff --git a/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpHandlerFilterTests.cs b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpHandlerFilterTests.cs new file mode 100644 index 00000000..322c7f69 --- /dev/null +++ b/test/OpenIddict.Server.SystemNetHttp.Tests/OpenIddictServerSystemNetHttpHandlerFilterTests.cs @@ -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() + }; + + return new HandleConfigurationRequestContext(transaction) + { + Issuer = new Uri("https://localhost/") + }; + } +}