diff --git a/src/WART-Client/WART-Client.csproj b/src/WART-Client/WART-Client.csproj index 43da9fe..6cee0fe 100755 --- a/src/WART-Client/WART-Client.csproj +++ b/src/WART-Client/WART-Client.csproj @@ -21,10 +21,10 @@ - - - - + + + + diff --git a/src/WART-Core/Authentication/JWT/JwtServiceCollectionExtension.cs b/src/WART-Core/Authentication/JWT/JwtServiceCollectionExtension.cs index 0e77b41..553dc96 100755 --- a/src/WART-Core/Authentication/JWT/JwtServiceCollectionExtension.cs +++ b/src/WART-Core/Authentication/JWT/JwtServiceCollectionExtension.cs @@ -27,9 +27,12 @@ public static class JwtServiceCollectionExtension /// /// The service collection to add the middleware to. /// The secret key used to sign and validate the JWT tokens. + /// Optional. When provided, JWT issuer validation is enabled and tokens must match this value. + /// Optional. When provided, JWT audience validation is enabled and tokens must match this value. /// The updated service collection. /// Thrown if the token key is null or empty. - 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)) @@ -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, diff --git a/src/WART-Core/Enum/HubType.cs b/src/WART-Core/Enum/HubType.cs index 2b407a0..cbf3cb2 100755 --- a/src/WART-Core/Enum/HubType.cs +++ b/src/WART-Core/Enum/HubType.cs @@ -1,5 +1,7 @@ -// (c) 2021 Francesco Del Re +// (c) 2021 Francesco Del Re // This code is licensed under MIT license (see LICENSE.txt for details) +using System; + namespace WART_Core.Enum { /// @@ -8,8 +10,19 @@ namespace WART_Core.Enum public enum HubType { /// - /// Simple SignalR hub without authentication + /// Simple SignalR hub without authentication. /// + /// + /// SECURITY WARNING: 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 or + /// instead. + /// See https://github.com/engineering87/WART/security/advisories + /// + [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, /// diff --git a/src/WART-Core/Hubs/WartHub.cs b/src/WART-Core/Hubs/WartHub.cs index f069ca6..51dd5d9 100755 --- a/src/WART-Core/Hubs/WartHub.cs +++ b/src/WART-Core/Hubs/WartHub.cs @@ -5,10 +5,16 @@ namespace WART_Core.Hubs { /// - /// The WART SignalR hub. + /// The WART SignalR hub (unauthenticated). + /// Group subscriptions are not permitted on this hub because connections + /// carry no verified identity. Use or + /// when group-scoped event delivery is required. /// public class WartHub : WartHubBase { public WartHub(ILogger logger) : base(logger) { } + + /// + protected override bool IsGroupSubscriptionAllowed() => false; } } diff --git a/src/WART-Core/Hubs/WartHubBase.cs b/src/WART-Core/Hubs/WartHubBase.cs index 2c638d3..1ba9453 100644 --- a/src/WART-Core/Hubs/WartHubBase.cs +++ b/src/WART-Core/Hubs/WartHubBase.cs @@ -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}", @@ -86,6 +95,17 @@ public override Task OnDisconnectedAsync(Exception exception) return base.OnDisconnectedAsync(exception); } + /// + /// Determines whether the current connection is allowed to subscribe to a SignalR group. + /// Authenticated hubs return true only when the connecting principal is authenticated. + /// The default unauthenticated overrides this to return false. + /// + /// true if group subscription is permitted; otherwise false. + protected virtual bool IsGroupSubscriptionAllowed() + { + return Context.User?.Identity?.IsAuthenticated == true; + } + /// /// Adds the current connection to a specified SignalR group. /// diff --git a/src/WART-Core/Middleware/WartApplicationBuilderExtension.cs b/src/WART-Core/Middleware/WartApplicationBuilderExtension.cs index d1ffffa..827942c 100755 --- a/src/WART-Core/Middleware/WartApplicationBuilderExtension.cs +++ b/src/WART-Core/Middleware/WartApplicationBuilderExtension.cs @@ -1,4 +1,4 @@ -// (c) 2021 Francesco Del Re +// (c) 2021 Francesco Del Re // This code is licensed under MIT license (see LICENSE.txt for details) using Microsoft.AspNetCore.Builder; using System; @@ -31,6 +31,16 @@ private static IReadOnlyList GetDistinctPaths(IEnumerable hubNam /// /// The IApplicationBuilder to configure the middleware pipeline. /// The updated IApplicationBuilder to continue configuration. + /// + /// SECURITY WARNING: This overload maps an unauthenticated SignalR hub that broadcasts + /// live API request and response payloads to every connected client with no credential check. + /// Use with + /// HubType.JwtAuthentication or HubType.CookieAuthentication instead. + /// See https://github.com/engineering87/WART/security/advisories + /// + [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(); @@ -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 => @@ -72,6 +83,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app }); break; } +#pragma warning restore CS0618 case HubType.JwtAuthentication: { app.UseJwtMiddleware(); @@ -105,6 +117,16 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app /// The custom SignalR hub name (URL path). /// The updated IApplicationBuilder to continue configuration. /// Thrown when the hub name is null or empty. + /// + /// SECURITY WARNING: This overload maps an unauthenticated SignalR hub that broadcasts + /// live API request and response payloads to every connected client with no credential check. + /// Use with + /// HubType.JwtAuthentication or HubType.CookieAuthentication instead. + /// See https://github.com/engineering87/WART/security/advisories + /// + [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)) @@ -132,6 +154,16 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app /// The list of custom SignalR hub names (URL paths). /// The updated IApplicationBuilder to continue configuration. /// Thrown when the hub name list is null. + /// + /// SECURITY WARNING: This overload maps unauthenticated SignalR hubs that broadcast + /// live API request and response payloads to every connected client with no credential check. + /// Use with + /// HubType.JwtAuthentication or HubType.CookieAuthentication instead. + /// See https://github.com/engineering87/WART/security/advisories + /// + [Obsolete("UseWartMiddleware(IEnumerable) 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 hubNameList) { ArgumentNullException.ThrowIfNull(hubNameList); @@ -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 => @@ -186,6 +219,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app }); break; } +#pragma warning restore CS0618 case HubType.JwtAuthentication: { app.UseJwtMiddleware(); @@ -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 => { @@ -243,6 +278,7 @@ public static IApplicationBuilder UseWartMiddleware(this IApplicationBuilder app endpoints.MapHub(path); }); break; +#pragma warning restore CS0618 case HubType.JwtAuthentication: app.UseJwtMiddleware(); diff --git a/src/WART-Core/Middleware/WartServiceCollectionExtension.cs b/src/WART-Core/Middleware/WartServiceCollectionExtension.cs index 1dfd14f..b4b66d6 100755 --- a/src/WART-Core/Middleware/WartServiceCollectionExtension.cs +++ b/src/WART-Core/Middleware/WartServiceCollectionExtension.cs @@ -1,4 +1,4 @@ -// (c) 2021 Francesco Del Re +// (c) 2021 Francesco Del Re // This code is licensed under MIT license (see LICENSE.txt for details) using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; @@ -26,6 +26,17 @@ public static class WartServiceCollectionExtension /// /// The IServiceCollection to configure. /// The updated IServiceCollection with WART middleware dependencies. + /// + /// SECURITY WARNING: 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 with + /// HubType.JwtAuthentication or HubType.CookieAuthentication instead. + /// See https://github.com/engineering87/WART/security/advisories + /// + [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). @@ -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. diff --git a/src/WART-Core/WART-Core.csproj b/src/WART-Core/WART-Core.csproj index e48acaf..71a4f36 100644 --- a/src/WART-Core/WART-Core.csproj +++ b/src/WART-Core/WART-Core.csproj @@ -12,7 +12,7 @@ https://github.com/engineering87/WART LICENSE.txt - 7.0.0 + 7.1.0 icon.png README.md @@ -20,7 +20,7 @@ - + diff --git a/src/WART-MinimalApi/WART-MinimalApi.csproj b/src/WART-MinimalApi/WART-MinimalApi.csproj index b6c4782..cdf53e7 100644 --- a/src/WART-MinimalApi/WART-MinimalApi.csproj +++ b/src/WART-MinimalApi/WART-MinimalApi.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/WART-Tests/WART-Tests.csproj b/src/WART-Tests/WART-Tests.csproj index 8e80022..c3383c0 100644 --- a/src/WART-Tests/WART-Tests.csproj +++ b/src/WART-Tests/WART-Tests.csproj @@ -10,18 +10,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/WART-WebApiRealTime/WART-Api.csproj b/src/WART-WebApiRealTime/WART-Api.csproj index 49aaaee..2e7fba9 100755 --- a/src/WART-WebApiRealTime/WART-Api.csproj +++ b/src/WART-WebApiRealTime/WART-Api.csproj @@ -8,7 +8,7 @@ - +