@continuoussecuritytooling/keycloak-reporter 0.1.1 → 0.2.0

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.
@@ -41,8 +41,9 @@ jobs:
41
41
  matrix:
42
42
  node_version:
43
43
  - 16
44
- - 18
45
- - 20
44
+ # TODO: Support Node 16+
45
+ #- 18
46
+ #- 20
46
47
  os:
47
48
  - ubuntu-latest
48
49
  steps:
@@ -72,11 +73,13 @@ jobs:
72
73
  name: Build Container Image
73
74
  runs-on: ubuntu-latest
74
75
  needs:
75
- - build
76
76
  - end2end
77
77
  steps:
78
78
  - uses: actions/checkout@v3
79
79
  - uses: actions/setup-node@v3
80
+ # TODO: Support Node 16+
81
+ with:
82
+ node-version: "16"
80
83
  - name: "Build Package"
81
84
  run: |
82
85
  npm run clean
package/Dockerfile CHANGED
@@ -1,4 +1,6 @@
1
- FROM node:20
1
+ FROM node:16
2
+
3
+ ENV CONFIG_FILE=/app/config.json
2
4
 
3
5
  COPY dist/ docker_entrypoint.sh /app
4
6
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  [![License](https://img.shields.io/github/license/ContinuousSecurityTooling/keycloak-reporter.svg)](LICENSE)
5
5
  [![CI](https://github.com/ContinuousSecurityTooling/keycloak-reporter/actions/workflows/pipeline.yml/badge.svg)](https://github.com/ContinuousSecurityTooling/keycloak-reporter/actions/workflows/pipeline.yml)
6
- [![npm version](https://badge.fury.io/js/%40ContinuousSecurityTooling%2Fkeycloak-reporter.svg)](https://badge.fury.io/js/%40ContinuousSecurityTooling%2Fkeycloak-reporter)
6
+ [![npm version](https://badge.fury.io/js/%40ContinuousSecurityTooling%2Fkeycloak-reporter.svg)](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
7
7
  [![npm downloads](https://img.shields.io/npm/dm/%40ContinuousSecurityTooling%2Fkeycloak-reporter.svg)](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
8
8
  [![npm downloads](https://img.shields.io/npm/dt/%40ContinuousSecurityTooling%2Fkeycloak-reporter.svg)](https://www.npmjs.com/package/@continuoussecuritytooling/keycloak-reporter)
9
9
  [![Docker Stars](https://img.shields.io/docker/stars/continuoussecuritytooling/keycloak-reporting-cli.svg)](https://hub.docker.com/r/continuoussecuritytooling/keycloak-reporting-cli/)
@@ -14,7 +14,12 @@
14
14
  ## Usage
15
15
 
16
16
  ```
17
- docker run continuoussecuritytooling/keycloak-reporting-cli listClients <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=csv
17
+ npm i @continuoussecuritytooling/keycloak-reporter --location=global
18
+ kc-reporter help
19
+ ```
20
+ For listing clients:
21
+ ```
22
+ kc-reporter listClients <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=csv
18
23
  ```
19
24
 
20
25
  The output looks for CSV, like that:
@@ -35,18 +40,25 @@ Valid commands are:
35
40
 
36
41
  ## Advanced
37
42
 
43
+ ### Config file
44
+
45
+ You can also provider a config file via env var `CONFIG_FILE` and then just provide the commands, e.g.:
46
+ ```
47
+ CONFIG_FILE=e2e/fixtures/config.json kc-reporter listClients
48
+ ```
49
+
38
50
  ### Post to Slack or Teams
39
51
 
40
52
  When using this command:
41
53
  ```
42
- node dist/index.js listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=slack --webhookUrl=$WEBHOOK_TESTING_SLACK
54
+ kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=slack --webhookUrl=$WEBHOOK_TESTING_SLACK
43
55
  ```
44
56
  the following entry in slack will be created:
45
57
  ![Slack Sample](.docs/webhook-slack-sample.png)
46
58
 
47
59
  And for Teams:
48
60
  ```
49
- node dist/index.js listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=teams --webhookUrl=$WEBHOOK_TESTING_TEAMS
61
+ kc-reporter listUsers <Keycloak_Root_URL> <Client_ID> <Client_Secret> --format=json --output=webhook --webhookType=teams --webhookUrl=$WEBHOOK_TESTING_TEAMS
50
62
  ```
51
63
  the following entry in slack will be created:
52
64
  ![Team Sample](.docs/webhook-teams-sample.png)
package/cli.ts CHANGED
@@ -2,10 +2,15 @@
2
2
 
3
3
  import yargs from 'yargs/yargs';
4
4
  import { hideBin } from 'yargs/helpers';
5
- import { listUsers, listClients } from './src/cli.js';
6
- import { Options } from './lib/client.js';
7
- import { convertJSON2CSV } from './lib/convert.js';
8
- import { post2Webhook } from './lib/output.js';
5
+ import {
6
+ listUsers,
7
+ listClients,
8
+ Options,
9
+ convertJSON2CSV,
10
+ post2Webhook,
11
+ } from './index.js';
12
+ import config from './src/config.js';
13
+
9
14
 
10
15
  class WebhookConfig {
11
16
  type: string;
@@ -60,9 +65,9 @@ yargs(hideBin(process.argv))
60
65
  () => {},
61
66
  async (argv) => {
62
67
  const users = await listUsers(<Options>{
63
- clientId: argv.clientId as string,
64
- clientSecret: argv.clientSecret as string,
65
- rootUrl: argv.url as string,
68
+ clientId: argv.clientId ? argv.clientId as string: config.clientId,
69
+ clientSecret: argv.clientSecret ? argv.clientSecret as string: config.clientSecret,
70
+ rootUrl:argv.url ? argv.url as string: config.url,
66
71
  });
67
72
  await convert(
68
73
  argv.format as string,
@@ -83,9 +88,9 @@ yargs(hideBin(process.argv))
83
88
  () => {},
84
89
  async (argv) => {
85
90
  const clients = await listClients(<Options>{
86
- clientId: argv.clientId as string,
87
- clientSecret: argv.clientSecret as string,
88
- rootUrl: argv.url as string,
91
+ clientId: argv.clientId ? argv.clientId as string: config.clientId,
92
+ clientSecret: argv.clientSecret ? argv.clientSecret as string: config.clientSecret,
93
+ rootUrl:argv.url ? argv.url as string: config.url,
89
94
  });
90
95
  await convert(
91
96
  argv.format as string,
@@ -0,0 +1,57 @@
1
+ {
2
+ "$id": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/blob/main/config/schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Keycloak Reporter Config",
5
+ "type": "object",
6
+ "definitions": {},
7
+ "required": ["url", "clientId", "clientSecret"],
8
+ "properties": {
9
+ "command": {
10
+ "type": "array",
11
+ "items": {
12
+ "type": "string",
13
+ "enum": ["listClients", "listUsers"]
14
+ }
15
+ },
16
+ "url": {
17
+ "type": "string",
18
+ "description": "Keycloak Server URL"
19
+ },
20
+ "clientId": {
21
+ "type": "string",
22
+ "description": "Keycloak Client used for reporting"
23
+ },
24
+ "clientSecret": {
25
+ "type": "string",
26
+ "description": "Keycloak Client Secret used for reporting"
27
+ },
28
+ "output": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "string",
32
+ "enum": ["webhook", "stdout"]
33
+ },
34
+ "description": "Output channel to use"
35
+ },
36
+ "format": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "string",
40
+ "enum": ["json", "csv"]
41
+ },
42
+ "description": "Report format"
43
+ },
44
+ "webhookType": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "string",
48
+ "enum": ["slack", "teams"]
49
+ },
50
+ "description": "Type of webhook"
51
+ },
52
+ "webhookUrl": {
53
+ "type": "string",
54
+ "description": "URL of the webhook"
55
+ }
56
+ }
57
+ }
package/dist/cli.js CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import yargs from 'yargs/yargs';
3
3
  import { hideBin } from 'yargs/helpers';
4
- import { listUsers, listClients } from './src/cli.js';
5
- import { convertJSON2CSV } from './lib/convert.js';
6
- import { post2Webhook } from './lib/output.js';
4
+ import { listUsers, listClients, convertJSON2CSV, post2Webhook, } from './index.js';
5
+ import config from './src/config.js';
7
6
  class WebhookConfig {
8
7
  constructor(type, url, title) {
9
8
  this.type = type;
@@ -40,9 +39,9 @@ yargs(hideBin(process.argv))
40
39
  // eslint-disable-next-line @typescript-eslint/no-empty-function
41
40
  () => { }, async (argv) => {
42
41
  const users = await listUsers({
43
- clientId: argv.clientId,
44
- clientSecret: argv.clientSecret,
45
- rootUrl: argv.url,
42
+ clientId: argv.clientId ? argv.clientId : config.clientId,
43
+ clientSecret: argv.clientSecret ? argv.clientSecret : config.clientSecret,
44
+ rootUrl: argv.url ? argv.url : config.url,
46
45
  });
47
46
  await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'User Listing'), users);
48
47
  })
@@ -50,9 +49,9 @@ yargs(hideBin(process.argv))
50
49
  // eslint-disable-next-line @typescript-eslint/no-empty-function
51
50
  () => { }, async (argv) => {
52
51
  const clients = await listClients({
53
- clientId: argv.clientId,
54
- clientSecret: argv.clientSecret,
55
- rootUrl: argv.url,
52
+ clientId: argv.clientId ? argv.clientId : config.clientId,
53
+ clientSecret: argv.clientSecret ? argv.clientSecret : config.clientSecret,
54
+ rootUrl: argv.url ? argv.url : config.url,
56
55
  });
57
56
  await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'Client Listing'), clients);
58
57
  })
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ {
2
+ "$id": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/blob/main/config/schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Keycloak Reporter Config",
5
+ "type": "object",
6
+ "definitions": {},
7
+ "required": ["url", "clientId", "clientSecret"],
8
+ "properties": {
9
+ "command": {
10
+ "type": "array",
11
+ "items": {
12
+ "type": "string",
13
+ "enum": ["listClients", "listUsers"]
14
+ }
15
+ },
16
+ "url": {
17
+ "type": "string",
18
+ "description": "Keycloak Server URL"
19
+ },
20
+ "clientId": {
21
+ "type": "string",
22
+ "description": "Keycloak Client used for reporting"
23
+ },
24
+ "clientSecret": {
25
+ "type": "string",
26
+ "description": "Keycloak Client Secret used for reporting"
27
+ },
28
+ "output": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "string",
32
+ "enum": ["webhook", "stdout"]
33
+ },
34
+ "description": "Output channel to use"
35
+ },
36
+ "format": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "string",
40
+ "enum": ["json", "csv"]
41
+ },
42
+ "description": "Report format"
43
+ },
44
+ "webhookType": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "string",
48
+ "enum": ["slack", "teams"]
49
+ },
50
+ "description": "Type of webhook"
51
+ },
52
+ "webhookUrl": {
53
+ "type": "string",
54
+ "description": "URL of the webhook"
55
+ }
56
+ }
57
+ }
package/dist/index.js CHANGED
@@ -1,83 +1,4 @@
1
- #!/usr/bin/env node
2
- import yargs from 'yargs/yargs';
3
- import { hideBin } from 'yargs/helpers';
4
- import { listUsers, listClients } from './src/cli.js';
5
- import { convertJSON2CSV } from './lib/convert.js';
6
- import { post2Webhook } from './lib/output.js';
7
- class WebhookConfig {
8
- constructor(type, url, title) {
9
- this.type = type;
10
- this.url = url;
11
- this.title = title;
12
- }
13
- }
14
- async function convert(format, output, config, json) {
15
- let outputContent;
16
- switch (format) {
17
- case 'csv':
18
- outputContent = (await convertJSON2CSV(json)).toString();
19
- break;
20
- // defaulting to JSON
21
- default:
22
- outputContent = JSON.stringify(json);
23
- }
24
- switch (output) {
25
- case 'webhook':
26
- try {
27
- await post2Webhook(config.type, config.url, config.title, outputContent);
28
- }
29
- catch (e) {
30
- console.error('Error during sending webhook: ', e);
31
- }
32
- break;
33
- // defaulting to standard out
34
- default:
35
- console.log(outputContent);
36
- }
37
- }
38
- yargs(hideBin(process.argv))
39
- .command('listUsers [url] [clientId] [clientSecret]', 'fetches all users in the realms.',
40
- // eslint-disable-next-line @typescript-eslint/no-empty-function
41
- () => { }, async (argv) => {
42
- const users = await listUsers({
43
- clientId: argv.clientId,
44
- clientSecret: argv.clientSecret,
45
- rootUrl: argv.url,
46
- });
47
- await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'User Listing'), users);
48
- })
49
- .command('listClients [url] [clientId] [clientSecret]', 'fetches all clients in the realms.',
50
- // eslint-disable-next-line @typescript-eslint/no-empty-function
51
- () => { }, async (argv) => {
52
- const clients = await listClients({
53
- clientId: argv.clientId,
54
- clientSecret: argv.clientSecret,
55
- rootUrl: argv.url,
56
- });
57
- await convert(argv.format, argv.output, new WebhookConfig(argv.webhookType, argv.webhookUrl, 'Client Listing'), clients);
58
- })
59
- .option('format', {
60
- alias: 'f',
61
- type: 'string',
62
- default: 'json',
63
- description: 'output format, e.g. JSON|CSV',
64
- })
65
- .option('output', {
66
- alias: 'o',
67
- type: 'string',
68
- default: 'stdout',
69
- description: 'output channel',
70
- })
71
- .option('webhookType', {
72
- alias: 'w',
73
- type: 'string',
74
- default: 'slack',
75
- description: 'Webhook Type',
76
- })
77
- .option('webhookUrl', {
78
- alias: 't',
79
- type: 'string',
80
- description: 'Webhook URL',
81
- })
82
- .parse();
1
+ export { listUsers, listClients } from './src/cli.js';
2
+ export { convertJSON2CSV } from './lib/convert.js';
3
+ export { post2Webhook } from './lib/output.js';
83
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
@@ -16,7 +16,7 @@ export async function createClient(options) {
16
16
  });
17
17
  }
18
18
  catch (e) {
19
- console.error('Check Client Config:', e.response.data.error_description);
19
+ console.error('Check Client Config:', e.response ? e.response.data.error_description : e);
20
20
  return Promise.reject();
21
21
  }
22
22
  const keycloakIssuer = await Issuer.discover(`${options.rootUrl}/realms/master`);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,aAAa,MAAM,iCAAiC,CAAC;AAE5D,oCAAoC;AACpC,MAAM,aAAa,GAAG,EAAE,CAAC;AAQzB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,IAAI;QACF,eAAe;QACf,MAAM,aAAa,CAAC,IAAI,CAAC;YACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC;KACJ;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACxE,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;KACzB;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,QAAQ,CAC1C,GAAG,OAAO,CAAC,OAAO,gBAAgB,CACnC,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC;QACvC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,0BAA0B,EAAE,MAAM,EAAE,uCAAuC;KAC5E,CAAC,CAAC;IAEH,gCAAgC;IAChC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,aAAa,EAAE,OAAO,CAAC,YAAY;QACnC,UAAU,EAAE,oBAAoB;KACjC,CAAC,CAAC;IAEH;;;;;gCAK4B;IAE5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,aAAa,MAAM,iCAAiC,CAAC;AAE5D,oCAAoC;AACpC,MAAM,aAAa,GAAG,EAAE,CAAC;AAQzB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,IAAI;QACF,eAAe;QACf,MAAM,aAAa,CAAC,IAAI,CAAC;YACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC;KACJ;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,CACX,sBAAsB,EACtB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CACnD,CAAC;QACF,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;KACzB;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,QAAQ,CAC1C,GAAG,OAAO,CAAC,OAAO,gBAAgB,CACnC,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC;QACvC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,0BAA0B,EAAE,MAAM,EAAE,uCAAuC;KAC5E,CAAC,CAAC;IAEH,gCAAgC;IAChC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,aAAa,EAAE,OAAO,CAAC,YAAY;QACnC,UAAU,EAAE,oBAAoB;KACjC,CAAC,CAAC;IAEH;;;;;gCAK4B;IAE5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { assoc, pick, mergeAll, mergeDeepRight } from 'ramda';
2
+ import Ajv from 'ajv';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import fs from 'fs';
6
+ const schema = JSON.parse(fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../../config/schema.json')), 'utf8'));
7
+ const ajv = new Ajv.default();
8
+ const ajvValidate = ajv.compile(schema);
9
+ // import the config file
10
+ function buildConfigFromFile(filePath) {
11
+ if (!filePath)
12
+ return {};
13
+ const isAbsolutePath = filePath.charAt(0) === '/';
14
+ return JSON.parse(isAbsolutePath
15
+ ? fs.readFileSync(filePath, 'utf8')
16
+ : fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../config', filePath)), 'utf8'));
17
+ }
18
+ // build an object using the defaults in the schema
19
+ function buildDefaults(schema, definitions) {
20
+ return Object.keys(schema.properties).reduce((acc, prop) => {
21
+ let spec = schema.properties[prop];
22
+ if (spec.$ref) {
23
+ spec = definitions[spec.$ref.replace('#/definitions/', '')];
24
+ if (spec && spec.type === 'object') {
25
+ return assoc(prop, buildDefaults(spec, definitions), acc);
26
+ }
27
+ }
28
+ return assoc(prop, spec.default, acc);
29
+ }, {});
30
+ }
31
+ // build an object of config values taken from process.env
32
+ function buildEnvironmentVariablesConfig(schema) {
33
+ const trueRx = /^true$/i;
34
+ const configKeys = Object.keys(schema.properties);
35
+ const env = pick(configKeys, process.env);
36
+ return Object.keys(env).reduce((acc, key) => {
37
+ const { type } = schema.properties[key];
38
+ switch (type) {
39
+ case 'integer':
40
+ return assoc(key, parseInt(env[key], 10), acc);
41
+ case 'boolean':
42
+ return assoc(key, trueRx.test(env[key]), acc);
43
+ default:
44
+ return assoc(key, env[key], acc);
45
+ }
46
+ }, {});
47
+ }
48
+ function validate(data) {
49
+ const valid = ajvValidate(data);
50
+ if (valid)
51
+ return true;
52
+ throw new Error(ajv.errorsText());
53
+ }
54
+ // merge the environment variables, config file values, and defaults
55
+ const config = mergeAll(mergeDeepRight(buildDefaults(schema, schema.definitions), buildConfigFromFile(process.env.CONFIG_FILE)), buildEnvironmentVariablesConfig(schema));
56
+ export default config;
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  #!/bin/bash
2
2
 
3
- node index.js "$@"
3
+ node cli.js "$@"
@@ -0,0 +1,5 @@
1
+ {
2
+ "url": "http://localhost:8080",
3
+ "clientId": "keycloak-reporter",
4
+ "clientSecret": "3UYhI2hryFwoVtcd7ljlaDuD9HXrGV5r"
5
+ }
@@ -4,7 +4,7 @@ import { test } from 'tape';
4
4
  import { spawn } from 'node:child_process';
5
5
  import path from 'node:path';
6
6
 
7
- test('Should list clients as JSON', (t) => {
7
+ test('Should list clients as JSON', { timeout: 3000 }, (t) => {
8
8
  const cli = spawn(
9
9
  path.join(path.dirname('.'), 'node'),
10
10
  [
@@ -21,8 +21,12 @@ test('Should list clients as JSON', (t) => {
21
21
  }
22
22
  );
23
23
  cli.stdout.on('data', (chunk) => {
24
+ console.log(chunk.toString())
24
25
  t.equal(JSON.parse(chunk.toString()).length, 24);
25
26
  });
27
+ cli.stderr.on('data', (msg) => {
28
+ t.fail(msg)
29
+ });
26
30
  cli.stdout.on('end', () => {
27
31
  t.end();
28
32
  });
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ import { test } from 'tape';
4
+ import { spawn } from 'node:child_process';
5
+ import path from 'node:path';
6
+
7
+ test('Should use config file', { timeout: 3000 }, (t) => {
8
+ const cli = spawn(
9
+ path.join(path.dirname('.'), 'node'),
10
+ [
11
+ 'dist/cli.js',
12
+ 'listClients'
13
+ ],
14
+ {
15
+ env: {
16
+ CONFIG_FILE: process.cwd() + '/e2e/fixtures/config.json',
17
+ ...process.env,
18
+ },
19
+ }
20
+ );
21
+ cli.stdout.on('data', (chunk) => {
22
+ console.log(chunk.toString())
23
+ t.equal(JSON.parse(chunk.toString()).length, 24);
24
+ });
25
+ cli.stderr.on('data', (msg) => {
26
+ t.fail(msg)
27
+ });
28
+ cli.stdout.on('end', () => {
29
+ t.end();
30
+ });
31
+ });
package/e2e/spec/users.js CHANGED
@@ -4,7 +4,7 @@ import { test } from 'tape';
4
4
  import { spawn } from 'node:child_process';
5
5
  import path from 'node:path';
6
6
 
7
- test('Should list users as JSON', (t) => {
7
+ test('Should list users as JSON', { timeout: 3000 }, (t) => {
8
8
  const cli = spawn(
9
9
  path.join(path.dirname('.'), 'node'),
10
10
  [
@@ -21,8 +21,12 @@ test('Should list users as JSON', (t) => {
21
21
  }
22
22
  );
23
23
  cli.stdout.on('data', (chunk) => {
24
+ console.log(chunk.toString())
24
25
  t.equal(JSON.parse(chunk.toString()).length, 3);
25
26
  });
27
+ cli.stderr.on('data', (msg) => {
28
+ t.fail(msg)
29
+ });
26
30
  cli.stdout.on('end', () => {
27
31
  t.end();
28
32
  });
@@ -4,7 +4,7 @@ import { test } from 'tape';
4
4
  import { spawn } from 'node:child_process';
5
5
  import path from 'node:path';
6
6
 
7
- test('Should post message to Teams', (t) => {
7
+ test('Should post message to Teams', { timeout: 3000 }, (t) => {
8
8
  const cli = spawn(
9
9
  path.join(path.dirname('.'), 'node'),
10
10
  [
@@ -26,12 +26,15 @@ test('Should post message to Teams', (t) => {
26
26
  cli.stdout.on('data', (chunk) => {
27
27
  console.log(chunk.toString())
28
28
  });
29
+ cli.stderr.on('data', (msg) => {
30
+ t.fail(msg)
31
+ });
29
32
  cli.stdout.on('end', () => {
30
33
  t.end();
31
34
  });
32
35
  });
33
36
 
34
- test('Should post message to Slack', (t) => {
37
+ test('Should post message to Slack', { timeout: 3000 }, (t) => {
35
38
  const cli = spawn(
36
39
  path.join(path.dirname('.'), 'node'),
37
40
  [
@@ -53,6 +56,9 @@ test('Should post message to Slack', (t) => {
53
56
  cli.stdout.on('data', (chunk) => {
54
57
  console.log(chunk.toString())
55
58
  });
59
+ cli.stderr.on('data', (msg) => {
60
+ t.fail(msg)
61
+ });
56
62
  cli.stdout.on('end', () => {
57
63
  t.end();
58
64
  });
package/index.ts CHANGED
@@ -1,125 +1,4 @@
1
- #!/usr/bin/env node
2
-
3
- import yargs from 'yargs/yargs';
4
- import { hideBin } from 'yargs/helpers';
5
- import { listUsers, listClients } from './src/cli.js';
6
- import { Options } from './lib/client.js';
7
- import { convertJSON2CSV } from './lib/convert.js';
8
- import { post2Webhook } from './lib/output.js';
9
-
10
- class WebhookConfig {
11
- type: string;
12
- url: string;
13
- title: string;
14
- constructor(type: string, url: string, title: string) {
15
- this.type = type;
16
- this.url = url;
17
- this.title = title;
18
- }
19
- }
20
-
21
- async function convert(
22
- format: string,
23
- output: string,
24
- config: WebhookConfig,
25
- json: object
26
- ) {
27
- let outputContent: string;
28
- switch (format) {
29
- case 'csv':
30
- outputContent = (await convertJSON2CSV(json)).toString();
31
- break;
32
- // defaulting to JSON
33
- default:
34
- outputContent = JSON.stringify(json);
35
- }
36
- switch (output) {
37
- case 'webhook':
38
- try {
39
- await post2Webhook(
40
- config.type,
41
- config.url,
42
- config.title,
43
- outputContent
44
- );
45
- } catch (e) {
46
- console.error('Error during sending webhook: ', e);
47
- }
48
- break;
49
- // defaulting to standard out
50
- default:
51
- console.log(outputContent);
52
- }
53
- }
54
-
55
- yargs(hideBin(process.argv))
56
- .command(
57
- 'listUsers [url] [clientId] [clientSecret]',
58
- 'fetches all users in the realms.',
59
- // eslint-disable-next-line @typescript-eslint/no-empty-function
60
- () => {},
61
- async (argv) => {
62
- const users = await listUsers(<Options>{
63
- clientId: argv.clientId as string,
64
- clientSecret: argv.clientSecret as string,
65
- rootUrl: argv.url as string,
66
- });
67
- await convert(
68
- argv.format as string,
69
- argv.output as string,
70
- new WebhookConfig(
71
- argv.webhookType as string,
72
- argv.webhookUrl as string,
73
- 'User Listing'
74
- ),
75
- users
76
- );
77
- }
78
- )
79
- .command(
80
- 'listClients [url] [clientId] [clientSecret]',
81
- 'fetches all clients in the realms.',
82
- // eslint-disable-next-line @typescript-eslint/no-empty-function
83
- () => {},
84
- async (argv) => {
85
- const clients = await listClients(<Options>{
86
- clientId: argv.clientId as string,
87
- clientSecret: argv.clientSecret as string,
88
- rootUrl: argv.url as string,
89
- });
90
- await convert(
91
- argv.format as string,
92
- argv.output as string,
93
- new WebhookConfig(
94
- argv.webhookType as string,
95
- argv.webhookUrl as string,
96
- 'Client Listing'
97
- ),
98
- clients
99
- );
100
- }
101
- )
102
- .option('format', {
103
- alias: 'f',
104
- type: 'string',
105
- default: 'json',
106
- description: 'output format, e.g. JSON|CSV',
107
- })
108
- .option('output', {
109
- alias: 'o',
110
- type: 'string',
111
- default: 'stdout',
112
- description: 'output channel',
113
- })
114
- .option('webhookType', {
115
- alias: 'w',
116
- type: 'string',
117
- default: 'slack',
118
- description: 'Webhook Type',
119
- })
120
- .option('webhookUrl', {
121
- alias: 't',
122
- type: 'string',
123
- description: 'Webhook URL',
124
- })
125
- .parse();
1
+ export { listUsers, listClients } from './src/cli.js';
2
+ export { Options } from './lib/client.js';
3
+ export { convertJSON2CSV } from './lib/convert.js';
4
+ export { post2Webhook } from './lib/output.js';
package/lib/client.ts CHANGED
@@ -24,7 +24,10 @@ export async function createClient(options: Options): Promise<KcAdminClient> {
24
24
  grantType: 'client_credentials',
25
25
  });
26
26
  } catch (e) {
27
- console.error('Check Client Config:',e.response.data.error_description);
27
+ console.error(
28
+ 'Check Client Config:',
29
+ e.response ? e.response.data.error_description : e
30
+ );
28
31
  return Promise.reject();
29
32
  }
30
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuoussecuritytooling/keycloak-reporter",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Reporting Tools for Keycloak",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "clean": "rm -rf dist/* && npm i",
12
- "build": "tsc && chmod +x dist/cli.js && cp package.json dist/",
12
+ "build": "tsc && chmod +x dist/cli.js && cp -rp config dist/",
13
13
  "test": "eslint . && jest",
14
14
  "end2end:start-server": ".bin/start-server.mjs -Dkeycloak.profile.feature.account_api=disabled -Dkeycloak.profile.feature.account2=disabled -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=e2e/fixtures/auth-utils/test-realm.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING",
15
15
  "end2end:test": "./e2e/run-tests.sh"
@@ -25,14 +25,15 @@
25
25
  },
26
26
  "homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
27
27
  "dependencies": {
28
- "@continuoussecuritytooling/keycloak-reporter": "^0.1.0",
29
28
  "@json2csv/node": "^7.0.0",
30
29
  "@keycloak/keycloak-admin-client": "^20.0.5",
31
30
  "@slack/webhook": "^6.1.0",
31
+ "ajv": "^8.12.0",
32
32
  "install": "^0.13.0",
33
33
  "ms-teams-webhook": "^2.0.2",
34
34
  "npm": "^9.6.7",
35
35
  "openid-client": "^5.4.2",
36
+ "ramda": "^0.29.0",
36
37
  "yargs": "^17.7.2"
37
38
  },
38
39
  "devDependencies": {
package/src/config.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { assoc, pick, mergeAll, mergeDeepRight } from 'ramda';
2
+ import Ajv from 'ajv';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import fs from 'fs';
6
+
7
+ const schema = JSON.parse(
8
+ fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../../config/schema.json')), 'utf8')
9
+ );
10
+
11
+ const ajv = new Ajv.default();
12
+ const ajvValidate = ajv.compile(schema);
13
+
14
+ // import the config file
15
+ function buildConfigFromFile(filePath) {
16
+ if (!filePath) return {};
17
+ const isAbsolutePath = filePath.charAt(0) === '/';
18
+ return JSON.parse(isAbsolutePath
19
+ ? fs.readFileSync(filePath, 'utf8')
20
+ : fs.readFileSync(fileURLToPath(path.join(import.meta.url, '../config', filePath)), 'utf8'));
21
+ }
22
+ // build an object using the defaults in the schema
23
+ function buildDefaults(schema, definitions) {
24
+ return Object.keys(schema.properties).reduce((acc, prop) => {
25
+ let spec = schema.properties[prop];
26
+ if (spec.$ref) {
27
+ spec = definitions[spec.$ref.replace('#/definitions/', '')];
28
+ if (spec && spec.type === 'object') {
29
+ return assoc(prop, buildDefaults(spec, definitions), acc);
30
+ }
31
+ }
32
+ return assoc(prop, spec.default, acc);
33
+ }, {});
34
+ }
35
+
36
+ // build an object of config values taken from process.env
37
+ function buildEnvironmentVariablesConfig(schema) {
38
+ const trueRx = /^true$/i;
39
+ const configKeys = Object.keys(schema.properties);
40
+ const env = pick(configKeys, process.env);
41
+ return Object.keys(env).reduce((acc, key) => {
42
+ const { type } = schema.properties[key];
43
+ switch (type) {
44
+ case 'integer':
45
+ return assoc(key, parseInt(env[key], 10), acc);
46
+ case 'boolean':
47
+ return assoc(key, trueRx.test(env[key]), acc);
48
+ default:
49
+ return assoc(key, env[key], acc);
50
+ }
51
+ }, {});
52
+ }
53
+
54
+ function validate(data) {
55
+ const valid = ajvValidate(data);
56
+ if (valid) return true;
57
+ throw new Error(ajv.errorsText());
58
+ }
59
+
60
+ // merge the environment variables, config file values, and defaults
61
+ const config = mergeAll(
62
+ mergeDeepRight(
63
+ buildDefaults(schema, schema.definitions),
64
+ buildConfigFromFile(process.env.CONFIG_FILE)
65
+ ),
66
+ buildEnvironmentVariablesConfig(schema)
67
+ );
68
+
69
+ export default config;
package/dist/package.json DELETED
@@ -1,56 +0,0 @@
1
- {
2
- "name": "@continuoussecuritytooling/keycloak-reporter",
3
- "version": "0.1.1",
4
- "description": "Reporting Tools for Keycloak",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "kc-reporter": "dist/cli.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "clean": "rm -rf dist/* && npm i",
12
- "build": "tsc && chmod +x dist/cli.js && cp package.json dist/",
13
- "test": "eslint . && jest",
14
- "end2end:start-server": ".bin/start-server.mjs -Dkeycloak.profile.feature.account_api=disabled -Dkeycloak.profile.feature.account2=disabled -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=e2e/fixtures/auth-utils/test-realm.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING",
15
- "end2end:test": "./e2e/run-tests.sh"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/ContinuousSecurityTooling/keycloak-reporter.git"
20
- },
21
- "author": "Martin Reinhardt",
22
- "license": "MIT",
23
- "bugs": {
24
- "url": "https://github.com/ContinuousSecurityTooling/keycloak-reporter/issues"
25
- },
26
- "homepage": "https://github.com/ContinuousSecurityTooling/keycloak-reporter#readme",
27
- "dependencies": {
28
- "@continuoussecuritytooling/keycloak-reporter": "^0.1.0",
29
- "@json2csv/node": "^7.0.0",
30
- "@keycloak/keycloak-admin-client": "^20.0.5",
31
- "@slack/webhook": "^6.1.0",
32
- "install": "^0.13.0",
33
- "ms-teams-webhook": "^2.0.2",
34
- "npm": "^9.6.7",
35
- "openid-client": "^5.4.2",
36
- "yargs": "^17.7.2"
37
- },
38
- "devDependencies": {
39
- "@octokit/rest": "^19.0.11",
40
- "@types/jest": "^29.5.1",
41
- "@types/node": "^20.1.5",
42
- "@types/yargs": "^17.0.24",
43
- "@typescript-eslint/eslint-plugin": "^5.59.6",
44
- "@typescript-eslint/parser": "^5.59.6",
45
- "eslint": "^8.40.0",
46
- "gunzip-maybe": "^1.4.2",
47
- "jest": "^29.5.0",
48
- "jest-extended": "^3.2.4",
49
- "node-fetch": "^3.3.1",
50
- "tap-xunit": "^2.4.1",
51
- "tape": "^5.6.3",
52
- "tar-fs": "^2.1.1",
53
- "ts-jest": "^29.1.0",
54
- "typescript": "^5.0.4"
55
- }
56
- }