From 65504096763835f540a8f42258dbbb760926ca2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:04:35 +0000 Subject: [PATCH] Extend IIdentityProvider with Logout and CreateForbiddenResponse; update HttpServer forbidden page flow Agent-Logs-Url: https://github.com/webexpress-framework/WebExpress.WebCore/sessions/42eb3cb9-7106-40a7-a098-76c8b8ae1498 Co-authored-by: ReneSchwarzer <31061438+ReneSchwarzer@users.noreply.github.com> --- .../Data/MockIdentityProvider.cs | 33 ++++++++++++++++ src/WebExpress.WebCore/HttpServer.cs | 18 +++++++++ .../WebIdentity/IIdentityManager.cs | 20 ++++++++++ .../WebIdentity/IIdentityProvider.cs | 29 ++++++++++++++ .../WebIdentity/IdentityManager.cs | 39 +++++++++++++++++++ 5 files changed, 139 insertions(+) diff --git a/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs b/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs index e39fb9d..da689ac 100644 --- a/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs +++ b/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs @@ -56,6 +56,17 @@ public IIdentity Authenticate(IRequest request) return null; // not needed for this test } + /// + /// Logs out the specified request by clearing any authentication state. + /// + /// + /// The request whose authentication state should be cleared. Cannot be null. + /// + public void Logout(IRequest request) + { + // not needed for this test + } + /// /// Displays a login dialog using the specified request and identity information. /// @@ -77,5 +88,27 @@ public IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext i { return null; } + + /// + /// Creates a forbidden response page for the specified request when the authenticated + /// user lacks the required permissions to access the requested resource. + /// + /// + /// The request for which access was denied. Cannot be null. + /// + /// + /// The endpoint that the user attempted to access. + /// + /// + /// The authenticated identity that lacks sufficient permissions. + /// + /// + /// A response representing the forbidden page if this provider can handle the forbidden + /// scenario; otherwise, null. + /// + public IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity) + { + return null; + } } } diff --git a/src/WebExpress.WebCore/HttpServer.cs b/src/WebExpress.WebCore/HttpServer.cs index 13ca08c..401a703 100644 --- a/src/WebExpress.WebCore/HttpServer.cs +++ b/src/WebExpress.WebCore/HttpServer.cs @@ -657,6 +657,24 @@ public async Task ProcessRequestAsync(IHttpContext httpContext) // check again if (!_componentHub.IdentityManager.CheckAccess(identity, searchResult.EndpointContext)) { + // if the user is authenticated but lacks the required permissions, show the forbidden page + if (identity is not null) + { + var forbiddenResponse = _componentHub.IdentityManager.CreateForbiddenResponse + ( + httpContext.Request, + searchResult.EndpointContext, + identity + ); + + if (forbiddenResponse is not null) + { + await responseSender.SendAsync(httpContext, forbiddenResponse); + return; + } + } + + // if the user is not authenticated, show the login prompt var loginResponse = _componentHub.IdentityManager.CreateAuthenticationPrompt ( httpContext.Request, diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs index b736b44..5d1674b 100644 --- a/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs @@ -41,6 +41,26 @@ public interface IIdentityManager : IComponentManager /// IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext initiator, IIdentity identity = null); + /// + /// Creates a forbidden response page for the specified request when the authenticated + /// user lacks the required permissions to access the requested resource. + /// + /// + /// The request for which access was denied. Cannot be null. + /// + /// + /// The endpoint that the user attempted to access. Used to determine the origin and + /// context of the authorization failure. + /// + /// + /// The authenticated identity that lacks sufficient permissions. Cannot be null. + /// + /// + /// A response representing the forbidden page if a registered identity provider can handle the + /// forbidden scenario; otherwise, null. + /// + IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity); + /// /// Attempts to authenticate the specified request within the given application context. /// diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs index e568759..bcc27ed 100644 --- a/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs @@ -31,6 +31,15 @@ public interface IIdentityProvider /// IIdentity Authenticate(IRequest request); + /// + /// Logs out the specified request by clearing any authentication state + /// managed by this identity provider. + /// + /// + /// The request whose authentication state should be cleared. Cannot be null. + /// + void Logout(IRequest request); + /// /// Displays a login dialog using the specified request and identity information. /// @@ -49,5 +58,25 @@ public interface IIdentityProvider /// relevant status information. /// IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext initiator, IIdentity identity); + + /// + /// Creates a forbidden response page for the specified request when the authenticated + /// user lacks the required permissions to access the requested resource. + /// + /// + /// The request for which access was denied. Cannot be null. + /// + /// + /// The endpoint that the user attempted to access. Used to determine the origin and + /// context of the authorization failure. + /// + /// + /// The authenticated identity that lacks sufficient permissions. Cannot be null. + /// + /// + /// A response representing the forbidden page if this provider can handle the forbidden + /// scenario; otherwise, null. + /// + IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity); } } diff --git a/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs index 05d10e1..afcedf3 100644 --- a/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs +++ b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs @@ -404,6 +404,36 @@ public IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext i return null; } + /// + /// Creates a forbidden response page for the specified request when the authenticated + /// user lacks the required permissions to access the requested resource. + /// + /// The request for which access was denied. Cannot be null. + /// The endpoint that the user attempted to access. + /// The authenticated identity that lacks sufficient permissions. + /// + /// A response representing the forbidden page if a registered identity provider can handle the + /// forbidden scenario; otherwise, null. + /// + public IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity) + { + if (_identityProviders.TryGetValue(initiator?.ApplicationContext, out var list)) + { + foreach (var provider in list) + { + var response = provider.CreateForbiddenResponse(request, initiator, identity); + + if (response is not null) + { + // the first provider that can show a forbidden page wins + return response; + } + } + } + + return null; + } + /// /// Attempts to authenticate the specified request within the given application context. /// @@ -454,6 +484,15 @@ public bool Login(IRequest request, IIdentity identity) /// The request. public void Logout(IRequest request) { + // notify all registered identity providers so they can clear their own state + foreach (var list in _identityProviders.Values) + { + foreach (var provider in list) + { + provider.Logout(request); + } + } + var session = _componentHub.SessionManager.GetSession(request); session.RemoveProperty(); }