From 01ad99b92589161cbd6633d08236e499cf85ee7a Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Sat, 13 Dec 2025 18:13:37 +0100 Subject: [PATCH 1/2] Enhance tab authorization with role and permission checks #5872 Add RoleName and PermissionName parameters to TabPanel for fine-grained tab visibility control. Update IsAuthorized logic in TabStrip to prioritize Host/Admin access, then check SecurityAccessLevel, and additionally require specified roles or permissions if provided. Removes redundant Admin/Host checks from the switch statement for clarity. --- Oqtane.Client/Modules/Controls/TabPanel.razor | 6 +++ Oqtane.Client/Modules/Controls/TabStrip.razor | 40 +++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/TabPanel.razor b/Oqtane.Client/Modules/Controls/TabPanel.razor index cff8d9e1..5ac41365 100644 --- a/Oqtane.Client/Modules/Controls/TabPanel.razor +++ b/Oqtane.Client/Modules/Controls/TabPanel.razor @@ -30,6 +30,12 @@ else [Parameter] public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel + [Parameter] + public string RoleName { get; set; } // optional - can be used to specify Role allowed to view this tab + + [Parameter] + public string PermissionName { get; set; } // optional - can be used to specify Permission allowed to view this tab + protected override void OnParametersSet() { base.OnParametersSet(); diff --git a/Oqtane.Client/Modules/Controls/TabStrip.razor b/Oqtane.Client/Modules/Controls/TabStrip.razor index e2a3c0f1..8fc5b2c7 100644 --- a/Oqtane.Client/Modules/Controls/TabStrip.razor +++ b/Oqtane.Client/Modules/Controls/TabStrip.razor @@ -84,12 +84,31 @@ } } + /// + /// Determines if a tab should be visible based on user permissions. + /// Authorization hierarchy: + /// 1. Host and Admin roles ALWAYS have access (bypass all checks) + /// 2. Check standard SecurityAccessLevel (View, Edit, etc.) + /// 3. If RoleName specified AND user is not Admin/Host, check RoleName + /// 4. If PermissionName specified AND user is not Admin/Host, check PermissionName + /// + /// The tab panel to check authorization for + /// True if user is authorized to see this tab, false otherwise private bool IsAuthorized(TabPanel tabPanel) { + // Step 1: Host and Admin bypass all restrictions + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) || + UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) + { + return true; + } + var authorized = false; + + // Step 2: Check standard SecurityAccessLevel switch (tabPanel.Security) { - case null: // security not specified - assume SecurityAccessLevel.Anonymous + case null: authorized = true; break; case SecurityAccessLevel.Anonymous: @@ -101,13 +120,20 @@ case SecurityAccessLevel.Edit: authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList); break; - case SecurityAccessLevel.Admin: - authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin); - break; - case SecurityAccessLevel.Host: - authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); - break; } + + // Step 3: Check RoleName if provided (additional requirement) + if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName)) + { + authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName); + } + + // Step 4: Check PermissionName if provided (additional requirement) + if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName)) + { + authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList); + } + return authorized; } } From e62268af2e3ba72fd2eed121c8284d61d2cb3931 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Sat, 13 Dec 2025 21:56:05 +0100 Subject: [PATCH 2/2] Update TabStrip.razor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The authorization flow is: • Host tabs: Only Host (Admin blocked by Step 1) • Everything else: Admin bypasses, others check permissions --- Oqtane.Client/Modules/Controls/TabStrip.razor | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/TabStrip.razor b/Oqtane.Client/Modules/Controls/TabStrip.razor index 8fc5b2c7..a8402d86 100644 --- a/Oqtane.Client/Modules/Controls/TabStrip.razor +++ b/Oqtane.Client/Modules/Controls/TabStrip.razor @@ -96,16 +96,22 @@ /// True if user is authorized to see this tab, false otherwise private bool IsAuthorized(TabPanel tabPanel) { - // Step 1: Host and Admin bypass all restrictions - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) || - UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) + // Step 1: Check for Host-only restriction + if (tabPanel.Security == SecurityAccessLevel.Host) + { + // Only Host users can access Host-level security tabs (Admin users are excluded) + return UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); + } + + // Step 2: Admin bypass all other restrictions + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { return true; } var authorized = false; - // Step 2: Check standard SecurityAccessLevel + // Step 3: Check standard SecurityAccessLevel switch (tabPanel.Security) { case null: @@ -120,15 +126,18 @@ case SecurityAccessLevel.Edit: authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList); break; + case SecurityAccessLevel.Host: + authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host); + break; } - // Step 3: Check RoleName if provided (additional requirement) + // Step 4: Check RoleName if provided (additional requirement) if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName)) { authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName); } - // Step 4: Check PermissionName if provided (additional requirement) + // Step 5: Check PermissionName if provided (additional requirement) if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName)) { authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);