Skip to content
Merged
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
8 changes: 4 additions & 4 deletions src/WART-Client/WART-Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.7" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.11" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.22.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ public static class JwtServiceCollectionExtension
/// </summary>
/// <param name="services">The service collection to add the middleware to.</param>
/// <param name="tokenKey">The secret key used to sign and validate the JWT tokens.</param>
/// <param name="validIssuer">Optional. When provided, JWT issuer validation is enabled and tokens must match this value.</param>
/// <param name="validAudience">Optional. When provided, JWT audience validation is enabled and tokens must match this value.</param>
/// <returns>The updated service collection.</returns>
/// <exception cref="ArgumentNullException">Thrown if the token key is null or empty.</exception>
public static IServiceCollection AddJwtMiddleware(this IServiceCollection services, string tokenKey)
public static IServiceCollection AddJwtMiddleware(this IServiceCollection services, string tokenKey,
string validIssuer = null, string validAudience = null)
{
// Validate that the token key is provided
if (string.IsNullOrEmpty(tokenKey))
Expand Down Expand Up @@ -64,8 +67,10 @@ public static IServiceCollection AddJwtMiddleware(this IServiceCollection servic
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, parameters) => expires != null && expires > DateTime.UtcNow,
ValidateAudience = false,
ValidateIssuer = false,
ValidateAudience = !string.IsNullOrEmpty(validAudience),
ValidAudience = validAudience,
ValidateIssuer = !string.IsNullOrEmpty(validIssuer),
ValidIssuer = validIssuer,
ValidateActor = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
Expand Down
17 changes: 15 additions & 2 deletions src/WART-Core/Enum/HubType.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// This code is licensed under MIT license (see LICENSE.txt for details)
using System;

namespace WART_Core.Enum
{
/// <summary>
Expand All @@ -8,8 +10,19 @@ namespace WART_Core.Enum
public enum HubType
{
/// <summary>
/// Simple SignalR hub without authentication
/// Simple SignalR hub without authentication.
/// </summary>
/// <remarks>
/// <b>SECURITY WARNING:</b> This mode maps an unauthenticated SignalR hub that broadcasts
/// live API request and response payloads to every connected client with no credential check.
/// Any anonymous network client can connect and receive sensitive data (request bodies,
/// response bodies, HTTP paths). Use <see cref="JwtAuthentication"/> or
/// <see cref="CookieAuthentication"/> instead.
/// See https://github.com/engineering87/WART/security/advisories
/// </remarks>
[Obsolete("NoAuthentication broadcasts all API events to any anonymous client and is insecure. " +
"Use HubType.JwtAuthentication or HubType.CookieAuthentication instead. " +
"See https://github.com/engineering87/WART/security/advisories")]
NoAuthentication,

/// <summary>
Expand Down
8 changes: 7 additions & 1 deletion src/WART-Core/Hubs/WartHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
namespace WART_Core.Hubs
{
/// <summary>
/// The WART SignalR hub.
/// The WART SignalR hub (unauthenticated).
/// Group subscriptions are not permitted on this hub because connections
/// carry no verified identity. Use <see cref="WartHubJwt"/> or
/// <see cref="WartHubCookie"/> when group-scoped event delivery is required.
/// </summary>
public class WartHub : WartHubBase
{
public WartHub(ILogger<WartHub> logger) : base(logger) { }

/// <inheritdoc />
protected override bool IsGroupSubscriptionAllowed() => false;
}
}
22 changes: 21 additions & 1 deletion src/WART-Core/Hubs/WartHubBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ public override async Task OnConnectedAsync()

if (!string.IsNullOrEmpty(wartGroup))
{
await AddToGroup(wartGroup);
if (IsGroupSubscriptionAllowed())
{
await AddToGroup(wartGroup);
}
else
{
_logger?.LogWarning(
"Group subscription denied for ConnectionId={ConnectionId}, User={User}: hub does not permit group subscriptions.",
Context.ConnectionId, LogSanitizer.Sanitize(userName));
}
}

_logger?.LogInformation("OnConnected: ConnectionId={ConnectionId}, User={User}",
Expand Down Expand Up @@ -86,6 +95,17 @@ public override Task OnDisconnectedAsync(Exception exception)
return base.OnDisconnectedAsync(exception);
}

/// <summary>
/// Determines whether the current connection is allowed to subscribe to a SignalR group.
/// Authenticated hubs return <c>true</c> only when the connecting principal is authenticated.
/// The default unauthenticated <see cref="WartHub"/> overrides this to return <c>false</c>.
/// </summary>
/// <returns><c>true</c> if group subscription is permitted; otherwise <c>false</c>.</returns>
protected virtual bool IsGroupSubscriptionAllowed()
{
return Context.User?.Identity?.IsAuthenticated == true;
}

/// <summary>
/// Adds the current connection to a specified SignalR group.
/// </summary>
Expand Down
38 changes: 37 additions & 1 deletion src/WART-Core/Middleware/WartApplicationBuilderExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// This code is licensed under MIT license (see LICENSE.txt for details)
using Microsoft.AspNetCore.Builder;
using System;
Expand Down Expand Up @@ -31,6 +31,16 @@ private static IReadOnlyList<string> GetDistinctPaths(IEnumerable<string> hubNam
/// </summary>
/// <param name="app">The IApplicationBuilder to configure the middleware pipeline.</param>
/// <returns>The updated IApplicationBuilder to continue configuration.</returns>
/// <remarks>
/// <b>SECURITY WARNING:</b> This overload maps an unauthenticated SignalR hub that broadcasts
/// live API request and response payloads to every connected client with no credential check.
/// Use <see cref="UseWartMiddleware(IApplicationBuilder, HubType)"/> with
/// <c>HubType.JwtAuthentication</c> or <c>HubType.CookieAuthentication</c> instead.
/// See https://github.com/engineering87/WART/security/advisories
/// </remarks>
[Obsolete("UseWartMiddleware() without authentication broadcasts all API events to any anonymous client and is insecure. " +
"Use UseWartMiddleware(HubType.JwtAuthentication) or UseWartMiddleware(HubType.CookieAuthentication) instead. " +
"See https://github.com/engineering87/WART/security/advisories")]
public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app)
{
app.UseForwardedHeaders();
Expand Down Expand Up @@ -63,6 +73,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
switch(hubType)
{
default:
#pragma warning disable CS0618 // Internal routing Β— intentional fallback to no-auth mode
case HubType.NoAuthentication:
{
app.UseEndpoints(endpoints =>
Expand All @@ -72,6 +83,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
});
break;
}
#pragma warning restore CS0618
case HubType.JwtAuthentication:
{
app.UseJwtMiddleware();
Expand Down Expand Up @@ -105,6 +117,16 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
/// <param name="hubName">The custom SignalR hub name (URL path).</param>
/// <returns>The updated IApplicationBuilder to continue configuration.</returns>
/// <exception cref="ArgumentException">Thrown when the hub name is null or empty.</exception>
/// <remarks>
/// <b>SECURITY WARNING:</b> This overload maps an unauthenticated SignalR hub that broadcasts
/// live API request and response payloads to every connected client with no credential check.
/// Use <see cref="UseWartMiddleware(IApplicationBuilder, string, HubType)"/> with
/// <c>HubType.JwtAuthentication</c> or <c>HubType.CookieAuthentication</c> instead.
/// See https://github.com/engineering87/WART/security/advisories
/// </remarks>
[Obsolete("UseWartMiddleware(string) without authentication broadcasts all API events to any anonymous client and is insecure. " +
"Use UseWartMiddleware(hubName, HubType.JwtAuthentication) or UseWartMiddleware(hubName, HubType.CookieAuthentication) instead. " +
"See https://github.com/engineering87/WART/security/advisories")]
public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app, string hubName)
{
if (string.IsNullOrWhiteSpace(hubName))
Expand Down Expand Up @@ -132,6 +154,16 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
/// <param name="hubNameList">The list of custom SignalR hub names (URL paths).</param>
/// <returns>The updated IApplicationBuilder to continue configuration.</returns>
/// <exception cref="ArgumentException">Thrown when the hub name list is null.</exception>
/// <remarks>
/// <b>SECURITY WARNING:</b> This overload maps unauthenticated SignalR hubs that broadcast
/// live API request and response payloads to every connected client with no credential check.
/// Use <see cref="UseWartMiddleware(IApplicationBuilder, IEnumerable{string}, HubType)"/> with
/// <c>HubType.JwtAuthentication</c> or <c>HubType.CookieAuthentication</c> instead.
/// See https://github.com/engineering87/WART/security/advisories
/// </remarks>
[Obsolete("UseWartMiddleware(IEnumerable<string>) without authentication broadcasts all API events to any anonymous client and is insecure. " +
"Use UseWartMiddleware(hubNameList, HubType.JwtAuthentication) or UseWartMiddleware(hubNameList, HubType.CookieAuthentication) instead. " +
"See https://github.com/engineering87/WART/security/advisories")]
public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app, IEnumerable<string> hubNameList)
{
ArgumentNullException.ThrowIfNull(hubNameList);
Expand Down Expand Up @@ -177,6 +209,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
switch (hubType)
{
default:
#pragma warning disable CS0618 // Internal routing Β— intentional fallback to no-auth mode
case HubType.NoAuthentication:
{
app.UseEndpoints(endpoints =>
Expand All @@ -186,6 +219,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
});
break;
}
#pragma warning restore CS0618
case HubType.JwtAuthentication:
{
app.UseJwtMiddleware();
Expand Down Expand Up @@ -235,6 +269,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
switch (hubType)
{
default:
#pragma warning disable CS0618 // Internal routing Β— intentional fallback to no-auth mode
case HubType.NoAuthentication:
app.UseEndpoints(endpoints =>
{
Expand All @@ -243,6 +278,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app
endpoints.MapHub<WartHub>(path);
});
break;
#pragma warning restore CS0618

case HubType.JwtAuthentication:
app.UseJwtMiddleware();
Expand Down
15 changes: 14 additions & 1 deletion src/WART-Core/Middleware/WartServiceCollectionExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// (c) 2021 Francesco Del Re <francesco.delre.87@gmail.com>
// This code is licensed under MIT license (see LICENSE.txt for details)
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
Expand Down Expand Up @@ -26,6 +26,17 @@ public static class WartServiceCollectionExtension
/// </summary>
/// <param name="services">The IServiceCollection to configure.</param>
/// <returns>The updated IServiceCollection with WART middleware dependencies.</returns>
/// <remarks>
/// <b>SECURITY WARNING:</b> This overload configures an unauthenticated SignalR hub that
/// broadcasts live API request and response payloads to every connected client with no
/// credential check. Any anonymous network client can connect and receive sensitive data.
/// Use <see cref="AddWartMiddleware(IServiceCollection, HubType, string)"/> with
/// <c>HubType.JwtAuthentication</c> or <c>HubType.CookieAuthentication</c> instead.
/// See https://github.com/engineering87/WART/security/advisories
/// </remarks>
[Obsolete("AddWartMiddleware() without authentication broadcasts all API events to any anonymous client and is insecure. " +
"Use AddWartMiddleware(HubType.JwtAuthentication, tokenKey) or AddWartMiddleware(HubType.CookieAuthentication) instead. " +
"See https://github.com/engineering87/WART/security/advisories")]
public static IServiceCollection AddWartMiddleware(this IServiceCollection services)
{
// Configure forwarded headers to support proxy scenarios (X-Forwarded-* headers).
Expand Down Expand Up @@ -81,12 +92,14 @@ public static IServiceCollection AddWartMiddleware(this IServiceCollection servi
switch(hubType)
{
default:
#pragma warning disable CS0618 // Internal routing Β— intentional fallback to no-auth mode
case HubType.NoAuthentication:
{
// If no authentication is required, configure WART middleware without authentication.
services.AddWartMiddleware();
break;
}
#pragma warning restore CS0618
case HubType.JwtAuthentication:
{
// If authentication is required, configure JWT middleware for authentication.
Expand Down
4 changes: 2 additions & 2 deletions src/WART-Core/WART-Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
<RepositoryUrl>https://github.com/engineering87/WART</RepositoryUrl>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageLicenseExpression></PackageLicenseExpression>
<Version>7.0.0</Version>
<Version>7.1.0</Version>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Duende.AspNetCore.Authentication.OAuth2Introspection" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.11" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/WART-MinimalApi/WART-MinimalApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 5 additions & 5 deletions src/WART-Tests/WART-Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.11" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.v3" Version="4.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/WART-WebApiRealTime/WART-Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading