ldap-rest 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +48 -0
- package/bin/cleanup-external-users.mjs +219 -0
- package/bin/index.mjs +54 -0
- package/bin/sync-james.mjs +190 -0
- package/dist/abstract/ldapFlat.js +1 -0
- package/dist/abstract/plugin.js +1 -0
- package/dist/abstract/twakePlugin.js +1 -0
- package/dist/bin/index.js +1 -0
- package/dist/config/args.js +1 -0
- package/dist/lib/auth/base.js +1 -0
- package/dist/lib/authz/base.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -0
- package/dist/lib/ldapActions.js +1 -0
- package/dist/lib/parseConfig.js +1 -0
- package/dist/lib/utils.js +1 -0
- package/dist/logger/winston.js +1 -0
- package/dist/plugins/auth/authzLinid1.js +1 -0
- package/dist/plugins/auth/authzPerBranch.js +1 -0
- package/dist/plugins/auth/crowdsec.js +1 -0
- package/dist/plugins/auth/llng.js +1 -0
- package/dist/plugins/auth/openidconnect.js +1 -0
- package/dist/plugins/auth/rateLimit.js +1 -0
- package/dist/plugins/auth/token.js +1 -0
- package/dist/plugins/configApi.js +1 -0
- package/dist/plugins/demo/helloworld.js +1 -0
- package/dist/plugins/ldap/bulkImport.js +1 -0
- package/dist/plugins/ldap/departmentSync.js +1 -0
- package/dist/plugins/ldap/externalUsersInGroups.js +1 -0
- package/dist/plugins/ldap/flatGeneric.js +1 -0
- package/dist/plugins/ldap/groups.js +1 -0
- package/dist/plugins/ldap/onChange.js +1 -0
- package/dist/plugins/ldap/organization.js +1 -0
- package/dist/plugins/ldap/trash.js +1 -0
- package/dist/plugins/priority.json.js +1 -0
- package/dist/plugins/static.js +1 -0
- package/dist/plugins/twake/calendarResources.js +1 -0
- package/dist/plugins/twake/james.js +1 -0
- package/dist/plugins/weblogs.js +1 -0
- package/dist/src/abstract/ldapFlat.d.ts +94 -0
- package/dist/src/abstract/plugin.d.ts +43 -0
- package/dist/src/abstract/twakePlugin.d.ts +90 -0
- package/dist/src/bin/index.d.ts +43 -0
- package/dist/src/browser/ldap-group-editor/LdapGroupEditor.d.ts +31 -0
- package/dist/src/browser/ldap-group-editor/api/GroupApiClient.d.ts +23 -0
- package/dist/src/browser/ldap-group-editor/components/CreateGroupModal.d.ts +19 -0
- package/dist/src/browser/ldap-group-editor/components/GroupPropertyEditor.d.ts +30 -0
- package/dist/src/browser/ldap-group-editor/components/GroupTree.d.ts +17 -0
- package/dist/src/browser/ldap-group-editor/components/MoveGroupModal.d.ts +20 -0
- package/dist/src/browser/ldap-group-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-group-editor/types.d.ts +58 -0
- package/dist/src/browser/ldap-resource-editor/api/ResourceApiClient.d.ts +53 -0
- package/dist/src/browser/ldap-resource-editor/cache/CacheManager.d.ts +65 -0
- package/dist/src/browser/ldap-resource-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-resource-editor/types.d.ts +78 -0
- package/dist/src/browser/ldap-tree-viewer/LdapTreeViewer.d.ts +26 -0
- package/dist/src/browser/ldap-tree-viewer/api/LdapApiClient.d.ts +30 -0
- package/dist/src/browser/ldap-tree-viewer/components/TreeNode.d.ts +17 -0
- package/dist/src/browser/ldap-tree-viewer/components/TreeRoot.d.ts +14 -0
- package/dist/src/browser/ldap-tree-viewer/index.d.ts +7 -0
- package/dist/src/browser/ldap-tree-viewer/store/Store.d.ts +18 -0
- package/dist/src/browser/ldap-tree-viewer/store/actions.d.ts +28 -0
- package/dist/src/browser/ldap-tree-viewer/store/reducers.d.ts +7 -0
- package/dist/src/browser/ldap-tree-viewer/types.d.ts +34 -0
- package/dist/src/browser/ldap-unit-editor/LdapUnitEditor.d.ts +27 -0
- package/dist/src/browser/ldap-unit-editor/api/UnitApiClient.d.ts +24 -0
- package/dist/src/browser/ldap-unit-editor/components/MoveUnitModal.d.ts +28 -0
- package/dist/src/browser/ldap-unit-editor/components/UnitPropertyEditor.d.ts +28 -0
- package/dist/src/browser/ldap-unit-editor/components/UnitTree.d.ts +18 -0
- package/dist/src/browser/ldap-unit-editor/index.d.ts +6 -0
- package/dist/src/browser/ldap-unit-editor/types.d.ts +55 -0
- package/dist/src/browser/ldap-user-editor/LdapUserEditor.d.ts +32 -0
- package/dist/src/browser/ldap-user-editor/api/UserApiClient.d.ts +55 -0
- package/dist/src/browser/ldap-user-editor/cache/CacheManager.d.ts +65 -0
- package/dist/src/browser/ldap-user-editor/components/MoveUserModal.d.ts +28 -0
- package/dist/src/browser/ldap-user-editor/components/PointerField.d.ts +22 -0
- package/dist/src/browser/ldap-user-editor/components/UserEditor.d.ts +46 -0
- package/dist/src/browser/ldap-user-editor/components/UserList.d.ts +24 -0
- package/dist/src/browser/ldap-user-editor/components/UserTree.d.ts +24 -0
- package/dist/src/browser/ldap-user-editor/index.d.ts +7 -0
- package/dist/src/browser/ldap-user-editor/types.d.ts +67 -0
- package/dist/src/browser/shared/components/Modal.d.ts +38 -0
- package/dist/src/browser/shared/components/StatusMessage.d.ts +48 -0
- package/dist/src/browser/shared/index.d.ts +10 -0
- package/dist/src/browser/shared/types/index.d.ts +65 -0
- package/dist/src/browser/shared/utils/dom.d.ts +33 -0
- package/dist/src/browser/shared/utils/form.d.ts +28 -0
- package/dist/src/browser/shared/utils/schema.d.ts +29 -0
- package/dist/src/config/args.d.ts +128 -0
- package/dist/src/config/schema.d.ts +18 -0
- package/dist/src/hooks.d.ts +76 -0
- package/dist/src/lib/auth/base.d.ts +9 -0
- package/dist/src/lib/authz/base.d.ts +63 -0
- package/dist/src/lib/expressFormatedResponses.d.ts +26 -0
- package/dist/src/lib/ldapActions.d.ts +91 -0
- package/dist/src/lib/parseConfig.d.ts +23 -0
- package/dist/src/lib/utils.d.ts +73 -0
- package/dist/src/logger/winston.d.ts +3 -0
- package/dist/src/plugins/auth/authzLinid1.d.ts +46 -0
- package/dist/src/plugins/auth/authzPerBranch.d.ts +59 -0
- package/dist/src/plugins/auth/crowdsec.d.ts +29 -0
- package/dist/src/plugins/auth/llng.d.ts +8 -0
- package/dist/src/plugins/auth/openidconnect.d.ts +11 -0
- package/dist/src/plugins/auth/rateLimit.d.ts +24 -0
- package/dist/src/plugins/auth/token.d.ts +17 -0
- package/dist/src/plugins/configApi.d.ts +24 -0
- package/dist/src/plugins/demo/helloworld.d.ts +12 -0
- package/dist/src/plugins/ldap/bulkImport.d.ts +48 -0
- package/dist/src/plugins/ldap/departmentSync.d.ts +35 -0
- package/dist/src/plugins/ldap/externalUsersInGroups.d.ts +21 -0
- package/dist/src/plugins/ldap/flatGeneric.d.ts +31 -0
- package/dist/src/plugins/ldap/groups.d.ts +95 -0
- package/dist/src/plugins/ldap/onChange.d.ts +32 -0
- package/dist/src/plugins/ldap/organization.d.ts +65 -0
- package/dist/src/plugins/ldap/trash.d.ts +52 -0
- package/dist/src/plugins/static.d.ts +7 -0
- package/dist/src/plugins/twake/calendarResources.d.ts +31 -0
- package/dist/src/plugins/twake/james.d.ts +83 -0
- package/dist/src/plugins/weblogs.d.ts +8 -0
- package/docs/DEVELOPER_GUIDE.md +298 -0
- package/docs/OPENAPI.md +440 -0
- package/docs/PLUGIN_DEPENDENCIES.md +612 -0
- package/docs/README.md +466 -0
- package/docs/api/REFERENCE.md +983 -0
- package/docs/api/REST_API.md +1237 -0
- package/docs/authentication.md +469 -0
- package/docs/authzLinid1.md +521 -0
- package/docs/authzPerBranch.md +596 -0
- package/docs/browser/LIBRARIES.md +1196 -0
- package/docs/examples/EXAMPLES.md +1659 -0
- package/docs/examples/twake-mailbox-type-nomenclature.ldif +37 -0
- package/docs/examples/twake-mailbox-type-schema-amendment.ldif +30 -0
- package/docs/jamesTeamMailboxes.md +343 -0
- package/docs/ldapBulkImport.md +722 -0
- package/docs/ldapExternalUsersInGroups.md +325 -0
- package/docs/ldapFlatGeneric.md +444 -0
- package/docs/ldapGroups.md +589 -0
- package/docs/ldapOrganizations.md +659 -0
- package/docs/ldapTrash.md +260 -0
- package/docs/linagora.png +0 -0
- package/docs/onChange.md +318 -0
- package/docs/plugins/DEVELOPMENT.md +1219 -0
- package/docs/presenterm-fr.md +721 -0
- package/docs/presenterm.md +721 -0
- package/docs/schemas/SCHEMAS.md +1212 -0
- package/docs/static.md +410 -0
- package/docs/twakeCalendarResources.md +148 -0
- package/docs/twakeJames.md +661 -0
- package/docs/weblogs.md +399 -0
- package/examples/web/ldap-group-editor.html +113 -0
- package/examples/web/ldap-tree-viewer.html +368 -0
- package/examples/web/ldap-unit-editor.html +108 -0
- package/examples/web/ldap-user-editor.html +412 -0
- package/package.json +458 -0
- package/static/browser/common-demo.css +450 -0
- package/static/browser/ldap-group-editor/LdapGroupEditor.js +207 -0
- package/static/browser/ldap-group-editor/LdapGroupEditor.js.map +1 -0
- package/static/browser/ldap-group-editor/api/GroupApiClient.js +112 -0
- package/static/browser/ldap-group-editor/api/GroupApiClient.js.map +1 -0
- package/static/browser/ldap-group-editor/components/CreateGroupModal.js +212 -0
- package/static/browser/ldap-group-editor/components/CreateGroupModal.js.map +1 -0
- package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js +497 -0
- package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js.map +1 -0
- package/static/browser/ldap-group-editor/components/GroupTree.js +153 -0
- package/static/browser/ldap-group-editor/components/GroupTree.js.map +1 -0
- package/static/browser/ldap-group-editor/components/MoveGroupModal.js +235 -0
- package/static/browser/ldap-group-editor/components/MoveGroupModal.js.map +1 -0
- package/static/browser/ldap-group-editor/index.js +2 -0
- package/static/browser/ldap-group-editor/index.js.map +1 -0
- package/static/browser/ldap-group-editor/types.js +2 -0
- package/static/browser/ldap-group-editor/types.js.map +1 -0
- package/static/browser/ldap-group-editor.esm.js +1399 -0
- package/static/browser/ldap-group-editor.esm.js.map +1 -0
- package/static/browser/ldap-group-editor.js +1407 -0
- package/static/browser/ldap-group-editor.js.map +1 -0
- package/static/browser/ldap-group-editor.min.js +2 -0
- package/static/browser/ldap-group-editor.min.js.map +1 -0
- package/static/browser/ldap-resource-editor/api/ResourceApiClient.js +226 -0
- package/static/browser/ldap-resource-editor/api/ResourceApiClient.js.map +1 -0
- package/static/browser/ldap-resource-editor/cache/CacheManager.js +123 -0
- package/static/browser/ldap-resource-editor/cache/CacheManager.js.map +1 -0
- package/static/browser/ldap-resource-editor/index.js +2 -0
- package/static/browser/ldap-resource-editor/index.js.map +1 -0
- package/static/browser/ldap-resource-editor/types.js +2 -0
- package/static/browser/ldap-resource-editor/types.js.map +1 -0
- package/static/browser/ldap-tree-viewer/LdapTreeViewer.js +187 -0
- package/static/browser/ldap-tree-viewer/LdapTreeViewer.js.map +1 -0
- package/static/browser/ldap-tree-viewer/api/LdapApiClient.js +50 -0
- package/static/browser/ldap-tree-viewer/api/LdapApiClient.js.map +1 -0
- package/static/browser/ldap-tree-viewer/components/TreeNode.js +140 -0
- package/static/browser/ldap-tree-viewer/components/TreeNode.js.map +1 -0
- package/static/browser/ldap-tree-viewer/components/TreeRoot.js +98 -0
- package/static/browser/ldap-tree-viewer/components/TreeRoot.js.map +1 -0
- package/static/browser/ldap-tree-viewer/index.js +2 -0
- package/static/browser/ldap-tree-viewer/index.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/Store.js +29 -0
- package/static/browser/ldap-tree-viewer/store/Store.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/actions.js +53 -0
- package/static/browser/ldap-tree-viewer/store/actions.js.map +1 -0
- package/static/browser/ldap-tree-viewer/store/reducers.js +79 -0
- package/static/browser/ldap-tree-viewer/store/reducers.js.map +1 -0
- package/static/browser/ldap-tree-viewer/types.js +2 -0
- package/static/browser/ldap-tree-viewer/types.js.map +1 -0
- package/static/browser/ldap-tree-viewer.css +2 -0
- package/static/browser/ldap-tree-viewer.css.map +1 -0
- package/static/browser/ldap-tree-viewer.esm.js +606 -0
- package/static/browser/ldap-tree-viewer.esm.js.map +1 -0
- package/static/browser/ldap-tree-viewer.js +614 -0
- package/static/browser/ldap-tree-viewer.js.map +1 -0
- package/static/browser/ldap-tree-viewer.min.js +2 -0
- package/static/browser/ldap-tree-viewer.min.js.map +1 -0
- package/static/browser/ldap-unit-editor/LdapUnitEditor.js +119 -0
- package/static/browser/ldap-unit-editor/LdapUnitEditor.js.map +1 -0
- package/static/browser/ldap-unit-editor/api/UnitApiClient.js +95 -0
- package/static/browser/ldap-unit-editor/api/UnitApiClient.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/MoveUnitModal.js +295 -0
- package/static/browser/ldap-unit-editor/components/MoveUnitModal.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js +321 -0
- package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js.map +1 -0
- package/static/browser/ldap-unit-editor/components/UnitTree.js +197 -0
- package/static/browser/ldap-unit-editor/components/UnitTree.js.map +1 -0
- package/static/browser/ldap-unit-editor/index.js +2 -0
- package/static/browser/ldap-unit-editor/index.js.map +1 -0
- package/static/browser/ldap-unit-editor/types.js +2 -0
- package/static/browser/ldap-unit-editor/types.js.map +1 -0
- package/static/browser/ldap-unit-editor.esm.js +1013 -0
- package/static/browser/ldap-unit-editor.esm.js.map +1 -0
- package/static/browser/ldap-unit-editor.js +1021 -0
- package/static/browser/ldap-unit-editor.js.map +1 -0
- package/static/browser/ldap-unit-editor.min.js +2 -0
- package/static/browser/ldap-unit-editor.min.js.map +1 -0
- package/static/browser/ldap-user-editor/LdapUserEditor.js +184 -0
- package/static/browser/ldap-user-editor/LdapUserEditor.js.map +1 -0
- package/static/browser/ldap-user-editor/api/UserApiClient.js +209 -0
- package/static/browser/ldap-user-editor/api/UserApiClient.js.map +1 -0
- package/static/browser/ldap-user-editor/cache/CacheManager.js +123 -0
- package/static/browser/ldap-user-editor/cache/CacheManager.js.map +1 -0
- package/static/browser/ldap-user-editor/components/MoveUserModal.js +302 -0
- package/static/browser/ldap-user-editor/components/MoveUserModal.js.map +1 -0
- package/static/browser/ldap-user-editor/components/PointerField.js +152 -0
- package/static/browser/ldap-user-editor/components/PointerField.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserEditor.js +608 -0
- package/static/browser/ldap-user-editor/components/UserEditor.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserList.js +181 -0
- package/static/browser/ldap-user-editor/components/UserList.js.map +1 -0
- package/static/browser/ldap-user-editor/components/UserTree.js +164 -0
- package/static/browser/ldap-user-editor/components/UserTree.js.map +1 -0
- package/static/browser/ldap-user-editor/index.js +2 -0
- package/static/browser/ldap-user-editor/index.js.map +1 -0
- package/static/browser/ldap-user-editor/types.js +2 -0
- package/static/browser/ldap-user-editor/types.js.map +1 -0
- package/static/browser/ldap-user-editor.css +2 -0
- package/static/browser/ldap-user-editor.css.map +1 -0
- package/static/browser/ldap-user-editor.esm.js +1897 -0
- package/static/browser/ldap-user-editor.esm.js.map +1 -0
- package/static/browser/ldap-user-editor.js +1905 -0
- package/static/browser/ldap-user-editor.js.map +1 -0
- package/static/browser/ldap-user-editor.min.js +2 -0
- package/static/browser/ldap-user-editor.min.js.map +1 -0
- package/static/browser/shared/components/Modal.js +121 -0
- package/static/browser/shared/components/Modal.js.map +1 -0
- package/static/browser/shared/components/StatusMessage.js +119 -0
- package/static/browser/shared/components/StatusMessage.js.map +1 -0
- package/static/browser/shared/index.js +7 -0
- package/static/browser/shared/index.js.map +1 -0
- package/static/browser/shared/types/index.js +37 -0
- package/static/browser/shared/types/index.js.map +1 -0
- package/static/browser/shared/utils/dom.js +78 -0
- package/static/browser/shared/utils/dom.js.map +1 -0
- package/static/browser/shared/utils/form.js +234 -0
- package/static/browser/shared/utils/form.js.map +1 -0
- package/static/browser/shared/utils/schema.js +121 -0
- package/static/browser/shared/utils/schema.js.map +1 -0
- package/static/browser/shared.esm.js +694 -0
- package/static/browser/shared.esm.js.map +1 -0
- package/static/browser/shared.js +723 -0
- package/static/browser/shared.js.map +1 -0
- package/static/browser/shared.min.js +2 -0
- package/static/browser/shared.min.js.map +1 -0
- package/static/schemas/ad/computers.json +92 -0
- package/static/schemas/ad/groups.json +55 -0
- package/static/schemas/ad/organizations.json +66 -0
- package/static/schemas/ad/users.json +116 -0
- package/static/schemas/standard/devices.json +72 -0
- package/static/schemas/standard/groups.json +54 -0
- package/static/schemas/standard/organizations.json +49 -0
- package/static/schemas/standard/users.json +86 -0
- package/static/schemas/twake/groups.json +75 -0
- package/static/schemas/twake/nomenclature/twakeAccountStatus.json +31 -0
- package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +31 -0
- package/static/schemas/twake/nomenclature/twakeListType.json +31 -0
- package/static/schemas/twake/nomenclature/twakeMailboxType.json +31 -0
- package/static/schemas/twake/nomenclature/twakeTitle.json +31 -0
- package/static/schemas/twake/organizations.json +52 -0
- package/static/schemas/twake/positions.json +32 -0
- package/static/schemas/twake/users.json +112 -0
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
# Plugin Development Guide
|
|
2
|
+
|
|
3
|
+
This guide will help you create powerful, production-ready plugins for LDAP-Rest.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Why Create a Plugin](#why-create-a-plugin)
|
|
8
|
+
- [Plugin Structure](#plugin-structure)
|
|
9
|
+
- [Creating Your Plugin](#creating-your-plugin)
|
|
10
|
+
- [Custom Configuration Options](#custom-configuration-options)
|
|
11
|
+
- [Loading Your Plugin](#loading-your-plugin)
|
|
12
|
+
- [Plugin Features](#plugin-features)
|
|
13
|
+
- [Configuration Best Practices](#configuration-best-practices)
|
|
14
|
+
- [Complete Plugin Example](#complete-plugin-example)
|
|
15
|
+
- [Testing Your Plugin](#testing-your-plugin)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why Create a Plugin
|
|
20
|
+
|
|
21
|
+
Plugins extend LDAP-Rest with custom functionality for your specific needs:
|
|
22
|
+
|
|
23
|
+
### Common Use Cases
|
|
24
|
+
|
|
25
|
+
- **Business Logic** - Enforce organization-specific validation rules, data transformation, or custom workflows
|
|
26
|
+
- **External Integration** - Connect to external systems like email servers, webhooks, notification services, or third-party APIs
|
|
27
|
+
- **Authentication & Authorization** - Implement custom authentication methods, rate limiting, or fine-grained access control
|
|
28
|
+
- **API Endpoints** - Expose custom REST endpoints for specialized operations or reporting
|
|
29
|
+
- **Data Transformation** - Modify LDAP entries before/after operations, compute derived attributes, or maintain consistency
|
|
30
|
+
|
|
31
|
+
Plugins can combine multiple features: hook into LDAP operations, expose API endpoints, and integrate external services all in one module.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Plugin Structure
|
|
36
|
+
|
|
37
|
+
Every plugin extends the `DmPlugin` base class and defines a unique name:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import DmPlugin from '../../abstract/plugin';
|
|
41
|
+
import type { Role } from '../../abstract/plugin';
|
|
42
|
+
|
|
43
|
+
export default class MyPlugin extends DmPlugin {
|
|
44
|
+
name = 'myPlugin'; // Unique identifier for your plugin
|
|
45
|
+
roles: Role[] = ['api'] as const; // Optional: categorize your plugin
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Core Properties
|
|
50
|
+
|
|
51
|
+
- **name** (required): Unique identifier for your plugin
|
|
52
|
+
- **roles** (optional): Array categorizing plugin purpose
|
|
53
|
+
- **dependencies** (optional): Required plugins that must load first
|
|
54
|
+
- **hooks** (optional): Callbacks for LDAP operations
|
|
55
|
+
- **api()** (optional): Method to register REST endpoints
|
|
56
|
+
|
|
57
|
+
### Available from Base Class
|
|
58
|
+
|
|
59
|
+
- **this.server**: DM server instance
|
|
60
|
+
- **this.config**: Complete configuration object
|
|
61
|
+
- **this.logger**: Winston logger instance
|
|
62
|
+
- **this.registeredHooks**: Access to global hook registry
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Creating Your Plugin
|
|
67
|
+
|
|
68
|
+
### Option 1: Core Plugin (Inside ldap-rest Repository)
|
|
69
|
+
|
|
70
|
+
Create a file in `src/plugins/<category>/<pluginName>.ts`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
/**
|
|
74
|
+
* @module plugins/notification/webhook
|
|
75
|
+
* @author Your Name <your.email@example.com>
|
|
76
|
+
*
|
|
77
|
+
* Webhook notification plugin that sends HTTP requests on LDAP changes
|
|
78
|
+
*/
|
|
79
|
+
import type { Express, Request, Response } from 'express';
|
|
80
|
+
|
|
81
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
82
|
+
import type { DM } from '../../bin';
|
|
83
|
+
import type { Hooks } from '../../hooks';
|
|
84
|
+
|
|
85
|
+
export default class WebhookNotifier extends DmPlugin {
|
|
86
|
+
name = 'webhookNotifier';
|
|
87
|
+
roles: Role[] = ['api'] as const;
|
|
88
|
+
|
|
89
|
+
// Declare dependencies (optional)
|
|
90
|
+
dependencies = {
|
|
91
|
+
ldapGroups: 'core/ldap/groups',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
constructor(server: DM) {
|
|
95
|
+
super(server);
|
|
96
|
+
|
|
97
|
+
// Validate configuration
|
|
98
|
+
if (!this.config.webhook_url) {
|
|
99
|
+
throw new Error('--webhook-url is required');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.logger.info('WebhookNotifier initialized');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Register API endpoints
|
|
106
|
+
api(app: Express): void {
|
|
107
|
+
app.get(`${this.config.api_prefix}/v1/webhook/status`, (req, res) => {
|
|
108
|
+
res.json({
|
|
109
|
+
status: 'active',
|
|
110
|
+
url: this.config.webhook_url,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Register hooks
|
|
116
|
+
hooks: Hooks = {
|
|
117
|
+
ldapadddone: async ([dn, entry]) => {
|
|
118
|
+
this.logger.info(`User added: ${dn}`);
|
|
119
|
+
await this.sendWebhook('user.added', { dn, entry });
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Private methods
|
|
124
|
+
private async sendWebhook(event: string, data: unknown): Promise<void> {
|
|
125
|
+
try {
|
|
126
|
+
await fetch(this.config.webhook_url as string, {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: { 'Content-Type': 'application/json' },
|
|
129
|
+
body: JSON.stringify({ event, data }),
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.error('Webhook failed:', error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Load with:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx ldap-rest \
|
|
142
|
+
--plugin core/notification/webhook \
|
|
143
|
+
--webhook-url https://example.com/webhook
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Option 2: External Plugin (Outside Repository)
|
|
147
|
+
|
|
148
|
+
Create a standalone file anywhere on your system, e.g., `/opt/custom-plugins/my-plugin.ts`:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
/**
|
|
152
|
+
* External plugin example
|
|
153
|
+
* This plugin can be loaded from anywhere
|
|
154
|
+
*/
|
|
155
|
+
import type { Express } from 'express';
|
|
156
|
+
|
|
157
|
+
// Import from ldap-rest (if installed as npm package)
|
|
158
|
+
// import DmPlugin from 'ldap-rest/plugin';
|
|
159
|
+
|
|
160
|
+
// Or use relative path to node_modules
|
|
161
|
+
import DmPlugin from './node_modules/ldap-rest/dist/abstract/plugin.js';
|
|
162
|
+
import type { DM } from './node_modules/ldap-rest/dist/bin/index.js';
|
|
163
|
+
import type { Hooks } from './node_modules/ldap-rest/dist/hooks.js';
|
|
164
|
+
|
|
165
|
+
export default class ExternalAuditPlugin extends DmPlugin {
|
|
166
|
+
name = 'externalAudit';
|
|
167
|
+
|
|
168
|
+
constructor(server: DM) {
|
|
169
|
+
super(server);
|
|
170
|
+
|
|
171
|
+
// Access custom configuration options
|
|
172
|
+
const auditPath = this.config.audit_path || '/var/log/ldap-audit.log';
|
|
173
|
+
this.logger.info(`Audit log path: ${auditPath}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
api(app: Express): void {
|
|
177
|
+
app.get(`${this.config.api_prefix}/v1/audit/stats`, (req, res) => {
|
|
178
|
+
res.json({
|
|
179
|
+
message: 'External plugin API',
|
|
180
|
+
auditEnabled: true,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
hooks: Hooks = {
|
|
186
|
+
ldapaddrequest: async ([dn, entry, req]) => {
|
|
187
|
+
this.logger.debug(`[AUDIT] Adding: ${dn}`);
|
|
188
|
+
// Your audit logic here
|
|
189
|
+
return [dn, entry, req];
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
ldapmodifyrequest: async ([dn, changes, op]) => {
|
|
193
|
+
this.logger.debug(`[AUDIT] Modifying: ${dn}`);
|
|
194
|
+
// Your audit logic here
|
|
195
|
+
return [dn, changes, op];
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Load with:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx ldap-rest \
|
|
205
|
+
--plugin /opt/custom-plugins/my-plugin.ts \
|
|
206
|
+
--audit-path /var/log/ldap-audit.log
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or via environment variable:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
export DM_PLUGINS=/opt/custom-plugins/my-plugin.ts
|
|
213
|
+
export DM_AUDIT_PATH=/var/log/ldap-audit.log
|
|
214
|
+
npx ldap-rest
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Custom Configuration Options
|
|
220
|
+
|
|
221
|
+
LDAP-Rest automatically accepts and stores any unknown command-line options in the config object. This allows your plugin to define custom configuration without modifying core files.
|
|
222
|
+
|
|
223
|
+
### How It Works
|
|
224
|
+
|
|
225
|
+
1. **Command-line option format**: `--option-name value`
|
|
226
|
+
2. **Converted to config key**: `option_name` (dashes become underscores)
|
|
227
|
+
3. **Environment variable format**: `DM_OPTION_NAME` (uppercase with underscores)
|
|
228
|
+
4. **Access in plugin**: `this.config.option_name`
|
|
229
|
+
|
|
230
|
+
### Single Value Options
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Command line
|
|
234
|
+
npx ldap-rest \
|
|
235
|
+
--webhook-url https://example.com/webhook \
|
|
236
|
+
--webhook-timeout 5000 \
|
|
237
|
+
--webhook-retry true
|
|
238
|
+
|
|
239
|
+
# Environment variables
|
|
240
|
+
export DM_WEBHOOK_URL=https://example.com/webhook
|
|
241
|
+
export DM_WEBHOOK_TIMEOUT=5000
|
|
242
|
+
export DM_WEBHOOK_RETRY=true
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
In your plugin:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
constructor(server: DM) {
|
|
249
|
+
super(server);
|
|
250
|
+
|
|
251
|
+
// Access custom options (converted to snake_case)
|
|
252
|
+
const url = this.config.webhook_url; // string
|
|
253
|
+
const timeout = this.config.webhook_timeout; // string (convert if needed)
|
|
254
|
+
const retry = this.config.webhook_retry; // string 'true' (convert if needed)
|
|
255
|
+
|
|
256
|
+
// Type conversion
|
|
257
|
+
const timeoutNum = parseInt(timeout as string || '3000');
|
|
258
|
+
const retryBool = (retry as string) === 'true';
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Multiple Value Options
|
|
263
|
+
|
|
264
|
+
Repeat the same option multiple times to create an array:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Command line
|
|
268
|
+
npx ldap-rest \
|
|
269
|
+
--allowed-domain example.com \
|
|
270
|
+
--allowed-domain test.com \
|
|
271
|
+
--allowed-domain dev.com
|
|
272
|
+
|
|
273
|
+
# Environment variable (comma or semicolon separated)
|
|
274
|
+
export DM_ALLOWED_DOMAINS=example.com,test.com,dev.com
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
In your plugin:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
constructor(server: DM) {
|
|
281
|
+
super(server);
|
|
282
|
+
|
|
283
|
+
// Access as array (note: singular becomes plural in config key)
|
|
284
|
+
const domains = this.config.allowed_domain as string[];
|
|
285
|
+
// or sometimes stored as: this.config.allowed_domains
|
|
286
|
+
|
|
287
|
+
this.logger.info(`Allowed domains: ${domains.join(', ')}`);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Configuration Priority
|
|
292
|
+
|
|
293
|
+
Values are resolved in this order (later overrides earlier):
|
|
294
|
+
|
|
295
|
+
1. **Default value** in your plugin
|
|
296
|
+
2. **Environment variable** (`DM_OPTION_NAME`)
|
|
297
|
+
3. **Command-line argument** (`--option-name`)
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
constructor(server: DM) {
|
|
301
|
+
super(server);
|
|
302
|
+
|
|
303
|
+
// Provide defaults for optional configuration
|
|
304
|
+
const webhookUrl = this.config.webhook_url || 'http://localhost:8080/webhook';
|
|
305
|
+
const webhookTimeout = parseInt(this.config.webhook_timeout as string || '3000');
|
|
306
|
+
const maxRetries = parseInt(this.config.max_retries as string || '3');
|
|
307
|
+
|
|
308
|
+
// Validate required configuration
|
|
309
|
+
if (!this.config.webhook_secret) {
|
|
310
|
+
throw new Error('--webhook-secret is required for authentication');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Loading Your Plugin
|
|
318
|
+
|
|
319
|
+
### Core Plugin
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Load by path within src/plugins/
|
|
323
|
+
npx ldap-rest --plugin core/notification/webhook
|
|
324
|
+
|
|
325
|
+
# Load multiple plugins
|
|
326
|
+
npx ldap-rest \
|
|
327
|
+
--plugin core/ldap/groups \
|
|
328
|
+
--plugin core/notification/webhook \
|
|
329
|
+
--plugin core/static
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### External Plugin
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Load by absolute file path
|
|
336
|
+
npx ldap-rest --plugin /path/to/my-plugin.ts
|
|
337
|
+
|
|
338
|
+
# Load multiple external plugins
|
|
339
|
+
npx ldap-rest \
|
|
340
|
+
--plugin /opt/plugins/audit.ts \
|
|
341
|
+
--plugin /opt/plugins/validation.ts
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Via Environment Variable
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
# Single plugin
|
|
348
|
+
export DM_PLUGINS=core/notification/webhook
|
|
349
|
+
npx ldap-rest
|
|
350
|
+
|
|
351
|
+
# Multiple plugins (comma-separated)
|
|
352
|
+
export DM_PLUGINS=core/ldap/groups,core/static,/opt/plugins/custom.ts
|
|
353
|
+
npx ldap-rest
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Plugin Loading Order
|
|
357
|
+
|
|
358
|
+
Some plugins must load before others (e.g., authentication plugins). Edit `src/plugins/priority.json`:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
[
|
|
362
|
+
"core/auth/token",
|
|
363
|
+
"core/auth/llng",
|
|
364
|
+
"core/auth/openidconnect",
|
|
365
|
+
"core/auth/authzPerBranch",
|
|
366
|
+
"core/myNewAuthPlugin"
|
|
367
|
+
]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Plugins in this list load first, in order. All other plugins load after.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Plugin Features
|
|
375
|
+
|
|
376
|
+
### Declaring Dependencies
|
|
377
|
+
|
|
378
|
+
Ensure required plugins load before yours:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
export default class MyPlugin extends DmPlugin {
|
|
382
|
+
name = 'myPlugin';
|
|
383
|
+
|
|
384
|
+
dependencies = {
|
|
385
|
+
// Key is how you reference it, value is the plugin path
|
|
386
|
+
groups: 'core/ldap/groups',
|
|
387
|
+
users: 'core/ldap/flatGeneric',
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
constructor(server: DM) {
|
|
391
|
+
super(server);
|
|
392
|
+
|
|
393
|
+
// Access dependent plugin
|
|
394
|
+
const groupsPlugin = this.server.loadedPlugins['ldapGroups'];
|
|
395
|
+
if (groupsPlugin) {
|
|
396
|
+
this.logger.info('Groups plugin is available');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Using LDAP Operations
|
|
403
|
+
|
|
404
|
+
The `this.server.ldap` object provides access to all LDAP operations:
|
|
405
|
+
|
|
406
|
+
#### Search
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// Basic search
|
|
410
|
+
const results = await this.server.ldap.search(
|
|
411
|
+
{
|
|
412
|
+
paged: false,
|
|
413
|
+
scope: 'sub', // 'base', 'one', or 'sub'
|
|
414
|
+
filter: '(objectClass=inetOrgPerson)',
|
|
415
|
+
attributes: ['cn', 'mail', 'uid'],
|
|
416
|
+
},
|
|
417
|
+
'ou=users,dc=example,dc=com'
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// Search with pagination (returns AsyncGenerator)
|
|
421
|
+
const pagedResults = await this.server.ldap.search(
|
|
422
|
+
{
|
|
423
|
+
paged: true,
|
|
424
|
+
filter: '(uid=*)',
|
|
425
|
+
attributes: ['cn', 'mail'],
|
|
426
|
+
},
|
|
427
|
+
'dc=example,dc=com'
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
for await (const page of pagedResults) {
|
|
431
|
+
page.searchEntries.forEach(entry => {
|
|
432
|
+
console.log(entry.dn, entry.cn);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### Add
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
await this.server.ldap.add('uid=john,ou=users,dc=example,dc=com', {
|
|
441
|
+
objectClass: ['inetOrgPerson', 'person', 'top'],
|
|
442
|
+
uid: 'john',
|
|
443
|
+
cn: 'John Doe',
|
|
444
|
+
sn: 'Doe',
|
|
445
|
+
mail: 'john@example.com',
|
|
446
|
+
userPassword: 'secret123',
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### Modify
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
await this.server.ldap.modify('uid=john,ou=users,dc=example,dc=com', {
|
|
454
|
+
replace: {
|
|
455
|
+
mail: 'john.doe@example.com',
|
|
456
|
+
telephoneNumber: '+1234567890',
|
|
457
|
+
},
|
|
458
|
+
add: {
|
|
459
|
+
description: 'Senior Developer',
|
|
460
|
+
},
|
|
461
|
+
delete: {
|
|
462
|
+
oldAttribute: 'valueToRemove',
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
#### Delete
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// Delete single entry
|
|
471
|
+
await this.server.ldap.delete('uid=john,ou=users,dc=example,dc=com');
|
|
472
|
+
|
|
473
|
+
// Delete multiple entries
|
|
474
|
+
await this.server.ldap.delete([
|
|
475
|
+
'uid=user1,ou=users,dc=example,dc=com',
|
|
476
|
+
'uid=user2,ou=users,dc=example,dc=com',
|
|
477
|
+
]);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Registering Hooks
|
|
481
|
+
|
|
482
|
+
Hooks let you intercept and modify LDAP operations:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
hooks: Hooks = {
|
|
486
|
+
// LDAP ADD HOOKS
|
|
487
|
+
ldapaddrequest: async ([dn, entry, req]) => {
|
|
488
|
+
// Called BEFORE adding an entry
|
|
489
|
+
// Validate, modify entry, or prevent operation
|
|
490
|
+
this.logger.debug(`Adding entry: ${dn}`);
|
|
491
|
+
|
|
492
|
+
// Add computed field
|
|
493
|
+
entry.createdAt = new Date().toISOString();
|
|
494
|
+
|
|
495
|
+
// Validation
|
|
496
|
+
if (!entry.mail) {
|
|
497
|
+
throw new Error('Email is required');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return [dn, entry, req];
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
ldapadddone: async ([dn, entry]) => {
|
|
504
|
+
// Called AFTER successful add
|
|
505
|
+
this.logger.info(`Entry added: ${dn}`);
|
|
506
|
+
// Trigger side effects, notifications, etc.
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// LDAP MODIFY HOOKS
|
|
510
|
+
ldapmodifyrequest: async ([dn, changes, opNumber]) => {
|
|
511
|
+
// Called BEFORE modifying an entry
|
|
512
|
+
this.logger.debug(`Modifying entry: ${dn}`);
|
|
513
|
+
|
|
514
|
+
// Add audit trail
|
|
515
|
+
if (!changes.add) changes.add = {};
|
|
516
|
+
changes.add.modifiedAt = new Date().toISOString();
|
|
517
|
+
|
|
518
|
+
return [dn, changes, opNumber];
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
ldapmodifydone: async ([dn, changes, opNumber]) => {
|
|
522
|
+
// Called AFTER successful modify
|
|
523
|
+
this.logger.info(`Entry modified: ${dn}`);
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
// LDAP DELETE HOOKS
|
|
527
|
+
ldapdeleterequest: async dn => {
|
|
528
|
+
// Called BEFORE deleting entry
|
|
529
|
+
this.logger.debug(`Deleting entry: ${dn}`);
|
|
530
|
+
|
|
531
|
+
// Prevent deletion of admin
|
|
532
|
+
if (dn.includes('cn=admin')) {
|
|
533
|
+
throw new Error('Cannot delete admin user');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return dn;
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
ldapdeletedone: async dn => {
|
|
540
|
+
// Called AFTER successful delete
|
|
541
|
+
this.logger.info(`Entry deleted: ${dn}`);
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
// CUSTOM HOOKS (from other plugins)
|
|
545
|
+
onLdapChange: async (dn, changes) => {
|
|
546
|
+
// React to any LDAP change
|
|
547
|
+
this.logger.info(`Change detected on ${dn}`);
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Exposing API Endpoints
|
|
553
|
+
|
|
554
|
+
Create REST API endpoints for your plugin:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
api(app: Express): void {
|
|
558
|
+
// GET endpoint
|
|
559
|
+
app.get(
|
|
560
|
+
`${this.config.api_prefix}/v1/myplugin/status`,
|
|
561
|
+
async (req: Request, res: Response) => {
|
|
562
|
+
try {
|
|
563
|
+
const status = await this.getStatus();
|
|
564
|
+
res.json({ success: true, data: status });
|
|
565
|
+
} catch (error) {
|
|
566
|
+
res.status(500).json({
|
|
567
|
+
success: false,
|
|
568
|
+
error: error.message
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
// POST endpoint with body
|
|
575
|
+
app.post(
|
|
576
|
+
`${this.config.api_prefix}/v1/myplugin/process`,
|
|
577
|
+
async (req: Request, res: Response) => {
|
|
578
|
+
try {
|
|
579
|
+
// Validate request body
|
|
580
|
+
if (!req.body || !req.body.data) {
|
|
581
|
+
return res.status(400).json({
|
|
582
|
+
success: false,
|
|
583
|
+
error: 'data is required'
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const result = await this.processData(req.body.data);
|
|
588
|
+
res.json({ success: true, data: result });
|
|
589
|
+
} catch (error) {
|
|
590
|
+
this.logger.error('Process failed:', error);
|
|
591
|
+
res.status(500).json({
|
|
592
|
+
success: false,
|
|
593
|
+
error: error.message
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
// PUT endpoint
|
|
600
|
+
app.put(
|
|
601
|
+
`${this.config.api_prefix}/v1/myplugin/config`,
|
|
602
|
+
async (req: Request, res: Response) => {
|
|
603
|
+
try {
|
|
604
|
+
await this.updateConfig(req.body);
|
|
605
|
+
res.json({ success: true });
|
|
606
|
+
} catch (error) {
|
|
607
|
+
res.status(500).json({ success: false, error: error.message });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// DELETE endpoint
|
|
613
|
+
app.delete(
|
|
614
|
+
`${this.config.api_prefix}/v1/myplugin/cache`,
|
|
615
|
+
async (req: Request, res: Response) => {
|
|
616
|
+
try {
|
|
617
|
+
await this.clearCache();
|
|
618
|
+
res.json({ success: true, message: 'Cache cleared' });
|
|
619
|
+
} catch (error) {
|
|
620
|
+
res.status(500).json({ success: false, error: error.message });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
this.logger.info('MyPlugin API endpoints registered');
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Logging
|
|
630
|
+
|
|
631
|
+
Use the provided logger with appropriate levels:
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
// Debug - detailed diagnostic information
|
|
635
|
+
this.logger.debug('Processing entry:', entry);
|
|
636
|
+
|
|
637
|
+
// Info - general informational messages
|
|
638
|
+
this.logger.info('Plugin initialized successfully');
|
|
639
|
+
|
|
640
|
+
// Warn - warning messages for potentially harmful situations
|
|
641
|
+
this.logger.warn('Configuration not found, using defaults');
|
|
642
|
+
|
|
643
|
+
// Error - error events that might still allow the application to continue
|
|
644
|
+
this.logger.error('Failed to process webhook:', error);
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Plugin Roles
|
|
648
|
+
|
|
649
|
+
Categorize your plugin using roles:
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
export default class MyPlugin extends DmPlugin {
|
|
653
|
+
name = 'myPlugin';
|
|
654
|
+
roles: Role[] = ['auth', 'api'] as const;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Available roles:
|
|
659
|
+
|
|
660
|
+
- **auth** - Authentication plugins
|
|
661
|
+
- **authz** - Authorization/access control plugins
|
|
662
|
+
- **protect** - Security/rate limiting plugins
|
|
663
|
+
- **api** - Plugins exposing API endpoints
|
|
664
|
+
- **logging** - Logging and audit plugins
|
|
665
|
+
- **demo** - Example/demonstration plugins
|
|
666
|
+
- **consistency** - Data consistency enforcement plugins
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## Configuration Best Practices
|
|
671
|
+
|
|
672
|
+
### Validation in Constructor
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
constructor(server: DM) {
|
|
676
|
+
super(server);
|
|
677
|
+
|
|
678
|
+
// Validate required configuration
|
|
679
|
+
if (!this.config.webhook_url) {
|
|
680
|
+
throw new Error('Missing required configuration: --webhook-url');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Validate format
|
|
684
|
+
const url = this.config.webhook_url as string;
|
|
685
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
686
|
+
throw new Error('--webhook-url must start with http:// or https://');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Validate numeric ranges
|
|
690
|
+
const timeout = parseInt(this.config.webhook_timeout as string || '3000');
|
|
691
|
+
if (timeout < 100 || timeout > 30000) {
|
|
692
|
+
throw new Error('--webhook-timeout must be between 100 and 30000');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
this.logger.info('Configuration validated successfully');
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Provide Defaults
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
constructor(server: DM) {
|
|
703
|
+
super(server);
|
|
704
|
+
|
|
705
|
+
// Set defaults for optional configuration
|
|
706
|
+
this.webhookUrl = this.config.webhook_url as string;
|
|
707
|
+
this.webhookTimeout = parseInt(
|
|
708
|
+
this.config.webhook_timeout as string || '3000'
|
|
709
|
+
);
|
|
710
|
+
this.maxRetries = parseInt(
|
|
711
|
+
this.config.webhook_max_retries as string || '3'
|
|
712
|
+
);
|
|
713
|
+
this.retryDelay = parseInt(
|
|
714
|
+
this.config.webhook_retry_delay as string || '1000'
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Document Configuration Options
|
|
720
|
+
|
|
721
|
+
Add clear documentation in your plugin file:
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
/**
|
|
725
|
+
* @module plugins/notification/webhook
|
|
726
|
+
* @author Your Name <your.email@example.com>
|
|
727
|
+
*
|
|
728
|
+
* Webhook notification plugin
|
|
729
|
+
*
|
|
730
|
+
* Configuration options:
|
|
731
|
+
*
|
|
732
|
+
* Required:
|
|
733
|
+
* - --webhook-url <url> Webhook endpoint URL (required)
|
|
734
|
+
* - --webhook-secret <string> Secret for webhook authentication (required)
|
|
735
|
+
*
|
|
736
|
+
* Optional:
|
|
737
|
+
* - --webhook-timeout <ms> Request timeout in milliseconds (default: 3000)
|
|
738
|
+
* - --webhook-max-retries <n> Maximum retry attempts (default: 3)
|
|
739
|
+
* - --webhook-retry-delay <ms> Delay between retries (default: 1000)
|
|
740
|
+
* - --webhook-event <event> Events to send (multiple, default: all)
|
|
741
|
+
*
|
|
742
|
+
* Environment variables:
|
|
743
|
+
* - DM_WEBHOOK_URL
|
|
744
|
+
* - DM_WEBHOOK_SECRET
|
|
745
|
+
* - DM_WEBHOOK_TIMEOUT
|
|
746
|
+
* - DM_WEBHOOK_MAX_RETRIES
|
|
747
|
+
* - DM_WEBHOOK_RETRY_DELAY
|
|
748
|
+
* - DM_WEBHOOK_EVENTS (comma-separated)
|
|
749
|
+
*
|
|
750
|
+
* Example usage:
|
|
751
|
+
*
|
|
752
|
+
* npx ldap-rest \
|
|
753
|
+
* --plugin core/notification/webhook \
|
|
754
|
+
* --webhook-url https://example.com/webhook \
|
|
755
|
+
* --webhook-secret mysecret123 \
|
|
756
|
+
* --webhook-event user.added \
|
|
757
|
+
* --webhook-event user.modified
|
|
758
|
+
*/
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## Complete Plugin Example
|
|
764
|
+
|
|
765
|
+
Here's a complete, production-ready plugin that sends webhook notifications on LDAP changes:
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
/**
|
|
769
|
+
* @module plugins/notification/webhook
|
|
770
|
+
* @author Example Developer <dev@example.com>
|
|
771
|
+
*
|
|
772
|
+
* Sends webhook notifications when LDAP entries are modified
|
|
773
|
+
*
|
|
774
|
+
* Configuration:
|
|
775
|
+
* - --webhook-url <url> Target webhook URL (required)
|
|
776
|
+
* - --webhook-secret <string> Authentication secret (required)
|
|
777
|
+
* - --webhook-timeout <ms> Request timeout (default: 5000)
|
|
778
|
+
* - --webhook-retry-count <n> Retry attempts (default: 3)
|
|
779
|
+
* - --webhook-event <event> Events to monitor (multiple allowed)
|
|
780
|
+
*/
|
|
781
|
+
import type { Express, Request, Response } from 'express';
|
|
782
|
+
import crypto from 'crypto';
|
|
783
|
+
|
|
784
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
785
|
+
import type { DM } from '../../bin';
|
|
786
|
+
import type { Hooks } from '../../hooks';
|
|
787
|
+
import type { AttributesList, ModifyRequest } from '../../lib/ldapActions';
|
|
788
|
+
|
|
789
|
+
interface WebhookPayload {
|
|
790
|
+
event: string;
|
|
791
|
+
timestamp: string;
|
|
792
|
+
dn: string;
|
|
793
|
+
data: AttributesList | ModifyRequest;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
interface WebhookStats {
|
|
797
|
+
sent: number;
|
|
798
|
+
failed: number;
|
|
799
|
+
lastSent: string | null;
|
|
800
|
+
lastError: string | null;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export default class NotificationPlugin extends DmPlugin {
|
|
804
|
+
name = 'notification';
|
|
805
|
+
roles: Role[] = ['api', 'logging'] as const;
|
|
806
|
+
|
|
807
|
+
private webhookUrl: string;
|
|
808
|
+
private webhookSecret: string;
|
|
809
|
+
private webhookTimeout: number;
|
|
810
|
+
private retryCount: number;
|
|
811
|
+
private enabledEvents: Set<string>;
|
|
812
|
+
private stats: WebhookStats;
|
|
813
|
+
|
|
814
|
+
constructor(server: DM) {
|
|
815
|
+
super(server);
|
|
816
|
+
|
|
817
|
+
// Validate required configuration
|
|
818
|
+
if (!this.config.webhook_url) {
|
|
819
|
+
throw new Error('NotificationPlugin requires --webhook-url');
|
|
820
|
+
}
|
|
821
|
+
if (!this.config.webhook_secret) {
|
|
822
|
+
throw new Error('NotificationPlugin requires --webhook-secret');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Load configuration with defaults
|
|
826
|
+
this.webhookUrl = this.config.webhook_url as string;
|
|
827
|
+
this.webhookSecret = this.config.webhook_secret as string;
|
|
828
|
+
this.webhookTimeout = parseInt(
|
|
829
|
+
(this.config.webhook_timeout as string) || '5000'
|
|
830
|
+
);
|
|
831
|
+
this.retryCount = parseInt(
|
|
832
|
+
(this.config.webhook_retry_count as string) || '3'
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
// Parse enabled events
|
|
836
|
+
const events = (this.config.webhook_event as string[]) || ['*'];
|
|
837
|
+
this.enabledEvents = new Set(events);
|
|
838
|
+
|
|
839
|
+
// Initialize stats
|
|
840
|
+
this.stats = {
|
|
841
|
+
sent: 0,
|
|
842
|
+
failed: 0,
|
|
843
|
+
lastSent: null,
|
|
844
|
+
lastError: null,
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
this.logger.info(
|
|
848
|
+
`NotificationPlugin initialized: ${this.webhookUrl} ` +
|
|
849
|
+
`(events: ${Array.from(this.enabledEvents).join(', ')})`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
api(app: Express): void {
|
|
854
|
+
// Get webhook stats
|
|
855
|
+
app.get(
|
|
856
|
+
`${this.config.api_prefix}/v1/notifications/stats`,
|
|
857
|
+
(req: Request, res: Response) => {
|
|
858
|
+
res.json({
|
|
859
|
+
success: true,
|
|
860
|
+
data: {
|
|
861
|
+
...this.stats,
|
|
862
|
+
webhookUrl: this.webhookUrl,
|
|
863
|
+
enabledEvents: Array.from(this.enabledEvents),
|
|
864
|
+
},
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
// Test webhook
|
|
870
|
+
app.post(
|
|
871
|
+
`${this.config.api_prefix}/v1/notifications/test`,
|
|
872
|
+
async (req: Request, res: Response) => {
|
|
873
|
+
try {
|
|
874
|
+
await this.sendWebhook('test', 'test', { message: 'Test webhook' });
|
|
875
|
+
res.json({ success: true, message: 'Test webhook sent' });
|
|
876
|
+
} catch (error) {
|
|
877
|
+
res.status(500).json({
|
|
878
|
+
success: false,
|
|
879
|
+
error: (error as Error).message,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
this.logger.info('NotificationPlugin API registered');
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
hooks: Hooks = {
|
|
889
|
+
ldapadddone: async ([dn, entry]) => {
|
|
890
|
+
if (this.shouldSendEvent('ldap.add')) {
|
|
891
|
+
void this.sendWebhook('ldap.add', dn, entry);
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
|
|
895
|
+
ldapmodifydone: async ([dn, changes, _op]) => {
|
|
896
|
+
if (this.shouldSendEvent('ldap.modify')) {
|
|
897
|
+
void this.sendWebhook('ldap.modify', dn, changes);
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
|
|
901
|
+
ldapdeletedone: async dn => {
|
|
902
|
+
if (this.shouldSendEvent('ldap.delete')) {
|
|
903
|
+
const dnStr = Array.isArray(dn) ? dn.join(', ') : dn;
|
|
904
|
+
void this.sendWebhook('ldap.delete', dnStr, {});
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Check if an event should trigger webhook
|
|
911
|
+
*/
|
|
912
|
+
private shouldSendEvent(event: string): boolean {
|
|
913
|
+
return this.enabledEvents.has('*') || this.enabledEvents.has(event);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Send webhook notification with retry logic
|
|
918
|
+
*/
|
|
919
|
+
private async sendWebhook(
|
|
920
|
+
event: string,
|
|
921
|
+
dn: string,
|
|
922
|
+
data: AttributesList | ModifyRequest
|
|
923
|
+
): Promise<void> {
|
|
924
|
+
const payload: WebhookPayload = {
|
|
925
|
+
event,
|
|
926
|
+
timestamp: new Date().toISOString(),
|
|
927
|
+
dn,
|
|
928
|
+
data,
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
let lastError: Error | null = null;
|
|
932
|
+
|
|
933
|
+
for (let attempt = 0; attempt <= this.retryCount; attempt++) {
|
|
934
|
+
try {
|
|
935
|
+
await this.sendWebhookRequest(payload);
|
|
936
|
+
|
|
937
|
+
// Success
|
|
938
|
+
this.stats.sent++;
|
|
939
|
+
this.stats.lastSent = payload.timestamp;
|
|
940
|
+
this.logger.info(`Webhook sent: ${event} ${dn}`);
|
|
941
|
+
return;
|
|
942
|
+
} catch (error) {
|
|
943
|
+
lastError = error as Error;
|
|
944
|
+
|
|
945
|
+
if (attempt < this.retryCount) {
|
|
946
|
+
this.logger.warn(
|
|
947
|
+
`Webhook attempt ${attempt + 1} failed, retrying...`
|
|
948
|
+
);
|
|
949
|
+
await this.delay(1000 * (attempt + 1));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// All attempts failed
|
|
955
|
+
this.stats.failed++;
|
|
956
|
+
this.stats.lastError = lastError?.message || 'Unknown error';
|
|
957
|
+
this.logger.error(
|
|
958
|
+
`Webhook failed after ${this.retryCount + 1} attempts:`,
|
|
959
|
+
lastError
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Send single webhook HTTP request
|
|
965
|
+
*/
|
|
966
|
+
private async sendWebhookRequest(payload: WebhookPayload): Promise<void> {
|
|
967
|
+
const body = JSON.stringify(payload);
|
|
968
|
+
const signature = this.generateSignature(body);
|
|
969
|
+
|
|
970
|
+
const controller = new AbortController();
|
|
971
|
+
const timeoutId = setTimeout(() => controller.abort(), this.webhookTimeout);
|
|
972
|
+
|
|
973
|
+
try {
|
|
974
|
+
const response = await fetch(this.webhookUrl, {
|
|
975
|
+
method: 'POST',
|
|
976
|
+
headers: {
|
|
977
|
+
'Content-Type': 'application/json',
|
|
978
|
+
'X-Webhook-Signature': signature,
|
|
979
|
+
'User-Agent': 'LDAP-Rest-Webhook/1.0',
|
|
980
|
+
},
|
|
981
|
+
body,
|
|
982
|
+
signal: controller.signal,
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
if (!response.ok) {
|
|
986
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
987
|
+
}
|
|
988
|
+
} finally {
|
|
989
|
+
clearTimeout(timeoutId);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Generate HMAC signature for webhook authentication
|
|
995
|
+
*/
|
|
996
|
+
private generateSignature(payload: string): string {
|
|
997
|
+
return crypto
|
|
998
|
+
.createHmac('sha256', this.webhookSecret)
|
|
999
|
+
.update(payload)
|
|
1000
|
+
.digest('hex');
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Delay helper for retry logic
|
|
1005
|
+
*/
|
|
1006
|
+
private delay(ms: number): Promise<void> {
|
|
1007
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### Usage Example
|
|
1013
|
+
|
|
1014
|
+
```bash
|
|
1015
|
+
# Start LDAP-Rest with notification plugin
|
|
1016
|
+
npx ldap-rest \
|
|
1017
|
+
--ldap-base dc=example,dc=com \
|
|
1018
|
+
--ldap-url ldap://localhost:389 \
|
|
1019
|
+
--ldap-dn cn=admin,dc=example,dc=com \
|
|
1020
|
+
--ldap-pwd admin \
|
|
1021
|
+
--plugin core/ldap/flatGeneric \
|
|
1022
|
+
--ldap-flat-schema ./static/schemas/standard/users.json \
|
|
1023
|
+
--plugin core/notification/webhook \
|
|
1024
|
+
--webhook-url https://example.com/webhook \
|
|
1025
|
+
--webhook-secret mysecret123 \
|
|
1026
|
+
--webhook-timeout 5000 \
|
|
1027
|
+
--webhook-retry-count 3 \
|
|
1028
|
+
--webhook-event ldap.add \
|
|
1029
|
+
--webhook-event ldap.modify
|
|
1030
|
+
|
|
1031
|
+
# Check webhook stats
|
|
1032
|
+
curl http://localhost:8081/api/v1/notifications/stats
|
|
1033
|
+
|
|
1034
|
+
# Test webhook
|
|
1035
|
+
curl -X POST http://localhost:8081/api/v1/notifications/test
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
## Testing Your Plugin
|
|
1041
|
+
|
|
1042
|
+
### Test File Structure
|
|
1043
|
+
|
|
1044
|
+
Create tests in `test/plugins/<category>/<pluginName>.test.ts`:
|
|
1045
|
+
|
|
1046
|
+
```typescript
|
|
1047
|
+
import { describe, it, before, after } from 'mocha';
|
|
1048
|
+
import { expect } from 'chai';
|
|
1049
|
+
import request from 'supertest';
|
|
1050
|
+
|
|
1051
|
+
import { DM } from '../../../src/bin/index';
|
|
1052
|
+
import NotificationPlugin from '../../../src/plugins/notification/webhook';
|
|
1053
|
+
|
|
1054
|
+
describe('NotificationPlugin', function () {
|
|
1055
|
+
let server: DM;
|
|
1056
|
+
let plugin: NotificationPlugin;
|
|
1057
|
+
|
|
1058
|
+
// Skip tests if environment not configured
|
|
1059
|
+
if (!process.env.DM_LDAP_DN || !process.env.DM_LDAP_PWD) {
|
|
1060
|
+
console.warn('Skipping NotificationPlugin tests: LDAP not configured');
|
|
1061
|
+
// @ts-ignore
|
|
1062
|
+
this.skip?.();
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
before(async () => {
|
|
1067
|
+
// Create server instance
|
|
1068
|
+
server = new DM();
|
|
1069
|
+
|
|
1070
|
+
// Set up command-line args for plugin
|
|
1071
|
+
process.argv = [
|
|
1072
|
+
'node',
|
|
1073
|
+
'test',
|
|
1074
|
+
'--plugin',
|
|
1075
|
+
'core/notification/webhook',
|
|
1076
|
+
'--webhook-url',
|
|
1077
|
+
'http://localhost:9999/webhook',
|
|
1078
|
+
'--webhook-secret',
|
|
1079
|
+
'test-secret',
|
|
1080
|
+
'--webhook-event',
|
|
1081
|
+
'ldap.add',
|
|
1082
|
+
];
|
|
1083
|
+
|
|
1084
|
+
await server.ready;
|
|
1085
|
+
await server.run();
|
|
1086
|
+
|
|
1087
|
+
// Get plugin instance
|
|
1088
|
+
plugin = server.loadedPlugins['notification'] as NotificationPlugin;
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
after(async () => {
|
|
1092
|
+
if (server.server) {
|
|
1093
|
+
await new Promise(resolve => server.server!.close(resolve));
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
describe('Initialization', () => {
|
|
1098
|
+
it('should initialize with correct configuration', () => {
|
|
1099
|
+
expect(plugin).to.exist;
|
|
1100
|
+
expect(plugin.name).to.equal('notification');
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
it('should throw error without required config', () => {
|
|
1104
|
+
expect(() => {
|
|
1105
|
+
const badServer = new DM();
|
|
1106
|
+
new NotificationPlugin(badServer);
|
|
1107
|
+
}).to.throw('requires --webhook-url');
|
|
1108
|
+
});
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
describe('API Endpoints', () => {
|
|
1112
|
+
it('should return webhook stats', async () => {
|
|
1113
|
+
const response = await request(server.app)
|
|
1114
|
+
.get('/api/v1/notifications/stats')
|
|
1115
|
+
.expect(200);
|
|
1116
|
+
|
|
1117
|
+
expect(response.body).to.have.property('success', true);
|
|
1118
|
+
expect(response.body.data).to.have.property('sent');
|
|
1119
|
+
expect(response.body.data).to.have.property('failed');
|
|
1120
|
+
expect(response.body.data).to.have.property('webhookUrl');
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
it('should send test webhook', async () => {
|
|
1124
|
+
const response = await request(server.app)
|
|
1125
|
+
.post('/api/v1/notifications/test')
|
|
1126
|
+
.expect(200);
|
|
1127
|
+
|
|
1128
|
+
expect(response.body).to.have.property('success', true);
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
describe('Hooks', () => {
|
|
1133
|
+
it('should trigger webhook on LDAP add', async () => {
|
|
1134
|
+
const testDn = `uid=testuser,${process.env.DM_LDAP_BASE}`;
|
|
1135
|
+
|
|
1136
|
+
// Add test entry
|
|
1137
|
+
await server.ldap.add(testDn, {
|
|
1138
|
+
objectClass: ['inetOrgPerson'],
|
|
1139
|
+
uid: 'testuser',
|
|
1140
|
+
cn: 'Test User',
|
|
1141
|
+
sn: 'User',
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
// Wait for webhook
|
|
1145
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1146
|
+
|
|
1147
|
+
// Check stats
|
|
1148
|
+
const response = await request(server.app)
|
|
1149
|
+
.get('/api/v1/notifications/stats')
|
|
1150
|
+
.expect(200);
|
|
1151
|
+
|
|
1152
|
+
expect(response.body.data.sent).to.be.greaterThan(0);
|
|
1153
|
+
|
|
1154
|
+
// Cleanup
|
|
1155
|
+
await server.ldap.delete(testDn);
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
describe('Private Methods', () => {
|
|
1160
|
+
it('should check enabled events correctly', () => {
|
|
1161
|
+
// Access private method via type assertion for testing
|
|
1162
|
+
const shouldSend = (plugin as any).shouldSendEvent('ldap.add');
|
|
1163
|
+
expect(shouldSend).to.be.true;
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
### Running Tests
|
|
1170
|
+
|
|
1171
|
+
```bash
|
|
1172
|
+
# Set up test environment
|
|
1173
|
+
cat > ~/.test-env <<EOF
|
|
1174
|
+
export DM_LDAP_URL="ldap://localhost:389"
|
|
1175
|
+
export DM_LDAP_DN="cn=admin,dc=example,dc=com"
|
|
1176
|
+
export DM_LDAP_PWD="admin"
|
|
1177
|
+
export DM_LDAP_BASE="dc=example,dc=com"
|
|
1178
|
+
EOF
|
|
1179
|
+
|
|
1180
|
+
# Run all tests
|
|
1181
|
+
source ~/.test-env && npm test
|
|
1182
|
+
|
|
1183
|
+
# Run specific test file
|
|
1184
|
+
source ~/.test-env && npm run test:one test/plugins/notification/webhook.test.ts
|
|
1185
|
+
|
|
1186
|
+
# Run tests in watch mode
|
|
1187
|
+
source ~/.test-env && npm run test:dev
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### Test Best Practices
|
|
1191
|
+
|
|
1192
|
+
1. **Use test helpers** from `test/helpers/` for common operations
|
|
1193
|
+
2. **Clean up** - Delete test entries in `after()` hooks
|
|
1194
|
+
3. **Skip gracefully** - Skip tests if environment not configured
|
|
1195
|
+
4. **Mock external services** - Use mock servers for webhooks, APIs
|
|
1196
|
+
5. **Test error cases** - Verify error handling and validation
|
|
1197
|
+
6. **Test configuration** - Verify all config options work correctly
|
|
1198
|
+
|
|
1199
|
+
---
|
|
1200
|
+
|
|
1201
|
+
## Additional Resources
|
|
1202
|
+
|
|
1203
|
+
- [Contributing Guide](../../CONTRIBUTING.md) - Git workflow and PR guidelines
|
|
1204
|
+
- [Hooks Reference](../../HOOKS.md) - Complete list of available hooks
|
|
1205
|
+
- [Core Plugin Examples](../../src/plugins/) - Browse existing plugins
|
|
1206
|
+
- [Test Examples](../../test/plugins/) - See how core plugins are tested
|
|
1207
|
+
|
|
1208
|
+
---
|
|
1209
|
+
|
|
1210
|
+
## Support
|
|
1211
|
+
|
|
1212
|
+
- **Issues**: https://github.com/linagora/ldap-rest/issues
|
|
1213
|
+
- **Discussions**: https://github.com/linagora/ldap-rest/discussions
|
|
1214
|
+
|
|
1215
|
+
---
|
|
1216
|
+
|
|
1217
|
+
## License
|
|
1218
|
+
|
|
1219
|
+
By contributing plugins to LDAP-Rest, you agree that your contributions will be licensed under the AGPL-3.0 License.
|