Codeunit 9852 Effective Permissions Mgt.

App
Base Application
Namespace
System.Security.AccessControl
Versions
17-28

Procedures, 18Events, 1

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/System/Permissions/EffectivePermissionsMgt.Codeunit.al858 lines, Copyright (c) Microsoft Corporation. MIT

namespace System.Security.AccessControl;

using System;
using System.Azure.Identity;
using System.Environment;
using System.Environment.Configuration;
using System.IO;
using System.Reflection;
using System.Security.User;

codeunit 9852 "Effective Permissions Mgt."
{

    trigger OnRun()
    begin
    end;

    var
        UserAccountHelper: DotNet NavUserAccountHelper;
        DialogFormatMsg: Label 'Reading objects...@1@@@@@@@@@@@@@@@@@@';
        CannotViewEffectivePermissionsForOtherUserErr: Label 'Only users with the SUPER or the SECURITY permission set can view effective permissions for other users.';
        ChangeAffectsOthersMsg: Label 'Your change in permission set %1 will affect other users that the permission set is assigned to.', Comment = '%1 = permission set ID that was changed';
        ChangeAffectsOthersNameTxt: Label 'Changing permission sets for other users';
        ChangeAffectsOthersDescTxt: Label 'Show a warning when changing a permission set that is assigned to other users.';
        DirectConflictWithIndirectMsg: Label 'This user has been given the Yes permission for the selected object, but their license allows them only the Indirect permission.';
        DirectConflictWithNoneMsg: Label 'This user has been given the Yes permission to the selected object, but the object is not included in their license.';
        IndirectConflictWithNoneMsg: Label 'This user has been given the Indirect permission for the selected object, but the object is not included in their license.';
        UserListLbl: Label 'See users affected';
        UndoChangeLbl: Label 'Undo change';
        DontShowAgainLbl: Label 'Never show again';
        RevertChangeQst: Label 'Do you want to revert the recent change to permission set %1?', Comment = '%1 = the permission set ID that has been changed.';
        ConflictLbl: Label '%1 (reduced)', Comment = '%1 = permission set type, e.g. Indirect';

    procedure OpenPageForUser(UserSID: Guid)
    var
        EffectivePermissions: Page "Effective Permissions";
    begin
        EffectivePermissions.SetUserSID(UserSID);
        EffectivePermissions.Run();
    end;

    procedure DisallowViewingEffectivePermissionsForNonAdminUsers(OtherUserSecurityId: Guid)
    var
        UserPermissions: Codeunit "User Permissions";
    begin
        if not UserPermissions.CanManageUsersOnTenant(UserSecurityId()) then
            if OtherUserSecurityId <> UserSecurityId() then
                Error(CannotViewEffectivePermissionsForOtherUserErr);
    end;

    local procedure GetPlanId(PlanOrRole: Enum Licenses): guid
    var
        PlanIds: Codeunit "Plan Ids";
    begin
        case PlanOrRole of
            Enum::Licenses::Basic:
                exit(PlanIds.GetBasicPlanId());
            Enum::Licenses::"Delegated Admin":
                exit(PlanIDs.GetDelegatedAdminPlanId());
            Enum::Licenses::Device:
                exit(PlanIds.GetDevicePlanId());
            Enum::Licenses::Essential:
                exit(PlanIds.GetEssentialPlanId());
            Enum::Licenses::"External Accountant":
                exit(PlanIds.GetExternalAccountantPlanId());
            Enum::Licenses::HelpDesk:
                exit(PlanIds.GetHelpDeskPlanId());
            Enum::Licenses::"Internal Admin":
                exit(PlanIds.GetGlobalAdminPlanId());
            Enum::Licenses::"D365 Admin":
                exit(PlanIds.GetD365AdminPlanId());
            Enum::Licenses::Premium:
                exit(PlanIds.GetPremiumPlanId());
            Enum::Licenses::"Team Member":
                exit(PlanIds.GetTeamMemberPlanId());
            Enum::Licenses::Viral:
                exit(PlanIds.GetViralSignupPlanId());
        end;
    end;

    internal procedure PopulatePermissionConflictsTable(PlanOrRole: Enum Licenses; RoleId: Code[20]; var PermissionConflicts: Record "Permission Conflicts" temporary)
    var
        ExpandedPermission: Record "Expanded Permission";
        EntitlementPermissionsCommaStr: Text;
        Read: Integer;
        Insert: Integer;
        Modify: Integer;
        Delete: Integer;
        Execute: Integer;
    begin
        PermissionConflicts.Reset();
        PermissionConflicts.DeleteAll();

        ExpandedPermission.SetRange("Role ID", RoleId);
        ExpandedPermission.SetRange("Object Type", ExpandedPermission."Object Type"::"Table Data");
        if ExpandedPermission.FindSet() then
            repeat
                EntitlementPermissionsCommaStr := UserAccountHelper.GetEntitlementPermissionForObjectAndPlan(GetPlanId(PlanOrRole), ExpandedPermission."Object Type"::"Table Data", ExpandedPermission."Object ID");
                ExtractPermissionsFromText(EntitlementPermissionsCommaStr, Read, Insert, Modify, Delete, Execute);

                InitializePermissionConflicts(ExpandedPermission."Object Type", ExpandedPermission."Object ID", ExpandedPermission."Read Permission",
                    ExpandedPermission."Insert Permission", ExpandedPermission."Modify Permission", ExpandedPermission."Delete Permission",
                    ExpandedPermission."Execute Permission", ExpandedPermission.Scope = ExpandedPermission.Scope::System, PermissionConflicts);

                UpdatePermissionIfHigherPermission(Read, PermissionConflicts."Entitlement Read Permission");
                UpdatePermissionIfHigherPermission(Insert, PermissionConflicts."Entitlement Insert Permission");
                UpdatePermissionIfHigherPermission(Modify, PermissionConflicts."Entitlement Modify Permission");
                UpdatePermissionIfHigherPermission(Delete, PermissionConflicts."Entitlement Delete Permission");
                UpdatePermissionIfHigherPermission(Execute, PermissionConflicts."Entitlement Execute Permission");

                InsertPermissionConflictIfConflictExists(PermissionConflicts);
            until ExpandedPermission.Next() = 0;
    end;

    local procedure InitializePermissionConflicts(PermissionObjectType: Option; PermissionObjectID: Integer;
                                                    ReadPerm: Option; InsertPerm: Option; ModifyPerm: Option; DeletePerm: Option; ExecutePerm: Option; UserDefined: Boolean;
                                                    var PermissionConflicts: Record "Permission Conflicts")
    begin
        PermissionConflicts.Init();
        PermissionConflicts."Object Type" := PermissionObjectType;
        PermissionConflicts."Object ID" := PermissionObjectID;
        PermissionConflicts."Read Permission" := ConvertToPermission(ReadPerm);
        PermissionConflicts."Insert Permission" := ConvertToPermission(InsertPerm);
        PermissionConflicts."Modify Permission" := ConvertToPermission(ModifyPerm);
        PermissionConflicts."Delete Permission" := ConvertToPermission(DeletePerm);
        PermissionConflicts."Execute Permission" := ConvertToPermission(ExecutePerm);
        PermissionConflicts."Entitlement Read Permission" := PermissionConflicts."Entitlement Read Permission"::None;
        PermissionConflicts."Entitlement Insert Permission" := PermissionConflicts."Entitlement Read Permission"::None;
        PermissionConflicts."Entitlement Modify Permission" := PermissionConflicts."Entitlement Read Permission"::None;
        PermissionConflicts."Entitlement Delete Permission" := PermissionConflicts."Entitlement Read Permission"::None;
        PermissionConflicts."Entitlement Execute Permission" := PermissionConflicts."Entitlement Read Permission"::None;
        PermissionConflicts."User Defined" := UserDefined;
    end;

    local procedure UpdatePermissionIfHigherPermission(Permission: Option; var UpdatePermission: Enum Permission)
    var
        ConvertedPermission: Enum Permission;
    begin
        ConvertedPermission := ConvertToPermission(Permission);
        if ConvertedPermission.AsInteger() > UpdatePermission.AsInteger() then
            UpdatePermission := ConvertedPermission;
    end;

    local procedure InsertPermissionConflictIfConflictExists(var PermissionConflicts: Record "Permission Conflicts")
    var
        RIMDConflict: Boolean;
        ExecuteConflict: Boolean;
    begin
        if (PermissionConflicts."Read Permission".AsInteger() > PermissionConflicts."Entitlement Read Permission".AsInteger()) or
            (PermissionConflicts."Insert Permission".AsInteger() > PermissionConflicts."Entitlement Insert Permission".AsInteger()) or
            (PermissionConflicts."Modify Permission".AsInteger() > PermissionConflicts."Entitlement Modify Permission".AsInteger()) or
            (PermissionConflicts."Delete Permission".AsInteger() > PermissionConflicts."Entitlement Delete Permission".AsInteger()) then
            RIMDConflict := true;

        if (PermissionConflicts."Object Type" = PermissionConflicts."Object Type"::System) and
            (PermissionConflicts."Execute Permission".AsInteger() > PermissionConflicts."Entitlement Execute Permission".AsInteger()) then
            ExecuteConflict := true;

        if RIMDConflict or ExecuteConflict then
            PermissionConflicts.Insert();
    end;

    internal procedure PopulatePermissionConflictsOverviewTable(var PermissionConflictsOverview: Record "Permission Conflicts Overview" temporary; PlansExist: Dictionary of [Guid, Boolean])
    var
        MetadataPermissionSet: Record "Metadata Permission Set";
        TenantPermissionSet: Record "Tenant Permission Set";
        ProgressBar: Codeunit "Config. Progress Bar";
        PlanIds: Codeunit "Plan Ids";
        PlanOrRole: Enum Licenses;
        CurrentPermissionSet: Integer;
        TotalPermissionSets: Integer;
        ProgressBarTitleLbl: Label 'Loading permission sets';
        ProgressLbl: Label '#1#### of #2####', Comment = '#1 - Current permission set, #2 - Total number of permission sets';
    begin
        TotalPermissionSets := MetadataPermissionSet.Count() + TenantPermissionSet.Count();

        ProgressBar.Init(TotalPermissionSets, 1, ProgressBarTitleLbl);
        PermissionConflictsOverview.Reset();
        PermissionConflictsOverview.DeleteAll();

        MetadataPermissionSet.SetRange(Assignable, true);
        if MetadataPermissionSet.FindSet() then
            repeat
                CurrentPermissionSet += 1;
                ProgressBar.Update(StrSubstNo(ProgressLbl, CurrentPermissionSet, TotalPermissionSets));
                PermissionConflictsOverview.PermissionSetID := CopyStr(MetadataPermissionSet."Role ID", 1, 20); // Assignable permission sets are always limited to length 20 
                PermissionConflictsOverview.Type := PermissionConflictsOverview.Type::System;
                if PlansExist.ContainsKey(PlanIds.GetBasicPlanId()) then
                    PermissionConflictsOverview.Basic := IsPermissionSetCoveredByLicense(PlanOrRole::Basic, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetTeamMemberPlanId()) then
                    PermissionConflictsOverview."Team Member" := IsPermissionSetCoveredByLicense(PlanOrRole::"Team Member", MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetEssentialPlanId()) then
                    PermissionConflictsOverview.Essential := IsPermissionSetCoveredByLicense(PlanOrRole::Essential, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetPremiumPlanId()) then
                    PermissionConflictsOverview.Premium := IsPermissionSetCoveredByLicense(PlanOrRole::Premium, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetDevicePlanId()) then
                    PermissionConflictsOverview.Device := IsPermissionSetCoveredByLicense(PlanOrRole::Device, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetExternalAccountantPlanId()) then
                    PermissionConflictsOverview."External Accountant" := IsPermissionSetCoveredByLicense(PlanOrRole::"External Accountant", MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetGlobalAdminPlanId()) then
                    PermissionConflictsOverview."Internal Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"Internal Admin", MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetD365AdminPlanId()) then
                    PermissionConflictsOverview."D365 Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"D365 Admin", MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetDelegatedAdminPlanId()) then
                    PermissionConflictsOverview."Delegated Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"Delegated Admin", MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetHelpDeskPlanId()) then
                    PermissionConflictsOverview.HelpDesk := IsPermissionSetCoveredByLicense(PlanOrRole::HelpDesk, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                if PlansExist.ContainsKey(PlanIds.GetViralSignupPlanId()) then
                    PermissionConflictsOverview.Viral := IsPermissionSetCoveredByLicense(PlanOrRole::Viral, MetadataPermissionSet."Role ID", PermissionConflictsOverview.Type::System);
                PermissionConflictsOverview.Insert();
            until MetadataPermissionSet.Next() = 0;

        if TenantPermissionSet.FindSet() then
            repeat
                CurrentPermissionSet += 1;
                ProgressBar.Update(StrSubstNo(ProgressLbl, CurrentPermissionSet, TotalPermissionSets));
                PermissionConflictsOverview.PermissionSetID := TenantPermissionSet."Role ID";
                PermissionConflictsOverview.Type := PermissionConflictsOverview.Type::User;
                if PlansExist.ContainsKey(PlanIds.GetBasicPlanId()) then
                    PermissionConflictsOverview.Basic := IsPermissionSetCoveredByLicense(PlanOrRole::Basic, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetTeamMemberPlanId()) then
                    PermissionConflictsOverview."Team Member" := IsPermissionSetCoveredByLicense(PlanOrRole::"Team Member", TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetEssentialPlanId()) then
                    PermissionConflictsOverview.Essential := IsPermissionSetCoveredByLicense(PlanOrRole::Essential, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetPremiumPlanId()) then
                    PermissionConflictsOverview.Premium := IsPermissionSetCoveredByLicense(PlanOrRole::Premium, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetDevicePlanId()) then
                    PermissionConflictsOverview.Device := IsPermissionSetCoveredByLicense(PlanOrRole::Device, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetExternalAccountantPlanId()) then
                    PermissionConflictsOverview."External Accountant" := IsPermissionSetCoveredByLicense(PlanOrRole::"External Accountant", TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetGlobalAdminPlanId()) then
                    PermissionConflictsOverview."Internal Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"Internal Admin", TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetD365AdminPlanId()) then
                    PermissionConflictsOverview."D365 Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"D365 Admin", TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetDelegatedAdminPlanId()) then
                    PermissionConflictsOverview."Delegated Admin" := IsPermissionSetCoveredByLicense(PlanOrRole::"Delegated Admin", TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetHelpDeskPlanId()) then
                    PermissionConflictsOverview.HelpDesk := IsPermissionSetCoveredByLicense(PlanOrRole::HelpDesk, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                if PlansExist.ContainsKey(PlanIds.GetViralSignupPlanId()) then
                    PermissionConflictsOverview.Viral := IsPermissionSetCoveredByLicense(PlanOrRole::Viral, TenantPermissionSet."Role ID", PermissionConflictsOverview.Type::User);
                PermissionConflictsOverview.Insert();
            until TenantPermissionSet.Next() = 0;
        ProgressBar.Close();
    end;

    local procedure IsPermissionSetCoveredByLicense(PlanOrRole: Enum Licenses; PermissionSetID: Code[30]; PermissionType: Option): Boolean
    var
        ExpandedPermission: Record "Expanded Permission";
        PermissionConflictsOverview: Record "Permission Conflicts Overview";
        PermissionBuffer: Record "Permission Buffer";
    begin
        if PermissionType = PermissionConflictsOverview.Type::System then
            ExpandedPermission.SetRange(Scope, ExpandedPermission.Scope::System)
        else
            ExpandedPermission.SetRange(Scope, ExpandedPermission.Scope::Tenant);

        ExpandedPermission.SetRange("Role ID", PermissionSetID);
        ExpandedPermission.SetRange("Object Type", ExpandedPermission."Object Type"::"Table Data");
        if ExpandedPermission.FindSet() then
            repeat
                FillPermissionBufferFromExpandedPermission(PermissionBuffer, ExpandedPermission);
                if IsPermissionRestrictedByLicense(PermissionBuffer, ExpandedPermission."Object Type", ExpandedPermission."Object ID", PlanOrRole) then
                    exit(false);
            until ExpandedPermission.Next() = 0;
        exit(true);
    end;

    local procedure IsPermissionRestrictedByLicense(Permission: Record "Permission Buffer"; PermissionObjectType: Option; PermissionObjectId: Integer; PlanOrRole: Enum Licenses) Result: Boolean
    var
        DummyExpandedPermission: Record "Expanded Permission";
        PermissionManager: Codeunit "Permission Manager";
        EntitlementPermissionsCommaStr: Text;
        Read: Integer;
        Insert: Integer;
        Modify: Integer;
        Delete: Integer;
        Execute: Integer;
    begin
        EntitlementPermissionsCommaStr := UserAccountHelper.GetEntitlementPermissionForObjectAndPlan(GetPlanId(PlanOrRole), PermissionObjectType, PermissionObjectId);
        ExtractPermissionsFromText(EntitlementPermissionsCommaStr, Read, Insert, Modify, Delete, Execute);

        if PermissionObjectType = DummyExpandedPermission."Object Type"::"Table Data" then begin
            Result := PermissionManager.IsFirstPermissionHigherThanSecond(Permission."Read Permission", Read);
            Result := Result or PermissionManager.IsFirstPermissionHigherThanSecond(Permission."Insert Permission", Insert);
            Result := Result or PermissionManager.IsFirstPermissionHigherThanSecond(Permission."Modify Permission", Modify);
            Result := Result or PermissionManager.IsFirstPermissionHigherThanSecond(Permission."Delete Permission", Delete);
        end else
            Result := PermissionManager.IsFirstPermissionHigherThanSecond(Permission."Execute Permission", Execute);

        exit(Result);
    end;

    procedure PopulatePermissionBuffer(var PermissionBuffer: Record "Permission Buffer"; PassedUserID: Guid; PassedCompanyName: Text[50]; PassedObjectType: Integer; PassedObjectId: Integer)
    var
        AccessControl: Record "Access Control";
        EffectivePermission: Record Permission;
        ExpandedPermission: Record "Expanded Permission";
        PermissionSetBuffer: Record "Permission Set Buffer";
        AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute : Integer;
    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));
        AccessControl.SetFilter("Company Name", '%1|%2', '', PassedCompanyName);
        if AccessControl.FindSet() then
            repeat
                // do not show permission sets for hidden extensions
                if StrPos(UpperCase(AccessControl."App Name"), UpperCase('_Exclude_')) <> 1 then begin
                    PermissionBuffer.Init();
                    PermissionBuffer."Permission Set" := AccessControl."Role ID";
                    PermissionBuffer.Type := PermissionSetBuffer.GetType(AccessControl.Scope, AccessControl."App ID");

                    if AccessControl."User Security ID" = PassedUserID then
                        PermissionBuffer.Source := PermissionBuffer.Source::Normal
                    else
                        PermissionBuffer.Source := PermissionBuffer.Source::"Security Group";

                    if AccessControl.Scope = AccessControl.Scope::System then
                        ExpandedPermission.SetRange(Scope, ExpandedPermission.Scope::System)
                    else
                        ExpandedPermission.SetRange(Scope, ExpandedPermission.Scope::Tenant);

                    ExpandedPermission.SetRange("App ID", AccessControl."App ID");
                    ExpandedPermission.SetRange("Role ID", AccessControl."Role ID");
                    if ExpandedPermission.FindFirst() then begin
                        FillPermissionBufferFromExpandedPermission(PermissionBuffer, ExpandedPermission);
                        SetHighestAssignedPermission(PermissionBuffer, AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute);
                        PermissionBuffer.Order := PermissionBuffer.Source;
                        if PermissionBuffer.Insert() then; // avoid errors in case the user was assigned same role both a specific company and globally
                    end;
                end;
            until AccessControl.Next() = 0;

        // find inherent permissions
        PopulatePermissionRecordWithEffectivePermissionsForObject(EffectivePermission, PassedUserID, PassedCompanyName, PassedObjectType, PassedObjectId);
        PopulatePermissionBufferWithInherentPermission(AssignedRead, AssignedInsert, AssignedModify, AssignedDelete, AssignedExecute, EffectivePermission, PermissionBuffer);

        // find entitlement permission
        PopulatePermissionBufferWithEntitlementPermissionsForObject(PermissionBuffer, PassedUserID, PassedObjectType, PassedObjectId);
    end;

    procedure PopulatePermissionBufferWithEntitlementPermissionsForObject(var PermissionBuffer: Record "Permission Buffer"; PassedUserID: Guid; PassedObjectType: Option; PassedObjectId: Integer)
    var
        EnvironmentInfo: Codeunit "Environment Information";
        PermissionCommaStr: Text;
        Read, Insert, Modify, Delete, Execute : Integer;
    begin
        if not EnvironmentInfo.IsSaaS() then
            exit;
        PermissionBuffer.Init();
        PermissionBuffer.Source := PermissionBuffer.Source::Entitlement;
        PermissionBuffer."Permission Set" := '';
        PermissionBuffer.Type := PermissionBuffer.Type::System;
        PermissionCommaStr := UserAccountHelper.GetEntitlementPermissionForObject(PassedUserID, PassedObjectType, PassedObjectId);
        ExtractPermissionsFromText(PermissionCommaStr, Read, Insert, Modify, Delete, Execute);
        PermissionBuffer."Read Permission" := Read;
        PermissionBuffer."Insert Permission" := Insert;
        PermissionBuffer."Modify Permission" := Modify;
        PermissionBuffer."Delete Permission" := Delete;
        PermissionBuffer."Execute Permission" := Execute;
        PermissionBuffer.Order := 10000; // order entitlement last
        PermissionBuffer.Insert();
    end;

    local procedure PopulatePermissionBufferWithInherentPermission(AssignedRead: Integer; AssignedInsert: Integer; AssignedModify: Integer; AssignedDelete: Integer; AssignedExecute: Integer; var EffectivePermission: Record Permission; var PermissionBuffer: Record "Permission Buffer")
    begin
        if (EffectivePermission."Read Permission" > AssignedRead) or
           (EffectivePermission."Insert Permission" > AssignedInsert) or
           (EffectivePermission."Modify Permission" > AssignedModify) or
           (EffectivePermission."Delete Permission" > AssignedDelete) or
           (EffectivePermission."Execute Permission" > AssignedExecute)
            then begin
            PermissionBuffer.Init();
            PermissionBuffer.Source := PermissionBuffer.Source::Inherent;
            PermissionBuffer."Permission Set" := '_';
            PermissionBuffer.Type := PermissionBuffer.Type::System;
            PermissionBuffer.Order := PermissionBuffer.Source;

            if EffectivePermission."Read Permission" > AssignedRead then
                PermissionBuffer."Read Permission" := EffectivePermission."Read Permission"
            else
                PermissionBuffer."Read Permission" := EffectivePermission."Read Permission"::" ";

            if EffectivePermission."Insert Permission" > AssignedInsert then
                PermissionBuffer."Insert Permission" := EffectivePermission."Insert Permission"
            else
                PermissionBuffer."Insert Permission" := EffectivePermission."Insert Permission"::" ";

            if EffectivePermission."Modify Permission" > AssignedModify then
                PermissionBuffer."Modify Permission" := EffectivePermission."Modify Permission"
            else
                PermissionBuffer."Modify Permission" := EffectivePermission."Modify Permission"::" ";

            if EffectivePermission."Delete Permission" > AssignedDelete then
                PermissionBuffer."Delete Permission" := EffectivePermission."Delete Permission"
            else
                PermissionBuffer."Delete Permission" := EffectivePermission."Delete Permission"::" ";

            if EffectivePermission."Execute Permission" > AssignedExecute then
                PermissionBuffer."Execute Permission" := EffectivePermission."Execute Permission"
            else
                PermissionBuffer."Execute Permission" := EffectivePermission."Execute Permission"::" ";

            PermissionBuffer.Insert();
        end;
    end;

    local procedure SetHighestAssignedPermission(PermissionBuffer: Record "Permission Buffer"; var AssignedRead: Integer; var AssignedInsert: Integer; var AssignedModify: Integer; var AssignedDelete: Integer; var AssignedExecute: Integer)
    begin
        if PermissionBuffer."Read Permission" > AssignedRead then
            AssignedRead := PermissionBuffer."Read Permission";

        if PermissionBuffer."Insert Permission" > AssignedInsert then
            AssignedInsert := PermissionBuffer."Insert Permission";

        if PermissionBuffer."Modify Permission" > AssignedModify then
            AssignedModify := PermissionBuffer."Modify Permission";

        if PermissionBuffer."Delete Permission" > AssignedDelete then
            AssignedDelete := PermissionBuffer."Delete Permission";

        if PermissionBuffer."Execute Permission" > AssignedExecute then
            AssignedExecute := PermissionBuffer."Execute Permission";
    end;

    local procedure GetAccessControlFilterForUser(UserSecId: Guid): Text
    var
        SecurityGroupMemberBuffer: Record "Security Group Member Buffer";
        SecurityGroup: Codeunit "Security Group";
        FilterTextBuilder: TextBuilder;
    begin
        // Consider permissions assigned to the user directly.
        FilterTextBuilder.Append(UserSecId);

        // Consider permissions assigned to the user through security groups.
        SecurityGroup.GetMembers(SecurityGroupMemberBuffer);
        SecurityGroupMemberBuffer.SetRange("User Security ID", UserSecId);
        if SecurityGroupMemberBuffer.FindSet() then
            repeat
                FilterTextBuilder.Append('|');
                FilterTextBuilder.Append(SecurityGroup.GetGroupUserSecurityId(SecurityGroupMemberBuffer."Security Group Code"));
            until SecurityGroupMemberBuffer.Next() = 0;

        exit(FilterTextBuilder.ToText());
    end;

    procedure PopulateEffectivePermissionsBuffer(var Permission: Record Permission; PassedUserID: Guid; PassedCompanyName: Text[50]; PassedObjectType: Integer; PassedObjectId: Integer; ShowAllObjects: Boolean)
    var
        AllObj: Record AllObj;
        Window: Dialog;
        TotalCount: Integer;
        NumObjectsProcessed: Integer;
        TimesToUpdate: Integer;
    begin
        Permission.Reset();
        Permission.DeleteAll();

        if PassedObjectId = 0 then begin
            Window.Open(DialogFormatMsg);
            AllObj.SetFilter("Object Type", '%1|%2|%3|%4|%5|%6|%7|%8|%9',
              Permission."Object Type"::"Table Data",
              Permission."Object Type"::Table,
              Permission."Object Type"::Report,
              Permission."Object Type"::Codeunit,
              Permission."Object Type"::XMLport,
              Permission."Object Type"::MenuSuite,
              Permission."Object Type"::Page,
              Permission."Object Type"::Query,
              Permission."Object Type"::System);
            if not ShowAllObjects then
                FilterOnlyObjectsPresentInUserPermissionSets(AllObj, PassedUserID, PassedCompanyName);
            if AllObj.FindSet() then begin
                TotalCount := AllObj.Count();
                // Only update every 10 %
                TimesToUpdate := TotalCount div 10;
                if TimesToUpdate = 0 then
                    TimesToUpdate := 1;

                repeat
                    InsertEffectivePermissionForObject(Permission, PassedUserID, PassedCompanyName,
                      AllObj."Object Type", AllObj."Object ID");
                    NumObjectsProcessed += 1;
                    if (NumObjectsProcessed mod TimesToUpdate) = 0 then
                        Window.Update(1, Round(NumObjectsProcessed * 10000 / TotalCount, 1));
                until AllObj.Next() = 0;
                Permission.FindFirst();
            end;
            Window.Close();
        end else
            InsertEffectivePermissionForObject(Permission, PassedUserID, PassedCompanyName, PassedObjectType, PassedObjectId);
    end;

    local procedure FilterOnlyObjectsPresentInUserPermissionSets(var AllObj: Record AllObj; PassedUserID: Guid; PassedCompanyName: Text[50])
    var
        AccessControl: Record "Access Control";
    begin
        AccessControl.SetFilter("User Security ID", GetAccessControlFilterForUser(PassedUserID));
        AccessControl.SetFilter("Company Name", '%1|%2', '', PassedCompanyName);
        if AccessControl.FindSet() then
            repeat
                MarkAllObjFromPermissionSet(AllObj, AccessControl."Role ID", AccessControl."App ID", AccessControl.Scope);
            until AccessControl.Next() = 0;

        AllObj.MarkedOnly(true);
    end;

    local procedure InsertEffectivePermissionForObject(var Permission: Record Permission; PassedUserID: Guid; PassedCompanyName: Text[50]; PassedObjectType: Integer; PassedObjectId: Integer)
    begin
        Permission.Init();
        Permission."Object Type" := PassedObjectType;
        Permission."Object ID" := PassedObjectId;
        PopulatePermissionRecordWithEffectivePermissionsForObject(Permission, PassedUserID, PassedCompanyName,
          PassedObjectType, PassedObjectId);
        Permission.Insert();
    end;

    local procedure FillPermissionBufferFromExpandedPermission(var PermissionBuffer: Record "Permission Buffer"; ExpandedPermission: Record "Expanded Permission")
    begin
        PermissionBuffer."Read Permission" := ExpandedPermission."Read Permission";
        PermissionBuffer."Insert Permission" := ExpandedPermission."Insert Permission";
        PermissionBuffer."Modify Permission" := ExpandedPermission."Modify Permission";
        PermissionBuffer."Delete Permission" := ExpandedPermission."Delete Permission";
        PermissionBuffer."Execute Permission" := ExpandedPermission."Execute Permission";
        PermissionBuffer."Security Filter" := ExpandedPermission."Security Filter";
    end;

    local procedure MarkAllObjFromPermissionSet(var AllObj: Record AllObj; PermissionSetID: Code[20]; AppID: Guid; ObjScope: Option)
    var
        ExpandedPermission: Record "Expanded Permission";
    begin
        ExpandedPermission.SetRange("App ID", AppID);
        ExpandedPermission.SetRange("Role ID", PermissionSetID);
        ExpandedPermission.SetRange(Scope, ObjScope);
        if ExpandedPermission.FindSet() then
            repeat
                MarkAllObj(AllObj, ExpandedPermission."Object Type", ExpandedPermission."Object ID");
            until ExpandedPermission.Next() = 0;
    end;

    local procedure MarkAllObj(var AllObj: Record AllObj; ObjectTypePassed: Integer; ObjectIDPassed: Integer)
    begin
        if ObjectIDPassed = 0 then begin
            AllObj.SetRange("Object Type", ObjectTypePassed);
            if AllObj.FindSet() then
                repeat
                    AllObj.Mark(true);
                until AllObj.Next() = 0;
            AllObj.SetRange("Object Type");
            exit;
        end;

        if AllObj.Get(ObjectTypePassed, ObjectIDPassed) then
            AllObj.Mark(true);
    end;

    procedure PopulatePermissionRecordWithEffectivePermissionsForObject(var Permission: Record Permission; PassedUserID: Guid; PassedCompanyName: Text[50]; PassedObjectType: Option; PassedObjectId: Integer)
    var
        PermissionCommaStr: Text;
        Read: Integer;
        Insert: Integer;
        Modify: Integer;
        Delete: Integer;
        Execute: Integer;
    begin
        PermissionCommaStr := UserAccountHelper.GetEffectivePermissionForObject(
            PassedUserID, PassedCompanyName, PassedObjectType, PassedObjectId);
        ExtractPermissionsFromText(PermissionCommaStr, Read, Insert, Modify, Delete, Execute);
        Permission."Read Permission" := Read;
        Permission."Insert Permission" := Insert;
        Permission."Modify Permission" := Modify;
        Permission."Delete Permission" := Delete;
        Permission."Execute Permission" := Execute;
    end;

    local procedure ExtractPermissionsFromText(PermissionCommaStr: Text; var Read: Integer; var Insert: Integer; var Modify: Integer; var Delete: Integer; var Execute: Integer)
    begin
        Evaluate(Read, SelectStr(1, PermissionCommaStr));
        Evaluate(Insert, SelectStr(2, PermissionCommaStr));
        Evaluate(Modify, SelectStr(3, PermissionCommaStr));
        Evaluate(Delete, SelectStr(4, PermissionCommaStr));
        Evaluate(Execute, SelectStr(5, PermissionCommaStr));
    end;

    procedure ModifyPermission(FieldNumChanged: Integer; PermissionBuffer: Record "Permission Buffer"; PassedObjectType: Integer; PassedObjectId: Integer; PassedUserID: Guid)
    var
        TenantPermission: Record "Tenant Permission";
        CallModify: Boolean;
        OldValue: Integer;
    begin
        TenantPermission.Get(PermissionBuffer.GetAppID(), PermissionBuffer."Permission Set", PassedObjectType, PassedObjectId);
        case FieldNumChanged of
            TenantPermission.FieldNo("Read Permission"):
                begin
                    OldValue := TenantPermission."Read Permission";
                    CallModify := TenantPermission."Read Permission" <> PermissionBuffer."Read Permission";
                    TenantPermission."Read Permission" := PermissionBuffer."Read Permission";
                end;
            TenantPermission.FieldNo("Insert Permission"):
                begin
                    OldValue := TenantPermission."Insert Permission";
                    CallModify := TenantPermission."Insert Permission" <> PermissionBuffer."Insert Permission";
                    TenantPermission."Insert Permission" := PermissionBuffer."Insert Permission";
                end;
            TenantPermission.FieldNo("Modify Permission"):
                begin
                    OldValue := TenantPermission."Modify Permission";
                    CallModify := TenantPermission."Modify Permission" <> PermissionBuffer."Modify Permission";
                    TenantPermission."Modify Permission" := PermissionBuffer."Modify Permission";
                end;
            TenantPermission.FieldNo("Delete Permission"):
                begin
                    OldValue := TenantPermission."Delete Permission";
                    CallModify := TenantPermission."Delete Permission" <> PermissionBuffer."Delete Permission";
                    TenantPermission."Delete Permission" := PermissionBuffer."Delete Permission";
                end;
            TenantPermission.FieldNo("Execute Permission"):
                begin
                    OldValue := TenantPermission."Execute Permission";
                    CallModify := TenantPermission."Execute Permission" <> PermissionBuffer."Execute Permission";
                    TenantPermission."Execute Permission" := PermissionBuffer."Execute Permission";
                end;
        end;
        if not CallModify then
            exit;
        TenantPermission.Modify();
        SendNotification(PermissionBuffer."Permission Set", PassedObjectType, PassedObjectId, PassedUserID, FieldNumChanged, OldValue);
        OnTenantPermissionModified(TenantPermission."Role ID");
    end;

    local procedure SendNotification(PermissionSetID: Code[20]; PassedObjectType: Integer; PassedObjectId: Integer; UserOnPage: Guid; FieldNumChanged: Integer; OldValue: Integer)
    var
        User: Record User;
        MyNotifications: Record "My Notifications";
        Notification: Notification;
        NotificationID: Guid;
    begin
        MarkUsersWithAssignedPermissionSet(User, PermissionSetID);
        User.SetFilter("User Security ID", '<>%1', UserOnPage);
        if User.IsEmpty() then
            exit;

        NotificationID := GetPermissionChangeNotificationId();
        if not MyNotifications.IsEnabled(NotificationID) then
            exit;

        Notification.Id := NotificationID;
        Notification.Message := StrSubstNo(ChangeAffectsOthersMsg, PermissionSetID);
        Notification.SetData('UserOnPage', UserOnPage);
        Notification.SetData('PermissionSetID', PermissionSetID);
        Notification.SetData('ObjectType', Format(PassedObjectType));
        Notification.SetData('ObjectID', Format(PassedObjectId));
        Notification.SetData('FieldNumChanged', Format(FieldNumChanged));
        Notification.SetData('OldValue', Format(OldValue));
        Notification.AddAction(UserListLbl, CODEUNIT::"Effective Permissions Mgt.", 'NotificationShowUsers');
        Notification.AddAction(UndoChangeLbl, CODEUNIT::"Effective Permissions Mgt.", 'NotificationUndoChange');
        Notification.AddAction(DontShowAgainLbl, CODEUNIT::"Effective Permissions Mgt.", 'DisableNotification');
        Notification.Send();
    end;

    procedure NotificationShowUsers(Notification: Notification)
    var
        User: Record User;
        PermissionSetID: Code[20];
    begin
        PermissionSetID := CopyStr(Notification.GetData('PermissionSetID'), 1, 20);
        MarkUsersWithAssignedPermissionSet(User, PermissionSetID);
        User.SetFilter("User Security ID", '<>%1', Notification.GetData('UserOnPage'));
        PAGE.RunModal(PAGE::Users, User);
        if Confirm(StrSubstNo(RevertChangeQst, PermissionSetID), false) then
            NotificationUndoChange(Notification);
    end;

    procedure NotificationUndoChange(Notification: Notification)
    var
        TenantPermission: Record "Tenant Permission";
        ZeroGUID: Guid;
        ObjType: Integer;
        ObjID: Integer;
        FieldNumChanged: Integer;
        OldValue: Integer;
    begin
        Evaluate(ObjType, Notification.GetData('ObjectType'));
        Evaluate(ObjID, Notification.GetData('ObjectID'));
        TenantPermission.Get(ZeroGUID, Notification.GetData('PermissionSetID'), ObjType, ObjID);

        Evaluate(FieldNumChanged, Notification.GetData('FieldNumChanged'));
        Evaluate(OldValue, Notification.GetData('OldValue'));
        case FieldNumChanged of
            TenantPermission.FieldNo("Read Permission"):
                TenantPermission."Read Permission" := OldValue;
            TenantPermission.FieldNo("Insert Permission"):
                TenantPermission."Insert Permission" := OldValue;
            TenantPermission.FieldNo("Modify Permission"):
                TenantPermission."Modify Permission" := OldValue;
            TenantPermission.FieldNo("Delete Permission"):
                TenantPermission."Delete Permission" := OldValue;
            TenantPermission.FieldNo("Execute Permission"):
                TenantPermission."Execute Permission" := OldValue;
        end;
        TenantPermission.Modify();
    end;

    procedure DisableNotification(Notification: Notification)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.Disable(GetPermissionChangeNotificationId());
    end;

    procedure ConvertToPermission(PermissionOption: Option) Permission: enum Permission
    var
        PermissionBuffer: Record "Permission Buffer";
    begin
        case PermissionOption of
            PermissionBuffer."Read Permission"::" ":
                exit(Permission::None);
            PermissionBuffer."Read Permission"::Yes:
                exit(Permission::Direct);
            PermissionBuffer."Read Permission"::Indirect:
                exit(Permission::Indirect);
        end;
    end;

    internal procedure ShowPermissionConflict(Permissions: Enum Permission; EntitlementPermissions: Enum Permission; IsSourceEntitlement: Boolean)
    var
        ConflictMsg: Text;
    begin
        if IsSourceEntitlement then
            exit;

        if not ArePermissionsInConflict(Permissions, EntitlementPermissions) then
            exit;

        if (Permissions = Permissions::Direct) and (EntitlementPermissions = EntitlementPermissions::Indirect) then
            ConflictMsg := DirectConflictWithIndirectMsg;

        if (Permissions = Permissions::Direct) and (EntitlementPermissions = EntitlementPermissions::None) then
            ConflictMsg := DirectConflictWithNoneMsg;

        if (Permissions = Permissions::Indirect) and (EntitlementPermissions = EntitlementPermissions::None) then
            ConflictMsg := IndirectConflictWithNoneMsg;

        Message(ConflictMsg);
    end;

    internal procedure GetPermissionStatus(Permissions: Enum Permission; EntitlementPermissions: Enum Permission; IsSourceEntitlement: Boolean): Text
    begin
        if IsSourceEntitlement then
            exit(Format(Permissions));

        if not ArePermissionsInConflict(Permissions, EntitlementPermissions) then
            exit(Format(Permissions));

        // Show the effective permission (the one that comes from the entitlement) and an indication that the permission has been reduced
        exit(StrSubstNo(ConflictLbl, Format(EntitlementPermissions)));
    end;

    local procedure ArePermissionsInConflict(PermissionsFromPermissionSet: Enum Permission; PermissionsFromEntitlement: Enum Permission): Boolean
    begin
        exit(PermissionsFromPermissionSet.AsInteger() > PermissionsFromEntitlement.AsInteger());
    end;

    internal procedure OpenPermissionConflicts(PermissionSetID: Code[20]; PlanOrRole: Enum Licenses)
    var
        PermissionConflictsPage: Page "Permission Conflicts";
    begin
        PermissionConflictsPage.SetPermissionSetId(PermissionSetID);
        PermissionConflictsPage.SetEntitlementId(PlanOrRole);
        PermissionConflictsPage.Run();
    end;

    local procedure MarkUsersWithAssignedPermissionSet(var User: Record User; PermissionSetID: Code[20])
    var
        AccessControl: Record "Access Control";
        SecurityGroupBuffer: Record "Security Group Buffer";
        SecurityGroupMemberBuffer: Record "Security Group Member Buffer";
        SecurityGroup: Codeunit "Security Group";
        GroupUserSecId: Guid;
    begin
        AccessControl.SetRange("Role ID", PermissionSetID);
        if AccessControl.FindSet() then
            repeat
                if User.Get(AccessControl."User Security ID") then
                    User.Mark(true);
            until AccessControl.Next() = 0;

        SecurityGroup.GetGroups(SecurityGroupBuffer);
        SecurityGroup.GetMembers(SecurityGroupMemberBuffer);
        if SecurityGroupBuffer.FindSet() then
            repeat
                GroupUserSecId := SecurityGroup.GetGroupUserSecurityId(SecurityGroupBuffer.Code);
                AccessControl.SetRange("User Security ID", GroupUserSecId);
                AccessControl.SetRange("Role ID", PermissionSetID);
                // If the permission set is assigned to the security group
                if not AccessControl.IsEmpty() then begin
                    // Mark all the security group members
                    SecurityGroupMemberBuffer.SetRange("Security Group Code", SecurityGroupBuffer.Code);
                    if SecurityGroupMemberBuffer.FindSet() then
                        repeat
                            if User.Get(SecurityGroupMemberBuffer."User Security ID") then
                                User.Mark(true);
                        until SecurityGroupMemberBuffer.Next() = 0;
                end;
            until SecurityGroupBuffer.Next() = 0;

        User.MarkedOnly(true);
    end;

    local procedure GetPermissionChangeNotificationId(): Guid
    begin
        exit('7E18A509-6579-471A-BF8D-4A9BDABB6008');
    end;

    /// <summary>
    /// Checks if the user has direct read, insert and modify permissions on the given table id.
    /// </summary>
    /// <param name="TableId">Id of the table</param>
    /// <returns>True if user has direct read, insert and modify permissions on table.</returns>
    internal procedure HasDirectRIMPermissionsOnTableData(TableId: Integer): Boolean
    var
        MetadataPermission: Record "Metadata Permission";
        EntitlementPermissionsCommaStr: Text;
        Read: Integer;
        Insert: Integer;
        Modify: Integer;
        Delete: Integer;
        Execute: Integer;
    begin
        EntitlementPermissionsCommaStr := UserAccountHelper.GetEntitlementPermissionForObject(UserSecurityId(), MetadataPermission."Object Type"::"Table Data", TableId);
        ExtractPermissionsFromText(EntitlementPermissionsCommaStr, Read, Insert, Modify, Delete, Execute);

        if ((Read = MetadataPermission."Read Permission"::Yes) and
            (Insert = MetadataPermission."Insert Permission"::Yes) and
            (Modify = MetadataPermission."Modify Permission"::Yes)) then
            exit(true);

        exit(false);
    end;

    [EventSubscriber(ObjectType::Page, Page::"My Notifications", 'OnInitializingNotificationWithDefaultState', '', false, false)]
    local procedure OnInitializingNotificationWithDefaultState()
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(GetPermissionChangeNotificationId(), ChangeAffectsOthersNameTxt, ChangeAffectsOthersDescTxt, true);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTenantPermissionModified(PermissionSetId: Code[20])
    begin
    end;
}