1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[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
This commit is contained in:
Thomas Rittson
2025-12-03 09:20:56 +10:00
committed by GitHub
parent 89a2eab32a
commit ee26a701e9
4 changed files with 126 additions and 36 deletions

View File

@@ -18,11 +18,11 @@ if ($LASTEXITCODE -ne 0) {
# Api internal & public # Api internal & public
Set-Location "../../src/Api" Set-Location "../../src/Api"
dotnet build dotnet build
dotnet swagger tofile --output "../../api.json" --host "https://api.bitwarden.com" "./bin/Debug/net8.0/Api.dll" "internal" dotnet swagger tofile --output "../../api.json" "./bin/Debug/net8.0/Api.dll" "internal"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE exit $LASTEXITCODE
} }
dotnet swagger tofile --output "../../api.public.json" --host "https://api.bitwarden.com" "./bin/Debug/net8.0/Api.dll" "public" dotnet swagger tofile --output "../../api.public.json" "./bin/Debug/net8.0/Api.dll" "public"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE exit $LASTEXITCODE
} }

View File

@@ -216,7 +216,7 @@ public class Startup
config.Conventions.Add(new PublicApiControllersModelConvention()); config.Conventions.Add(new PublicApiControllersModelConvention());
}); });
services.AddSwagger(globalSettings, Environment); services.AddSwaggerGen(globalSettings, Environment);
Jobs.JobsHostedService.AddJobsServices(services, globalSettings.SelfHosted); Jobs.JobsHostedService.AddJobsServices(services, globalSettings.SelfHosted);
services.AddHostedService<Jobs.JobsHostedService>(); services.AddHostedService<Jobs.JobsHostedService>();
@@ -292,17 +292,59 @@ public class Startup
}); });
// Add Swagger // Add Swagger
// Note that the swagger.json generation is configured in the call to AddSwaggerGen above.
if (Environment.IsDevelopment() || globalSettings.SelfHosted) if (Environment.IsDevelopment() || globalSettings.SelfHosted)
{ {
// adds the middleware to serve the swagger.json while the server is running
app.UseSwagger(config => app.UseSwagger(config =>
{ {
config.RouteTemplate = "specs/{documentName}/swagger.json"; config.RouteTemplate = "specs/{documentName}/swagger.json";
// Remove all Bitwarden cloud servers and only register the local server
config.PreSerializeFilters.Add((swaggerDoc, httpReq) => config.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
swaggerDoc.Servers = new List<OpenApiServer>
{ {
new OpenApiServer { Url = globalSettings.BaseServiceUri.Api } swaggerDoc.Servers.Clear();
swaggerDoc.Servers.Add(new OpenApiServer
{
Url = globalSettings.BaseServiceUri.Api,
});
swaggerDoc.Components.SecuritySchemes.Clear();
swaggerDoc.Components.SecuritySchemes.Add("oauth2-client-credentials", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
Scopes = new Dictionary<string, string>
{
{ ApiScopes.ApiOrganization, "Organization APIs" }
}
}
}
});
swaggerDoc.SecurityRequirements.Clear();
swaggerDoc.SecurityRequirements.Add(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2-client-credentials"
}
},
[ApiScopes.ApiOrganization]
}
}); });
}); });
});
// adds the middleware to display the web UI
app.UseSwaggerUI(config => app.UseSwaggerUI(config =>
{ {
config.DocumentTitle = "Bitwarden API Documentation"; config.DocumentTitle = "Bitwarden API Documentation";

View File

@@ -1,6 +1,5 @@
using Bit.Api.AdminConsole.Authorization; using Bit.Api.AdminConsole.Authorization;
using Bit.Api.Tools.Authorization; using Bit.Api.Tools.Authorization;
using Bit.Core.Auth.IdentityServer;
using Bit.Core.PhishingDomainFeatures; using Bit.Core.PhishingDomainFeatures;
using Bit.Core.PhishingDomainFeatures.Interfaces; using Bit.Core.PhishingDomainFeatures.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@@ -10,6 +9,7 @@ using Bit.Core.Utilities;
using Bit.Core.Vault.Authorization.SecurityTasks; using Bit.Core.Vault.Authorization.SecurityTasks;
using Bit.SharedWeb.Health; using Bit.SharedWeb.Health;
using Bit.SharedWeb.Swagger; using Bit.SharedWeb.Swagger;
using Bit.SharedWeb.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
@@ -17,7 +17,10 @@ namespace Bit.Api.Utilities;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static void AddSwagger(this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment) /// <summary>
/// Configures the generation of swagger.json OpenAPI spec.
/// </summary>
public static void AddSwaggerGen(this IServiceCollection services, GlobalSettings globalSettings, IWebHostEnvironment environment)
{ {
services.AddSwaggerGen(config => services.AddSwaggerGen(config =>
{ {
@@ -36,6 +39,8 @@ public static class ServiceCollectionExtensions
organizations tools for managing members, collections, groups, event logs, and policies. organizations tools for managing members, collections, groups, event logs, and policies.
If you are looking for the Vault Management API, refer instead to If you are looking for the Vault Management API, refer instead to
[this document](https://bitwarden.com/help/vault-management-api/). [this document](https://bitwarden.com/help/vault-management-api/).
**Note:** your authorization must match the server you have selected.
""", """,
License = new OpenApiLicense License = new OpenApiLicense
{ {
@@ -46,36 +51,20 @@ public static class ServiceCollectionExtensions
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" }); config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme // Configure Bitwarden cloud US and EU servers. These will appear in the swagger.json build artifact
{ // used for our help center. These are overwritten with the local server when running in self-hosted
Type = SecuritySchemeType.OAuth2, // or dev mode (see Api Startup.cs).
Flows = new OpenApiOAuthFlows config.AddSwaggerServerWithSecurity(
{ serverId: "US_server",
ClientCredentials = new OpenApiOAuthFlow serverUrl: "https://api.bitwarden.com",
{ identityTokenUrl: "https://identity.bitwarden.com/connect/token",
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"), serverDescription: "US server");
Scopes = new Dictionary<string, string>
{
{ ApiScopes.ApiOrganization, "Organization APIs" },
},
}
},
});
config.AddSecurityRequirement(new OpenApiSecurityRequirement config.AddSwaggerServerWithSecurity(
{ serverId: "EU_server",
{ serverUrl: "https://api.bitwarden.eu",
new OpenApiSecurityScheme identityTokenUrl: "https://identity.bitwarden.eu/connect/token",
{ serverDescription: "EU server");
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2-client-credentials"
},
},
new[] { ApiScopes.ApiOrganization }
}
});
config.DescribeAllParametersInCamelCase(); config.DescribeAllParametersInCamelCase();
// config.UseReferencedDefinitionsForEnums(); // config.UseReferencedDefinitionsForEnums();

View File

@@ -85,7 +85,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using StackExchange.Redis; using StackExchange.Redis;
using Swashbuckle.AspNetCore.SwaggerGen;
using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion;
using NoopRepos = Bit.Core.Repositories.Noop; using NoopRepos = Bit.Core.Repositories.Noop;
using Role = Bit.Core.Entities.Role; using Role = Bit.Core.Entities.Role;
@@ -1067,4 +1069,61 @@ public static class ServiceCollectionExtensions
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) && CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName); CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
} }
/// <summary>
/// Adds a server with its corresponding OAuth2 client credentials security definition and requirement.
/// </summary>
/// <param name="config">The SwaggerGen configuration</param>
/// <param name="serverId">Unique identifier for this server (e.g., "us-server", "eu-server")</param>
/// <param name="serverUrl">The API server URL</param>
/// <param name="identityTokenUrl">The identity server token URL</param>
/// <param name="serverDescription">Human-readable description for the server</param>
public static void AddSwaggerServerWithSecurity(
this SwaggerGenOptions config,
string serverId,
string serverUrl,
string identityTokenUrl,
string serverDescription)
{
// Add server
config.AddServer(new OpenApiServer
{
Url = serverUrl,
Description = serverDescription
});
// Add security definition
config.AddSecurityDefinition(serverId, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Description = $"**Use this option if you've selected the {serverDescription}**",
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri(identityTokenUrl),
Scopes = new Dictionary<string, string>
{
{ ApiScopes.ApiOrganization, $"Organization APIs ({serverDescription})" },
},
}
},
});
// Add security requirement
config.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = serverId
},
},
[ApiScopes.ApiOrganization]
}
});
}
} }