diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs index 38669b56e0..0ca25662de 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs @@ -38,7 +38,7 @@ public static IApplicationBuilder UseSwaggerUI( if (options.ConfigObject.Urls == null) { var hostingEnv = app.ApplicationServices.GetRequiredService(); - options.ConfigObject.Urls = new[] { new UrlDescriptor { Name = $"{hostingEnv.ApplicationName} v1", Url = "v1/swagger.json" } }; + options.ConfigObject.Urls = [new UrlDescriptor { Name = $"{hostingEnv.ApplicationName} v1", Url = "v1/swagger.json" }]; } return app.UseSwaggerUI(options); diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs index 6f6e4f1cff..3d9a9e9437 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs @@ -72,30 +72,37 @@ public class ConfigObject /// /// One or more Swagger JSON endpoints (url and name) to power the UI /// + [JsonPropertyName("urls")] public IEnumerable Urls { get; set; } = null; /// /// If set to true, enables deep linking for tags and operations /// + [JsonPropertyName("deepLinking")] public bool DeepLinking { get; set; } = false; + /// /// If set to true, it persists authorization data and it would not be lost on browser close/refresh /// + [JsonPropertyName("persistAuthorization")] public bool PersistAuthorization { get; set; } = false; /// /// Controls the display of operationId in operations list /// + [JsonPropertyName("displayOperationId")] public bool DisplayOperationId { get; set; } = false; /// /// The default expansion depth for models (set to -1 completely hide the models) /// + [JsonPropertyName("defaultModelsExpandDepth")] public int DefaultModelsExpandDepth { get; set; } = 1; /// /// The default expansion depth for the model on the model-example section /// + [JsonPropertyName("defaultModelExpandDepth")] public int DefaultModelExpandDepth { get; set; } = 1; /// @@ -105,11 +112,13 @@ public class ConfigObject #if NET6_0_OR_GREATER [JsonConverter(typeof(JavascriptStringEnumConverter))] #endif + [JsonPropertyName("defaultModelRendering")] public ModelRendering DefaultModelRendering { get; set; } = ModelRendering.Example; /// /// Controls the display of the request duration (in milliseconds) for Try-It-Out requests /// + [JsonPropertyName("displayRequestDuration")] public bool DisplayRequestDuration { get; set; } = false; /// @@ -119,6 +128,7 @@ public class ConfigObject #if NET6_0_OR_GREATER [JsonConverter(typeof(JavascriptStringEnumConverter))] #endif + [JsonPropertyName("docExpansion")] public DocExpansion DocExpansion { get; set; } = DocExpansion.List; /// @@ -126,21 +136,25 @@ public class ConfigObject /// that are shown. Can be an empty string or specific value, in which case filtering will be enabled using that /// value as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag /// + [JsonPropertyName("filter")] public string Filter { get; set; } = null; /// /// If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations /// + [JsonPropertyName("maxDisplayedTags")] public int? MaxDisplayedTags { get; set; } = null; /// /// Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema /// + [JsonPropertyName("showExtensions")] public bool ShowExtensions { get; set; } = false; /// /// Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for Parameters /// + [JsonPropertyName("showCommonExtensions")] public bool ShowCommonExtensions { get; set; } = false; /// @@ -156,6 +170,7 @@ public class ConfigObject #if NET6_0_OR_GREATER [JsonConverter(typeof(JavascriptStringEnumEnumerableConverter))] #endif + [JsonPropertyName("supportedSubmitMethods")] public IEnumerable SupportedSubmitMethods { get; set; } = #if NET5_0_OR_GREATER Enum.GetValues(); @@ -174,6 +189,7 @@ public class ConfigObject /// You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator Badge). /// Setting it to null will disable validation /// + [JsonPropertyName("validatorUrl")] public string ValidatorUrl { get; set; } = null; [JsonExtensionData] @@ -182,8 +198,10 @@ public class ConfigObject public class UrlDescriptor { + [JsonPropertyName("url")] public string Url { get; set; } + [JsonPropertyName("name")] public string Name { get; set; } } @@ -222,37 +240,44 @@ public class OAuthConfigObject /// /// Default clientId /// + [JsonPropertyName("clientId")] public string ClientId { get; set; } = null; /// /// Default clientSecret /// /// Setting this exposes the client secrets in inline javascript in the swagger-ui generated html. + [JsonPropertyName("clientSecret")] public string ClientSecret { get; set; } = null; /// /// Realm query parameter (for oauth1) added to authorizationUrl and tokenUrl /// + [JsonPropertyName("realm")] public string Realm { get; set; } = null; /// /// Application name, displayed in authorization popup /// + [JsonPropertyName("appName")] public string AppName { get; set; } = null; /// /// Scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20) /// + [JsonPropertyName("scopeSeparator")] public string ScopeSeparator { get; set; } = " "; /// /// String array of initially selected oauth scopes, default is empty array /// + [JsonPropertyName("scopes")] public IEnumerable Scopes { get; set; } = []; /// /// Additional query parameters added to authorizationUrl and tokenUrl /// + [JsonPropertyName("additionalQueryStringParams")] public Dictionary AdditionalQueryStringParams { get; set; } = null; /// @@ -260,12 +285,14 @@ public class OAuthConfigObject /// pass the Client Password using the HTTP Basic Authentication scheme /// (Authorization header with Basic base64encode(client_id + client_secret)) /// + [JsonPropertyName("useBasicAuthenticationWithAccessCodeGrant")] public bool UseBasicAuthenticationWithAccessCodeGrant { get; set; } = false; /// /// Only applies to authorizatonCode flows. Proof Key for Code Exchange brings enhanced security for OAuth public clients. /// The default is false /// + [JsonPropertyName("usePkceWithAuthorizationCodeGrant")] public bool UsePkceWithAuthorizationCodeGrant { get; set; } = false; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs index d884b6cde6..a5b05fa946 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs @@ -1,7 +1,6 @@ using System; -using System.Text; -using System.Linq; using System.Collections.Generic; +using System.Text; using Swashbuckle.AspNetCore.SwaggerUI; namespace Microsoft.AspNetCore.Builder @@ -42,8 +41,10 @@ public static void InjectJavascript(this SwaggerUIOptions options, string path, /// The description that appears in the document selector drop-down public static void SwaggerEndpoint(this SwaggerUIOptions options, string url, string name) { - var urls = new List(options.ConfigObject.Urls ?? Enumerable.Empty()); - urls.Add(new UrlDescriptor { Url = url, Name = name }); + var urls = new List(options.ConfigObject.Urls ?? []) + { + new() { Url = url, Name = name } + }; options.ConfigObject.Urls = urls; } diff --git a/test/WebSites/OAuth2Integration/AuthServer/Config.cs b/test/WebSites/OAuth2Integration/AuthServer/Config.cs index 0f598c5f36..c51f6312d0 100644 --- a/test/WebSites/OAuth2Integration/AuthServer/Config.cs +++ b/test/WebSites/OAuth2Integration/AuthServer/Config.cs @@ -13,17 +13,19 @@ internal static IEnumerable Clients() ClientId = "test-id", ClientName = "Test client (Code with PKCE)", - RedirectUris = new[] { + RedirectUris = + [ "http://localhost:55202/resource-server/swagger/oauth2-redirect.html", // IIS Express - "http://localhost:5000/resource-server/swagger/oauth2-redirect.html", // Kestrel - }, + "http://localhost:5000/resource-server/swagger/oauth2-redirect.html", // Kestrel (HTTP) + "https://localhost:5001/resource-server/swagger/oauth2-redirect.html", // Kestrel (HTTPS) + ], ClientSecrets = { new Secret("test-secret".Sha256()) }, RequireConsent = true, AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, - AllowedScopes = new[] { "readAccess", "writeAccess" }, + AllowedScopes = ["readAccess", "writeAccess"], }; } @@ -33,25 +35,25 @@ internal static IEnumerable ApiResources() { Name = "api", DisplayName = "API", - Scopes = new[] - { + Scopes = + [ new Scope("readAccess", "Access read operations"), new Scope("writeAccess", "Access write operations") - } + ] }; } internal static List TestUsers() { - return new List - { + return + [ new TestUser { SubjectId = "joebloggs", Username = "joebloggs", Password = "pass123" } - }; + ]; } } } diff --git a/test/WebSites/OAuth2Integration/AuthServer/Controllers/ConsentController.cs b/test/WebSites/OAuth2Integration/AuthServer/Controllers/ConsentController.cs index 1b9db1b8aa..32e5652e3f 100644 --- a/test/WebSites/OAuth2Integration/AuthServer/Controllers/ConsentController.cs +++ b/test/WebSites/OAuth2Integration/AuthServer/Controllers/ConsentController.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Mvc; -using IdentityServer4.Stores; -using IdentityServer4.Services; +using System.Threading.Tasks; using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; namespace OAuth2Integration.AuthServer.Controllers { diff --git a/test/WebSites/OAuth2Integration/Program.cs b/test/WebSites/OAuth2Integration/Program.cs index ca6d56c728..ccf58763de 100644 --- a/test/WebSites/OAuth2Integration/Program.cs +++ b/test/WebSites/OAuth2Integration/Program.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace OAuth2Integration { diff --git a/test/WebSites/OAuth2Integration/Properties/launchSettings.json b/test/WebSites/OAuth2Integration/Properties/launchSettings.json index 78cb5cd016..5da0cd06eb 100644 --- a/test/WebSites/OAuth2Integration/Properties/launchSettings.json +++ b/test/WebSites/OAuth2Integration/Properties/launchSettings.json @@ -1,13 +1,4 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55202", - "sslPort": 0 - } - }, +{ "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -21,10 +12,19 @@ "commandName": "Project", "launchBrowser": true, "launchUrl": "resource-server/swagger", - "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "applicationUrl": "https://localhost:5001" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55202", + "sslPort": 0 } } } diff --git a/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs b/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs index a521ef0fec..f6e6b6a92a 100644 --- a/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs +++ b/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.Extensions.Options; @@ -28,9 +27,10 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) .Concat(context.MethodInfo.DeclaringType.GetCustomAttributes(true)) .OfType() .Select(attr => attr.Policy) + .Where(p => p != null) .Distinct(); - var requiredScopes = requiredPolicies.Select(p => _authorizationOptions.GetPolicy(p)) + var requiredScopes = requiredPolicies.Select(_authorizationOptions.GetPolicy) .SelectMany(r => r.Requirements.OfType()) .Where(cr => cr.ClaimType == "scope") .SelectMany(r => r.AllowedValues) @@ -47,13 +47,13 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } }; - operation.Security = new List - { + operation.Security = + [ new OpenApiSecurityRequirement { [ oAuthScheme ] = requiredScopes } - }; + ]; } } } diff --git a/test/WebSites/OAuth2Integration/Startup.cs b/test/WebSites/OAuth2Integration/Startup.cs index 3c37912feb..03d3cffd79 100644 --- a/test/WebSites/OAuth2Integration/Startup.cs +++ b/test/WebSites/OAuth2Integration/Startup.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; +using OAuth2Integration.ResourceServer.Swagger; namespace OAuth2Integration { @@ -31,12 +33,12 @@ public void ConfigureServices(IServiceCollection services) // The auth setup is a little nuanced because this app provides the auth-server & the resource-server // Use the "Cookies" scheme by default & explicitly require "Bearer" in the resource-server controllers - // See https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x + // See https://learn.microsoft.com/aspnet/core/security/authorization/limitingidentitybyscheme services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie() .AddIdentityServerAuthentication(c => { - c.Authority = "http://localhost:5000/auth-server/"; + c.Authority = "https://localhost:5001/auth-server/"; c.RequireHttpsMetadata = false; c.ApiName = "api"; }); @@ -80,12 +82,12 @@ public void ConfigureServices(IServiceCollection services) { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } }, - new[] { "readAccess", "writeAccess" } + ["readAccess", "writeAccess"] } }); // Assign scope requirements to operations based on AuthorizeAttribute - //c.OperationFilter(); + c.OperationFilter(); }); }