Codeunit 9002 Permission Manager
- App
- Base Application
- Namespace
- System.Security.AccessControl
- Versions
- 17-28
Procedures, 22Events, 1Obsolete, 10
Versions171819202122232425262728
Source29
Source in 29
src/Layers/W1/BaseApp/Modules/System/PermissionSets/PermissionManager.Codeunit.al366 lines, Copyright (c) Microsoft Corporation. MIT
namespace System.Security.AccessControl;
using System.Azure.Identity;
using System.Environment;
using System.Environment.Configuration;
using System.Integration;
using System.Reflection;
using System.Security.Encryption;
using System.Security.User;
using System.Telemetry;
codeunit 9002 "Permission Manager"
{
InherentEntitlements = X;
InherentPermissions = X;
Permissions = TableData "Permission Set Link" = rd,
TableData "Aggregate Permission Set" = rimd;
SingleInstance = true;
var
EnvironmentInfo: Codeunit "Environment Information";
OfficePortalUserAdministrationUrlTxt: Label 'https://portal.office.com/admin/default.aspx#ActiveUsersPage', Locked = true;
IntelligentCloudTok: Label 'INTELLIGENT CLOUD', Locked = true;
LocalTok: Label 'LOCAL', Locked = true;
TestabilityIntelligentCloud: Boolean;
CannotModifyOtherUsersErr: Label 'You cannot change settings for another user.';
PlanConfigurationFeatureNameTxt: Label 'Custom Permissions Assignment Per Plan', Locked = true;
procedure AssignDefaultPermissionsToUser(UserSecurityID: Guid): Boolean
var
Company: Text[30];
begin
Company := CopyStr(CompanyName(), 1, 30);
exit(AssignDefaultPermissionsToUser(UserSecurityID, Company));
end;
procedure AssignDefaultPermissionsToUser(UserSecurityID: Guid; Company: Text[30]) PermissionsAdded: Boolean
var
PlanConfiguration: Codeunit "Plan Configuration";
FeatureTelemetry: Codeunit "Feature Telemetry";
UsersInPlans: Query "Users in Plans";
begin
// If intelligent cloud is enabled, then assign the intelligent cloud permissions
if IsIntelligentCloud() then begin
AddPermissionSetToUser(UserSecurityID, IntelligentCloudTok, Company);
PermissionsAdded := true;
exit;
end;
// No plan is assigned to this user
UsersInPlans.SetRange(User_Security_ID, UserSecurityID);
if not UsersInPlans.Open() then begin
PermissionsAdded := false;
exit;
end;
// There is at least a plan assigned (and probably only one)
while UsersInPlans.Read() do
if PlanConfiguration.IsCustomized(UsersInPlans.Plan_ID) then begin
FeatureTelemetry.LogUptake('0000HSP', PlanConfigurationFeatureNameTxt, Enum::"Feature Uptake Status"::Used);
PlanConfiguration.AssignCustomPermissionsToUser(UsersInPlans.Plan_ID, UserSecurityID);
PermissionsAdded := true;
end else begin
PlanConfiguration.AssignDefaultPermissionsToUser(UsersInPlans.Plan_ID, UserSecurityID, Company);
PermissionsAdded := true;
end;
end;
procedure ResetUserToDefaultPermissions(UserSecurityID: Guid)
begin
// Remove the user from any additional, manually assigned permission sets
RemoveAllPermissionSetsFromUser(UserSecurityID);
// Assign all permission sets associated with user's plans
AssignDefaultPermissionsToUser(UserSecurityID);
end;
local procedure RemoveAllPermissionSetsFromUser(UserSecurityID: Guid)
var
AccessControl: Record "Access Control";
begin
AccessControl.SetRange("User Security ID", UserSecurityID);
AccessControl.DeleteAll(true);
end;
procedure GetOfficePortalUserAdminUrl(): Text
begin
exit(OfficePortalUserAdministrationUrlTxt);
end;
procedure UpdateUserAccessForSaaS(UserSID: Guid) UserGroupsAdded: Boolean
begin
if not AllowUpdateUserAccessForSaaS(UserSID) then
exit;
if AssignDefaultPermissionsToUser(UserSID) then begin
AssignDefaultRoleCenterToUser(UserSID);
UserGroupsAdded := true;
end;
end;
local procedure AllowUpdateUserAccessForSaaS(UserSID: Guid): Boolean
var
User: Record User;
Plan: Query Plan;
UsersInPlans: Query "Users in Plans";
begin
if not EnvironmentInfo.IsSaaS() then
exit(false);
if IsNullGuid(UserSID) then
exit(false);
// Don't demote external users (like the sync daemon) and AAD groups
User.Get(UserSID);
if User."License Type" in [User."License Type"::"External User", User."License Type"::"AAD Group"] then
exit(false);
// Don't demote users which don't come from Office365 (have no plans assigned)
// Note: all users who come from O365, if they don't have a plan, they don't get a license (hence, no SUPER role)
UsersInPlans.SetFilter(User_Security_ID, User."User Security ID");
if not UsersInPlans.Open() then
exit(false);
// Don't demote users that have an invalid plan(likely coming from 1.5)
while UsersInPlans.Read() do begin
Plan.SetFilter(Plan_ID, UsersInPlans.Plan_ID);
if not Plan.Open() then
exit(false);
Plan.Read();
if Plan.Role_Center_ID = 0 then
exit(false);
Plan.Close();
end;
exit(true);
end;
local procedure AssignDefaultRoleCenterToUser(UserSecurityID: Guid)
var
UserPersonalization: Record "User Personalization";
AllProfile: Record "All Profile";
UsersInPlans: Query "Users in Plans";
Plan: Query Plan;
begin
UsersInPlans.SetRange(User_Security_ID, UserSecurityID);
if not UsersInPlans.Open() then
exit; // this user has no plans assigned, so they'll get the app-wide default role center
UsersInPlans.Read();
Plan.SetRange(Plan_ID, UsersInPlans.Plan_ID);
Plan.Open();
Plan.Read();
if Plan.Role_Center_ID = 9022 then // 9022 = Page::"Business Manager Role Center"
FilterProfileToBusinessManager(AllProfile)
else
AllProfile.SetRange("Role Center ID", Plan.Role_Center_ID);
if not AllProfile.FindFirst() then
exit; // the plan does not have a role center, so they'll get the app-wide default role center
// Only assign a role center to users that don't have one yet. Never overwrite an existing
// user's Profile ID, otherwise a plan re-sync (e.g. Retrieve Users) would silently reset the
// user's customized Role Center back to the plan default.
if UserPersonalization.Get(UserSecurityID) then
exit;
UserPersonalization.Init();
UserPersonalization.Validate("User SID", UserSecurityID);
UserPersonalization.Validate("Profile ID", AllProfile."Profile ID");
UserPersonalization.Validate("App ID", AllProfile."App ID");
UserPersonalization.Validate(Scope, AllProfile.Scope);
UserPersonalization.Insert();
end;
procedure CanCurrentUserManagePlansAndGroups(): Boolean
var
[SecurityFiltering(SecurityFilter::Ignored)]
AccessControl: Record "Access Control";
begin
exit(AccessControl.WritePermission());
end;
procedure GenerateHashForPermissionSet(PermissionSetId: Code[30]): Text[250]
var
MetadataPermission: Record "Metadata Permission";
CryptographyManagement: Codeunit "Cryptography Management";
InputText: Text;
ObjectType: Integer;
begin
InputText += PermissionSetId;
MetadataPermission.SetRange("Role ID", PermissionSetId);
if MetadataPermission.FindSet() then
repeat
ObjectType := MetadataPermission."Object Type";
InputText += Format(ObjectType);
InputText += Format(MetadataPermission."Object ID");
if ObjectType = MetadataPermission."Object Type"::"Table Data" then begin
InputText += GetCharRepresentationOfPermission(MetadataPermission."Read Permission");
InputText += GetCharRepresentationOfPermission(MetadataPermission."Insert Permission");
InputText += GetCharRepresentationOfPermission(MetadataPermission."Modify Permission");
InputText += GetCharRepresentationOfPermission(MetadataPermission."Delete Permission");
end else
InputText += GetCharRepresentationOfPermission(MetadataPermission."Execute Permission");
InputText += Format(MetadataPermission."Security Filter", 0, 9);
until MetadataPermission.Next() = 0;
exit(CopyStr(CryptographyManagement.GenerateHash(InputText, 2), 1, 250)); // 2 corresponds to SHA256
end;
local procedure FilterProfileToBusinessManager(var AllProfile: Record "All Profile")
var
Company: Record Company;
begin
if not Company.Get(CompanyName()) then
exit;
if Company."Evaluation Company" and Company.Name.ToLower().StartsWith('cronus') then
AllProfile.SetRange("Profile ID", 'Business Manager Evaluation')
else
if not Company."Evaluation Company" then
AllProfile.SetRange("Profile ID", 'Business Manager');
end;
local procedure GetCharRepresentationOfPermission(PermissionOption: Integer): Text[1]
begin
exit(StrSubstNo('%1', PermissionOption));
end;
procedure IsFirstPermissionHigherThanSecond(First: Option; Second: Option): Boolean
var
MetadataPermission: Record "Metadata Permission";
begin
case First of
MetadataPermission."Read Permission"::" ":
exit(false);
MetadataPermission."Read Permission"::Indirect:
exit(Second = MetadataPermission."Read Permission"::" ");
MetadataPermission."Read Permission"::Yes:
exit(Second in [MetadataPermission."Read Permission"::Indirect, MetadataPermission."Read Permission"::" "]);
end;
end;
procedure ResetUsersToIntelligentCloudPermissions()
var
User: Record User;
AccessControl: Record "Access Control";
IntelligentCloud: Record "Intelligent Cloud";
UserPermissions: Codeunit "User Permissions";
UserSelection: Codeunit "User Selection";
begin
if not EnvironmentInfo.IsSaaS() then
exit;
if not IntelligentCloud.Get() then
exit;
if IntelligentCloud.Enabled then begin
UserSelection.FilterSystemUserAndAADGroupUsers(User);
User.SetFilter("Windows Security ID", '=''''');
if User.Count = 0 then
exit;
User.FindSet();
repeat
if not UserPermissions.IsSuper(User."User Security ID") and not IsNullGuid(User."User Security ID") then begin
AccessControl.SetRange("User Security ID", User."User Security ID");
if AccessControl.FindSet() then
repeat
RemoveExistingPermissionsAndAddIntelligentCloud(AccessControl."User Security ID", AccessControl."Company Name");
until AccessControl.Next() = 0;
end;
until User.Next() = 0;
end;
end;
procedure IsIntelligentCloud(): Boolean
var
IntelligentCloud: Record "Intelligent Cloud";
begin
if TestabilityIntelligentCloud then
exit(true);
if IntelligentCloud.Get() then
exit(IntelligentCloud.Enabled);
end;
local procedure RemoveExistingPermissionsAndAddIntelligentCloud(UserSecurityID: Guid; CompanyName: Text[30])
var
AccessControl: Record "Access Control";
begin
// Remove User from all Permission Sets for the company
AccessControl.SetRange("User Security ID", UserSecurityID);
AccessControl.SetRange("Company Name", CompanyName);
AccessControl.SetRange(Scope, AccessControl.Scope::System);
AccessControl.SetFilter("Role ID", '<>%1', IntelligentCloudTok);
AccessControl.SetFilter("Role ID", '<>%1', LocalTok);
AccessControl.DeleteAll(true);
AddPermissionSetToUser(UserSecurityID, IntelligentCloudTok, CompanyName);
end;
procedure SetTestabilityIntelligentCloud(EnableIntelligentCloudForTest: Boolean)
begin
TestabilityIntelligentCloud := EnableIntelligentCloudForTest;
end;
local procedure AddPermissionSetToUser(UserSecurityID: Guid; RoleID: Code[20]; Company: Text[30])
var
AccessControl: Record "Access Control";
begin
AccessControl.SetRange("User Security ID", UserSecurityID);
AccessControl.SetRange("Role ID", RoleID);
AccessControl.SetRange("Company Name", Company);
if not AccessControl.IsEmpty() then
exit;
AccessControl.Init();
AccessControl."Company Name" := Company;
AccessControl."User Security ID" := UserSecurityID;
AccessControl."Role ID" := RoleID;
AccessControl.Insert(true);
end;
[EventSubscriber(ObjectType::Table, Database::"Tenant Permission Set", OnBeforeDeleteEvent, '', false, false)]
local procedure OnBeforeDeleteTenantPermissionSet(var Rec: Record "Tenant Permission Set")
var
PermissionSetLink: Record "Permission Set Link";
begin
if Rec.IsTemporary() then
exit;
PermissionSetLink.SetRange("Linked Permission Set ID", Rec."Role ID");
PermissionSetLink.DeleteAll();
end;
[EventSubscriber(ObjectType::Table, Database::User, 'OnBeforeModifyEvent', '', true, true)]
procedure CheckCurrentUserCanModifyUser(var Rec: Record User; var xRec: Record user; RunTrigger: Boolean)
var
LoggedInUser: Record User;
UserPermissions: Codeunit "User Permissions";
CurrentUserSecurityId: Guid;
begin
if Rec.IsTemporary() then
exit;
if Rec."License Type" = Rec."License Type"::Agent then
exit;
Rec.TestField("User Name");
CurrentUserSecurityId := UserSecurityId();
if not LoggedInUser.Get(CurrentUserSecurityId) then // Current user is Super from when there were no users in the system
exit;
if LoggedInUser."User Security ID" = Rec."User Security ID" then
exit;
if not UserPermissions.CanManageUsersOnTenant(CurrentUserSecurityId) then
Error(CannotModifyOtherUsersErr);
end;
}