Permissions & Access Control

The acre Access Control permission system provides fine-grained control over who can do what within your system. This guide explains how to configure, manage, and troubleshoot permissions for users and groups.

Key Concept: Permissions are defined by Operation Rights that specify which Actions (Read, Create, Update, Delete, etc.) are allowed or denied for specific System Items (object types like Person, Controller, AccessLevel).


Quick Start: Common Permission Scenarios

Before diving into details, here are the most common, very basic, permission patterns:

Scenario System Actions Grant Type
Read-Only User All Types Read Grant
Cardholder Admin Person Read, Create, Update, Delete Grant
Hardware Technician Controller, Reader, Door Read, Create, Update Grant
Event Viewer EventMessage Read Grant
Full Administrator All Types All Actions Grant

Permission Architecture

Operation Rights

Permissions are configured using OperationRightInfo objects with these key properties:

Property Type Description
System string The object type(s) this permission applies to
Actions string[] What operations are allowed/denied
GrantType GrantType Grant (allow) or Deny (block)
Enabled bool Whether this permission is active
Tags string[] Optional: limit to tagged objects only
// Example: Create a permission allowing read access to People
var readPeoplePermission = new OperationRightInfo
{
    CommonName = "Read People",
    System = "Person",
    Actions = new[] { "Read" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

System Items

System Items define which object types a permission applies to. These names match how objects are stored in MongoDB (without the “Info” suffix).

Common System Items

System Item Description Example Use Case
All Types Everything (BaseInfo) Full administrator access
Person Cardholders Cardholder management
AccessLevel Access rules Access configuration
Schedule Time schedules Schedule management
Controller All controllers Hardware technicians
MercuryController Mercury controllers only Vendor-specific access
EngageReader Allegion Engage locks Wireless lock management
Door All door types Door operations
EventMessage System events Event viewing/reporting
User System users User administration
Group Permission groups Group management
Folder Organizational folders Folder organization
Instance Instance management Instance administrators

Complete System Item List

The following system items are available:

AccessLevel, AlarmAction, AlarmDefinition, AreaAssignment, Attendee, 
AwsKinesisAlarmAction, AwsKinesisEventDestination, AwsSnsAlarmAction, 
AwsSnsEventDestination, AwsSqsAlarmAction, AwsSqsEventDestination, 
BadgeType, BadgeTypeField, BoschArea, BoschAuthorityLevel, BoschDevice, 
BoschDoor, BoschOutput, BoschPanel, BoschPoint, BoschService, BoschSked, 
BoschUser, Camera, CardFormat, Controller, Downstream, 
ElevatorAccessLevel, EngageIpGateway, EngageReader, EngageSite, 
EventMessage, ExecuteProcedureAction, Floorplan, FloorplanItem, Folder, 
Generic, Group, HgProcedure, HgTrigger, Holiday, ImagePointer, 
InputScanner, Instance, InstanceSettings, KeepObject, KoneCabinAccess, 
LdapAgent, License, LocalArea, MercuryInput, MercuryOutput, 
MercuryReader, MessageTemplate, NotificationAction, Person, Schedule, 
ServiceContainer, User, Visit, VisitorManagementSettings, WebHook

Tip: Use All Types (which maps to BaseInfo) to apply permissions to all object types at once.


Actions

Actions define what operations a user can perform on the specified system items.

Standard Actions

Action When Required Description
Read Viewing objects Required to see any object in lists or details
Create Adding new objects Required to POST new objects
Update Modifying objects Required to PUT changes to existing objects
Delete Removing objects Required to DELETE objects
Publish Creating events Required to publish custom events
Linking Connecting objects Required to add/remove object relationships

Linking Actions (Advanced)

Linking permissions control object relationships:

// Allow linking any object type to Access Levels
var linkingPermission = new OperationRightInfo
{
    CommonName = "Link to Access Levels",
    System = "AccessLevel",
    Actions = new[] { "Linking" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

// Use wildcard * for system to link any type
var wildcardLinking = new OperationRightInfo
{
    CommonName = "Universal Linking",
    System = "*",
    Actions = new[] { "Linking" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

Advanced/Custom Actions

You can define custom action names for specialized permissions:

// Custom action for a specific operation
var customAction = new OperationRightInfo
{
    CommonName = "Allow Card Enrollment",
    System = "Person",
    Actions = new[] { "EnrollCard" },  // Custom action name
    GrantType = GrantTypes.Grant,
    Enabled = true
};

Grant Types

Grant Type Behavior Precedence
Grant Allows the specified action Lower priority
Deny Blocks the specified action Higher priority

Critical Rule: Deny always wins. If a user has both a Grant and Deny for the same system/action, the Deny takes precedence.

Example: Deny Overrides Grant

// User is in two groups with conflicting permissions:

// Group A: Grants delete on all types
var groupAPermission = new OperationRightInfo
{
    System = "All Types",
    Actions = new[] { "Delete" },
    GrantType = GrantTypes.Grant,  // Allow delete
    Enabled = true
};

// Group B: Denies delete on Person
var groupBPermission = new OperationRightInfo
{
    System = "Person",
    Actions = new[] { "Delete" },
    GrantType = GrantTypes.Deny,  // Block delete
    Enabled = true
};

// Result: User CAN delete Controllers, Readers, etc.
// Result: User CANNOT delete People (Deny wins)

Enabled Flag

The Enabled property provides a quick way to toggle permissions without deleting them:

// Get the operation right by href
var permissionHref = $"/api/f/{instanceKey}/operationrights/{permissionKey}";
var tempPermission = await client.GetByHrefAsync<OperationRightInfo>(permissionHref);

// Temporarily elevate permissions
tempPermission.Enabled = true;  // Activate
await client.UpdateOperationRightAsync(tempPermission);

// ... user performs elevated task ...

// Deactivate
tempPermission.Enabled = false;
await client.UpdateOperationRightAsync(tempPermission);

Use Cases:

  • Temporary access elevation during maintenance
  • Emergency access during incidents
  • Testing permission configurations without deletion

Tag-Based Permissions

Limit permissions to objects with specific tags:

// Only allow access to "vip" tagged people
var vipOnlyPermission = new OperationRightInfo
{
    CommonName = "VIP Cardholder Management",
    System = "Person",
    Actions = new[] { "Read", "Update" },
    GrantType = GrantTypes.Grant,
    Enabled = true,
    Tags = new[] { "vip" }  // Only applies to tagged objects - must be lowercase
};

How It Works:

  1. Create objects with tags (lowercase, no spaces): person.Tags = new[] { "vip" };
  2. Create permissions with matching tags
  3. Users with tagged permissions can only access matching objects

Inheritance

Permissions respect the object type hierarchy:

ControllerInfo (base)
├── MercuryController
│   ├── Ep1501Info
│   ├── Lp1502Info
│   └── Lp4502Info
├── EngageSite
└── BoschPanel

Example: A permission on Controller automatically applies to all derived types (MercuryController, EngageSite, BoschPanel, etc.):

// This grants access to ALL controller types
var allControllersPermission = new OperationRightInfo
{
    System = "Controller",  // Base type
    Actions = new[] { "Read", "Create", "Update" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

// This grants access ONLY to Mercury controllers
var mercuryOnlyPermission = new OperationRightInfo
{
    System = "MercuryController",  // Specific type
    Actions = new[] { "Read", "Create", "Update" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

Complete Examples

Example 1: Read-Only Event Viewer

// Create a group for event viewers
var eventViewer = new GroupInfo
{
    CommonName = "Event Viewers"
};
var group = await client.AddGroupAsync(instance, eventViewer);

// Create operation right for viewing events
var viewEventsPermission = new OperationRightInfo
{
    CommonName = "View Events Only",
    System = "EventMessage",
    Actions = new[] { "Read" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

// Add the operation right - requires appCode and credentialKey
// These associate the right with the application and user/group credential
await client.AddOperationRightsAsync(instance, "YourAppCode", group.Key, viewEventsPermission);

Example 2: Cardholder Administrator

var cardholderAdminPermissions = new[]
{
    new OperationRightInfo
    {
        CommonName = "Manage People",
        System = "Person",
        Actions = new[] { "Read", "Create", "Update", "Delete" },
        GrantType = GrantTypes.Grant,
        Enabled = true
    },
    new OperationRightInfo
    {
        CommonName = "View Access Levels",
        System = "AccessLevel",
        Actions = new[] { "Read" },
        GrantType = GrantTypes.Grant,
        Enabled = true
    },
    new OperationRightInfo
    {
        CommonName = "View Schedules",
        System = "Schedule",
        Actions = new[] { "Read" },
        GrantType = GrantTypes.Grant,
        Enabled = true
    }
};

// Get the admin group by searching for it
var adminGroup = (await client.SearchAsync(instance, "{\"_t\":\"Group\",\"CommonName\":\"Admins\"}"))
    .OfType<GroupInfo>().FirstOrDefault();

// Add each permission for the group
foreach (var permission in cardholderAdminPermissions)
{
    await client.AddOperationRightsAsync(instance, "YourAppCode", adminGroup.Key, permission);
}

Example 3: Hardware Technician (No Cardholder Access)

// Grant hardware access
var hardwarePermissions = new[]
{
    new OperationRightInfo
    {
        System = "Controller",
        Actions = new[] { "Read", "Create", "Update" },
        GrantType = GrantTypes.Grant
    },
    new OperationRightInfo
    {
        System = "Reader",
        Actions = new[] { "Read", "Create", "Update" },
        GrantType = GrantTypes.Grant
    },
    new OperationRightInfo
    {
        System = "Door",
        Actions = new[] { "Read", "Create", "Update" },
        GrantType = GrantTypes.Grant
    }
};

// Explicitly deny access to cardholder data
var denyCardholders = new OperationRightInfo
{
    System = "Person",
    Actions = new[] { "Read", "Create", "Update", "Delete" },
    GrantType = GrantTypes.Deny,  // Explicit deny
    Enabled = true
};

Best Practices

✅ DO

  1. Use Groups - Assign permissions to groups, then add users to groups
  2. Start Restrictive - Grant minimum necessary permissions, add more as needed
  3. Use Deny Sparingly - Only use Deny to override inherited grants
  4. Document Permissions - Use meaningful CommonName values
  5. Test Permission Changes - Verify configured permissions before assigning to real users
  6. Use Tags for Segmentation - Tag objects and permissions for departmental access

❌ DON’T

  1. Don’t Forget Linking - Object relationships require Linking permission
  2. Don’t Ignore Inheritance - Permissions on base types affect derived types

Troubleshooting

“Permissions Error”

Cause: User lacks required permission for the operation.

Debug:

// Get user by href
var userHref = $"/api/f/{instanceKey}/users/{userId}";
var user = await client.GetByHrefAsync<UserInfo>(userHref);

// Get all operation rights for the instance
var allRights = await client.GetOperationRightsAsync(instance);

// Filter to rights associated with this user's credential
var userRights = allRights.Where(r => 
    r.ObjectLinks.Any(ol => ol.Relation == "Credential" && ol.LinkedObjectKey == user.Key));

foreach (var perm in userRights)
{
    Console.WriteLine($"{perm.System}: {string.Join(", ", perm.Actions)} ({perm.GrantType})");
}

Permission Granted But Still Denied

Possible Causes:

  1. A Deny permission overrides the Grant
  2. Permission is not Enabled
  3. Tag mismatch (permission has tags, object doesn’t match)
  4. Wrong System type (e.g., MercuryController vs Controller)
  5. Missing Linking requirement on the permission

New User Can’t See Anything

Solution: Ensure user is in a group with at least Read permission on required types:

var minimumReadPermission = new OperationRightInfo
{
    CommonName = "Basic Read Access",
    System = "All Types",
    Actions = new[] { "Read" },
    GrantType = GrantTypes.Grant,
    Enabled = true
};

A user must be able to Read the Instance they are in, as well as the InstanceSetting.


API Reference

Method Description
GetOperationRightsAsync(folder) Get all operation rights in a folder
AddOperationRightsAsync(folder, appCode, credentialKey, item, appliesTo) Create a new operation right
UpdateOperationRightsAsync(item) Update an operation right
DeleteOperationRightsAsync(item) Delete an operation right
VerifyOperationRightsAsync(folder, request) Verify operation rights for objects

Note: Operation rights are associated with users/groups through the credentialKey parameter when creating them. The appCode identifies the application context.