Skip to content

Permissions Reference

This page is the complete permission catalogue for the solution: every permission constant, its exact string value, the PermissionDefinitionProvider that registers it, and the role(s) that receive it by default at seed time.

For the concepts — how [GrantInRoles] works, how the role hierarchy rolls grants up, how to define a new permission, and how to enforce one with [Authorize] / IAuthorizationService — see Authorization. This page is the lookup table; that page is the how-to.

Where things live

Permission definitions (the constants and the providers) live in the *.Application.Contracts projects. The grant logic that maps permissions onto roles lives in src/Cargonerds.Domain (RolesDataSeedContributor). Enforcement lives in the *.Application services. Adding a permission constant is not enough for a role to receive it — see the seeding section.

Permission groups

Permissions are defined as const string values in *Permissions classes and registered with ABP in the matching *PermissionDefinitionProvider. There are exactly three project-owned groups, one per layer/module:

Group Constants class Provider Project
Cargonerds CargonerdsPermissions CargonerdsPermissionDefinitionProvider src/Cargonerds.Application.Contracts/Permissions/
Hub HubPermissions HubPermissionDefinitionProvider modules/hub/src/Hub.Application.Contracts/Permissions/
Pricing PricingPermissions PricingPermissionDefinitionProvider modules/pricing/src/Pricing.Application.Contracts/Permissions/

The group name itself is a constant on each class (GroupName): "Cargonerds", "Hub", "Pricing". The Cargonerds provider additionally extends the built-in ABP Identity and CMS Kit groups (see Cross-module additions) — those permissions live under ABP's groups in the management UI, not under Cargonerds.

flowchart LR
    subgraph Contracts["*.Application.Contracts (definition)"]
        HP["HubPermissions<br/>+ [GrantInRoles]"]
        CP["CargonerdsPermissions<br/>+ [GrantInRoles]"]
        PP["PricingPermissions<br/>+ [GrantInRoles]"]
        HPP[HubPermissionDefinitionProvider]
        CPP[CargonerdsPermissionDefinitionProvider]
        PPP[PricingPermissionDefinitionProvider]
        HP --> HPP
        CP --> CPP
        PP --> PPP
    end
    subgraph Domain["Cargonerds.Domain (seeding)"]
        RDS["RolesDataSeedContributor<br/>reflects over all 3 classes"]
    end
    HPP & CPP & PPP -->|register tree → mgmt UI| UI[(Permission tree<br/>per group)]
    HP & CP & PP -->|read by reflection at seed time| RDS
    RDS -->|SetForRoleAsync| Grants[(Role → permission<br/>grants)]

Tree shape and default grants are independent

The parent/child structure built by a provider (AddPermission / AddChild) only drives the permission-management UI — checking a parent suggests its children. It does not grant anything. Whether a role actually receives a permission is decided solely by [GrantInRoles] + RolesDataSeedContributor. The two can legitimately disagree — e.g. Hub.Shipment.Create is a child of Shipment.View in the tree but carries no [GrantInRoles], so seeding grants it to no role.

The role hierarchy

Roles and their order (lowest → highest privilege) are defined in RoleConsts (modules/hub/src/Hub.Domain.Shared/Consts/RoleConsts.cs):

DefaultCustomer → Operator → AccountOwner → LocalRealtimeAdmin → SuperUser
public static class RoleConsts
{
    public const string Admin               = "admin";           // built-in ABP admin role
    public const string DefaultCustomer     = nameof(DefaultCustomer);
    public const string Operator            = nameof(Operator);
    public const string AccountOwner        = nameof(AccountOwner);
    public const string LocalRealtimeAdmin  = nameof(LocalRealtimeAdmin);
    public const string SuperUser           = nameof(SuperUser);

    public static readonly string[] Hierarchy =
    [
        DefaultCustomer, Operator, AccountOwner, LocalRealtimeAdmin, SuperUser,
    ];
}

DefaultCustomer is marked as the default role at seed time (new users get it automatically). The built-in ABP admin role exists but is not part of Hierarchy — it is not created by RolesDataSeedContributor and is excluded from the inheritance roll-up.

How default roles are resolved

[GrantInRoles] is a custom attribute (Hub.Attributes.GrantInRolesAttribute, in modules/hub/src/Hub.Domain.Shared/Attributes/). By default (Inherit = true) a permission granted to a role is also granted to every higher role in the hierarchy; Inherit = false pins it to exactly the listed role(s). The roll-up lives in RolesDataSeedContributor.ResolveRoles:

private static string[] ResolveRoles(string[] roles, bool inherit)
{
    if (!inherit) return roles;

    var result = new HashSet<string>();
    foreach (var role in roles)
    {
        var index = Array.IndexOf(RoleConsts.Hierarchy, role);
        if (index < 0) { result.Add(role); continue; }   // role outside hierarchy → as-is
        for (var i = index; i < RoleConsts.Hierarchy.Length; i++)
            result.Add(RoleConsts.Hierarchy[i]);          // this role and every higher one
    }
    return result.ToArray();
}

Throughout the tables below, the "Annotated role" column shows the role written in the attribute; the "Effective roles" column shows the full set after the upward roll-up. With the hierarchy above, the resolution is always one of:

[GrantInRoles(...)] annotation Effective roles (after roll-up)
DefaultCustomer DefaultCustomer, Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
LocalRealtimeAdmin LocalRealtimeAdmin, SuperUser
SuperUser SuperUser
DefaultCustomer, Inherit = false DefaultCustomer (only)
(no [GrantInRoles]) none — granted to no role by seeding

Grants apply only at seed time

Adding, removing, or changing a [GrantInRoles] attribute has no effect until the seeder runs again — re-run the DbMigrator. A constant with no [GrantInRoles] is granted to no role by seeding (it can still be granted manually via the permission-management UI). Because inheritance is upward and on by default, annotating a permission for a low role silently grants it to every higher role too.

Hub group

Source: modules/hub/src/Hub.Application.Contracts/Permissions/HubPermissions.cs + HubPermissionDefinitionProvider.cs. Group name constant HubPermissions.GroupName = "Hub".

Group-header constants are themselves granted

Several area "group" constants (Shipment.ShipmentGroup, Orders.OrdersGroup, Document.DocumentGroup, Invoice.InvoiceGroup, Organization.OrganizationGroup, Insights.InsightsGroup, Reports.ReportsGroup, CodeEntities.Default, Notification.Default) carry their own [GrantInRoles]. They are listed below alongside the actions. Note that the provider registers Shipment/Orders/Document/Invoice/Organization using the .View constant as the parent node, while Insights/Reports use the *Group constant as the parent.

Permission constant Value Annotated role Effective roles
Shipment.ShipmentGroup Hub.Shipment DefaultCustomer all roles
Shipment.View Hub.Shipment.View DefaultCustomer all roles
Shipment.Create Hub.Shipment.Create none (seed)
Shipment.Edit Hub.Shipment.Edit none (seed)
Shipment.Delete Hub.Shipment.Delete none (seed)
Orders.OrdersGroup Hub.Orders DefaultCustomer all roles
Orders.View Hub.Orders.View DefaultCustomer all roles
Orders.Create Hub.Orders.Create none (seed)
Orders.Edit Hub.Orders.Edit none (seed)
Orders.Delete Hub.Orders.Delete none (seed)
Document.DocumentGroup Hub.Document DefaultCustomer all roles
Document.View Hub.Document.View DefaultCustomer all roles
Document.Create Hub.Document.Create none (seed)
Document.Delete Hub.Document.Delete none (seed)
Invoice.InvoiceGroup Hub.Invoice DefaultCustomer all roles
Invoice.View Hub.Invoice.View DefaultCustomer all roles
Invoice.Create Hub.Invoice.Create none (seed)
Invoice.Delete Hub.Invoice.Delete none (seed)
Organization.OrganizationGroup Hub.Organization DefaultCustomer all roles
Organization.View Hub.Organization.View DefaultCustomer all roles
Insights.InsightsGroup Hub.Insights SuperUser SuperUser
Insights.View Hub.Insights.View SuperUser SuperUser
Insights.ViewFinancial Hub.Insights.ViewFinancial SuperUser SuperUser
Insights.MyInsightsView Hub.Insights.MyInsights.View SuperUser SuperUser
Insights.MyInsightsEdit Hub.Insights.MyInsights.Edit SuperUser SuperUser
Reports.ReportsGroup Hub.Reports SuperUser SuperUser
Reports.View Hub.Reports.View SuperUser SuperUser
Reports.Create Hub.Reports.Create SuperUser SuperUser
RealtimeAdmin.Default Hub.RealtimeAdmin Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
InternalAdmin.Default Hub.InternalAdmin none (seed)
Notification.Default Hub.Notification DefaultCustomer all roles
Notification.Internal Hub.Notification.Internal Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
CodeEntities.Default Hub.CodeEntities DefaultCustomer all roles
CodeEntities.View Hub.CodeEntities.View DefaultCustomer all roles
CodeEntities.Create Hub.CodeEntities.Create SuperUser SuperUser
CodeEntities.Edit Hub.CodeEntities.Edit SuperUser SuperUser

Hub.RealtimeAdmin is duplicated in the frontend

RealtimeAdmin.Default gates the admin page in the Next.js frontend. A source comment warns that "the value needs to be updated in frontend manually" — the literal string is referenced in the frontend and is not imported from the backend constant. Renaming it requires a matching frontend change. See Realtime Frontend.

Hub.InternalAdmin is surfaced under the Cargonerds group

InternalAdmin.Default is declared in HubPermissions but registered into the Cargonerds group by CargonerdsPermissionDefinitionProvider (myGroup.AddPermission(HubPermissions.InternalAdmin.Default, ...)). It carries no [GrantInRoles], so no role receives it by seeding.

Cargonerds group

Source: src/Cargonerds.Application.Contracts/Permissions/CargonerdsPermissions.cs + CargonerdsPermissionDefinitionProvider.cs. Group name constant CargonerdsPermissions.GroupName = "Cargonerds".

This is the host layer: API-key management, dashboard tiles, and the custom-script setting. Several constants here alias ABP Identity permission strings purely so a [GrantInRoles] attribute can be attached for the seeder (see Cross-module additions).

Own permissions (registered under the Cargonerds group)

Permission constant Value Annotated role Effective roles
Settings.CustomScript Cargonerds.Settings.CustomScript SuperUser SuperUser
Dashboard.Host Cargonerds.Dashboard.Host none (seed) — Host side (MultiTenancySides.Host)
Dashboard.Tenant Cargonerds.Dashboard.Tenant none (seed) — Tenant side (MultiTenancySides.Tenant)
ApiKeys.Default Cargonerds.ApiKeys none (seed)
ApiKeys.Create Cargonerds.ApiKeys.Create none (seed)
ApiKeys.Edit Cargonerds.ApiKeys.Edit none (seed)
ApiKeys.Delete Cargonerds.ApiKeys.Delete none (seed)
ApiKeys.ManagePermissions Cargonerds.ApiKeys.ManagePermissions none (seed)
ApiKeys.ManageAll Cargonerds.ApiKeys.ManageAll SuperUser SuperUser — manage any user's keys

Dashboard.Host / Dashboard.Tenant are multi-tenancy-scoped

They are the only permissions in the catalogue passed a MultiTenancySides argument (Host / Tenant), so each is only visible/grantable on the matching side of the multi-tenant boundary.

Cross-module additions

CargonerdsPermissionDefinitionProvider does not only build its own group — it augments built-in ABP groups. These permissions appear under ABP's Identity and CMS Kit groups in the management UI, not under Cargonerds, but they still carry [GrantInRoles] and are seeded by the same contributor. The Users.* and OrganizationUnits.* constants alias ABP's own IdentityPermissions.* strings.

Permission constant Value Annotated role Effective roles
Users.Default AbpIdentity.Users Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Users.ViewProfile AbpIdentity.Users.ViewProfile AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Users.SendPasswordMail AbpIdentity.Users.SendPasswordMail AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Users.SendWelcomeMail AbpIdentity.Users.SendWelcomeMail AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Users.EditEmail AbpIdentity.Users.EditEmail LocalRealtimeAdmin LocalRealtimeAdmin, SuperUser
Users.Create AbpIdentity.Users.Create AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Users.Update AbpIdentity.Users.Update AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Users.Delete AbpIdentity.Users.Delete SuperUser SuperUser
Users.ManagePermissions AbpIdentity.Users.ManagePermissions SuperUser SuperUser
Users.ManageRoles AbpIdentity.Users.ManageRoles SuperUser SuperUser
OrganizationUnits.Default AbpIdentity.OrganizationUnits Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
OrganizationUnits.ManageUsers AbpIdentity.OrganizationUnits.ManageUsers LocalRealtimeAdmin LocalRealtimeAdmin, SuperUser
OrganizationUnits.ManageOU AbpIdentity.OrganizationUnits.ManageOU LocalRealtimeAdmin LocalRealtimeAdmin, SuperUser
OrganizationUnits.ManageRoles AbpIdentity.OrganizationUnits.ManageRoles LocalRealtimeAdmin LocalRealtimeAdmin, SuperUser
OrganizationUnits.Export AbpIdentity.OrganizationUnits.Export SuperUser SuperUser
OrganizationUnits.AdvancedManagement AbpIdentity.OrganizationUnits.AdvancedManagement none — [GrantInRoles] commented out (feature unfinished)
Comments.UpdateStatus CmsKit.UpdateStatus Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser

Unguarded CMS Kit group lookup

The Users / OrganizationUnits augmentation guards with GetPermissionOrNull(...) != null, but the CMS Kit comment group is fetched with context.GetGroup(CmsKitPermissions.GroupName) directly. If the CMS Kit module were ever removed from the dependency graph, that call would throw at application startup.

Commented-out permissions

CargonerdsPermissions also contains a fully commented-out InternalUsers block and a commented Users.Impersonation alias — "the feature doesn't exist, will be enabled once the feature is added." They are not registered and not seeded; do not rely on them.

Pricing group

Source: modules/pricing/src/Pricing.Application.Contracts/Permissions/PricingPermissions.cs + PricingPermissionDefinitionProvider.cs. Group name constant PricingPermissions.GroupName = "Pricing".

Permission constant Value Annotated role Effective roles
Quotation.QuotationGroup Pricing.Quotation DefaultCustomer all roles
Quotation.View Pricing.Quotation.View DefaultCustomer all roles
Quotation.Add Pricing.Quotation.Add DefaultCustomer all roles
Quotation.Edit Pricing.Quotation.Edit AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Quotation.Delete Pricing.Quotation.Delete AccountOwner AccountOwner, LocalRealtimeAdmin, SuperUser
Quotation.Admin Pricing.Quotation.Admin Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Quotation.QuoteRequest Pricing.Quotation.QuoteRequest DefaultCustomer (Inherit = false) DefaultCustomer only
Offer.OfferGroup Pricing.Offer Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Offer.Add Pricing.Offer.Add Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Offer.Edit Pricing.Offer.Edit Operator Operator, AccountOwner, LocalRealtimeAdmin, SuperUser
Offer.Delete Pricing.Offer.Delete none (seed)

Offer is nested under Quotation in the tree

In the provider, the Offer group is added as a child of the Quotation permission (var offer = quotation.AddChild(PricingPermissions.Offer.OfferGroup, ...)), not directly under the Pricing group. This only affects the management-UI tree shape — the seeded grants are still determined by each constant's [GrantInRoles].

QuoteRequest is the solution's only Inherit = false

It is pinned to exactly DefaultCustomer and is deliberately not rolled up to higher roles — the customer-facing "request a quote" action that internal/admin roles intentionally do not get via inheritance.

Defining a permission

A permission is just a string constant, organised into nested static classes so a child's value encodes its parent. Add the constant (with a [GrantInRoles] attribute if a role should receive it by default), then register it in the matching provider with a localized display name. Example, the Hub shipment area:

// modules/hub/src/Hub.Application.Contracts/Permissions/HubPermissions.cs
public static class Shipment
{
    [GrantInRoles(RoleConsts.DefaultCustomer)]
    public const string ShipmentGroup = GroupName + ".Shipment";

    [GrantInRoles(RoleConsts.DefaultCustomer)]
    public const string View   = ShipmentGroup + ".View";
    public const string Create = ShipmentGroup + ".Create";   // no role by seeding
    public const string Edit   = ShipmentGroup + ".Edit";
    public const string Delete = ShipmentGroup + ".Delete";
}
// modules/hub/src/Hub.Application.Contracts/Permissions/HubPermissionDefinitionProvider.cs
public override void Define(IPermissionDefinitionContext context)
{
    var hubGroup = context.AddGroup(HubPermissions.GroupName, L("Permission:Hub"));

    var shipment = hubGroup.AddPermission(HubPermissions.Shipment.View, L("Permission:Shipment:View"));
    shipment.AddChild(HubPermissions.Shipment.Create, L("Permission:Shipment:Create"));
    shipment.AddChild(HubPermissions.Shipment.Edit,   L("Permission:Shipment:Edit"));
    shipment.AddChild(HubPermissions.Shipment.Delete, L("Permission:Shipment:Delete"));
}

private static LocalizableString L(string name) => LocalizableString.Create<HubResource>(name);

Each *Permissions class (Hub and Pricing) exposes a reflection-based GetAll() that enumerates every permission string in the group via ReflectionHelper.GetPublicConstantsRecursively:

public static string[] GetAll() =>
    ReflectionHelper.GetPublicConstantsRecursively(typeof(HubPermissions));

Localization keys

Display names are localized through each module's resource (L(...)LocalizableString.Create<HubResource> / <CargonerdsResource> / <PricingResource>). The keys follow the Permission:Area:Action convention (e.g. Permission:Shipment:View). See Localization.

Checking a permission in code

ABP registers every defined permission name as an ASP.NET Core authorization policy, so [Authorize("Perm")] and [Authorize(Policy = "Perm")] are equivalent (both appear in the codebase). Always use the constants rather than string literals:

// Declarative — hard gate on a method
[Authorize(HubPermissions.Shipment.View)]
public async Task<ShipmentDto> GetAsync(Guid id) { /* ... */ }
// Imperative — conditional shaping (returns bool, does NOT throw)
var canSeeInvoices = await AuthorizationService.IsGrantedAnyAsync(HubPermissions.Invoice.View);

The custom read/list base HubEntityBaseService enforces reads through a ReadPermission override (e.g. ShipmentAppService.ReadPermission => HubPermissions.Shipment.View) rather than [Authorize]. The full mechanics of declarative vs. imperative enforcement, the ReadPermission hook, and permission-gated query includes are documented in Authorization.

API keys: permissions capped by the owner

The custom API-key authentication subsystem participates in the same permission system rather than bypassing it. ApiKeyPermissionValueProvider (provider name "AK") only activates for api-key principals and grants a permission only if (a) some other provider — i.e. the owner's roles/grants — also grants it, and (b) the key itself carries the "AK" grant. The guarantee: a key can never exceed its owner's permissions. Key management is itself gated by CargonerdsPermissions.ApiKeys.* (with ManageAll for admin scope). Full details: API Authentication.

Gotchas

Common pitfalls

  • Tree shape ≠ grants. The parent/child structure in a PermissionDefinitionProvider drives the management UI only. Actual role grants come from [GrantInRoles] + the seeder.
  • Grants are seed-time only. Changing a [GrantInRoles] attribute requires re-running the DbMigrator to take effect.
  • Inheritance is upward and on by default. [GrantInRoles(lowRole)] grants to that role and every higher role. Use Inherit = false to pin (as Pricing's QuoteRequest does).
  • The admin role is not in Hierarchy. It is not created or roll-up-targeted by RolesDataSeedContributor; only the five hierarchy roles are.
  • Many actions are intentionally ungranted by seeding (e.g. all *.Create/Edit/Delete on Shipment/Orders/Document/Invoice, all ApiKeys.* except ManageAll, Dashboard.*, InternalAdmin, Offer.Delete). They have no [GrantInRoles] and must be granted manually through the management UI, or are reachable via roles assigned out of band.
  • OrganizationUnits.AdvancedManagement, user Impersonation, and the whole InternalUsers block are deliberately ungranted — their [GrantInRoles] is commented out because the backing feature/logic doesn't exist yet.
  • Hub.RealtimeAdmin's value is duplicated in the frontend (per the source comment); changing the string requires a matching frontend update.
  • Hub.InternalAdmin lives under the Cargonerds group, not Hub, despite being declared in HubPermissions.
  • Unguarded CMS Kit group lookup in CargonerdsPermissionDefinitionProvider would throw at startup if the CMS Kit module were removed.
  • [Authorize(x)] and [Authorize(Policy = x)] are identical in ABP (permission name = policy name). Don't read meaning into the difference.

See also