From 32b4d5cc6ce8a938d83d473a7e0030184b008e4a Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Wed, 4 Apr 2018 17:12:50 -0700 Subject: [PATCH] Set X-Requested-With on all requests (#1848) --- .../HttpConnection.cs | 4 ++ .../Internal/WebSocketsTransport.cs | 2 + .../HubConnectionBuilderHttpExtensions.cs | 3 +- .../HttpConnectionTests.Transport.cs | 40 ++++++++++++ .../AuthConnectionHandler.cs | 34 ++++++++++ .../EndToEndTests.cs | 63 +++++++++++++++++++ .../Microsoft.AspNetCore.SignalR.Tests.csproj | 2 + .../Startup.cs | 9 +++ .../WebSocketsTransportTests.cs | 24 +++++++ 9 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 test/Microsoft.AspNetCore.SignalR.Tests/AuthConnectionHandler.cs diff --git a/src/Microsoft.AspNetCore.Http.Connections.Client/HttpConnection.cs b/src/Microsoft.AspNetCore.Http.Connections.Client/HttpConnection.cs index 8cef636e3e..50d8a984d8 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Client/HttpConnection.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Client/HttpConnection.cs @@ -428,6 +428,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Client } } + httpClient.DefaultRequestHeaders.Remove("X-Requested-With"); + // Tell auth middleware to 401 instead of redirecting + httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest"); + return httpClient; } diff --git a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/WebSocketsTransport.cs b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/WebSocketsTransport.cs index c04862a961..6530953b5b 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/WebSocketsTransport.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/WebSocketsTransport.cs @@ -82,6 +82,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal _closeTimeout = httpOptions.CloseTimeout; } + _webSocket.Options.SetRequestHeader("X-Requested-With", "XMLHttpRequest"); + _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger(); } diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HubConnectionBuilderHttpExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Client/HubConnectionBuilderHttpExtensions.cs index 73e8dd0c1e..69f521a1d2 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HubConnectionBuilderHttpExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HubConnectionBuilderHttpExtensions.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -64,7 +63,7 @@ namespace Microsoft.AspNetCore.SignalR.Client o.Url = url; o.Transport = transportType; }); - + if (configureHttpConnection != null) { hubConnectionBuilder.Services.Configure(configureHttpConnection); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HttpConnectionTests.Transport.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HttpConnectionTests.Transport.cs index d0b6736f34..a09fd682b3 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HttpConnectionTests.Transport.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HttpConnectionTests.Transport.cs @@ -66,6 +66,46 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests Assert.True(requestsExecuted); } + [Theory] + [InlineData(HttpTransportType.LongPolling)] + [InlineData(HttpTransportType.ServerSentEvents)] + public async Task HttpConnectionSetsRequestedWithOnAllRequests(HttpTransportType transportType) + { + var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); + var requestsExecuted = false; + + testHttpHandler.OnRequest((request, next, token) => + { + return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + }); + + testHttpHandler.OnNegotiate((_, cancellationToken) => + { + return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent()); + }); + + testHttpHandler.OnRequest(async (request, next, token) => + { + var requestedWithHeader = request.Headers.GetValues("X-Requested-With"); + var requestedWithValue = Assert.Single(requestedWithHeader); + Assert.Equal("XMLHttpRequest", requestedWithValue); + + requestsExecuted = true; + + return await next(); + }); + + await WithConnectionAsync( + CreateConnection(testHttpHandler, transportType: transportType), + async (connection) => + { + await connection.StartAsync(TransferFormat.Text).OrTimeout(); + await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello World")); + }); + // Fail safe in case the code is modified and some requests don't execute as a result + Assert.True(requestsExecuted); + } + [Fact] public async Task CanReceiveData() { diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/AuthConnectionHandler.cs b/test/Microsoft.AspNetCore.SignalR.Tests/AuthConnectionHandler.cs new file mode 100644 index 0000000000..ad885409d1 --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Tests/AuthConnectionHandler.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Buffers; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.SignalR.Tests +{ + [Authorize] + public class AuthConnectionHandler : ConnectionHandler + { + public override async Task OnConnectedAsync(ConnectionContext connection) + { + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + var buffer = result.Buffer; + + if (!buffer.IsEmpty) + { + await connection.Transport.Output.WriteAsync(buffer.ToArray()); + } + else if (result.IsCompleted) + { + break; + } + + connection.Transport.Input.AdvanceTo(buffer.End); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs index 47ff1b8058..8186af0960 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs @@ -302,6 +302,69 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + [ConditionalFact] + [WebSocketsSupportedCondition] + public async Task UnauthorizedWebSocketsConnectionDoesNotConnect() + { + using (StartLog(out var loggerFactory, LogLevel.Trace)) + { + var logger = loggerFactory.CreateLogger(); + + var url = _serverFixture.Url + "/auth"; + var connection = new HttpConnection(new Uri(url), HttpTransportType.WebSockets, loggerFactory); + + try + { + logger.LogInformation("Starting connection to {url}", url); + await connection.StartAsync(TransferFormat.Binary).OrTimeout(); + Assert.True(false); + } + catch (WebSocketException) { } + catch (Exception ex) + { + logger.LogInformation(ex, "Test threw exception"); + throw; + } + finally + { + logger.LogInformation("Disposing Connection"); + await connection.DisposeAsync().OrTimeout(); + logger.LogInformation("Disposed Connection"); + } + } + } + + [Theory] + [InlineData(HttpTransportType.LongPolling)] + [InlineData(HttpTransportType.ServerSentEvents)] + public async Task UnauthorizedConnectionDoesNotConnect(HttpTransportType transportType) + { + using (StartLog(out var loggerFactory, LogLevel.Trace, testName: $"{nameof(UnauthorizedConnectionDoesNotConnect)}_{transportType}")) + { + var logger = loggerFactory.CreateLogger(); + + var url = _serverFixture.Url + "/auth"; + var connection = new HttpConnection(new Uri(url), transportType, loggerFactory); + + try + { + logger.LogInformation("Starting connection to {url}", url); + await connection.StartAsync(TransferFormat.Binary).OrTimeout(); + Assert.True(false); + } + catch (Exception ex) + { + Assert.Equal("Response status code does not indicate success: 401 (Unauthorized).", ex.Message); + } + finally + { + logger.LogInformation("Disposing Connection"); + await connection.DisposeAsync().OrTimeout(); + logger.LogInformation("Disposed Connection"); + } + } + } + [ConditionalFact] [WebSocketsSupportedCondition] public async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated_WebSocket() diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj index b8d8ef3466..da0a775aaa 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -34,6 +34,8 @@ + + diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Startup.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Startup.cs index 2ed9cb4b61..1fe89590ac 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Startup.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Startup.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -16,6 +18,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests { options.EnableDetailedErrors = true; }); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }).AddCookie(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) @@ -25,6 +33,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests routes.MapConnectionHandler("/echo"); routes.MapConnectionHandler("/echoAndClose"); routes.MapConnectionHandler("/httpheader"); + routes.MapConnectionHandler("/auth"); }); app.UseSignalR(routes => diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs index f384b3ad09..3ea8e9c0c0 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs @@ -107,6 +107,30 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + [ConditionalFact] + [WebSocketsSupportedCondition] + public async Task WebSocketsTransportSendsXRequestedWithHeader() + { + using (StartLog(out var loggerFactory)) + { + var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); + var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory); + await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), pair.Application, + TransferFormat.Binary, connection: Mock.Of()).OrTimeout(); + + await pair.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With")); + + // The HTTP header endpoint closes the connection immediately after sending response which should stop the transport + await webSocketsTransport.Running.OrTimeout(); + + Assert.True(pair.Transport.Input.TryRead(out var result)); + + string headerValue = Encoding.UTF8.GetString(result.Buffer.ToArray()); + + Assert.Equal("XMLHttpRequest", headerValue); + } + } + [ConditionalFact] [WebSocketsSupportedCondition] public async Task WebSocketsTransportStopsWhenConnectionChannelClosed()