Indirect Permissions in Business Central

Indirect Permissions in Business Central

Recently I created a new Rule in the BC Linter Cop to inform about missing Permission to do data Access:
https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0068

This resulted in a number of discussions about when to set the Permissions property, and if it makes sense at all.

https://github.com/StefanMaron/BusinessCentral.LinterCop/pull/768
https://github.com/StefanMaron/BusinessCentral.LinterCop/issues/725
https://github.com/StefanMaron/BusinessCentral.LinterCop/issues/780

… and some more, also on other platforms.

So I want to use this Blog post to try to really explain my reasoning behind the rule and why I think that it does make sense exaclty as it is.

Background

It all started when I was working on a customer project. I was working on some custom E-Mail functionality and wanted to link the Sent Mails Id in my custom table:

SentMailCode

And I am sure every developer has seen the error message You do not have the following permissions on TableData .... : Read.

But with Sent Emails it was a different one, it said ...: Read! I had never seen that before, and I was a bit confused at first, so I checked my Effective Permissions.

SentEmailsIndirectPermissions

As you can see this table has indeed indirect Read Permissions, although I have the SUPER Permission Set.

And to give you an overview of all Tables that would fall under this category in production environment right now, here is the total list of all 250 tables having at leas one of the RIMD set to indirect:

Indirect Permissions
Object TypeObject NameRead PermissionInsert PermissionModify PermissionDelete Permission
Table DataG/L EntryYesIndirectIndirectIndirect
Table DataCust. Ledger EntryYesIndirectYesIndirect
Table DataVendor Ledger EntryYesIndirectYesIndirect
Table DataItem Ledger EntryYesIndirectIndirectIndirect
Table DataG/L RegisterYesIndirectIndirectIndirect
Table DataItem RegisterYesIndirectIndirectIndirect
Table DataBatch Processing ParameterYesIndirectIndirectIndirect
Table DataBatch Processing Session MapYesIndirectIndirectIndirect
Table DataExch. Rate Adjmt. Reg.YesIndirectIndirectIndirect
Table DataDate Compr. RegisterYesIndirectIndirectIndirect
Table DataSales Shipment HeaderYesIndirectIndirectYes
Table DataSales Shipment LineYesIndirectIndirectIndirect
Table DataSales Invoice HeaderYesIndirectIndirectYes
Table DataSales Invoice LineYesIndirectIndirectIndirect
Table DataSales Cr.Memo HeaderYesIndirectIndirectYes
Table DataSales Cr.Memo LineYesIndirectIndirectIndirect
Table DataPurch. Rcpt. LineYesIndirectIndirectIndirect
Table DataPurch. Inv. HeaderYesIndirectIndirectYes
Table DataPurch. Inv. LineYesIndirectIndirectIndirect
Table DataPurch. Cr. Memo LineYesIndirectIndirectIndirect
Table DataJob Ledger EntryYesIndirectIndirectIndirect
Table DataExch. Rate Adjmt. Ledg. EntryYesIndirectIndirectIndirect
Table DataRes. Ledger EntryYesIndirectIndirectIndirect
Table DataJob RegisterYesIndirectIndirectIndirect
Table DataG/L Entry - VAT Entry LinkYesIndirectIndirectIndirect
Table DataVAT EntryYesIndirectIndirectIndirect
Table DataBank Account Ledger EntryYesIndirectIndirectIndirect
Table DataBank Account StatementYesIndirectIndirectYes
Table DataBank Account Statement LineYesIndirectIndirectIndirect
Table DataPhys. Inventory Ledger EntryYesIndirectIndirectIndirect
Table DataIssued Reminder HeaderYesIndirectIndirectYes
Table DataIssued Reminder LineYesIndirectIndirectIndirect
Table DataReminder/Fin. Charge EntryYesIndirectIndirectIndirect
Table DataIssued Fin. Charge Memo HeaderYesIndirectIndirectYes
Table DataIssued Fin. Charge Memo LineYesIndirectIndirectIndirect
Table DataItem Application EntryYesYesIndirectIndirect
Table DataDetailed Cust. Ledg. EntryYesIndirectIndirectIndirect
Table DataDetailed Vendor Ledg. EntryYesIndirectIndirectIndirect
Table DataChange Log EntryYesIndirectIndirectIndirect
Table DataApproval EntryYesIndirectIndirectIndirect
Table DataPosted Approval EntryYesIndirectIndirectIndirect
Table DataPosted Approval Comment LineYesIndirectIndirectIndirect
Table DataOverdue Approval EntryYesIndirectIndirectIndirect
Table DataWorkflow Webhook EntryYesIndirectIndirectIndirect
Table DataWorkflow Webhook NotificationYesIndirectIndirectIndirect
Table DataDimension Set EntryYesIndirectIndirectIndirect
Table DataDimension Set Tree NodeYesIndirectIndirectIndirect
Table DataVAT Rate Change Log EntryYesIndirectIndirectIndirect
Table DataStandard AddressYesIndirectIndirectIndirect
Table DataVAT Report ArchiveYesIndirectIndirectIndirect
Table DataWorkflows Entries BufferYesIndirectIndirectIndirect
Table DataCash Flow Azure AI BufferYesIndirectIndirectIndirect
Table DataJob WIP G/L EntryYesIndirectIndirectIndirect
Table DataJob BufferYesIndirectIndirectIndirect
Table DataJob WIP BufferYesIndirectIndirectIndirect
Table DataJob Difference BufferYesIndirectIndirectIndirect
Table DataLine Fee Note on Report Hist.YesIndirectIndirectIndirect
Table DataPayment Reporting ArgumentYesIndirectIndirectIndirect
Table DataCost Budget BufferYesIndirectIndirectIndirect
Table DataPayment Export DataYesIndirectIndirectIndirect
Table DataField Monitoring SetupYesIndirectYesIndirect
Table DataNet Promoter Score SetupIndirectIndirectIndirectIndirect
Table DataNet Promoter ScoreIndirectIndirectIndirectIndirect
Table DataProduct Video BufferIndirect
Table DataWorkflow - Record ChangeYesIndirectIndirectIndirect
Table DataWorkflow Record Change ArchiveYesIndirectIndirectIndirect
Table DataWorkflow Step Instance ArchiveYesIndirectIndirectIndirect
Table DataWorkflow Step Argument ArchiveYesIndirectIndirectIndirect
Table DataFlow Service ConfigurationYesIndirectIndirectIndirect
Table DataOffice Admin. CredentialsYesIndirectIndirectIndirect
Table DataOption Lookup BufferIndirectIndirectIndirectIndirect
Table DataEmail Logging SetupIndirectIndirectIndirectIndirect
Table DataFields Sync StatusIndirectIndirectIndirectIndirect
Table DataCancelled DocumentYesIndirectIndirectIndirect
Table DataGuided Experience ItemYesIndirectIndirectIndirect
Table DataUser Checklist StatusYesIndirectIndirect
Table DataChecklist Item BufferIndirect
Table DataChecklist SetupYesYesYesIndirect
Table DataSpotlight Tour TextIndirectIndirectIndirectIndirect
Table DataPrimary Guided Experience ItemIndirectIndirectIndirectIndirect
Table DataAzure AI UsageYesIndirectIndirectIndirect
Table DataImage Analysis ScenarioYesIndirectIndirectIndirect
Table DataSales Document IconYesIndirectIndirectIndirect
Table DataCalendar EventYesIndirectIndirectIndirect
Table DataCalendar Event User Config.YesIndirectIndirectIndirect
Table DataExtension Pending SetupIndirectIndirectIndirectIndirect
Table DataFeature Data Update StatusYesIndirectIndirectIndirect
Table DataTranslationIndirectIndirectIndirectIndirect
Table DataRetention Policy Allowed TableIndirectIndirectIndirectIndirect
Table DataRetention Policy Log EntryIndirectIndirectIndirectIndirect
Table DataPersistent BlobIndirectIndirectIndirectIndirect
Table DataEmail - Outlook AccountIndirectIndirectIndirectIndirect
Table DataRecurrence ScheduleIndirectIndirectIndirectIndirect
Table DataLogged SegmentYesYesYesIndirect
Table DataRM Matrix ManagementYesIndirect
Table DataEmployee Ledger EntryYesIndirectIndirectIndirect
Table DataDetailed Employee Ledger EntryYesIndirectIndirectIndirect
Table DataProduction OrderYesIndirect
Table DataProd. Order LineYesIndirect
Table DataProd. Order ComponentYesIndirect
Table DataProd. Order Routing LineYesIndirect
Table DataProd. Order Capacity NeedYesIndirect
Table DataProd. Order Routing ToolYesIndirect
Table DataProd. Order Routing PersonnelYesIndirect
Table DataProd. Order Rtng Qlty Meas.YesIndirect
Table DataAPI Extension UploadIndirectIndirectIndirectIndirect
Table DataOnboarding SignalIndirectIndirectIndirectIndirect
Table DataFA Ledger EntryYesIndirectIndirectIndirect
Table DataFA RegisterYesIndirectIndirectIndirect
Table DataMaintenance Ledger EntryYesIndirectIndirectIndirect
Table DataIns. Coverage Ledger EntryYesIndirectIndirectIndirect
Table DataInsurance RegisterYesIndirectIndirectIndirect
Table DataRegistered Whse. Activity Hdr.YesIndirectIndirectYes
Table DataRegistered Whse. Activity LineYesIndirectIndirectYes
Table DataValue EntryYesIndirectIndirectIndirect
Table DataRounding Residual BufferYesIndirectIndirectIndirect
Table DataPost Value Entry to G/LYesYesIndirectIndirect
Table DataCapacity Ledger EntryYesIndirectIndirect
Table DataInvt. Receipt HeaderYesIndirectIndirectIndirect
Table DataInvt. Receipt LineYesIndirectIndirectIndirect
Table DataInvt. Shipment HeaderYesIndirectIndirectIndirect
Table DataInvt. Shipment LineYesIndirectIndirectIndirect
Table DataInventory Adjustment BufferYesIndirectIndirectIndirect
Table DataInventory Adjmt. Entry (Order)YesIndirectIndirectIndirect
Table DataService HeaderYesIndirect
Table DataService Item LineYesIndirect
Table DataService LineYesIndirect
Table DataService Ledger EntryYesIndirect
Table DataService Shipment BufferYesIndirectIndirectYes
Table DataService Contract LineYesIndirect
Table DataService Contract HeaderYesIndirect
Table DataFiled Service Contract HeaderYesIndirect
Table DataService Invoice LineYesIndirect
Table DataItem Tracing BufferYesIndirectIndirectIndirect
Table DataItem Tracing History BufferYesIndirectIndirectIndirect
Table DataRecord BufferYesIndirectIndirectIndirect
Table DataReturn Shipment HeaderYesIndirectIndirectIndirect
Table DataReturn Shipment LineYesIndirectIndirectIndirect
Table DataReturn Receipt HeaderYesIndirectIndirectYes
Table DataReturn Receipt LineYesIndirectIndirectIndirect
Table DataWarehouse EntryYesIndirectIndirectIndirect
Table DataRegistered Invt. Movement Hdr.YesIndirectIndirectYes
Table DataRegistered Invt. Movement LineYesIndirectIndirectIndirect
Table DataCopilot SettingsIndirectIndirectIndirectIndirect
Table DataRecord Set DefinitionYesIndirectIndirectIndirect
Table DataRecord Set TreeYesIndirectIndirectIndirect
Table DataRecord Set BufferYesIndirectIndirectIndirect
Table DataTable Information CacheIndirectIndirectIndirectIndirect
Table DataCompany Size CacheIndirectIndirectIndirectIndirect
Table DataFeature UptakeIndirectIndirectIndirectIndirect
Table DataEmail Connector LogoIndirectIndirectIndirectIndirect
Table DataEmail OutboxIndirectIndirectIndirectIndirect
Table DataSent EmailIndirectIndirectIndirectIndirect
Table DataEmail MessageIndirectIndirectIndirectIndirect
Table DataEmail ErrorIndirectIndirectIndirectIndirect
Table DataEmail RecipientIndirectIndirectIndirectIndirect
Table DataEmail Message AttachmentIndirectIndirectIndirectIndirect
Table DataEmail ScenarioIndirectIndirectIndirectIndirect
Table DataEmail Related RecordIndirectIndirectIndirect
Table DataEmail Scenario AttachmentsIndirectIndirectIndirectIndirect
Table DataEmail Rate LimitIndirectIndirectIndirectIndirect
Table DataEmail AttachmentsIndirectIndirectIndirectIndirect
Table DataEmail View PolicyYesYesYesIndirect
Table DataPlanIndirectIndirectIndirectIndirect
Table DataUser PlanIndirectIndirectIndirectIndirect
Table DataUser LoginIndirectIndirectIndirect
Table DataUser Environment LoginIndirectIndirect
Table DataPlan ConfigurationIndirectIndirectIndirectIndirect
Table DataCustom Permission Set In PlanIndirectIndirectIndirectIndirect
Table DataDefault Permission Set In PlanIndirectIndirectIndirectIndirect
Table DataSecurity GroupIndirectIndirectIndirectIndirect
Table DataCustom User Group In PlanIndirectIndirectIndirectIndirect
Table DataSupport Contact InformationYesIndirectIndirectIndirect
Table DataApplication User SettingsIndirectIndirectIndirect
Table DataDocument Service CacheYesIndirectIndirectIndirect
Table DataPermission Set LinkIndirectIndirectIndirectIndirect
Table DataWord Templates TableIndirectIndirectIndirectIndirect
Table DataWord Template FieldIndirectIndirectIndirectIndirect
Table DataWord Templates Related TableIndirectIndirectIndirectIndirect
Table DataUpgrade Tag BackupIndirectIndirectIndirectIndirect
Table DataUpgrade TagsIndirectIndirectIndirectIndirect
Table DataProduction BOM LineYesIndirect
Table DataSales Planning LineYesIndirectIndirectIndirect
Table DataObjectYesIndirectIndirectIndirect
Table DataPermission SetYesIndirectIndirectIndirect
Table DataPermissionYesIndirectIndirectIndirect
Table DataDateYesIndirectIndirectIndirect
Table DataSessionYesIndirectIndirectIndirect
Table DataDriveYesIndirectIndirectIndirect
Table DataFileYesIndirectIndirectIndirect
Table DataIntegerYesIndirectIndirectIndirect
Table DataTable InformationYesIndirectIndirectIndirect
Table DataSystem ObjectYesIndirectIndirectIndirect
Table DataAllObjYesIndirectIndirectIndirect
Table DataLicense InformationYesIndirectIndirectIndirect
Table DataFieldYesIndirectIndirectIndirect
Table DataLicense PermissionYesIndirectIndirectIndirect
Table DataPermission RangeYesIndirectIndirectIndirect
Table DataWindows LanguageYesIndirectIndirectIndirect
Table DataCode CoverageYesIndirectIndirectIndirect
Table DataSID - Account IDYesIndirectIndirectIndirect
Table DataAllObjWithCaptionYesIndirectIndirectIndirect
Table DataKeyYesIndirectIndirectIndirect
Table DataAdd-inYesIndirectIndirectIndirect
Table DataObject MetadataYesIndirectIndirectIndirect
Table DataChartYesIndirectIndirectIndirect
Table DataUpgrade Blob StorageYesIndirectIndirectIndirect
Table DataReport LayoutYesIndirectIndirectIndirect
Table DataActive SessionYesIndirectIndirectIndirect
Table DataSession EventYesIndirectIndirectIndirect
Table DataServer InstanceYesIndirectIndirectIndirect
Table DataDocument ServiceYesIndirectIndirectIndirect
Table DataDocument Service ScenarioYesIndirectIndirectIndirect
Table DataUser PropertyYesIndirectIndirectIndirect
Table DataDeviceYesIndirectIndirectIndirect
Table DataTable Synch. SetupYesIndirectIndirectIndirect
Table DataTable MetadataYesIndirectIndirectIndirect
Table DataCodeUnit MetadataYesIndirectIndirectIndirect
Table DataPage MetadataYesIndirectIndirectIndirect
Table DataReport MetadataYesIndirectIndirectIndirect
Table DataEvent SubscriptionYesIndirectIndirectIndirect
Table DataIntelligent CloudYesIndirectIndirectIndirect
Table DataNAV App Data ArchiveYesIndirectIndirectIndirect
Table DataNAV App Installed AppYesIndirectIndirectIndirect
Table DataNAV App CapabilitiesYesIndirectIndirectIndirect
Table DataNAV App Object PrerequisitesYesIndirectIndirectIndirect
Table DataTime ZoneYesIndirectIndirectIndirect
Table DataAggregate Permission SetYesIndirectIndirectIndirect
Table DataNAV App Tenant Add-InYesIndirectIndirectIndirect
Table DataIntelligent Cloud StatusYesIndirectIndirectIndirect
Table DataScheduled TaskYesIndirectIndirectIndirect
Table DataOData Edm TypeYesIndirectIndirectIndirect
Table DataMedia SetYesIndirectIndirectIndirect
Table DataMediaYesIndirectIndirectIndirect
Table DataMedia ResourcesYesIndirectIndirectIndirect
Table DataTenant Media SetYesIndirectIndirectIndirect
Table DataTenant MediaYesIndirectIndirectIndirect
Table DataTenant Media ThumbnailsYesIndirectIndirectIndirect
Table DataEntitlement SetYesIndirectIndirectIndirect
Table DataEntitlementYesIndirectIndirectIndirect
Table DataMembership EntitlementYesIndirectIndirectIndirect
Table DataToken CacheYesIndirectIndirectIndirect
Table DataWebhook SubscriptionYesIndirectIndirectIndirect
Table DataPublished ApplicationYesIndirectIndirectIndirect
Table DataApplication Object MetadataYesIndirectIndirectIndirect
Table DataApplication ResourceYesIndirectIndirectIndirect
Table DataApplication DependencyYesIndirectIndirectIndirect
Table DataInstalled ApplicationYesIndirectIndirectIndirect
Table DataDesigned Query ObjYesIndirectIndirectIndirect
Table DataPrivacy NoticeYesYesYesIndirect

The reason for those tables being restricted to indirect, is the license. Thats also the reason why those errors never get noticed during development, because the developer license does not have those indirect restrictions.

And since licensing can change at any time, especcially in the cloud, we can never be sure that this list is finite and wont change.

My solution to the problem.

If you want to, you can watch me build out the rule here:

But long story short: There is simply no way to know which tables absolutely need to have the Permission property set correctly to prevent those runtime errors. Except for just always applying it for all reads, modifies, inserts and deletes.

So I wrote the linter rule to identify those database accesses and to inform the developer that there should be a Permission set. The also handles inherent permissions both set on the table directly as well as the procedure scoped inherent permissions. If those are set, no indirect permissions should be needed. Reports and XMLPorts should also be handled correctly, reports only care about reads, while XMLPorts can also sometime import data, therefore need insert, modify and delete as well.

But all that should be detected correctly by now.

The only thing, that does not get detected, is any database operation done with RecordRef, since there is not really a reliable way to always know at compile time, which record is accessed.

Extension objects

One major point of discussion was the fact that the Permissions property does not exist on pageextensions or tableextensions. I do not have any idea what the reason for this is.

And the linter cop does still inform for table data access on extension objects even though it can not really be fixed in place. The reason for this is, that a runtime error will still occur if you happen to hit one of those 250 tables I listed above, even if you access it on an extension object.

The only solution to this really is to move the code that does the data access out of the extension object into a Codeunit, and set the Permission correctly.

There where discussion on splitting the rule into two rule to make it easier to comply with the “main” rule, and to prevent the need to move tons of code. While I do understand the reasoning behind this, I am still not convinced that this is the right approach. Personally, I would probably disable the rule for large legacy projects alltogether if there is no intend to refactor the entire codebase. At least for the Pipelines I would disable this. In VS Code it could stay active if a developer works on a new feature inside the project.

Is that all?

There is something else. Let me first explain how indirect permissions work:

The idea is that a user does not always need direct permissions RIMD but sometimes might just have indirect permissions rimd. For example: A user might be allowed to work with customers. He can create new ones, edit of course, and also delete customers. Per his permission sets. But this user is not allowed to look at Posted Sales Invoices. So he does not have direct read permissions on the Sales Invoice Header and Lines. Yet, he should be able to view selected information about invoices, maybe some totals or statistics that are placed in a FactBox on the customer card.

He should be able to indirectly read sales invoice information. For this to work the permission set need to have indrect read access specified to Sales Invoice Header and Lines and its crutial that the the Permissions property in code is set correctly, because thats a benefitial part in making the indirect permissions concept work at all.

My Conclusion

It all started with avoiding permissions errors on those tables which are restriced by the users license. But as I learned a bit more and also thought a bit more about indirect permissions, I honestly think that the Permissions propery should always be set in code to cover just all data access operations.

The linter cop is all about clean code, this is one step torwards more clean code. I also disabled that rule for some of my larger/older projects because I can simply not uptake it right now. If you do care about this piece to comply with the permissions model that Business Central has, I think you should activate this rule. Since without setting the permissions, Admins and Users can simply not use indirect permissions.

Thats all, I hope this helps :)