Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ codeunit 9018 "Azure AD Plan Impl."
begin
case true of
AzureADGraphUser.IsUserDelegatedAdmin():
PlanId := PlanIds.GetDelegatedAdminPlanId();
PlanId := PlanIds.GetGlobalAdminPlanId();
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegated admins are being assigned PlanIds.GetGlobalAdminPlanId() here. GetGlobalAdminPlanId() is the tenant Global Administrator role GUID, not the delegated admin plan GUID (DelegatedAdminGUIDTxt). This will assign the wrong plan to delegated admins and can grant/track the wrong entitlements. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated-admin path.

Suggested change
PlanId := PlanIds.GetGlobalAdminPlanId();
PlanId := PlanIds.GetDelegatedGlobalAdminPlanId();

Copilot uses AI. Check for mistakes.
AzureADGraphUser.IsUserDelegatedHelpdesk():
PlanId := PlanIds.GetHelpDeskPlanId();
else begin
Expand Down Expand Up @@ -805,7 +805,7 @@ codeunit 9018 "Azure AD Plan Impl."
begin
exit(
IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsUserAdmin now checks GetGlobalAdminPlanId() twice, which makes the second condition redundant and drops the delegated-admin check that previously existed. Replace the duplicate with IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), SecurityID) (or the appropriate delegated plan ID) so delegated admins are still treated as admins when intended.

Suggested change
or IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), SecurityID)

Copilot uses AI. Check for mistakes.
or IsPlanAssignedToUser(PlanIds.GetD365AdminPlanId(), SecurityID));
end;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,28 +105,52 @@ codeunit 9027 "Plan Ids"
exit(ExternalAccountantPlanGUIDTxt);
end;

#if not CLEAN32
/// <summary>
/// Returns the ID for the Delegated Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated Admin agent - Partner plan.</returns>
[Obsolete('Use GetDelegatedGlobalAdminPlanId instead', '28.0')]
procedure GetDelegatedAdminPlanId(): Guid
begin
exit(DelegatedAdminGUIDTxt);
end;
#endif

/// <summary>
/// Returns the ID for the Delegated Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated Admin agent - Partner plan.</returns>
procedure GetDelegatedGlobalAdminPlanId(): Guid
Comment thread
BenPlunk marked this conversation as resolved.
begin
exit(DelegatedAdminGUIDTxt);
end;
Comment thread
WaelAbuSeada marked this conversation as resolved.

#if not CLEAN32
/// <summary>
/// Returns the ID for the Delegated BC Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated BC Admin agent - Partner plan.</returns>
[Obsolete('Use GetD365BCAdminPlanId instead', '28.0')]
procedure GetDelegatedBCAdminPlanId(): Guid
begin
exit(BCAdminPartnerGUIDTxt);
end;
#endif

/// <summary>
/// Returns the ID for the Delegated BC Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated BC Admin agent - Partner plan.</returns>
procedure GetD365BCAdminPlanId(): Guid
Comment thread
BenPlunk marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be named delegated, like the rest of the delegated plans?

Suggested change
procedure GetD365BCAdminPlanId(): Guid
procedure GetDelegatedD365BCAdminPlanId(): Guid

begin
exit(BCAdminPartnerGUIDTxt);
end;
Comment thread
WaelAbuSeada marked this conversation as resolved.

/// <summary>
/// Returns the ID for the Internal BC Administrator plan.
/// Returns the ID for the D365 Business Central Administrator plan.
/// </summary>
/// <returns>The ID for the Internal BC Administrator plan.</returns>
/// <returns>The ID for the D365 Business Central Administrator plan.</returns>
procedure GetBCAdminPlanId(): Guid
begin
exit(BCAdminGUIDTxt);
Expand Down
Comment thread
BenPlunk marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ codeunit 9056 "Plan Installer"
UpgradeTag: Codeunit "Upgrade Tag";
PlanUpgradeTag: Codeunit "Plan Upgrade Tag";
begin
CreatePlan(PlanIds.GetDelegatedBCAdminPlanId(), 'Delegated BC Admin agent - Partner', 9022, 'FFF16A30-3B0B-47CB-9751-54A5C8F634ED');
CreatePlan(PlanIds.GetBCAdminPlanId(), 'Internal BC Administrator', 9022, 'A2BB1194-FC0B-4C6B-840F-963851B783C9');
CreatePlan(PlanIds.GetDelegatedAdminPlanId(), 'Delegated Admin agent - Partner', 9022, '7584DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk agent - Partner', 9022, '8884DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Internal Administrator', 9022, '9B84DDCA-27B8-E911-BB26-000D3A2B005C'); // Global admin
CreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated D365 Business Central Administrator', 9022, 'FFF16A30-3B0B-47CB-9751-54A5C8F634ED');
CreatePlan(PlanIds.GetBCAdminPlanId(), 'D365 Business Central Administrator', 9022, 'A2BB1194-FC0B-4C6B-840F-963851B783C9');
CreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Global Administrator', 9022, '7584DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk Administrator', 9022, '8884DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Global Administrator', 9022, '9B84DDCA-27B8-E911-BB26-000D3A2B005C'); // Global admin
CreatePlan(PlanIds.GetD365AdminPlanId(), 'Dynamics 365 Administrator', 9022, 'F67B9B96-C667-4DD2-B370-FA065A895C9D');
CreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Admin agent - Partner', 9022, '5C28E514-8AFD-4158-B8C0-93A5915938F9');
CreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Administrator', 9022, '5C28E514-8AFD-4158-B8C0-93A5915938F9');
CreatePlan(PlanIds.GetEssentialISVPlanId(), 'Dynamics 365 Business Central Essential - Embedded', 9022, '2E84DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetTeamMemberPlanId(), 'Dynamics 365 Business Central Team Member', 9028, '5784DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetMicrosoft365PlanId(), 'Microsoft 365', 8999, '57ff2da0-773e-42df-b2af-ffb7a2317929');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ codeunit 9057 "Plan Upgrade"
begin
UpdateSubscriptionPlan();
RenamePlansAndDeleteOldPlans();
RenameDelegatedAdminPlans();
RenameTeamMemberPlan();
RenameDevicePlan();
AddPremiumPartnerSandbox();
Expand Down Expand Up @@ -86,7 +87,7 @@ codeunit 9057 "Plan Upgrade"
RenameOrCreatePlan(PlanIds.GetExternalAccountantPlanId(), 'Dynamics 365 Business Central External Accountant');
RenameOrCreatePlan(PlanIds.GetPremiumISVPlanId(), 'Dynamics 365 Business Central Premium - Embedded');
RenameOrCreatePlan(PlanIds.GetViralSignupPlanId(), 'Dynamics 365 Business Central for IWs');
RenameOrCreatePlan(PlanIds.GetDelegatedAdminPlanId(), 'Delegated Admin agent - Partner');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Admin agent - Partner');
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RenamePlansAndDeleteOldPlans renames 'Delegated Admin agent - Partner' using PlanIds.GetGlobalAdminPlanId(). That GUID corresponds to the Global Administrator plan, not the delegated admin plan (DelegatedAdminGUIDTxt). This will rename the wrong plan during upgrade. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated plan rename.

Suggested change
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Admin agent - Partner');
RenameOrCreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Admin agent - Partner');

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, and other renames in the upgrade codeunit does not match those in the installer.

RenameOrCreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk agent - Partner');
RenameOrCreatePlan('996DEF3D-B36C-4153-8607-A6FD3C01B89F', 'D365 Business Central Infrastructure');

Expand All @@ -96,11 +97,33 @@ codeunit 9057 "Plan Upgrade"
DeletePlan('46764787-E039-4AB0-8F00-820FC2D89BF9');
DeletePlan('312BDEEE-8FBD-496E-B529-EB985F305FCF');

Session.LogMessage('0000AHN', 'Subscription Plans were renamed and old plans werer deleted.', Verbosity::Normal, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');
Session.LogMessage('0000AHN', 'Subscription Plans were renamed and old plans were deleted.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');

UpgradeTag.SetUpgradeTag(PlanUpgradeTag.GetRenamePlansUpgradeTag());
end;

[NonDebuggable]
local procedure RenameDelegatedAdminPlans()
Comment thread
BenPlunk marked this conversation as resolved.
var
UpgradeTag: Codeunit "Upgrade Tag";
PlanUpgradeTag: Codeunit "Plan Upgrade Tag";
PlanIds: Codeunit "Plan Ids";
begin
if UpgradeTag.HasUpgradeTag(PlanUpgradeTag.GetRenameDelegatedAdminPlansUpgradeTag()) then
exit;

Comment thread
stkillen marked this conversation as resolved.
RenameOrCreatePlan(PlanIds.GetBCAdminPlanId(), 'D365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Global Administrator');
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated Dynamics 365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Global Administrator');
Comment on lines +117 to +118
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RenameDelegatedAdminPlans renames GetGlobalAdminPlanId() twice (to 'Global Administrator' and then to 'Delegated Global Administrator'). The second rename should target the delegated admin plan GUID (GetDelegatedGlobalAdminPlanId()), otherwise it will overwrite the global admin plan name and never rename the delegated plan. Also note that 'Delegated Dynamics 365 Business Central Administrator' exceeds the Plan.Name length (50) and will be truncated by CopyStr, so the resulting display name will not match the intended role name.

Suggested change
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated Dynamics 365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Global Administrator');
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated D365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Global Administrator');

Copilot uses AI. Check for mistakes.
RenameOrCreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk Administrator');
RenameOrCreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Administrator');

Session.LogMessage('0000AHN', 'Subscription Plans were renamed.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');

UpgradeTag.SetUpgradeTag(PlanUpgradeTag.GetRenameDelegatedAdminPlansUpgradeTag());
end;

[NonDebuggable]
local procedure RenameTeamMemberPlan()
var
Expand Down Expand Up @@ -225,15 +248,15 @@ codeunit 9057 "Plan Upgrade"

// Create internal plan
PlanId := PlanIds.GetBCAdminPlanId();
PlanName := 'Internal BC Administrator';
PlanName := 'D365 Business Central Administrator';
RoleCenterId := 9022;

if not Plan.Get(PlanId) then
CreatePlan(PlanId, PlanName, RoleCenterId);

// Create delegated plan
PlanId := PlanIds.GetDelegatedBCAdminPlanId();
PlanName := 'Delegated BC Admin agent - Partner';
PlanId := PlanIds.GetD365BCAdminPlanId();
PlanName := 'Delegated Dynamics 365 Business Central Admin';
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In AddBCAdmin, the delegated BC admin plan name is set to 'Delegated Dynamics 365 Business Central Admin', while Plan Installer creates 'Delegated D365 Business Central Administrator' and RenameDelegatedAdminPlans tries to rename to yet another variant. Please align on a single <=50-char display name across install + upgrade paths to avoid inconsistent names and accidental truncation.

Suggested change
PlanName := 'Delegated Dynamics 365 Business Central Admin';
PlanName := 'Delegated D365 Business Central Administrator';

Copilot uses AI. Check for mistakes.
RoleCenterId := 9022;

if not Plan.Get(PlanId) then
Expand Down Expand Up @@ -283,7 +306,7 @@ codeunit 9057 "Plan Upgrade"
exit;

PlanId := PlanIds.GetD365AdminPartnerPlanId();
PlanName := 'Delegated Dynamics 365 Admin agent - Partner';
PlanName := 'Delegated Dynamics 365 Administrator';
RoleCenterId := 9022;

if Plan.Get(PlanId) then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ codeunit 9058 "Plan Upgrade Tag"
exit('MS-329421-RenamePlans-20211028');
end;

/// <summary>
/// Returns the rename delegated admin plans upgrade tag.
/// </summary>
/// <returns>The rename delegated admin plans upgrade tag.</returns>
internal procedure GetRenameDelegatedAdminPlansUpgradeTag(): Code[250]
begin
exit('MS-582117-RenameDelegatedAdminPlans-20260128');
end;
Comment thread
WaelAbuSeada marked this conversation as resolved.

/// <summary>
/// Returns the rename team member plan upgrade tag.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ codeunit 776 "Plan User Details"
end;

UserDetails."User Plans" := CopyStr(UserPlansTextBuilder.ToText().TrimEnd(' ; '), 1, MaxStrLen(UserDetails."User Plans"));
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), UserSecId) or
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecId) or
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Is Delegated" is currently derived from GetGlobalAdminPlanId(), which represents the real Global Administrator role, not the delegated admin plan. This will mark global admins as delegated and fail to mark delegated admins if they only have the delegated plan GUID. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated-global-admin plan check.

Suggested change
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecId) or
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), UserSecId) or

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this needs to be the delegated global admin plan.

AzureADPlan.IsPlanAssignedToUser(PlanIds.GetHelpDeskPlanId(), UserSecId) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365AdminPartnerPlanId(), UserSecId) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedBCAdminPlanId(), UserSecId);
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365BCAdminPlanId(), UserSecId);

UserDetails."Has M365 Plan" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetMicrosoft365PlanId(), UserSecId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,10 @@ codeunit 9017 "Azure AD User Mgmt. Impl."
var
PlanIds: Codeunit "Plan Ids";
begin
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), UserSecID) or
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecID) or
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsUserDelegated should check the delegated admin plan ({000...007}), not the tenant Global Administrator role (GetGlobalAdminPlanId(), {62e9...}). Using the global admin plan here will classify true global admins as "delegated" and miss actual delegated admins. Replace GetGlobalAdminPlanId() with GetDelegatedGlobalAdminPlanId() in this predicate.

Suggested change
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecID) or
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), UserSecID) or

Copilot uses AI. Check for mistakes.
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetHelpDeskPlanId(), UserSecID) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365AdminPartnerPlanId(), UserSecID) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedBCAdminPlanId(), UserSecID))
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365BCAdminPlanId(), UserSecID))
end;

[NonDebuggable]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,9 @@ codeunit 9029 "Azure AD User Sync Impl."
end;

// If the user's plans are any of the following:
// - Internal Administrator (Global Administrator or Dynamics 365 Administrator or BC Administrator)
// - Global Administrator (Global Administrator or Dynamics 365 Administrator or BC Administrator)
Comment thread
WaelAbuSeada marked this conversation as resolved.
// - Microsoft 365
// - Internal Administrator + Microsoft 365
// - Global Administrator + Microsoft 365
// and there is no environment security group defined,
// then we don't want to create a BC user during user sync.
local procedure SkipCreatingUserDuringSync(UserPlanIDs: List of [Guid]): Boolean
Expand Down
Loading
Loading