Skip to content

plugin sdk custom privileges

Andre Lafleur edited this page Apr 24, 2026 · 9 revisions

Defining custom privileges

Custom privileges let you control access to your plugin or module features. Administrators can assign these privileges to users or user groups through Config Tool, just like built-in Security Center privileges.

For an overview of how the privilege system works, see About privileges. For information on how privileges interact with partitions, see About partitions.

Custom privileges can be used to control access to:

  • Custom entities: View, modify, add, or delete custom entity types
  • Custom tasks: Access to custom tasks and pages
  • Features: Any custom functionality within your integration

How custom privileges work

Understanding how privileges flow through the Security Center architecture is essential for proper implementation and deployment.

Privilege loading and validation

The Directory server is the central authority for privileges. At startup, the Directory discovers privilege files through two methods:

  1. Installation directory scanning: The Directory scans its installation directory for *.privileges.xml files. Each privilege file must have a corresponding .dll file with the same name in the same directory. If MyModule.privileges.xml exists but MyModule.dll does not, the privilege file is ignored.

  2. Registered plugin paths: For each registered plugin (via Windows Registry or .Plugin.xml file), the Directory checks the ServerModule and ClientModule paths. If the registration points to C:\Plugins\MyPlugin.dll, the Directory looks for C:\Plugins\MyPlugin.privileges.xml.

After discovering privilege files, the Directory:

  1. Parses and stores the privilege definitions in the database
  2. Provides these definitions to client applications when they connect
  3. Uses these definitions to validate IsPrivilegeGranted() calls

UI display vs privilege validation

Client applications use separate sources for displaying privileges in the UI and for validating privilege checks:

  • UI display: Shows privileges from locally loaded XML files and any privileges added via RegisterPrivileges()
  • Privilege validation: IsPrivilegeGranted() only recognizes privileges that the Directory server knows about

This separation means a privilege can appear in the Config Tool UI but still fail validation if the Directory server doesn't have the privilege definition.

Why deployment location matters

Because the Directory server is the authority for privilege validation:

  • XML file on Directory server: The privilege is recognized by IsPrivilegeGranted(). Validation works.
  • XML file on client only: The privilege appears in the UI, but IsPrivilegeGranted() returns false.
  • XML file on both: Full functionality. The privilege appears in the UI and validation works.

Implementing custom privileges

To implement custom privileges:

  1. Define privileges in an XML file
  2. Deploy the XML file to the correct locations
  3. Check privileges from your code

Defining privileges in an XML file

Create a file named {YourAssemblyName}.privileges.xml where {YourAssemblyName} matches your plugin or Workspace module DLL name without the .dll extension.

Security Center discovers privilege files using this pattern:

  • Looks for files named *.privileges.xml
  • Expects a corresponding .dll file with the same base name
  • If MyIntegration.privileges.xml exists, Security Center looks for MyIntegration.dll
  • If the DLL doesn't exist, the privilege file is ignored

Example: If your DLL is MyIntegration.dll, create MyIntegration.privileges.xml.

XML structure

The privilege file has a root ModulePrivileges element containing Resources and Privileges sections. The Resources element is required and must include the fallbackLanguage attribute, even if you do not define custom images.

<?xml version="1.0" encoding="utf-8"?>
<ModulePrivileges>
  <Resources fallbackLanguage="en">
    <!-- Custom Base64 image -->
    <Image name="CustomIcon" type="Base64">iVBORw0KGgoAAAANSUhEUgAA...==</Image>
    <!-- Built-in icon resource -->
    <Image name="ViewIcon" type="SmallImage">Eye</Image>
  </Resources>
  <Privileges>
    <!-- Group privilege (container) -->
    <Privilege
      id="{12345678-1234-1234-1234-123456789ABC}"
      parentId="{38ACC600-4A32-44ff-8DF5-0797D888930B}"
      type="Group"
      description="My Custom Features"
      priority="1"
      image="CustomIcon">
      <Detail type="Embedded">Controls access to custom integration features</Detail>
    </Privilege>

    <!-- Entity privilege - View -->
    <Privilege
      id="{11111111-1111-1111-1111-111111111111}"
      parentId="{12345678-1234-1234-1234-123456789ABC}"
      type="Entity"
      description="View custom entities"
      priority="1"
      image="Eye">
      <Detail type="Embedded">Allows viewing custom entity properties</Detail>
    </Privilege>

    <!-- Entity privilege - Modify (requires View) -->
    <Privilege
      id="{22222222-2222-2222-2222-222222222222}"
      parentId="{12345678-1234-1234-1234-123456789ABC}"
      type="Entity"
      description="Modify custom entities"
      priority="2"
      image="Edit">
      <Detail type="Embedded">Allows modifying custom entity properties</Detail>
      <RequiredPrivilegeGroup>
        <RequiredPrivilege>{11111111-1111-1111-1111-111111111111}</RequiredPrivilege>
      </RequiredPrivilegeGroup>
    </Privilege>

    <!-- Task privilege with localization -->
    <Privilege
      id="{33333333-3333-3333-3333-333333333333}"
      parentId="{12345678-1234-1234-1234-123456789ABC}"
      type="Task"
      description="Access custom report"
      priority="3">
      <Description language="fr" type="Embedded">Accéder au rapport personnalisé</Description>
      <Detail type="Embedded">Allows generating custom reports</Detail>
    </Privilege>
  </Privileges>
</ModulePrivileges>

Privilege attributes

Attribute Required Description
id Yes Unique GUID identifier for the privilege
parentId Yes GUID of the parent privilege for hierarchy
type Yes Privilege type: Group, Task, or Entity
description No Short description shown in privilege lists (can also use child element)
priority No Display order under the same parent (lower numbers appear first)
image Yes Icon name referencing a custom image resource or built-in icon. Use an empty string (image="") for the default icon.
hierarchical No Boolean for entity privileges that apply to hierarchical entities
hidden No Boolean to hide the privilege from the UI

Privilege types

Type Description Displayed in partition treeview Included in privilege computation
Group Organizational container for related privileges Yes No
Task Permission for system-wide functionality like reports or pages No Yes
Entity Permission for entity operations like view, modify, add, delete Yes Yes

Group: Use for organizing related privileges in the Config Tool interface. Groups cannot be used for permission checking. Group privileges that contain no child privileges are automatically hidden from the UI. If you define a Group with no Task or Entity children, it does not appear in Config Tool.

Task: Use for general functionality that applies system-wide, such as viewing reports or accessing configuration pages.

Entity: Use for operations on specific entity types. These privileges can be granted or denied per partition.

Description and detail elements

Descriptions can be specified as attributes or child elements. Child elements support localization:

<Privilege id="{...}" parentId="{...}" type="Task">
  <!-- Primary description with localization -->
  <Description type="Embedded">View custom report</Description>
  <Description language="fr" type="Embedded">Afficher le rapport personnalisé</Description>
  <Description language="es" type="Embedded">Ver informe personalizado</Description>

  <!-- Tooltip detail -->
  <Detail type="Embedded">Allows users to view and generate custom reports</Detail>
</Privilege>

The type attribute for Description and Detail elements:

Type Description
Embedded The text is directly in the XML
Language The text is a resource key for localization lookup
Trademark The text is a trademark resource key

License requirements

License requirements prevent a privilege from being granted when the required licenses are not present. The privilege still appears in the Config Tool UI, but it is denied and cannot be granted. This feature has limited use for custom privileges because each License value must match a Security Center license item identifier.

How license requirements work:

  • Within a single RequiredLicensesGroup, licenses use OR logic (any one license satisfies the group)
  • Between multiple RequiredLicensesGroup elements, AND logic applies (all groups must be satisfied)
  • If license requirements are not met, the privilege still appears in Config Tool, but it is denied and cannot be granted
<Privilege id="{...}" parentId="{...}" type="Task" description="Advanced feature">
  <!-- All groups must be satisfied (AND logic between groups) -->
  <RequiredLicensesGroup>
    <!-- Any license in this group satisfies it (OR logic within group) -->
    <License>iNumOfInputs</License>
    <License>iNumOfOutputs</License>
  </RequiredLicensesGroup>
  <RequiredLicensesGroup>
    <License>SynergisFeaturesEnabled</License>
  </RequiredLicensesGroup>
</Privilege>

In this example, the privilege requires (iNumOfInputs OR iNumOfOutputs) AND SynergisFeaturesEnabled.

Privilege dependencies

Privilege dependencies enforce that certain privileges must be granted before a dependent privilege can be granted. This feature is useful for custom privileges because you can reference your own custom privilege GUIDs.

How privilege dependencies work:

  • A privilege with dependencies cannot be granted unless all required privileges are also granted
  • All required privileges across all RequiredPrivilegeGroup elements use AND logic (every privilege must be granted)
  • The RequiredPrivilegeGroup element is a container for organizational purposes only
  • If a required privilege is denied, the dependent privilege is also denied
  • If a required privilege is undefined, the dependent privilege cannot be granted
<Privilege id="{...}" parentId="{...}" type="Entity" description="Modify advanced settings">
  <!-- All required privileges must be granted -->
  <RequiredPrivilegeGroup>
    <!-- All required privileges must be granted (AND logic) -->
    <RequiredPrivilege>{11111111-1111-1111-1111-111111111111}</RequiredPrivilege>
  </RequiredPrivilegeGroup>
</Privilege>

Common use case: Create a base "View" privilege and require it for "Modify", "Add", and "Delete" privileges:

<!-- Base view privilege -->
<Privilege
  id="{11111111-1111-1111-1111-111111111111}"
  parentId="{12345678-1234-1234-1234-123456789ABC}"
  type="Entity"
  description="View custom entities" />

<!-- Modify requires View -->
<Privilege
  id="{22222222-2222-2222-2222-222222222222}"
  parentId="{12345678-1234-1234-1234-123456789ABC}"
  type="Entity"
  description="Modify custom entities">
  <RequiredPrivilegeGroup>
    <RequiredPrivilege>{11111111-1111-1111-1111-111111111111}</RequiredPrivilege>
  </RequiredPrivilegeGroup>
</Privilege>

With this configuration, administrators cannot grant "Modify custom entities" unless "View custom entities" is also granted.

Images

Security Center supports multiple methods for privilege icons. The image attribute on a privilege element references either a named image resource or a built-in icon name directly.

Method 1: Built-in icon name (simplest)

Reference a built-in icon directly in the image attribute:

<Privilege id="{...}" image="Edit" ... />
<Privilege id="{...}" image="Add" ... />
<Privilege id="{...}" image="Remove" ... />
<Privilege id="{...}" image="Eye" ... />

The Resources section is still required for fallbackLanguage, even when you use a built-in icon name. Use the casing shown in Built-in icon names for consistency.

Method 2: Base64 encoded custom images

Define custom images as Base64-encoded data in the Resources section, then reference by name:

<ModulePrivileges>
  <Resources fallbackLanguage="en">
    <Image name="CustomIcon" type="Base64">iVBORw0KGgoAAAANSUhEUgAA...==</Image>
  </Resources>
  <Privileges>
    <Privilege id="{...}" image="CustomIcon" ... />
  </Privileges>
</ModulePrivileges>

Base64 encoding guidance:

  • For best compatibility, use PNG format
  • Use 16x16 pixels for typical privilege icons
  • Encode the raw binary image data using standard Base64 encoding
  • Keep the encoded string on a single line

Method 3: Built-in icon resource

Define an image resource that maps to a built-in icon, then reference by the resource name:

<ModulePrivileges>
  <Resources fallbackLanguage="en">
    <Image name="MyViewIcon" type="SmallImage">Eye</Image>
    <Image name="MyEditIcon" type="SmallImage">Edit</Image>
  </Resources>
  <Privileges>
    <Privilege id="{...}" image="MyViewIcon" ... />
    <Privilege id="{...}" image="MyEditIcon" ... />
  </Privileges>
</ModulePrivileges>

This method is useful when you want to use descriptive names in your privilege definitions while mapping to built-in icons.

For common built-in icon names, see Built-in icon names.

If an image reference cannot be resolved, Security Center uses the default Privilege icon.

Finding parent privilege GUIDs

To find appropriate parent privileges:

  1. Open Config Tool and navigate to System > Users and User Groups > Privileges
  2. Press Ctrl+Shift+G to display GUIDs next to privilege names
  3. Explore the privilege hierarchy to find where your privileges logically belong
  4. Use the root privilege GUID {38ACC600-4A32-44ff-8DF5-0797D888930B} for top-level groups

Note

If a privilege specifies a parentId that does not exist, it appears under the fallback privileges group in Config Tool. This group always appears at the bottom of the privilege tree, regardless of the priority value set on your privileges. To control where your privileges appear in the hierarchy, always specify a valid parentId.

Priority sorting

The priority attribute controls display order within the same parent:

  1. Lower numbers appear first (priority 1 before priority 2)
  2. Same priority items are sorted alphabetically by description
<Privilege priority="1" description="Add entities" />    <!-- First (alphabetical with View) -->
<Privilege priority="1" description="View entities" />   <!-- Second (alphabetical) -->
<Privilege priority="2" description="Advanced settings" />  <!-- Third (lower priority) -->

Deploying the XML file

To show a custom privilege in Config Tool and make IsPrivilegeGranted() recognize it, deploy the same privilege file on both client workstations and the Directory server.

The privilege file name must match the module DLL name and be in the same folder. Replace MODULE-NAME with your module file base name, such as MyPlugin.

C:\Plugins\MyPlugin\
|-- MyPlugin.dll
`-- MyPlugin.privileges.xml

Client workstations

On each workstation that runs Config Tool or Security Desk, place MODULE-NAME.privileges.xml next to the ClientModule DLL.

Directory server

Place the privilege file where the Directory server loads custom privileges.

Server module present

Place MODULE-NAME.privileges.xml next to the ServerModule DLL.

Workspace module only

The Directory server needs a module path to locate the privilege file. Use one of the following options:

  • Register the module on the Directory server, point the module path to MODULE-NAME.dll, and place MODULE-NAME.privileges.xml in the same folder.
  • Copy MODULE-NAME.dll and MODULE-NAME.privileges.xml into the Directory server installation folder.

For registration instructions, see Deploying plugins and workspace modules.

Important

If privileges appear in Config Tool but IsPrivilegeGranted() always returns false, the privilege file is missing from the Directory server.

Checking privileges in code

Use SecurityManager.IsPrivilegeGranted() to check if a user has a specific privilege. For details on privilege checking and other privilege operations, see About privileges.

In PageDescriptor subclasses:

public override bool HasPrivilege()
{
    return m_sdk.SecurityManager.IsPrivilegeGranted(MyPrivileges.CustomReport);
}

In Workspace module classes:

if (Workspace.Sdk.SecurityManager.IsPrivilegeGranted(MyPrivileges.ViewCustomEntity))
{
    // User has the privilege
}

In Plugin classes:

if (Engine.SecurityManager.IsPrivilegeGranted(MyPrivileges.CustomFeature))
{
    // User has the privilege
}

Managing privilege GUIDs

Create a constants class to manage privilege GUIDs:

public static class MyPrivileges
{
    public static class CustomEntity
    {
        public static readonly Guid GroupId = new Guid("{12345678-1234-1234-1234-123456789ABC}");
        public static readonly Guid ViewId = new Guid("{11111111-1111-1111-1111-111111111111}");
        public static readonly Guid ModifyId = new Guid("{22222222-2222-2222-2222-222222222222}");
        public static readonly Guid AddId = new Guid("{33333333-3333-3333-3333-333333333333}");
        public static readonly Guid DeleteId = new Guid("{44444444-4444-4444-4444-444444444444}");

        public static SdkPrivilege View => new SdkPrivilege(ViewId);
        public static SdkPrivilege Modify => new SdkPrivilege(ModifyId);
        public static SdkPrivilege Add => new SdkPrivilege(AddId);
        public static SdkPrivilege Delete => new SdkPrivilege(DeleteId);
    }

    public static class Tasks
    {
        public static readonly Guid CustomReportId = new Guid("{77777777-7777-7777-7777-777777777777}");

        public static SdkPrivilege CustomReport => new SdkPrivilege(CustomReportId);
    }
}

Assigning privileges to custom entities

When creating custom entity types, assign privileges through CustomEntityTypeDescriptor:

private void CreateCustomEntityType()
{
    var config = (SystemConfiguration)Workspace.Sdk.GetEntity(SystemConfiguration.SystemConfigurationGuid);

    var capabilities = CustomEntityTypeCapabilities.CanBeFederated |
                       CustomEntityTypeCapabilities.IsVisible |
                       CustomEntityTypeCapabilities.CreateDelete;

    var descriptor = new CustomEntityTypeDescriptor(
        MyCustomEntityType.Id,
        "My Custom Entity",
        capabilities,
        new Version(1, 0))
    {
        ViewPrivilege = MyPrivileges.CustomEntity.View,
        ModifyPrivilege = MyPrivileges.CustomEntity.Modify,
        AddPrivilege = MyPrivileges.CustomEntity.Add,
        DeletePrivilege = MyPrivileges.CustomEntity.Delete,
    };

    config.AddOrUpdateCustomEntityType(descriptor);
}

Using RegisterPrivileges in Workspace modules

Workspace modules can register privileges programmatically using SecurityManager.RegisterPrivileges(). This method adds privileges to the local application's memory.

What RegisterPrivileges does

When a Workspace module calls RegisterPrivileges():

  1. The privilege is added to the local privilege dictionary in Config Tool or Security Desk
  2. The privilege appears in the privilege treeview UI
  3. Administrators can assign the privilege to users
  4. The privilege state is saved to the database

What RegisterPrivileges does not do

  • Does not send the privilege definition to the Directory server
  • Does not enable IsPrivilegeGranted() validation (returns false for unknown privileges)
  • Does not persist the privilege definition (only the assigned state)

When to use RegisterPrivileges

RegisterPrivileges() can replace the XML file on the client side only. Use it when:

  • You need to register privileges dynamically at runtime
  • You want to add privileges without deploying an XML file to clients

You still need the XML file on the Directory server for IsPrivilegeGranted() to work.

Example usage

public class MyModule : Module
{
    private static readonly Guid GroupId = new Guid("{C63C0E8C-807D-4652-9877-282F78AA9DDA}");
    private static readonly Guid ViewId = new Guid("{D09AD885-7C3C-4DFC-918B-E7191B43CF33}");

    public override void Load()
    {
        RegisterPrivileges();
    }

    private void RegisterPrivileges()
    {
        var registrations = new List<PrivilegeRegistration>();

        // Create a group
        var group = new PrivilegeRegistration(
            GroupId,
            PrivilegeType.Group,
            "My Custom Privileges");
        group.ParentId = SdkPrivilege.Root;
        group.Details = "Custom privileges for my integration";
        registrations.Add(group);

        // Create a privilege under the group
        var viewPrivilege = new PrivilegeRegistration(
            ViewId,
            PrivilegeType.Entity,
            "View custom data",
            "Allows viewing custom data",
            1,
            null,
            GroupId);
        registrations.Add(viewPrivilege);

        // Register with the SDK
        Workspace.Sdk.SecurityManager.RegisterPrivileges(registrations);
    }

    public override void Unload()
    {
        // Unregister when module unloads
        var privilegeIds = new List<Guid> { GroupId, ViewId };
        Workspace.Sdk.SecurityManager.UnregisterPrivileges(privilegeIds);
    }
}

UnregisterPrivileges limitations

UnregisterPrivileges() only removes privileges that were registered via RegisterPrivileges() or loaded from custom privilege XML files. Built-in Security Center privileges cannot be unregistered. If you attempt to unregister a built-in privilege, the call has no effect.

Comparison: XML file vs RegisterPrivileges

Aspect XML file RegisterPrivileges
Deployment File alongside DLL Code in module
Loaded at Application startup When module loads
Persistence Permanent (file on disk) Session only
License dependencies Supported Not supported
Privilege dependencies Supported Not supported
Localization Built-in support Manual
Directory server awareness Yes (if file is on server) No

For production deployments, use XML files deployed to both client and server machines.

External SDK applications

External SDK applications that use Genetec.Sdk cannot create custom privileges that work system-wide. While you can call RegisterPrivileges(), it only affects the local application's memory:

  • The privilege is not sent to the Directory server
  • Other applications (Config Tool, Security Desk) do not see the privilege
  • IsPrivilegeGranted() returns false because the Directory doesn't know about it
  • The privilege is lost when the application closes

For integrations that require custom privileges, build a plugin or Workspace module instead.

Testing custom privileges

Verify privilege loading

After deploying the privilege file to the Directory server (see deployment scenarios above):

  1. Restart the Genetec Server service
  2. Open Config Tool
  3. Navigate to System > Users and User Groups
  4. Select a user or user group
  5. Click the Privileges tab
  6. Verify your custom privileges appear under the appropriate group

If the privilege does not appear:

  • Verify the privilege file is on the Directory server machine
  • Verify a matching DLL exists in the same directory as the privilege file
  • Check that the DLL and privilege file names match (e.g., MyModule.dll and MyModule.privileges.xml)

Test privilege validation

  1. Create a test user without administrator privileges
  2. Assign your custom privilege to the user
  3. In your code, call IsPrivilegeGranted() for the custom privilege
  4. Verify it returns true when the privilege is granted
  5. Remove the privilege from the user and verify IsPrivilegeGranted() returns false

If IsPrivilegeGranted() always returns false:

  • The privilege file is not on the Directory server, or the Directory cannot discover it
  • See the deployment scenarios above to ensure files are correctly deployed

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Media Gateway

Genetec Web Player

Clone this wiki locally