From 952ad44c5a2a3221f0b499de3297a500cb84ad71 Mon Sep 17 00:00:00 2001 From: Darrick Joo Date: Thu, 23 Jul 2026 13:49:53 +0200 Subject: [PATCH 1/3] Fix effective permission filtering Prefer exact object permissions over wildcard permissions when populating the permission-set detail buffer, including explicit exclusions. Add regression coverage for both precedence scenarios. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../EffectivePermissionsMgt.Codeunit.al | 13 ++- .../Permissions/PermissionsTest.Codeunit.al | 108 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al index 71aeaae6ca5..43671f3501a 100644 --- a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al +++ b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al @@ -297,12 +297,12 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission: Record "Expanded Permission"; PermissionSetBuffer: Record "Permission Set Buffer"; AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute : Integer; + ExpandedPermissionFound: Boolean; begin PermissionBuffer.Reset(); PermissionBuffer.DeleteAll(); ExpandedPermission.SetRange("Object Type", PassedObjectType); - ExpandedPermission.SetFilter("Object ID", '%1|%2', 0, PassedObjectId); // find permissions from all permission sets for this user AccessControl.SetFilter("User Security ID", GetAccessControlFilterForUser(PassedUserID)); @@ -327,7 +327,16 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission.SetRange("App ID", AccessControl."App ID"); ExpandedPermission.SetRange("Role ID", AccessControl."Role ID"); - if ExpandedPermission.FindFirst() then begin + + // Specific object permissions override the wildcard entry. + ExpandedPermission.SetRange("Object ID", PassedObjectId); + ExpandedPermissionFound := ExpandedPermission.FindFirst(); + if not ExpandedPermissionFound then begin + ExpandedPermission.SetRange("Object ID", 0); + ExpandedPermissionFound := ExpandedPermission.FindFirst(); + end; + + if ExpandedPermissionFound then begin FillPermissionBufferFromExpandedPermission(PermissionBuffer, ExpandedPermission); SetHighestAssignedPermission(PermissionBuffer, AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute); PermissionBuffer.Order := PermissionBuffer.Source; diff --git a/src/Layers/W1/Tests/Permissions/PermissionsTest.Codeunit.al b/src/Layers/W1/Tests/Permissions/PermissionsTest.Codeunit.al index eb9afcec76f..03c08c57b58 100644 --- a/src/Layers/W1/Tests/Permissions/PermissionsTest.Codeunit.al +++ b/src/Layers/W1/Tests/Permissions/PermissionsTest.Codeunit.al @@ -509,6 +509,114 @@ codeunit 139400 "Permissions Test" RecRef.Delete(); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + [Scope('OnPrem')] + procedure PopulatePermissionBufferPrefersSpecificObjectOverWildcard() + var + TenantPermissionSet: Record "Tenant Permission Set"; + TenantPermission: Record "Tenant Permission"; + AccessControl: Record "Access Control"; + PermissionBuffer: Record "Permission Buffer" temporary; + EffectivePermissionsMgt: Codeunit "Effective Permissions Mgt."; + SpecificPageId: Integer; + begin + // [SCENARIO 615963] A specific object permission is displayed instead of the wildcard permission + // [GIVEN] A permission set with an indirect wildcard permission + SpecificPageId := Page::"Customer Card"; + LibraryPermissions.CreateTenantPermissionSet(TenantPermissionSet, LibraryUtility.GenerateGUID(), NullGuid); + + TenantPermission.Init(); + TenantPermission."App ID" := NullGuid; + TenantPermission."Role ID" := TenantPermissionSet."Role ID"; + TenantPermission."Object Type" := TenantPermission."Object Type"::Page; + TenantPermission."Object ID" := 0; + TenantPermission."Execute Permission" := TenantPermission."Execute Permission"::Indirect; + TenantPermission.Insert(true); + + // [GIVEN] The same permission set grants direct access to a specific page + TenantPermission.Init(); + TenantPermission."App ID" := NullGuid; + TenantPermission."Role ID" := TenantPermissionSet."Role ID"; + TenantPermission."Object Type" := TenantPermission."Object Type"::Page; + TenantPermission."Object ID" := SpecificPageId; + TenantPermission."Execute Permission" := TenantPermission."Execute Permission"::Yes; + TenantPermission.Insert(true); + + // [GIVEN] The permission set is assigned to the current user + AccessControl.Init(); + AccessControl."User Security ID" := UserSecurityId(); + AccessControl."Role ID" := TenantPermissionSet."Role ID"; + AccessControl.Scope := AccessControl.Scope::Tenant; + AccessControl."Company Name" := CompanyName(); + AccessControl.Insert(true); + + // [WHEN] The permission buffer is populated for the specific page + EffectivePermissionsMgt.PopulatePermissionBuffer( + PermissionBuffer, UserSecurityId(), CompanyName(), TenantPermission."Object Type"::Page, SpecificPageId); + + // [THEN] The specific page permission is displayed + PermissionBuffer.SetRange("Permission Set", TenantPermissionSet."Role ID"); + Assert.IsTrue(PermissionBuffer.FindFirst(), 'Permission buffer should contain the permission set.'); + Assert.AreEqual( + PermissionBuffer."Execute Permission"::Yes, PermissionBuffer."Execute Permission", + 'The specific page permission should override the wildcard permission.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + [Scope('OnPrem')] + procedure PopulatePermissionBufferPrefersSpecificEmptyOverWildcard() + var + TenantPermissionSet: Record "Tenant Permission Set"; + TenantPermission: Record "Tenant Permission"; + AccessControl: Record "Access Control"; + PermissionBuffer: Record "Permission Buffer" temporary; + EffectivePermissionsMgt: Codeunit "Effective Permissions Mgt."; + SpecificPageId: Integer; + begin + // [SCENARIO 615963] A specific exclusion is displayed instead of the wildcard permission + // [GIVEN] A permission set with a wildcard permission + SpecificPageId := Page::"Customer Card"; + LibraryPermissions.CreateTenantPermissionSet(TenantPermissionSet, LibraryUtility.GenerateGUID(), NullGuid); + + TenantPermission.Init(); + TenantPermission."App ID" := NullGuid; + TenantPermission."Role ID" := TenantPermissionSet."Role ID"; + TenantPermission."Object Type" := TenantPermission."Object Type"::Page; + TenantPermission."Object ID" := 0; + TenantPermission."Execute Permission" := TenantPermission."Execute Permission"::Yes; + TenantPermission.Insert(true); + + // [GIVEN] The same permission set excludes a specific page + TenantPermission.Init(); + TenantPermission."App ID" := NullGuid; + TenantPermission."Role ID" := TenantPermissionSet."Role ID"; + TenantPermission."Object Type" := TenantPermission."Object Type"::Page; + TenantPermission."Object ID" := SpecificPageId; + TenantPermission."Execute Permission" := TenantPermission."Execute Permission"::" "; + TenantPermission.Insert(true); + + // [GIVEN] The permission set is assigned to the current user + AccessControl.Init(); + AccessControl."User Security ID" := UserSecurityId(); + AccessControl."Role ID" := TenantPermissionSet."Role ID"; + AccessControl.Scope := AccessControl.Scope::Tenant; + AccessControl."Company Name" := CompanyName(); + AccessControl.Insert(true); + + // [WHEN] The permission buffer is populated for the specific page + EffectivePermissionsMgt.PopulatePermissionBuffer( + PermissionBuffer, UserSecurityId(), CompanyName(), TenantPermission."Object Type"::Page, SpecificPageId); + + // [THEN] The specific page exclusion is displayed + PermissionBuffer.SetRange("Permission Set", TenantPermissionSet."Role ID"); + Assert.IsTrue(PermissionBuffer.FindFirst(), 'Permission buffer should contain the permission set.'); + Assert.AreEqual( + PermissionBuffer."Execute Permission"::" ", PermissionBuffer."Execute Permission", + 'The specific page exclusion should override the wildcard permission.'); + end; + //[Test] ignore 426467 [HandlerFunctions('SendResolveNotificationHandler')] [TransactionModel(TransactionModel::AutoRollback)] From 81bde8f89e4d5852caae0e99263dd88079abfe3c Mon Sep 17 00:00:00 2001 From: Darrick Joo Date: Thu, 23 Jul 2026 13:56:19 +0200 Subject: [PATCH 2/3] Refactor expanded permission lookup Combine exact-object and wildcard fallback selection behind a single lookup helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../EffectivePermissionsMgt.Codeunit.al | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al index 43671f3501a..535c87ff36a 100644 --- a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al +++ b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al @@ -297,7 +297,6 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission: Record "Expanded Permission"; PermissionSetBuffer: Record "Permission Set Buffer"; AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute : Integer; - ExpandedPermissionFound: Boolean; begin PermissionBuffer.Reset(); PermissionBuffer.DeleteAll(); @@ -328,15 +327,7 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission.SetRange("App ID", AccessControl."App ID"); ExpandedPermission.SetRange("Role ID", AccessControl."Role ID"); - // Specific object permissions override the wildcard entry. - ExpandedPermission.SetRange("Object ID", PassedObjectId); - ExpandedPermissionFound := ExpandedPermission.FindFirst(); - if not ExpandedPermissionFound then begin - ExpandedPermission.SetRange("Object ID", 0); - ExpandedPermissionFound := ExpandedPermission.FindFirst(); - end; - - if ExpandedPermissionFound then begin + if FindExpandedPermissionForObject(ExpandedPermission, PassedObjectId) then begin FillPermissionBufferFromExpandedPermission(PermissionBuffer, ExpandedPermission); SetHighestAssignedPermission(PermissionBuffer, AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute); PermissionBuffer.Order := PermissionBuffer.Source; @@ -538,6 +529,16 @@ codeunit 9852 "Effective Permissions Mgt." PermissionBuffer."Security Filter" := ExpandedPermission."Security Filter"; end; + local procedure FindExpandedPermissionForObject(var ExpandedPermission: Record "Expanded Permission"; ObjectId: Integer): Boolean + begin + ExpandedPermission.SetRange("Object ID", ObjectId); + if ExpandedPermission.FindFirst() then + exit(true); + + ExpandedPermission.SetRange("Object ID", 0); + exit(ExpandedPermission.FindFirst()); + end; + local procedure MarkAllObjFromPermissionSet(var AllObj: Record AllObj; PermissionSetID: Code[20]; AppID: Guid; ObjScope: Option) var ExpandedPermission: Record "Expanded Permission"; From f051f5c0878ea493fa61721caaaefd7cdab1c849 Mon Sep 17 00:00:00 2001 From: Darrick Joo Date: Thu, 23 Jul 2026 14:01:07 +0200 Subject: [PATCH 3/3] Revert expanded permission lookup refactor Restore the original inline exact-object and wildcard fallback lookup. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../EffectivePermissionsMgt.Codeunit.al | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al index 535c87ff36a..43671f3501a 100644 --- a/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al +++ b/src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al @@ -297,6 +297,7 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission: Record "Expanded Permission"; PermissionSetBuffer: Record "Permission Set Buffer"; AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute : Integer; + ExpandedPermissionFound: Boolean; begin PermissionBuffer.Reset(); PermissionBuffer.DeleteAll(); @@ -327,7 +328,15 @@ codeunit 9852 "Effective Permissions Mgt." ExpandedPermission.SetRange("App ID", AccessControl."App ID"); ExpandedPermission.SetRange("Role ID", AccessControl."Role ID"); - if FindExpandedPermissionForObject(ExpandedPermission, PassedObjectId) then begin + // Specific object permissions override the wildcard entry. + ExpandedPermission.SetRange("Object ID", PassedObjectId); + ExpandedPermissionFound := ExpandedPermission.FindFirst(); + if not ExpandedPermissionFound then begin + ExpandedPermission.SetRange("Object ID", 0); + ExpandedPermissionFound := ExpandedPermission.FindFirst(); + end; + + if ExpandedPermissionFound then begin FillPermissionBufferFromExpandedPermission(PermissionBuffer, ExpandedPermission); SetHighestAssignedPermission(PermissionBuffer, AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute); PermissionBuffer.Order := PermissionBuffer.Source; @@ -529,16 +538,6 @@ codeunit 9852 "Effective Permissions Mgt." PermissionBuffer."Security Filter" := ExpandedPermission."Security Filter"; end; - local procedure FindExpandedPermissionForObject(var ExpandedPermission: Record "Expanded Permission"; ObjectId: Integer): Boolean - begin - ExpandedPermission.SetRange("Object ID", ObjectId); - if ExpandedPermission.FindFirst() then - exit(true); - - ExpandedPermission.SetRange("Object ID", 0); - exit(ExpandedPermission.FindFirst()); - end; - local procedure MarkAllObjFromPermissionSet(var AllObj: Record AllObj; PermissionSetID: Code[20]; AppID: Guid; ObjScope: Option) var ExpandedPermission: Record "Expanded Permission";