@sap/cds 5.5.5 → 5.6.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.
Files changed (204) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +3 -1
  14. package/lib/env/index.js +175 -41
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +31 -4
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  50. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  53. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  54. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  55. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  56. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  57. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  58. package/libx/_runtime/cds-services/util/assert.js +29 -13
  59. package/libx/_runtime/cds.js +2 -1
  60. package/libx/_runtime/common/aspects/Association.js +72 -0
  61. package/libx/_runtime/common/aspects/any.js +8 -45
  62. package/libx/_runtime/common/aspects/entity.js +0 -1
  63. package/libx/_runtime/common/aspects/relation.js +40 -0
  64. package/libx/_runtime/common/aspects/utils.js +73 -1
  65. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  66. package/libx/_runtime/common/composition/data.js +3 -2
  67. package/libx/_runtime/common/composition/delete.js +3 -1
  68. package/libx/_runtime/common/composition/tree.js +23 -18
  69. package/libx/_runtime/common/composition/utils.js +34 -8
  70. package/libx/_runtime/common/error/frontend.js +6 -1
  71. package/libx/_runtime/common/generic/auth.js +5 -9
  72. package/libx/_runtime/common/generic/crud.js +2 -2
  73. package/libx/_runtime/common/generic/etag.js +11 -8
  74. package/libx/_runtime/common/generic/input.js +3 -3
  75. package/libx/_runtime/common/generic/paging.js +9 -5
  76. package/libx/_runtime/common/generic/put.js +3 -2
  77. package/libx/_runtime/common/generic/sorting.js +3 -3
  78. package/libx/_runtime/common/generic/temporal.js +3 -3
  79. package/libx/_runtime/common/utils/cqn.js +20 -1
  80. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  81. package/libx/_runtime/common/utils/csn.js +50 -52
  82. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  83. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  84. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  85. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  86. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  87. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  88. package/libx/_runtime/common/utils/resolveView.js +7 -5
  89. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  90. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  91. package/libx/_runtime/common/utils/template.js +54 -46
  92. package/libx/_runtime/db/Service.js +9 -2
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  94. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  95. package/libx/_runtime/db/generic/create.js +1 -0
  96. package/libx/_runtime/db/generic/input.js +7 -11
  97. package/libx/_runtime/db/generic/integrity.js +2 -2
  98. package/libx/_runtime/db/generic/rewrite.js +2 -5
  99. package/libx/_runtime/db/generic/update.js +1 -0
  100. package/libx/_runtime/db/query/read.js +9 -4
  101. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  102. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  103. package/libx/_runtime/db/utils/columns.js +14 -43
  104. package/libx/_runtime/fiori/generic/activate.js +3 -2
  105. package/libx/_runtime/fiori/generic/before.js +2 -2
  106. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  107. package/libx/_runtime/fiori/generic/delete.js +3 -2
  108. package/libx/_runtime/fiori/generic/edit.js +2 -2
  109. package/libx/_runtime/fiori/generic/new.js +2 -2
  110. package/libx/_runtime/fiori/generic/patch.js +2 -2
  111. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  112. package/libx/_runtime/fiori/generic/read.js +17 -63
  113. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  114. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  115. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  116. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  117. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  118. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  119. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  120. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  121. package/libx/_runtime/fiori/utils/handler.js +3 -13
  122. package/libx/_runtime/fiori/utils/where.js +6 -1
  123. package/libx/_runtime/hana/pool.js +12 -11
  124. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  125. package/libx/_runtime/hana/searchToContains.js +3 -3
  126. package/libx/_runtime/index.js +5 -2
  127. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  128. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  129. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  130. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  131. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  132. package/libx/_runtime/messaging/message-queuing.js +18 -0
  133. package/libx/_runtime/remote/Service.js +14 -2
  134. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  135. package/libx/_runtime/remote/utils/client.js +117 -23
  136. package/libx/_runtime/sqlite/Service.js +2 -2
  137. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  138. package/libx/gql/GraphQLAdapter.js +33 -0
  139. package/libx/gql/constants/adapter.js +69 -0
  140. package/libx/gql/constants/cds.js +18 -0
  141. package/libx/gql/constants/graphql.js +33 -0
  142. package/libx/gql/resolvers/crud/create.js +15 -0
  143. package/libx/gql/resolvers/crud/delete.js +24 -0
  144. package/libx/gql/resolvers/crud/index.js +6 -0
  145. package/libx/gql/resolvers/crud/read.js +25 -0
  146. package/libx/gql/resolvers/crud/update.js +31 -0
  147. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  148. package/libx/gql/resolvers/field.js +5 -0
  149. package/libx/gql/resolvers/index.js +7 -0
  150. package/libx/gql/resolvers/mutation.js +23 -0
  151. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  152. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  153. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  154. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  155. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  156. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  157. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  158. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  159. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  164. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  165. package/libx/gql/resolvers/query.js +13 -0
  166. package/libx/gql/resolvers/root.js +34 -0
  167. package/libx/gql/schema/generate.js +18 -0
  168. package/libx/gql/schema/index.js +5 -0
  169. package/libx/gql/schema/mutation.js +76 -0
  170. package/libx/gql/schema/query.js +108 -0
  171. package/libx/gql/schema/typeDefMap.js +45 -0
  172. package/libx/gql/schema/utils/index.js +54 -0
  173. package/libx/gql/utils/index.js +12 -0
  174. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  175. package/libx/odata/index.js +80 -0
  176. package/libx/odata/odata2cqn/afterburner.js +170 -0
  177. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  178. package/libx/odata/odata2cqn/index.js +3 -0
  179. package/libx/odata/odata2cqn/parser.js +1 -0
  180. package/libx/odata/utils/index.js +64 -0
  181. package/libx/rest/RestAdapter.js +101 -0
  182. package/libx/rest/RestRequest.js +30 -0
  183. package/libx/rest/index.js +3 -0
  184. package/libx/rest/middleware/auth.js +22 -0
  185. package/libx/rest/middleware/content.js +15 -0
  186. package/libx/rest/middleware/create.js +40 -0
  187. package/libx/rest/middleware/delete.js +20 -0
  188. package/libx/rest/middleware/error.js +56 -0
  189. package/libx/rest/middleware/operation.js +39 -0
  190. package/libx/rest/middleware/parse.js +90 -0
  191. package/libx/rest/middleware/read.js +29 -0
  192. package/libx/rest/middleware/update.js +42 -0
  193. package/libx/rest/utils/data.js +65 -0
  194. package/package.json +4 -1
  195. package/server.js +20 -2
  196. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  197. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  198. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  199. package/libx/_runtime/common/utils/backlinks.js +0 -83
  200. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  201. package/libx/_runtime/odata/index.js +0 -55
  202. package/libx/_runtime/odata/odata2cqn.js +0 -1
  203. package/libx/_runtime/odata/readToCqn.js +0 -129
  204. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,42 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { INSERT } = cds.ql
3
+
4
+ const RestRequest = require('../RestRequest')
5
+
6
+ const UPSERT_ALLOWED = !(cds.env.runtime && cds.env.runtime.allow_upsert === false)
7
+
8
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
9
+
10
+ module.exports = async (_req, _res, next) => {
11
+ let { _srv: srv, _query: query, _target, _data, _params } = _req
12
+
13
+ let result,
14
+ status = 200
15
+
16
+ // unfortunately, express doesn't catch async errors -> try catch needed
17
+ try {
18
+ // if upsert it allowed, we need to catch 404 and retry with create
19
+ try {
20
+ // add the data (as copy, if upsert allowed)
21
+ query.with(UPSERT_ALLOWED ? deepCopyObject(_data) : _data)
22
+ // REVISIT: if PUT, req.method should be PUT -> Crud2Http maps UPSERT to PUT
23
+ result = await srv.dispatch(new RestRequest({ query, _target, method: _req.method }))
24
+ if (_params) Object.assign(result, _params[_params.length - 1])
25
+ } catch (e) {
26
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && UPSERT_ALLOWED) {
27
+ query = INSERT.into(query.UPDATE.entity).entries(
28
+ _params ? Object.assign(_data, _params[_params.length - 1]) : _data
29
+ )
30
+ result = await srv.dispatch(new RestRequest({ query, _target }))
31
+ status = 201
32
+ } else {
33
+ throw e
34
+ }
35
+ }
36
+ } catch (e) {
37
+ return next(e)
38
+ }
39
+
40
+ _req._result = { result, status }
41
+ next()
42
+ }
@@ -0,0 +1,65 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ const { deepCopyObject } = require('../../_runtime/common/utils/copy')
4
+ const { checkKeys, checkStatic } = require('../../_runtime/cds-services/util/assert')
5
+ const { MULTIPLE_ERRORS } = require('../../_runtime/common/error/constants')
6
+
7
+ // this can be reused for flattening data on db layer, if necessary
8
+ // const _getFlattenedCopy = (data, key, entity) => {
9
+ // const d = {}
10
+ // const prefix = key + '_'
11
+ // // TODO: ignore or preserve unknown?
12
+ // const matches = Object.keys(entity.elements)
13
+ // .filter(ele => ele.startsWith(prefix))
14
+ // .map(ele => ele.replace(prefix, ''))
15
+ // if (matches.length) {
16
+ // const current = data[key]
17
+ // for (const k of matches) if (current[k] !== undefined) d[prefix + k] = current[k]
18
+ // const nested = Object.keys(current).filter(k => current[k] && typeof current[k] === 'object')
19
+ // for (const k of nested) Object.assign(d, _getFlattenedCopy({ [prefix + k]: current[k] }, prefix + k, entity))
20
+ // }
21
+ // return d
22
+ // }
23
+
24
+ const _getDeepCopy = (data, definition, model, validations, skipKeys) => {
25
+ skipKeys || (definition.keys && validations.push(...checkKeys(definition, data)))
26
+ validations.push(...checkStatic(definition, data, true))
27
+
28
+ const d = {}
29
+ for (const k in data) {
30
+ const element = (definition.elements && definition.elements[k]) || (definition.params && definition.params[k])
31
+ if (!element) {
32
+ const { additional_properties } = cds.env.features
33
+ if (additional_properties === 'ignore' || !additional_properties) {
34
+ // ignore input (the default)
35
+ } else if (additional_properties === 'error') {
36
+ validations.push({ message: `Unknown property "${k}"`, code: 400 })
37
+ } else {
38
+ d[k] = data[k] && typeof data[k] === 'object' ? deepCopyObject(data[k]) : data[k]
39
+ }
40
+ } else if (element.isAssociation) {
41
+ d[k] = Array.isArray(data[k])
42
+ ? data[k].map(d => _getDeepCopy(d, model.definitions[element.target], model, validations))
43
+ : _getDeepCopy(data[k], model.definitions[element.target], model, validations)
44
+ } else if (element._isStructured) {
45
+ d[k] = _getDeepCopy(data[k], element, model, validations)
46
+ } else {
47
+ d[k] = data[k]
48
+ }
49
+ }
50
+ return d
51
+ }
52
+
53
+ const getDeepCopy = (data, definition, model, skipKeys) => {
54
+ const validations = []
55
+ const copy = Array.isArray(data)
56
+ ? data.map(d => _getDeepCopy(d, definition, model, validations, skipKeys))
57
+ : _getDeepCopy(data, definition, model, validations, skipKeys)
58
+ if (!validations.length) return [undefined, copy]
59
+ if (validations.length === 1) return [validations[0]]
60
+ return [Object.assign(new Error(MULTIPLE_ERRORS), { details: validations })]
61
+ }
62
+
63
+ module.exports = {
64
+ getDeepCopy
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "5.5.5",
3
+ "version": "5.6.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -40,6 +40,9 @@
40
40
  }
41
41
  },
42
42
  "lint-staged": {
43
+ "libx/odata/odata2cqn/grammar.pegjs": [
44
+ "npm run pegjs:odata2cqn && git add libx/odata/odata2cqn/parser.js"
45
+ ],
43
46
  "{libx,tests/_runtime}/**/*.js": [
44
47
  "npx prettier --write"
45
48
  ]
package/server.js CHANGED
@@ -40,16 +40,23 @@ module.exports = async function cds_server (options, o = { ...options, __proto__
40
40
  if (o.logger) app.use (o.logger) //> basic request logging
41
41
  if (o.toggler) app.use (o.toggler) //> feature toggler
42
42
 
43
+ // give uiflex a chance to plug into everything
44
+ if (cds.requires.extensibility) await require('./libx/_runtime/fiori/uiflex')() // REVISIT: later this should be a ext umbrella service
45
+
43
46
  // load specified models or all in project
44
47
  const csn = await cds.load (o.from||'*', {mocked:o.mocked})
45
- cds.model = o.from = cds.linked (cds.compile.for.odata(csn))
48
+ const m = cds.linked(csn).minified()
49
+ cds.model = o.from = cds.linked (cds.compile.for.odata(m))
46
50
 
47
51
  // connect to essential framework services if required
48
- const _init = o.in_memory && (db => cds.deploy(csn).to(db,o))
52
+ const _init = o.in_memory && (db => cds.deploy(m).to(db,o))
49
53
  if (cds.requires.db) cds.db = await cds.connect.to ('db') .then (_init)
50
54
  if (cds.requires.messaging) await cds.connect.to ('messaging')
51
55
  if (cds.requires.multitenancy) await cds.mtx.in (app)
52
56
 
57
+ // serve graphql
58
+ if (cds.env.features.graphql) serve_graphql(app)
59
+
53
60
  // serve all services declared in models
54
61
  await cds.serve (o.service,o).in (app)
55
62
  await cds.emit ('served', cds.services) //> hook for listeners
@@ -113,6 +120,16 @@ const _app_serve = function (endpoint) { return {
113
120
  }}
114
121
 
115
122
 
123
+ // register graphql router on served event
124
+ function serve_graphql (app) {
125
+ cds.on('served', services => {
126
+ const GraphQLAdapter = require('./libx/gql/GraphQLAdapter')
127
+ app.use(new GraphQLAdapter(services, { graphiql: true }))
128
+ cds.log()("serving GraphQL endpoint for all services { at: '/graphql' }")
129
+ })
130
+ }
131
+
132
+
116
133
  function cors (req, res, next) {
117
134
  const { origin } = req.headers
118
135
  if (origin) res.set('access-control-allow-origin', origin)
@@ -133,5 +150,6 @@ function correlate (req,_,next) {
133
150
  next()
134
151
  }
135
152
 
153
+
136
154
  // -------------------------------------------------------------------------
137
155
  if (!module.parent) module.exports ({from:process.argv[2]})
@@ -1,53 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- // REVISIT: probably needed for personal data handling
4
-
5
- const _getDiff = (data, status) => {
6
- const diffs = []
7
-
8
- for (const key in data) {
9
- if (!key.startsWith('*@odata.')) {
10
- diffs.push({ name: key, [status]: data[key] })
11
- }
12
- }
13
-
14
- return diffs
15
- }
16
-
17
- const _getDiffForUpdate = (oldData, newData) => {
18
- const diffs = []
19
-
20
- // Diff added if field in newData is not present or has a different value in oldData.
21
- // No diff added if a field is not present in newData.
22
- for (const key in newData) {
23
- if (!key.startsWith('*@odata.') && oldData[key] !== newData[key]) {
24
- diffs.push({ name: key, old: oldData[key], new: newData[key] })
25
- }
26
- }
27
-
28
- return diffs
29
- }
30
-
31
- /**
32
- * Compare to objects on first level.
33
- * Returns a list of changes like {name: 'key', old: 'val', new: 'val'}
34
- *
35
- * @param {string} event
36
- * @param {object} oldData
37
- * @param {object} newData
38
- * @returns {Array}
39
- */
40
- const diff = (event, oldData = {}, newData = {}) => {
41
- switch (event) {
42
- case 'CREATE':
43
- return _getDiff(newData, 'new')
44
- case 'UPDATE':
45
- return _getDiffForUpdate(oldData, newData)
46
- case 'DELETE':
47
- return _getDiff(oldData, 'old')
48
- default:
49
- return []
50
- }
51
- }
52
-
53
- module.exports = diff
@@ -1,247 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- const { getAuditLogNotWrittenError } = require('./errors')
4
-
5
- const _getCredentials = auditLog => {
6
- return require('./xsenv')('auditlog', auditLog)
7
- }
8
-
9
- const _keysToString = keys => {
10
- const strings = []
11
- for (const key in keys) {
12
- strings.push(`${key}: ${keys[key]}`)
13
- }
14
-
15
- return strings.join(', ')
16
- }
17
-
18
- const _logAttributes = (log, attributes) => {
19
- for (const attribute in attributes) {
20
- log = log.attribute({ name: attribute })
21
- }
22
- }
23
-
24
- const _logTenant = (log, tenant) => {
25
- log = tenant ? log.tenant(tenant) : log
26
- }
27
-
28
- /**
29
- * Logs securityMessage that user is not authorized.
30
- *
31
- * @param auditLogger - the audit logger
32
- * @param credentials - credentials for audit log instance
33
- * @param logger - logger object
34
- * @param {object} args - user if provided via basic auth, if not => ip address
35
- * @param args.user
36
- * @param args.ip ip address of the user
37
- * @private
38
- */
39
- const _logUnauthorized = (auditLogger, credentials, logger, { user, ip }) => {
40
- auditLogger.v2(credentials, function (err, auditLog) {
41
- if (err) {
42
- // TODO: Decide for a more meaningful error message
43
- return logger.error(`Error occurred while writing audit log: ${err}`)
44
- }
45
-
46
- let log = auditLog.securityMessage('Unsuccessful login attempt').by(user)
47
- log = ip ? log.externalIP(ip) : log
48
-
49
- log.log(function (err) {
50
- if (err) {
51
- return logger.error(`Error occurred while writing audit log: ${err}`)
52
- }
53
- })
54
- })
55
- }
56
-
57
- /**
58
- * Logs securityMessage that user does not have sufficient permissions.
59
- *
60
- * @param auditLogger - the audit logger
61
- * @param credentials - credentials for audit log instance
62
- * @param logger - logger object
63
- * @param {object} args
64
- * @param args.user - user that has not sufficient privileges
65
- * @param args.ip - ip address of the user
66
- * @param args.tenant - tenant of the user
67
- * @private
68
- */
69
- const _logMissingPermissions = (auditLogger, credentials, logger, { user, ip, tenant }) => {
70
- auditLogger.v2(credentials, function (err, auditLog) {
71
- if (err) {
72
- return logger.error(`Error occurred while writing audit log: ${err}`)
73
- }
74
-
75
- let log = auditLog.securityMessage('User does not have required permissions').by(user)
76
- log = ip ? log.externalIP(ip) : log
77
- log = tenant ? log.tenant(tenant) : log
78
-
79
- log.log(function (err) {
80
- if (err) {
81
- return logger.error(`Error occurred while writing audit log: ${err}`)
82
- }
83
- })
84
- })
85
- }
86
-
87
- /**
88
- *
89
- * Logs data read access.
90
- *
91
- * @param auditLogger - the audit logger
92
- * @param credentials - credentials for audit log instance
93
- * @param auditLogData - audit log input like dataSubject, the changed sensitive attributes or the user and tenant
94
- * @returns {Promise} which rejects if audit log cannot be written
95
- * @private
96
- */
97
- const _logReadAccess = (auditLogger, credentials, auditLogData) => {
98
- return new Promise((resolve, reject) => {
99
- auditLogger.v2(credentials, function (err, auditLog) {
100
- if (err) {
101
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
102
- }
103
-
104
- const { dataSubject, attributes, auditObject, user, tenant } = auditLogData
105
-
106
- try {
107
- let log = auditLog.read({
108
- type: auditObject.type,
109
- id: { key: _keysToString(auditObject.keys) }
110
- })
111
-
112
- _logAttributes(log, attributes)
113
- log = log.dataSubject({
114
- type: dataSubject.type,
115
- id: dataSubject.keys,
116
- role: dataSubject.role
117
- })
118
-
119
- _logTenant(log, tenant)
120
- log = log.by(user)
121
-
122
- log.log(function (err) {
123
- if (err) {
124
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
125
- }
126
- resolve()
127
- })
128
- } catch (err) {
129
- return reject(getAuditLogNotWrittenError(err, 'before commit', 'READ'))
130
- }
131
- })
132
- })
133
- }
134
-
135
- /**
136
- *
137
- * Logs diff of a data manipulation event.
138
- *
139
- * @param auditLogger - the audit logger
140
- * @param credentials - credentials for audit log instance
141
- * @param {object} args
142
- * @param args.context - the context object
143
- * @param args.auditLogData - audit log input like dataSubject, the changed sensitive attributes or the user and tenant
144
- * @param args.phase - the phase the logDataChange handler was triggered in
145
- * @returns {Promise}
146
- * @private
147
- */
148
- const _logDataChange = (auditLogger, credentials, { context, auditLogData, phase }) => {
149
- const { dataSubject, attributes, auditObject, diff, user, tenant } = auditLogData
150
- return new Promise((resolve, reject) => {
151
- auditLogger.v2(credentials, function (err, auditLog) {
152
- if (err) {
153
- return reject(getAuditLogNotWrittenError(err, phase))
154
- }
155
-
156
- try {
157
- let log = auditLog.update({
158
- type: auditObject.type,
159
- id: { key: _keysToString(auditObject.keys) }
160
- })
161
-
162
- for (const difference of diff) {
163
- if (attributes[difference.name]) {
164
- log = log.attribute(Object.assign({ new: 'null', old: 'null' }, difference))
165
- }
166
- }
167
-
168
- log = log.dataSubject({
169
- type: dataSubject.type,
170
- id: dataSubject.keys,
171
- role: dataSubject.role
172
- })
173
-
174
- log = tenant ? log.tenant(tenant) : log
175
- log = log.by(user)
176
-
177
- log.logPrepare(function (err) {
178
- if (err) {
179
- return reject(getAuditLogNotWrittenError(err, phase))
180
- }
181
- context._.auditLogContinuation = log
182
- resolve()
183
- })
184
- } catch (err) {
185
- return reject(getAuditLogNotWrittenError(err, phase))
186
- }
187
- })
188
- })
189
- }
190
-
191
- /**
192
- * Initializes the audit log object.
193
- * If options.auditLog provided, it looks via xsenv for a configured audit log instance.
194
- * If not, logs the audit events to the logger object (if provided via options, if not to console).
195
- *
196
- * @param {object} auditLog - the service options
197
- * @param {object} logger - the logger object
198
- * @returns {object} - with convenience methods logUnauthorized and logMissingPermissions to write audit logs
199
- */
200
- const initialize = (auditLog, logger) => {
201
- // REVISIT: use xsenv directly and remove xsenv util
202
- let auditLogConfig = _getCredentials(auditLog)
203
-
204
- try {
205
- const auditLogger = require('@sap/audit-logging')
206
- if (!auditLogConfig) {
207
- auditLogConfig = { logToConsole: true }
208
- }
209
-
210
- return {
211
- logDataChange: info => {
212
- return _logDataChange(auditLogger, auditLogConfig, info)
213
- },
214
- logReadAccess: info => {
215
- return _logReadAccess(auditLogger, auditLogConfig, info)
216
- },
217
- logUnauthorized: info => {
218
- return _logUnauthorized(auditLogger, auditLogConfig, logger, info)
219
- },
220
- logMissingPermissions: info => {
221
- return _logMissingPermissions(auditLogger, auditLogConfig, logger, info)
222
- }
223
- }
224
- } catch (err) {
225
- if (auditLogConfig) {
226
- // this should crash the app if audit log was defined in VCAP and module could not be loaded
227
- setImmediate(() => {
228
- throw err
229
- })
230
- }
231
-
232
- return {
233
- logDataChange: info => {
234
- logger.log(info)
235
- return Promise.resolve()
236
- },
237
- logReadAccess: info => {
238
- logger.log(info)
239
- return Promise.resolve()
240
- },
241
- logUnauthorized: logger.warn,
242
- logMissingPermissions: logger.warn
243
- }
244
- }
245
- }
246
-
247
- module.exports = initialize
@@ -1,51 +0,0 @@
1
- /* istanbul ignore file */
2
-
3
- const _findByType = type => {
4
- try {
5
- return require('@sap/xsenv').getServices({
6
- [type]: {
7
- tag: type
8
- }
9
- })
10
- } catch (e) {}
11
- }
12
-
13
- const _structured = filter => {
14
- return Object.keys(filter).some(key => {
15
- return typeof filter[key] === 'object'
16
- })
17
- }
18
-
19
- const _filter = (serviceName, filter) => {
20
- if (_structured(filter)) {
21
- return filter
22
- }
23
-
24
- return { [serviceName]: filter }
25
- }
26
-
27
- const _getServices = (serviceName, filter) => {
28
- if (typeof filter === 'object') {
29
- return require('@sap/xsenv').getServices(_filter(serviceName, filter))
30
- }
31
-
32
- return _findByType(serviceName)
33
- }
34
-
35
- /**
36
- * Get credentials for service by name and/or filter object.
37
- *
38
- * @param {string} serviceName
39
- * @param {object} [filter]
40
- * @returns {*}
41
- * @private
42
- */
43
- const getCredentialsFromXsEnv = (serviceName, filter) => {
44
- const services = _getServices(serviceName, filter)
45
-
46
- if (services) {
47
- return services[Object.keys(services)[0]]
48
- }
49
- }
50
-
51
- module.exports = getCredentialsFromXsEnv
@@ -1,83 +0,0 @@
1
- const { foreignKeyPropagations } = require('./foreignKeyPropagations')
2
-
3
- const getOnCondElements = (onCond, onCondElements = []) => {
4
- const andIndex = onCond.indexOf('and')
5
- const entityKey = onCond[2].ref && onCond[2].ref.join('.')
6
- const entityVal = onCond[2].val
7
- const targetKey = onCond[0].ref && onCond[0].ref.join('.')
8
- const targetVal = onCond[0].val
9
- onCondElements.push({ entityKey, targetKey, entityVal, targetVal })
10
-
11
- if (andIndex !== -1) {
12
- getOnCondElements(onCond.slice(andIndex + 1), onCondElements)
13
- }
14
- return onCondElements
15
- }
16
-
17
- const isSelfManaged = element => {
18
- if (element.on && element.on.length > 2) {
19
- return (
20
- (element.on[0].ref && element.on[0].ref[0]) === '$self' || (element.on[2].ref && element.on[2].ref[0] === '$self')
21
- )
22
- }
23
- return false
24
- }
25
-
26
- const _getBacklinkNameFromOnCond = element => {
27
- if (element.on && element.on.length === 3 && element.on[0].ref && element.on[2].ref) {
28
- if (element.on[0].ref[0] === '$self') {
29
- return element.on[2].ref[element.on[2].ref.length - 1]
30
- } else if (element.on[2].ref[0] === '$self') {
31
- return element.on[0].ref[element.on[0].ref.length - 1]
32
- }
33
- }
34
- }
35
-
36
- const isBacklink = (element, parent, checkContained, backLinkName) => {
37
- if (!element._isAssociationStrict) return false
38
- if (!parent || !(element.keys || element.on)) return false
39
- if (element.target !== parent.name) return false
40
-
41
- const _isBackLink = parentElement =>
42
- (!checkContained || parentElement._isContained) && _getBacklinkNameFromOnCond(parentElement) === element.name
43
-
44
- if (backLinkName) {
45
- const parentElement = parent.elements[backLinkName]
46
- return parentElement.isAssociation && _isBackLink(parentElement)
47
- }
48
- for (const parentElementName in parent.elements) {
49
- const parentElement = parent.elements[parentElementName]
50
- if (!parentElement.isAssociation) continue
51
- if (_isBackLink(parentElement)) return true
52
- }
53
-
54
- return false
55
- }
56
- // REVISIT: replace getBacklinks where used with foreignKeyPropagation
57
- const getBackLinks = element => {
58
- const res = foreignKeyPropagations(element)
59
-
60
- if (element.on) {
61
- return res.map(e => ({
62
- entityKey:
63
- e.prefix && !e.childFieldName.includes(e.prefix) ? e.prefix + '_' + e.childFieldName : e.childFieldName,
64
- entityVal: e.childFieldValue,
65
- targetKey:
66
- e.prefix && !e.parentFieldName.includes(e.prefix) ? e.prefix + '_' + e.parentFieldName : e.parentFieldName,
67
- targetVal: e.parentFieldValue
68
- }))
69
- }
70
-
71
- return res.map(e => ({
72
- entityKey:
73
- e.prefix && !e.parentFieldName.includes(e.prefix) ? e.prefix + '_' + e.parentFieldName : e.parentFieldName,
74
- targetKey: e.prefix && !e.childFieldName.includes(e.prefix) ? e.prefix + '_' + e.childFieldName : e.childFieldName
75
- }))
76
- }
77
-
78
- module.exports = {
79
- getBackLinks,
80
- isSelfManaged,
81
- getOnCondElements,
82
- isBacklink
83
- }
@@ -1,72 +0,0 @@
1
- const { getNavigationIfStruct } = require('./structured')
2
- const getColumns = require('../../db/utils/columns')
3
- const _isAsteriskCol = col => col === '*' || (col.ref && col.ref[0] === '*')
4
- const cds = require('../../../../libx/_runtime/cds')
5
-
6
- const _isDraft = req => {
7
- return (
8
- req.target &&
9
- ((typeof req.target.name === 'string' && req.target.name.endsWith('_drafts')) ||
10
- typeof req.target.name === 'object') // > union, which is (currently) only the case with draft
11
- )
12
- }
13
-
14
- const _getColumns = target => {
15
- const columns = getColumns(target)
16
- return columns.map(col => ({ ref: [col.name] }))
17
- }
18
-
19
- const _rewriteExpandAsterisks = (column, entity, refs) => {
20
- const navigation = getNavigationIfStruct(entity, refs)
21
- const targetEntity = navigation && navigation._target
22
-
23
- if (Array.isArray(column.expand) && targetEntity) {
24
- column.expand.forEach(col => {
25
- if (col.ref && col.expand) {
26
- _rewriteExpandAsterisks(col, targetEntity, col.ref)
27
- }
28
- })
29
- }
30
-
31
- if (column.expand === '*') {
32
- column.expand = _getColumns(targetEntity)
33
- return
34
- }
35
-
36
- if (Array.isArray(column.expand)) {
37
- const asteriskColumnIndex = column.expand.findIndex(col => _isAsteriskCol(col))
38
- if (asteriskColumnIndex === -1) return // * not found
39
-
40
- column.expand.splice(asteriskColumnIndex, 1)
41
- const columns = getColumns(targetEntity)
42
- columns.forEach(col => {
43
- column.expand.push({ ref: [col.name] })
44
- })
45
- }
46
- }
47
-
48
- const _rewriteAsterisks = req => {
49
- if (_isDraft(req) || !req.query.SELECT) return
50
-
51
- if (cds.env.features.odata_new_parser && !req.query.SELECT.columns) {
52
- req.query.SELECT.columns = _getColumns(req.target)
53
- return
54
- }
55
-
56
- if (!req.query.SELECT.columns) return
57
- const columnsIncludesAsterisk = req.query.SELECT.columns.some(col => _isAsteriskCol(col))
58
-
59
- if (columnsIncludesAsterisk) {
60
- const expandColumns = req.query.SELECT.columns.filter(column => column.expand)
61
- const columns = _getColumns(req.target)
62
- req.query.SELECT.columns = expandColumns.length ? [...columns, ...expandColumns] : columns
63
- }
64
-
65
- req.query.SELECT.columns.forEach(col => {
66
- if (col.ref && col.ref[0] !== 'DraftAdministrativeData' && col.expand && req.target) {
67
- _rewriteExpandAsterisks(col, req.target, col.ref)
68
- }
69
- })
70
- }
71
-
72
- module.exports = _rewriteAsterisks