Skip to content
Closed
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
14 changes: 13 additions & 1 deletion src/OpenApi/src/Services/OpenApiDocumentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,26 @@ internal List<OpenApiServer> GetOpenApiServers(HttpRequest? httpRequest = null)
{
if (httpRequest is not null)
{
var serverUrl = UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase);
// Handle forwarded headers directly if present
var scheme = httpRequest.Headers.GetCommaSeparatedValues("X-Forwarded-Proto") is [var forwardedScheme, ..]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this happening in the document service? We have a whole middleware for forwarded headers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't always rely on the forwarded headers middleware being enabled for this feature to work --especially since the training people are used to is that OpenAPI operates independent of any other options.

There's also some local dev scenarios (Codespaces, for example) that make it hard to discover that you need to enable forwarded headers middleware.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That applies to all of asp.net core, not this feature. There are also security concerns with blindly reading xff headers (the middleware goes through great lengths to make that configurable but secure by default). You can (as a customer) enable his flag ASPNETCORE_FORWARDEDHEADERS_ENABLED, to turn on the middleware from the outside.

The custom should be using one of those techniques. This middleware is no more special than the rest.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lucky for you I don't feel argumentative today. 😆 I'll close this PR out and make the same change in the servicing PR.

? forwardedScheme
: httpRequest.Scheme;

var host = httpRequest.Headers.GetCommaSeparatedValues("X-Forwarded-Host") is [var forwardedHost, ..]
? forwardedHost
: httpRequest.Host.Value;

var hostString = new HostString(host);
var serverUrl = UriHelper.BuildAbsolute(scheme, hostString, httpRequest.PathBase);

return [new OpenApiServer { Url = serverUrl }];
}
else
{
return GetDevelopmentOpenApiServers();
}
}

private List<OpenApiServer> GetDevelopmentOpenApiServers()
{
if (hostEnvironment.IsDevelopment() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,47 @@ public void GetOpenApiServers_HandlesServerAddressFeatureWithNoValues()
// Assert
Assert.Empty(servers);
}

[Theory]
[InlineData("https", "proxy-server.com", "https://proxy-server.com/original-path")]
[InlineData("http", "proxy:8080", "http://proxy:8080/original-path")]
[InlineData("https", "proxy.example.org", "https://proxy.example.org/original-path")]
public void GetOpenApiServers_HandlesForwardedHeaders(string forwardedProto, string forwardedHost, string expectedUrl)
{
// Arrange
var hostEnvironment = new HostingEnvironment
{
ApplicationName = "TestApplication",
EnvironmentName = "Production"
};
var docService = new OpenApiDocumentService(
"v1",
new Mock<IApiDescriptionGroupCollectionProvider>().Object,
hostEnvironment,
GetMockOptionsMonitor(),
new Mock<IKeyedServiceProvider>().Object,
new OpenApiTestServer(["http://localhost:5000"]));

var httpContext = new DefaultHttpContext()
{
Request =
{
// Original values that should be overridden by forwarded headers
Host = new HostString("localhost:5000"),
PathBase = "/original-path",
Scheme = "http"
}
};

// Add forwarded headers
httpContext.Request.Headers["X-Forwarded-Proto"] = forwardedProto;
httpContext.Request.Headers["X-Forwarded-Host"] = forwardedHost;

// Act
var servers = docService.GetOpenApiServers(httpContext.Request);

// Assert
var serverUrl = Assert.Single(servers).Url;
Assert.Equal(expectedUrl, serverUrl);
}
}
Loading