Skip to content

plugin sdk restricted configuration

Andre Lafleur edited this page Dec 12, 2025 · 1 revision

About plugin restricted configuration

The RestrictedConfiguration API provides secure storage for plugin credentials and admin-only configuration. This is separate from SpecificConfiguration and has stronger access controls.

Overview

Role.RestrictedConfiguration
├── AdminConfigXml        (Admin-only XML configuration)
└── Private Values        (Secure credential storage - SecureString)
    ├── SetPrivateValue()
    ├── GetPrivateValue()
    ├── TryGetPrivateValue()
    └── DeletePrivateValue()

Key differences from SpecificConfiguration:

Feature SpecificConfiguration RestrictedConfiguration
Purpose General plugin settings Credentials and admin config
Format String (typically JSON) XML (AdminConfigXml), SecureString (Private Values)
Access Anyone with role access Plugin itself or administrators only
Storage Plaintext in database Plaintext (AdminConfigXml), SecureString (Private Values)
Use Case URLs, timeouts, options Admin-only settings, passwords, API keys, certificates

When to Use

Use RestrictedConfiguration for:

  • ✅ Passwords and API keys
  • ✅ Authentication tokens
  • ✅ Certificates and private keys
  • ✅ Admin-only configuration
  • ✅ Sensitive connection strings

Use SpecificConfiguration for:

  • ✅ Server URLs and endpoints
  • ✅ Polling intervals and timeouts
  • ✅ Feature flags and options
  • ✅ Non-sensitive settings
  • ✅ User-configurable options

AdminConfigXml

Administrator-only configuration storage.

Storage format: The property stores a string value. While technically you can store any string format, the API is designed for XML and includes special handling for XML serialization (carriage return normalization for XML deserializers).

Recommended: Use XML format as intended by the API design.

Access Control

Reading AdminConfigXml:

  • Plugin itself (running in plugin role context)
  • Administrator users

Writing AdminConfigXml:

  • Plugin itself (running in plugin role context)
  • Administrator users

Reading AdminConfigXml

protected override void OnPluginLoaded()
{
    try
    {
        var role = Engine.GetEntity<Role>(PluginGuid);
        string adminXml = role.RestrictedConfiguration.AdminConfigXml;
        
        if (!string.IsNullOrEmpty(adminXml))
        {
            var adminConfig = DeserializeAdminConfig(adminXml);
            ApplyAdminConfiguration(adminConfig);
        }
    }
    catch (SdkException ex) when (ex.Error == SdkError.UnsufficientPrivilege)
    {
        // Not plugin or not administrator
        Logger.TraceWarning("Insufficient privilege to read AdminConfigXml");
    }
    catch (SdkException ex) when (ex.Error == SdkError.NotConnected)
    {
        Logger.TraceError("Engine is not connected");
    }
    catch (SdkException ex) when (ex.Error == SdkError.InvalidOperation)
    {
        // Can only access AdminConfigXml of Plugin roles
        Logger.TraceError("AdminConfigXml only available for Plugin roles");
    }
}

private AdminConfiguration DeserializeAdminConfig(string xml)
{
    using (var reader = new StringReader(xml))
    {
        var serializer = new XmlSerializer(typeof(AdminConfiguration));
        return (AdminConfiguration)serializer.Deserialize(reader);
    }
}

Writing AdminConfigXml

private void UpdateAdminConfiguration(AdminConfiguration adminConfig)
{
    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        var role = Engine.GetEntity<Role>(PluginGuid);

        using (var writer = new StringWriter())
        {
            var serializer = new XmlSerializer(typeof(AdminConfiguration));
            serializer.Serialize(writer, adminConfig);

            role.RestrictedConfiguration.AdminConfigXml = writer.ToString();
        }
    });

    Logger.TraceInformation("Admin configuration updated");
}

AdminConfigXml Example

[XmlRoot("AdminConfiguration")]
public class AdminConfiguration
{
    public string LicenseServer { get; set; }
    public bool DebugMode { get; set; }
    public int MaxConcurrentConnections { get; set; }
    public List<string> AllowedIpAddresses { get; set; }
}

// Usage
var adminConfig = new AdminConfiguration
{
    LicenseServer = "license.company.com",
    DebugMode = false,
    MaxConcurrentConnections = 100,
    AllowedIpAddresses = new List<string> { "192.168.1.0/24" }
};

UpdateAdminConfiguration(adminConfig);

Private Values (Secure Credentials)

Private values store credentials as SecureString with restricted access.

Important

The returned SecureString is read-only. Always dispose it after use to clear sensitive data from memory.

Access Control

Reading Private Values:

  • ✅ Only the plugin itself (verified by GUID match)
  • ❌ Administrators CANNOT read
  • ❌ Other plugins CANNOT read

Writing/Deleting Private Values:

  • ✅ Plugin itself
  • ✅ Administrators (but cannot read back)

This allows administrators to SET credentials without being able to READ them.

Storing Credentials

protected override void OnPluginLoaded()
{
    // Check if credentials exist
    var role = Engine.GetEntity<Role>(PluginGuid);
    
    if (!role.RestrictedConfiguration.TryGetPrivateValue("ApiKey", out var apiKey))
    {
        Logger.TraceWarning("API key not configured");
        ModifyPluginState(new PluginStateEntry("Configuration",
            "API key not configured") { IsWarning = true });
        return;
    }
    
    try
    {
        // Use the credential
        string apiKeyValue = SecureStringToString(apiKey);
        InitializeApiClient(apiKeyValue);
        
        ModifyPluginState(new PluginStateEntry("Configuration", "API key loaded"));
    }
    finally
    {
        // Always dispose SecureString
        apiKey?.Dispose();
    }
}

private string SecureStringToString(SecureString secureString)
{
    IntPtr ptr = IntPtr.Zero;
    try
    {
        ptr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
        return Marshal.PtrToStringUni(ptr);
    }
    finally
    {
        if (ptr != IntPtr.Zero)
            Marshal.ZeroFreeGlobalAllocUnicode(ptr);
    }
}

Setting Credentials from Config Page

Custom configuration page can set credentials (typically entered by administrator):

// In custom config page or request handler
private void SaveCredentials(string username, string password)
{
    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        var role = Engine.GetEntity<Role>(PluginGuid);

        // Convert string to SecureString
        using (var securePassword = StringToSecureString(password))
        {
            role.RestrictedConfiguration.SetPrivateValue("Username",
                StringToSecureString(username));
            role.RestrictedConfiguration.SetPrivateValue("Password",
                securePassword);
        }
    });

    Logger.TraceInformation("Credentials saved successfully");
}

private SecureString StringToSecureString(string str)
{
    var secureString = new SecureString();
    foreach (char c in str)
    {
        secureString.AppendChar(c);
    }
    secureString.MakeReadOnly();
    return secureString;
}

Deleting Credentials

private void ClearCredentials()
{
    Engine.TransactionManager.ExecuteTransaction(() =>
    {
        var role = Engine.GetEntity<Role>(PluginGuid);

        role.RestrictedConfiguration.DeletePrivateValue("Username");
        role.RestrictedConfiguration.DeletePrivateValue("Password");
        role.RestrictedConfiguration.DeletePrivateValue("ApiKey");
    });

    Logger.TraceInformation("Credentials cleared");
}

Complete Example: External System Integration

public class ExternalSystemPlugin : Plugin
{
    private HttpClient m_httpClient;
    private string m_apiKey;
    
    protected override void OnPluginLoaded()
    {
        // Load configuration
        if (!LoadCredentials())
        {
            ModifyPluginState(new PluginStateEntry("Configuration",
                "Credentials not configured") { IsError = true });
            return;
        }
        
        // Initialize HTTP client with credentials
        m_httpClient = new HttpClient();
        m_httpClient.DefaultRequestHeaders.Add("X-API-Key", m_apiKey);
        
        ModifyPluginState(new PluginStateEntry("Configuration", "Configured"));
    }
    
    private bool LoadCredentials()
    {
        try
        {
            var role = Engine.GetEntity<Role>(PluginGuid);
            
            if (!role.RestrictedConfiguration.TryGetPrivateValue("ApiKey", out var apiKey))
            {
                Logger.TraceWarning("API key not found in RestrictedConfiguration");
                return false;
            }
            
            try
            {
                m_apiKey = SecureStringToString(apiKey);
                
                if (string.IsNullOrWhiteSpace(m_apiKey))
                {
                    Logger.TraceError("API key is empty");
                    return false;
                }
                
                return true;
            }
            finally
            {
                apiKey?.Dispose();
            }
        }
        catch (SdkException ex) when (ex.Error == SdkError.UnsufficientPrivilege)
        {
            Logger.TraceError("Insufficient privilege to read credentials");
            return false;
        }
        catch (Exception ex)
        {
            Logger.TraceError(ex, "Failed to load credentials");
            return false;
        }
    }
    
    private string SecureStringToString(SecureString secureString)
    {
        IntPtr ptr = IntPtr.Zero;
        try
        {
            ptr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
            return Marshal.PtrToStringUni(ptr);
        }
        finally
        {
            if (ptr != IntPtr.Zero)
                Marshal.ZeroFreeGlobalAllocUnicode(ptr);
        }
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Clear sensitive data from memory
            m_apiKey = null;
            m_httpClient?.Dispose();
        }
        base.Dispose(disposing);
    }
}

Request Handler Example: Credential Management

[Serializable]
public class SetCredentialsRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string ApiKey { get; set; }
}

[Serializable]
public class SetCredentialsResponse
{
    public bool Success { get; set; }
    public string Error { get; set; }
}

// In Plugin class
protected override void OnPluginLoaded()
{
    // Use Plugin's protected helper method to register handler
    AddRequestHandler<SetCredentialsRequest, SetCredentialsResponse>(HandleSetCredentials);
}

private SetCredentialsResponse HandleSetCredentials(SetCredentialsRequest request)
{
    try
    {
        Engine.TransactionManager.ExecuteTransaction(() =>
        {
            var role = Engine.GetEntity<Role>(PluginGuid);

            if (!string.IsNullOrEmpty(request.Username))
            {
                using (var secureUsername = StringToSecureString(request.Username))
                {
                    role.RestrictedConfiguration.SetPrivateValue("Username", secureUsername);
                }
            }

            if (!string.IsNullOrEmpty(request.Password))
            {
                using (var securePassword = StringToSecureString(request.Password))
                {
                    role.RestrictedConfiguration.SetPrivateValue("Password", securePassword);
                }
            }

            if (!string.IsNullOrEmpty(request.ApiKey))
            {
                using (var secureApiKey = StringToSecureString(request.ApiKey))
                {
                    role.RestrictedConfiguration.SetPrivateValue("ApiKey", secureApiKey);
                }
            }
        });

        Logger.TraceInformation("Credentials updated successfully");

        // Reload credentials and reconnect
        ReloadCredentialsAndReconnect();

        return new SetCredentialsResponse { Success = true };
    }
    catch (Exception ex)
    {
        Logger.TraceError(ex, "Failed to set credentials");
        return new SetCredentialsResponse
        {
            Success = false,
            Error = ex.Message
        };
    }
}

private SecureString StringToSecureString(string str)
{
    var secureString = new SecureString();
    foreach (char c in str)
    {
        secureString.AppendChar(c);
    }
    secureString.MakeReadOnly();
    return secureString;
}

Error Handling

The RestrictedConfiguration API throws SdkException for standard SDK error conditions. Common SdkError values that you should handle are:

  • NotConnected: the engine or proxy is not connected.
  • InvalidOperation: operation not valid for the target role type (for example, accessing plugin-only features on a non-plugin role).
  • UnsufficientPrivilege: caller lacks the required privileges for the operation.
  • SdkRequestError: wrapped exceptions and transaction failures.

Guidance:

  • Use TryGetPrivateValue when possible to avoid exceptions on missing keys.
  • Always check engine connectivity before calling APIs that require a connected engine.
  • When running inside a plugin role, many privilege checks are relaxed for the owning plugin; other callers (Config Tool, clients, other roles) will encounter standard privilege errors.
  • Avoid referencing internal implementation details or private method names; treat the above error codes as the contract to reason about.

A concise example of how to handle errors:

try
{
    if (role.RestrictedConfiguration.TryGetPrivateValue("ApiKey", out var secure))
    {
        try { /* use secure */ }
        finally { secure?.Dispose(); }
    }
}
catch (SdkException ex) when (ex.Error == SdkError.NotConnected)
{
    Logger.TraceError("RestrictedConfiguration: engine not connected");
}
catch (SdkException ex) when (ex.Error == SdkError.UnsufficientPrivilege)
{
    Logger.TraceWarning("RestrictedConfiguration: insufficient privileges");
}
catch (SdkException ex)
{
    Logger.TraceError(ex, "RestrictedConfiguration operation failed");
}

Graceful Degradation

protected override void OnPluginLoaded()
{
    if (!LoadCredentials())
    {
        ModifyPluginState(new PluginStateEntry("Configuration",
            "Credentials not configured. Please configure credentials in Config Tool.")
            { IsWarning = true });

        // Continue running in limited mode
        m_operatingMode = OperatingMode.Limited;
        return;
    }

    // Full functionality available
    m_operatingMode = OperatingMode.Full;
    InitializeFullFunctionality();
}

Comparison with SpecificConfiguration

Example Configuration Strategy

// SpecificConfiguration - General settings (JSON recommended)
public class PluginConfig
{
    public string ServerUrl { get; set; }
    public int PollingInterval { get; set; }
    public bool EnableDebugLogging { get; set; }
}

// RestrictedConfiguration.AdminConfigXml - Admin-only (XML)
[XmlRoot("AdminConfig")]
public class AdminConfig
{
    public List<string> AllowedIpRanges { get; set; }
    public int MaxRetries { get; set; }
}

// RestrictedConfiguration.PrivateValues - Credentials (SecureString)
// - Username
// - Password
// - ApiKey
// - Certificate

protected override void OnPluginLoaded()
{
    // Load all configurations
    var config = LoadConfiguration();              // SpecificConfiguration (JSON)
    var adminConfig = LoadAdminConfiguration();    // RestrictedConfiguration.AdminConfigXml (XML)
    var credentials = LoadCredentials();           // RestrictedConfiguration.PrivateValues (SecureString)
    
    // Apply all settings
    ApplyConfiguration(config, adminConfig, credentials);
}

Related Guides

Security Center SDK


Macro SDK Developer Guide


Web SDK Developer Guide

  • Getting Started Setup, authentication, and basic configuration for the Web SDK.
  • Referencing Entities Entity discovery, search capabilities, and parameter formats.
  • Entity Operations CRUD operations, multi-value fields, and method execution.
  • Partitions Managing partitions, entity membership, and user access control.
  • Custom Fields Creating, reading, writing, and filtering custom entity fields.
  • Custom Card Formats Managing custom credential card format definitions.
  • Actions Control operations for doors, cameras, macros, and notifications.
  • Events and Alarms Real-time event monitoring, alarm monitoring, and custom events.
  • Incidents Incident management, creation, and attachment handling.
  • Reports Activity reports, entity queries, and historical data retrieval.
  • Performance Guide Optimization tips and best practices for efficient API usage.
  • Reference Entity GUIDs, EntityType enumeration, and EventType enumeration.
  • Under the Hood Technical architecture, query reflection, and SDK internals.
  • Troubleshooting Common error resolution and debugging techniques.

Media Gateway Developer Guide


Web Player Developer Guide

Clone this wiki locally