Skip to content

Commit 45f3049

Browse files
authored
Handle application paths for remote calls (#192)
This change does two things: 1) Ensures that the root address ends in a '/' and that the request path starts with a './'. This is done in a way that gets cached and won't be recomputed each request 2) Update the RemoteModule to compare the incoming strings against the app relative path that accounts for any application paths or virtual directories. Fixes #191
1 parent 2329100 commit 45f3049

File tree

8 files changed

+72
-8
lines changed

8 files changed

+72
-8
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Authentication/RemoteAppAuthenticationClientOptions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,11 @@ public class RemoteAppAuthenticationClientOptions : AuthenticationSchemeOptions
4141
/// services. Requests to authenticate are sent to this endpoint.
4242
/// </summary>
4343
[Required]
44-
public PathString AuthenticationEndpointPath { get; set; } = AuthenticationConstants.DefaultEndpoint;
44+
public PathString AuthenticationEndpointPath
45+
{
46+
get => Path.Path;
47+
set => Path = new(value);
48+
}
49+
50+
internal RelativePathString Path { get; private set; } = new(AuthenticationConstants.DefaultEndpoint);
4551
}

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Authentication/RemoteAppAuthenticationService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public async Task<RemoteAppAuthenticationResult> AuthenticateAsync(HttpRequest o
8585
// that may matter for authentication. Also include the original request path as
8686
// as a query parameter so that the ASP.NET app can redirect back to it if an
8787
// authentication provider attempts to redirect back to the authenticate URL.
88-
var url = $"{_options.AuthenticationEndpointPath}?{AuthenticationConstants.OriginalUrlQueryParamName}={WebUtility.UrlEncode(originalRequest.GetEncodedPathAndQuery())}";
88+
var url = $"{_options.Path.Relative}?{AuthenticationConstants.OriginalUrlQueryParamName}={WebUtility.UrlEncode(originalRequest.GetEncodedPathAndQuery())}";
8989
using var authRequest = new HttpRequestMessage(HttpMethod.Get, url);
9090
AddHeaders(_options.RequestHeadersToForward, originalRequest, authRequest);
9191

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace Microsoft.AspNetCore.SystemWebAdapters;
7+
8+
internal readonly struct RelativePathString
9+
{
10+
public RelativePathString(PathString path)
11+
{
12+
Path = path;
13+
Relative = "." + path;
14+
}
15+
16+
public PathString Path { get; }
17+
18+
/// <summary>
19+
/// Use when you want the path to be relative to whatever URI you may combine it with
20+
/// </summary>
21+
public string Relative { get; }
22+
}

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/RemoteAppClientOptions.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.SystemWebAdapters;
1212
/// </summary>
1313
public class RemoteAppClientOptions
1414
{
15+
private Uri _remoteAppUrl = null!;
16+
1517
/// <summary>
1618
/// Gets or sets the header used to store the API key
1719
/// </summary>
@@ -28,7 +30,27 @@ public class RemoteAppClientOptions
2830
/// Gets or sets the remote app url
2931
/// </summary>
3032
[Required]
31-
public Uri RemoteAppUrl { get; set; } = null!;
33+
public Uri RemoteAppUrl
34+
{
35+
get => _remoteAppUrl;
36+
set
37+
{
38+
if (value is null)
39+
{
40+
throw new ArgumentNullException(nameof(RemoteAppUrl));
41+
}
42+
43+
// Path must end in '/' so that it will combine correctly with subpaths
44+
if (!value.AbsolutePath.EndsWith("/"))
45+
{
46+
var builder = new UriBuilder(value);
47+
builder.Path += "/";
48+
value = builder.Uri;
49+
}
50+
51+
_remoteAppUrl = value;
52+
}
53+
}
3254

3355
/// <summary>
3456
/// Gets or sets an <see cref="HttpMessageHandler"/> to use for making requests to the remote app.

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionState/RemoteSession/RemoteAppSessionStateClientOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.ComponentModel.DataAnnotations;
5+
using Microsoft.AspNetCore.Http;
56

67
namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.RemoteSession;
78

89
public class RemoteAppSessionStateClientOptions
910
{
1011
[Required]
11-
public string SessionEndpointPath { get; set; } = SessionConstants.SessionEndpointPath;
12+
public PathString SessionEndpointPath
13+
{
14+
get => Path.Path;
15+
set => Path = new(value);
16+
}
17+
18+
internal RelativePathString Path { get; private set; } = new(SessionConstants.SessionEndpointPath);
1219

1320
/// <summary>
1421
/// Gets or sets the cookie name that the ASP.NET framework app is expecting to hold the session id

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionState/RemoteSession/RemoteAppSessionStateManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private async Task<ISessionState> GetSessionDataAsync(string? sessionId, bool re
7373
{
7474
// The request message is manually disposed at a later time
7575
#pragma warning disable CA2000 // Dispose objects before losing scope
76-
var req = new HttpRequestMessage(HttpMethod.Get, _options.SessionEndpointPath);
76+
var req = new HttpRequestMessage(HttpMethod.Get, _options.Path.Relative);
7777
#pragma warning restore CA2000 // Dispose objects before losing scope
7878

7979
AddSessionCookieToHeader(req, sessionId);
@@ -112,7 +112,7 @@ private async Task<ISessionState> GetSessionDataAsync(string? sessionId, bool re
112112
/// </summary>
113113
private async Task SetOrReleaseSessionData(ISessionState? state, CancellationToken cancellationToken)
114114
{
115-
using var req = new HttpRequestMessage(HttpMethod.Put, _options.SessionEndpointPath);
115+
using var req = new HttpRequestMessage(HttpMethod.Put, _options.Path.Relative);
116116

117117
if (state is not null)
118118
{

src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/RemoteModule.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ void IHttpModule.Dispose()
5050

5151
void IHttpModule.Init(HttpApplication context)
5252
{
53+
var appRelativePath = $"~{Path}";
54+
5355
context.PostMapRequestHandler += (s, _) =>
5456
{
5557
var context = ((HttpApplication)s).Context;
5658

57-
if (!string.Equals(context.Request.Path, Path, StringComparison.Ordinal))
59+
// Compare against the AppRelativeCurrentExecutionFilePath to account for potential virtual directories
60+
if (!string.Equals(context.Request.AppRelativeCurrentExecutionFilePath, appRelativePath, StringComparison.Ordinal))
5861
{
5962
return;
6063
}

test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/RemoteAppClientOptionsTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ public void VerifyIsCalled(string apiKeyHeader, string apiKey, string remoteAppU
3535
{
3636
options.ApiKey = apiKey;
3737
options.ApiKeyHeader = apiKeyHeader;
38-
options.RemoteAppUrl = (remoteAppUrl is null ? null : new Uri(remoteAppUrl, UriKind.Absolute))!;
38+
39+
if (remoteAppUrl is not null)
40+
{
41+
options.RemoteAppUrl = new Uri(remoteAppUrl, UriKind.Absolute);
42+
}
3943
}));
4044

4145
using var serviceProvider = services.BuildServiceProvider();

0 commit comments

Comments
 (0)