IceOnlys Blog

All Around Microsoft Dynamics Business Central

BusinessCraft: Permissions and Virtual Tables

The last BusinessCraft post ended with permissions as the next big construction site. They work now: system, tenant, and AL permission sets, plus inherent permissions directly on objects. On the way, BusinessCraft also got its first virtual tables: Date, Integer, Session, AllObj, and AllObjWithCaption.

Permissions

In the last post, only the NAV way worked: Permission Set, Permission, and Access Control. That is one source of permissions. Business Central has three:

Source Where it comes from
System Permission sets that ship with the platform, like SUPER
Tenant Permission sets that users create or copy in the client
AL permissionset objects in installed apps

BusinessCraft now reads all three and aggregates them into one effective permission set per user, together with the inherent permissions described below. That is what the checks for read, insert, modify, delete, and execute run against.

A few details follow BC closely:

  • AL permission sets are installed into Metadata Permission Set and Metadata Permission, with the same table IDs as in BC (2000000250 and 2000000251). Included sets and permission set extensions are flattened into them.
  • Tenant permission sets support include and exclude lines.
  • Aggregate Permission Set is not stored. It is projected from the three sources whenever it is read, so it can never be out of date.
  • The effective permissions are cached per user. Any write to a permission table drops the cache, and the next login reloads it.

An AL permission set is an object like any other:

permissionset 50100 "SALES BASIC"
{
    Assignable = true;
    Caption = 'Sales Basic';

    Permissions =
        tabledata "Sales Header" = RIMD,
        tabledata "Sales Line" = RIMD,
        page "Sales Order" = X;
}

Inherent permissions

Permissions do not have to come from a permission set. With InherentPermissions, an object can grant permissions on itself:

table 50100 "Sales Setup"
{
    InherentPermissions = R;
    ...
}

Every user can read this table now, even if no permission set mentions it. This is the modern way in BC for things like setup tables, which every user needs to read anyway.

BusinessCraft parses InherentPermissions on tables, pages, and codeunits and adds them to the effective permissions, in addition to the permission sets the user has.

Indirect permissions

Indirect permissions are parsed, but not tested yet.

The idea in BC: a user with indirect permission on a table may not touch it directly, only through an object that grants access with its Permissions property. A typical example is a posting codeunit that writes ledger entries, while the user can only read them. That needs a proper test before I call it done.

The permission pages

The last post said it already: the permission pages in BC use a different corner of the runtime on every page. That was true. Most of the work was not the permission logic itself, but getting the triggers right and passing state through them cleanly.

Some examples:

  • Page.Run with a temporary record. The permission set list works on a temporary buffer. The temporary records now travel into pages with SourceTableTemporary, and OnInit runs before the page opens. That is what hides the tenant permission subform for system sets.
  • OnInsertRecord returning false. On a temporary source table, the page inserts the record itself and returns false. The shared Rec buffer has to survive that.
  • CurrPage.Editable. The tenant permission subform decides in ActivateControls whether it can be edited, so it needs to read this.
  • Page variables across triggers. BC keeps page-level variables and records alive between triggers. The user subform calls User.Get in OnNewRecord and User.TestField in OnInsertRecord. Without the record from the first trigger, the second one failed. The same goes for flags like IsPermissionSetEditable, which were wiped on OnValidate and blocked editing the name.
  • Rec until the trigger finishes. Field writes in OnValidate and OnInsert were lost when the cursor was released too early. This one did not only affect permissions: it also dropped the Item No. validation on sales lines and number series assignments.
  • Lookups on text variables. Permission columns like Read or Insert are text variables on the page, not table fields. They now honor LookupPageId and TableRelation on the page field, so choosing Yes or Indirect from the lookup list writes the value and runs OnValidate.
  • Captions. CurrPage.Caption changes the page caption at runtime, and the delete confirmation for a permission line now shows object type and ID instead of the permission set.
  • Zero is not blank. A permission line for all objects of a type uses object ID 0, and the first Object Type option has the value 0 too. Both have to count as real values, not as blank.

None of this is permission logic. But without it, the BC permission pages do not run.

Virtual tables

Virtual tables are tables that have no rows in the database. The platform computes them on the fly. Every BC developer uses them, often without thinking about it:

  • Integer for loops in reports, like copies or blank lines
  • Date for periods: days, weeks, months, quarters, years
  • AllObj and AllObjWithCaption for object lookups
  • Session for the sessions on the server

BusinessCraft now implements these five.

AllObj and AllObjWithCaption

These two were the easy ones. BusinessCraft already knows every object in every installed app, so AllObj simply lists them with object type, ID, name, and app. AllObjWithCaption adds the caption, translated into the session language with the XLIFF files from the earlier posts.

Integer and Date

These two were harder than expected.

The first version did the obvious thing: generate all values when the table is opened. For Integer, that is every number from roughly minus two billion to plus two billion. For Date, every day, week, month, quarter, and year across the whole calendar. It worked in theory, but it ate the performance.

The fix is a window. BusinessCraft only generates the rows that are actually needed. FindFirst, FindLast, Next, and filters are calculated directly, without a list behind them. When the client asks for the next 50 rows, it gets exactly 50 rows. This works very well now, also with filters like these:

Integer.SetRange(Number, 1, NoOfCopies);

Date.SetRange("Period Type", Date."Period Type"::Month);
Date.SetRange("Period Start", DMY2Date(1, 1, 2026), DMY2Date(31, 12, 2026));

Scrolling up

The web client needed an adjustment too.

Until now, a list started at the first record and only loaded more rows when you scrolled down. That is fine for customers or items. It is not fine for Integer.

In Business Central, a list on Integer opens at 0. But 0 is not the first record. You can scroll up into the negative numbers. So the client now also loads rows before the first loaded row, when you scroll up. Same behavior as in BC.

Session

The Session table lists the active sessions on the server.

And it does a bit more: deleting a record ends that session. The user of that session is logged out. Your own session cannot be ended that way. That would be a strange way to log out.

DataCaptionFields and DataCaptionExpression

A small thing, but you notice it on every card: the page caption.

BusinessCraft now uses DataCaptionFields on the table and DataCaptionExpression on the page to build the caption, like BC does. The card shows which record you are looking at, not just the page name.

ListPart repeaters

The repeater in a ListPart, like the lines on a sales order, got an overhaul.

Until now, there was a New button to add a line. It is gone. The logic is now the same as in Business Central:

  • At the end of the list, there is always an empty trailing line.
  • As soon as you type into it, it becomes a draft line, and a new empty line appears below.
  • When you leave the draft line, it is inserted, but only if you actually changed something. A line you just clicked through is not saved.

This sounds like a detail, but it is how everyone enters sales lines in BC. With a New button in between, it never felt right.

View and edit on list pages

List pages now have the view/edit toggle action, like in BC. An editable list opens in view mode, and you switch to edit mode when you want to change values directly in the list.

Numbers

  • 173 commits, up from 125
  • About 61,100 lines of Rust, up from about 48,500
  • Three permission sources: system, tenant, and AL, plus inherent permissions
  • Five virtual tables: Date, Integer, Session, AllObj, and AllObjWithCaption
  • Still a Cursor Pro subscription for $20 a month

Next: Indirect permissions

Permissions work, but one part is still open: indirect permissions. They are parsed, and now they need a real test. A user who can only read the item ledger entries should still be able to post a sales order, because Sales Post writes the entries on their behalf. And the same user must not be able to insert an entry directly.

If that works, the permission system is complete. I'll keep you updated. :)


BusinessCraft is not affiliated with or endorsed by Microsoft. "Microsoft", "Dynamics 365" and "Business Central" are trademarks of Microsoft Corporation.