-
Notifications
You must be signed in to change notification settings - Fork 459
[Bug 640454] Scope Expense Agent Entra permissions by company #11290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Prangshuman Das (t-prda)
wants to merge
24
commits into
main
Choose a base branch
from
bugs/640454-expense-agent-entra-company-permissions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
9de0037
[Bug 640454] Scope Expense Agent Entra permissions by company
t-prda c45dda7
Address Expense Agent Entra lifecycle feedback
t-prda eb2c987
Test Expense Agent Entra lifecycle scenarios
t-prda a70355a
Refactor Expense Agent lifecycle tests
t-prda 457683d
Address Expense Agent permission review findings
t-prda ce5d475
Harden Expense Agent Entra permission lifecycle
t-prda bba1520
Fail on missing Expense Agent prerequisites
t-prda deaffd7
Preflight Expense Agent security permissions
t-prda e503922
Preflight Expense Agent Entra authorization
t-prda 728248e
Align Expense Agent preflight with platform security
t-prda 86c9a40
Narrow Expense Agent fix to activation scope
t-prda 756ca7d
Complete Expense Agent company lifecycle
t-prda 2e4ae43
Reorder Expense Agent manager procedures
t-prda 569b361
Simplify Expense Agent lifecycle flow
t-prda d868c82
Simplify company permission handling
t-prda 4e7932b
Polish Expense Agent lifecycle tests
t-prda f0ab337
Preserve Expense Agent API compatibility
t-prda 79d0ef8
Refine Expense Agent deactivation preflight
t-prda cbefdd5
Verify Expense Agent final deactivation
t-prda 79d1377
Align Expense Agent obsolete tag
t-prda 63f161a
Keep Expense Agent compatibility delegate
t-prda 7343b04
Allow SUPER to manage Expense Agent Entra app
t-prda 0ae5941
Run SUPER tests under SUPER permissions
t-prda bb2182e
Make Expense Agent lifecycle test permissions explicit
t-prda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
305 changes: 305 additions & 0 deletions
305
src/Apps/W1/ExpenseAgent/app/src/Setup/Codeunits/ExpenseAgentEntraAppMgt.Codeunit.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,305 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
| namespace Microsoft.ExpenseAgent; | ||
|
|
||
| using System.Agents; | ||
| using System.Environment; | ||
| using System.Environment.Configuration; | ||
| using System.Security.AccessControl; | ||
| using System.Security.User; | ||
|
|
||
| codeunit 6913 "Expense Agent Entra App Mgt." | ||
| { | ||
| Access = Internal; | ||
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
| Permissions = | ||
| tabledata "AAD Application" = rm, | ||
| tabledata "Access Control" = rimd; | ||
|
|
||
| internal procedure EnableAadApplicationForCurrentCompany() | ||
|
t-prda marked this conversation as resolved.
|
||
| begin | ||
| VerifyCurrentUserCanManageExpenseAgent(); | ||
| EnableAadApplicationForCurrentCompanyWithoutAuthorization(); | ||
| end; | ||
|
|
||
| internal procedure DisableAadApplicationForCurrentCompany() | ||
| begin | ||
| VerifyCanDisableAadApplicationForCurrentCompany(); | ||
| DisableAadApplicationForCompany(GetCurrentCompanyName()); | ||
| end; | ||
|
|
||
| internal procedure IsCurrentUserExpenseAgent(): Boolean | ||
| var | ||
| AadApplication: Record "AAD Application"; | ||
| EnvironmentInfo: Codeunit "Environment Information"; | ||
| begin | ||
| if not EnvironmentInfo.IsSaaSInfrastructure() then | ||
| exit(true); | ||
|
|
||
| if not AadApplication.Get(GetAadAppId()) then | ||
| exit(false); | ||
|
|
||
| exit(AadApplication."User ID" = UserSecurityId()); | ||
| end; | ||
|
|
||
| internal procedure GetEnabledExpenseAgentUserId(): Guid | ||
| var | ||
| AadApplication: Record "AAD Application"; | ||
| begin | ||
| AadApplication.Get(GetAadAppId()); | ||
| AadApplication.TestField(State, AadApplication.State::Enabled); | ||
|
|
||
| exit(AadApplication."User ID"); | ||
| end; | ||
|
|
||
| internal procedure GetAadAppId(): Text | ||
| begin | ||
| exit(ExpenseAgentAadAppIdTxt); | ||
| end; | ||
|
|
||
| internal procedure VerifyCanDisableAadApplicationForCurrentCompany() | ||
| begin | ||
| VerifyCurrentUserCanManageExpenseAgent(); | ||
| end; | ||
|
|
||
| local procedure EnableAadApplicationForCurrentCompanyWithoutAuthorization() | ||
|
t-prda marked this conversation as resolved.
|
||
| var | ||
| AadApplication: Record "AAD Application"; | ||
| begin | ||
| GetAadApplication(AadApplication); | ||
|
|
||
| // Enabling creates the application user. Disable it again before changing permissions. | ||
| if AadApplication.State <> AadApplication.State::Enabled then begin | ||
| AadApplication.Validate(State, AadApplication.State::Enabled); | ||
| AadApplication.Modify(true); | ||
| end; | ||
|
|
||
| if HasPermissionForCurrentCompany(AadApplication) then | ||
| exit; | ||
|
|
||
| AadApplication.Validate(State, AadApplication.State::Disabled); | ||
| AadApplication.Modify(true); | ||
|
|
||
| AddPermissionForCompany(AadApplication, GetCurrentCompanyName()); | ||
|
|
||
| AadApplication.Validate(State, AadApplication.State::Enabled); | ||
| AadApplication.Modify(true); | ||
| end; | ||
|
|
||
| local procedure DisableAadApplicationForCompany(CompanyNameValue: Text[30]) | ||
| var | ||
| AadApplication: Record "AAD Application"; | ||
| OtherCompanyPermissionExists: Boolean; | ||
| begin | ||
| if not AadApplication.Get(GetAadAppId()) then | ||
| exit; | ||
|
|
||
| OtherCompanyPermissionExists := HasOtherCompanyPermission(AadApplication, CompanyNameValue); | ||
| RemovePermissionForCompany(AadApplication, CompanyNameValue, not OtherCompanyPermissionExists); | ||
| if OtherCompanyPermissionExists then | ||
| exit; | ||
| if AadApplication.State = AadApplication.State::Disabled then | ||
| exit; | ||
|
|
||
| AadApplication.Validate(State, AadApplication.State::Disabled); | ||
| AadApplication.Modify(true); | ||
| end; | ||
|
|
||
| local procedure VerifyCurrentUserCanManageExpenseAgent() | ||
| var | ||
| AggregatePermissionSet: Record "Aggregate Permission Set"; | ||
| AgentSystemPermissions: Codeunit "Agent System Permissions"; | ||
| UserPermissions: Codeunit "User Permissions"; | ||
| begin | ||
| if UserPermissions.IsSuper(UserSecurityId()) then | ||
| exit; | ||
|
|
||
| GetAgentAdminPermissionSet(AggregatePermissionSet); | ||
|
t-prda marked this conversation as resolved.
|
||
| if not UserPermissions.HasUserPermissionSetAssigned( | ||
| UserSecurityId(), | ||
| GetCurrentCompanyName(), | ||
| AggregatePermissionSet."Role ID", | ||
| AggregatePermissionSet.Scope, | ||
| AggregatePermissionSet."App ID") | ||
| then | ||
| Error(PermissionSetRequiredErr, AggregatePermissionSet.Name); | ||
|
|
||
| if not AgentSystemPermissions.CurrentUserHasCanManageAllAgentsPermission() then | ||
| Error(PermissionSetRequiredErr, AggregatePermissionSet.Name); | ||
|
|
||
| GetExpenseManagementAdminPermissionSet(AggregatePermissionSet); | ||
| if not UserPermissions.HasUserPermissionSetAssigned( | ||
| UserSecurityId(), | ||
| GetCurrentCompanyName(), | ||
| AggregatePermissionSet."Role ID", | ||
| AggregatePermissionSet.Scope, | ||
| AggregatePermissionSet."App ID") | ||
| then | ||
| Error(PermissionSetRequiredErr, AggregatePermissionSet.Name); | ||
|
|
||
| if not HasSecurityPermission(UserPermissions) then | ||
|
t-prda marked this conversation as resolved.
|
||
| Error(SecurityPermissionRequiredErr); | ||
|
|
||
| GetExpenseAgentPermissionSet(AggregatePermissionSet); | ||
| if not UserPermissions.HasUserPermissionSetAssigned( | ||
| UserSecurityId(), | ||
| GetCurrentCompanyName(), | ||
| AggregatePermissionSet."Role ID", | ||
| AggregatePermissionSet.Scope, | ||
| AggregatePermissionSet."App ID") | ||
| then | ||
| Error(PermissionSetRequiredErr, AggregatePermissionSet.Name); | ||
| end; | ||
|
|
||
| local procedure HasSecurityPermission(UserPermissions: Codeunit "User Permissions"): Boolean | ||
| var | ||
| AccessControl: Record "Access Control"; | ||
| NullGuid: Guid; | ||
| begin | ||
| exit(UserPermissions.HasUserPermissionSetAssigned( | ||
| UserSecurityId(), | ||
| GetCurrentCompanyName(), | ||
| SecurityPermissionSetLbl, | ||
| AccessControl.Scope::System, | ||
| NullGuid)); | ||
| end; | ||
|
|
||
| local procedure HasPermissionForCurrentCompany(AadApplication: Record "AAD Application"): Boolean | ||
| begin | ||
| exit(HasPermissionForCompany(AadApplication, GetCurrentCompanyName())); | ||
| end; | ||
|
|
||
| local procedure HasPermissionForCompany(AadApplication: Record "AAD Application"; CompanyNameValue: Text[30]): Boolean | ||
| var | ||
| AccessControl: Record "Access Control"; | ||
| AggregatePermissionSet: Record "Aggregate Permission Set"; | ||
| begin | ||
| GetExpenseAgentPermissionSet(AggregatePermissionSet); | ||
|
|
||
| SetExpenseAgentPermissionFilters(AccessControl, AadApplication, AggregatePermissionSet); | ||
| AccessControl.SetRange("Company Name", CompanyNameValue); | ||
| exit(not AccessControl.IsEmpty()); | ||
| end; | ||
|
|
||
| local procedure HasOtherCompanyPermission(AadApplication: Record "AAD Application"; CompanyNameValue: Text[30]): Boolean | ||
| var | ||
| AccessControl: Record "Access Control"; | ||
| AggregatePermissionSet: Record "Aggregate Permission Set"; | ||
| begin | ||
| GetExpenseAgentPermissionSet(AggregatePermissionSet); | ||
| SetExpenseAgentPermissionFilters(AccessControl, AadApplication, AggregatePermissionSet); | ||
| AccessControl.SecurityFiltering(SecurityFilter::Ignored); | ||
| AccessControl.SetFilter("Company Name", '<>''''&<>%1', CompanyNameValue); | ||
| exit(not AccessControl.IsEmpty()); | ||
| end; | ||
|
|
||
| local procedure AddPermissionForCompany(AadApplication: Record "AAD Application"; CompanyNameValue: Text[30]) | ||
| var | ||
| AccessControl: Record "Access Control"; | ||
| AggregatePermissionSet: Record "Aggregate Permission Set"; | ||
| begin | ||
| if HasPermissionForCompany(AadApplication, CompanyNameValue) then | ||
| exit; | ||
|
|
||
| GetExpenseAgentPermissionSet(AggregatePermissionSet); | ||
|
|
||
| AccessControl.Init(); | ||
| AccessControl.Validate("User Security ID", AadApplication."User ID"); | ||
| AccessControl.Validate("Role ID", AggregatePermissionSet."Role ID"); | ||
| AccessControl.Validate("App ID", AggregatePermissionSet."App ID"); | ||
| AccessControl.Validate(Scope, AggregatePermissionSet.Scope); | ||
| AccessControl.Validate("Company Name", CompanyNameValue); | ||
| AccessControl.Insert(true); | ||
| end; | ||
|
|
||
| local procedure RemovePermissionForCompany(AadApplication: Record "AAD Application"; CompanyNameValue: Text[30]; RunTrigger: Boolean) | ||
| var | ||
| AccessControl: Record "Access Control"; | ||
| AggregatePermissionSet: Record "Aggregate Permission Set"; | ||
| begin | ||
| GetExpenseAgentPermissionSet(AggregatePermissionSet); | ||
|
|
||
| SetExpenseAgentPermissionFilters(AccessControl, AadApplication, AggregatePermissionSet); | ||
| AccessControl.SetRange("Company Name", CompanyNameValue); | ||
| AccessControl.DeleteAll(RunTrigger); | ||
| end; | ||
|
|
||
| local procedure GetAadApplication(var AadApplication: Record "AAD Application") | ||
| begin | ||
| if not AadApplication.Get(GetAadAppId()) then | ||
| Error(AadApplicationMissingErr); | ||
| end; | ||
|
|
||
| local procedure GetCurrentCompanyName(): Text[30] | ||
| begin | ||
| exit(CopyStr(CompanyName(), 1, 30)); | ||
| end; | ||
|
|
||
| local procedure GetExpenseAgentPermissionSet(var AggregatePermissionSet: Record "Aggregate Permission Set") | ||
| begin | ||
| GetPermissionSet(AggregatePermissionSet, ExpenseAgentPermissionSetLbl); | ||
| end; | ||
|
|
||
| local procedure GetAgentAdminPermissionSet(var AggregatePermissionSet: Record "Aggregate Permission Set") | ||
| var | ||
| BaseApplicationAppId: Guid; | ||
| begin | ||
| Evaluate(BaseApplicationAppId, BaseApplicationAppIdTxt); | ||
| AggregatePermissionSet.Reset(); | ||
| AggregatePermissionSet.SetRange("App ID", BaseApplicationAppId); | ||
| AggregatePermissionSet.SetRange("Role ID", AgentAdminPermissionSetLbl); | ||
| if not AggregatePermissionSet.FindFirst() then | ||
|
t-prda marked this conversation as resolved.
|
||
| ErrorPermissionSetMissing(AgentAdminPermissionSetLbl); | ||
| end; | ||
|
|
||
| local procedure GetExpenseManagementAdminPermissionSet(var AggregatePermissionSet: Record "Aggregate Permission Set") | ||
| begin | ||
| GetPermissionSet(AggregatePermissionSet, ExpenseManagementAdminPermissionSetLbl); | ||
| end; | ||
|
|
||
| local procedure GetPermissionSet(var AggregatePermissionSet: Record "Aggregate Permission Set"; PermissionSetId: Code[20]) | ||
| var | ||
| ExpenseAgentAppId: Guid; | ||
| begin | ||
| AggregatePermissionSet.Reset(); | ||
| Evaluate(ExpenseAgentAppId, ExpenseAgentAppIdTxt); | ||
| AggregatePermissionSet.SetRange("App ID", ExpenseAgentAppId); | ||
| AggregatePermissionSet.SetRange("Role ID", PermissionSetId); | ||
| if not AggregatePermissionSet.FindFirst() then | ||
| ErrorPermissionSetMissing(PermissionSetId); | ||
| end; | ||
|
|
||
| local procedure ErrorPermissionSetMissing(PermissionSetId: Code[20]) | ||
| var | ||
| MissingPermissionSetErrorInfo: ErrorInfo; | ||
| begin | ||
| MissingPermissionSetErrorInfo.ErrorType := ErrorType::Internal; | ||
| MissingPermissionSetErrorInfo.DataClassification := DataClassification::SystemMetadata; | ||
| MissingPermissionSetErrorInfo.Message := StrSubstNo(PermissionSetMissingErr, PermissionSetId); | ||
| Error(MissingPermissionSetErrorInfo); | ||
| end; | ||
|
|
||
| local procedure SetExpenseAgentPermissionFilters(var AccessControl: Record "Access Control"; AadApplication: Record "AAD Application"; AggregatePermissionSet: Record "Aggregate Permission Set") | ||
| begin | ||
| AccessControl.SetRange("User Security ID", AadApplication."User ID"); | ||
| AccessControl.SetRange("Role ID", AggregatePermissionSet."Role ID"); | ||
| AccessControl.SetRange(Scope, AggregatePermissionSet.Scope); | ||
| AccessControl.SetRange("App ID", AggregatePermissionSet."App ID"); | ||
| end; | ||
|
|
||
| var | ||
| ExpenseAgentAadAppIdTxt: Label 'ee1eb5fd-719b-44f2-97d0-0efd34bc4148', Locked = true; | ||
| ExpenseAgentAppIdTxt: Label '66efe10c-8033-403b-a86d-77c0887178ba', Locked = true; | ||
| BaseApplicationAppIdTxt: Label '437dbf0e-84ff-417a-965d-ed2bb9650972', Locked = true; | ||
| AgentAdminPermissionSetLbl: Label 'Agent - Admin', Locked = true; | ||
| ExpenseAgentPermissionSetLbl: Label 'Expense Agent', Locked = true; | ||
| ExpenseManagementAdminPermissionSetLbl: Label 'Expense Mgmt. Admin', Locked = true; | ||
| SecurityPermissionSetLbl: Label 'SECURITY', Locked = true; | ||
| AadApplicationMissingErr: Label 'The Expense Agent Microsoft Entra application is not configured.'; | ||
| PermissionSetRequiredErr: Label 'You must be assigned the %1 permission set to manage the Expense Agent Microsoft Entra application.', Comment = '%1 = permission set name'; | ||
| PermissionSetMissingErr: Label 'The %1 permission set is not available.', Comment = '%1 = permission set ID'; | ||
| SecurityPermissionRequiredErr: Label 'You must be assigned either the SUPER or SECURITY permission set to manage the Expense Agent Microsoft Entra application.'; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.