Hi, Readers.
I was asked a question recently, whether developers could get Security Groups and Security Group Members via AL. In this post, I’d like to talk briefly about this.
Security groups make it easier for administrators to manage user permissions. For example, for Business Central online, they’re reusable across Dynamics 365 applications, such as SharePoint Online, CRM Online, and Business Central. Administrators add permissions to their Business Central security groups, and when they add users to the group the permissions apply to all members. More details: Business Central 2023 wave 1 (BC22): Manage user permissions using Azure Active Directory security groups (‘User Group’ -> ‘Security Group’) and Control Access to Business Central Using Security Groups
Azure Active Directory is now Microsoft Entra ID. Learn more
First of all, why is this a problem? As you might know, table ‘User Group’ is marked for removal (Replaced by the Security Group) from Business Central 2023 wave 1 (BC22).
Table ‘User Group’ is marked for removal. Reason: [220_UserGroups] Replaced by the Security Group table and Security Group codeunit in the security groups system; by Tenant Permission Set table in the permission sets system. To learn more, go to https://go.microsoft.com/fwlink/?linkid=2245709.. Tag: 22.0.
Then, the source table used on the page are all temporary tables.
For example,
Security Groups (9871, List): Security Group Buffer (9022)
Security Group Lookup (9877, List) – Available Security Groups: Security Group Buffer (9022) Table
Security Group Members (9869, List): Security Group Member Buffer (9021)
Finally there is a table, table 9020 “Security Group”.
The Access property is set to Internal. (The object can be accessed only by code in the same module, but not from another module)
PS: Can we access the standard internal table/field (Access Property = Internal) via AL???
Is there no way out? Please don’t give up. This is actually not very difficult, it just requires another method. Let’s see more details.
All Security Groups:
Already added to BC:
Not yet added to BC:
1. Security Groups (Already added to BC)
The key method is Codeunit “Security Group”.GetGroups(var SecurityGroupBuffer: Record “Security Group Buffer”).
In page 9871 “Security Groups”:
In codeunit 9031 “Security Group”:
A simple example:
2. Available Security Groups (not yet added to BC)
This should be the most difficult in this discussion, because it is collected when the page is opened, and its logic cannot be copied.
In page 9877 “Security Group Lookup”:
And codeunit 9871 “Security Group Impl.” (Access = Internal): procedure GetAvailableGroups(var SecurityGroupBuffer: Record “Security Group Buffer”)
So if you need to collect Available Security Groups (not yet added to BC), you can only use the standard page.
A simple example:
3. Security Group Members
Similar to the first situation, the key method is Codeunit “Security Group”.procedure GetMembers(var SecurityGroupMemberBuffer: Record “Security Group Member Buffer”).
In page 9869 “Security Group Members”:
In codeunit 9031 “Security Group”:
A simple example:
Note that the data retrieved by default includes members of all groups.
Great. Maybe I didn’t give a very good example, but I hope it can give you some tips. Give it a try!!!😁
My test code:
pageextension 50100 CustomerListExt extends "Customer List"
{
actions
{
addafter("Sent Emails")
{
action(GetSecurityGroups)
{
Caption = 'Get Security Groups';
Image = Open;
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
trigger OnAction()
begin
GetSecurityGroups();
end;
}
action(GetAvailableGroups)
{
Caption = 'Get Available Security Groups';
Image = Open;
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
trigger OnAction()
begin
GetAvailableSecurityGroups();
end;
}
action(GetSecurityGroupMembers)
{
Caption = 'Get Security Group Members';
Image = Open;
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
trigger OnAction()
begin
GetSecurityMembers();
end;
}
}
}
local procedure GetSecurityGroups()
var
SecurityGroupBuffer: Record "Security Group Buffer";
SecurityGroup: Codeunit "Security Group";
Msg: Label 'There are %1 Security Groups, and the code of the first one is %2.';
begin
SecurityGroup.GetGroups(SecurityGroupBuffer);
if SecurityGroupBuffer.FindFirst() then
Message(Msg, SecurityGroupBuffer.Count, SecurityGroupBuffer.Code);
end;
local procedure GetSecurityMembers()
var
SecurityGroupMemberBuffer: Record "Security Group Member Buffer";
SecurityGroup: Codeunit "Security Group";
Msg: Label 'There are %1 Security Group Members, and the user name of the first one is %2.';
begin
SecurityGroup.GetMembers(SecurityGroupMemberBuffer);
SecurityGroupMemberBuffer.SetAutoCalcFields("User Name");
if SecurityGroupMemberBuffer.FindFirst() then
Message(Msg, SecurityGroupMemberBuffer.Count, SecurityGroupMemberBuffer."User Name");
end;
local procedure GetAvailableSecurityGroups()
var
SelectedSecurityGroup: Record "Security Group Buffer";
begin
if Page.RunModal(Page::"Security Group Lookup", SelectedSecurityGroup) = Action::LookupOK then
Message(SelectedSecurityGroup."Group Name");
end;
}
PS: If your code is also used in On-Premise version, please note that it may be Windows authentication, and the Group Code needs to be obtained from the Domain name. Here is a simple example that references the standard code.
pageextension 50100 CustomerListExt extends "Customer List"
{
actions
{
addafter("Sent Emails")
{
action(GetAvailableGroups)
{
Caption = 'Get Available Security Groups';
Image = Open;
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
trigger OnAction()
begin
LookupAvailableGroups();
end;
}
}
}
local procedure LookupAvailableGroups()
var
SecurityGroupLookup: Page "Security Group Lookup";
SelectedSecurityGroup: Record "Security Group Buffer";
NewSecurityGroupCodeValue: Code[20];
NewSecurityGroupNameValue: Text;
NewSecurityGroupIdValue: Text;
begin
SecurityGroupLookup.LookupMode(true);
if SecurityGroupLookup.RunModal() = Action::LookupOK then begin
SecurityGroupLookup.GetRecord(SelectedSecurityGroup);
NewSecurityGroupNameValue := SelectedSecurityGroup."Group Name";
NewSecurityGroupIdValue := SelectedSecurityGroup."Group ID";
NewSecurityGroupCodeValue := GetDesirableCode(NewSecurityGroupNameValue);
end;
Message(Format(NewSecurityGroupNameValue) + Format(NewSecurityGroupIdValue) + Format(NewSecurityGroupCodeValue));
end;
procedure GetDesirableCode(GroupName: Text): Code[20]
var
GroupDomainAndNameList: List of [Text];
SecurityGroup: Codeunit "Security Group";
begin
if SecurityGroup.IsWindowsAuthentication() then begin
GroupDomainAndNameList := GroupName.Split('\');
exit(CopyStr(GroupDomainAndNameList.Get(GroupDomainAndNameList.Count()), 1, 20));
end else
exit(CopyStr(GroupName, 1, 20));
end;
}
END
Hope this will help.
Thanks for reading.
ZHU
コメント