@brickert/kerio-connect-api 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/README.md +64 -0
- package/index.d.ts +246 -0
- package/jsconfig.json +10 -0
- package/package.json +40 -0
- package/src/client.js +97 -0
- package/src/ipaddressgroups.js +489 -0
- package/src/kerio.js +260 -0
- package/src/modules.js +112 -0
- package/src/session.js +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
# kerio-connect-api
|
|
3
|
+
|
|
4
|
+
An unofficial API wrapper in NodeJS for Kerio Connect mail server.
|
|
5
|
+
|
|
6
|
+
## Notes
|
|
7
|
+
|
|
8
|
+
Tested on Kerio Connect Linux (using Debian 12 `.deb` file install) version `10.0.8 Patch 2`. You can download it here: [http://download.kerio.com/archive/](http://download.kerio.com/archive/)
|
|
9
|
+
|
|
10
|
+
Under the hood it uses jsonrpc protocol version 2. With session cookies `SESSION_CONNECT_WEBADMIN` and an `X-Token` Header for CSRF protection, and a `json` body
|
|
11
|
+
|
|
12
|
+
The official documentation can be viewed here: [https://manuals.gfi.com/en/kerio/api/connect/admin/reference/index.html](https://manuals.gfi.com/en/kerio/api/connect/admin/reference/index.html)
|
|
13
|
+
|
|
14
|
+
Although the documentation looks hard to read I did find an official [PHP library example](https://cdn.kerio.com/dwn/kerio-api-php.zip) that helped greatly.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
Currently only implemented a very small set of methods for the Administrative API under endpoint `/admin/api/jsonrpc`
|
|
19
|
+
|
|
20
|
+
When importing this library a log file is created relative to the where the process is started. E.g. `node /opt/kerio/app.js` will have a `json` log file at `/opt/kerio/log/kerio.log` Generated by the [pino](https://www.npmjs.com/package/pino) package. With also `pino-pretty` printed to `stdout`
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Use the package manager [npm](https://npmjs.org) to install.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @brickert/kerio-connect-api
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
A typical quick one-off script can be such as:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { Kerio } from "@brickert/kerio-connect-api";
|
|
36
|
+
|
|
37
|
+
var kerio = new Kerio({
|
|
38
|
+
hostname: "192.168.1.1",
|
|
39
|
+
port: 4040,
|
|
40
|
+
endpoint_url: '/admin/api/jsonrpc',
|
|
41
|
+
https: true
|
|
42
|
+
}, {
|
|
43
|
+
name: "kerio-connect-api",
|
|
44
|
+
vendor: "Example Inc",
|
|
45
|
+
version: "1.0"
|
|
46
|
+
}, {
|
|
47
|
+
logLevel: 'info'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await kerio.login({
|
|
52
|
+
username: "operator",
|
|
53
|
+
password: "foobar"
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let add_host_id = await kerio.IPAddressGroups.addHostToIPAddressGroup("192.168.1.2", "Example Client Name", "My Group Name", true);
|
|
57
|
+
|
|
58
|
+
console.log(add_host_id);
|
|
59
|
+
// keriodb://ipaddressgroup/1234-5678-901-2345
|
|
60
|
+
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.log(e);
|
|
63
|
+
}
|
|
64
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
|
|
3
|
+
export class WebClient {
|
|
4
|
+
constructor(config: WebClientConfig);
|
|
5
|
+
|
|
6
|
+
hostname: string;
|
|
7
|
+
port: number;
|
|
8
|
+
endpoint_url: string;
|
|
9
|
+
https: boolean;
|
|
10
|
+
fullURL: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Executes a request to the WebClient.fullURL with body object
|
|
14
|
+
* @param requestBody
|
|
15
|
+
* @param method
|
|
16
|
+
* @param follow_redirects
|
|
17
|
+
*/
|
|
18
|
+
doRequest(
|
|
19
|
+
requestBody: GenericJsonRPCBodyPayload,
|
|
20
|
+
method?: HTTPMethod,
|
|
21
|
+
follow_redirects?: RequestRedirect
|
|
22
|
+
): Promise<GenericResponse>;
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WebClientConfig {
|
|
27
|
+
/** FQDN or IP address of the Kerio Connect Instance */
|
|
28
|
+
hostname: string;
|
|
29
|
+
|
|
30
|
+
/** Port number of the Kerio Connect Instance's API */
|
|
31
|
+
port?: number;
|
|
32
|
+
|
|
33
|
+
/** URL endpoint of the Kerio Connect Instance's Admin API
|
|
34
|
+
* @example '/admin/api/jsonrpc'
|
|
35
|
+
*/
|
|
36
|
+
endpoint_url?: string;
|
|
37
|
+
|
|
38
|
+
/** The WebClient instance to use https or http */
|
|
39
|
+
https?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'HEAD';
|
|
43
|
+
|
|
44
|
+
export interface GenericJsonRPCBodyPayload {
|
|
45
|
+
jsonrpc: string;
|
|
46
|
+
id: number;
|
|
47
|
+
method: string;
|
|
48
|
+
params?: {
|
|
49
|
+
userName?: string;
|
|
50
|
+
password?: string;
|
|
51
|
+
application?: KerioUserAgent;
|
|
52
|
+
groups?: Array<object>;
|
|
53
|
+
query?: object;
|
|
54
|
+
groupIds?: Array<string>;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GenericResponse {
|
|
59
|
+
_headers: object;
|
|
60
|
+
_body: {
|
|
61
|
+
jsonrpc: string;
|
|
62
|
+
id: number;
|
|
63
|
+
error?: {
|
|
64
|
+
code: number;
|
|
65
|
+
message: string;
|
|
66
|
+
data: object;
|
|
67
|
+
}
|
|
68
|
+
results?: {
|
|
69
|
+
errors?: {
|
|
70
|
+
code: number;
|
|
71
|
+
message: string;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
result?: {
|
|
75
|
+
userDetails?: object;
|
|
76
|
+
list?: Array<object> | Array<IPAddressGroupItem>;
|
|
77
|
+
totalItems?: number;
|
|
78
|
+
errors?: Array<{
|
|
79
|
+
code: number,
|
|
80
|
+
message: string
|
|
81
|
+
}>;
|
|
82
|
+
result?: Array;
|
|
83
|
+
token?: string;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class Kerio extends WebClient {
|
|
89
|
+
constructor(config: WebClientConfig, userAgent: KerioUserAgent, logConfig?: KerioLogConfig);
|
|
90
|
+
|
|
91
|
+
hostname: string;
|
|
92
|
+
port: number;
|
|
93
|
+
endpoint_url: string;
|
|
94
|
+
https: boolean;
|
|
95
|
+
fullURL: string;
|
|
96
|
+
|
|
97
|
+
userAgent: KerioUserAgent;
|
|
98
|
+
session_cookie: string;
|
|
99
|
+
x_token: string;
|
|
100
|
+
logged_in: boolean;
|
|
101
|
+
heartbeat: object;
|
|
102
|
+
|
|
103
|
+
logger: pino.Logger;
|
|
104
|
+
|
|
105
|
+
IPAddressGroups: IPAddressGroup;
|
|
106
|
+
Session: Session;
|
|
107
|
+
|
|
108
|
+
login(user: KerioUser): void;
|
|
109
|
+
|
|
110
|
+
_createHeartBeatCronJob(): CronJob<null, null>;
|
|
111
|
+
|
|
112
|
+
renewSessionHeartBeat(): Promise<boolean>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface KerioLogConfig {
|
|
116
|
+
logLevel: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Mainly to be shown in Kerio Connect's logs
|
|
121
|
+
*/
|
|
122
|
+
export interface KerioUserAgent {
|
|
123
|
+
/**
|
|
124
|
+
* The name for your API Connector
|
|
125
|
+
* @example 'KerioConnector'
|
|
126
|
+
*/
|
|
127
|
+
name: string;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The name for your API Connector Author or Company
|
|
131
|
+
* @example 'Example Inc'
|
|
132
|
+
*/
|
|
133
|
+
vendor: string;
|
|
134
|
+
/**
|
|
135
|
+
* The version of your API Connector
|
|
136
|
+
* @example '1.0'
|
|
137
|
+
*/
|
|
138
|
+
version: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface KerioUser {
|
|
142
|
+
/**
|
|
143
|
+
* The username of the built-in user that has access to the Admin GUI
|
|
144
|
+
*/
|
|
145
|
+
username: string;
|
|
146
|
+
/**
|
|
147
|
+
* The password of the built-in user that has access to the Admin GUI
|
|
148
|
+
*/
|
|
149
|
+
password: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class KerioModules {
|
|
153
|
+
sessionedRequest(options: SessionedRequestOptions): GenericResponse;
|
|
154
|
+
reset(): void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface SessionedRequestOptions {
|
|
158
|
+
http_method: HTTPMethod;
|
|
159
|
+
api_method: string;
|
|
160
|
+
auth: SessionKeys;
|
|
161
|
+
body_params: object;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface SessionKeys {
|
|
165
|
+
cookie: string;
|
|
166
|
+
token: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export class IPAddressGroup extends KerioModules {
|
|
170
|
+
/**
|
|
171
|
+
* Obtain a Mapped Array of IPAddressGroupItem indexed by their ID for the specified group name
|
|
172
|
+
* @param group_name Name of the IPAddressGroup
|
|
173
|
+
*/
|
|
174
|
+
listIPAddressItemsByGroupName(group_name: string): Promise<Map<KerioIPAddressID, IPAddressGroupItem>>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Add a IPAddressGroupItem of type 'host' of a single IP Address, with a specified description name, within a specified group name, and whether to have it enabled.
|
|
178
|
+
* The IPAddressGroup will also be created automatically if it doesn't exist yet
|
|
179
|
+
* @param host_ip The single IP Address string
|
|
180
|
+
* @param description Name of this IPAddressGroupItem string
|
|
181
|
+
* @param group_name Name of the IPAddressGroup string to add this host in
|
|
182
|
+
* @param enabled True to enable this host in the group
|
|
183
|
+
* @returns Provides the unique KerioID of this host entry
|
|
184
|
+
*/
|
|
185
|
+
addHostToIPAddressGroup(host_ip: string, description: string, group_name: string, enabled: boolean): Promise<KerioIPAddressID>;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Remove this IPAddressGroupItem of type 'host' of a single IP Address from this group name
|
|
189
|
+
* @param host_ip The single IP Address string
|
|
190
|
+
* @param group_name Name of the IPAddressGroup string to remove this host in
|
|
191
|
+
* @returns Returns True when removal was successful
|
|
192
|
+
*/
|
|
193
|
+
removeHostFromIPAddressGroupByIP(host_ip: string, group_name: string): Promise<boolean>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Remove IPAddressGroupItem(s) of type 'host' by the description name from this group name. TAKE CARE as this will remove all items upto the specified max limit that share the same name. You may limit number of items to remove. They are sorted by their unique ID.
|
|
197
|
+
* @param description Name of the group item(s)
|
|
198
|
+
* @param group_name Name of the IPAddressGroup string to remove from
|
|
199
|
+
* @param remove_limit Max number of group items to remove upto. Specifying -1 will remove ALL by the description name
|
|
200
|
+
* @returns Returns an array of host IP addresses that were removed
|
|
201
|
+
*/
|
|
202
|
+
removeHostFromIPAddressGroupByDesc(description: string, group_name: string, remove_limit: number): Promise<Array<KerioIPAddressHost>>;
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type KerioIPAddressHost = string;
|
|
207
|
+
|
|
208
|
+
export interface IPAddressGroupItem {
|
|
209
|
+
id: KerioIPAddressID;
|
|
210
|
+
groupId: KerioIPAddressGroupID;
|
|
211
|
+
sharedId?: string;
|
|
212
|
+
appManagerId?: string;
|
|
213
|
+
groupName: KerioIPAddressGroupName;
|
|
214
|
+
description: string;
|
|
215
|
+
type: IPAddressGroupItemType;
|
|
216
|
+
enabled: boolean;
|
|
217
|
+
host?: string;
|
|
218
|
+
addr1?: string;
|
|
219
|
+
addr2?: string | number;
|
|
220
|
+
childGroupId?: string;
|
|
221
|
+
childGroupName?: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export type IPAddressGroupItemType = 'Host' | 'Network' | 'Range' | 'ChildGroup';
|
|
225
|
+
/**
|
|
226
|
+
* @example "keriodb://ipaddressgroup/1234-5678-901-2345"
|
|
227
|
+
*/
|
|
228
|
+
export type KerioIPAddressGroupID = string;
|
|
229
|
+
/**
|
|
230
|
+
* @example "keriodb://ipaddress/1234-5678-901-2345"
|
|
231
|
+
*/
|
|
232
|
+
export type KerioIPAddressID = string;
|
|
233
|
+
export type KerioIPAddressGroupName = string;
|
|
234
|
+
|
|
235
|
+
export class Session extends KerioModules {
|
|
236
|
+
whoAmI(): Promise<KerioInstanceUserResponse>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface KerioInstanceUserResponse {
|
|
240
|
+
id: string;
|
|
241
|
+
username: string;
|
|
242
|
+
name: string;
|
|
243
|
+
roles: {
|
|
244
|
+
userRole: string;
|
|
245
|
+
}
|
|
246
|
+
}
|
package/jsconfig.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brickert/kerio-connect-api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An unofficial API wrapper for Kerio Connect Mail Server jsonrpc Admin API.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kerio",
|
|
7
|
+
"connect",
|
|
8
|
+
"mail",
|
|
9
|
+
"server",
|
|
10
|
+
"email",
|
|
11
|
+
"wrapper",
|
|
12
|
+
"smtp",
|
|
13
|
+
"imap"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://gitlab.com/beckrickert/kerio-connect-api.git"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"author": "Beck Rickert",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "src/kerio.js",
|
|
26
|
+
"types": "index.d.ts",
|
|
27
|
+
"directories": {
|
|
28
|
+
"src": "src"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"cron": "^4.4.0",
|
|
32
|
+
"fastify": "^5.6.2",
|
|
33
|
+
"lodash": "^4.17.21",
|
|
34
|
+
"node-fetch": "^3.3.2",
|
|
35
|
+
"pino": "^10.2.0",
|
|
36
|
+
"pino-pretty": "^13.1.3",
|
|
37
|
+
"qs": "^6.14.1",
|
|
38
|
+
"set-cookie-parser": "^2.7.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import setCookie from 'set-cookie-parser';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {import('../index.d.ts').WebClient}
|
|
7
|
+
*/
|
|
8
|
+
export class WebClient {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('../index.d.ts').WebClientConfig} config
|
|
12
|
+
*/
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.hostname = config.hostname;
|
|
15
|
+
this.port = config.port ?? 4040;
|
|
16
|
+
this.endpoint_url = config.endpoint_url ?? "/admin/api/jsonrpc";
|
|
17
|
+
this.https = config.https ?? true;
|
|
18
|
+
|
|
19
|
+
this.fullURL = this.#getWebClientURL();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
#GLOBAL_REQUEST_TIMEOUT = 5000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Obtain the full URL string from WebClientConfig
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
#getWebClientURL() {
|
|
30
|
+
|
|
31
|
+
let baseURL;
|
|
32
|
+
|
|
33
|
+
if (this.https) {
|
|
34
|
+
baseURL = `https://${this.hostname}:${this.port}`;
|
|
35
|
+
} else {
|
|
36
|
+
baseURL = `http://${this.hostname}:${this.port}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new URL(this.endpoint_url, baseURL).toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Executes a request to the WebClient.fullURL with body of GenericJsonRPCBodyPayload
|
|
44
|
+
* @param {import('../index.d.ts').GenericJsonRPCBodyPayload} requestBody
|
|
45
|
+
* @param {import('../index.d.ts').HTTPMethod} method
|
|
46
|
+
* @param {import('node-fetch').RequestRedirect} follow_redirects
|
|
47
|
+
* @returns {Promise<import('../index.d.ts').GenericResponse>}
|
|
48
|
+
*/
|
|
49
|
+
async doRequest(requestBody = null, method = "GET", follow_redirects = "follow") {
|
|
50
|
+
try {
|
|
51
|
+
|
|
52
|
+
let fetchOptions = {
|
|
53
|
+
method: method,
|
|
54
|
+
headers: new Object(),
|
|
55
|
+
redirect: follow_redirects,
|
|
56
|
+
signal: AbortSignal.timeout(this.#GLOBAL_REQUEST_TIMEOUT)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof requestBody == 'object') {
|
|
60
|
+
fetchOptions.body = JSON.stringify(requestBody);
|
|
61
|
+
fetchOptions.headers['Content-Type'] = 'application/json';
|
|
62
|
+
} else {
|
|
63
|
+
throw "Expected Request Body to be an object";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this.https) {
|
|
67
|
+
fetchOptions.agent = new https.Agent({
|
|
68
|
+
rejectUnauthorized: false,
|
|
69
|
+
requestCert: true,
|
|
70
|
+
keepAlive: true
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
let response = await fetch(this.fullURL, fetchOptions);
|
|
76
|
+
|
|
77
|
+
if (response.status < 400) {
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @type {object}
|
|
81
|
+
*/
|
|
82
|
+
let response_data = await response.json();
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
_headers: response.headers.raw(),
|
|
86
|
+
_body: response_data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
} else {
|
|
90
|
+
throw "Unexpected HTTP response";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} catch (e) {
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { KerioModules } from './modules.js';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
/**
|
|
4
|
+
* @type {import('../index.d.ts').IPAddressGroup}
|
|
5
|
+
*/
|
|
6
|
+
export class IPAddressGroup extends KerioModules {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Gets a Map Set of IPAddressGroupItem indexed by their UID
|
|
10
|
+
* @param {string} group_name Name of the IPAddressGroup these items are a member of
|
|
11
|
+
* @returns {Promise<Map<import('../index.d.ts').KerioIPAddressID, import('../index.d.ts').IPAddressGroupItem>>}
|
|
12
|
+
*/
|
|
13
|
+
async listIPAddressItemsByGroupName(group_name = null) {
|
|
14
|
+
try {
|
|
15
|
+
|
|
16
|
+
if (!this.instance.logged_in) {
|
|
17
|
+
|
|
18
|
+
this.reset();
|
|
19
|
+
|
|
20
|
+
throw {
|
|
21
|
+
name: "KerioClientSessionError",
|
|
22
|
+
message: `Kerio session invalid. Try logging in again.`,
|
|
23
|
+
type: 'Kerio',
|
|
24
|
+
from: "Kerio.IPAddressGroups.listIPAddressItemsByGroupName"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if (!group_name) {
|
|
30
|
+
throw {
|
|
31
|
+
name: "KerioInvalidIPAddressGroupNameError",
|
|
32
|
+
message: `Invalid referred Group Name while processing API method 'IPAddressGroups.get' for name '${group_name}'`,
|
|
33
|
+
type: 'Kerio',
|
|
34
|
+
from: "Kerio.IPAddressGroups.listIPAddressItemsByGroupName"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
let ip_address_items_response = await this.sessionedRequest({
|
|
40
|
+
http_method: 'POST',
|
|
41
|
+
api_method: 'IpAddressGroups.get',
|
|
42
|
+
auth: {
|
|
43
|
+
cookie: this.instance.session_cookie,
|
|
44
|
+
token: this.instance.x_token
|
|
45
|
+
},
|
|
46
|
+
body_params: {
|
|
47
|
+
query: {
|
|
48
|
+
conditions: [
|
|
49
|
+
{
|
|
50
|
+
fieldName: 'name',
|
|
51
|
+
comparator: "Eq",
|
|
52
|
+
value: group_name
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let response_body = ip_address_items_response._body;
|
|
60
|
+
|
|
61
|
+
if (!response_body.result?.errors && !response_body?.error) {
|
|
62
|
+
/**
|
|
63
|
+
* @type {import('../index.d.ts').IPAddressGroupItem[]}
|
|
64
|
+
*/
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
let ipAddressItems = response_body.result.list;
|
|
67
|
+
|
|
68
|
+
if (ipAddressItems.length != 0 || response_body.result.totalItems > 0) {
|
|
69
|
+
//clean up properties who have empty strings and values
|
|
70
|
+
ipAddressItems.forEach(ipItem => {
|
|
71
|
+
return Object.fromEntries(Object.entries(ipItem).filter(([param, value]) => {
|
|
72
|
+
if (value != null && value !== "") {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let ipAddresslist = new Map();
|
|
79
|
+
|
|
80
|
+
ipAddressItems.forEach(item => {
|
|
81
|
+
ipAddresslist.set(item.id, item);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.instance.logger.debug({
|
|
85
|
+
name: "KerioListIPAddressItems",
|
|
86
|
+
message: `listed ${ipAddresslist.size} items in IPAddressGroup '${group_name}'`,
|
|
87
|
+
type: 'Kerio',
|
|
88
|
+
from: "Kerio.IPAddressGroups.listIPAddressItemsByGroupName"
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return ipAddresslist;
|
|
92
|
+
} else {
|
|
93
|
+
return new Map();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
} else {
|
|
97
|
+
throw {
|
|
98
|
+
name: "KerioRequestError",
|
|
99
|
+
message: `Error occured while fetching results from API method 'IPAddressGroups.get' by name of '${group_name}'`,
|
|
100
|
+
type: 'Kerio',
|
|
101
|
+
from: "Kerio.IPAddressGroups.listIPAddressItemsByGroupName"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
} catch (e) {
|
|
106
|
+
this.instance.logger.error(e);
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Add an IPAddressEntry whose a member under the specific IPAddressGroup name and returns the new KerioIPAddressID. This will also create the IPAddressGroup at root level (no parent) if it does not exist.
|
|
113
|
+
* @param {string} host_ip Host's IPv4/IPv6 to add
|
|
114
|
+
* @param {string} description Descriptive name for this host
|
|
115
|
+
* @param {string} group_name IPAddressGroup name this host is a member of
|
|
116
|
+
* @param {boolean} enabled Enabled or disabled for this entry, defaults true
|
|
117
|
+
* @returns {Promise<import('../index.d.ts').KerioIPAddressID>}
|
|
118
|
+
*/
|
|
119
|
+
async addHostToIPAddressGroup(host_ip, description = null, group_name = null, enabled = true) {
|
|
120
|
+
try {
|
|
121
|
+
|
|
122
|
+
if (!this.instance.logged_in) {
|
|
123
|
+
|
|
124
|
+
this.reset();
|
|
125
|
+
|
|
126
|
+
throw {
|
|
127
|
+
name: "KerioClientSessionError",
|
|
128
|
+
message: `Kerio session invalid. Try logging in again.`,
|
|
129
|
+
type: 'Kerio',
|
|
130
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (net.isIP(host_ip) == 0) {
|
|
135
|
+
throw {
|
|
136
|
+
name: "KerioInvalidHostError",
|
|
137
|
+
message: `Invalid Host IP while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
138
|
+
type: 'Kerio',
|
|
139
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!description) {
|
|
144
|
+
throw {
|
|
145
|
+
name: "KerioInvalidHostDescriptionError",
|
|
146
|
+
message: `Invalid Description for IPAddressEntry while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
147
|
+
type: 'Kerio',
|
|
148
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!group_name) {
|
|
153
|
+
throw {
|
|
154
|
+
name: "KerioInvalidIPAddressGroupNameError",
|
|
155
|
+
message: `Invalid referred Group Name while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
156
|
+
type: 'Kerio',
|
|
157
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (typeof enabled !== 'boolean') {
|
|
162
|
+
throw {
|
|
163
|
+
name: "KerioInvalidIPAddressEntryEnableError",
|
|
164
|
+
message: `Invalid Enabled value while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
165
|
+
type: 'Kerio',
|
|
166
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let add_host_response = await this.sessionedRequest({
|
|
171
|
+
http_method: 'POST',
|
|
172
|
+
api_method: 'IpAddressGroups.create',
|
|
173
|
+
auth: {
|
|
174
|
+
cookie: this.instance.session_cookie,
|
|
175
|
+
token: this.instance.x_token
|
|
176
|
+
},
|
|
177
|
+
body_params: {
|
|
178
|
+
groups: [
|
|
179
|
+
{
|
|
180
|
+
groupName: group_name,
|
|
181
|
+
host: host_ip,
|
|
182
|
+
type: "Host",
|
|
183
|
+
description: description,
|
|
184
|
+
enabled: enabled
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
let response_body = add_host_response._body;
|
|
191
|
+
|
|
192
|
+
if (response_body.result?.errors.length == 0) {
|
|
193
|
+
|
|
194
|
+
this.instance.logger.info({
|
|
195
|
+
name: "KerioAddHostIPAddressGroup",
|
|
196
|
+
message: `Added host ${host_ip} with description ${description} in IPAddressGroup '${group_name}'`,
|
|
197
|
+
type: 'Kerio',
|
|
198
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return response_body.result.result[0].id;
|
|
202
|
+
|
|
203
|
+
} else {
|
|
204
|
+
switch (response_body.result.errors[0].code) {
|
|
205
|
+
case 1001:
|
|
206
|
+
throw {
|
|
207
|
+
name: "KerioDuplicateError",
|
|
208
|
+
message: `Duplicate Host IP while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
209
|
+
type: 'Kerio',
|
|
210
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
211
|
+
}
|
|
212
|
+
case 1003:
|
|
213
|
+
throw {
|
|
214
|
+
name: "KerioInvalidHostError",
|
|
215
|
+
message: `Invalid Host IP while processing API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
216
|
+
type: 'Kerio',
|
|
217
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
218
|
+
}
|
|
219
|
+
default:
|
|
220
|
+
throw {
|
|
221
|
+
name: "KerioRequestError",
|
|
222
|
+
message: `Error occured while fetching results from API method 'IPAddressGroups.create' for host '${host_ip}' in group '${group_name}'`,
|
|
223
|
+
type: 'Kerio',
|
|
224
|
+
from: "Kerio.IPAddressGroups.addHostToIPAddressGroup"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
} catch (e) {
|
|
230
|
+
this.instance.logger.error(e);
|
|
231
|
+
throw e;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Remove an IPAddressEntry from the specific IPAddressGroup name. Returns true when successfully removed.
|
|
237
|
+
* @param {string} host_ip Host's IPv4/IPv6 to add
|
|
238
|
+
* @param {string} group_name IPAddressGroup name this host is a member of
|
|
239
|
+
* @returns {Promise<boolean>} Returns true when removal was successful
|
|
240
|
+
*/
|
|
241
|
+
async removeHostFromIPAddressGroupByIP(host_ip, group_name = null) {
|
|
242
|
+
try {
|
|
243
|
+
|
|
244
|
+
if (!this.instance.logged_in) {
|
|
245
|
+
|
|
246
|
+
this.reset();
|
|
247
|
+
|
|
248
|
+
throw {
|
|
249
|
+
name: "KerioClientSessionError",
|
|
250
|
+
message: `Kerio session invalid. Try logging in again.`,
|
|
251
|
+
type: 'Kerio',
|
|
252
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (net.isIP(host_ip) == 0) {
|
|
257
|
+
throw {
|
|
258
|
+
name: "KerioInvalidHostError",
|
|
259
|
+
message: `Invalid Host IP while processing API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
260
|
+
type: 'Kerio',
|
|
261
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!group_name) {
|
|
266
|
+
throw {
|
|
267
|
+
name: "KerioInvalidIPAddressGroupNameError",
|
|
268
|
+
message: `Invalid referred Group Name while processing API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
269
|
+
type: 'Kerio',
|
|
270
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let get_host_ID_response = await this.listIPAddressItemsByGroupName(group_name);
|
|
275
|
+
|
|
276
|
+
if (get_host_ID_response.size != 0) {
|
|
277
|
+
|
|
278
|
+
var foundHostID = "";
|
|
279
|
+
|
|
280
|
+
for (const [hostID, entryproperties] of get_host_ID_response) {
|
|
281
|
+
if (entryproperties.host == host_ip) {
|
|
282
|
+
foundHostID = hostID;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (foundHostID.length > 0) {
|
|
288
|
+
let remove_host_response = await this.sessionedRequest({
|
|
289
|
+
http_method: 'POST',
|
|
290
|
+
api_method: 'IpAddressGroups.remove',
|
|
291
|
+
auth: {
|
|
292
|
+
cookie: this.instance.session_cookie,
|
|
293
|
+
token: this.instance.x_token
|
|
294
|
+
},
|
|
295
|
+
body_params: {
|
|
296
|
+
groupIds: [foundHostID]
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
let response_body = remove_host_response._body;
|
|
301
|
+
|
|
302
|
+
if (Array.isArray(response_body.result.errors) && response_body.result.errors.length == 0) {
|
|
303
|
+
|
|
304
|
+
this.instance.logger.info({
|
|
305
|
+
name: "KerioRemoveHostIPAddressGroup",
|
|
306
|
+
message: `Removed host '${host_ip}' in group '${group_name}'`,
|
|
307
|
+
type: 'Kerio',
|
|
308
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return true;
|
|
312
|
+
|
|
313
|
+
} else {
|
|
314
|
+
switch (response_body.result.errors[0].code) {
|
|
315
|
+
case 1002:
|
|
316
|
+
throw {
|
|
317
|
+
name: "KerioHostRemoveError",
|
|
318
|
+
message: `Host does not exist within group while fetching results from API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
319
|
+
type: 'Kerio',
|
|
320
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
321
|
+
}
|
|
322
|
+
case 1003:
|
|
323
|
+
throw {
|
|
324
|
+
name: "KerioHostRemoveError",
|
|
325
|
+
message: `Host ID invalid while fetching results from API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
326
|
+
type: 'Kerio',
|
|
327
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
328
|
+
}
|
|
329
|
+
default:
|
|
330
|
+
throw {
|
|
331
|
+
name: "KerioRequestError",
|
|
332
|
+
message: `Error occured while fetching results from API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
333
|
+
type: 'Kerio',
|
|
334
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
throw {
|
|
340
|
+
name: "KerioHostNotExistError",
|
|
341
|
+
message: `Host IP does not exist in group while processing API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
342
|
+
type: 'Kerio',
|
|
343
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
throw {
|
|
348
|
+
name: "KerioHostNotExistGroupError",
|
|
349
|
+
message: `Group has no hosts while processing API method 'IPAddressGroups.remove' for host '${host_ip}' in group '${group_name}'`,
|
|
350
|
+
type: 'Kerio',
|
|
351
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByIP"
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
} catch (e) {
|
|
356
|
+
this.instance.logger.error(e);
|
|
357
|
+
throw e;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async removeHostFromIPAddressGroupByDesc(description = null, group_name = null, remove_limit = 1) {
|
|
362
|
+
try {
|
|
363
|
+
|
|
364
|
+
if (!this.instance.logged_in) {
|
|
365
|
+
|
|
366
|
+
this.reset();
|
|
367
|
+
|
|
368
|
+
throw {
|
|
369
|
+
name: "KerioClientSessionError",
|
|
370
|
+
message: `Kerio session invalid. Try logging in again.`,
|
|
371
|
+
type: 'Kerio',
|
|
372
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (remove_limit < -1 || !Number.isInteger(remove_limit)) {
|
|
377
|
+
throw {
|
|
378
|
+
name: "KerioInvalidGroupEntryRemoveLimitError",
|
|
379
|
+
message: `Invalid number of max host entries to remove while processing API method 'IPAddressGroups.remove' for description '${description}' in group '${group_name}'`,
|
|
380
|
+
type: 'Kerio',
|
|
381
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!description) {
|
|
386
|
+
throw {
|
|
387
|
+
name: "KerioInvalidGroupEntryDescriptionError",
|
|
388
|
+
message: `Invalid Description to search query while processing API method 'IPAddressGroups.remove' for description '${description}' in group '${group_name}'`,
|
|
389
|
+
type: 'Kerio',
|
|
390
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!group_name) {
|
|
395
|
+
throw {
|
|
396
|
+
name: "KerioInvalidIPAddressGroupNameError",
|
|
397
|
+
message: `Invalid referred Group Name while processing API method 'IPAddressGroups.remove' for description '${description}' in group '${group_name}'`,
|
|
398
|
+
type: 'Kerio',
|
|
399
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let get_host_ID_response = await this.listIPAddressItemsByGroupName(group_name);
|
|
404
|
+
|
|
405
|
+
if (get_host_ID_response.size != 0) {
|
|
406
|
+
|
|
407
|
+
var foundHostIds = new Array();
|
|
408
|
+
var foundHostIPs = new Array();
|
|
409
|
+
|
|
410
|
+
for (const [hostID, entryproperties] of get_host_ID_response) {
|
|
411
|
+
if (entryproperties.description == description) {
|
|
412
|
+
foundHostIds.push(hostID);
|
|
413
|
+
foundHostIPs.push(entryproperties.host);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (remove_limit > 0) {
|
|
418
|
+
//only the beginning of the array is kept
|
|
419
|
+
foundHostIPs = foundHostIPs.slice(0, remove_limit);
|
|
420
|
+
foundHostIds = foundHostIds.slice(0, remove_limit);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (foundHostIds.length > 0) {
|
|
424
|
+
let remove_host_response = await this.sessionedRequest({
|
|
425
|
+
http_method: 'POST',
|
|
426
|
+
api_method: 'IpAddressGroups.remove',
|
|
427
|
+
auth: {
|
|
428
|
+
cookie: this.instance.session_cookie,
|
|
429
|
+
token: this.instance.x_token
|
|
430
|
+
},
|
|
431
|
+
body_params: {
|
|
432
|
+
groupIds: foundHostIds
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
let response_body = remove_host_response._body;
|
|
437
|
+
|
|
438
|
+
if (Array.isArray(response_body.result.errors) && response_body.result.errors.length == 0) {
|
|
439
|
+
|
|
440
|
+
return foundHostIPs;
|
|
441
|
+
|
|
442
|
+
} else {
|
|
443
|
+
switch (response_body.result.errors[0].code) {
|
|
444
|
+
case 1002:
|
|
445
|
+
throw {
|
|
446
|
+
name: "KerioHostRemoveError",
|
|
447
|
+
message: `Host does not exist within group while fetching results from API method 'IPAddressGroups.remove' in group '${group_name}'`,
|
|
448
|
+
type: 'Kerio',
|
|
449
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
450
|
+
}
|
|
451
|
+
case 1003:
|
|
452
|
+
throw {
|
|
453
|
+
name: "KerioHostRemoveError",
|
|
454
|
+
message: `Host ID invalid while fetching results from API method 'IPAddressGroups.remove' in group '${group_name}'`,
|
|
455
|
+
type: 'Kerio',
|
|
456
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
457
|
+
}
|
|
458
|
+
default:
|
|
459
|
+
throw {
|
|
460
|
+
name: "KerioRequestError",
|
|
461
|
+
message: `Error occured while fetching results from API method 'IPAddressGroups.remove' in group '${group_name}'`,
|
|
462
|
+
type: 'Kerio',
|
|
463
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
throw {
|
|
469
|
+
name: "KerioDescriptionHostsNotExistError",
|
|
470
|
+
message: `Host ID(s) from specified description does not exist in group while processing API method 'IPAddressGroups.remove' for description '${description}' in group '${group_name}'`,
|
|
471
|
+
type: 'Kerio',
|
|
472
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
throw {
|
|
477
|
+
name: "KerioHostNotExistGroupError",
|
|
478
|
+
message: `Group has no hosts with the specified description while processing API method 'IPAddressGroups.remove' for description '${description}' in group '${group_name}'`,
|
|
479
|
+
type: 'Kerio',
|
|
480
|
+
from: "Kerio.IPAddressGroups.removeHostFromIPAddressGroupByDesc"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
} catch (e) {
|
|
485
|
+
this.instance.logger.error(e);
|
|
486
|
+
throw e;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
package/src/kerio.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { IPAddressGroup } from "./ipaddressgroups.js";
|
|
2
|
+
import { Session } from "./session.js";
|
|
3
|
+
import { WebClient } from "./client.js";
|
|
4
|
+
import { CronJob } from 'cron';
|
|
5
|
+
import pino from 'pino';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
//https://manuals.gfi.com/en/kerio/api/connect/admin/reference/index.html
|
|
9
|
+
|
|
10
|
+
export class Kerio extends WebClient {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('../index.d.ts').WebClientConfig} config
|
|
14
|
+
* @param {import('../index.d.ts').KerioUserAgent} userAgent
|
|
15
|
+
* @param {import('../index.d.ts').KerioLogConfig} logConfig
|
|
16
|
+
*/
|
|
17
|
+
constructor(config, userAgent, logConfig) {
|
|
18
|
+
super(config);
|
|
19
|
+
|
|
20
|
+
this.userAgent = userAgent;
|
|
21
|
+
|
|
22
|
+
this.IPAddressGroups = new IPAddressGroup(this);
|
|
23
|
+
|
|
24
|
+
this.Session = new Session(this);
|
|
25
|
+
|
|
26
|
+
this.logger = pino({
|
|
27
|
+
level: "debug",
|
|
28
|
+
transport: {
|
|
29
|
+
targets: [
|
|
30
|
+
{
|
|
31
|
+
//Pretty logging to stdout
|
|
32
|
+
target: 'pino-pretty',
|
|
33
|
+
level: "info",
|
|
34
|
+
options: {
|
|
35
|
+
destination: 1
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
target: 'pino/file',
|
|
40
|
+
level: logConfig.logLevel ?? "debug",
|
|
41
|
+
options: {
|
|
42
|
+
destination: path.join(process.cwd(), 'log', 'kerio.log'),
|
|
43
|
+
mkdir: true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Defined once successfully logged in via Kerio.login(user: KerioUser).
|
|
53
|
+
* Contains the name=value cookie to be passed in subsequent requests in the Cookie header.
|
|
54
|
+
* @type {string}
|
|
55
|
+
*/
|
|
56
|
+
session_cookie = null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Defined once successfully logged in via Kerio.login(user: KerioUser). Used in subsequent requests in the X-Token header.
|
|
60
|
+
* @type {string}
|
|
61
|
+
*/
|
|
62
|
+
x_token = null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* If successfully logged in via Kerio.login(user: KerioUser). This becomes true.
|
|
66
|
+
* @type {boolean}
|
|
67
|
+
*/
|
|
68
|
+
logged_in = false;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The heartbeat cron job to retain the current logged in session on an interval.
|
|
72
|
+
* @type {object}
|
|
73
|
+
*/
|
|
74
|
+
heartbeat = new Object();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @type {pino.Logger}
|
|
78
|
+
*/
|
|
79
|
+
logger;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Attempts to login with the specified Kerio user
|
|
83
|
+
* @param {import('../index.d.ts').KerioUser} user
|
|
84
|
+
*/
|
|
85
|
+
async login(user) {
|
|
86
|
+
try {
|
|
87
|
+
let login_response = await this.doRequest({
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
id: 1,
|
|
90
|
+
method: "Session.login",
|
|
91
|
+
params: {
|
|
92
|
+
userName: user.username,
|
|
93
|
+
password: user.password,
|
|
94
|
+
application: this.userAgent
|
|
95
|
+
}
|
|
96
|
+
}, "POST", "manual");
|
|
97
|
+
|
|
98
|
+
let response_body = login_response._body;
|
|
99
|
+
|
|
100
|
+
if (!response_body.result?.errors && !response_body?.error) {
|
|
101
|
+
|
|
102
|
+
this.x_token = response_body.result.token;
|
|
103
|
+
|
|
104
|
+
let cookies = login_response._headers["set-cookie"];
|
|
105
|
+
let raw_session_connect_webadmin_cookie = cookies.find(cookie_row => cookie_row.includes("SESSION_CONNECT_WEBADMIN"));
|
|
106
|
+
this.session_cookie = raw_session_connect_webadmin_cookie.split(";")[0];
|
|
107
|
+
|
|
108
|
+
this.logged_in = true;
|
|
109
|
+
|
|
110
|
+
this.logger.info({
|
|
111
|
+
name: "KerioLogin",
|
|
112
|
+
message: `Logged into Kerio successfully`,
|
|
113
|
+
type: "Kerio",
|
|
114
|
+
from: "Kerio.login"
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.logger.debug({
|
|
118
|
+
name: "KerioSession",
|
|
119
|
+
message: `Obtained valid cookies and tokens for current session`,
|
|
120
|
+
type: "Kerio",
|
|
121
|
+
from: "Kerio.login"
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.heartbeat = this._createHeartBeatCronJob();
|
|
125
|
+
|
|
126
|
+
this.logger.debug({
|
|
127
|
+
name: "KerioHeartBeatTask",
|
|
128
|
+
message: `Created session heartbeat cron task to renew session on an interval`,
|
|
129
|
+
type: "Kerio",
|
|
130
|
+
from: "Kerio.login"
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
this.heartbeat.start();
|
|
134
|
+
|
|
135
|
+
this.logger.debug({
|
|
136
|
+
name: "KerioHeartBeat",
|
|
137
|
+
message: `Started session heartbeat cron task`,
|
|
138
|
+
type: "Kerio",
|
|
139
|
+
from: "Kerio.login"
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return;
|
|
143
|
+
|
|
144
|
+
} else {
|
|
145
|
+
if (response_body.error.code == 1000) {
|
|
146
|
+
|
|
147
|
+
throw {
|
|
148
|
+
name: "KerioLoginError",
|
|
149
|
+
message: `Kerio username or password is incorrect`,
|
|
150
|
+
type: 'Kerio',
|
|
151
|
+
from: "Kerio.login"
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
|
|
155
|
+
throw {
|
|
156
|
+
name: "KerioRequestError",
|
|
157
|
+
message: `Error occured while attempting login to Kerio`,
|
|
158
|
+
type: 'Kerio',
|
|
159
|
+
from: "Kerio.login"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
} catch (e) {
|
|
165
|
+
this.logged_in = false;
|
|
166
|
+
this.session_cookie = null;
|
|
167
|
+
this.x_token = null;
|
|
168
|
+
|
|
169
|
+
if (e.name == "AbortError" || e.name == "TimeoutError") {
|
|
170
|
+
|
|
171
|
+
this.logger.error({
|
|
172
|
+
name: "KerioLoginTimeout",
|
|
173
|
+
message: `Login attempt to Kerio timed out`,
|
|
174
|
+
type: 'Kerio',
|
|
175
|
+
from: "Kerio.login"
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
throw {
|
|
179
|
+
name: "KerioLoginTimeout",
|
|
180
|
+
message: "Login attempt Kerio timed out",
|
|
181
|
+
type: "Kerio",
|
|
182
|
+
from: "Kerio.login"
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
|
|
186
|
+
this.logger.error(e);
|
|
187
|
+
|
|
188
|
+
throw e;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_createHeartBeatCronJob() {
|
|
194
|
+
return CronJob.from({
|
|
195
|
+
cronTime: `*/15 * * * *`,
|
|
196
|
+
onTick: async () => {
|
|
197
|
+
try {
|
|
198
|
+
await this.renewSessionHeartBeat();
|
|
199
|
+
} catch (e) {
|
|
200
|
+
throw e;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
start: false,
|
|
204
|
+
runOnInit: false,
|
|
205
|
+
waitForCompletion: true
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async renewSessionHeartBeat() {
|
|
210
|
+
try {
|
|
211
|
+
if (!this.logged_in) {
|
|
212
|
+
|
|
213
|
+
throw {
|
|
214
|
+
name: "KerioClientSessionRenewError",
|
|
215
|
+
message: `Kerio session invalid to renew. Try logging in again.`,
|
|
216
|
+
type: 'Kerio',
|
|
217
|
+
from: "Kerio.renewSessionHeartBeat"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.session_cookie == null) {
|
|
222
|
+
|
|
223
|
+
throw {
|
|
224
|
+
name: "KerioClientSessionRenewError",
|
|
225
|
+
message: `Kerio session cookie invalid to renew. Try logging in again.`,
|
|
226
|
+
type: 'Kerio',
|
|
227
|
+
from: "Kerio.renewSessionHeartBeat"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.x_token == null) {
|
|
232
|
+
|
|
233
|
+
throw {
|
|
234
|
+
name: "KerioClientSessionRenewError",
|
|
235
|
+
message: `Kerio session token invalid to renew. Try logging in again.`,
|
|
236
|
+
type: 'Kerio',
|
|
237
|
+
from: "Kerio.renewSessionHeartBeat"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let renewed = await this.Session.whoAmI();
|
|
242
|
+
|
|
243
|
+
if (renewed.id) {
|
|
244
|
+
this.logger.info({
|
|
245
|
+
name: "KerioClientSessionRenew",
|
|
246
|
+
message: `Kerio session renewed, timeout extended`,
|
|
247
|
+
type: 'Kerio',
|
|
248
|
+
from: "Kerio.renewSessionHeartBeat"
|
|
249
|
+
});
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
} catch (e) {
|
|
254
|
+
this.logger.error(e);
|
|
255
|
+
throw e;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
}
|
package/src/modules.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
|
|
4
|
+
export class KerioModules {
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../index.d.ts').Kerio} instance
|
|
7
|
+
*/
|
|
8
|
+
constructor(instance) {
|
|
9
|
+
/**
|
|
10
|
+
* @type {import('../index.d.ts').Kerio}
|
|
11
|
+
*/
|
|
12
|
+
this.instance = instance;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {import("../index.d.ts").SessionedRequestOptions} options
|
|
17
|
+
* @returns {Promise<import('../index.d.ts').GenericResponse>}
|
|
18
|
+
*/
|
|
19
|
+
async sessionedRequest(options) {
|
|
20
|
+
try {
|
|
21
|
+
|
|
22
|
+
if (!this.instance.logged_in) {
|
|
23
|
+
|
|
24
|
+
throw {
|
|
25
|
+
name: "KerioClientSessionError",
|
|
26
|
+
message: "This Kerio instance currently has no valid login session",
|
|
27
|
+
type: "Kerio"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!this.instance.session_cookie?.includes("SESSION_CONNECT_WEBADMIN") || this.instance.x_token?.length != 64) {
|
|
32
|
+
throw {
|
|
33
|
+
name: "KerioClientSessionTokenError",
|
|
34
|
+
message: "This Kerio instance currently has no valid tokens",
|
|
35
|
+
type: "Kerio"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let fetchOptions = {
|
|
40
|
+
method: options.http_method,
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Cookie': options.auth.cookie,
|
|
44
|
+
'X-Token': options.auth.token
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
jsonrpc: '2.0',
|
|
48
|
+
id: 1,
|
|
49
|
+
method: options.api_method,
|
|
50
|
+
params: options.body_params
|
|
51
|
+
}),
|
|
52
|
+
redirect: 'follow',
|
|
53
|
+
signal: AbortSignal.timeout(5000)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.instance.https) {
|
|
57
|
+
fetchOptions.agent = new https.Agent({
|
|
58
|
+
rejectUnauthorized: false,
|
|
59
|
+
requestCert: true,
|
|
60
|
+
keepAlive: true
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
let sessionResponse = await fetch(this.instance.fullURL, fetchOptions);
|
|
66
|
+
|
|
67
|
+
if (sessionResponse.ok) {
|
|
68
|
+
|
|
69
|
+
let sessionResponseBody = await sessionResponse.json();
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
_headers: sessionResponse.headers.raw(),
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
_body: sessionResponseBody
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} else {
|
|
78
|
+
let errBody = await sessionResponse.text();
|
|
79
|
+
let _e = {
|
|
80
|
+
name: "HTTPError",
|
|
81
|
+
message: `HTTP Error ${sessionResponse.status} ${sessionResponse.statusText} ${fetchOptions.method} ${options.api_method}`,
|
|
82
|
+
type: 'fetch',
|
|
83
|
+
data: errBody
|
|
84
|
+
}
|
|
85
|
+
throw(_e);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
} catch (e) {
|
|
89
|
+
this.instance.logger.debug(e);
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset login flag and session token and cookie
|
|
96
|
+
* @returns {void}
|
|
97
|
+
*/
|
|
98
|
+
reset() {
|
|
99
|
+
this.instance.logged_in = false;
|
|
100
|
+
this.instance.session_cookie = null;
|
|
101
|
+
this.instance.x_token = null;
|
|
102
|
+
|
|
103
|
+
this.instance.logger.debug({
|
|
104
|
+
name: "KerioInstanceSessionReset",
|
|
105
|
+
message: "Reset login flag, session cookie, and token to defaults",
|
|
106
|
+
type: "Kerio",
|
|
107
|
+
from: "Kerio._Modules.reset"
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/session.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { KerioModules } from './modules.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {import('../index.d.ts').Session}
|
|
5
|
+
*/
|
|
6
|
+
export class Session extends KerioModules {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Obtain the Kerio Instance's user details. Also used to renew the Instance's session to prevent timeout expiration.
|
|
10
|
+
* @returns {Promise<import('../index.d.ts').KerioInstanceUserResponse>}
|
|
11
|
+
*/
|
|
12
|
+
async whoAmI() {
|
|
13
|
+
try {
|
|
14
|
+
if (!this.instance.logged_in) {
|
|
15
|
+
throw {
|
|
16
|
+
name: "KerioSessionErrorError",
|
|
17
|
+
message: `Kerio session invalid. Try logging in again.`,
|
|
18
|
+
type: 'Kerio',
|
|
19
|
+
from: "Kerio.Session.whoAmI"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let whoami_response = await this.sessionedRequest({
|
|
24
|
+
http_method: 'POST',
|
|
25
|
+
api_method: 'Session.whoAmI',
|
|
26
|
+
auth: {
|
|
27
|
+
cookie: this.instance.session_cookie,
|
|
28
|
+
token: this.instance.x_token
|
|
29
|
+
},
|
|
30
|
+
body_params: {
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let response_body = whoami_response._body;
|
|
35
|
+
|
|
36
|
+
if (!response_body.result?.errors && !response_body?.error) {
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
id: response_body.result.userDetails.id,
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
username: response_body.result.userDetails.loginName,
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
name: response_body.result.userDetails.fullName,
|
|
45
|
+
roles: {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
userRole: response_body.result.userDetails.effectiveRole.userRole
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} else {
|
|
52
|
+
throw {
|
|
53
|
+
name: "KerioRequestError",
|
|
54
|
+
message: `Error occured while fetching results from API method 'Session.whoAmI'`,
|
|
55
|
+
type: 'Kerio',
|
|
56
|
+
from: "Kerio.Session.whoAmI"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
} catch (e) {
|
|
61
|
+
this.instance.logger.error(e);
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|