Refresh of the plugin SDK to match fastway-server v0.5.3. Plugins that want to use the typed DBAPI (DeclareTable/StoreInsert/etc.) need dbapi_consts.pas for TDBAColumnType/TDBAOnDeleteAction/etc. and dbapi_dialect.pas for TDBATableSpec/TDBAColumnSpec/TDBACriterion and the builder helpers (MakeColumn, MakeFK, MakeUnique, etc.). fw_plugin_api.pas: latest IFWPluginHost with typed DBAPI methods (DeclareTable, DeclareColumn, DeclareIndex, StoreInsert, StoreUpdate, StoreDelete, StoreSelect, StoreUpsert) added under plugin API v1 (still in flux — no version bump during pre-production). Plugin repos vendor this via 'make pull-sdk' which copies these files into the plugin's local sdk/ dir and pins the commit hash in sdk/VERSION. Mirrors the Fimail/fpc-msgbase pattern.
559 lines
24 KiB
ObjectPascal
559 lines
24 KiB
ObjectPascal
unit fw_plugin_api;
|
|
{
|
|
Fastway BBS Plugin API
|
|
|
|
BACKWARD COMPATIBILITY POLICY:
|
|
The server accepts plugins compiled against API versions from
|
|
FW_MIN_PLUGIN_API_VERSION through FW_PLUGIN_API_VERSION (currently v1).
|
|
This gives plugin authors a 2-version grace period to update.
|
|
|
|
When the API version is bumped:
|
|
- New interfaces/methods are ADDED, never removed or changed
|
|
- Plugins compiled against older versions continue to work — they simply
|
|
don't see the new features
|
|
- The server logs an info message when loading an older-version plugin
|
|
- FW_MIN_PLUGIN_API_VERSION = 1 (pre-production, all versions reset)
|
|
|
|
VERSION HISTORY:
|
|
v1 — Current. Still in flux during pre-production — new methods
|
|
get added to IFWPluginHost without bumping the version number.
|
|
When we're closer to a stable plugin API we'll lock v1 and
|
|
bump to v2 for the next breaking change. Methods added under
|
|
v1 since initial Phase 2:
|
|
* IFWMenuPlugin / IFWConfigPlugin / IFWScheduledPlugin
|
|
* IFWServicePlugin, ACL methods, WebSocket methods
|
|
* File transfer API (FileReceiveStart/Chunk/Complete/Abort,
|
|
FileResumeCheck)
|
|
* Typed DBAPI (DeclareTable/Column/Index + StoreInsert/
|
|
Update/Delete/Select/Upsert) — backend-agnostic across
|
|
SQLite/MariaDB/PostgreSQL, replaces raw-SQL DB* methods
|
|
v4 — Added IFWProtocolDetector, IMailerIO, detection signatures (Phase 6)
|
|
}
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$INTERFACES CORBA}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fpjson,
|
|
dbapi_consts, dbapi_dialect;
|
|
|
|
const
|
|
FW_PLUGIN_API_VERSION = 1;
|
|
FW_MIN_PLUGIN_API_VERSION = 1; { Oldest API version we still accept (N-2 backward compat) }
|
|
|
|
{ Protocol detector result codes (returned by HandleInbound) }
|
|
PDR_COMPLETED = 0; { Mailer session completed — transport should disconnect }
|
|
PDR_IEMSI = 1; { IEMSI auto-login — transport should BBS with user data in AResultJSON }
|
|
PDR_ABORT_TO_BBS = 2; { User hit ESC — transport should go to normal BBS login }
|
|
PDR_FAILED = 3; { Session failed — transport should disconnect }
|
|
|
|
type
|
|
{ Plugin permission flags }
|
|
TFWPermission = (
|
|
fpNetworkListen, // Open TCP ports
|
|
fpNetworkConnect, // Make outbound connections
|
|
fpDatabaseRead, // Read from database
|
|
fpDatabaseWrite, // Write to database
|
|
fpDatabaseCreateTables, // Create new tables
|
|
fpFilesystemRead, // Read files
|
|
fpFilesystemWrite, // Write files
|
|
fpSystemExec, // Execute system commands (CRITICAL)
|
|
fpEventsSubscribe, // Listen to events
|
|
fpEventsFire, // Emit events
|
|
fpRoutesRegister, // Add HTTP routes
|
|
fpAdminMenu, // Add admin menu items
|
|
fpConfigRead, // Read configuration
|
|
fpConfigWrite, // Modify configuration
|
|
fpUsersRead, // Read user data
|
|
fpUsersWrite, // Modify user data
|
|
fpSessionsRead, // Read session data
|
|
fpSessionsWrite, // Modify sessions (kick users, etc.)
|
|
fpPluginCall // Call other plugins
|
|
);
|
|
TFWPermissionSet = set of TFWPermission;
|
|
|
|
{ Event callback type }
|
|
TFWEventCallback = procedure(const AEventType: string; AData: TJSONObject) of object;
|
|
|
|
{ Route handler type }
|
|
TFWRouteHandler = procedure(const AMethod, APath: string; ARequestBody: TJSONObject;
|
|
out AResponseCode: Integer; out AResponseBody: TJSONObject) of object;
|
|
|
|
{ Admin menu item record }
|
|
TFWAdminMenuItem = record
|
|
Title: string;
|
|
URL: string;
|
|
Icon: string;
|
|
Position: Integer;
|
|
PluginName: string; { Group name for sidebar section }
|
|
OwnerPlugin: string; { Actual plugin name for removal }
|
|
end;
|
|
|
|
{ Scheduled task info }
|
|
TFWScheduledTask = record
|
|
Name: string;
|
|
IntervalSeconds: Integer;
|
|
LastRun: TDateTime;
|
|
end;
|
|
|
|
{ Plugin target environment }
|
|
TFWPluginTarget = (
|
|
ptServer, // Runs on primary server only
|
|
ptClient, // Runs on thin client only
|
|
ptBoth // Runs on either
|
|
);
|
|
|
|
{ Plugin capability flags - used to query sub-interfaces }
|
|
TFWPluginCapability = (
|
|
pcProtocol, // IFWProtocolPlugin — telnet, SSH, modem listeners
|
|
pcRoutes, // IFWRoutePlugin — REST API endpoints
|
|
pcAdmin, // IFWAdminPlugin — web admin pages/menus
|
|
pcDatabase, // IFWDatabasePlugin — plugin DB tables
|
|
pcEvents, // IFWEventPlugin — event subscriptions
|
|
pcMenu, // IFWMenuPlugin — BBS menu commands
|
|
pcConfig, // IFWConfigPlugin — configuration management
|
|
pcScheduled, // IFWScheduledPlugin — periodic tasks
|
|
pcService, // IFWServicePlugin — inter-plugin callable functions
|
|
pcProtocolDetector // IFWProtocolDetector — registers detection signatures with transports
|
|
);
|
|
TFWPluginCapabilities = set of TFWPluginCapability;
|
|
|
|
{ ---- IMailerIO — Transport-agnostic I/O interface ----
|
|
Provides byte-level I/O for mailer protocols (EMSI, Wazoo, ZModem, etc.).
|
|
Concrete implementations wrap serial ports (TSerialMailerIO in modem.pp)
|
|
or TCP sockets (TSocketMailerIO in telnet.pp). Protocol code works with
|
|
either transport through this interface. }
|
|
IMailerIO = interface
|
|
['{9A5F7E3D-B1C4-4D2E-8F6A-1C3D5E7F9A0B}']
|
|
function SendBytes(const ABuf: PByte; ALen: Integer): Boolean;
|
|
function SendString(const S: string): Boolean;
|
|
function RecvByte(ATimeoutMs: Integer; out B: Byte): Boolean;
|
|
function RecvBytes(ATimeoutMs: Integer; ABuf: PByte; ALen: Integer): Integer;
|
|
function RecvString(ATimeoutMs: Integer; AMaxLen: Integer): string;
|
|
function IsConnected: Boolean;
|
|
procedure Flush;
|
|
procedure PurgeInput;
|
|
end;
|
|
|
|
{ Forward declarations }
|
|
IFWPluginHost = interface;
|
|
IFWPlugin = interface;
|
|
IFWProtocolPlugin = interface;
|
|
IFWRoutePlugin = interface;
|
|
IFWAdminPlugin = interface;
|
|
IFWDatabasePlugin = interface;
|
|
IFWEventPlugin = interface;
|
|
IFWServicePlugin = interface;
|
|
IFWProtocolDetector = interface;
|
|
IFWScheduledPlugin = interface;
|
|
|
|
{ IFWPlugin - base interface every plugin must implement }
|
|
IFWPlugin = interface
|
|
['{E1A2B3C4-D5E6-F7A8-B9C0-D1E2F3A4B5C6}']
|
|
function GetName: string;
|
|
function GetVersion: string;
|
|
function GetDescription: string;
|
|
function GetAuthor: string;
|
|
function GetDependencies: TStringArray;
|
|
function GetPermissions: TFWPermissionSet;
|
|
function GetCapabilities: TFWPluginCapabilities;
|
|
function GetTarget: TFWPluginTarget;
|
|
function AsProtocol: IFWProtocolPlugin;
|
|
function AsRoute: IFWRoutePlugin;
|
|
function AsAdmin: IFWAdminPlugin;
|
|
function AsDBPlugin: IFWDatabasePlugin;
|
|
function AsEvent: IFWEventPlugin;
|
|
function AsService: IFWServicePlugin;
|
|
function AsProtocolDetector: IFWProtocolDetector;
|
|
function AsScheduled: IFWScheduledPlugin;
|
|
function Initialize(AHost: IFWPluginHost): Boolean;
|
|
procedure Finalize;
|
|
end;
|
|
|
|
{ IFWPluginHost - what the server provides to every plugin }
|
|
IFWPluginHost = interface
|
|
['{F2B3C4D5-E6F7-A8B9-C0D1-E2F3A4B5C6D7}']
|
|
// Logging (prefixed with [plugin:name])
|
|
procedure LogInfo(const AMsg: string);
|
|
procedure LogWarning(const AMsg: string);
|
|
procedure LogError(const AMsg: string);
|
|
procedure LogDebug(const AMsg: string);
|
|
|
|
// Structured logging to system_logs (visible in web admin)
|
|
procedure SystemLog(const ALevel, ACategory, AMessage: string);
|
|
procedure SystemLog(const ALevel, ACategory, AMessage, ADetails: string);
|
|
|
|
// Database — legacy raw-SQL path. Still supported for compatibility
|
|
// but deprecated: the raw SQL you pass is routed through a best-
|
|
// effort translator on non-SQLite backends, and any pattern the
|
|
// translator can't handle will fail at runtime on MariaDB/PG.
|
|
// Prefer the typed Declare*/Store* methods below.
|
|
// (Plugin gets prefixed tables: p_<name>_*)
|
|
function DBQuery(const ASQL: string): TJSONArray;
|
|
function DBExec(const ASQL: string): Boolean;
|
|
function DBExecParams(const ASQL: string; AParams: TJSONArray): Boolean;
|
|
procedure DBCreateTable(const ASQL: string);
|
|
|
|
// Database — typed, declarative, backend-agnostic API.
|
|
//
|
|
// Schema declaration (idempotent; reconciles against the live schema):
|
|
// - missing table → CREATE TABLE
|
|
// - missing column → ADD COLUMN
|
|
// - compatible type change → no-op (reconciler logs a schema event)
|
|
// - incompatible type change → raises EDBAIncompatibleSchema
|
|
//
|
|
// Plugin authors build the spec records directly:
|
|
// var Spec: TDBATableSpec;
|
|
// Spec.Name := 'p_myplugin_records';
|
|
// SetLength(Spec.Columns, 2);
|
|
// Spec.Columns[0] := MakeColumn('id', dctBigInt, 0, False, '',
|
|
// True, False, True);
|
|
// Spec.Columns[1] := MakeColumn('created_at', dctDateTime);
|
|
// FHost.DeclareTable(Spec);
|
|
//
|
|
// Table name must be prefixed p_<pluginname>_ (enforced here).
|
|
function DeclareTable(const ASpec: TDBATableSpec): Boolean;
|
|
function DeclareColumn(const ATableName: string; const ASpec: TDBAColumnSpec): Boolean;
|
|
function DeclareIndex(const ATableName: string; const ASpec: TDBAIndexSpec): Boolean;
|
|
|
|
// Structured data ops.
|
|
//
|
|
// ARow is a TJSONObject of {column_name: value, ...}. Values are
|
|
// bound as SQL parameters (no injection). Column names are quoted
|
|
// via the dialect's QuoteIdent (safe against quote chars).
|
|
//
|
|
// AWhere is a TDBACriterion tree — build with Cmp/CmpStr/CmpInt/
|
|
// AndOf/OrOf helpers from dbapi_dialect. Caller retains ownership
|
|
// and must Free it.
|
|
function StoreInsert(const ATableName: string; ARow: TJSONObject): Int64;
|
|
function StoreUpdate(const ATableName: string; AWhere: TDBACriterion;
|
|
ARow: TJSONObject): Integer;
|
|
function StoreDelete(const ATableName: string; AWhere: TDBACriterion): Integer;
|
|
function StoreSelect(const ASpec: TDBASelectSpec): TJSONArray;
|
|
function StoreUpsert(const ATableName: string; ARow: TJSONObject;
|
|
const AKeyCols: array of string): Int64;
|
|
|
|
// Configuration
|
|
function ConfigGet(const AKey: string; const ADefault: string = ''): string;
|
|
procedure ConfigSet(const AKey, AValue: string);
|
|
function ConfigGetSection(const ASection: string): TJSONObject;
|
|
|
|
// Events
|
|
procedure EventSubscribe(const AEventType: string; ACallback: TFWEventCallback);
|
|
procedure EventUnsubscribeCallback(ACallback: TFWEventCallback);
|
|
procedure EventFire(const AEventType: string; AData: TJSONObject);
|
|
|
|
// Routes
|
|
procedure RouteRegister(const AMethod, APath: string; AHandler: TFWRouteHandler);
|
|
|
|
// Admin
|
|
// AGroup: sidebar section name to group under (default = plugin name)
|
|
procedure AdminAddMenu(const ATitle, AURL, AIcon: string; APosition: Integer; const AGroup: string);
|
|
procedure AdminRegisterPage(const APath, AHTMLFile: string);
|
|
|
|
// Sessions (read-only for most plugins)
|
|
function SessionGetAll: TJSONArray;
|
|
function SessionGetByNode(ANode: Integer): TJSONObject;
|
|
function SessionGetCount: Integer;
|
|
|
|
// Inter-plugin
|
|
function PluginCall(const APluginName, AFunctionName: string; AArgs: TJSONObject): TJSONObject;
|
|
function PluginGetService(const APluginName: string): IFWPlugin;
|
|
|
|
// Security
|
|
function GetCurrentPermissions: TFWPermissionSet;
|
|
function HasPermission(APerm: TFWPermission): Boolean;
|
|
|
|
// User data access (permission-gated)
|
|
function UserGet(AUserID: Integer): TJSONObject;
|
|
function UserAuthenticate(const AUsername, APassword: string): TJSONObject;
|
|
function UserCreate(const AUsername, APassword, ARealName, AEmail: string): Integer;
|
|
|
|
// ACL — flag-based access control
|
|
function ACLGetUserFlags(AUserID: Integer): string;
|
|
function ACLCheckAccess(AUserID: Integer; AResourceSecurity: Integer;
|
|
const ARequiredFlags: string): Boolean;
|
|
function ACLCheckAccessByFlags(const AUserFlags: string;
|
|
AUserIsAdmin: Boolean; AUserSecurityLevel: Integer;
|
|
AResourceSecurity: Integer; const ARequiredFlags: string): Boolean;
|
|
procedure ACLRegisterFlag(const AFlagName, ADescription, ACategory: string);
|
|
|
|
// Server info
|
|
function GetServerVersion: string;
|
|
function GetServerName: string;
|
|
function GetUptime: Int64;
|
|
|
|
// File transfer API (protocol plugins use this for inbound file handling)
|
|
// Thin client: streams to server. Primary server: writes to local inbound.
|
|
function FileReceiveStart(const AFilename, AFromAddr: string;
|
|
ASize: Int64; AFileTime: LongInt; const AHash: string): Integer;
|
|
function FileReceiveChunk(AHandle: Integer;
|
|
const AData: RawByteString): Boolean;
|
|
function FileReceiveComplete(AHandle: Integer): Boolean;
|
|
procedure FileReceiveAbort(AHandle: Integer);
|
|
function FileResumeCheck(const AFilename, AFromAddr: string;
|
|
ASize: Int64; const AHash: string; out AOffset: Int64): Boolean;
|
|
|
|
// Session management (for protocol plugins)
|
|
function SessionAllocateNode(const AProtocol: string): Integer;
|
|
procedure SessionReleaseNode(ANodeID: Integer);
|
|
procedure SessionSetNodeUser(ANodeID: Integer; const AUsername: string; AUserID: Integer);
|
|
procedure SessionSetNodeAddress(ANodeID: Integer; const AAddress: string);
|
|
procedure SessionSetNodeStatus(ANodeID: Integer; const AStatus: string);
|
|
|
|
// Protocol detection (thin client only — stubs on primary server)
|
|
// Transport plugins call these to discover and dispatch to protocol detectors.
|
|
function GetDetectionSignatureCount: Integer;
|
|
procedure GetDetectionSignature(AIndex: Integer; out AName: string;
|
|
out APattern: string; out AProbeData: string;
|
|
out AProbeIntervalMs: Integer; out APriority: Integer);
|
|
function DispatchInboundProtocol(AIO: IMailerIO; const ASignatureName: string;
|
|
const ABufferedData: string; out AResultJSON: string): Integer;
|
|
function DispatchOutboundProtocol(AIO: IMailerIO; const AProtocol: string;
|
|
const AParamsJSON: string): Boolean;
|
|
|
|
// WebSocket command push (primary server → thin clients)
|
|
// Pushes a command JSON to the first connected thin client that supports
|
|
// the given protocol. No-op if WebSocket server is not running or no
|
|
// clients support the protocol. Thin client stub does nothing.
|
|
procedure WSPushCommand(const AProtocol: string; ACommand: TJSONObject);
|
|
|
|
// WebSocket state — check if WS client is connected to primary.
|
|
// Used by protocol plugins to skip REST polling when WS is active.
|
|
// Returns True on thin client when WSClient.Connected, False on primary.
|
|
function IsWSConnected: Boolean;
|
|
|
|
// WebSocket client ID — the integer ID assigned by the primary server.
|
|
// Used by protocol plugins to pass client_id when starting BBS sessions
|
|
// so door I/O can be routed back to the correct thin client.
|
|
// Returns 0 on primary server (not applicable).
|
|
function GetWSClientID: Integer;
|
|
|
|
// WebSocket targeted delivery — send a JSON command to a specific client by ID.
|
|
// Used for door session routing (primary → specific thin client).
|
|
// No-op on thin client.
|
|
procedure WSSendToClient(AClientID: Integer; ACommand: TJSONObject);
|
|
|
|
// WebSocket client discovery — get list of connected clients supporting a protocol.
|
|
// Returns JSON array of [{id, client_name, active_nodes, max_nodes}].
|
|
// Caller owns the returned array. Returns empty array on thin client.
|
|
function WSGetClientsByProtocol(const AProtocol: string): TJSONArray;
|
|
end;
|
|
|
|
{ IFWProtocolPlugin - for telnet, SSH, modem, etc. }
|
|
IFWProtocolPlugin = interface
|
|
['{A3B4C5D6-E7F8-A9B0-C1D2-E3F4A5B6C7D8}']
|
|
function GetProtocolName: string;
|
|
function GetListenPort: Integer;
|
|
function GetListenAddress: string;
|
|
function Start: Boolean;
|
|
procedure Stop;
|
|
function GetConnectionCount: Integer;
|
|
function IsRunning: Boolean;
|
|
end;
|
|
|
|
{ IFWRoutePlugin - registers REST API routes }
|
|
IFWRoutePlugin = interface
|
|
['{B4C5D6E7-F8A9-B0C1-D2E3-F4A5B6C7D8E9}']
|
|
procedure RegisterRoutes;
|
|
procedure UnregisterRoutes;
|
|
end;
|
|
|
|
{ IFWAdminPlugin - adds web admin pages/menu items }
|
|
IFWAdminPlugin = interface
|
|
['{C5D6E7F8-A9B0-C1D2-E3F4-A5B6C7D8E9F0}']
|
|
procedure RegisterAdminPages;
|
|
function GetAdminMenuItems: TJSONArray;
|
|
end;
|
|
|
|
{ IFWDatabasePlugin - creates/migrates plugin DB tables }
|
|
IFWDatabasePlugin = interface
|
|
['{D6E7F8A9-B0C1-D2E3-F4A5-B6C7D8E9F0A1}']
|
|
function GetSchemaVersion: Integer;
|
|
procedure CreateSchema;
|
|
procedure MigrateSchema(AFromVersion: Integer);
|
|
end;
|
|
|
|
{ IFWEventPlugin - subscribes to and fires events }
|
|
IFWEventPlugin = interface
|
|
['{E7F8A9B0-C1D2-E3F4-A5B6-C7D8E9F0A1B2}']
|
|
procedure RegisterEvents;
|
|
function GetSubscribedEvents: TStringArray;
|
|
end;
|
|
|
|
{ IFWMenuPlugin - adds BBS menu commands }
|
|
IFWMenuPlugin = interface
|
|
['{F8A9B0C1-D2E3-F4A5-B6C7-D8E9F0A1B2C3}']
|
|
function GetMenuCommands: TJSONArray;
|
|
end;
|
|
|
|
{ IFWConfigPlugin - registers config sections }
|
|
IFWConfigPlugin = interface
|
|
['{A9B0C1D2-E3F4-A5B6-C7D8-E9F0A1B2C3D4}']
|
|
function GetConfigDefaults: TJSONObject;
|
|
procedure OnConfigChanged(const AKey, AValue: string);
|
|
end;
|
|
|
|
{ IFWScheduledPlugin - registers periodic tasks }
|
|
IFWScheduledPlugin = interface
|
|
['{B0C1D2E3-F4A5-B6C7-D8E9-F0A1B2C3D4E5}']
|
|
function GetScheduledTasks: TJSONArray;
|
|
procedure RunTask(const ATaskName: string);
|
|
end;
|
|
|
|
{ IFWServicePlugin - exports callable services for other plugins }
|
|
IFWServicePlugin = interface
|
|
['{C1D2E3F4-A5B6-C7D8-E9F0-A1B2C3D4E5F6}']
|
|
function GetExportedFunctions: TStringArray;
|
|
function CallFunction(const AName: string; AArgs: TJSONObject): TJSONObject;
|
|
end;
|
|
|
|
{ IFWProtocolDetector - registers detection signatures with transport plugins.
|
|
Session protocol plugins (EMSI, Wazoo, etc.) implement this interface
|
|
so transports (modem, telnet) can discover what to watch for and
|
|
dispatch matching connections to the right handler.
|
|
|
|
Detection signatures specify:
|
|
Name — unique identifier (e.g., 'emsi_inq', 'yoohoo')
|
|
Pattern — byte sequence to watch for (may contain binary)
|
|
ProbeData — data to send periodically during detection (empty = passive)
|
|
ProbeInterval — ms between probe sends (0 = don't send)
|
|
Priority — higher = check first
|
|
|
|
HandleInbound returns a PDR_* result code:
|
|
PDR_COMPLETED — mailer session done, transport disconnects
|
|
PDR_IEMSI — IEMSI auto-login, AResultJSON has user data
|
|
PDR_ABORT_TO_BBS — user hit ESC, transport goes to normal BBS login
|
|
PDR_FAILED — session failed }
|
|
IFWProtocolDetector = interface
|
|
['{D2E3F4A5-B6C7-D8E9-F0A1-B2C3D4E5F6A7}']
|
|
function GetDetectionSignatureCount: Integer;
|
|
procedure GetDetectionSignature(AIndex: Integer; out AName: string;
|
|
out APattern: string; out AProbeData: string;
|
|
out AProbeIntervalMs: Integer; out APriority: Integer);
|
|
function HandleInbound(AIO: IMailerIO; const AMatchedName: string;
|
|
const ABufferedData: string; out AResultJSON: string): Integer;
|
|
function HandleOutbound(AIO: IMailerIO; const AProtocol: string;
|
|
const AParamsJSON: string): Boolean;
|
|
end;
|
|
|
|
{ Native plugin library exports }
|
|
TFWPluginAPIVersionFunc = function: Integer; cdecl;
|
|
TFWPluginCreateFunc = function(AHost: IFWPluginHost): IFWPlugin; cdecl;
|
|
TFWPluginDestroyProc = procedure(APlugin: IFWPlugin); cdecl;
|
|
|
|
const
|
|
{ Export function names for native plugins }
|
|
FW_EXPORT_API_VERSION = 'FWPluginAPIVersion';
|
|
FW_EXPORT_CREATE = 'FWPluginCreate';
|
|
FW_EXPORT_DESTROY = 'FWPluginDestroy';
|
|
|
|
{ Standard event types }
|
|
FW_EVENT_SERVER_START = 'server.start';
|
|
FW_EVENT_SERVER_STOP = 'server.stop';
|
|
FW_EVENT_USER_LOGIN = 'user.login';
|
|
FW_EVENT_USER_LOGOUT = 'user.logout';
|
|
FW_EVENT_USER_CREATE = 'user.create';
|
|
FW_EVENT_SESSION_START = 'session.start';
|
|
FW_EVENT_SESSION_END = 'session.end';
|
|
FW_EVENT_CONNECTION_NEW = 'connection.new';
|
|
FW_EVENT_CONNECTION_CLOSE = 'connection.close';
|
|
FW_EVENT_PLUGIN_LOADED = 'plugin.loaded';
|
|
FW_EVENT_PLUGIN_UNLOADED = 'plugin.unloaded';
|
|
FW_EVENT_CONFIG_CHANGED = 'config.changed';
|
|
FW_EVENT_USER_UPDATED = 'user.updated';
|
|
FW_EVENT_USER_DELETED = 'user.deleted';
|
|
FW_EVENT_SCHEDULER_RESULT = 'scheduler.task_result';
|
|
|
|
{ Permission helper functions }
|
|
function PermissionToString(APerm: TFWPermission): string;
|
|
function StringToPermission(const AStr: string; out APerm: TFWPermission): Boolean;
|
|
function PermissionSetToJSON(APerms: TFWPermissionSet): TJSONArray;
|
|
function JSONToPermissionSet(AArr: TJSONArray): TFWPermissionSet;
|
|
function PermissionIsCritical(APerm: TFWPermission): Boolean;
|
|
|
|
implementation
|
|
|
|
function PermissionToString(APerm: TFWPermission): string;
|
|
begin
|
|
case APerm of
|
|
fpNetworkListen: Result := 'network.listen';
|
|
fpNetworkConnect: Result := 'network.connect';
|
|
fpDatabaseRead: Result := 'database.read';
|
|
fpDatabaseWrite: Result := 'database.write';
|
|
fpDatabaseCreateTables: Result := 'database.create_tables';
|
|
fpFilesystemRead: Result := 'filesystem.read';
|
|
fpFilesystemWrite: Result := 'filesystem.write';
|
|
fpSystemExec: Result := 'system.exec';
|
|
fpEventsSubscribe: Result := 'events.subscribe';
|
|
fpEventsFire: Result := 'events.fire';
|
|
fpRoutesRegister: Result := 'routes.register';
|
|
fpAdminMenu: Result := 'admin.menu';
|
|
fpConfigRead: Result := 'config.read';
|
|
fpConfigWrite: Result := 'config.write';
|
|
fpUsersRead: Result := 'users.read';
|
|
fpUsersWrite: Result := 'users.write';
|
|
fpSessionsRead: Result := 'sessions.read';
|
|
fpSessionsWrite: Result := 'sessions.write';
|
|
fpPluginCall: Result := 'plugin.call';
|
|
else
|
|
Result := 'unknown';
|
|
end;
|
|
end;
|
|
|
|
function StringToPermission(const AStr: string; out APerm: TFWPermission): Boolean;
|
|
begin
|
|
Result := True;
|
|
if AStr = 'network.listen' then APerm := fpNetworkListen
|
|
else if AStr = 'network.connect' then APerm := fpNetworkConnect
|
|
else if AStr = 'database.read' then APerm := fpDatabaseRead
|
|
else if AStr = 'database.write' then APerm := fpDatabaseWrite
|
|
else if AStr = 'database.create_tables' then APerm := fpDatabaseCreateTables
|
|
else if AStr = 'filesystem.read' then APerm := fpFilesystemRead
|
|
else if AStr = 'filesystem.write' then APerm := fpFilesystemWrite
|
|
else if AStr = 'system.exec' then APerm := fpSystemExec
|
|
else if AStr = 'events.subscribe' then APerm := fpEventsSubscribe
|
|
else if AStr = 'events.fire' then APerm := fpEventsFire
|
|
else if AStr = 'routes.register' then APerm := fpRoutesRegister
|
|
else if AStr = 'admin.menu' then APerm := fpAdminMenu
|
|
else if AStr = 'config.read' then APerm := fpConfigRead
|
|
else if AStr = 'config.write' then APerm := fpConfigWrite
|
|
else if AStr = 'users.read' then APerm := fpUsersRead
|
|
else if AStr = 'users.write' then APerm := fpUsersWrite
|
|
else if AStr = 'sessions.read' then APerm := fpSessionsRead
|
|
else if AStr = 'sessions.write' then APerm := fpSessionsWrite
|
|
else if AStr = 'plugin.call' then APerm := fpPluginCall
|
|
else Result := False;
|
|
end;
|
|
|
|
function PermissionSetToJSON(APerms: TFWPermissionSet): TJSONArray;
|
|
var
|
|
P: TFWPermission;
|
|
begin
|
|
Result := TJSONArray.Create;
|
|
for P := Low(TFWPermission) to High(TFWPermission) do
|
|
if P in APerms then
|
|
Result.Add(PermissionToString(P));
|
|
end;
|
|
|
|
function JSONToPermissionSet(AArr: TJSONArray): TFWPermissionSet;
|
|
var
|
|
I: Integer;
|
|
P: TFWPermission;
|
|
begin
|
|
Result := [];
|
|
if AArr = nil then Exit;
|
|
for I := 0 to AArr.Count - 1 do
|
|
if StringToPermission(AArr.Strings[I], P) then
|
|
Include(Result, P);
|
|
end;
|
|
|
|
function PermissionIsCritical(APerm: TFWPermission): Boolean;
|
|
begin
|
|
Result := APerm in [fpSystemExec, fpFilesystemWrite, fpUsersWrite, fpSessionsWrite];
|
|
end;
|
|
|
|
end.
|