Amplio Smart Permissions
Sync Azure AD security groups to BC custom permission sets.
- Current version: v1.1.0.11
- AL prefix: AMPLI
- Object range: 81600-81699
- Business Central: 26.0.0.0
- Publisher: Amplio Solutions
Amplio Smart Permissions
Status: v1.0.1.0 — ready for sandbox install and AppSource submission. All eleven build steps shipped; v1.0.1.0 renames every object and shipped role code from theAMP/AMP-SP-prefix to theASSPM/ASSPM-prefix. SeeBuild-Plan.mdfor the per-step breakdown andCHANGELOG.mdfor the release manifest.
A Business Central 26.x extension that synchronises Azure Active Directory security group membership to custom-built Amplio permission sets — automatically, on login and on a schedule.
Setup prerequisites — Microsoft Entra integration
Smart Permissions does not call Microsoft Graph directly. It reads BC's built-in Microsoft Entra integration: BC's standard Microsoft Entra Group setup binds each AAD security group to a BC User Group, and BC itself keeps User Group Member in sync as users log in. Smart Permissions then maps those User Groups to Amplio custom permission sets per company.
This means the BC admin must wire up the AAD groups in BC before configuring Smart Permissions. If you skip this step, Smart Permissions sees empty groups and does nothing — there are no errors, just no permission changes.
End-to-end setup is four steps:
- Create the AAD security groups in your Microsoft Entra tenant (e.g.
BC Sales Team,BC AP Processors). Standard Entra ID security groups — no special properties required. - In BC, search
Microsoft Entra Groupsin Tell Me → New → pick the AAD group → save. BC reads the group from Entra and creates a matchingUser Grouprow. From this point on, BC keepsUser Group Memberin sync as users log in — there is nothing further to maintain on the BC side. - In Smart Permissions Setup, map each BC User Group to one Amplio role + permission sets per company via Azure Group Role Mappings (search "Smart Permissions" in Tell Me, then click the action on the hub).
- For new hires, just add them to the AAD security group in Entra ID. BC picks up the membership on their next login (via the Microsoft Entra Group integration), and Smart Permissions then applies the mapped permission sets on the same login (via the Smart Permissions login subscriber and the hourly catch-up sync).
If you have not configured step 2, Smart Permissions Setup shows a yellow Setup prerequisites notice at the top of the page reminding you to do so before mapping groups.
Quick start (under 30 minutes in a fresh sandbox)
- Install the
.appfile in your sandbox. - Open ASSPM Setup Hub from Tell Me search. The hub shows live status counts and Quick Links.
- Open Azure Group Role Mappings from the hub. The 16 sample mappings are pre-seeded with placeholder GUIDs (rows shown in the
Attentionstyle — visual flag for "still unconfigured"). - For each role you want Azure-driven, replace the placeholder Group Object ID with the real Azure AD group GUID (copy it from the group's Overview page in the Microsoft Entra admin centre). Ignore or delete the rows for roles you do not want.
- Set Status = Active on each configured row. The page refuses to activate while the Group Object ID still matches the placeholder pattern.
- Open Smart Permissions Setup again, click Open scheduled job, find the row described "Amplio Smart Permissions - hourly sync", and set its Status = Ready. The first scheduled pass lands at the next top-of-hour.
- Optional: click Run sync now on the hub for an immediate first pass; check the Permission Sync Log to confirm the per-user verdicts.
That is the end-to-end happy path. Sections below cover the nuance.
What it does
- Admins map each Azure AD security group to one Amplio role (e.g.
ASSPM-AR-PROC,ASSPM-FPA,ASSPM-RO-AUD). - Each role grants a configurable list of custom permission sets, optionally restricted to specific companies.
- When a user logs into BC, their Azure group memberships are read via Microsoft Graph, matched against the mapping table, and their
Access Controlrows are reconciled — adds what's missing, removes what's no longer applicable, leaves manual MS permission sets alone. - A scheduled job (recommended hourly) catches group membership changes that happen between logins.
- Every change is written to a permanent audit log.
What it does NOT do
- It does not include any standard Microsoft permission sets. Every right is declared object-level from scratch (Read / Insert / Modify / Delete on each table, page, report, codeunit).
- It does not create BC users from Azure AD — users must already exist in BC's
Usertable. - It does not modify Access Control rows it doesn't own (i.e. manually-assigned MS permission sets stay put).
- It does not support nested group expansion in v1 (deferred to v1.1).
Per-user controls
A per-user opt-out flag on ASSPM User Settings (Skip Login Sync) suppresses the login trigger for one user without touching the global subscriber. The scheduled job ignores the flag, so a skewed configuration is always caught up by the next scheduled run. v1.0.0 ships the table only — a flip-the-switch UI is on the v1.1 roadmap. Until then a developer can toggle the row directly during testing.
Sync architecture — three sync paths
Smart Permissions reconciles Access Control rows on three independent paths. Each path calls the same engine (ASSPM Perm Sync Engine, codeunit 81620) with a different Sync Source, which is how the audit log distinguishes them. The three paths are layered so a misconfiguration on any one of them is caught by another.
| Path | Codeunit | Sync Source | Fires when | Throttle |
|---|---|---|---|---|
| Login event | ASSPM Perm Sync Login (81621) |
Login |
A user opens a company in BC. Subscribes to LogInManagement.OnAfterCompanyOpen. Screens for licence type, state, and the per-user opt-out before reaching the engine. |
60-second per-user in-memory throttle (ASSPM Perm Sync Throttle, 81626). Catches a multi-company tab storm without re-syncing. |
| Scheduled job | ASSPM Perm Sync Scheduler (81622) |
Scheduled |
The seeded Job Queue Entry fires on its cadence (one hour by default). Calls Engine.SyncAllUsers, which iterates every BC user that passes the licence-type and state gates. |
Bypasses the login throttle. The 60 minutes between runs cadence is the cadence. |
| Manual button | ASSPM Setup Hub (page 81637) — Run sync now action; ASSPM Azure Grp Map List — Sync now (selected) action |
Manual |
Admin clicks the button. The hub action calls Engine.SyncAllUsers; the mapping-list action calls Engine.SyncUserPermissions for every member of every selected mapping. |
Bypasses the login throttle. |
Each run produces its own Sync Run ID. Every audit log entry written during one run shares that GUID, so the log viewer can group an entire run as a single event regardless of which path triggered it.
When does each path fire?
- Login path — every time a user opens a company. Cost: one Graph call per user per minute at most. Optimal for active users; misses inactive users entirely.
- Scheduled path — every hour, every day, regardless of whether anyone is logged in. Cost: one Graph call per active BC user per hour. Optimal for catching up users who have not logged in (group memberships in Azure AD change all the time, but a user who has not logged in cannot pick up the change until next login).
- Manual path — on demand. Optimal for the "I just changed an Azure mapping; pick it up now" flow, and for the troubleshooting workflow ("am I getting the right permission sets?").
Enabling the scheduled job
The install seeder writes the Job Queue Entry with Status = On Hold so the engine cannot start firing Graph calls before admins have replaced the placeholder sample mappings with real Azure AD group GUIDs. To enable the schedule:
- Open Smart Permissions Setup (search "Smart Permissions" in Tell Me).
- Click Open scheduled job (or click the
Scheduled jobrow in the Status group). The standardJob Queue Entriespage opens, filtered to the Smart Permissions row. - Find the row described "Amplio Smart Permissions - hourly sync".
- Set Status to Ready (action:
Set Status to Ready).
The first run lands at the next top-of-hour. To pause the schedule again, set Status back to On Hold. To change the cadence, edit No. of Minutes between Runs on the Job Queue Entry directly — the seeder leaves admin edits alone on subsequent re-installs and upgrades.
In a multi-company tenant, each company gets its own scheduler row — Job Queue Entry is per-company in BC, so the seeder cannot collapse them at install time. To stop the redundant work, the engine consults a tenant-wide dedup gate at the top of every Scheduled-source SyncAllUsers pass: the ASSPM Settings singleton holds Last Scheduled Batch Run At, and only the first scheduled pass within the configured Min Scheduled Batch Window Mins (default 30) actually does the work. Subsequent companies' passes write a single Skipped log row and exit. Manual runs and login-driven syncs bypass the gate. The window is configurable on the Settings card.
Reading the audit log
The Permission Sync Log (search "Permission Sync Log" in Tell Me, or open from the Setup hub) is the authoritative record of every change the engine has made and every decision it has skipped. The page opens with a 7-day default filter, action-coloured rows (green for additions, amber for removals, red for errors), and a ribbon of admin actions.
Filters and presets
- Date range action group — quick presets for
Today,Last 7 days(default),Last 30 days, andAll. Each preset clears the prior Timestamp filter and applies the new one in a single click. Older entries can also be reached by clearing the column filter manually. - Sync Run ID column — every entry produced in one engine run carries the same Guid. Filter the column to a specific run to see the full per-user picture, or use Group by run in the ribbon to open the Sync Run Summary page (one row per run with counts of additions / removals / updates / skips / errors and a
Show entriesdrilldown). - User Name, Action, Sync Source, Permission Set ID, Company Name, Role Code — each column supports the standard BC column-filter pane (right-click → Filter) for any ad-hoc narrowing.
Re-sync from a log row
Two actions take a selection of one or more log rows and run the engine for each unique User Security ID found in the selection. Both deduplicate by user — selecting twenty rows that all belong to the same user is one engine call.
- Re-sync (live) — same selection model, but actually applies the changes after a Confirm dialog (
"Re-sync N user(s) now? Access Control rows owned by Smart Permissions may be created or removed for each user."). Default answer isNo. The engine writes new audit rows taggedManualso the run is distinguishable from automatic Login / Scheduled passes.
Re-sync (preview) — calls Engine.DryRunForUser per unique user. The engine computes the diff, writes audit rows tagged Dry Run – Would Add / Dry Run – Would Remove, and returns a JsonObject. The page renders the result as a plain-text Message in this shape:
Dry run for ALICE.SMITH (b21f...):
Would ADD:
- ASSPM-AR-PROC in CRONUS UK Ltd. (role ASSPM-AR-PROC)
Would REMOVE:
- ASSPM-INV-CLK in CRONUS UK Ltd.
Errors: 0
No Access Control rows are touched. Use this to verify what the engine would do before committing.
Other ribbon actions
- View error detail — pops the verbatim
Error Detailtext for the highlighted row (truncated in-grid to fit the column). - Group by run — opens Sync Run Summary.
- Edit in Excel — invokes the standard BC
Edit in Excelcodeunit. Filters in place on the page carry over. The auto-generated Excel button on the BC ribbon does the same; this action is a discoverable shortcut. - Clean old entries — reads
Audit Retention Daysfrom the Settings singleton (default 90), confirms with the admin, and deletes every log row older than that threshold. This is destructive — once deleted, log rows are not recoverable. - Settings — opens the Smart Permissions Settings card so admins can change retention, the scheduler dedup window, and notification preferences without leaving the log viewer.
Settings card
The Smart Permissions Settings card (search "Smart Permissions Settings" in Tell Me, or open from the Settings action on the hub or the log) is a tenant-wide singleton — one row applies to every company. Fields:
| Field | Default | Effect |
|---|---|---|
| Audit retention (days) | 90 | The threshold the Clean old entries action uses. |
| Scheduled batch dedup window (minutes) | 30 | Multi-company scheduler dedup gate (see above). |
| Last scheduled batch run at | (read-only) | Stamp written by the engine on every scheduled batch start. |
| Last scheduled batch run ID | (read-only) | Sync Run ID of the most recent scheduled batch — useful when filtering the log. |
| Email administrators on failure | unticked | Reserved for a future release. |
| Default manual sync source label | blank | Informational text shown alongside the Run sync now action. |
Who can configure Smart Permissions?
Three patterns work:
- A SUPER user — full access, no permission set assignment needed.
- A user with
ASSPMAll— can manage every Smart Permissions setup page, run syncs, read the audit log, edit settings. Cannot edit non-Smart-Permissions parts of BC. - A user with both
ASSPMAlland at least one of the standard Smart Permissions roles — can configure Smart Permissions AND has the role they're configuring. Useful for a team lead who needs both.
To assign: open the User card, navigate to Permission Sets, add ASSPMAll (Source = Amplio Smart Permissions).
The extension itself respects this set — its setup pages will refuse access to users without it (or without SUPER), reducing the chance of an accidental config change by a non-admin.
ASSPMAll is a self-contained, object-level permission set: it does not inherit from any Microsoft permission set via IncludedPermissionSets. The same independence rule that defines the 16 standard role sets applies here too, so the admin set is portable across BC versions and never accidentally drags in MS scope.
Standard role library (16 roles, shipped object-level)
| Code | Role | Pairing |
|---|---|---|
ASSPM-AR-PROC |
AR Processor | Processor side of the AR pair |
ASSPM-AR-SUPV |
AR Supervisor | Supervisor side — superset of Processor |
ASSPM-AP-PROC |
AP Processor | Processor side of the AP pair |
ASSPM-AP-SUPV |
AP Supervisor | Supervisor side — superset of Processor |
ASSPM-SO-PROC |
Sales Order Processor | Independent |
ASSPM-PO-PROC |
Purchase Order Processor | Independent |
ASSPM-SQ-ONLY |
Sales Quote Only | Strict subset of Sales Order Processor |
ASSPM-PQ-ONLY |
Purchase Quote Only | Strict subset of Purchase Order Processor |
ASSPM-INV-CLK |
Inventory Clerk | Independent |
ASSPM-CASH-ALLOC |
Cash Allocator | Independent |
ASSPM-GL-ACCT |
General Ledger Accountant | Independent |
ASSPM-FA-ACCT |
Fixed Asset Accountant | Independent |
ASSPM-PROJ-MGR |
Project Manager | Independent |
ASSPM-APPR-MGR |
Approver Manager | Often layered on top of another role |
ASSPM-FPA |
FP&A | Independent |
ASSPM-RO-AUD |
Read-Only Auditor | Independent |
Per the Smart-Permissions design rule, no permission set uses IncludedPermissionSets — every set is a self-contained, object-level grant. Pair members duplicate scope explicitly so unassign is clean and reviewable.
Bespoke roles
The role list is a table (ASSPM Smart Role), not an enum. Admins can create their own bespoke role groups via the Smart Role List page (delivered in Step 4) and bundle any combination of permission sets — Amplio-shipped, MS standard, or other extensions' — into them. Standard roles are seeded on install and cannot be deleted; bespoke roles are fully editable.
What ships pre-configured (the install seeder)
When the extension installs (or upgrades), an idempotent seeder fills three tables with sensible defaults so a fresh install is immediately useful:
ASSPM Smart Role— 16 standard role rows,Type = Standard,Status = Active. These cannot be deleted (admins disable them instead).ASSPM Role PermSet Assign— 16 rows, one per standard role, mapping each role 1:1 to its matching shipped permission set.Source = Amplio-Builtin,Sort Order = 10,Company Filterblank (applies to every company). Admins can layer extra permission sets on top by adding new rows at higher Sort Order values.ASSPM Azure Grp Role Mapping— 16 sample rows, one per standard role, allStatus = Disabledwith a deterministic placeholder Group Object ID. Configure in-place rather than from scratch: replace the placeholder GUID with the real Azure AD group GUID, then flip Status to Active.
The seeder runs OnInstallAppPerCompany (codeunit ASSPM Smart Role Install, Subtype = Install) and OnUpgradePerCompany (codeunit ASSPM Smart Role Upgrade, Subtype = Upgrade). Both call the same idempotent SeedAll() entry point, so re-running is safe and admin edits to seeded rows are preserved across upgrades.
The 16 sample Azure Group Role Mappings
Each row is keyed on its placeholder Group Object ID. The Group Display Name is the suggested-but-rename-friendly placeholder admins typically replace with their actual Entra ID group name. The Notes field reads: "Sample mapping — replace Group Object ID with your actual Azure AD group GUID, then switch Status to Active to enable."
| # | Sample Group Display Name |
Role Code |
Placeholder Group Object ID |
|---|---|---|---|
| 1 | BC AR Processors | ASSPM-AR-PROC |
aaaaaaaa-aaaa-aaaa-aaaa-000000000001 |
| 2 | BC AR Supervisors | ASSPM-AR-SUPV |
aaaaaaaa-aaaa-aaaa-aaaa-000000000002 |
| 3 | BC AP Processors | ASSPM-AP-PROC |
aaaaaaaa-aaaa-aaaa-aaaa-000000000003 |
| 4 | BC AP Supervisors | ASSPM-AP-SUPV |
aaaaaaaa-aaaa-aaaa-aaaa-000000000004 |
| 5 | BC Sales Orders | ASSPM-SO-PROC |
aaaaaaaa-aaaa-aaaa-aaaa-000000000005 |
| 6 | BC Purchase Orders | ASSPM-PO-PROC |
aaaaaaaa-aaaa-aaaa-aaaa-000000000006 |
| 7 | BC Sales Quotes Only | ASSPM-SQ-ONLY |
aaaaaaaa-aaaa-aaaa-aaaa-000000000007 |
| 8 | BC Purchase Quotes Only | ASSPM-PQ-ONLY |
aaaaaaaa-aaaa-aaaa-aaaa-000000000008 |
| 9 | BC Inventory Clerks | ASSPM-INV-CLK |
aaaaaaaa-aaaa-aaaa-aaaa-000000000009 |
| 10 | BC Cash Allocators | ASSPM-CASH-ALLOC |
aaaaaaaa-aaaa-aaaa-aaaa-000000000010 |
| 11 | BC GL Accountants | ASSPM-GL-ACCT |
aaaaaaaa-aaaa-aaaa-aaaa-000000000011 |
| 12 | BC Fixed Asset Accountants | ASSPM-FA-ACCT |
aaaaaaaa-aaaa-aaaa-aaaa-000000000012 |
| 13 | BC Project Managers | ASSPM-PROJ-MGR |
aaaaaaaa-aaaa-aaaa-aaaa-000000000013 |
| 14 | BC Approver Managers | ASSPM-APPR-MGR |
aaaaaaaa-aaaa-aaaa-aaaa-000000000014 |
| 15 | BC FP&A | ASSPM-FPA |
aaaaaaaa-aaaa-aaaa-aaaa-000000000015 |
| 16 | BC Read-Only Auditors | ASSPM-RO-AUD |
aaaaaaaa-aaaa-aaaa-aaaa-000000000016 |
The all-a body is the visual flag: any row with that pattern is unconfigured. The Status field has an OnValidate gate that refuses to flip from Disabled to Active while the Group Object ID still matches the placeholder pattern, so accidentally enabling an un-wired sample is impossible.
How to fill in real Azure AD group GUIDs
- In the Microsoft Entra admin centre, open the security group you want to bind to a Smart Role.
- Copy the Object ID from the group's overview page (it is a Guid, e.g.
c47b8e23-2f6a-4d11-9f05-ab2c4e1d8e12). - In BC, open Azure Group Role Mappings, find the matching sample row, and replace the placeholder Group Object ID with the real one.
- Optionally replace the Group Display Name with your real group name (the engine will refresh it from Azure on the next sync anyway).
- Set Status to Active. The OnValidate trigger now permits the change.
- Repeat for each role you want Azure-driven assignment for. Roles you do not want to drive from Azure can be left Disabled or deleted entirely.
Project layout
17-Smart-Permissions/
├── app.json Extension manifest (id range 81600–81699)
├── Object-Range.md Collision audit + per-object allocations
├── Build-Plan.md 11-step build roadmap
├── README.md This file
├── .gitignore
├── .vscode/ Sandbox + OnPrem launch configs
├── .alpackages/ AL symbol packages (gitignored)
├── translations/ XLF translation stubs (en-GB)
└── src/
├── Tables/
│ ├── SmartRole.Table.al Table 81600 — Standard + Bespoke role master (+ enums 81600/81601)
│ ├── PermissionSetSource.Enum.al Enum 81602 — Amplio-Builtin / Amplio-Tenant
│ ├── AzureGroupRoleMapping.Table.al Table 81618 — group → role (PK on Group Object ID Guid; + enums 81603/81604)
│ ├── RolePermissionSetAssignment.Table.al Table 81619 — role → permission sets (per-company); Step-3 triggers + OnDelete audit
│ ├── PermissionSyncLog.Table.al Table 81624 — audit log (+ enums 81606/81607; + WriteEntry helper)
│ ├── UserSettings.Table.al Table 81627 — per-user `Skip Login Sync` opt-out
│ └── SmartPermissionsSettings.Table.al Table 81628 — tenant-wide singleton (`DataPerCompany = false`); audit retention + scheduler dedup state
├── PermissionSets/
│ ├── ARProcessor.PermissionSet.al Permset 81601 — AR Processor
│ ├── ARSupervisor.PermissionSet.al Permset 81602 — AR Supervisor
│ ├── APProcessor.PermissionSet.al Permset 81603 — AP Processor
│ ├── APSupervisor.PermissionSet.al Permset 81604 — AP Supervisor
│ ├── SalesOrderProcessor.PermissionSet.al Permset 81605 — Sales Order Processor
│ ├── PurchaseOrderProcessor.PermissionSet.al Permset 81606 — Purchase Order Processor
│ ├── SalesQuoteOnly.PermissionSet.al Permset 81607 — Sales Quote Only
│ ├── PurchaseQuoteOnly.PermissionSet.al Permset 81608 — Purchase Quote Only
│ ├── InventoryClerk.PermissionSet.al Permset 81609 — Inventory Clerk
│ ├── CashAllocator.PermissionSet.al Permset 81610 — Cash Allocator
│ ├── GLAccountant.PermissionSet.al Permset 81611 — GL Accountant
│ ├── FixedAssetAccountant.PermissionSet.al Permset 81612 — Fixed Asset Accountant
│ ├── ProjectManager.PermissionSet.al Permset 81613 — Project Manager
│ ├── ApproverManager.PermissionSet.al Permset 81614 — Approver Manager
│ ├── FPA.PermissionSet.al Permset 81615 — FP&A
│ └── ReadOnlyAuditor.PermissionSet.al Permset 81616 — Read-Only Auditor
├── Pages/
│ ├── SmartRoleList.Page.al Page 81630 — Smart Role list (Standard + Bespoke)
│ ├── SmartRoleCard.Page.al Page 81631 — Smart Role card with embedded assignments subform
│ ├── AzureGroupRoleMappingList.Page.al Page 81632 — Azure group → role mappings list
│ ├── RolePermSetAssignList.Page.al Page 81633 — flat list of every role → permission set assignment
│ ├── RolePermSetAssignSubform.Page.al Page 81634 — `ASSPM Role PermSet Lines` subform on the Smart Role Card
│ ├── AzureGroupRoleMappingCard.Page.al Page 81635 — Azure group → role mapping card
│ ├── PermissionSyncLog.Page.al Page 81636 — audit log viewer (filter presets, Re-sync preview/live, Edit in Excel, Clean old entries, Settings)
│ ├── SmartPermissionsSetup.Page.al Page 81637 — admin landing hub (NavigatePage)
│ ├── SyncRunSummary.Page.al Page 81638 — per-run aggregation over the audit log
│ └── SmartPermissionsSettingsCard.Page.al Page 81639 — card over the tenant-wide Settings singleton
├── Codeunits/
│ ├── SmartRoleInstall.Codeunit.al Codeunits 81617 (Install) + 81629 (Upgrade) — seed standard roles, builtin assignments, sample mappings, and the hourly Job Queue Entry
│ ├── RolePermSetValidator.Codeunit.al Codeunit 81605 — MS-shipped reject list + Permission Set ID / Company Filter validators
│ ├── PermissionSyncEngine.Codeunit.al Codeunit 81620 — diff + apply core
│ ├── PermissionSyncLogin.Codeunit.al Codeunit 81621 — OnAfterCompanyOpen subscriber
│ ├── PermissionSyncScheduler.Codeunit.al Codeunit 81622 — Job Queue runnable (hourly catch-up sync)
│ ├── AADGraphReader.Codeunit.al Codeunit 81623 — Microsoft Graph wrapper with cache + 401/403/404/429 handling
│ └── PermSyncThrottle.Codeunit.al Codeunit 81626 — SingleInstance 60-second per-user login throttle
└── permissionset/
└── 81600_ASSPMAll_permissionset.al Permset 81600 — `ASSPMAll`, the role admins assign in lieu of SUPER for Smart Permissions configuration
Required Azure AD app permissions
The Microsoft Graph reader inside the extension calls Graph as the BC service principal — it does not register a separate app. The standard Business Central first-party permission set covers the calls the engine makes, but the administrator of the Microsoft 365 tenant must consent to the following Graph application permissions:
| Permission | Why it is needed |
|---|---|
User.Read.All |
Resolve a BC user's Azure AD object ID and read their User Principal Name. Read-only across all users. |
GroupMember.Read.All |
Read the membership list of each Azure AD security group named in an Active mapping (used by the Sync now (selected) path that walks group → members). |
Directory.Read.All |
Resolve a security group's display name from its object ID, and enumerate the groups a single user belongs to (used by the Login / Scheduled / Manual single-user paths). |
To grant in Microsoft Entra admin centre:
- Identity → Applications → Enterprise applications, search for
Dynamics 365 Business Central(the first-party service principal). - Open it, then Permissions → Grant admin consent.
- Verify the three permissions above appear with Granted status.
User.Read.Allis usually already granted; the twoGroupMember.Read.AllandDirectory.Read.Allmay need explicit consent.
If consent is missing the engine writes Action = Error, Reason = Graph 401 / Graph 403 rows to the audit log on every sync. The Graph reader codeunit (ASSPM AAD Graph Reader, 81623) renders the underlying Graph error verbatim into the log's Error Detail field for triage.
Troubleshooting
Common Graph errors
| Symptom in the log | Likely cause | Fix |
|---|---|---|
Action = Error, Reason: Graph 401 ... |
The BC service principal has no token for Graph yet, or the token has expired and the silent refresh failed. | Sign out and back in to BC. If the error persists, verify admin consent in the Entra admin centre (see "Required Azure AD app permissions" above). |
Action = Error, Reason: Graph 403 ... |
Admin consent for one of the three Graph application permissions is missing. | Grant the missing permission in the Entra admin centre and re-run the sync. |
Action = Error, Reason: Graph 429 ... |
Microsoft Graph throttling. The reader respects the Retry-After header but a sustained burst can still trip the limit. |
Wait a few minutes, then re-run. If the error persists, lower the schedule cadence (the Job Queue Entry's No. of Minutes between Runs field) so fewer Graph calls land per hour. |
Action = Error, Reason: Graph 404 ... |
The Group Object ID on a mapping row no longer exists in Azure AD (group deleted or renamed-in-place to a fresh GUID). | Open the mapping row, paste the current Group Object ID from Entra ID, save, re-run. |
Use Re-sync (preview) before Re-sync (live)
When in doubt about what the engine would do for a user, highlight that user's row in Permission Sync Log and click Re-sync (preview). The popup shows every Add and Remove the engine would apply, without writing to Access Control. Re-run with Re-sync (live) once the preview reads correctly.
Clean up the audit log
The log is append-only; in a busy tenant it grows by a few hundred rows per hour. The Clean old entries action on the log page reads the Audit Retention Days field from the Smart Permissions Settings card (default 90) and deletes every row older than that. Confirm dialog defaults to No so an accidental click cannot mass-delete.
"I added a user to an Azure group; why are they not seeing their new permissions yet?"
Three sync paths exist (see "Sync architecture" below). Group changes flow through them in this order:
- Login path — picks up the change the next time the user opens a company.
- Scheduled path — picks up the change at the next hourly run, regardless of whether the user has logged in.
- Manual path — picks up the change immediately when an admin clicks Sync now on the hub or Sync now (selected) on the mapping list.
If you need to verify "did the engine see this group?", run Re-sync (preview) on the user from the log — the popup lists every group the engine read from Graph.
Bypass SUPER for admin tasks
Assign the ASSPMAll permission set to the admin user (User card → Permission Sets → add row, Source = Amplio Smart Permissions). They can then operate every Smart Permissions setup page, run manual syncs, read the audit log, edit settings, and clean old entries without holding SUPER. The engine codeunit's elevated indirect permissions handle Access Control writes on their behalf — admins do not need direct rights on the standard Access Control table.
Known limitations (v1.0.1.0)
- No nested-group expansion. If user U is in group A, and group A is a member of group B, only A is matched. Mapping B → role grants nothing to U. Microsoft Graph supports
transitiveMemberOfand the engine will pick this up in v1.1. - No automatic creation of BC users from Azure AD. A user must already exist in BC's
Usertable for the sync to assign permissions. Use Microsoft's standard "Update users from Office 365" flow for user provisioning. - No PowerShell / external API to manage mappings — admin UI only in v1.
- Per-user opt-out has table-only support. The
ASSPM User Settingstable carries theSkip Login Syncflag and the login subscriber honours it, but the v1.0.0 ship does not include a setup page for the table. A developer can edit the row directly during testing; a UI lands in v1.1. - No "deny" semantics. Conflict resolution is "union of rights", same as native BC's Access Control table.
Object range
81600–81699 (ASSPM prefix). Collision check against the other 14 extensions in the workspace: clean. See Object-Range.md for the per-object allocation table.
How to compile
Open the folder in VS Code with the AL Language extension. Then:
Ctrl+Shift+P→AL: Download symbols(downloads BC platform + Base App + System App symbols into.alpackages/)Ctrl+Shift+P→AL: Package(orCtrl+Shift+B) — producesAmplio Solutions_Amplio Smart Permissions_1.0.1.0.app
The launch configs in .vscode/launch.json cover both Sandbox (online) and OnPrem-container deployment.
For the smoke-test runbook to validate the package before AppSource submission, see Smoke-Test.md.
Publisher
Amplio Solutions — https://www.ampliosolutions.co.uk