diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs index 051a1d21..d690ccd8 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs @@ -196,7 +196,7 @@ public class Startup if (await manager.FindByClientIdAsync("mvc") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Web, ClientId = "mvc", @@ -223,20 +223,23 @@ public class Startup Permissions.ResponseTypes.Code, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } if (await manager.FindByClientIdAsync("postman") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Native, ClientId = "postman", @@ -260,13 +263,13 @@ public class Startup Permissions.Scopes.Email, Permissions.Scopes.Profile, Permissions.Scopes.Roles - }, - Settings = - { - // Use a shorter access token lifetime for tokens issued to the Postman application. - [Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture) } - }); + }; + + // Use a shorter access token lifetime for tokens issued to the Postman application. + descriptor.SetAccessTokenLifetime(TimeSpan.FromMinutes(10)); + + await manager.CreateAsync(descriptor); } }).GetAwaiter().GetResult(); } diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs index 724e60b3..ed53e2f6 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs @@ -30,7 +30,7 @@ public class Worker : IHostedService if (await manager.FindByClientIdAsync("console") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { // Note: the application must be registered as a native application to force OpenIddict // to apply a relaxed redirect_uri validation policy that allows specifying a random port. @@ -79,20 +79,23 @@ public class Worker : IHostedService Permissions.ResponseTypes.None, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } if (await manager.FindByClientIdAsync("maui") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Native, ClientId = "maui", @@ -122,20 +125,23 @@ public class Worker : IHostedService Permissions.ResponseTypes.Code, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } if (await manager.FindByClientIdAsync("mvc") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Web, ClientId = "mvc", @@ -186,20 +192,23 @@ public class Worker : IHostedService Permissions.ResponseTypes.Code, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } if (await manager.FindByClientIdAsync("winforms") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Native, ClientId = "winforms", @@ -229,20 +238,23 @@ public class Worker : IHostedService Permissions.ResponseTypes.Code, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } if (await manager.FindByClientIdAsync("wpf") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Native, ClientId = "wpf", @@ -272,15 +284,18 @@ public class Worker : IHostedService Permissions.ResponseTypes.Code, Permissions.Scopes.Email, Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" + Permissions.Scopes.Roles }, Requirements = { Requirements.Features.ProofKeyForCodeExchange, Requirements.Features.PushedAuthorizationRequests } - }); + }; + + descriptor.AddScopePermissions("demo_api"); + + await manager.CreateAsync(descriptor); } // Note: when using introspection instead of local token validation, @@ -288,7 +303,7 @@ public class Worker : IHostedService // to communicate with OpenIddict's introspection endpoint. if (await manager.FindByClientIdAsync("resource_server") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ClientId = "resource_server", ClientSecret = "80B552BB-4CD8-48DA-946E-0815E0147DD2", @@ -297,7 +312,9 @@ public class Worker : IHostedService { Permissions.Endpoints.Introspection } - }); + }; + + await manager.CreateAsync(descriptor); } // To test this sample with Postman, use the following settings: @@ -311,7 +328,7 @@ public class Worker : IHostedService // * Request access token locally: yes if (await manager.FindByClientIdAsync("postman") is null) { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + var descriptor = new OpenIddictApplicationDescriptor { ApplicationType = ApplicationTypes.Native, ClientId = "postman", @@ -335,13 +352,13 @@ public class Worker : IHostedService Permissions.Scopes.Email, Permissions.Scopes.Profile, Permissions.Scopes.Roles - }, - Settings = - { - // Use a shorter access token lifetime for tokens issued to the Postman application. - [Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture) } - }); + }; + + // Use a shorter access token lifetime for tokens issued to the Postman application. + descriptor.SetAccessTokenLifetime(TimeSpan.FromMinutes(10)); + + await manager.CreateAsync(descriptor); } #if SUPPORTS_PEM_ENCODED_KEY_IMPORT @@ -361,7 +378,7 @@ public class Worker : IHostedService if (await manager.FindByNameAsync("demo_api") is null) { - await manager.CreateAsync(new OpenIddictScopeDescriptor + var descriptor = new OpenIddictScopeDescriptor { DisplayName = "Demo API access", DisplayNames = @@ -373,7 +390,9 @@ public class Worker : IHostedService { "resource_server" } - }); + }; + + await manager.CreateAsync(descriptor); } } } diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs index c5da1905..741002e1 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs @@ -80,4 +80,298 @@ public class OpenIddictApplicationDescriptor /// Gets the settings associated with the application. /// public Dictionary Settings { get; } = new(StringComparer.Ordinal); + + /// + /// Adds audience permissions for all the specified . + /// + /// The audiences for which audience permissions will be added. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor AddAudiencePermissions(params string[] audiences) + { + if (audiences is null) + { + throw new ArgumentNullException(nameof(audiences)); + } + + foreach (var audience in audiences) + { + Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Audience + audience); + } + + return this; + } + + /// + /// Adds resource permissions for all the specified . + /// + /// The resources for which resource permissions will be added. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor AddResourcePermissions(params string[] resources) + { + if (resources is null) + { + throw new ArgumentNullException(nameof(resources)); + } + + foreach (var resource in resources) + { + Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Resource + resource); + } + + return this; + } + + /// + /// Adds scope permissions for all the specified . + /// + /// The scopes for which scope permissions will be added. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor AddScopePermissions(params string[] scopes) + { + if (scopes is null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + foreach (var scope in scopes) + { + Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); + } + + return this; + } + + /// + /// Configures the application to use the specified access token . + /// + /// The access token lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetAccessTokenLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.AccessToken] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.AccessToken); + } + + return this; + } + + /// + /// Configures the application to use the specified authorization code . + /// + /// The authorization code lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetAuthorizationCodeLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.AuthorizationCode] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.AuthorizationCode); + } + + return this; + } + + /// + /// Configures the application to use the specified device code . + /// + /// The device code lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetDeviceCodeLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.DeviceCode] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.DeviceCode); + } + + return this; + } + + /// + /// Configures the application to use the specified identity token . + /// + /// The identity token lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetIdentityTokenLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.IdentityToken] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.IdentityToken); + } + + return this; + } + + /// + /// Configures the application to use the specified issued token . + /// + /// The issued token lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetIssuedTokenLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.IssuedToken] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.IssuedToken); + } + + return this; + } + + /// + /// Configures the application to use the specified refresh token . + /// + /// The refresh token lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetRefreshTokenLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.RefreshToken] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.RefreshToken); + } + + return this; + } + + /// + /// Configures the application to use the specified request token . + /// + /// The request token lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetRequestTokenLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.RequestToken] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.RequestToken); + } + + return this; + } + + /// + /// Configures the application to use the specified user code . + /// + /// The user code lifetime or to remove the stored value. + /// The instance. + public OpenIddictApplicationDescriptor SetUserCodeLifetime(TimeSpan? lifetime) + { + if (lifetime is not null) + { + Settings[OpenIddictConstants.Settings.TokenLifetimes.UserCode] = + lifetime.Value.ToString("c", CultureInfo.InvariantCulture); + } + + else + { + Settings.Remove(OpenIddictConstants.Settings.TokenLifetimes.UserCode); + } + + return this; + } + + /// + /// Removes all the audience permissions corresponding to the specified . + /// + /// The audiences for which audience permissions will be removed. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor RemoveAudiencePermissions(params string[] audiences) + { + if (audiences is null) + { + throw new ArgumentNullException(nameof(audiences)); + } + + foreach (var audience in audiences) + { + Permissions.Remove(OpenIddictConstants.Permissions.Prefixes.Audience + audience); + } + + return this; + } + + /// + /// Removes all the resource permissions corresponding to the specified . + /// + /// The resources for which resource permissions will be added. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor RemoveResourcePermissions(params string[] resources) + { + if (resources is null) + { + throw new ArgumentNullException(nameof(resources)); + } + + foreach (var resource in resources) + { + Permissions.Remove(OpenIddictConstants.Permissions.Prefixes.Resource + resource); + } + + return this; + } + + /// + /// Removes all the scope permissions corresponding to the specified . + /// + /// The scopes for which scope permissions will be added. + /// The instance. + /// is . + public OpenIddictApplicationDescriptor RemoveScopePermissions(params string[] scopes) + { + if (scopes is null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + foreach (var scope in scopes) + { + Permissions.Remove(OpenIddictConstants.Permissions.Prefixes.Scope + scope); + } + + return this; + } } diff --git a/test/OpenIddict.Abstractions.Tests/Descriptors/OpenIddictApplicationDescriptorTests.cs b/test/OpenIddict.Abstractions.Tests/Descriptors/OpenIddictApplicationDescriptorTests.cs new file mode 100644 index 00000000..f29e9e6d --- /dev/null +++ b/test/OpenIddict.Abstractions.Tests/Descriptors/OpenIddictApplicationDescriptorTests.cs @@ -0,0 +1,322 @@ +using System.Globalization; +using Xunit; + +namespace OpenIddict.Abstractions.Tests.Descriptors; + +public class OpenIddictApplicationDescriptorTests +{ + [Fact] + public void AddAudiencePermissions_AddsPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act + descriptor.AddAudiencePermissions("Fabrikam", "Contoso"); + + // Assert + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Audience + "Fabrikam", descriptor.Permissions); + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Audience + "Contoso", descriptor.Permissions); + } + + [Fact] + public void AddAudiencePermissions_ThrowsAnExceptionForNullAudiences() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.AddAudiencePermissions(null!)); + } + + [Fact] + public void AddResourcePermissions_AddsPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act + descriptor.AddResourcePermissions("urn:fabrikam", "urn:contoso"); + + // Assert + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Resource + "urn:fabrikam", descriptor.Permissions); + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Resource + "urn:contoso", descriptor.Permissions); + } + + [Fact] + public void AddResourcePermissions_ThrowsAnExceptionForNullResources() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.AddResourcePermissions(null!)); + } + + [Fact] + public void AddScopePermissions_AddsPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act + descriptor.AddScopePermissions("scope1", "scope2"); + + // Assert + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Scope + "scope1", descriptor.Permissions); + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Scope + "scope2", descriptor.Permissions); + } + + [Fact] + public void AddScopePermissions_ThrowsAnExceptionForNullScopes() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.AddScopePermissions(null!)); + } + + [Fact] + public void SetAccessTokenLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetAccessTokenLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.AccessToken]); + + // Act + descriptor.SetAccessTokenLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.AccessToken)); + } + + [Fact] + public void SetAuthorizationCodeLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetAuthorizationCodeLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.AuthorizationCode]); + + // Act + descriptor.SetAuthorizationCodeLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.AuthorizationCode)); + } + + [Fact] + public void SetDeviceCodeLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetDeviceCodeLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.DeviceCode]); + + // Act + descriptor.SetDeviceCodeLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.DeviceCode)); + } + + [Fact] + public void SetIdentityTokenLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetIdentityTokenLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.IdentityToken]); + + // Act + descriptor.SetIdentityTokenLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.IdentityToken)); + } + + [Fact] + public void SetIssuedTokenLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetIssuedTokenLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.IssuedToken]); + + // Act + descriptor.SetIssuedTokenLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.IssuedToken)); + } + + [Fact] + public void SetRefreshTokenLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetRefreshTokenLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.RefreshToken]); + + // Act + descriptor.SetRefreshTokenLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.RefreshToken)); + } + + [Fact] + public void SetRequestTokenLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetRequestTokenLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.RequestToken]); + + // Act + descriptor.SetRequestTokenLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.RequestToken)); + } + + [Fact] + public void SetUserCodeLifetime_SetsAndRemovesCorrectly() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + var lifetime = TimeSpan.FromHours(1); + + // Act + descriptor.SetUserCodeLifetime(lifetime); + + // Assert + Assert.Equal(lifetime.ToString("c", CultureInfo.InvariantCulture), + descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.UserCode]); + + // Act + descriptor.SetUserCodeLifetime(null); + + // Assert + Assert.False(descriptor.Settings.ContainsKey(OpenIddictConstants.Settings.TokenLifetimes.UserCode)); + } + + [Fact] + public void RemoveAudiencePermissions_RemovesPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + descriptor.AddAudiencePermissions("Fabrikam", "Contoso"); + + // Act + descriptor.RemoveAudiencePermissions("Fabrikam"); + + // Assert + Assert.DoesNotContain(OpenIddictConstants.Permissions.Prefixes.Audience + "Fabrikam", descriptor.Permissions); + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Audience + "Contoso", descriptor.Permissions); + } + + [Fact] + public void RemoveAudiencePermissions_ThrowsAnExceptionForNullAudiences() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.RemoveAudiencePermissions(null!)); + } + + [Fact] + public void RemoveResourcePermissions_RemovesPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + descriptor.AddResourcePermissions("urn:fabrikam", "urn:contoso"); + + // Act + descriptor.RemoveResourcePermissions("urn:contoso"); + + // Assert + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Resource + "urn:fabrikam", descriptor.Permissions); + Assert.DoesNotContain(OpenIddictConstants.Permissions.Prefixes.Resource + "urn:contoso", descriptor.Permissions); + } + + [Fact] + public void RemoveResourcePermissions_ThrowsAnExceptionForNullResources() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.RemoveResourcePermissions(null!)); + } + + [Fact] + public void RemoveScopePermissions_RemovesPermissions() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + descriptor.AddScopePermissions("scope1", "scope2"); + + // Act + descriptor.RemoveScopePermissions("scope1"); + + // Assert + Assert.DoesNotContain(OpenIddictConstants.Permissions.Prefixes.Scope + "scope1", descriptor.Permissions); + Assert.Contains(OpenIddictConstants.Permissions.Prefixes.Scope + "scope2", descriptor.Permissions); + } + + [Fact] + public void RemoveScopePermissions_ThrowsAnExceptionForNullScopes() + { + // Arrange + var descriptor = new OpenIddictApplicationDescriptor(); + + // Act and assert + Assert.Throws(() => descriptor.RemoveScopePermissions(null!)); + } +}