Migrate from User Groups to Permission Sets or Security Groups
APPLIES TO: Business Central 2023 release wave 1 and later
Note
Azure Active Directory is now Microsoft Entra ID. Learn more
The recent improvements to permission set features and the introduction of Business Central security groups made user groups redundant. Therefore, to streamline permission management, we've deprecated user groups.
This article provides developers with examples of how they can update their extensions to accommodate the change.
To learn more about permission sets, go to Assign Permissions to Users and Groups.
Optional: Security groups
You have the option of assigning permission sets to security groups. In terms of managing permissions, security groups work in the same way as user groups.
However, user groups were only relevant for Business Central. Security groups are based on the security groups in Microsoft Entra ID or Windows Active Directory, depending on whether you're using Business Central online or on-premises, respectively. That means administrators can use them with other Dynamics 365 apps. For example, if salespeople use Business Central and SharePoint, administrators don't have to recreate the group and its members. To learn more, go to Control Access to Business Central Using Security Groups.
Example scenarios
Depending on how your extensions leverage user groups, the actions you need to take might differ. In some cases, you'll only need to change your code to use permission sets. In other cases, you'll need to use security groups. You might even want to do both.
The following examples show how to update your extension to address various scenarios. The examples include both how you could handle the scenarios before, and how to handle them now.
Example: Your extension creates user groups
The following example shows how to create permission sets instead of user groups.
Before
local procedure InsertUserGroup(UserGroupCode: Code[20]; UserGroupName: Text[50])
var
UserGroup: Record "User Group";
begin
if UserGroup.Get(UserGroupCode) then
exit;
UserGroup.Code := UserGroupCode;
UserGroup.Name := UserGroupName;
UserGroup.Insert();
// More logic, for example, adding permission sets to the user group by inserting records into the "User Group Permission Set" table.
end;
After
The preferred way is to create a new object of the type permissionset
.
permissionset 9999 "Permission set code"
{
Assignable = true;
Caption 'Permission set name';
IncludedPermissionSets = ...; // This is similar to adding permission sets to the user group by inserting records into the "User Group Permission Set" table.
Permissions = ...;
}
Alternatively, you can create a tenant permission set dynamically.
local procedure CreateNewTenantPermissionSet(PermissionSetRoleID: Code[20]; PermissionSetName: Text[30])
var
TenantPermissionSet: Record "Tenant Permission Set";
NullGuid: Guid;
begin
TenantPermissionSet.SetRange("App ID", NullGuid);
TenantPermissionSet.SetRange("Role ID", PermissionSetRoleID);
if not TenantPermissionSet.IsEmpty() then
exit;
TenantPermissionSet."App ID" := NullGuid;
TenantPermissionSet."Role ID" := PermissionSetRoleID;
TenantPermissionSet.Name := PermissionSetName;
TenantPermissionSet.Insert();
// More logic, e. g. adding permission sets to the parent permission set by calling CreateTenantPermissionSetRelation
end;
local procedure CreateTenantPermissionSetRelation(RoleId: Code[20]; RelatedRoleID: Code[20]; RelatedAppId: Guid; RelatedScope: Option System,Tenant; PermissionType: Option Include,Exclude)
var
TenantPermissionSetRel: Record "Tenant Permission Set Rel.";
NullGuid: Guid;
begin
TenantPermissionSetRel.Init();
TenantPermissionSetRel."Role ID" := CopyStr(RoleId, 1, MaxStrLen(TenantPermissionSetRel."Role ID"));
TenantPermissionSetRel."App ID" := NullGuid;
TenantPermissionSetRel."Related Role ID" := RelatedRoleID;
TenantPermissionSetRel."Related App ID" := RelatedAppId;
TenantPermissionSetRel."Related Scope" := RelatedScope;
TenantPermissionSetRel.Type := PermissionType;
TenantPermissionSetRel.Insert();
end;
Example: You add members to user groups through code
The following example shows how to rewrite your code to assign permission sets to users instead of adding members to user groups.
Before
local procedure AssignUserGroupToUser(InputUserGroupCode: Code[20]; InputUserSecurityID: Guid)
var
UserGroupMember: Record "User Group Member";
begin
if UserGroupIsAssignedToUser(InputUserGroupCode, InputUserSecurityID) then
exit;
UserGroupMember."User Group Code" := InputUserGroupCode;
UserGroupMember."User Security ID" := InputUserSecurityID;
UserGroupMember.Insert(true);
end;
local procedure IsUserGroupAssignedToUser(InputUserGroupCode: Code[20]; InputUserSecurityID: Guid): Boolean
var
UserGroupMember: Record "User Group Member";
begin
UserGroupMember.SetRange("User Group Code", InputUserGroupCode);
UserGroupMember.SetRange("User Security ID", InputUserSecurityID);
exit(not UserGroupMember.IsEmpty());
end;
After
local procedure AssignPermissionSetToUser(PermissionSetRoleId: Code[20]; InputUserSecurityID: Guid)
var
AccessControl: Record "Access Control";
AggregatePermissionSet: Record "Aggregate Permission Set";
begin
if IsPermissionSetAssignedToUser(PermissionSetRoleId, InputUserSecurityID) then
exit;
AggregatePermissionSet.SetRange("Role ID", PermissionSetRoleId);
if not AggregatePermissionSet.FindFirst() then
exit;
AccessControl."Role Id" := PermissionSetRoleId;
AccessControl."User Security ID" := InputUserSecurityID;
AccessControl.Scope := AccessControl.Scope::System;
AccessControl."App ID" := AggregatePermissionSet."App ID";
if AccessControl.Insert() then;
end;
local procedure IsPermissionSetAssignedToUser(PermissionSetRoleId: Code[20]; InputUserSecurityID: Guid): Boolean
var
AccessControl: Record "Access Control";
begin
AccessControl.SetRange("Role Id", PermissionSetRoleId);
AccessControl.SetRange("User Security ID", InputUserSecurityID);
exit(not AccessControl.IsEmpty());
end;
Example: Your extension provides capabilities to groups of users
The following example shows how to rewrite your code to use the grouping mechanism in security groups to group users in the same way as you did using user groups.
Before
local procedure DoStuffIfUserGroupMember(InputUserGroupCode: Code[20]; InputUserSecurityID: Guid)
var
UserGroupMember: Record "User Group Member";
begin
UserGroupMember.SetRange("User Group Code", InputUserGroupCode);
UserGroupMember.SetRange("User Security ID", InputUserSecurityID);
if not UserGroupMember.IsEmpty() then
...
// Apply extension-specific capabilities to a member.
end;
After
local procedure DoStuffIfSecurityGroupMember(InputSecurityGroupCode: Code[20]; InputUserSecurityID: Guid)
var
SecurityGroupMemberBuffer: Record "Security Group Member Buffer";
SecurityGroup: Codeunit "Security Group";
begin
SecurityGroup.GetMembers(SecurityGroupMemberBuffer);
SecurityGroupMemberBuffer.SetRange("Security Group Code", InputSecurityGroupCode);
SecurityGroupMemberBuffer.SetRange("User Security ID", InputUserSecurityID);
if not SecurityGroupMemberBuffer.IsEmpty() then
...
// Apply extension-specific capabilities to a member.
end;
Example: Your extension relies on default profiles from user groups
The default profile functionality can no longer be dynamically defined for groups of users. Instead, use the Profiles (Roles) page to specify a default profile for your company. To learn more about default profiles, go to Manage User Profiles.
Example: You add actions, fields, or controls to user group pages
If you extend the user group functionality by adding actions, fields, or controls, you can add them to pages related to permission sets and security groups. The following example shows how to do that.
Before
pageextension 9999 "New Group Functionality" extends "User Group Members"
{
actions
{
addlast(creation)
{
action(ApplyFunctionalityToMembers)
{
...
}
}
}
}
After
pageextension 9999 "New Group Functionality" extends "Security Group Members"
{
actions
{
addlast(Processing)
{
action(ApplyFunctionalityToMembers)
{
...
}
}
}
}
...
pageextension 9998 "New Group Functionality" extends "Perm. Set Assignments Part"
{
actions
{
addlast(Processing)
{
action(ApplyFunctionalityToUsersWithPermissionSet)
{
...
}
}
}
}
Deprecated objects
The following objects are deprecated. To make these objects easy to find in the code, we've added a [220_UserGroups]
tag to the obsolete reason.
Name | Object type |
---|---|
User Group | table |
User Group Member | table |
User Group Access Control | table |
User Group Permission Set | table |
User Group Plan | table |
Users in User Groups Chart | page |
Default User Groups In Plan | page |
Custom User Groups In Plan | page |
User Group Plan FactBox | page |
User Groups FactBox | page |
User Groups | page |
User Group Members | page |
User Group Members FactBox | page |
User Groups User SubPage | page |
User Group Permission Sets | page |
User Group Permissions FactBox | page |
User Group Memberships FactBox | page |
Permission Set by User Group | page |
User by User Group | page |
User Group by Plan | page |
Users in User Groups | query |
Copy User Group | report |
Export/Import User Groups | xmlport |
Export/Import Plans | xmlport |
Objects that can help you migrate
The following table lists the objects we've introduced to make it easier for you to migrate your extension to use permission sets instead of user groups.
Name | Object type | Comment |
---|---|---|
Legacy User Groups | codeunit | Check whether user groups are enabled in the environment. |
User Groups Migration Guide | page | A guide that can help you convert user groups to permission sets. To learn more, go to Optional: Convert user groups to permission sets. |
See also
Deprecated Features in the Base App
Best Practices for Deprecation of AL Code
Microsoft Timeline for Deprecating Code in Business Central