@sap/cds 5.8.4 → 5.9.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 (244) hide show
  1. package/CHANGELOG.md +174 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskFactory.js +3 -3
  5. package/bin/build/buildTaskProviderFactory.js +1 -1
  6. package/bin/build/constants.js +1 -1
  7. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  8. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  9. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  10. package/bin/build/provider/hana/2migration.js +27 -24
  11. package/bin/build/provider/hana/index.js +17 -18
  12. package/bin/build/provider/hana/migrationtable.js +9 -10
  13. package/bin/build/provider/java-cf/index.js +4 -5
  14. package/bin/build/provider/node-cf/index.js +99 -6
  15. package/bin/cds.js +17 -18
  16. package/bin/deploy/to-hana/cfUtil.js +16 -19
  17. package/bin/deploy/to-hana/hana.js +7 -24
  18. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  19. package/bin/mtx/in-cds.js +2 -2
  20. package/bin/serve.js +10 -3
  21. package/bin/utils/modules.js +7 -0
  22. package/bin/version.js +56 -3
  23. package/lib/compile/cdsc.js +26 -3
  24. package/lib/compile/etc/_localized.js +36 -25
  25. package/lib/compile/etc/csv.js +8 -8
  26. package/lib/compile/for/drafts.js +9 -0
  27. package/lib/compile/for/java.js +16 -0
  28. package/lib/compile/for/nodejs.js +12 -0
  29. package/lib/compile/for/odata.js +1 -1
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/index.js +1 -1
  38. package/lib/core/entities.js +15 -14
  39. package/lib/core/index.js +39 -36
  40. package/lib/core/reflect.js +4 -2
  41. package/lib/deploy.js +114 -127
  42. package/lib/env/defaults.js +1 -0
  43. package/lib/env/index.js +165 -165
  44. package/lib/env/presets.js +1 -0
  45. package/lib/env/requires.js +120 -49
  46. package/lib/index.js +1 -0
  47. package/lib/log/format/kibana.js +2 -2
  48. package/lib/ql/SELECT.js +10 -0
  49. package/lib/ql/parse.js +1 -0
  50. package/lib/req/cds-context.js +4 -1
  51. package/lib/req/context.js +50 -56
  52. package/lib/req/event.js +1 -6
  53. package/lib/req/locale.js +6 -5
  54. package/lib/req/request.js +2 -0
  55. package/lib/req/user.js +7 -5
  56. package/lib/serve/Service-api.js +10 -7
  57. package/lib/serve/Service-dispatch.js +9 -11
  58. package/lib/serve/Service-methods.js +30 -41
  59. package/lib/serve/Transaction.js +10 -7
  60. package/lib/serve/adapters.js +7 -5
  61. package/lib/serve/index.js +24 -12
  62. package/lib/utils/data.js +1 -1
  63. package/lib/utils/index.js +27 -30
  64. package/lib/utils/resources/index.js +101 -0
  65. package/lib/utils/resources/tar.js +71 -0
  66. package/lib/utils/resources/utils.js +11 -0
  67. package/libx/_runtime/audit/Service.js +36 -39
  68. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  69. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  70. package/libx/_runtime/audit/utils/v2.js +1 -2
  71. package/libx/_runtime/auth/index.js +126 -84
  72. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  73. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  74. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  75. package/libx/_runtime/auth/strategies/mock.js +0 -4
  76. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  77. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  78. package/libx/_runtime/auth/utils.js +22 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  80. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  81. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  82. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  91. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  99. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  101. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  104. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  105. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  106. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  107. package/libx/_runtime/cds-services/services/Service.js +40 -0
  108. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  109. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  110. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  111. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  113. package/libx/_runtime/cds-services/util/assert.js +20 -14
  114. package/libx/_runtime/cds.js +9 -1
  115. package/libx/_runtime/common/aspects/any.js +5 -0
  116. package/libx/_runtime/common/aspects/entity.js +25 -7
  117. package/libx/_runtime/common/aspects/utils.js +2 -2
  118. package/libx/_runtime/common/composition/data.js +6 -0
  119. package/libx/_runtime/common/composition/insert.js +3 -2
  120. package/libx/_runtime/common/composition/tree.js +4 -10
  121. package/libx/_runtime/common/composition/update.js +4 -4
  122. package/libx/_runtime/common/constants/draft.js +29 -26
  123. package/libx/_runtime/common/error/constants.js +2 -2
  124. package/libx/_runtime/common/error/frontend.js +7 -15
  125. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  126. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  127. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  128. package/libx/_runtime/common/generic/auth/index.js +32 -0
  129. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  130. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  131. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  132. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  133. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  134. package/libx/_runtime/common/generic/crud.js +8 -6
  135. package/libx/_runtime/common/generic/etag.js +1 -1
  136. package/libx/_runtime/common/generic/input.js +35 -35
  137. package/libx/_runtime/common/generic/sorting.js +2 -3
  138. package/libx/_runtime/common/generic/temporal.js +2 -2
  139. package/libx/_runtime/common/i18n/messages.properties +1 -1
  140. package/libx/_runtime/common/toggles/handler.js +21 -0
  141. package/libx/_runtime/common/utils/copy.js +10 -1
  142. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  143. package/libx/_runtime/common/utils/csn.js +63 -1
  144. package/libx/_runtime/common/utils/dollar.js +10 -1
  145. package/libx/_runtime/common/utils/draft.js +46 -7
  146. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  147. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  148. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  149. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  150. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  151. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  152. package/libx/_runtime/common/utils/resolveView.js +4 -1
  153. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  154. package/libx/_runtime/common/utils/structured.js +33 -37
  155. package/libx/_runtime/common/utils/template.js +17 -8
  156. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  157. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  158. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  159. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  160. package/libx/_runtime/db/generic/index.js +1 -3
  161. package/libx/_runtime/db/generic/input.js +5 -10
  162. package/libx/_runtime/db/generic/rewrite.js +5 -2
  163. package/libx/_runtime/db/generic/structured.js +2 -2
  164. package/libx/_runtime/db/query/delete.js +2 -2
  165. package/libx/_runtime/db/query/insert.js +1 -1
  166. package/libx/_runtime/db/query/update.js +9 -14
  167. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  168. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  169. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  170. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  171. package/libx/_runtime/db/utils/columns.js +3 -3
  172. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  173. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  174. package/libx/_runtime/extensibility/mps/index.js +5 -0
  175. package/libx/_runtime/extensibility/mps/service.js +111 -0
  176. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  177. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  178. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  179. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  180. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  181. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  182. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  184. package/libx/_runtime/fiori/generic/activate.js +2 -2
  185. package/libx/_runtime/fiori/generic/before.js +4 -4
  186. package/libx/_runtime/fiori/generic/new.js +3 -3
  187. package/libx/_runtime/fiori/generic/patch.js +1 -1
  188. package/libx/_runtime/fiori/generic/read.js +58 -66
  189. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  190. package/libx/_runtime/fiori/utils/handler.js +6 -13
  191. package/libx/_runtime/fiori/utils/where.js +6 -5
  192. package/libx/_runtime/hana/Service.js +4 -10
  193. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  194. package/libx/_runtime/hana/driver.js +2 -2
  195. package/libx/_runtime/hana/execute.js +27 -74
  196. package/libx/_runtime/hana/pool.js +1 -1
  197. package/libx/_runtime/hana/streaming.js +2 -1
  198. package/libx/_runtime/index.js +6 -6
  199. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  200. package/libx/_runtime/messaging/Outbox.js +2 -2
  201. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  202. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  203. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  204. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  205. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  206. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  207. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  208. package/libx/_runtime/messaging/file-based.js +5 -5
  209. package/libx/_runtime/messaging/message-queuing.js +14 -12
  210. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  211. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  212. package/libx/_runtime/messaging/service.js +8 -6
  213. package/libx/_runtime/remote/Service.js +44 -8
  214. package/libx/_runtime/remote/utils/client.js +20 -13
  215. package/libx/_runtime/remote/utils/data.js +11 -11
  216. package/libx/_runtime/sqlite/Service.js +6 -9
  217. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  218. package/libx/_runtime/types/api.js +10 -2
  219. package/libx/common/utils/ucsn.js +109 -0
  220. package/libx/gql/resolvers/crud/update.js +5 -0
  221. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  222. package/libx/gql/schema/typeDefMap.js +2 -2
  223. package/libx/odata/afterburner.js +110 -16
  224. package/libx/odata/grammar.pegjs +9 -1
  225. package/libx/odata/parseToCqn.js +39 -0
  226. package/libx/odata/parser.js +1 -1
  227. package/libx/rest/RestAdapter.js +9 -1
  228. package/libx/rest/middleware/input.js +54 -0
  229. package/libx/rest/middleware/operation.js +14 -1
  230. package/libx/rest/middleware/parse.js +11 -7
  231. package/package.json +1 -1
  232. package/server.js +34 -19
  233. package/srv/audit-log.cds +2 -2
  234. package/srv/flex.cds +8 -2
  235. package/srv/flex.js +1 -1
  236. package/srv/mps.cds +23 -0
  237. package/srv/mps.js +1 -0
  238. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  239. package/libx/_runtime/common/generic/auth.js +0 -874
  240. package/libx/_runtime/common/toggles/alpha.js +0 -43
  241. package/libx/_runtime/db/generic/arrayed.js +0 -33
  242. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  243. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  244. package/libx/rest/utils/data.js +0 -60
@@ -14,7 +14,7 @@ const {
14
14
  resolveDataSubjectPromises
15
15
  } = require('./utils')
16
16
 
17
- let als
17
+ let auditLogService
18
18
 
19
19
  const attachDiffToContextHandler = async function (req) {
20
20
  // store diff in audit data structure at context
@@ -112,15 +112,14 @@ const calcModificationLogsHandler4After = function (_, req) {
112
112
  }
113
113
 
114
114
  const emitModificationHandler = async function (_, req) {
115
- als = als || (await cds.connect.to('audit-log'))
116
- if (!als.ready) return
115
+ auditLogService = auditLogService || (await cds.connect.to('audit-log'))
117
116
 
118
117
  const modificationLogs = req.context._audit.modificationLogs.get(req.query)
119
118
  const modifications = Object.keys(modificationLogs)
120
119
  .map(k => modificationLogs[k])
121
120
  .filter(log => log.attributes.length)
122
121
 
123
- await als.emit('dataModificationLog', { modifications })
122
+ await auditLogService.emit('dataModificationLog', { modifications })
124
123
  }
125
124
 
126
125
  module.exports = {
@@ -9,8 +9,7 @@ function connect(credentials) {
9
9
  try {
10
10
  auditLogging = require('@sap/audit-logging')
11
11
  } catch (e) {
12
- LOG._warn &&
13
- LOG.warn('Unable to require module @sap/audit-logging. Make sure it is installed if audit logging is required.')
12
+ // not able to require lib -> no audit logging ootb
14
13
  return resolve()
15
14
  }
16
15
  try {
@@ -1,10 +1,10 @@
1
1
  const cds = require('../cds')
2
- const LOG = cds.log('app')
2
+ const LOG = cds.log()
3
3
 
4
4
  const _require = require('../common/utils/require')
5
- const { UNAUTHORIZED } = require('./utils')
5
+ const { UNAUTHORIZED, isRestricted } = require('./utils')
6
6
 
7
- let passport
7
+ let passport, logged
8
8
 
9
9
  // strategy initializers for lazy loading of dependencies
10
10
  const _initializers = {
@@ -21,125 +21,167 @@ const _initializers = {
21
21
  const DwcStrategy = require('./strategies/dwc')
22
22
  passport.use(new DwcStrategy())
23
23
  },
24
- JWT: ({ uaa }) => {
24
+ jwt: ({ credentials, uaa }) => {
25
25
  const JWTStrategy = require('./strategies/JWT')
26
- passport.use(new JWTStrategy(uaa))
26
+ if (credentials) {
27
+ passport.use(new JWTStrategy(credentials))
28
+ } else if (uaa) {
29
+ // REVISIT: compat, remove with cds^6
30
+ passport.use(new JWTStrategy(uaa.credentials))
31
+ } else {
32
+ throw Object.assign(new Error('No or malformed credentials for auth kind "jwt-auth"'), { credentials })
33
+ }
27
34
  },
28
35
  mock: ({ users }, srvName) => {
29
36
  const MockStrategy = require('./strategies/mock')
30
37
  passport.use(new MockStrategy(users, `mock_${srvName}`))
31
38
  },
32
- xsuaa: ({ uaa }) => {
39
+ xsuaa: ({ credentials, uaa }) => {
33
40
  const XSUAAStrategy = require('./strategies/xsuaa')
34
- passport.use(new XSUAAStrategy(uaa))
41
+ if (credentials) {
42
+ passport.use(new XSUAAStrategy(credentials))
43
+ } else if (uaa) {
44
+ // REVISIT: compat, remove with cds^6
45
+ passport.use(new XSUAAStrategy(uaa.credentials))
46
+ } else {
47
+ throw Object.assign(new Error('No or malformed credentials for auth kind "xsuaa"'), { credentials })
48
+ }
35
49
  }
36
50
  }
37
51
 
38
52
  // map for initialized authenticators
39
53
  const _authenticators = {}
40
54
 
41
- const _isRestricted = srv => {
42
- return !!(
43
- srv.definition['@requires'] ||
44
- Object.keys(srv.entities).some(k => srv.entities[k]['@requires'] || srv.entities[k]['@restrict']) ||
45
- Object.keys(srv.entities).some(
46
- k =>
47
- srv.entities[k].actions &&
48
- Object.keys(srv.entities[k].actions).some(
49
- l => srv.entities[k].actions[l]['@requires'] || srv.entities[k].actions[l]['@restrict']
50
- )
51
- ) ||
52
- Object.keys(srv.operations).some(k => srv.operations[k]['@requires'] || srv.operations[k]['@restrict'])
53
- )
55
+ const _log = (req, challenges) => {
56
+ if (!LOG._debug) return
57
+ const challengesLog = challenges ? ['User challenges:', challenges] : []
58
+ LOG.debug(`User "${req.user.id}" request URL`, req.url, '\n', ...challengesLog)
54
59
  }
55
60
 
56
- const _initializeStrategy = (strategy, config, srv) => {
57
- if (!_initializers[strategy]) {
58
- // REVISIT: why?
59
- process.exitCode = 1
60
- throw new Error(`Authentication strategy "${strategy}" is not supported`)
61
+ const _authCallback = (req, res, next, internalError, user, challenges) => {
62
+ // An internal error occurs during the authentication process
63
+ if (internalError) {
64
+ // REVISIT: What to do? Security log?
65
+ return res.status(401).json(UNAUTHORIZED) // no details to client
61
66
  }
62
67
 
63
- if (!_authenticators[strategy] || strategy === 'mock' || process.env.NODE_ENV === 'test') {
64
- _initializers[strategy](config, srv.name)
65
- _authenticators[strategy] = true
68
+ let infoChallenges
69
+
70
+ if (challenges) {
71
+ if (Array.isArray(challenges)) {
72
+ infoChallenges = challenges.filter(ele => ele)
73
+ infoChallenges = infoChallenges.length ? infoChallenges : undefined
74
+ } else {
75
+ req.authInfo = challenges
76
+ }
66
77
  }
78
+
79
+ req.user = user || Object.defineProperty(new cds.User(), '_challenges', { enumerable: false, value: infoChallenges })
80
+ Object.defineProperty(req.user, '_req', { enumerable: false, value: req })
81
+ _log(req, infoChallenges)
82
+ next()
67
83
  }
68
84
 
69
- const _callback = (req, res, next, err, user, info) => {
70
- // REVISIT: when is this the case and what to do?
71
- if (err) {
72
- // REVISIT: security log?
73
- return res.status(401).json(UNAUTHORIZED) // > no details to client
74
- }
85
+ const _mountMockAuth = (srv, app, strategy, config) => {
86
+ const impl =
87
+ strategy === 'dummy'
88
+ ? new (require('./strategies/dummy'))()
89
+ : new (require('./strategies/mock'))(config.users, `mock_${srv.name}`)
75
90
 
76
- let challenges
77
- if (info && Array.isArray(info)) {
78
- // > info === challenges
79
- challenges = info.filter(ele => ele)
80
- challenges = challenges.length ? challenges : undefined
81
- info = null
82
- }
91
+ app.use(srv.path, (req, res, next) => {
92
+ let user, challenge
93
+ impl.success = arg => (user = arg)
94
+ impl.fail = arg => (challenge = arg)
95
+ impl.authenticate(req)
96
+ _authCallback(req, res, next, undefined, user, [challenge])
97
+ })
98
+ }
83
99
 
84
- // compat req._.req.authInfo
85
- if (info) req.authInfo = info
100
+ const _mountPassportAuth = (srv, app, strategy, config) => {
101
+ passport = passport || _require('passport')
86
102
 
87
- req.user = user || Object.defineProperty(new cds.User(), '_challenges', { enumerable: false, value: challenges })
88
- Object.defineProperty(req.user, '_req', { enumerable: false, value: req })
103
+ // initialize strategy
104
+ if (!_authenticators[strategy] || strategy === 'mock' || process.env.NODE_ENV === 'test') {
105
+ _initializers[strategy](config, srv.name)
106
+ _authenticators[strategy] = true
107
+ }
89
108
 
90
- next()
109
+ // authenticate
110
+ app.use(srv.path, passport.initialize())
111
+ app.use(srv.path, (req, res, next) => {
112
+ const options = { session: false, failWithError: true }
113
+ const callback = _authCallback.bind(undefined, req, res, next)
114
+ passport.authenticate(strategy === 'jwt' ? 'JWT' : strategy, options, callback)(req, res, next)
115
+ })
91
116
  }
92
117
 
118
+ /*
119
+ * export authentication middleware
120
+ */
121
+ // eslint-disable-next-line complexity
93
122
  module.exports = (srv, app, options) => {
94
- let config = options.auth
123
+ // NOTE: options.auth is not an official API
124
+ let config = 'auth' in options ? options.auth : cds.env.requires.auth
95
125
 
96
- const isRestricted = _isRestricted(srv) || (cds.env.requires.auth && cds.env.requires.auth.restrict_all_services)
97
- const isMultiTenant = !!(cds.env.requires && cds.env.requires.db && cds.env.requires.db.multiTenant)
126
+ // cds.env.requires.auth = { kind: 'xsuaa-auth' } was briefly documented on capire -> also support
127
+ if (config && config.kind === 'xsuaa-auth' && !config.credentials) config = cds.env.requires.xsuaa
98
128
 
99
- if (!config && !isRestricted && (!isMultiTenant || process.env.NODE_ENV !== 'production')) {
100
- if (isMultiTenant) LOG._warn && LOG.warn(`[${srv.name}] - Authentication needed for multitenancy in production.`)
129
+ // mount custom authentication middleware
130
+ if (config && config.impl) {
131
+ app.use(srv.path, _require(cds.resolve(config.impl)))
101
132
  return
102
133
  }
103
134
 
104
- config = config || cds.env.requires.auth
135
+ const restricted = isRestricted(srv)
136
+ if (restricted && !config) {
137
+ // REVISIT: why exitCode needed?
138
+ process.exitCode = 1
139
+ throw new Error('Authentication required for authorization checks')
140
+ }
105
141
 
106
- if (config.impl) {
107
- // > custom middleware
108
- app.use(srv.path, _require(cds.resolve(config.impl)))
109
- return
142
+ const isMultiTenant = cds.env.requires.db && cds.env.requires.db.multiTenant
143
+ if (isMultiTenant && !config) {
144
+ // REVISIT: why exitCode needed?
145
+ process.exitCode = 1
146
+ throw new Error('Authentication required for multitenancy')
110
147
  }
111
148
 
112
- config.strategy = Array.isArray(config.strategy) ? config.strategy : [config.strategy]
113
-
114
- if (config.strategy.length === 1 && config.strategy[0] in { dummy: 1, mock: 1 }) {
115
- // > without passport
116
- const impl =
117
- config.strategy[0] === 'dummy'
118
- ? new (require('./strategies/dummy'))()
119
- : new (require('./strategies/mock'))(config.users, `mock_${srv.name}`)
120
- app.use(srv.path, (req, res, next) => {
121
- let user, challenge
122
- impl.success = arg => (user = arg)
123
- impl.fail = arg => (challenge = arg)
124
- impl.authenticate(req)
125
- _callback(req, res, next, undefined, user, [challenge])
126
- })
149
+ if (!config) {
150
+ if (!logged) {
151
+ const msg = 'No authentication configured'
152
+ if (process.env.NODE_ENV !== 'production') LOG._debug && LOG.debug(msg)
153
+ else LOG._info && LOG.info(`${msg}. This is not recommended in production.`)
154
+ }
155
+
156
+ // no auth wanted > return
127
157
  return
128
158
  }
129
159
 
130
- // here, we need passport
131
- passport = passport || _require('passport')
160
+ // strategy from kind
161
+ let strategy
162
+ // compat for auth.strategy
163
+ if (config.strategy) {
164
+ strategy = config.strategy.toLowerCase()
165
+ }
166
+ strategy = strategy || config.kind.replace('-auth', '').toLowerCase()
167
+ if (strategy === 'mocked') strategy = 'mock'
132
168
 
133
- // initialize strategies
134
- for (const strategy of config.strategy) _initializeStrategy(strategy, config, srv)
169
+ if (!_initializers[strategy]) {
170
+ // REVISIT: why exitCode needed?
171
+ process.exitCode = 1
172
+ throw new Error(`Authentication kind "${config.kind}" is not supported`)
173
+ }
135
174
 
136
- // authenticate
137
- app.use(srv.path, passport.initialize())
138
- app.use(srv.path, (req, res, next) => {
139
- passport.authenticate(
140
- config.strategy.map(s => (s === 'mock' ? `mock_${srv.name}` : s)),
141
- { session: false, failWithError: true },
142
- _callback.bind(undefined, req, res, next)
143
- )(req, res, next)
144
- })
175
+ if (!logged) LOG._debug && LOG.debug(`Using authentication kind "${config.kind}"`)
176
+
177
+ if (strategy in { dummy: 1, mock: 1 }) {
178
+ // > dummy or mock authentication (for development/testing)
179
+ _mountMockAuth(srv, app, strategy, config)
180
+ } else {
181
+ // > passport authentication
182
+ _mountPassportAuth(srv, app, strategy, config)
183
+ }
184
+
185
+ // so we don't log the same stuff multiple times
186
+ logged = true
145
187
  }
@@ -1,40 +1,33 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const _require = require('../../common/utils/require')
4
- const uaaUtils = require('./utils/uaa')
5
- const xssecUtils = require('./utils/xssec')
4
+ const xssecUtils = require('./xssecUtils')
6
5
 
7
6
  // use _require for a better error message
8
7
  const { JWTStrategy: JS } = _require('@sap/xssec')
9
8
 
10
9
  class JWTStrategy extends JS {
11
- constructor(uaa) {
12
- super(uaaUtils.getCredentials(uaa))
10
+ constructor(credentials) {
11
+ super(credentials)
12
+ this.credentials = credentials
13
13
  }
14
14
 
15
15
  authenticate(req, options) {
16
+ const credentials = this.credentials
17
+
16
18
  // monkey patch success
17
19
  const _success = this.success
18
20
  this.success = (user, info) => {
19
21
  // create cds.User
20
- user = new cds.User(
21
- Object.assign(
22
- { id: xssecUtils.getUserId(user, info) },
23
- {
24
- _roles: xssecUtils.getRoles(['any', 'identified-user'], info),
25
- attr: xssecUtils.getAttrForJWT(info),
26
- tenant: xssecUtils.getTenant(info) || null
27
- }
28
- )
29
- )
30
-
31
- // set _req for locale getter
32
- Object.defineProperty(user, '_req', { enumerable: false, value: req })
33
-
22
+ user = new cds.User({
23
+ id: xssecUtils.getUserId(user, info),
24
+ tenant: xssecUtils.getTenant(info) || null,
25
+ _roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
26
+ attr: xssecUtils.getAttrForJWT(info)
27
+ })
34
28
  // call "super.success"
35
29
  _success(user, info)
36
30
  }
37
-
38
31
  super.authenticate(req, options)
39
32
  }
40
33
  }
@@ -5,12 +5,8 @@ class DummyStrategy {
5
5
  this.name = 'dummy'
6
6
  }
7
7
 
8
- authenticate(req) {
8
+ authenticate() {
9
9
  const user = new cds.User.Privileged()
10
-
11
- // set _req for locale getter
12
- Object.defineProperty(user, '_req', { enumerable: false, value: req })
13
-
14
10
  this.success(user)
15
11
  }
16
12
  }
@@ -1,11 +1,10 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log()
3
3
 
4
- function decode(req, header, parsed) {
4
+ function decode(req, header, encoded) {
5
5
  if (!req.headers[header]) return
6
- return parsed
7
- ? JSON.parse(Buffer.from(req.headers[header], 'base64').toString('utf-8'))
8
- : Buffer.from(req.headers[header], 'base64').toString('utf-8')
6
+ if (encoded) return JSON.parse(Buffer.from(req.headers[header], 'base64').toString('utf-8'))
7
+ return req.headers[header]
9
8
  }
10
9
 
11
10
  class DwcStrategy {
@@ -15,17 +14,23 @@ class DwcStrategy {
15
14
 
16
15
  authenticate(req) {
17
16
  let tenant, usr, scopes, attr
17
+
18
18
  try {
19
19
  tenant = decode(req, 'dwc-tenant')
20
20
  usr = decode(req, 'dwc-user', true)
21
- scopes = decode(req, 'dwc-scopes', true) || []
21
+ scopes = decode(req, 'dwc-scopes') || ''
22
22
  attr = decode(req, 'dwc-xsuaa-attributes', true) || {}
23
23
  } catch (e) {
24
- LOG._warn && LOG.warn('Error while parsing headers for dwc-auth:', e)
24
+ LOG._debug && LOG.debug('Error while parsing HTTP headers for DWC authentication:', e)
25
25
  return this.fail()
26
26
  }
27
+
27
28
  if (!tenant || !usr) return this.fail()
28
29
 
30
+ // make scopes app-local
31
+ const xsappname = decode(req, 'dwc-xsuaa-xsappname') || ''
32
+ scopes = scopes.split(' ').map(s => (s.startsWith(xsappname + '.') ? s.replace(xsappname + '.', '') : s))
33
+
29
34
  const user = new cds.User({
30
35
  id: usr.logonName,
31
36
  tenant,
@@ -33,9 +38,6 @@ class DwcStrategy {
33
38
  attr: Object.assign(attr, usr)
34
39
  })
35
40
 
36
- // set _req for locale getter
37
- Object.defineProperty(user, '_req', { enumerable: false, value: req })
38
-
39
41
  this.success(user)
40
42
  }
41
43
  }
@@ -69,10 +69,6 @@ class MockStrategy {
69
69
  const tenant = user.tenant || (user.jwt && user.jwt.zid) || null
70
70
 
71
71
  user = new cds.User({ id: user.ID || id, _roles, attr, tenant })
72
-
73
- // set _req for locale getter
74
- Object.defineProperty(user, '_req', { enumerable: false, value: req })
75
-
76
72
  this.success(user)
77
73
  }
78
74
  }
@@ -1,21 +1,24 @@
1
+ const CLIENT = { client_credentials: 1, client_x509: 1 }
2
+
1
3
  const getUserId = (user, info) => {
2
4
  // fallback for grant_type=client_credentials (xssec v3)
3
5
  return user.id || (info && info.getClientId && info.getClientId())
4
6
  }
5
7
 
6
- const _addRolesFromGrantType = (roles, info) => {
8
+ const _addRolesFromGrantType = (roles, info, credentials) => {
7
9
  const grantType = info && (info.grantType || (info.getGrantType && info.getGrantType()))
8
10
  if (grantType) {
9
11
  // > not "weak"
10
12
  roles.push('authenticated-user')
11
- if (['client_credentials', 'client_x509'].includes(grantType)) {
13
+ if (grantType in CLIENT) {
12
14
  roles.push('system-user')
15
+ if (info.getClientId() === credentials.clientid) roles.push('internal-user')
13
16
  }
14
17
  }
15
18
  }
16
19
 
17
- const getRoles = (roles, info) => {
18
- _addRolesFromGrantType(roles, info)
20
+ const getRoles = (roles, info, credentials) => {
21
+ _addRolesFromGrantType(roles, info, credentials)
19
22
 
20
23
  // convert to object
21
24
  roles = Object.assign(...roles.map(ele => ({ [ele]: true })))
@@ -1,41 +1,34 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const _require = require('../../common/utils/require')
4
- const uaaUtils = require('./utils/uaa')
5
- const xssecUtils = require('./utils/xssec')
4
+ const xssecUtils = require('./xssecUtils')
6
5
 
7
6
  // use _require for a better error message
8
7
  const { JWTStrategy: JS } = _require('@sap/xssec')
9
8
 
10
9
  class XSUAAStrategy extends JS {
11
- constructor(uaa) {
12
- super(uaaUtils.getCredentials(uaa))
10
+ constructor(credentials) {
11
+ super(credentials)
12
+ this.credentials = credentials
13
13
  this.name = 'xsuaa'
14
14
  }
15
15
 
16
16
  authenticate(req, options) {
17
+ const credentials = this.credentials
18
+
17
19
  // monkey patch success
18
20
  const _success = this.success
19
21
  this.success = (user, info) => {
20
22
  // create cds.User
21
- user = new cds.User(
22
- Object.assign(
23
- { id: xssecUtils.getUserId(user, info) },
24
- {
25
- _roles: xssecUtils.getRoles(['any', 'identified-user'], info),
26
- attr: xssecUtils.getAttrForXSSEC(info),
27
- tenant: xssecUtils.getTenant(info) || null
28
- }
29
- )
30
- )
31
-
32
- // set _req for locale getter
33
- Object.defineProperty(user, '_req', { enumerable: false, value: req })
34
-
23
+ user = new cds.User({
24
+ id: xssecUtils.getUserId(user, info),
25
+ tenant: xssecUtils.getTenant(info) || null,
26
+ _roles: xssecUtils.getRoles(['any', 'identified-user'], info, credentials),
27
+ attr: xssecUtils.getAttrForXSSEC(info)
28
+ })
35
29
  // call "super.success"
36
30
  _success(user, info)
37
31
  }
38
-
39
32
  super.authenticate(req, options)
40
33
  }
41
34
  }
@@ -17,8 +17,29 @@ const getRequiresAsArray = definition => {
17
17
  return requires
18
18
  }
19
19
 
20
+ const isRestricted = srv => {
21
+ const envAuth = cds.env.requires.auth
22
+ if (envAuth && envAuth.restrict_all_services) return true
23
+ if (srv.definition['@requires']) return true
24
+
25
+ const entities = srv.entities
26
+ const entitiesKeys = Object.keys(entities)
27
+
28
+ return !!(
29
+ entitiesKeys.some(entity => entities[entity]['@requires'] || entities[entity]['@restrict']) ||
30
+ entitiesKeys.some(entity => {
31
+ const actions = entities[entity].actions
32
+ actions && Object.keys(actions).some(action => actions[action]['@requires'] || actions[action]['@restrict'])
33
+ }) ||
34
+ Object.keys(srv.operations).some(
35
+ operation => srv.operations[operation]['@requires'] || srv.operations[operation]['@restrict']
36
+ )
37
+ )
38
+ }
39
+
20
40
  module.exports = {
21
41
  UNAUTHORIZED,
22
42
  FORBIDDEN,
23
- getRequiresAsArray
43
+ getRequiresAsArray,
44
+ isRestricted
24
45
  }