Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swagger UI refactoring #2942

Merged
merged 6 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static IApplicationBuilder UseSwaggerUI(
if (options.ConfigObject.Urls == null)
{
var hostingEnv = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
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);
Expand Down
27 changes: 27 additions & 0 deletions src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,37 @@ public class ConfigObject
/// <summary>
/// One or more Swagger JSON endpoints (url and name) to power the UI
/// </summary>
[JsonPropertyName("urls")]
public IEnumerable<UrlDescriptor> Urls { get; set; } = null;

/// <summary>
/// If set to true, enables deep linking for tags and operations
/// </summary>
[JsonPropertyName("deepLinking")]
public bool DeepLinking { get; set; } = false;

/// <summary>
/// If set to true, it persists authorization data and it would not be lost on browser close/refresh
/// </summary>
[JsonPropertyName("persistAuthorization")]
public bool PersistAuthorization { get; set; } = false;

/// <summary>
/// Controls the display of operationId in operations list
/// </summary>
[JsonPropertyName("displayOperationId")]
public bool DisplayOperationId { get; set; } = false;

/// <summary>
/// The default expansion depth for models (set to -1 completely hide the models)
/// </summary>
[JsonPropertyName("defaultModelsExpandDepth")]
public int DefaultModelsExpandDepth { get; set; } = 1;

/// <summary>
/// The default expansion depth for the model on the model-example section
/// </summary>
[JsonPropertyName("defaultModelExpandDepth")]
public int DefaultModelExpandDepth { get; set; } = 1;

/// <summary>
Expand All @@ -105,11 +112,13 @@ public class ConfigObject
#if NET6_0_OR_GREATER
[JsonConverter(typeof(JavascriptStringEnumConverter<ModelRendering>))]
#endif
[JsonPropertyName("defaultModelRendering")]
public ModelRendering DefaultModelRendering { get; set; } = ModelRendering.Example;

/// <summary>
/// Controls the display of the request duration (in milliseconds) for Try-It-Out requests
/// </summary>
[JsonPropertyName("displayRequestDuration")]
public bool DisplayRequestDuration { get; set; } = false;

/// <summary>
Expand All @@ -119,28 +128,33 @@ public class ConfigObject
#if NET6_0_OR_GREATER
[JsonConverter(typeof(JavascriptStringEnumConverter<DocExpansion>))]
#endif
[JsonPropertyName("docExpansion")]
public DocExpansion DocExpansion { get; set; } = DocExpansion.List;

/// <summary>
/// If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations
/// 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
/// </summary>
[JsonPropertyName("filter")]
public string Filter { get; set; } = null;

/// <summary>
/// If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations
/// </summary>
[JsonPropertyName("maxDisplayedTags")]
public int? MaxDisplayedTags { get; set; } = null;

/// <summary>
/// Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema
/// </summary>
[JsonPropertyName("showExtensions")]
public bool ShowExtensions { get; set; } = false;

/// <summary>
/// Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for Parameters
/// </summary>
[JsonPropertyName("showCommonExtensions")]
public bool ShowCommonExtensions { get; set; } = false;

/// <summary>
Expand All @@ -156,6 +170,7 @@ public class ConfigObject
#if NET6_0_OR_GREATER
[JsonConverter(typeof(JavascriptStringEnumEnumerableConverter<SubmitMethod>))]
#endif
[JsonPropertyName("supportedSubmitMethods")]
public IEnumerable<SubmitMethod> SupportedSubmitMethods { get; set; } =
#if NET5_0_OR_GREATER
Enum.GetValues<SubmitMethod>();
Expand All @@ -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
/// </summary>
[JsonPropertyName("validatorUrl")]
public string ValidatorUrl { get; set; } = null;

[JsonExtensionData]
Expand All @@ -182,8 +198,10 @@ public class ConfigObject

public class UrlDescriptor
{
[JsonPropertyName("url")]
public string Url { get; set; }

[JsonPropertyName("name")]
public string Name { get; set; }
}

Expand Down Expand Up @@ -222,50 +240,59 @@ public class OAuthConfigObject
/// <summary>
/// Default clientId
/// </summary>
[JsonPropertyName("clientId")]
public string ClientId { get; set; } = null;

/// <summary>
/// Default clientSecret
/// </summary>
/// <remarks>Setting this exposes the client secrets in inline javascript in the swagger-ui generated html.</remarks>
[JsonPropertyName("clientSecret")]
public string ClientSecret { get; set; } = null;

/// <summary>
/// Realm query parameter (for oauth1) added to authorizationUrl and tokenUrl
/// </summary>
[JsonPropertyName("realm")]
public string Realm { get; set; } = null;

/// <summary>
/// Application name, displayed in authorization popup
/// </summary>
[JsonPropertyName("appName")]
public string AppName { get; set; } = null;

/// <summary>
/// Scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20)
/// </summary>
[JsonPropertyName("scopeSeparator")]
public string ScopeSeparator { get; set; } = " ";

/// <summary>
/// String array of initially selected oauth scopes, default is empty array
/// </summary>
[JsonPropertyName("scopes")]
public IEnumerable<string> Scopes { get; set; } = [];

/// <summary>
/// Additional query parameters added to authorizationUrl and tokenUrl
/// </summary>
[JsonPropertyName("additionalQueryStringParams")]
public Dictionary<string, string> AdditionalQueryStringParams { get; set; } = null;

/// <summary>
/// Only activated for the accessCode flow. During the authorization_code request to the tokenUrl,
/// pass the Client Password using the HTTP Basic Authentication scheme
/// (Authorization header with Basic base64encode(client_id + client_secret))
/// </summary>
[JsonPropertyName("useBasicAuthenticationWithAccessCodeGrant")]
public bool UseBasicAuthenticationWithAccessCodeGrant { get; set; } = false;

/// <summary>
/// Only applies to authorizatonCode flows. Proof Key for Code Exchange brings enhanced security for OAuth public clients.
/// The default is false
/// </summary>
[JsonPropertyName("usePkceWithAuthorizationCodeGrant")]
public bool UsePkceWithAuthorizationCodeGrant { get; set; } = false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -42,8 +41,10 @@ public static void InjectJavascript(this SwaggerUIOptions options, string path,
/// <param name="name">The description that appears in the document selector drop-down</param>
public static void SwaggerEndpoint(this SwaggerUIOptions options, string url, string name)
{
var urls = new List<UrlDescriptor>(options.ConfigObject.Urls ?? Enumerable.Empty<UrlDescriptor>());
urls.Add(new UrlDescriptor { Url = url, Name = name });
var urls = new List<UrlDescriptor>(options.ConfigObject.Urls ?? [])
{
new() { Url = url, Name = name }
};
options.ConfigObject.Urls = urls;
}

Expand Down
22 changes: 12 additions & 10 deletions test/WebSites/OAuth2Integration/AuthServer/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ internal static IEnumerable<Client> 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"],
};
}

Expand All @@ -33,25 +35,25 @@ internal static IEnumerable<ApiResource> ApiResources()
{
Name = "api",
DisplayName = "API",
Scopes = new[]
{
Scopes =
[
new Scope("readAccess", "Access read operations"),
new Scope("writeAccess", "Access write operations")
}
]
};
}

internal static List<TestUser> TestUsers()
{
return new List<TestUser>
{
return
[
new TestUser
{
SubjectId = "joebloggs",
Username = "joebloggs",
Password = "pass123"
}
};
];
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
6 changes: 0 additions & 6 deletions test/WebSites/OAuth2Integration/Program.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
24 changes: 12 additions & 12 deletions test/WebSites/OAuth2Integration/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -28,9 +27,10 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
.Concat(context.MethodInfo.DeclaringType.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>()
.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<ClaimsAuthorizationRequirement>())
.Where(cr => cr.ClaimType == "scope")
.SelectMany(r => r.AllowedValues)
Expand All @@ -47,13 +47,13 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};

operation.Security = new List<OpenApiSecurityRequirement>
{
operation.Security =
[
new OpenApiSecurityRequirement
{
[ oAuthScheme ] = requiredScopes
}
};
];
}
}
}
Expand Down
Loading