@continuoussecuritytooling/keycloak-reporter 1.1.9 → 1.2.0-3084
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/.github/workflows/pipeline.yml +2 -2
- package/CHANGELOG.md +9 -0
- package/Dockerfile +1 -1
- package/README.md +26 -1
- package/charts/keycloak-reporter/Chart.yaml +2 -2
- package/charts/keycloak-reporter/README.md +1 -1
- package/config/schema.json +1 -1
- package/dist/config/schema.json +1 -1
- package/dist/lib/output.js +22 -0
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/report.js +3 -0
- package/dist/lib/report.js.map +1 -1
- package/e2e/spec/webhooks.js +127 -78
- package/lib/output.ts +23 -1
- package/lib/report.ts +3 -0
- package/package.json +1 -1
- package/test/output.spec.ts +129 -0
- package/continuoussecuritytooling-keycloak-reporting-cli-latest_digest.txt +0 -1
- package/keycloak-reporter-1.4.10.tgz +0 -0
|
@@ -74,7 +74,7 @@ jobs:
|
|
|
74
74
|
|
|
75
75
|
- name: Create kind cluster
|
|
76
76
|
if: steps.list-changed.outputs.changed == 'true'
|
|
77
|
-
uses: helm/kind-action@v1.
|
|
77
|
+
uses: helm/kind-action@v1.14.0
|
|
78
78
|
|
|
79
79
|
- name: Run chart-testing (install - no further args)
|
|
80
80
|
if: steps.list-changed.outputs.changed == 'true'
|
|
@@ -88,7 +88,7 @@ jobs:
|
|
|
88
88
|
--from-literal=clientSecret=test
|
|
89
89
|
ct install --target-branch ${{ github.event.repository.default_branch }} --namespace kc-reporter --helm-extra-set-args "-f charts/keycloak-reporter/ci.values.yaml"
|
|
90
90
|
|
|
91
|
-
- uses: actions/upload-artifact@
|
|
91
|
+
- uses: actions/upload-artifact@v7
|
|
92
92
|
with:
|
|
93
93
|
name: dist-folder
|
|
94
94
|
path: dist
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/ContinuousSecurityTooling/keycloak-reporter/compare/v1.1.9...v1.2.0) (2026-03-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **Webhooks:** Adding generic webhook support ([c79ca91](https://github.com/ContinuousSecurityTooling/keycloak-reporter/commit/c79ca917c4debd0796bcd10117f3c7b5924b5806)), closes [#240](https://github.com/ContinuousSecurityTooling/keycloak-reporter/issues/240)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
1
10
|
## [1.1.9](https://github.com/ContinuousSecurityTooling/keycloak-reporter/compare/v1.1.8...v1.1.9) (2026-02-14)
|
|
2
11
|
|
|
3
12
|
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ You can also provider a config file via env var `CONFIG_FILE` and then just prov
|
|
|
75
75
|
CONFIG_FILE==$(pwd)/e2e/fixtures/config.json kc-reporter listClients
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
### Post to Slack or
|
|
78
|
+
### Post to Slack, Teams or Generic Webhook
|
|
79
79
|
|
|
80
80
|
When using this command:
|
|
81
81
|
```
|
|
@@ -90,3 +90,28 @@ kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=j
|
|
|
90
90
|
```
|
|
91
91
|
the following entry in slack will be created:
|
|
92
92
|

|
|
93
|
+
|
|
94
|
+
#### Generic Webhook
|
|
95
|
+
|
|
96
|
+
The `generic` webhook type posts a plain JSON payload via HTTP POST to any URL, making it compatible with custom integrations, automation platforms, or any HTTP endpoint.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=generic --webhookUrl=$WEBHOOK_URL
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The JSON payload sent to the endpoint has the following structure:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"title": "Users Report",
|
|
107
|
+
"date": "10-3-2026",
|
|
108
|
+
"reportContent": "{...}",
|
|
109
|
+
"message": "Optional message (only included when --webhookMessage is provided)"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
An optional message can be included:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=generic --webhookUrl=$WEBHOOK_URL --webhookMessage="Scheduled report"
|
|
117
|
+
```
|
|
@@ -15,14 +15,14 @@ type: application
|
|
|
15
15
|
# This is the chart version. This version number should be incremented each time you make changes
|
|
16
16
|
# to the chart and its templates, including the app version.
|
|
17
17
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
|
18
|
-
version: 1.4.
|
|
18
|
+
version: 1.4.11
|
|
19
19
|
|
|
20
20
|
# This is the version number of the application being deployed. This version number should be
|
|
21
21
|
# incremented each time you make changes to the application. Versions are not expected to
|
|
22
22
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
|
23
23
|
# It is recommended to use it with quotes.
|
|
24
24
|
# renovate: datasource=docker depName=ContinuousSecurityTooling/keycloak-reporter
|
|
25
|
-
appVersion: 1.
|
|
25
|
+
appVersion: 1.2.0
|
|
26
26
|
maintainers:
|
|
27
27
|
# Martin Reinhardt
|
|
28
28
|
- name: hypery2k
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# keycloak-reporter
|
|
2
2
|
|
|
3
|
-
  
|
|
4
4
|
|
|
5
5
|
Keycloak user and client reporting tool for automated regular access checks.
|
|
6
6
|
|
package/config/schema.json
CHANGED
package/dist/config/schema.json
CHANGED
package/dist/lib/output.js
CHANGED
|
@@ -4,6 +4,7 @@ var WebhookType;
|
|
|
4
4
|
(function (WebhookType) {
|
|
5
5
|
WebhookType["SLACK"] = "slack";
|
|
6
6
|
WebhookType["TEAMS"] = "teams";
|
|
7
|
+
WebhookType["GENERIC"] = "generic";
|
|
7
8
|
})(WebhookType || (WebhookType = {}));
|
|
8
9
|
export async function post2Webhook(type, url, title, reportContent, text) {
|
|
9
10
|
const date = new Date();
|
|
@@ -59,6 +60,27 @@ export async function post2Webhook(type, url, title, reportContent, text) {
|
|
|
59
60
|
}
|
|
60
61
|
]
|
|
61
62
|
});
|
|
63
|
+
case WebhookType.GENERIC.toString(): {
|
|
64
|
+
const payload = {
|
|
65
|
+
title,
|
|
66
|
+
date: `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`,
|
|
67
|
+
reportContent
|
|
68
|
+
};
|
|
69
|
+
if (text != null) {
|
|
70
|
+
payload.message = text;
|
|
71
|
+
}
|
|
72
|
+
const response = await fetch(url, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify(payload)
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const error = new Error(`Generic webhook request failed with status ${response.status}`);
|
|
79
|
+
error.code = 'generic_webhook_http_error';
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
62
84
|
// defaulting to Slack
|
|
63
85
|
default:
|
|
64
86
|
// eslint-disable-next-line no-case-declarations
|
package/dist/lib/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../lib/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEjE,IAAK,
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../lib/output.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/report.js
CHANGED
|
@@ -36,6 +36,9 @@ export async function convert(cfg) {
|
|
|
36
36
|
case 'slack_webhook_http_error':
|
|
37
37
|
logger.error('Invalid Slack Webhook Payload. Check your params.');
|
|
38
38
|
throw new Error('Invalid Slack Payload');
|
|
39
|
+
case 'generic_webhook_http_error':
|
|
40
|
+
logger.error('Generic Webhook request failed. Check your URL and params.');
|
|
41
|
+
throw new Error('Invalid Generic Webhook request');
|
|
39
42
|
default:
|
|
40
43
|
logger.error(`Error during sending webhook.(${e === null || e === void 0 ? void 0 : e.code})`, e === null || e === void 0 ? void 0 : e.original);
|
|
41
44
|
throw e;
|
package/dist/lib/report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../lib/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAkB;IAC9C,IAAI,aAAqB,CAAC;IAC1B,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,KAAK;YACR,aAAa,GAAG,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC7D,MAAM;QACR,qBAAqB;QACrB;YACE,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,aAAa,CACX,IAAI,CAAC,IAAI,CACP,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAC1B,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CACjH,EACD,aAAa,CACd,CAAC;IACJ,CAAC;IACD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;gBACpB,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC;gBACrE,MAAM,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACzG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;oBAC5B,KAAK,qCAAqC;wBACxC,MAAM,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;wBAClE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;oBAC3C,KAAK,0BAA0B;wBAC7B,MAAM,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;wBAClE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;oBAC3C;wBACE,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,IAAI,GAAG,EAAE,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,CAAC,CAAC;wBACvE,MAAM,CAAC,CAAC;gBACZ,CAAC;YACH,CAAC;YACD,MAAM;QACR,6BAA6B;QAC7B;YACE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../lib/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAkB;IAC9C,IAAI,aAAqB,CAAC;IAC1B,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,KAAK;YACR,aAAa,GAAG,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC7D,MAAM;QACR,qBAAqB;QACrB;YACE,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,aAAa,CACX,IAAI,CAAC,IAAI,CACP,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAC1B,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CACjH,EACD,aAAa,CACd,CAAC;IACJ,CAAC;IACD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;gBACpB,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC;gBACrE,MAAM,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACzG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;oBAC5B,KAAK,qCAAqC;wBACxC,MAAM,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;wBAClE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;oBAC3C,KAAK,0BAA0B;wBAC7B,MAAM,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;wBAClE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;oBAC3C,KAAK,4BAA4B;wBAC/B,MAAM,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;wBAC3E,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;oBACrD;wBACE,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,IAAI,GAAG,EAAE,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,CAAC,CAAC;wBACvE,MAAM,CAAC,CAAC;gBACZ,CAAC;YACH,CAAC;YACD,MAAM;QACR,6BAA6B;QAC7B;YACE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
|
package/e2e/spec/webhooks.js
CHANGED
|
@@ -15,12 +15,12 @@ test('Should post message to Teams', { timeout: 10000 }, (t) => {
|
|
|
15
15
|
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
16
16
|
'--output=webhook',
|
|
17
17
|
'--webhookType=teams',
|
|
18
|
-
'--webhookUrl=' + process.env.WEBHOOK_TESTING_TEAMS
|
|
18
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_TEAMS,
|
|
19
19
|
],
|
|
20
20
|
{
|
|
21
21
|
env: {
|
|
22
|
-
...process.env
|
|
23
|
-
}
|
|
22
|
+
...process.env,
|
|
23
|
+
},
|
|
24
24
|
}
|
|
25
25
|
);
|
|
26
26
|
cli.stdout.on('data', (chunk) => {
|
|
@@ -34,79 +34,67 @@ test('Should post message to Teams', { timeout: 10000 }, (t) => {
|
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
test(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
cli.stdout.on('end', () => {
|
|
69
|
-
t.end();
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
);
|
|
37
|
+
test('Should post message to Teams with additional message', { timeout: 10000 }, (t) => {
|
|
38
|
+
const cli = spawn(
|
|
39
|
+
path.join(path.dirname('.'), 'node'),
|
|
40
|
+
[
|
|
41
|
+
'dist/cli.js',
|
|
42
|
+
'listUsers',
|
|
43
|
+
'http://localhost:8080',
|
|
44
|
+
'keycloak-reporter',
|
|
45
|
+
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
46
|
+
'--output=webhook',
|
|
47
|
+
'--webhookType=teams',
|
|
48
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_TEAMS,
|
|
49
|
+
'--webhookMessage="' + (process.env.WEBHOOK_ADDITIONAL_MESSAGE || 'From Github Actions') + '"',
|
|
50
|
+
],
|
|
51
|
+
{
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
cli.stdout.on('data', (chunk) => {
|
|
58
|
+
console.log(chunk.toString());
|
|
59
|
+
});
|
|
60
|
+
cli.stderr.on('data', (msg) => {
|
|
61
|
+
t.fail(msg);
|
|
62
|
+
});
|
|
63
|
+
cli.stdout.on('end', () => {
|
|
64
|
+
t.end();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
73
67
|
|
|
74
|
-
test(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
cli.stdout.on('end', () => {
|
|
106
|
-
t.end();
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
);
|
|
68
|
+
test('Should post message to Slack with additional message', { timeout: 10000 }, (t) => {
|
|
69
|
+
const cli = spawn(
|
|
70
|
+
path.join(path.dirname('.'), 'node'),
|
|
71
|
+
[
|
|
72
|
+
'dist/cli.js',
|
|
73
|
+
'listUsers',
|
|
74
|
+
'http://localhost:8080',
|
|
75
|
+
'keycloak-reporter',
|
|
76
|
+
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
77
|
+
'--output=webhook',
|
|
78
|
+
'--webhookType=slack',
|
|
79
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_SLACK,
|
|
80
|
+
'--webhookMessage="' + (process.env.WEBHOOK_ADDITIONAL_MESSAGE || 'From Github Actions') + '"',
|
|
81
|
+
],
|
|
82
|
+
{
|
|
83
|
+
env: {
|
|
84
|
+
...process.env,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
cli.stdout.on('data', (chunk) => {
|
|
89
|
+
console.log(chunk.toString());
|
|
90
|
+
});
|
|
91
|
+
cli.stderr.on('data', (msg) => {
|
|
92
|
+
t.fail(msg);
|
|
93
|
+
});
|
|
94
|
+
cli.stdout.on('end', () => {
|
|
95
|
+
t.end();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
110
98
|
|
|
111
99
|
test('Should post message to Slack', { timeout: 10000 }, (t) => {
|
|
112
100
|
const cli = spawn(
|
|
@@ -119,12 +107,73 @@ test('Should post message to Slack', { timeout: 10000 }, (t) => {
|
|
|
119
107
|
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
120
108
|
'--output=webhook',
|
|
121
109
|
'--webhookType=slack',
|
|
122
|
-
'--webhookUrl=' + process.env.WEBHOOK_TESTING_SLACK
|
|
110
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_SLACK,
|
|
111
|
+
],
|
|
112
|
+
{
|
|
113
|
+
env: {
|
|
114
|
+
...process.env,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
cli.stdout.on('data', (chunk) => {
|
|
119
|
+
console.log(chunk.toString());
|
|
120
|
+
});
|
|
121
|
+
cli.stderr.on('data', (msg) => {
|
|
122
|
+
t.fail(msg);
|
|
123
|
+
});
|
|
124
|
+
cli.stdout.on('end', () => {
|
|
125
|
+
t.end();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test.skip('Should post message to generic webhook', { timeout: 10000 }, (t) => {
|
|
130
|
+
const cli = spawn(
|
|
131
|
+
path.join(path.dirname('.'), 'node'),
|
|
132
|
+
[
|
|
133
|
+
'dist/cli.js',
|
|
134
|
+
'listUsers',
|
|
135
|
+
'http://localhost:8080',
|
|
136
|
+
'keycloak-reporter',
|
|
137
|
+
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
138
|
+
'--output=webhook',
|
|
139
|
+
'--webhookType=generic',
|
|
140
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_GENERIC,
|
|
141
|
+
],
|
|
142
|
+
{
|
|
143
|
+
env: {
|
|
144
|
+
...process.env,
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
cli.stdout.on('data', (chunk) => {
|
|
149
|
+
console.log(chunk.toString());
|
|
150
|
+
});
|
|
151
|
+
cli.stderr.on('data', (msg) => {
|
|
152
|
+
t.fail(msg);
|
|
153
|
+
});
|
|
154
|
+
cli.stdout.on('end', () => {
|
|
155
|
+
t.end();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test.skip('Should post message to generic webhook with additional message', { timeout: 10000 }, (t) => {
|
|
160
|
+
const cli = spawn(
|
|
161
|
+
path.join(path.dirname('.'), 'node'),
|
|
162
|
+
[
|
|
163
|
+
'dist/cli.js',
|
|
164
|
+
'listUsers',
|
|
165
|
+
'http://localhost:8080',
|
|
166
|
+
'keycloak-reporter',
|
|
167
|
+
'3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r',
|
|
168
|
+
'--output=webhook',
|
|
169
|
+
'--webhookType=generic',
|
|
170
|
+
'--webhookUrl=' + process.env.WEBHOOK_TESTING_GENERIC,
|
|
171
|
+
'--webhookMessage="' + (process.env.WEBHOOK_ADDITIONAL_MESSAGE || 'From Github Actions') + '"',
|
|
123
172
|
],
|
|
124
173
|
{
|
|
125
174
|
env: {
|
|
126
|
-
...process.env
|
|
127
|
-
}
|
|
175
|
+
...process.env,
|
|
176
|
+
},
|
|
128
177
|
}
|
|
129
178
|
);
|
|
130
179
|
cli.stdout.on('data', (chunk) => {
|
package/lib/output.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { IncomingWebhook as SlackWebhook } from '@slack/webhook';
|
|
|
4
4
|
|
|
5
5
|
enum WebhookType {
|
|
6
6
|
SLACK = 'slack',
|
|
7
|
-
TEAMS = 'teams'
|
|
7
|
+
TEAMS = 'teams',
|
|
8
|
+
GENERIC = 'generic'
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface WebhookMessage {
|
|
@@ -75,6 +76,27 @@ export async function post2Webhook(
|
|
|
75
76
|
}
|
|
76
77
|
]
|
|
77
78
|
});
|
|
79
|
+
case WebhookType.GENERIC.toString(): {
|
|
80
|
+
const payload: Record<string, unknown> = {
|
|
81
|
+
title,
|
|
82
|
+
date: `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`,
|
|
83
|
+
reportContent
|
|
84
|
+
};
|
|
85
|
+
if (text != null) {
|
|
86
|
+
payload.message = text;
|
|
87
|
+
}
|
|
88
|
+
const response = await fetch(url, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify(payload)
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const error = new Error(`Generic webhook request failed with status ${response.status}`);
|
|
95
|
+
(error as NodeJS.ErrnoException).code = 'generic_webhook_http_error';
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
78
100
|
// defaulting to Slack
|
|
79
101
|
default:
|
|
80
102
|
// eslint-disable-next-line no-case-declarations
|
package/lib/report.ts
CHANGED
|
@@ -43,6 +43,9 @@ export async function convert(cfg: ConvertConfig) {
|
|
|
43
43
|
case 'slack_webhook_http_error':
|
|
44
44
|
logger.error('Invalid Slack Webhook Payload. Check your params.');
|
|
45
45
|
throw new Error('Invalid Slack Payload');
|
|
46
|
+
case 'generic_webhook_http_error':
|
|
47
|
+
logger.error('Generic Webhook request failed. Check your URL and params.');
|
|
48
|
+
throw new Error('Invalid Generic Webhook request');
|
|
46
49
|
default:
|
|
47
50
|
logger.error(`Error during sending webhook.(${e?.code})`, e?.original);
|
|
48
51
|
throw e;
|
package/package.json
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/// <reference types="jest-extended" />
|
|
2
|
+
import { post2Webhook } from '../lib/output';
|
|
3
|
+
|
|
4
|
+
const mockSlackSend = jest.fn();
|
|
5
|
+
jest.mock('@slack/webhook', () => ({
|
|
6
|
+
IncomingWebhook: jest.fn().mockImplementation(() => ({
|
|
7
|
+
send: mockSlackSend
|
|
8
|
+
}))
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const mockTeamsSend = jest.fn();
|
|
12
|
+
jest.mock('ms-teams-webhook', () => ({
|
|
13
|
+
IncomingWebhook: jest.fn().mockImplementation(() => ({
|
|
14
|
+
send: mockTeamsSend
|
|
15
|
+
}))
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe('post2Webhook - Slack', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
mockSlackSend.mockResolvedValue({});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('posts to Slack webhook', async () => {
|
|
25
|
+
await post2Webhook('slack', 'https://hooks.slack.com/test', 'Users Report', 'report content');
|
|
26
|
+
expect(mockSlackSend).toHaveBeenCalledTimes(1);
|
|
27
|
+
expect(mockSlackSend).toHaveBeenCalledWith(
|
|
28
|
+
expect.objectContaining({ blocks: expect.any(Array) })
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('posts to Slack webhook with message', async () => {
|
|
33
|
+
await post2Webhook('slack', 'https://hooks.slack.com/test', 'Users Report', 'report content', 'Hello from CI');
|
|
34
|
+
expect(mockSlackSend).toHaveBeenCalledTimes(1);
|
|
35
|
+
const blocks = mockSlackSend.mock.calls[0][0].blocks;
|
|
36
|
+
const contextBlock = blocks.find((b) => b.type === 'context' && b.elements?.[0]?.text === 'Hello from CI');
|
|
37
|
+
expect(contextBlock).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Slack payload includes title and date fields', async () => {
|
|
41
|
+
await post2Webhook('slack', 'https://hooks.slack.com/test', 'My Title', 'content');
|
|
42
|
+
const blocks = mockSlackSend.mock.calls[0][0].blocks;
|
|
43
|
+
const sectionBlock = blocks.find((b) => b.type === 'section');
|
|
44
|
+
expect(sectionBlock.fields[0].text).toContain('My Title');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('post2Webhook - Teams', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
mockTeamsSend.mockResolvedValue({});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('posts to Teams webhook', async () => {
|
|
55
|
+
await post2Webhook('teams', 'https://outlook.office.com/webhook/test', 'Clients Report', 'report content');
|
|
56
|
+
expect(mockTeamsSend).toHaveBeenCalledTimes(1);
|
|
57
|
+
expect(mockTeamsSend).toHaveBeenCalledWith(
|
|
58
|
+
expect.objectContaining({ type: 'message' })
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('posts to Teams webhook with message', async () => {
|
|
63
|
+
await post2Webhook('teams', 'https://outlook.office.com/webhook/test', 'Clients Report', 'report content', 'Hello from CI');
|
|
64
|
+
expect(mockTeamsSend).toHaveBeenCalledTimes(1);
|
|
65
|
+
const attachment = mockTeamsSend.mock.calls[0][0].attachments[0];
|
|
66
|
+
const textBlock = attachment.content.body.find((b) => b.type === 'TextBlock' && b.text === 'Hello from CI');
|
|
67
|
+
expect(textBlock).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('Teams payload includes title in FactSet', async () => {
|
|
71
|
+
await post2Webhook('teams', 'https://outlook.office.com/webhook/test', 'My Title', 'content');
|
|
72
|
+
const attachment = mockTeamsSend.mock.calls[0][0].attachments[0];
|
|
73
|
+
const factSet = attachment.content.body.find((b) => b.type === 'FactSet');
|
|
74
|
+
const typeFact = factSet.facts.find((f) => f.title === 'Type');
|
|
75
|
+
expect(typeFact.value).toBe('My Title');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('post2Webhook - Generic', () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
jest.clearAllMocks();
|
|
82
|
+
global.fetch = jest.fn();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('posts to generic webhook', async () => {
|
|
86
|
+
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, status: 200 });
|
|
87
|
+
await post2Webhook('generic', 'https://example.com/webhook', 'Users Report', 'report content');
|
|
88
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
89
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
90
|
+
'https://example.com/webhook',
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { 'Content-Type': 'application/json' }
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('generic webhook payload contains title, date and reportContent', async () => {
|
|
99
|
+
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, status: 200 });
|
|
100
|
+
await post2Webhook('generic', 'https://example.com/webhook', 'My Title', 'my content');
|
|
101
|
+
const callArgs = (global.fetch as jest.Mock).mock.calls[0];
|
|
102
|
+
const body = JSON.parse(callArgs[1].body);
|
|
103
|
+
expect(body.title).toBe('My Title');
|
|
104
|
+
expect(body.reportContent).toBe('my content');
|
|
105
|
+
expect(body.date).toBeDefined();
|
|
106
|
+
expect(body.message).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('generic webhook payload includes message when provided', async () => {
|
|
110
|
+
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, status: 200 });
|
|
111
|
+
await post2Webhook('generic', 'https://example.com/webhook', 'My Title', 'my content', 'Hello from CI');
|
|
112
|
+
const callArgs = (global.fetch as jest.Mock).mock.calls[0];
|
|
113
|
+
const body = JSON.parse(callArgs[1].body);
|
|
114
|
+
expect(body.message).toBe('Hello from CI');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('throws on generic webhook HTTP error response', async () => {
|
|
118
|
+
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 500 });
|
|
119
|
+
await expect(
|
|
120
|
+
post2Webhook('generic', 'https://example.com/webhook', 'My Title', 'my content')
|
|
121
|
+
).rejects.toThrow('Generic webhook request failed with status 500');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('thrown error has generic_webhook_http_error code', async () => {
|
|
125
|
+
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 400 });
|
|
126
|
+
const err = await post2Webhook('generic', 'https://example.com/webhook', 'My Title', 'my content').catch((e) => e);
|
|
127
|
+
expect((err as NodeJS.ErrnoException).code).toBe('generic_webhook_http_error');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
sha256:c7df4f7c36b529d39cc93cf6ba77e49e196ff3a05177c8904183647974fe2f80
|
|
Binary file
|