@manyos/smileconnect-api 1.31.1 → 1.34.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.
package/Dockerfile CHANGED
@@ -2,20 +2,24 @@
2
2
 
3
3
  # We label our stage as 'builder'
4
4
  FROM node:12.14 as builder
5
-
5
+ ARG NPM_TOKEN
6
6
  COPY package.json package-lock.json ./
7
7
 
8
8
  RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
9
9
 
10
10
  ## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
11
- RUN npm i
12
-
11
+ RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc && \
12
+ npm install && \
13
+ rm ~/.npmrc
14
+ RUN rm -f .npmrc
13
15
  WORKDIR /home/node/app
14
16
 
15
17
  COPY . .
16
18
 
17
- ## Build the angular app in production mode and store the artifacts in dist folder
18
-
19
19
  EXPOSE 3000
20
20
 
21
+ RUN groupadd -g 999 appuser && \
22
+ useradd -r -u 999 -g appuser appuser
23
+ USER appuser
24
+
21
25
  CMD npm start
package/app.js CHANGED
@@ -11,6 +11,7 @@ const JwtStrategy = require('passport-jwt').Strategy,
11
11
  const bodyParser = require('body-parser');
12
12
 
13
13
  const config = require('./util/config');
14
+ const authUtil = require('./util/auth');
14
15
 
15
16
  const cors = require('cors');
16
17
 
@@ -47,6 +48,8 @@ const https = require('https');
47
48
 
48
49
  const maxHTTPSockets = process.env.MAX_HTTP_SOCKETS || 10;
49
50
 
51
+ const SSO_CLIENTNAME_ATTRIBUTE = process.env.SSO_CLIENTNAME_ATTRIBUTE || 'azp';
52
+
50
53
  http.globalAgent.maxSockets = maxHTTPSockets;
51
54
  https.globalAgent.maxSockets = maxHTTPSockets;
52
55
 
@@ -81,6 +84,8 @@ if (audienceArray.length > 0) {
81
84
  opts.audience = audienceArray;
82
85
  }
83
86
 
87
+ opts.passReqToCallback = true
88
+
84
89
  // Do any necessary shutdown logic for our application here
85
90
  const shutdown = (signal, value) => {
86
91
  console.log("shutdown!");
@@ -128,22 +133,32 @@ app.use(compression()); //Compress all routes
128
133
 
129
134
  log.debug('Passport Opts', opts);
130
135
  passport.use(
131
- new JwtStrategy(opts, function (jwt_payload, done) {
136
+ new JwtStrategy(opts, function (req, jwt_payload, done) {
132
137
  //log.info(jwt_payload);
133
138
  //log.info('token', jwt_payload.sub);
134
139
  //TODO: Config error abfangen
135
140
  //TODO: Add AdminScope and Impersonate
141
+ let clientId = jwt_payload[SSO_CLIENTNAME_ATTRIBUTE];
142
+
143
+ //check for master client
144
+ const requestedClientId = req.query.clientId
145
+ if (requestedClientId && authUtil.isMasterClient(clientId)) {
146
+ log.debug(`client ${clientId} acts as ${requestedClientId}`)
147
+ clientId = requestedClientId
148
+ }
149
+
136
150
  const user = {
137
151
  'id': jwt_payload.sub,
138
152
  'azp': jwt_payload.azp,
139
153
  'scope': jwt_payload.scope,
140
154
  'exp': jwt_payload.exp,
141
- 'config': config.getClientConfig(jwt_payload.azp),
155
+ 'config': config.getClientConfig(clientId),
142
156
  'username': jwt_payload.preferred_username
143
157
  }
144
158
  log.debug('Passport User', jwt_payload);
145
159
  const resource_access = jwt_payload.resource_access;
146
160
  log.debug('User Resource Access', resource_access);
161
+ //TODO Prüfen ob das auch per
147
162
  if (resource_access !== null && resource_access !== undefined && resource_access[user.azp] !== null && resource_access[user.azp] !== undefined) {
148
163
  user.accessRoles = resource_access[user.azp].roles;
149
164
  }
@@ -156,6 +171,11 @@ passport.use(
156
171
  app.use(bodyParser.json({limit: '200mb'}));
157
172
  app.use(bodyParser.urlencoded({limit: '200mb', extended: true}));
158
173
 
174
+ //health check
175
+ app.use('/v1/health', function (req, res, next) {
176
+ res.json({status:"ok"})
177
+ })
178
+
159
179
  const maxFilesize = process.env.MAX_FILESIZE || 5;
160
180
 
161
181
  app.use(fileUpload({
package/conf/clients.json CHANGED
@@ -21,8 +21,10 @@
21
21
  "scripts": {}
22
22
  },
23
23
  "incident": {
24
- "basequery": "1=2",
25
- "fields": [],
24
+ "basequery": "1=1",
25
+ "fields": [
26
+ "Description"
27
+ ],
26
28
  "constants": [],
27
29
  "scripts": {}
28
30
  },
@@ -112,7 +112,7 @@ async function createTask(clientConfig, rootForm, rootRequestId, taskData, creat
112
112
  }
113
113
  }
114
114
  }
115
-
115
+ //Todo make dynamic
116
116
  async function createWorklog(clientConfig, taskId, summary, text, attachment) {
117
117
  const task = await getTask(clientConfig,taskId)
118
118
  const fields = clientConfig.taskWorklog.fields;
@@ -292,6 +292,7 @@ async function updateTicket(ticketConfig, clientConfig, id, ticketData) {
292
292
  const myMapping = [{"oldName":ticketConfig.requestIdField, "newName" : "internalId"}];
293
293
  const ticket = await getTicket(ticketConfig, myClientConfig, id, myMapping);
294
294
  log.debug('Ticket to Update', ticket);
295
+ //todo Abfangen wenn getTicket nichts liefert.
295
296
  const mapping = config.getMapping(ticketConfig.requestType);
296
297
 
297
298
  //Constants work only on new.
@@ -82,6 +82,15 @@ List of users who are allowed to access /v1/appconfig endpoints.
82
82
  Sample:
83
83
  ADMIN_USERS=username1, username2
84
84
 
85
+ ### MASTER_CLIENTS
86
+
87
+ List of clients that can act on behalf of other clients.
88
+
89
+ The URL Parameter *clientId* is used for this.
90
+
91
+ Sample:
92
+ MASTER_CLIENTS=idm,adminTool
93
+
85
94
  ## Cache
86
95
 
87
96
  ### CACHETTL_CMDB
package/docs/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.0.2",
3
3
  "info": {
4
4
  "title": "SMILEconnect",
5
- "version": "1.22.1",
5
+ "version": "1.29.1",
6
6
  "contact": {
7
7
  "name": "manyos technology GmbH",
8
8
  "url": "https://manyos.it",
@@ -3401,6 +3401,119 @@
3401
3401
  },
3402
3402
  "summary": "Create a new Client"
3403
3403
  }
3404
+ },
3405
+ "/scripts": {
3406
+ "get": {
3407
+ "tags": [
3408
+ "Scripts"
3409
+ ],
3410
+ "responses": {
3411
+ "200": {
3412
+ "content": {
3413
+ "application/json": {
3414
+ "schema": {
3415
+ "type": "array",
3416
+ "items": {
3417
+ "$ref": "#/components/schemas/script"
3418
+ }
3419
+ }
3420
+ }
3421
+ },
3422
+ "description": "Returns an array with all scripts"
3423
+ }
3424
+ },
3425
+ "summary": "Get all scripts"
3426
+ },
3427
+ "post": {
3428
+ "requestBody": {
3429
+ "content": {
3430
+ "application/json": {
3431
+ "schema": {
3432
+ "$ref": "#/components/schemas/script"
3433
+ }
3434
+ }
3435
+ },
3436
+ "required": true
3437
+ },
3438
+ "tags": [
3439
+ "Scripts"
3440
+ ],
3441
+ "responses": {
3442
+ "200": {
3443
+ "$ref": "#/components/responses/ScriptDefinitionResponse"
3444
+ }
3445
+ },
3446
+ "summary": "Create a new script",
3447
+ "description": "Create a new script that is accessible by all catalog items."
3448
+ }
3449
+ },
3450
+ "/scripts/{scriptId}": {
3451
+ "get": {
3452
+ "tags": [
3453
+ "Scripts"
3454
+ ],
3455
+ "responses": {
3456
+ "200": {
3457
+ "content": {
3458
+ "application/json": {
3459
+ "schema": {
3460
+ "$ref": "#/components/schemas/script"
3461
+ }
3462
+ }
3463
+ },
3464
+ "description": "Return a script"
3465
+ }
3466
+ },
3467
+ "summary": "Get a global script"
3468
+ },
3469
+ "put": {
3470
+ "requestBody": {
3471
+ "content": {
3472
+ "application/json": {
3473
+ "schema": {
3474
+ "$ref": "#/components/schemas/script"
3475
+ }
3476
+ }
3477
+ },
3478
+ "required": true
3479
+ },
3480
+ "tags": [
3481
+ "Scripts"
3482
+ ],
3483
+ "responses": {
3484
+ "200": {
3485
+ "$ref": "#/components/responses/ScriptDefinitionResponse"
3486
+ }
3487
+ },
3488
+ "summary": "Update a script",
3489
+ "description": "Updates a existing script that is accessible by all catalog item."
3490
+ },
3491
+ "delete": {
3492
+ "tags": [
3493
+ "Scripts"
3494
+ ],
3495
+ "responses": {
3496
+ "200": {
3497
+ "$ref": "#/components/responses/DeleteResponse"
3498
+ }
3499
+ },
3500
+ "summary": "Delete this script"
3501
+ },
3502
+ "parameters": [
3503
+ {
3504
+ "examples": {
3505
+ "catalogIdExample": {
3506
+ "value": "serverRestart"
3507
+ }
3508
+ },
3509
+ "name": "scriptId",
3510
+ "schema": {
3511
+ "type": "string"
3512
+ },
3513
+ "in": "path",
3514
+ "required": true
3515
+ }
3516
+ ]
3404
3517
  }
3405
3518
  },
3406
3519
  "components": {
@@ -15130,6 +15243,25 @@
15130
15243
  "summary": "Email Password Reset",
15131
15244
  "notes": "Reset the password for the email system"
15132
15245
  }
15246
+ },
15247
+ "script": {
15248
+ "title": "Root Type for script",
15249
+ "description": "Scripts can be executed to gather data or perform actions.\n\nScripts always need to end with resolve() or reject().\n\nresolve(data) is called when the script execution was successful. Data will be returned as scriptResult. reject(error) is called when the script was not successful and an error needs to be returned.",
15250
+ "type": "object",
15251
+ "properties": {
15252
+ "id": {
15253
+ "description": "id of the script",
15254
+ "type": "string"
15255
+ },
15256
+ "code": {
15257
+ "description": "the code of the script",
15258
+ "type": "string"
15259
+ }
15260
+ },
15261
+ "example": {
15262
+ "id": "ars101",
15263
+ "code": "try {\n const result = await adapter.remedy.search(params.form, params.query, params.valueField + ',' + params.labelField);\n const data = result.data.map(item => {\n return {\n \"value\":item[params.valueField],\n \"label\":item[params.labelField],\n }\n });\n resolve(data);\n} catch (error) {\n reject(error);\n}"
15264
+ }
15133
15265
  }
15134
15266
  },
15135
15267
  "responses": {
@@ -15185,6 +15317,42 @@
15185
15317
  }
15186
15318
  },
15187
15319
  "description": "Get the attachment of a worklog."
15320
+ },
15321
+ "ScriptDefinitionResponse": {
15322
+ "content": {
15323
+ "application/json": {
15324
+ "schema": {
15325
+ "$ref": "#/components/schemas/script"
15326
+ }
15327
+ }
15328
+ },
15329
+ "description": "Returns a script definition"
15330
+ },
15331
+ "DeleteResponse": {
15332
+ "content": {
15333
+ "application/json": {
15334
+ "schema": {
15335
+ "type": "object",
15336
+ "properties": {
15337
+ "id": {
15338
+ "type": "string"
15339
+ },
15340
+ "status": {
15341
+ "type": "string"
15342
+ }
15343
+ }
15344
+ },
15345
+ "examples": {
15346
+ "DeletionResponseExample": {
15347
+ "value": {
15348
+ "id": "serverRestart",
15349
+ "status": "deleted"
15350
+ }
15351
+ }
15352
+ }
15353
+ }
15354
+ },
15355
+ "description": "Returns deletion confirmation for the requested item"
15188
15356
  }
15189
15357
  },
15190
15358
  "securitySchemes": {
@@ -15241,6 +15409,10 @@
15241
15409
  {
15242
15410
  "name": "Templates",
15243
15411
  "description": ""
15412
+ },
15413
+ {
15414
+ "name": "Scripts",
15415
+ "description": ""
15244
15416
  }
15245
15417
  ]
15246
15418
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyos/smileconnect-api",
3
- "version": "1.31.1",
3
+ "version": "1.34.0",
4
4
  "description": "A proxy and abstraction layer for BMCs IT Service Management Suite",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -11,7 +11,7 @@
11
11
  "author": "Robert Hannemann",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@manyos/adapter-foundation": "^1.1.1",
14
+ "@manyos/adapter-foundation": "^1.1.2",
15
15
  "@manyos/logger": "^1.3.0",
16
16
  "bunyan": "^1.8.15",
17
17
  "bunyan-express-serializer": "^1.0.0",
@@ -22,9 +22,9 @@
22
22
  "express-fileupload": "^1.2.1",
23
23
  "express-rate-limit": "^5.2.6",
24
24
  "express-request-id": "^1.4.1",
25
- "express-validator": "^6.10.0",
25
+ "express-validator": "^6.10.1",
26
26
  "moment": "^2.29.1",
27
- "mongoose": "^5.12.3",
27
+ "mongoose": "^5.12.5",
28
28
  "node-cache": "^4.2.1",
29
29
  "node-fetch": "^2.6.1",
30
30
  "only": "0.0.2",
package/util/auth.js CHANGED
@@ -19,6 +19,20 @@ function isAuthorizedAdmin(req, res, next) {
19
19
  }
20
20
  }
21
21
 
22
+ function isMasterClient(clientId) {
23
+ const masterClients = process.env.MASTER_CLIENTS;
24
+ if (masterClients !== null
25
+ && masterClients !== undefined
26
+ && clientId !== null
27
+ && clientId !== undefined
28
+ && isUserInList(masterClients, clientId)) {
29
+ log.debug('master client authorized', clientId);
30
+ return true
31
+ } else {
32
+ return false
33
+ }
34
+ }
35
+
22
36
  function isUserInList(userList, userName) {
23
37
  log.debug('Check if user is in List', userName, userList)
24
38
  if (userList !== null && userList !== undefined && userName !== null && userName !== undefined) {
@@ -33,5 +47,5 @@ function isUserInList(userList, userName) {
33
47
  }
34
48
 
35
49
  module.exports = {
36
- isAuthorizedAdmin
50
+ isAuthorizedAdmin, isMasterClient
37
51
  }