Better circuit ids (#10688)
* Better circuit ids. * Generates cryptographically strong circuit ids. * 32 bits of entropy from a PRNG. * DataProtected for authenticated encryption.
This commit is contained in:
parent
bd4b843678
commit
2d2806b083
|
|
@ -6,6 +6,7 @@
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||||
<Compile Include="Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs" />
|
<Compile Include="Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Browser" />
|
<Reference Include="Microsoft.AspNetCore.Components.Browser" />
|
||||||
|
<Reference Include="Microsoft.AspNetCore.DataProtection" />
|
||||||
<Reference Include="Microsoft.Extensions.Logging" />
|
<Reference Include="Microsoft.Extensions.Logging" />
|
||||||
<Reference Include="Microsoft.AspNetCore.SignalR" />
|
<Reference Include="Microsoft.AspNetCore.SignalR" />
|
||||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Server
|
namespace Microsoft.AspNetCore.Components.Server
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public event UnhandledExceptionEventHandler UnhandledException;
|
public event UnhandledExceptionEventHandler UnhandledException;
|
||||||
|
|
||||||
public CircuitHost(
|
public CircuitHost(
|
||||||
|
string circuitId,
|
||||||
IServiceScope scope,
|
IServiceScope scope,
|
||||||
CircuitClientProxy client,
|
CircuitClientProxy client,
|
||||||
RendererRegistry rendererRegistry,
|
RendererRegistry rendererRegistry,
|
||||||
|
|
@ -60,6 +61,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
CircuitHandler[] circuitHandlers,
|
CircuitHandler[] circuitHandlers,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
|
CircuitId = circuitId;
|
||||||
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
|
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||||
Dispatcher = dispatcher;
|
Dispatcher = dispatcher;
|
||||||
Client = client;
|
Client = client;
|
||||||
|
|
@ -78,7 +80,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
Renderer.UnhandledSynchronizationException += SynchronizationContext_UnhandledException;
|
Renderer.UnhandledSynchronizationException += SynchronizationContext_UnhandledException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CircuitId { get; } = Guid.NewGuid().ToString();
|
public string CircuitId { get; }
|
||||||
|
|
||||||
public Circuit Circuit { get; }
|
public Circuit Circuit { get; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// 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;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
// This is a singleton instance
|
||||||
|
// Generates strong cryptographic ids for circuits that are protected with authenticated encryption.
|
||||||
|
internal class CircuitIdFactory
|
||||||
|
{
|
||||||
|
private const string CircuitIdProtectorPurpose = "Microsoft.AspNetCore.Components.Server";
|
||||||
|
|
||||||
|
private readonly RandomNumberGenerator _generator = RandomNumberGenerator.Create();
|
||||||
|
private readonly IDataProtector _protector;
|
||||||
|
|
||||||
|
public CircuitIdFactory(IDataProtectionProvider provider)
|
||||||
|
{
|
||||||
|
_protector = provider.CreateProtector(CircuitIdProtectorPurpose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a circuit id that is produced from a strong cryptographic random number generator
|
||||||
|
// we don't care about the underlying payload, other than its uniqueness and the fact that we
|
||||||
|
// authenticate encrypt it using data protection.
|
||||||
|
// For validation, the fact that we can unprotect the payload is guarantee enough.
|
||||||
|
public string CreateCircuitId()
|
||||||
|
{
|
||||||
|
var buffer = new byte[32];
|
||||||
|
_generator.GetBytes(buffer);
|
||||||
|
var payload = _protector.Protect(buffer);
|
||||||
|
|
||||||
|
return Base64UrlTextEncoder.Encode(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateCircuitId(string circuitId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var protectedBytes = Base64UrlTextEncoder.Decode(circuitId);
|
||||||
|
_protector.Unprotect(protectedBytes);
|
||||||
|
|
||||||
|
// Its enough that we prove that we can unprotect the payload to validate the circuit id,
|
||||||
|
// as this demonstrates that it the id wasn't tampered with.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// The payload format is not correct (either not base64urlencoded or not data protected)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -41,15 +40,17 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
private readonly object CircuitRegistryLock = new object();
|
private readonly object CircuitRegistryLock = new object();
|
||||||
private readonly CircuitOptions _options;
|
private readonly CircuitOptions _options;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly CircuitIdFactory _circuitIdFactory;
|
||||||
private readonly PostEvictionCallbackRegistration _postEvictionCallback;
|
private readonly PostEvictionCallbackRegistration _postEvictionCallback;
|
||||||
|
|
||||||
public CircuitRegistry(
|
public CircuitRegistry(
|
||||||
IOptions<CircuitOptions> options,
|
IOptions<CircuitOptions> options,
|
||||||
ILogger<CircuitRegistry> logger)
|
ILogger<CircuitRegistry> logger,
|
||||||
|
CircuitIdFactory circuitIdFactory)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_circuitIdFactory = circuitIdFactory;
|
||||||
ConnectedCircuits = new ConcurrentDictionary<string, CircuitHost>(StringComparer.Ordinal);
|
ConnectedCircuits = new ConcurrentDictionary<string, CircuitHost>(StringComparer.Ordinal);
|
||||||
|
|
||||||
DisconnectedCircuits = new MemoryCache(new MemoryCacheOptions
|
DisconnectedCircuits = new MemoryCache(new MemoryCacheOptions
|
||||||
|
|
@ -139,6 +140,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
|
||||||
public virtual async Task<CircuitHost> ConnectAsync(string circuitId, IClientProxy clientProxy, string connectionId, CancellationToken cancellationToken)
|
public virtual async Task<CircuitHost> ConnectAsync(string circuitId, IClientProxy clientProxy, string connectionId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (!_circuitIdFactory.ValidateCircuitId(circuitId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
CircuitHost circuitHost;
|
CircuitHost circuitHost;
|
||||||
bool previouslyConnected;
|
bool previouslyConnected;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,16 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private readonly CircuitIdFactory _circuitIdFactory;
|
||||||
|
|
||||||
public DefaultCircuitFactory(
|
public DefaultCircuitFactory(
|
||||||
IServiceScopeFactory scopeFactory,
|
IServiceScopeFactory scopeFactory,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory,
|
||||||
|
CircuitIdFactory circuitIdFactory)
|
||||||
{
|
{
|
||||||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
|
_circuitIdFactory = circuitIdFactory ?? throw new ArgumentNullException(nameof(circuitIdFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CircuitHost CreateCircuitHost(
|
public override CircuitHost CreateCircuitHost(
|
||||||
|
|
@ -81,6 +84,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var circuitHost = new CircuitHost(
|
var circuitHost = new CircuitHost(
|
||||||
|
_circuitIdFactory.CreateCircuitId(),
|
||||||
scope,
|
scope,
|
||||||
client,
|
client,
|
||||||
rendererRegistry,
|
rendererRegistry,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
var builder = new DefaultServerSideBlazorBuilder(services);
|
var builder = new DefaultServerSideBlazorBuilder(services);
|
||||||
|
|
||||||
|
services.AddDataProtection();
|
||||||
|
|
||||||
// This call INTENTIONALLY uses the AddHubOptions on the SignalR builder, because it will merge
|
// This call INTENTIONALLY uses the AddHubOptions on the SignalR builder, because it will merge
|
||||||
// the global HubOptions before running the configure callback. We want to ensure that happens
|
// the global HubOptions before running the configure callback. We want to ensure that happens
|
||||||
// once. Our AddHubOptions method doesn't do this.
|
// once. Our AddHubOptions method doesn't do this.
|
||||||
|
|
@ -51,6 +53,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
// Components entrypoints, this lot is the same and repeated registrations are a no-op.
|
// Components entrypoints, this lot is the same and repeated registrations are a no-op.
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<StaticFileOptions>, ConfigureStaticFilesOptions>());
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<StaticFileOptions>, ConfigureStaticFilesOptions>());
|
||||||
services.TryAddSingleton<CircuitFactory, DefaultCircuitFactory>();
|
services.TryAddSingleton<CircuitFactory, DefaultCircuitFactory>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<CircuitIdFactory>();
|
||||||
|
|
||||||
services.TryAddScoped(s => s.GetRequiredService<ICircuitAccessor>().Circuit);
|
services.TryAddScoped(s => s.GetRequiredService<ICircuitAccessor>().Circuit);
|
||||||
services.TryAddScoped<ICircuitAccessor, DefaultCircuitAccessor>();
|
services.TryAddScoped<ICircuitAccessor, DefaultCircuitAccessor>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Browser" />
|
<Reference Include="Microsoft.AspNetCore.Components.Browser" />
|
||||||
|
<Reference Include="Microsoft.AspNetCore.DataProtection" />
|
||||||
<Reference Include="Microsoft.Extensions.Logging" />
|
<Reference Include="Microsoft.Extensions.Logging" />
|
||||||
<Reference Include="Microsoft.AspNetCore.SignalR" />
|
<Reference Include="Microsoft.AspNetCore.SignalR" />
|
||||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
||||||
|
|
@ -52,10 +53,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="..\..\Browser.JS\dist\$(Configuration)\blazor.server.js" LogicalName="_framework/%(Filename)%(Extension)" />
|
<EmbeddedResource Include="..\..\Browser.JS\dist\$(Configuration)\blazor.server.js" LogicalName="_framework/%(Filename)%(Extension)" />
|
||||||
|
|
||||||
<EmbeddedResource
|
<EmbeddedResource Include="..\..\Browser.JS\dist\$(Configuration)\blazor.server.js.map" LogicalName="_framework/%(Filename)%(Extension)" Condition="Exists('..\..\Browser.JS\dist\$(Configuration)\blazor.server.js.map')" />
|
||||||
Include="..\..\Browser.JS\dist\$(Configuration)\blazor.server.js.map"
|
|
||||||
LogicalName="_framework/%(Filename)%(Extension)"
|
|
||||||
Condition="Exists('..\..\Browser.JS\dist\$(Configuration)\blazor.server.js.map')" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
var serviceScope = new Mock<IServiceScope>();
|
var serviceScope = new Mock<IServiceScope>();
|
||||||
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher());
|
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher());
|
||||||
var circuitHost = TestCircuitHost.Create(
|
var circuitHost = TestCircuitHost.Create(
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
serviceScope.Object,
|
serviceScope.Object,
|
||||||
remoteRenderer);
|
remoteRenderer);
|
||||||
|
|
||||||
|
|
@ -46,6 +47,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
var serviceScope = new Mock<IServiceScope>();
|
var serviceScope = new Mock<IServiceScope>();
|
||||||
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher());
|
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher());
|
||||||
var circuitHost = TestCircuitHost.Create(
|
var circuitHost = TestCircuitHost.Create(
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
serviceScope.Object,
|
serviceScope.Object,
|
||||||
remoteRenderer);
|
remoteRenderer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// 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;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
public class CircuitIdFactoryTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateCircuitId_Generates_NewRandomId()
|
||||||
|
{
|
||||||
|
var factory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var id = factory.CreateCircuitId();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(id);
|
||||||
|
// This is the magic data protection header that validates its protected
|
||||||
|
Assert.StartsWith("CfDJ", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateCircuitId_Generates_GeneratesDifferentIds_ForSuccesiveCalls()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ids = Enumerable.Range(0, 100).Select(i => factory.CreateCircuitId()).ToArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.All(ids, id => Assert.NotNull(id));
|
||||||
|
Assert.Equal(100, ids.Distinct(StringComparer.Ordinal).Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CircuitIds_Roundtrip()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
var id = factory.CreateCircuitId();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isValid = factory.ValidateCircuitId(id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(isValid, "Failed to validate id");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateCircuitId_ReturnsFalseForMalformedPayloads()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isValid = factory.ValidateCircuitId("$%@&==");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(isValid, "Accepted an invalid payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateCircuitId_ReturnsFalseForPotentiallyTamperedPayloads()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var factory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
var id = factory.CreateCircuitId();
|
||||||
|
var protectedBytes = Base64UrlTextEncoder.Decode(id);
|
||||||
|
for (int i = protectedBytes.Length - 10; i < protectedBytes.Length; i++)
|
||||||
|
{
|
||||||
|
protectedBytes[i] = 0;
|
||||||
|
}
|
||||||
|
var tamperedId = Base64UrlTextEncoder.Encode(protectedBytes);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isValid = factory.ValidateCircuitId(tamperedId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(isValid, "Accepted a tampered payload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,7 +35,10 @@ namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var circuitFactory = new TestCircuitFactory();
|
var circuitFactory = new TestCircuitFactory();
|
||||||
var circuitRegistry = new CircuitRegistry(Options.Create(new CircuitOptions()), Mock.Of<ILogger<CircuitRegistry>>());
|
var circuitRegistry = new CircuitRegistry(
|
||||||
|
Options.Create(new CircuitOptions()),
|
||||||
|
Mock.Of<ILogger<CircuitRegistry>>(),
|
||||||
|
TestCircuitIdFactory.CreateTestFactory());
|
||||||
var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry);
|
var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry);
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
var httpRequest = httpContext.Request;
|
var httpRequest = httpContext.Request;
|
||||||
|
|
@ -76,7 +79,10 @@ namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var circuitFactory = new TestCircuitFactory();
|
var circuitFactory = new TestCircuitFactory();
|
||||||
var circuitRegistry = new CircuitRegistry(Options.Create(new CircuitOptions()), Mock.Of<ILogger<CircuitRegistry>>());
|
var circuitRegistry = new CircuitRegistry(
|
||||||
|
Options.Create(new CircuitOptions()),
|
||||||
|
Mock.Of<ILogger<CircuitRegistry>>(),
|
||||||
|
TestCircuitIdFactory.CreateTestFactory());
|
||||||
var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry);
|
var circuitPrerenderer = new CircuitPrerenderer(circuitFactory, circuitRegistry);
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
var httpRequest = httpContext.Request;
|
var httpRequest = httpContext.Request;
|
||||||
|
|
@ -117,7 +123,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits
|
||||||
return uriHelper;
|
return uriHelper;
|
||||||
});
|
});
|
||||||
var serviceScope = serviceCollection.BuildServiceProvider().CreateScope();
|
var serviceScope = serviceCollection.BuildServiceProvider().CreateScope();
|
||||||
return TestCircuitHost.Create(serviceScope);
|
return TestCircuitHost.Create(Guid.NewGuid().ToString(), serviceScope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
@ -34,8 +35,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task ConnectAsync_TransfersClientOnActiveCircuit()
|
public async Task ConnectAsync_TransfersClientOnActiveCircuit()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = CreateRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
var circuitHost = TestCircuitHost.Create();
|
|
||||||
|
var registry = CreateRegistry(circuitIdFactory);
|
||||||
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId());
|
||||||
registry.Register(circuitHost);
|
registry.Register(circuitHost);
|
||||||
|
|
||||||
var newClient = Mock.Of<IClientProxy>();
|
var newClient = Mock.Of<IClientProxy>();
|
||||||
|
|
@ -57,8 +60,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task ConnectAsync_MakesInactiveCircuitActive()
|
public async Task ConnectAsync_MakesInactiveCircuitActive()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = CreateRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
var circuitHost = TestCircuitHost.Create();
|
|
||||||
|
var registry = CreateRegistry(circuitIdFactory);
|
||||||
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId());
|
||||||
registry.DisconnectedCircuits.Set(circuitHost.CircuitId, circuitHost, new MemoryCacheEntryOptions { Size = 1 });
|
registry.DisconnectedCircuits.Set(circuitHost.CircuitId, circuitHost, new MemoryCacheEntryOptions { Size = 1 });
|
||||||
|
|
||||||
var newClient = Mock.Of<IClientProxy>();
|
var newClient = Mock.Of<IClientProxy>();
|
||||||
|
|
@ -81,9 +86,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task ConnectAsync_InvokesCircuitHandlers_WhenCircuitWasPreviouslyDisconnected()
|
public async Task ConnectAsync_InvokesCircuitHandlers_WhenCircuitWasPreviouslyDisconnected()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = CreateRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
var registry = CreateRegistry(circuitIdFactory);
|
||||||
var handler = new Mock<CircuitHandler> { CallBase = true };
|
var handler = new Mock<CircuitHandler> { CallBase = true };
|
||||||
var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object });
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { handler.Object });
|
||||||
registry.DisconnectedCircuits.Set(circuitHost.CircuitId, circuitHost, new MemoryCacheEntryOptions { Size = 1 });
|
registry.DisconnectedCircuits.Set(circuitHost.CircuitId, circuitHost, new MemoryCacheEntryOptions { Size = 1 });
|
||||||
|
|
||||||
var newClient = Mock.Of<IClientProxy>();
|
var newClient = Mock.Of<IClientProxy>();
|
||||||
|
|
@ -104,9 +110,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task ConnectAsync_InvokesCircuitHandlers_WhenCircuitWasConsideredConnected()
|
public async Task ConnectAsync_InvokesCircuitHandlers_WhenCircuitWasConsideredConnected()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = CreateRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
var registry = CreateRegistry(circuitIdFactory);
|
||||||
var handler = new Mock<CircuitHandler> { CallBase = true };
|
var handler = new Mock<CircuitHandler> { CallBase = true };
|
||||||
var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object });
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { handler.Object });
|
||||||
registry.Register(circuitHost);
|
registry.Register(circuitHost);
|
||||||
|
|
||||||
var newClient = Mock.Of<IClientProxy>();
|
var newClient = Mock.Of<IClientProxy>();
|
||||||
|
|
@ -199,11 +206,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task Connect_WhileDisconnectIsInProgress()
|
public async Task Connect_WhileDisconnectIsInProgress()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = new TestCircuitRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
var registry = new TestCircuitRegistry(circuitIdFactory);
|
||||||
registry.BeforeDisconnect = new ManualResetEventSlim();
|
registry.BeforeDisconnect = new ManualResetEventSlim();
|
||||||
var tcs = new TaskCompletionSource<int>();
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
|
||||||
var circuitHost = TestCircuitHost.Create();
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId());
|
||||||
registry.Register(circuitHost);
|
registry.Register(circuitHost);
|
||||||
var client = Mock.Of<IClientProxy>();
|
var client = Mock.Of<IClientProxy>();
|
||||||
var newId = "new-connection";
|
var newId = "new-connection";
|
||||||
|
|
@ -238,13 +247,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task Connect_WhileDisconnectIsInProgress_SeriallyExecutesCircuitHandlers()
|
public async Task Connect_WhileDisconnectIsInProgress_SeriallyExecutesCircuitHandlers()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = new TestCircuitRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
var registry = new TestCircuitRegistry(circuitIdFactory);
|
||||||
registry.BeforeDisconnect = new ManualResetEventSlim();
|
registry.BeforeDisconnect = new ManualResetEventSlim();
|
||||||
// This verifies that connection up \ down events on a circuit handler are always invoked serially.
|
// This verifies that connection up \ down events on a circuit handler are always invoked serially.
|
||||||
var circuitHandler = new SerialCircuitHandler();
|
var circuitHandler = new SerialCircuitHandler();
|
||||||
var tcs = new TaskCompletionSource<int>();
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
|
||||||
var circuitHost = TestCircuitHost.Create(handlers: new[] { circuitHandler });
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { circuitHandler });
|
||||||
registry.Register(circuitHost);
|
registry.Register(circuitHost);
|
||||||
var client = Mock.Of<IClientProxy>();
|
var client = Mock.Of<IClientProxy>();
|
||||||
var newId = "new-connection";
|
var newId = "new-connection";
|
||||||
|
|
@ -276,9 +287,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public async Task DisconnectWhenAConnectIsInProgress()
|
public async Task DisconnectWhenAConnectIsInProgress()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var registry = new TestCircuitRegistry();
|
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||||
|
|
||||||
|
var registry = new TestCircuitRegistry(circuitIdFactory);
|
||||||
registry.BeforeConnect = new ManualResetEventSlim();
|
registry.BeforeConnect = new ManualResetEventSlim();
|
||||||
var circuitHost = TestCircuitHost.Create();
|
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId());
|
||||||
registry.Register(circuitHost);
|
registry.Register(circuitHost);
|
||||||
var client = Mock.Of<IClientProxy>();
|
var client = Mock.Of<IClientProxy>();
|
||||||
var oldId = circuitHost.Client.ConnectionId;
|
var oldId = circuitHost.Client.ConnectionId;
|
||||||
|
|
@ -302,8 +315,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
|
||||||
private class TestCircuitRegistry : CircuitRegistry
|
private class TestCircuitRegistry : CircuitRegistry
|
||||||
{
|
{
|
||||||
public TestCircuitRegistry()
|
public TestCircuitRegistry(CircuitIdFactory factory)
|
||||||
: base(Options.Create(new CircuitOptions()), NullLogger<CircuitRegistry>.Instance)
|
: base(Options.Create(new CircuitOptions()), NullLogger<CircuitRegistry>.Instance, factory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,11 +344,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CircuitRegistry CreateRegistry()
|
private static CircuitRegistry CreateRegistry(CircuitIdFactory factory = null)
|
||||||
{
|
{
|
||||||
return new CircuitRegistry(
|
return new CircuitRegistry(
|
||||||
Options.Create(new CircuitOptions()),
|
Options.Create(new CircuitOptions()),
|
||||||
NullLogger<CircuitRegistry>.Instance);
|
NullLogger<CircuitRegistry>.Instance,
|
||||||
|
factory ?? TestCircuitIdFactory.CreateTestFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SerialCircuitHandler : CircuitHandler
|
private class SerialCircuitHandler : CircuitHandler
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
internal class TestCircuitHost : CircuitHost
|
internal class TestCircuitHost : CircuitHost
|
||||||
{
|
{
|
||||||
private TestCircuitHost(IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList<ComponentDescriptor> descriptors, IDispatcher dispatcher, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger)
|
private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList<ComponentDescriptor> descriptors, IDispatcher dispatcher, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger)
|
||||||
: base(scope, client, rendererRegistry, renderer, descriptors, dispatcher, jsRuntime, circuitHandlers, logger)
|
: base(circuitId, scope, client, rendererRegistry, renderer, descriptors, dispatcher, jsRuntime, circuitHandlers, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CircuitHost Create(
|
public static CircuitHost Create(
|
||||||
|
string circuitId = null,
|
||||||
IServiceScope serviceScope = null,
|
IServiceScope serviceScope = null,
|
||||||
RemoteRenderer remoteRenderer = null,
|
RemoteRenderer remoteRenderer = null,
|
||||||
CircuitHandler[] handlers = null,
|
CircuitHandler[] handlers = null,
|
||||||
|
|
@ -54,6 +55,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
|
||||||
handlers = handlers ?? Array.Empty<CircuitHandler>();
|
handlers = handlers ?? Array.Empty<CircuitHandler>();
|
||||||
return new TestCircuitHost(
|
return new TestCircuitHost(
|
||||||
|
circuitId ?? Guid.NewGuid().ToString(),
|
||||||
serviceScope,
|
serviceScope,
|
||||||
clientProxy,
|
clientProxy,
|
||||||
renderRegistry,
|
renderRegistry,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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 Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
internal class TestCircuitIdFactory
|
||||||
|
{
|
||||||
|
public static CircuitIdFactory CreateTestFactory()
|
||||||
|
{
|
||||||
|
return new CircuitIdFactory(new EphemeralDataProtectionProvider());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -432,6 +432,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
services.AddDataProtection();
|
||||||
services.AddSingleton(HtmlEncoder.Default);
|
services.AddSingleton(HtmlEncoder.Default);
|
||||||
configureServices = configureServices ?? (s => s.AddServerSideBlazor());
|
configureServices = configureServices ?? (s => s.AddServerSideBlazor());
|
||||||
configureServices?.Invoke(services);
|
configureServices?.Invoke(services);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue