-
Notifications
You must be signed in to change notification settings - Fork 6
plugin sdk 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
Understanding how privileges flow through the Security Center architecture is essential for proper implementation and deployment.
The Directory server is the central authority for privileges. At startup, the Directory discovers privilege files through two methods:
-
Installation directory scanning: The Directory scans its installation directory for
*.privileges.xmlfiles. Each privilege file must have a corresponding.dllfile with the same name in the same directory. IfMyModule.privileges.xmlexists butMyModule.dlldoes not, the privilege file is ignored. -
Registered plugin paths: For each registered plugin (via Windows Registry or
.Plugin.xmlfile), the Directory checks theServerModuleandClientModulepaths. If the registration points toC:\Plugins\MyPlugin.dll, the Directory looks forC:\Plugins\MyPlugin.privileges.xml.
After discovering privilege files, the Directory:
- Parses and stores the privilege definitions in the database
- Provides these definitions to client applications when they connect
- Uses these definitions to validate
IsPrivilegeGranted()calls
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.
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()returnsfalse. - XML file on both: Full functionality. The privilege appears in the UI and validation works.
To implement custom privileges:
- Define privileges in an XML file
- Deploy the XML file to the correct locations
- Check privileges from your code
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
.dllfile with the same base name - If
MyIntegration.privileges.xmlexists, Security Center looks forMyIntegration.dll - If the DLL doesn't exist, the privilege file is ignored
Example: If your DLL is MyIntegration.dll, create MyIntegration.privileges.xml.
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>| 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 |
| 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.
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 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
RequiredLicensesGroupelements, 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 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
RequiredPrivilegeGroupelements use AND logic (every privilege must be granted) - The
RequiredPrivilegeGroupelement 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.
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.
To find appropriate parent privileges:
- Open Config Tool and navigate to System > Users and User Groups > Privileges
- Press Ctrl+Shift+G to display GUIDs next to privilege names
- Explore the privilege hierarchy to find where your privileges logically belong
- 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.
The priority attribute controls display order within the same parent:
- Lower numbers appear first (priority 1 before priority 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) -->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
On each workstation that runs Config Tool or Security Desk, place MODULE-NAME.privileges.xml next to the ClientModule DLL.
Place the privilege file where the Directory server loads custom privileges.
Place MODULE-NAME.privileges.xml next to the ServerModule DLL.
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 placeMODULE-NAME.privileges.xmlin the same folder. - Copy
MODULE-NAME.dllandMODULE-NAME.privileges.xmlinto 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.
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
}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);
}
}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);
}Workspace modules can register privileges programmatically using SecurityManager.RegisterPrivileges(). This method adds privileges to the local application's memory.
When a Workspace module calls RegisterPrivileges():
- The privilege is added to the local privilege dictionary in Config Tool or Security Desk
- The privilege appears in the privilege treeview UI
- Administrators can assign the privilege to users
- The privilege state is saved to the database
- Does not send the privilege definition to the Directory server
- Does not enable
IsPrivilegeGranted()validation (returnsfalsefor unknown privileges) - Does not persist the privilege definition (only the assigned state)
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.
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() 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.
| 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 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()returnsfalsebecause 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.
After deploying the privilege file to the Directory server (see deployment scenarios above):
- Restart the Genetec Server service
- Open Config Tool
- Navigate to System > Users and User Groups
- Select a user or user group
- Click the Privileges tab
- 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.dllandMyModule.privileges.xml)
- Create a test user without administrator privileges
- Assign your custom privilege to the user
- In your code, call
IsPrivilegeGranted()for the custom privilege - Verify it returns
truewhen the privilege is granted - Remove the privilege from the user and verify
IsPrivilegeGranted()returnsfalse
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
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- .NET 8 support
- Overview
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Under the hood
- Troubleshooting