/* * 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.Buffers.Binary; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Moq; using Xunit; namespace OpenIddict.Core.Tests; public class OpenIddictApplicationManagerTests { [Fact] public void Constructor_ThrowsAnExceptionForNullCache() { // Arrange var cache = (IOpenIddictApplicationCache) null!; var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictApplicationManager(cache, logger, options, store)); Assert.Equal("cache", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullLogger() { // Arrange var cache = Mock.Of>(); var logger = (ILogger>) null!; var options = Mock.Of>(); var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictApplicationManager(cache, logger, options, store)); Assert.Equal("logger", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullOptions() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = (IOptionsMonitor) null!; var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictApplicationManager(cache, logger, options, store)); Assert.Equal("options", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullStore() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = (IOpenIddictApplicationStore) null!; // Act and assert var exception = Assert.Throws( () => new OpenIddictApplicationManager(cache, logger, options, store)); Assert.Equal("store", exception.ParamName); } [Fact] public async Task CountAsync_CallsStoreMethod() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.CountAsync(It.IsAny())) .ReturnsAsync(42); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var count = await manager.CountAsync(); // Assert Assert.Equal(42, count); store.Verify(store => store.CountAsync(It.IsAny()), Times.Once()); } [Fact] public async Task CountAsync_WithQuery_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CountAsync(query: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task CreateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CreateAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task CreateAsync_WithSecret_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CreateAsync(application: null!, secret: "secret").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task CreateAsync_WithDescriptor_ThrowsAnExceptionForNullDescriptor() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CreateAsync(descriptor: null!).AsTask()); Assert.Equal("descriptor", exception.ParamName); } [Fact] public async Task DeleteAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.DeleteAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task DeleteAsync_RemovesApplicationFromCache_WhenCachingIsEnabled() { // Arrange var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); var application = new CustomApplication(); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act await manager.DeleteAsync(application); // Assert cache.Verify(cache => cache.RemoveAsync(application, It.IsAny()), Times.Once()); store.Verify(store => store.DeleteAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task DeleteAsync_DoesNotRemoveFromCache_WhenCachingIsDisabled() { // Arrange var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); var application = new CustomApplication(); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act await manager.DeleteAsync(application); // Assert cache.Verify(cache => cache.RemoveAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.DeleteAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task FindByClientIdAsync_ThrowsAnExceptionForNullIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByClientIdAsync(identifier: null!).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByClientIdAsync_ThrowsAnExceptionForEmptyIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByClientIdAsync(identifier: string.Empty).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByClientIdAsync_UsesCache_WhenCachingIsEnabled() { // Arrange var application = new CustomApplication(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); cache.Setup(cache => cache.FindByClientIdAsync("client_id", It.IsAny())) .ReturnsAsync(application); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client_id"); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByClientIdAsync("client_id"); // Assert Assert.Same(application, result); cache.Verify(cache => cache.FindByClientIdAsync("client_id", It.IsAny()), Times.Once()); store.Verify(store => store.FindByClientIdAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task FindByClientIdAsync_UsesStore_WhenCachingIsDisabled() { // Arrange var application = new CustomApplication(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); store.Setup(store => store.FindByClientIdAsync("client_id", It.IsAny())) .ReturnsAsync(application); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client_id"); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByClientIdAsync("client_id"); // Assert Assert.Same(application, result); cache.Verify(cache => cache.FindByClientIdAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.FindByClientIdAsync("client_id", It.IsAny()), Times.Once()); } [Fact] public async Task FindByIdAsync_ThrowsAnExceptionForNullIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByIdAsync(identifier: null!).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByIdAsync_ThrowsAnExceptionForEmptyIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByIdAsync(identifier: string.Empty).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByIdAsync_UsesCache_WhenCachingIsEnabled() { // Arrange var application = new CustomApplication(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); cache.Setup(cache => cache.FindByIdAsync("id", It.IsAny())) .ReturnsAsync(application); store.Setup(store => store.GetIdAsync(application, It.IsAny())) .ReturnsAsync("id"); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByIdAsync("id"); // Assert Assert.Same(application, result); cache.Verify(cache => cache.FindByIdAsync("id", It.IsAny()), Times.Once()); store.Verify(store => store.FindByIdAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task FindByIdAsync_UsesStore_WhenCachingIsDisabled() { // Arrange var application = new CustomApplication(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); store.Setup(store => store.FindByIdAsync("id", It.IsAny())) .ReturnsAsync(application); store.Setup(store => store.GetIdAsync(application, It.IsAny())) .ReturnsAsync("id"); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByIdAsync("id"); // Assert Assert.Same(application, result); cache.Verify(cache => cache.FindByIdAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.FindByIdAsync("id", It.IsAny()), Times.Once()); } [Fact] public void FindByPostLogoutRedirectUriAsync_ThrowsAnExceptionForNullUri() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws(() => manager.FindByPostLogoutRedirectUriAsync(uri: null!)); Assert.Equal("uri", exception.ParamName); } [Fact] public void FindByPostLogoutRedirectUriAsync_ThrowsAnExceptionForEmptyUri() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws(() => manager.FindByPostLogoutRedirectUriAsync(uri: string.Empty)); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task FindByPostLogoutRedirectUriAsync_UsesCache_WhenCachingIsEnabled() { // Arrange var applications = new[] { new CustomApplication() }; var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); cache.Setup(cache => cache.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout", It.IsAny())) .Returns(applications.ToAsyncEnumerable()); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(applications[0], It.IsAny())) .ReturnsAsync(["https://www.fabrikam.com/callback/logout"]); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout").ToListAsync(); // Assert Assert.Same(applications[0], result[0]); cache.Verify(cache => cache.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout", It.IsAny()), Times.Once()); store.Verify(store => store.FindByPostLogoutRedirectUriAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task FindByPostLogoutRedirectUriAsync_UsesStore_WhenCachingIsDisabled() { // Arrange var applications = new[] { new CustomApplication() }; var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); store.Setup(store => store.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout", It.IsAny())) .Returns(applications.ToAsyncEnumerable()); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(applications[0], It.IsAny())) .ReturnsAsync(["https://www.fabrikam.com/callback/logout"]); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout").ToListAsync(); // Assert Assert.Same(applications[0], result[0]); cache.Verify(cache => cache.FindByPostLogoutRedirectUriAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.FindByPostLogoutRedirectUriAsync("https://www.fabrikam.com/callback/logout", It.IsAny()), Times.Once()); } [Fact] public void FindByRedirectUriAsync_ThrowsAnExceptionForNullUri() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws(() => manager.FindByRedirectUriAsync(uri: null!)); Assert.Equal("uri", exception.ParamName); } [Fact] public void FindByRedirectUriAsync_ThrowsAnExceptionForEmptyUri() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws(() => manager.FindByRedirectUriAsync(uri: string.Empty)); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task FindByRedirectUriAsync_UsesCache_WhenCachingIsEnabled() { // Arrange var applications = new[] { new CustomApplication() }; var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); cache.Setup(cache => cache.FindByRedirectUriAsync("https://www.fabrikam.com/callback", It.IsAny())) .Returns(applications.ToAsyncEnumerable()); store.Setup(store => store.GetRedirectUrisAsync(applications[0], It.IsAny())) .ReturnsAsync(["https://www.fabrikam.com/callback"]); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByRedirectUriAsync("https://www.fabrikam.com/callback").ToListAsync(); // Assert Assert.Same(applications[0], result[0]); cache.Verify(cache => cache.FindByRedirectUriAsync("https://www.fabrikam.com/callback", It.IsAny()), Times.Once()); store.Verify(store => store.FindByRedirectUriAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task FindByRedirectUriAsync_UsesStore_WhenCachingIsDisabled() { // Arrange var applications = new[] { new CustomApplication() }; var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); store.Setup(store => store.FindByRedirectUriAsync("https://www.fabrikam.com/callback", It.IsAny())) .Returns(applications.ToAsyncEnumerable()); store.Setup(store => store.GetRedirectUrisAsync(applications[0], It.IsAny())) .ReturnsAsync(["https://www.fabrikam.com/callback"]); var manager = new OpenIddictApplicationManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByRedirectUriAsync("https://www.fabrikam.com/callback").ToListAsync(); // Assert Assert.Same(applications[0], result[0]); cache.Verify(cache => cache.FindByRedirectUriAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.FindByRedirectUriAsync("https://www.fabrikam.com/callback", It.IsAny()), Times.Once()); } [Fact] public async Task GetApplicationTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetApplicationTypeAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetAsync_WithQuery_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetAsync(query: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task GetAsync_WithQueryAndState_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetAsync(query: null!, state: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task GetClientIdAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetClientIdAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetClientIdAsync_ReturnsClientIdFromStore() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("my-client-id"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var clientId = await manager.GetClientIdAsync(application); // Assert Assert.Equal("my-client-id", clientId); store.Verify(store => store.GetClientIdAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task GetClientTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetClientTypeAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetClientTypeAsync_ReturnsClientTypeFromStore() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync("public"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var clientType = await manager.GetClientTypeAsync(application); // Assert Assert.Equal("public", clientType); store.Verify(store => store.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task GetConsentTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetConsentTypeAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetDisplayNameAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetDisplayNameAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetDisplayNameAsync_ReturnsDisplayNameFromStore() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetDisplayNameAsync(application, It.IsAny())) .ReturnsAsync("My Application"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var displayName = await manager.GetDisplayNameAsync(application); // Assert Assert.Equal("My Application", displayName); store.Verify(store => store.GetDisplayNameAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task GetDisplayNamesAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetDisplayNamesAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetIdAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetIdAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetIdAsync_ReturnsIdentifierFromStore() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetIdAsync(application, It.IsAny())) .ReturnsAsync("unique-id"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var id = await manager.GetIdAsync(application); // Assert Assert.Equal("unique-id", id); store.Verify(store => store.GetIdAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task GetJsonWebKeySetAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetJsonWebKeySetAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetLocalizedDisplayNameAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetLocalizedDisplayNameAsync(application: null!, culture: CultureInfo.InvariantCulture).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetLocalizedDisplayNameAsync_WithCulture_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetLocalizedDisplayNameAsync(application: null!, CultureInfo.CurrentCulture).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetPermissionsAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetPermissionsAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetPostLogoutRedirectUrisAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetPostLogoutRedirectUrisAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetPropertiesAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetPropertiesAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetPublicKeyInfrastructureTlsClientAuthenticationPolicyAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetPublicKeyInfrastructureTlsClientAuthenticationPolicyAsync(application: null!, policy).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetRedirectUrisAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetRedirectUrisAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetRequirementsAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetRequirementsAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetSelfSignedTlsClientAuthenticationPolicyAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetSelfSignedTlsClientAuthenticationPolicyAsync(application: null!, policy).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task GetSettingsAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetSettingsAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasApplicationTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasApplicationTypeAsync(application: null!, "web").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasApplicationTypeAsync_ThrowsAnExceptionForNullType() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasApplicationTypeAsync(application, type: null!).AsTask()); Assert.Equal("type", exception.ParamName); } [Fact] public async Task HasApplicationTypeAsync_ReturnsTrueWhenTypeMatches() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync("web"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.HasApplicationTypeAsync(application, "web"); // Assert Assert.True(result); } [Fact] public async Task HasClientTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasClientTypeAsync(application: null!, "public").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasClientTypeAsync_ThrowsAnExceptionForNullType() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasClientTypeAsync(application, type: null!).AsTask()); Assert.Equal("type", exception.ParamName); } [Theory] [InlineData("public", "public", true)] [InlineData("confidential", "public", false)] public async Task HasClientTypeAsync_ReturnsExpectedResult(string storedType, string testedType, bool expected) { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(storedType); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.HasClientTypeAsync(application, testedType); // Assert Assert.Equal(expected, result); } [Fact] public async Task HasConsentTypeAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasConsentTypeAsync(application: null!, "explicit").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasConsentTypeAsync_ThrowsAnExceptionForNullType() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasConsentTypeAsync(application, type: null!).AsTask()); Assert.Equal("type", exception.ParamName); } [Fact] public async Task HasConsentTypeAsync_ReturnsTrueWhenTypeMatches() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetConsentTypeAsync(application, It.IsAny())) .ReturnsAsync("explicit"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.HasConsentTypeAsync(application, "explicit"); // Assert Assert.True(result); } [Fact] public async Task HasPermissionAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasPermissionAsync(application: null!, "ept:token").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasPermissionAsync_ThrowsAnExceptionForNullPermission() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasPermissionAsync(application, permission: null!).AsTask()); Assert.Equal("permission", exception.ParamName); } [Fact] public async Task HasRequirementAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasRequirementAsync(application: null!, "requirement").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task HasRequirementAsync_ThrowsAnExceptionForNullRequirement() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.HasRequirementAsync(application, requirement: null!).AsTask()); Assert.Equal("requirement", exception.ParamName); } [Fact] public async Task HasRequirementAsync_ReturnsTrueWhenRequirementExists() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRequirementsAsync(application, It.IsAny())) .ReturnsAsync(["requirement1", "requirement2"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.HasRequirementAsync(application, "requirement1"); // Assert Assert.True(result); } [Fact] public async Task ListAsync_ReturnsAllApplications() { // Arrange var applications = new[] { new CustomApplication(), new CustomApplication() }; var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.ListAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(applications.ToAsyncEnumerable()); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = new List(); await foreach (var app in manager.ListAsync()) { results.Add(app); } // Assert Assert.Equal(2, results.Count); store.Verify(store => store.ListAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task ObfuscateClientSecretAsync_ThrowsAnExceptionForNullSecret() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ObfuscateClientSecretAsync(secret: null!).AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ObfuscateClientSecretAsync_ThrowsAnExceptionForEmptySecret() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ObfuscateClientSecretAsync(secret: string.Empty).AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ObfuscateClientSecretAsync_ReturnsBase64EncodedHash() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.NotEmpty(payload); } [Fact] public async Task ObfuscateClientSecretAsync_WritesFormatMarker() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.Equal(0x01, payload[0]); } [Fact] public async Task ObfuscateClientSecretAsync_UsesSha512ByDefault() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.Equal(2u, BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(1, 4))); } [Fact] public async Task ObfuscateClientSecretAsync_UsesSha256WhenConfigured() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { ClientSecretKeyDerivationHashAlgorithm = HashAlgorithmName.SHA256 }); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(1, 4))); } [Fact] public async Task ObfuscateClientSecretAsync_UsesSha1WhenConfigured() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { ClientSecretKeyDerivationHashAlgorithm = HashAlgorithmName.SHA1 }); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.Equal(0u, BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(1, 4))); } [Fact] public async Task ObfuscateClientSecretAsync_ThrowsForUnsupportedAlgorithm() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { ClientSecretKeyDerivationHashAlgorithm = HashAlgorithmName.SHA384 }); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert await Assert.ThrowsAsync( () => manager.ObfuscateClientSecretAsync("my-secret").AsTask()); } [Fact] public async Task ObfuscateClientSecretAsync_WritesConfiguredIterationCount() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { ClientSecretKeyDerivationIterations = 50_000 }); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); // Assert var payload = Convert.FromBase64String(hash); Assert.Equal(50_000u, BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(5, 4))); } [Fact] public async Task ObfuscateClientSecretAsync_WritesSaltOfConfiguredLength() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { ClientSecretKeyDerivationSaltLength = 128 // bits → 16 bytes }); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var payload = Convert.FromBase64String(hash); Assert.Equal(16u, BinaryPrimitives.ReadUInt32BigEndian(payload.AsSpan(9, 4))); } [Fact] public async Task ObfuscateClientSecretAsync_ProducesDifferentHashesForSameSecret() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var hash1 = await manager.ObfuscateClientSecretAsync("my-secret"); var hash2 = await manager.ObfuscateClientSecretAsync("my-secret"); Assert.NotEqual(hash1, hash2, StringComparer.Ordinal); } [Fact] public async Task ObfuscateClientSecretAsync_ProducesHashValidatableByValidateClientSecretAsync() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var hash = await manager.ObfuscateClientSecretAsync("my-secret"); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", hash); // Assert Assert.True(isValid); Assert.False(isRehashRequired); } [Fact] public async Task PopulateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var descriptor = new OpenIddictApplicationDescriptor(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.PopulateAsync(application: null!, descriptor).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task PopulateAsync_ThrowsAnExceptionForNullDescriptor() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.PopulateAsync(application, descriptor: null!).AsTask()); Assert.Equal("descriptor", exception.ParamName); } [Fact] public async Task UpdateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.UpdateAsync(application: null!).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task UpdateAsync_WithDescriptor_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var descriptor = new OpenIddictApplicationDescriptor(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.UpdateAsync(application: null!, descriptor).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task UpdateAsync_WithSecret_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.UpdateAsync(application: null!, secret: "secret").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public void ValidateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws( () => manager.ValidateAsync(application: null!)); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenClientIdIsEmpty() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2036), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenClientIdIsAlreadyUsed() { // Arrange var application = new CustomApplication(); var other = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.FindByClientIdAsync("client-id", It.IsAny())) .ReturnsAsync(other); store.Setup(store => store.GetIdAsync(application, It.IsAny())) .ReturnsAsync("application-id"); store.Setup(store => store.GetIdAsync(other, It.IsAny())) .ReturnsAsync("other-application-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2111), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenClientTypeIsEmpty() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2050), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenClientTypeIsNotSupported() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync("unsupported-type"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2112), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenPublicApplicationHasClientSecret() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("some-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2114), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenConfidentialApplicationHasNoSecretAndNoSigningKey() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetJsonWebKeySetAsync(application, It.IsAny())) .ReturnsAsync((JsonWebKeySet?) null); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2113), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenConfidentialApplicationHasJwksWithoutSigningKey() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); var jwks = new JsonWebKeySet(); jwks.Keys.Add(new JsonWebKey { Kty = JsonWebAlgorithmsKeyTypes.Octet, Use = JsonWebKeyUseNames.Sig }); store.Setup(store => store.GetJsonWebKeySetAsync(application, It.IsAny())) .ReturnsAsync(jwks); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2113), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenRedirectUriIsEmpty() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([string.Empty]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2061), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenRedirectUriIsNotAbsolute() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["not-an-absolute-uri"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2062), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenRedirectUriContainsFragment() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://client.contoso.com/callback#fragment"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2115), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenRedirectUriContainsIssParameter() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://client.contoso.com/callback?iss=https%3A%2F%2Fissuer.contoso.com"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.FormatID2134(Parameters.Iss), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenPostLogoutRedirectUriContainsFragment() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://client.contoso.com/logout#fragment"]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2115), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsNoErrorsForValidConfidentialApplicationWithSecret() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("hashed-secret"); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://client.contoso.com/callback"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.DoesNotContain(results, static result => result != ValidationResult.Success); } [Fact] public async Task ValidateAsync_ReturnsNoErrorsForValidConfidentialApplicationWithRsaSigningKey() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); var jwks = new JsonWebKeySet(); jwks.Keys.Add(new JsonWebKey { Kty = JsonWebAlgorithmsKeyTypes.RSA, Use = JsonWebKeyUseNames.Sig }); store.Setup(store => store.GetJsonWebKeySetAsync(application, It.IsAny())) .ReturnsAsync(jwks); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.DoesNotContain(results, static result => result != ValidationResult.Success); } [Fact] public async Task ValidateAsync_ReturnsNoErrorsForValidPublicApplication() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://client.contoso.com/callback"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(application).ToListAsync(); // Assert Assert.DoesNotContain(results, static result => result != ValidationResult.Success); } [Fact] public async Task ValidateClientSecretAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync(application: null!, "secret").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ThrowsAnExceptionForNullSecret() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync(application, secret: null!).AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ThrowsAnExceptionForEmptySecret() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync(application, secret: string.Empty).AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ReturnsFalseForPublicClients() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "any-secret"); // Assert Assert.False(result); } [Fact] public async Task ValidateClientSecretAsync_ReturnsFalseWhenStoredSecretIsNull() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync((string?) null); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "any-secret"); // Assert Assert.False(result); } [Fact] public async Task ValidateClientSecretAsync_ReturnsFalseWhenStoredSecretIsEmpty() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(string.Empty); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "any-secret"); // Assert Assert.False(result); } [Fact] public async Task ValidateClientSecretAsync_ReturnsFalseWhenSecretDoesNotMatch() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); var hashedSecret = "AQAAAAIAAYagAAAAEOlzsJpxnNJRh9HnCQNEzZMPLqTYYqH5WJ5+DJMwL+BhiLUGjL2SQnfRU8bQv2K2Sg=="; store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(hashedSecret); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "wrong-secret"); // Assert Assert.False(result); } [Fact] public async Task ValidateClientSecretAsync_PerformsRehashWhenRequired() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = new Mock>(); var storedHash = "AQAAAAEAACcQAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fMMgkjB4tHCgxxjPemQnSkFqO0dtrtE3xiqISZNQJfWNqm+Flbr/sNlQCAWRy0XrIxw6HU3B+YB5aHDlJhCTw2w=="; var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableAutomaticClientSecretRehashing = false }); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(storedHash); store.Setup(store => store.FindByClientIdAsync("client-id", It.IsAny())) .ReturnsAsync((CustomApplication?) null); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.UpdateAsync(application, It.IsAny())) .Returns(ValueTask.CompletedTask); store.Setup(store => store.SetClientSecretAsync(application, It.IsAny(), It.IsAny())) .Returns(ValueTask.CompletedTask); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act await manager.ValidateClientSecretAsync(application, "test-secret"); // Assert store.Verify(store => store.UpdateAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateClientSecretAsync_SkipsRehashWhenDisabled() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableAutomaticClientSecretRehashing = true }); var store = new Mock>(); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); var oldHash = "AQAAAAEACicQAAAAEJPkfY9tVqKqHQlLPqNT0ksqN8C3LqY4RqBYQaJHhWPQXuYhWFpN9pLkR8vZxQ=="; store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync(oldHash); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "test-secret"); // Assert store.Verify(store => store.UpdateAsync(application, It.IsAny()), Times.Never()); } [Fact] public async Task ValidateClientSecretAsync_ContinuesOnRehashFailure() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = new Mock>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableAutomaticClientSecretRehashing = false }); store.Setup(store => store.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); store.Setup(store => store.GetClientIdAsync(application, It.IsAny())) .ReturnsAsync("client-id"); store.Setup(store => store.GetClientSecretAsync(application, It.IsAny())) .ReturnsAsync("AQAAAAEAACcQAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fMMgkjB4tHCgxxjPemQnSkFqO0dtrtE3xiqISZNQJfWNqm+Flbr/sNlQCAWRy0XrIxw6HU3B+YB5aHDlJhCTw2w=="); store.Setup(store => store.FindByClientIdAsync("client-id", It.IsAny())) .ReturnsAsync((CustomApplication?) null); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync([]); store.Setup(store => store.UpdateAsync(application, It.IsAny())) .Returns(new ValueTask(Task.FromException(new InvalidOperationException("Concurrency conflict")))); store.Setup(store => store.SetClientSecretAsync(application, It.IsAny(), It.IsAny())) .Returns(ValueTask.CompletedTask); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateClientSecretAsync(application, "test-secret"); // Assert Assert.True(result); store.Verify(store => store.UpdateAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ThrowsAnExceptionForNullSecret() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync(secret: null!, "comparand").AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ThrowsAnExceptionForEmptySecret() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync(secret: string.Empty, "comparand").AsTask()); Assert.Equal("secret", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ThrowsAnExceptionForNullComparand() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync("secret", comparand: null!).AsTask()); Assert.Equal("comparand", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ThrowsAnExceptionForEmptyComparand() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateClientSecretAsync("secret", comparand: string.Empty).AsTask()); Assert.Equal("comparand", exception.ParamName); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForInvalidBase64Comparand() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", "not-valid-base64!!!"); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForEmptyPayload() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String([0x01])); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForInvalidFormatMarker() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var payload = new byte[20]; payload[0] = 0x02; var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String(payload)); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForUnknownAlgorithmVersion() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var payload = new byte[50]; payload[0] = 0x01; BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, 4), 99); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String(payload)); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForTooFewIterations() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var payload = new byte[50]; payload[0] = 0x01; BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, 4), 1); // SHA256 BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(5, 4), 9_999); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String(payload)); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForTooManyIterations() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var payload = new byte[50]; payload[0] = 0x01; BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, 4), 1); // SHA256 BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(5, 4), 10_000_001); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String(payload)); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseForTooShortSalt() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); var payload = new byte[50]; payload[0] = 0x01; BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, 4), 1); // SHA256 BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(5, 4), 100_000); BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(9, 4), 15); var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("secret", Convert.ToBase64String(payload)); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsFalseWhenSecretDoesNotMatch() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("wrong-secret", "AQAAAAIAAYagAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fa7sVq4Aib4PoDsWFuIX7V4NFLHw5zf2TM+3M6JSc3S2ae5j2Njof7cWUUqaG4esxvbG6aKTTQMogVSejRPMbhQ=="); // Assert Assert.False(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsTrueWithoutRehashWhenOptionsMatch() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", "AQAAAAIAAYagAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fhmvJW1GK8OLHa33ut1TfRoG5BojsxFN8Oq8lnF9CTGu/Ti7ItixBhZaDxxb5lejILz/Ob+33P0h2Zax9+FTeWg=="); // Assert Assert.True(isValid); Assert.False(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsTrueWithRehashWhenAlgorithmChanged() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = Mock.Of>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", "AQAAAAEAAYagAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fcc2TaLOEfZnHMySJlkQmmM1nnq4c6jFhE65SjmqPATGuvpSybeRzlk8584bUg3qjJbxxJ1oALKzppT0V6sPYQQ=="); // Assert Assert.True(isValid); Assert.True(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsTrueWithRehashWhenIterationsChanged() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = Mock.Of>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", "AQAAAAIAAMNQAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fhUaA0RyMXFhPzsknq1++eBXKuNGp6nPquCMb4NVu9st1Qj6cDZXnnHmS2FXgc/2AUe8+QJ35JGY3SW3gJjzQUA=="); // Assert Assert.True(isValid); Assert.True(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsTrueWithRehashWhenSaltLengthChanged() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = Mock.Of>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", "AQAAAAIAAYagAAAAEAABAgMEBQYHCAkKCwwNDg9/NCTaaTU2IRS086JD6lOexCek5paQ+YtfkDinqaRBpu6sdgeztT00H36B5hWW787rjVDMg9IpYdZ7iPiQZZZu"); // Assert Assert.True(isValid); Assert.True(isRehashRequired); } [Fact] public async Task ValidateClientSecretAsync_ProtectedMethod_ReturnsTrueWithRehashWhenOutputLengthChanged() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var store = Mock.Of>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var manager = new CustomApplicationManagerWithProtectedAccess(cache, logger, options, store); // Act var (isValid, isRehashRequired) = await manager.ValidateClientSecretAsync("my-secret", "AQAAAAIAAYagAAAAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fhmvJW1GK8OLHa33ut1TfRg=="); // Assert Assert.True(isValid); Assert.True(isRehashRequired); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidatePostLogoutRedirectUriAsync(application: null!, "https://localhost").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ThrowsAnExceptionForNullUri() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidatePostLogoutRedirectUriAsync(application, uri: null!).AsTask()); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ThrowsAnExceptionForEmptyUri() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidatePostLogoutRedirectUriAsync(application, uri: string.Empty).AsTask()); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseWhenUriNotRegistered() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/logout"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "https://other.com/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsTrueWhenUriMatchesExactly() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/logout"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "https://contoso.com/logout"); // Assert Assert.True(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseWhenUriDiffersInCase() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/Logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Web); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "https://contoso.com/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsTrueForNativeApplicationWhenLoopbackUriMatchesWithDifferentPort() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "http://127.0.0.1:7890/logout"); // Assert Assert.True(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseForNativeApplicationWhenClientUriUsesDefaultPort() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "http://127.0.0.1:80/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseForNativeApplicationWhenUriIsNotLoopback() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "https://contoso.com:7890/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseForNativeApplicationWhenSchemesDiffer() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "https://127.0.0.1:7890/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseForNativeApplicationWhenPathsDiffer() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "http://127.0.0.1:7890/other"); // Assert Assert.False(result); } [Fact] public async Task ValidatePostLogoutRedirectUriAsync_ReturnsFalseForNonNativeApplicationWhenPortDiffers() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPostLogoutRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/logout"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Web); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidatePostLogoutRedirectUriAsync(application, "http://127.0.0.1:7890/logout"); // Assert Assert.False(result); } [Fact] public async Task ValidatePublicKeyInfrastructureTlsClientCertificateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var certificate = (X509Certificate2)null!; var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidatePublicKeyInfrastructureTlsClientCertificateAsync(application: null!, certificate, policy).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidatePublicKeyInfrastructureTlsClientCertificateAsync_ThrowsAnExceptionForNullCertificate() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidatePublicKeyInfrastructureTlsClientCertificateAsync(application, certificate: null!, policy).AsTask()); Assert.Equal("certificate", exception.ParamName); } [Fact] public async Task ValidateRedirectUriAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateRedirectUriAsync(application: null!, "https://localhost").AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidateRedirectUriAsync_ThrowsAnExceptionForNullUri() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateRedirectUriAsync(application, uri: null!).AsTask()); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task ValidateRedirectUriAsync_ThrowsAnExceptionForEmptyUri() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateRedirectUriAsync(application, uri: string.Empty).AsTask()); Assert.Equal("uri", exception.ParamName); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseWhenUriNotRegistered() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/callback"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateRedirectUriAsync(application, "https://other.com/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsTrueWhenUriMatchesExactly() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/callback"]); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateRedirectUriAsync(application, "https://contoso.com/callback"); // Assert Assert.True(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseWhenUriDiffersInCase() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/Callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Web); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateRedirectUriAsync(application, "https://contoso.com/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsTrueForNativeApplicationWhenLoopbackUriMatchesWithDifferentPort() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidateRedirectUriAsync(application, "http://127.0.0.1:7890/callback"); // Assert Assert.True(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseForNativeApplicationWhenClientUriUsesDefaultPort() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidateRedirectUriAsync(application, "http://127.0.0.1:80/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseForNativeApplicationWhenUriIsNotLoopback() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["https://contoso.com/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidateRedirectUriAsync(application, "https://contoso.com:7890/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseForNativeApplicationWhenSchemesDiffer() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidateRedirectUriAsync(application, "https://127.0.0.1:7890/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseForNativeApplicationWhenPathsDiffer() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Native); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); var result = await manager.ValidateRedirectUriAsync(application, "http://127.0.0.1:7890/other"); // Assert Assert.False(result); } [Fact] public async Task ValidateRedirectUriAsync_ReturnsFalseForNonNativeApplicationWhenPortDiffers() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetRedirectUrisAsync(application, It.IsAny())) .ReturnsAsync(["http://127.0.0.1/callback"]); store.Setup(store => store.GetApplicationTypeAsync(application, It.IsAny())) .ReturnsAsync(ApplicationTypes.Web); var manager = new OpenIddictApplicationManager(cache, logger, options, store.Object); // Act var result = await manager.ValidateRedirectUriAsync(application, "http://127.0.0.1:7890/callback"); // Assert Assert.False(result); } [Fact] public async Task ValidateSelfSignedTlsClientCertificateAsync_ThrowsAnExceptionForNullApplication() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var certificate = (X509Certificate2)null!; var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateSelfSignedTlsClientCertificateAsync(application: null!, certificate, policy).AsTask()); Assert.Equal("application", exception.ParamName); } [Fact] public async Task ValidateSelfSignedTlsClientCertificateAsync_ThrowsAnExceptionForNullCertificate() { // Arrange var application = new CustomApplication(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictApplicationManager(cache, logger, options, store); var policy = new X509ChainPolicy(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.ValidateSelfSignedTlsClientCertificateAsync(application, certificate: null!, policy).AsTask()); Assert.Equal("certificate", exception.ParamName); } public class CustomApplication; private sealed class CustomApplicationManagerWithProtectedAccess : OpenIddictApplicationManager { public CustomApplicationManagerWithProtectedAccess( IOpenIddictApplicationCache cache, ILogger> logger, IOptionsMonitor options, IOpenIddictApplicationStore store) : base(cache, logger, options, store) { } public new ValueTask ObfuscateClientSecretAsync(string secret, CancellationToken cancellationToken = default) => base.ObfuscateClientSecretAsync(secret, cancellationToken); public new ValueTask<(bool IsValid, bool IsRehashRequired)> ValidateClientSecretAsync( string secret, string comparand, CancellationToken cancellationToken = default) => base.ValidateClientSecretAsync(secret, comparand, cancellationToken); } }