Skip to content

Commit 1332212

Browse files
Make infer security schemes opt-in
1 parent 71ed7d3 commit 1332212

File tree

5 files changed

+131
-119
lines changed

5 files changed

+131
-119
lines changed

src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.OpenApi.Models;
55
using Microsoft.AspNetCore.Mvc.ApiExplorer;
66
using Swashbuckle.AspNetCore.SwaggerGen;
7+
using Microsoft.AspNetCore.Authentication;
78

89
namespace Microsoft.Extensions.DependencyInjection
910
{
@@ -308,6 +309,22 @@ public static void SupportNonNullableReferenceTypes(this SwaggerGenOptions swagg
308309
swaggerGenOptions.SchemaGeneratorOptions.SupportNonNullableReferenceTypes = true;
309310
}
310311

312+
/// <summary>
313+
/// Automatically infer security schemes from authentication/authorization state in ASP.NET Core.
314+
/// </summary>
315+
/// <param name="swaggerGenOptions"></param>
316+
/// <param name="securitySchemesSelector">
317+
/// Provide alternative implementation that maps ASP.NET Core Authentication schemes to Open API security schemes
318+
/// </param>
319+
/// <remarks>Currently only supports JWT Bearer authentication</remarks>
320+
public static void InferSecuritySchemes(
321+
this SwaggerGenOptions swaggerGenOptions,
322+
Func<IEnumerable<AuthenticationScheme>, IDictionary<string, OpenApiSecurityScheme>> securitySchemesSelector = null)
323+
{
324+
swaggerGenOptions.SwaggerGeneratorOptions.InferSecuritySchemes = true;
325+
swaggerGenOptions.SwaggerGeneratorOptions.SecuritySchemesSelector = securitySchemesSelector;
326+
}
327+
311328
/// <summary>
312329
/// Extend the Swagger Generator with "filters" that can modify Schemas after they're initially generated
313330
/// </summary>

src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs

+25-18
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public SwaggerGenerator(
3434
SwaggerGeneratorOptions options,
3535
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
3636
ISchemaGenerator schemaGenerator,
37-
IAuthenticationSchemeProvider authentiationSchemeProvider) : this(options, apiDescriptionsProvider, schemaGenerator)
37+
IAuthenticationSchemeProvider authenticationSchemeProvider) : this(options, apiDescriptionsProvider, schemaGenerator)
3838
{
39-
_authenticationSchemeProvider = authentiationSchemeProvider;
39+
_authenticationSchemeProvider = authenticationSchemeProvider;
4040
}
4141

4242
public async Task<OpenApiDocument> GetSwaggerAsync(string documentName, string host = null, string basePath = null)
@@ -88,27 +88,34 @@ public OpenApiDocument GetSwagger(string documentName, string host = null, strin
8888
return (applicableApiDescriptions, swaggerDoc, schemaRepository);
8989
}
9090

91-
private async Task<Dictionary<string, OpenApiSecurityScheme>> GetSecuritySchemes()
91+
private async Task<IDictionary<string, OpenApiSecurityScheme>> GetSecuritySchemes()
9292
{
93-
var securitySchemes = new Dictionary<string, OpenApiSecurityScheme>(_options.SecuritySchemes);
94-
var authenticationSchemes = Enumerable.Empty<AuthenticationScheme>();
95-
if (_authenticationSchemeProvider is not null)
93+
if (!_options.InferSecuritySchemes)
9694
{
97-
authenticationSchemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
95+
return new Dictionary<string, OpenApiSecurityScheme>(_options.SecuritySchemes);
9896
}
99-
var securitySchemesFromSelector = _options.SecuritySchemesSelector(authenticationSchemes);
100-
// Favor security schemes set via options over those generated
101-
// from the selector. For the default selector, this effectively
102-
// ends up favoring `Bearer` authentication types explicitly set
103-
// by the user over those derived by the selector.
104-
foreach (var securityScheme in securitySchemesFromSelector)
97+
98+
var authenticationSchemes = (_authenticationSchemeProvider is not null)
99+
? await _authenticationSchemeProvider.GetAllSchemesAsync()
100+
: Enumerable.Empty<AuthenticationScheme>();
101+
102+
if (_options.SecuritySchemesSelector != null)
105103
{
106-
if (!securitySchemes.ContainsKey(securityScheme.Key))
107-
{
108-
securitySchemes.Add(securityScheme.Key, securityScheme.Value);
109-
}
104+
return _options.SecuritySchemesSelector(authenticationSchemes);
110105
}
111-
return securitySchemes;
106+
107+
// Default implementation, currently only supports JWT Bearer scheme
108+
return authenticationSchemes
109+
.Where(authScheme => authScheme.Name == "Bearer")
110+
.ToDictionary(
111+
(authScheme) => authScheme.Name,
112+
(authScheme) => new OpenApiSecurityScheme
113+
{
114+
Type = SecuritySchemeType.Http,
115+
Scheme = "bearer", // "bearer" refers to the header name here
116+
In = ParameterLocation.Header,
117+
BearerFormat = "Json Web Token"
118+
});
112119
}
113120

114121
private IList<OpenApiServer> GenerateServers(string host, string basePath)

src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs

+5-24
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public SwaggerGeneratorOptions()
2020
OperationIdSelector = DefaultOperationIdSelector;
2121
TagsSelector = DefaultTagsSelector;
2222
SortKeySelector = DefaultSortKeySelector;
23-
SecuritySchemesSelector = DefaultSecuritySchemeSelector;
23+
SecuritySchemesSelector = null;
2424
SchemaComparer = StringComparer.Ordinal;
2525
Servers = new List<OpenApiServer>();
2626
SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>();
@@ -45,6 +45,10 @@ public SwaggerGeneratorOptions()
4545

4646
public Func<ApiDescription, string> SortKeySelector { get; set; }
4747

48+
public bool InferSecuritySchemes { get; set; }
49+
50+
public Func<IEnumerable<AuthenticationScheme>, IDictionary<string, OpenApiSecurityScheme>> SecuritySchemesSelector { get; set;}
51+
4852
public bool DescribeAllParametersInCamelCase { get; set; }
4953

5054
public List<OpenApiServer> Servers { get; set; }
@@ -63,8 +67,6 @@ public SwaggerGeneratorOptions()
6367

6468
public IList<IDocumentFilter> DocumentFilters { get; set; }
6569

66-
public Func<IEnumerable<AuthenticationScheme>, Dictionary<string, OpenApiSecurityScheme>> SecuritySchemesSelector { get; set;}
67-
6870
private bool DefaultDocInclusionPredicate(string documentName, ApiDescription apiDescription)
6971
{
7072
return apiDescription.GroupName == null || apiDescription.GroupName == documentName;
@@ -106,26 +108,5 @@ private string DefaultSortKeySelector(ApiDescription apiDescription)
106108
{
107109
return TagsSelector(apiDescription).First();
108110
}
109-
110-
private Dictionary<string, OpenApiSecurityScheme> DefaultSecuritySchemeSelector(IEnumerable<AuthenticationScheme> schemes)
111-
{
112-
Dictionary<string, OpenApiSecurityScheme> securitySchemes = new();
113-
#if (NET6_0_OR_GREATER)
114-
foreach (var scheme in schemes)
115-
{
116-
if (scheme.Name == "Bearer")
117-
{
118-
securitySchemes[scheme.Name] = new OpenApiSecurityScheme
119-
{
120-
Type = SecuritySchemeType.Http,
121-
Scheme = "bearer", // "bearer" refers to the header name here
122-
In = ParameterLocation.Header,
123-
BearerFormat = "Json Web Token"
124-
};
125-
}
126-
}
127-
#endif
128-
return securitySchemes;
129-
}
130111
}
131112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authentication;
6+
7+
namespace Swashbuckle.AspNetCore.SwaggerGen.Test
8+
{
9+
public class FakeAuthenticationSchemeProvider : IAuthenticationSchemeProvider
10+
{
11+
private readonly IEnumerable<AuthenticationScheme> _authenticationSchemes;
12+
13+
public FakeAuthenticationSchemeProvider(IEnumerable<AuthenticationScheme> authenticationSchemes)
14+
{
15+
_authenticationSchemes = authenticationSchemes;
16+
}
17+
18+
public void AddScheme(AuthenticationScheme scheme)
19+
=> throw new NotImplementedException();
20+
public Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
21+
=> Task.FromResult(_authenticationSchemes);
22+
23+
public Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
24+
=> Task.FromResult(_authenticationSchemes.First());
25+
26+
public Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
27+
=> Task.FromResult(_authenticationSchemes.First());
28+
29+
public Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
30+
=> Task.FromResult(_authenticationSchemes.First());
31+
32+
public Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
33+
=> Task.FromResult(_authenticationSchemes.First());
34+
35+
public Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
36+
=> Task.FromResult(_authenticationSchemes.First());
37+
38+
public Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
39+
=> throw new NotImplementedException();
40+
41+
public Task<AuthenticationScheme> GetSchemeAsync(string name)
42+
=> Task.FromResult(_authenticationSchemes.First());
43+
44+
public void RemoveScheme(string name)
45+
=> throw new NotImplementedException();
46+
}
47+
}

test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs

+37-77
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
using Swashbuckle.AspNetCore.TestSupport;
1414
using Xunit;
1515
using System.Threading.Tasks;
16-
using Microsoft.AspNetCore.Authentication;
1716
using Microsoft.AspNetCore.Server.HttpSys;
17+
using Microsoft.AspNetCore.Authentication;
1818

1919
namespace Swashbuckle.AspNetCore.SwaggerGen.Test
2020
{
@@ -1081,76 +1081,70 @@ public void GetSwagger_SupportsOption_SecuritySchemes()
10811081

10821082
var document = subject.GetSwagger("v1");
10831083

1084-
Assert.Equal(new[] { "basic", "Bearer" }, document.Components.SecuritySchemes.Keys);
1085-
}
1086-
1087-
[Fact]
1088-
public async Task GetSwagger_SupportsSecuritySchemesSelector()
1089-
{
1090-
var subject = Subject(
1091-
apiDescriptions: new ApiDescription[] { },
1092-
options: new SwaggerGeneratorOptions
1093-
{
1094-
SwaggerDocs = new Dictionary<string, OpenApiInfo>
1095-
{
1096-
["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
1097-
},
1098-
SecuritySchemesSelector = (schemes) => new Dictionary<string, OpenApiSecurityScheme>
1099-
{
1100-
["basic"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "basic" }
1101-
}
1102-
}
1103-
);
1104-
1105-
var document = await subject.GetSwaggerAsync("v1");
1106-
1107-
// Overrides the default set of [basic, bearer] with just [basic]
11081084
Assert.Equal(new[] { "basic" }, document.Components.SecuritySchemes.Keys);
11091085
}
11101086

1111-
[Fact]
1112-
public async Task GetSwagger_DefaultSecuritySchemeSelectorAddsBearerByDefault()
1087+
[Theory]
1088+
[InlineData(false, new string[] { })]
1089+
[InlineData(true, new string[] { "Bearer" })]
1090+
public async Task GetSwagger_SupportsOption_InferSecuritySchemes(
1091+
bool inferSecuritySchemes,
1092+
string[] expectedSecuritySchemeNames)
1093+
11131094
{
11141095
var subject = Subject(
11151096
apiDescriptions: new ApiDescription[] { },
1097+
authenticationSchemes: new[] {
1098+
new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler)),
1099+
new AuthenticationScheme("Cookies", null, typeof(IAuthenticationHandler))
1100+
},
11161101
options: new SwaggerGeneratorOptions
11171102
{
11181103
SwaggerDocs = new Dictionary<string, OpenApiInfo>
11191104
{
11201105
["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
11211106
},
1107+
InferSecuritySchemes = inferSecuritySchemes
11221108
}
11231109
);
11241110

11251111
var document = await subject.GetSwaggerAsync("v1");
11261112

1127-
Assert.Equal(new[] { "Bearer" }, document.Components.SecuritySchemes.Keys);
1113+
Assert.Equal(expectedSecuritySchemeNames, document.Components.SecuritySchemes.Keys);
11281114
}
11291115

1130-
[Fact]
1131-
public async Task GetSwagger_DefaultSecuritySchemesSelectorDoesNotOverrideBearer()
1116+
[Theory]
1117+
[InlineData(false, new string[] { })]
1118+
[InlineData(true, new string[] { "Bearer", "Cookies" })]
1119+
public async Task GetSwagger_SupportsOption_SecuritySchemesSelector(
1120+
bool inferSecuritySchemes,
1121+
string[] expectedSecuritySchemeNames)
1122+
11321123
{
11331124
var subject = Subject(
11341125
apiDescriptions: new ApiDescription[] { },
1126+
authenticationSchemes: new[] {
1127+
new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler)),
1128+
new AuthenticationScheme("Cookies", null, typeof(IAuthenticationHandler))
1129+
},
11351130
options: new SwaggerGeneratorOptions
11361131
{
11371132
SwaggerDocs = new Dictionary<string, OpenApiInfo>
11381133
{
11391134
["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
11401135
},
1141-
SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>
1142-
{
1143-
["Bearer"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, Scheme = "someSpecialOne" }
1144-
}
1136+
InferSecuritySchemes = inferSecuritySchemes,
1137+
SecuritySchemesSelector = (authenticationSchemes) =>
1138+
authenticationSchemes
1139+
.ToDictionary(
1140+
(authScheme) => authScheme.Name,
1141+
(authScheme) => new OpenApiSecurityScheme())
11451142
}
11461143
);
11471144

11481145
var document = await subject.GetSwaggerAsync("v1");
11491146

1150-
var securityScheme = Assert.Single(document.Components.SecuritySchemes);
1151-
Assert.Equal("Bearer", securityScheme.Key);
1152-
Assert.Equal(SecuritySchemeType.ApiKey, securityScheme.Value.Type);
1153-
Assert.Equal("someSpecialOne", securityScheme.Value.Scheme);
1147+
Assert.Equal(expectedSecuritySchemeNames, document.Components.SecuritySchemes.Keys);
11541148
}
11551149

11561150
[Fact]
@@ -1283,13 +1277,16 @@ public void GetSwagger_SupportsOption_DocumentFilters()
12831277
Assert.Contains("ComplexType", document.Components.Schemas.Keys);
12841278
}
12851279

1286-
private SwaggerGenerator Subject(IEnumerable<ApiDescription> apiDescriptions, SwaggerGeneratorOptions options = null)
1280+
private SwaggerGenerator Subject(
1281+
IEnumerable<ApiDescription> apiDescriptions,
1282+
SwaggerGeneratorOptions options = null,
1283+
IEnumerable<AuthenticationScheme> authenticationSchemes = null)
12871284
{
12881285
return new SwaggerGenerator(
12891286
options ?? DefaultOptions,
12901287
new FakeApiDescriptionGroupCollectionProvider(apiDescriptions),
12911288
new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
1292-
new TestAuthenticationSchemeProvider()
1289+
new FakeAuthenticationSchemeProvider(authenticationSchemes ?? Enumerable.Empty<AuthenticationScheme>())
12931290
);
12941291
}
12951292

@@ -1301,41 +1298,4 @@ private SwaggerGenerator Subject(IEnumerable<ApiDescription> apiDescriptions, Sw
13011298
}
13021299
};
13031300
}
1304-
1305-
class TestAuthenticationSchemeProvider : IAuthenticationSchemeProvider
1306-
{
1307-
private readonly IEnumerable<AuthenticationScheme> _authenticationSchemes = new AuthenticationScheme[]
1308-
{
1309-
new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler))
1310-
};
1311-
1312-
public void AddScheme(AuthenticationScheme scheme)
1313-
=> throw new NotImplementedException();
1314-
public Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
1315-
=> Task.FromResult(_authenticationSchemes);
1316-
1317-
public Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
1318-
=> Task.FromResult(_authenticationSchemes.First());
1319-
1320-
public Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
1321-
=> Task.FromResult(_authenticationSchemes.First());
1322-
1323-
public Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
1324-
=> Task.FromResult(_authenticationSchemes.First());
1325-
1326-
public Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
1327-
=> Task.FromResult(_authenticationSchemes.First());
1328-
1329-
public Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
1330-
=> Task.FromResult(_authenticationSchemes.First());
1331-
1332-
public Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
1333-
=> throw new NotImplementedException();
1334-
1335-
public Task<AuthenticationScheme> GetSchemeAsync(string name)
1336-
=> Task.FromResult(_authenticationSchemes.First());
1337-
1338-
public void RemoveScheme(string name)
1339-
=> throw new NotImplementedException();
1340-
}
13411301
}

0 commit comments

Comments
 (0)