Skip to content

Commit ee26a70

Browse files
authored
[BEEEP] [PM-28808] Fix invalid identity URL in Swagger (#6653)
- in generated JSON (used in help center), only show cloud options (with corrected identity URL) - in self-host and dev, only show local option
1 parent 89a2eab commit ee26a70

4 files changed

Lines changed: 127 additions & 37 deletions

File tree

dev/generate_openapi_files.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ if ($LASTEXITCODE -ne 0) {
1818
# Api internal & public
1919
Set-Location "../../src/Api"
2020
dotnet build
21-
dotnet swagger tofile --output "../../api.json" --host "https://api.bitwarden.com" "./bin/Debug/net8.0/Api.dll" "internal"
21+
dotnet swagger tofile --output "../../api.json" "./bin/Debug/net8.0/Api.dll" "internal"
2222
if ($LASTEXITCODE -ne 0) {
2323
exit $LASTEXITCODE
2424
}
25-
dotnet swagger tofile --output "../../api.public.json" --host "https://api.bitwarden.com" "./bin/Debug/net8.0/Api.dll" "public"
25+
dotnet swagger tofile --output "../../api.public.json" "./bin/Debug/net8.0/Api.dll" "public"
2626
if ($LASTEXITCODE -ne 0) {
2727
exit $LASTEXITCODE
2828
}

src/Api/Startup.cs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public void ConfigureServices(IServiceCollection services)
216216
config.Conventions.Add(new PublicApiControllersModelConvention());
217217
});
218218

219-
services.AddSwagger(globalSettings, Environment);
219+
services.AddSwaggerGen(globalSettings, Environment);
220220
Jobs.JobsHostedService.AddJobsServices(services, globalSettings.SelfHosted);
221221
services.AddHostedService<Jobs.JobsHostedService>();
222222

@@ -292,17 +292,59 @@ public void Configure(
292292
});
293293

294294
// Add Swagger
295+
// Note that the swagger.json generation is configured in the call to AddSwaggerGen above.
295296
if (Environment.IsDevelopment() || globalSettings.SelfHosted)
296297
{
298+
// adds the middleware to serve the swagger.json while the server is running
297299
app.UseSwagger(config =>
298300
{
299301
config.RouteTemplate = "specs/{documentName}/swagger.json";
302+
303+
// Remove all Bitwarden cloud servers and only register the local server
300304
config.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
301-
swaggerDoc.Servers = new List<OpenApiServer>
305+
{
306+
swaggerDoc.Servers.Clear();
307+
swaggerDoc.Servers.Add(new OpenApiServer
308+
{
309+
Url = globalSettings.BaseServiceUri.Api,
310+
});
311+
312+
swaggerDoc.Components.SecuritySchemes.Clear();
313+
swaggerDoc.Components.SecuritySchemes.Add("oauth2-client-credentials", new OpenApiSecurityScheme
302314
{
303-
new OpenApiServer { Url = globalSettings.BaseServiceUri.Api }
315+
Type = SecuritySchemeType.OAuth2,
316+
Flows = new OpenApiOAuthFlows
317+
{
318+
ClientCredentials = new OpenApiOAuthFlow
319+
{
320+
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
321+
Scopes = new Dictionary<string, string>
322+
{
323+
{ ApiScopes.ApiOrganization, "Organization APIs" }
324+
}
325+
}
326+
}
304327
});
328+
329+
swaggerDoc.SecurityRequirements.Clear();
330+
swaggerDoc.SecurityRequirements.Add(new OpenApiSecurityRequirement
331+
{
332+
{
333+
new OpenApiSecurityScheme
334+
{
335+
Reference = new OpenApiReference
336+
{
337+
Type = ReferenceType.SecurityScheme,
338+
Id = "oauth2-client-credentials"
339+
}
340+
},
341+
[ApiScopes.ApiOrganization]
342+
}
343+
});
344+
});
305345
});
346+
347+
// adds the middleware to display the web UI
306348
app.UseSwaggerUI(config =>
307349
{
308350
config.DocumentTitle = "Bitwarden API Documentation";

src/Api/Utilities/ServiceCollectionExtensions.cs

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Bit.Api.AdminConsole.Authorization;
22
using Bit.Api.Tools.Authorization;
3-
using Bit.Core.Auth.IdentityServer;
43
using Bit.Core.PhishingDomainFeatures;
54
using Bit.Core.PhishingDomainFeatures.Interfaces;
65
using Bit.Core.Repositories;
@@ -10,14 +9,18 @@
109
using Bit.Core.Vault.Authorization.SecurityTasks;
1110
using Bit.SharedWeb.Health;
1211
using Bit.SharedWeb.Swagger;
12+
using Bit.SharedWeb.Utilities;
1313
using Microsoft.AspNetCore.Authorization;
1414
using Microsoft.OpenApi.Models;
1515

1616
namespace Bit.Api.Utilities;
1717

1818
public static class ServiceCollectionExtensions
1919
{
20-
public static void AddSwagger(this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment)
20+
/// <summary>
21+
/// Configures the generation of swagger.json OpenAPI spec.
22+
/// </summary>
23+
public static void AddSwaggerGen(this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment)
2124
{
2225
services.AddSwaggerGen(config =>
2326
{
@@ -36,6 +39,8 @@ public static void AddSwagger(this IServiceCollection services, GlobalSettings g
3639
organizations tools for managing members, collections, groups, event logs, and policies.
3740
If you are looking for the Vault Management API, refer instead to
3841
[this document](https://bitwarden.com/help/vault-management-api/).
42+
43+
**Note:** your authorization must match the server you have selected.
3944
""",
4045
License = new OpenApiLicense
4146
{
@@ -46,36 +51,20 @@ [this document](https://bitwarden.com/help/vault-management-api/).
4651

4752
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
4853

49-
config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme
50-
{
51-
Type = SecuritySchemeType.OAuth2,
52-
Flows = new OpenApiOAuthFlows
53-
{
54-
ClientCredentials = new OpenApiOAuthFlow
55-
{
56-
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
57-
Scopes = new Dictionary<string, string>
58-
{
59-
{ ApiScopes.ApiOrganization, "Organization APIs" },
60-
},
61-
}
62-
},
63-
});
64-
65-
config.AddSecurityRequirement(new OpenApiSecurityRequirement
66-
{
67-
{
68-
new OpenApiSecurityScheme
69-
{
70-
Reference = new OpenApiReference
71-
{
72-
Type = ReferenceType.SecurityScheme,
73-
Id = "oauth2-client-credentials"
74-
},
75-
},
76-
new[] { ApiScopes.ApiOrganization }
77-
}
78-
});
54+
// Configure Bitwarden cloud US and EU servers. These will appear in the swagger.json build artifact
55+
// used for our help center. These are overwritten with the local server when running in self-hosted
56+
// or dev mode (see Api Startup.cs).
57+
config.AddSwaggerServerWithSecurity(
58+
serverId: "US_server",
59+
serverUrl: "https://api.bitwarden.com",
60+
identityTokenUrl: "https://identity.bitwarden.com/connect/token",
61+
serverDescription: "US server");
62+
63+
config.AddSwaggerServerWithSecurity(
64+
serverId: "EU_server",
65+
serverUrl: "https://api.bitwarden.eu",
66+
identityTokenUrl: "https://identity.bitwarden.eu/connect/token",
67+
serverDescription: "EU server");
7968

8069
config.DescribeAllParametersInCamelCase();
8170
// config.UseReferencedDefinitionsForEnums();

src/SharedWeb/Utilities/ServiceCollectionExtensions.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@
8585
using Microsoft.Extensions.Hosting;
8686
using Microsoft.Extensions.Logging;
8787
using Microsoft.Extensions.Options;
88+
using Microsoft.OpenApi.Models;
8889
using StackExchange.Redis;
90+
using Swashbuckle.AspNetCore.SwaggerGen;
8991
using ZiggyCreatures.Caching.Fusion;
9092
using NoopRepos = Bit.Core.Repositories.Noop;
9193
using Role = Bit.Core.Entities.Role;
@@ -1067,4 +1069,61 @@ private static bool IsRabbitMqEnabled(GlobalSettings settings)
10671069
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
10681070
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
10691071
}
1072+
1073+
/// <summary>
1074+
/// Adds a server with its corresponding OAuth2 client credentials security definition and requirement.
1075+
/// </summary>
1076+
/// <param name="config">The SwaggerGen configuration</param>
1077+
/// <param name="serverId">Unique identifier for this server (e.g., "us-server", "eu-server")</param>
1078+
/// <param name="serverUrl">The API server URL</param>
1079+
/// <param name="identityTokenUrl">The identity server token URL</param>
1080+
/// <param name="serverDescription">Human-readable description for the server</param>
1081+
public static void AddSwaggerServerWithSecurity(
1082+
this SwaggerGenOptions config,
1083+
string serverId,
1084+
string serverUrl,
1085+
string identityTokenUrl,
1086+
string serverDescription)
1087+
{
1088+
// Add server
1089+
config.AddServer(new OpenApiServer
1090+
{
1091+
Url = serverUrl,
1092+
Description = serverDescription
1093+
});
1094+
1095+
// Add security definition
1096+
config.AddSecurityDefinition(serverId, new OpenApiSecurityScheme
1097+
{
1098+
Type = SecuritySchemeType.OAuth2,
1099+
Description = $"**Use this option if you've selected the {serverDescription}**",
1100+
Flows = new OpenApiOAuthFlows
1101+
{
1102+
ClientCredentials = new OpenApiOAuthFlow
1103+
{
1104+
TokenUrl = new Uri(identityTokenUrl),
1105+
Scopes = new Dictionary<string, string>
1106+
{
1107+
{ ApiScopes.ApiOrganization, $"Organization APIs ({serverDescription})" },
1108+
},
1109+
}
1110+
},
1111+
});
1112+
1113+
// Add security requirement
1114+
config.AddSecurityRequirement(new OpenApiSecurityRequirement
1115+
{
1116+
{
1117+
new OpenApiSecurityScheme
1118+
{
1119+
Reference = new OpenApiReference
1120+
{
1121+
Type = ReferenceType.SecurityScheme,
1122+
Id = serverId
1123+
},
1124+
},
1125+
[ApiScopes.ApiOrganization]
1126+
}
1127+
});
1128+
}
10701129
}

0 commit comments

Comments
 (0)