@sap/cds 5.9.1 → 5.9.4

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 (45) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/lib/compile/etc/_localized.js +3 -2
  3. package/lib/compile/for/drafts.js +1 -1
  4. package/lib/connect/bindings.js +1 -1
  5. package/lib/connect/index.js +2 -3
  6. package/lib/env/requires.js +1 -1
  7. package/lib/index.js +2 -1
  8. package/lib/serve/Service-methods.js +28 -1
  9. package/lib/serve/adapters.js +6 -6
  10. package/lib/serve/factory.js +14 -9
  11. package/lib/serve/index.js +4 -3
  12. package/libx/_runtime/auth/index.js +16 -1
  13. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -1
  14. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
  15. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +11 -6
  16. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
  17. package/libx/_runtime/cds-services/services/Service.js +1 -1
  18. package/libx/_runtime/common/aspects/utils.js +8 -2
  19. package/libx/_runtime/common/composition/data.js +22 -13
  20. package/libx/_runtime/common/composition/delete.js +14 -12
  21. package/libx/_runtime/common/generic/auth/expand.js +1 -0
  22. package/libx/_runtime/common/generic/auth/restrict.js +3 -1
  23. package/libx/_runtime/{cds-services/services/utils → common/generic/auth}/restrictions.js +8 -1
  24. package/libx/_runtime/common/generic/input.js +1 -0
  25. package/libx/_runtime/common/generic/put.js +1 -0
  26. package/libx/_runtime/common/utils/cqn.js +5 -10
  27. package/libx/_runtime/common/utils/cqn2cqn4sql.js +37 -63
  28. package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
  29. package/libx/_runtime/common/utils/path.js +3 -3
  30. package/libx/_runtime/common/utils/require.js +2 -1
  31. package/libx/_runtime/common/utils/resolveView.js +3 -0
  32. package/libx/_runtime/common/utils/structured.js +6 -1
  33. package/libx/_runtime/db/Service.js +10 -0
  34. package/libx/_runtime/db/expand/expand-v2.js +13 -5
  35. package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
  36. package/libx/_runtime/db/utils/generateAliases.js +9 -0
  37. package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
  38. package/libx/_runtime/fiori/generic/read.js +83 -31
  39. package/libx/_runtime/fiori/generic/readOverDraft.js +31 -19
  40. package/libx/_runtime/fiori/utils/handler.js +3 -0
  41. package/libx/_runtime/fiori/utils/where.js +38 -25
  42. package/libx/_runtime/hana/execute.js +18 -1
  43. package/libx/_runtime/hana/search2cqn4sql.js +4 -1
  44. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,54 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## Version 5.9.4 - 2022-05-02
8
+
9
+ ### Fixed
10
+
11
+ - Error messages are improved if no `passport` module was found or if no `xsuaa` service binding is available
12
+ - Issue fixed for `srv.get()`. It was returning `TypeError` in plain REST usage for external services, e.g. `srv.get('/some/arbitrary/path/111')`
13
+ - Allow unrestricted services to run unauthenticated, removing the `Unable to require required package/file "passport"` error. Totally not recommended in production. Note that though this restores pre 5.9.0 behavior, this will come again starting in 6.0.
14
+ - Audit logging of sensitive data in a composition child if its parent is annotated with `@PersonalData.EntitySemantics: 'Other'` and has no data privacy annotations other than `@PersonalData.FieldSemantics: 'DataSubjectID'` annotating a corresponding composition, for example:
15
+ ```js
16
+ annotate Customers with @PersonalData : {
17
+ DataSubjectRole : 'Address',
18
+ EntitySemantics : 'Other'
19
+ } {
20
+ addresses @PersonalData.FieldSemantics: 'DataSubjectID';
21
+ }
22
+ annotate CustomerPostalAddress with @PersonalData : {
23
+ DataSubjectRole : 'Address',
24
+ EntitySemantics : 'DataSubject'
25
+ } {
26
+ ID @PersonalData.FieldSemantics : 'DataSubjectID';
27
+ street @PersonalData.IsPotentiallyPersonal;
28
+ town @PersonalData.IsPotentiallySensitive;
29
+ }
30
+ ```
31
+
32
+ ## Version 5.9.3 - 2022-04-25
33
+
34
+ ### Fixed
35
+
36
+ - Since 5.8.2 `req.target` for requests like `srv.put('/MyService.entity')` is defined, but `req.query` undefined (before `req.target` was also undefined). This was leading to accessing undefined, which has been fixed.
37
+ - Custom actions with names conflicting with methods from service base classes, e.g. `run()`, could lead to hard-to-detect errors. This is now detected and avoided with a warning.
38
+ - Typed methods for custom actions were erroneously applied to `cds.db` service, which led to server crashes, e.g. when the action was named `deploy()`.
39
+ - Invalid batch requests were further processed after error response was already sent to client, leading to an InternalServerError
40
+ - Full support of `SELECT` queries with operator expressions (`xpr`)
41
+
42
+
43
+ ## Version 5.9.2 - 2022-04-07
44
+
45
+ ### Fixed
46
+
47
+ - i18n translation for errors did not work correctly in some cases
48
+ - Normalization in custom `getRestrictions`
49
+ - Throw exception by `INSERT` into HANA queries if number of provided rows deviates from number of affected rows returned by hdb to prevent data losses
50
+ - Handler detection for extended services
51
+ - Speed-up in localization handling
52
+ - Draft: navigation via an association to many from a non-draft enabled entity to a draft-enabled entity
53
+ - Limited support of `SELECT` queries with operator expressions (`xpr`)
54
+
7
55
  ## Version 5.9.1 - 2022-03-31
8
56
 
9
57
  ### Fixed
@@ -97,8 +97,8 @@ function unfold_csn (m) { // NOSONAR
97
97
 
98
98
 
99
99
  const $localized = '$$localized', _is_localized = (d,_path={}) => {
100
- if (d.own($localized)) return true
101
- if (!d.elements || d.name.endsWith('.texts')) return false
100
+ if (typeof d.own($localized) === 'boolean') return d.own($localized)
101
+ if (!d.elements || d.name.endsWith('.texts')) return d.set($localized,false)
102
102
  // if (d.elements.texts && d.elements.texts.target === `${d.name}.texts`) return d.set($localized,true)
103
103
  for (let each in d.elements) {
104
104
  const e = d.elements [each]
@@ -106,6 +106,7 @@ const $localized = '$$localized', _is_localized = (d,_path={}) => {
106
106
  return d.set($localized,true)
107
107
  }
108
108
  }
109
+ return d.set($localized,false)
109
110
  }
110
111
 
111
112
 
@@ -4,6 +4,6 @@ module.exports = function cds_compile_for_drafts (csn,o) {
4
4
  const unfold = cds_compile_for_drafts.unfold || (
5
5
  cds_compile_for_drafts.unfold = require ('@sap/cds-compiler/lib/transform/draft/odata')
6
6
  )
7
- // csn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
7
+ csn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
8
8
  return unfold (csn,o||{})
9
9
  }
@@ -1,4 +1,4 @@
1
- const DEBUG = /\b(y|all|serve)\b/.test (process.env.DEBUG) && console.warn
1
+ const DEBUG = /\b(y|all|serve|bindings)\b/.test (process.env.DEBUG) && console.warn
2
2
  // || console.debug
3
3
 
4
4
  const cds = require ('..')
@@ -1,5 +1,4 @@
1
1
  const cds = require('..'), {one_model} = cds.env.features, LOG = cds.log('cds.connect')
2
- const factory = require('../serve/factory')
3
2
  const _pending = cds.services._pending || {} // used below to chain parallel connect.to(<same>)
4
3
 
5
4
  /**
@@ -22,7 +21,7 @@ const connect = module.exports = async function cds_connect (options) {
22
21
  * @returns { Promise<import('../serve/Service-api')> }
23
22
  */
24
23
  connect.to = async (datasource, options) => {
25
- let Service = factory, _done = x=>x
24
+ let Service = cds.service.factory, _done = x=>x
26
25
  if (typeof datasource === 'object') [options,datasource] = [datasource]
27
26
  else if (datasource) {
28
27
  if (datasource._is_service_class) [ Service, datasource ] = [ datasource, datasource.name ]
@@ -33,7 +32,7 @@ connect.to = async (datasource, options) => {
33
32
  // queue parallel requests to a single promise, to avoid creating multiple services
34
33
  _pending[datasource] = new Promise (r=>_done=r).finally(()=>{ delete _pending[datasource] })
35
34
  }
36
- const o = Service === factory ? options4 (datasource, options) : {}
35
+ const o = Service === cds.service.factory ? options4 (datasource, options) : {}
37
36
  const m = await model4 (o)
38
37
  // check if required service definition exists
39
38
  const required = cds.requires[datasource]
@@ -1,4 +1,4 @@
1
- const _runtime = '../../libx/_runtime'
1
+ const _runtime = '@sap/cds/libx/_runtime'
2
2
 
3
3
  exports = module.exports = {
4
4
 
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
12
12
  /** @type {{ [path:string] : Service }} */ paths: {},
13
13
  /** @type Service[] */ providers: [],
14
14
  factory: require ('./serve/factory'),
15
+ adapters: require ('./serve/adapters'),
15
16
  bindings: require ('./connect/bindings'),
16
17
  })}
17
18
  /** @type {import './req/context'} */
@@ -80,7 +81,7 @@ if (global.cds) Object.assign(module,{exports:global.cds}) ; else {
80
81
  ApplicationService: lazy => require('../libx/_runtime/cds-services/services/Service.js'),
81
82
  MessagingService: lazy => require('../libx/_runtime/messaging/service.js'),
82
83
  DatabaseService: lazy => require('../libx/_runtime/db/Service.js'),
83
- RemoteService: lazy => require('../libx/_runtime/rest/service.js'),
84
+ RemoteService: lazy => require('../libx/_runtime/remote/Service.js'),
84
85
  AuditLogService: lazy => require('../libx/_runtime/audit/Service.js'),
85
86
  odata: require('../libx/odata'),
86
87
 
@@ -1,5 +1,12 @@
1
+ const cds = require('..')
2
+ const LOG = cds.log('cds-app-service-methods')
3
+
1
4
 
2
5
  module.exports = (srv) => {
6
+ if (!( //> we only support that for app services
7
+ srv instanceof cds.ApplicationService ||
8
+ srv instanceof cds.RemoteService
9
+ )) return
3
10
  for (const each of srv.operations) {
4
11
  add_handler_for (srv, each)
5
12
  }
@@ -16,7 +23,23 @@ const add_handler_for = (srv, def) => {
16
23
  // Use existing methods as handler implementations
17
24
  const method = srv[event]
18
25
  if (method) {
19
- if (method._is_stub || method.name in srv.__proto__.__proto__) return
26
+ if (method._is_stub) return
27
+ const baseclass = (
28
+ srv.__proto__ === cds.ApplicationService.prototype ? srv.__proto__ :
29
+ srv.__proto__ === cds.RemoteService.prototype ? srv.__proto__ :
30
+ srv.__proto__.__proto__ // in case of class-based impls
31
+ )
32
+ if (method.name in baseclass) return LOG.warn(`WARNING: custom ${def.kind} '${event}()' conflicts with method in base class.
33
+
34
+ Cannot add typed method for custom ${def.kind} '${event}' to service impl of '${srv.name}',
35
+ as this would shadow equally named method in service base class '${baseclass.constructor.name}'.
36
+ Consider choosing a different name for your custom ${def.kind}.
37
+ Learn more at https://cap.cloud.sap/docs/guides/providing-services#actions-and-functions.
38
+ `)
39
+ LOG.debug (`
40
+ Using method ${event} from service class '${baseclass.constructor.name}'
41
+ as handler for ${def.kind} '${event}' in service '${srv.name}'
42
+ `)
20
43
  srv.on (event, function ({params,data}) {
21
44
  const args = []; if (def.parent) args.push (def.parent)
22
45
  for (let p in params) args.push(params[p])
@@ -26,6 +49,10 @@ const add_handler_for = (srv, def) => {
26
49
  }
27
50
 
28
51
  // Add stub methods to send request via typed API
52
+ LOG.debug (`
53
+ Adding typed method stub for calling custom ${def.kind} '${event}'
54
+ to service impl '${srv.name}'
55
+ `)
29
56
  const stub = srv[event] = function (...args) {
30
57
  const req = { event, data:{} }, $ = args[0]
31
58
  const target = this.entities [ $ && $.name ? $.name.match(/\w*$/)[0] : $ ]
@@ -1,11 +1,11 @@
1
1
  const lib = require('../../libx/_runtime')
2
2
  const registry = {
3
- rest: lib.to.old_rest,
4
- new_rest: lib.to.new_rest,
5
- odata: lib.to.odata_v4,
6
- odata_v2: lib.to.odata_v4,
7
- odata_v4: lib.to.odata_v4,
8
- fiori: lib.to.odata_v4,
3
+ get rest() { return lib.to.old_rest },
4
+ get new_rest() { return lib.to.new_rest },
5
+ get odata() { return lib.to.odata_v4 },
6
+ get odata_v2() { return lib.to.odata_v4 },
7
+ get odata_v4() { return lib.to.odata_v4 },
8
+ get fiori() { return lib.to.odata_v4 },
9
9
  }
10
10
 
11
11
 
@@ -1,5 +1,6 @@
1
- const cds = require('..')
2
- const { path, isfile } = cds.utils
1
+ const cds = require('..'), { path, isfile } = cds.utils
2
+ const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
3
+ const DEBUG = cds.debug('srv.factory'); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
3
4
 
4
5
  /** @typedef {import('./Service-api')} Service @type { (()=>Service) & (new()=>Service) } */
5
6
  const ServiceFactory = function (name, model, options) { //NOSONAR
@@ -8,6 +9,7 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
8
9
  const serve = !cds.requires[name] || o.mocked
9
10
  const defs = !model ? {[name]:{}} : model.definitions || cds.error (`Invalid argument for 'model': ${model}`)
10
11
  const def = !name || name === 'db' ? {} : defs[name] || {}
12
+ DEBUG && DEBUG ({ name, definition:def, options:o })
11
13
 
12
14
  let it /* eslint-disable no-cond-assign */
13
15
  if (it = o.with) return _use (it) // from cds.serve (<options>)
@@ -27,21 +29,24 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
27
29
 
28
30
  function _required() {
29
31
  const kind = o.kind = serve && def['@kind'] || o.kind || 'app-service'
30
- if (_required[kind]) return _required[kind]
32
+ if (_require[kind]) return _require[kind]
31
33
  const {impl} = cds.requires[kind] || cds.error (`No configuration found for 'cds.requires.${kind}'`)
32
- return _required[kind] = _require (impl || cds.error (`No 'impl' configured for 'cds.requires.${kind}'`))
34
+ DEBUG && DEBUG ('requires',{kind,impl})
35
+ return _require[kind] = _require (impl || cds.error (`No 'impl' configured for 'cds.requires.${kind}'`))
33
36
  }
34
37
  }
35
38
 
36
39
  const _require = (it,d) => {
37
- if (it.startsWith('@sap/cds/')) it = cds.home + it.slice(8) //> for local tests in @sap/cds dev
38
- else if (it.startsWith('./')) it = _relative (d, it.slice(2)) //> relative to <service>.cds
39
- else if (it.startsWith('//')) it = path.resolve (cds.root,it.slice(2)) //> relative to cds.root
40
- try { var resolved = require.resolve(it) } catch(e) {
40
+ DEBUG && d && DEBUG ('requires',{ service: d.name, source:_source(d), impl:it })
41
+ if (it.startsWith('@sap/cds/')) it = cds.home + it.slice(8) //> for local tests in @sap/cds dev
42
+ if (it.startsWith('./')) it = _relative (d,it.slice(2)) //> relative to <service>.cds
43
+ try { var resolved = require.resolve(it,{paths}) } catch(e) {
41
44
  try { resolved = require.resolve(it = path.resolve(cds.root,it)) } catch(e) { // for compatibility
45
+ DEBUG && DEBUG (`Failed loading service implementation from '${it}'`, { 'cds.root':cds.root, paths })
42
46
  throw cds.error(`Failed loading service implementation from '${it}'`)
43
47
  }
44
48
  }
49
+ DEBUG && DEBUG({resolved})
45
50
  return require(resolved)
46
51
  }
47
52
 
@@ -59,7 +64,7 @@ const sibling = (d) => {
59
64
  let found
60
65
  if (process.env.CDS_TYPESCRIPT === 'true') found = isfile(path.join(home, each, file + '.ts'))
61
66
  if (!found) found = isfile(path.join(home, each, file + '.js'))
62
- if (found) return found
67
+ if (found) return found //> equiv to '.'+found.slice(home.length)
63
68
  }
64
69
  }
65
70
 
@@ -1,6 +1,6 @@
1
- const { ProtocolAdapter } = require('./adapters')
2
- const { Service } = require('./factory')
3
1
  const cds = require ('..')
2
+ const { ProtocolAdapter } = cds.service.adapters
3
+ const { Service } = cds.service.factory
4
4
  const _ready = Symbol(), _pending = cds.services._pending || {}
5
5
 
6
6
  /** @param som - a service name or a model (name or csn) */
@@ -54,7 +54,8 @@ function cds_serve (som, _options) { // NOSONAR
54
54
  // Shortcut for directly passed service classes
55
55
  if (o.service && o.service._is_service_class) {
56
56
  const Service = o.service, d = { name: o.service.name }
57
- return all.push (_new (Service, d,csn,o))
57
+ const srv = _new (Service, d,csn,o)
58
+ return all.push (srv)
58
59
  }
59
60
 
60
61
  // Get relevant service definitions from model...
@@ -44,7 +44,10 @@ const _initializers = {
44
44
  // REVISIT: compat, remove with cds^6
45
45
  passport.use(new XSUAAStrategy(uaa.credentials))
46
46
  } else {
47
- throw Object.assign(new Error('No or malformed credentials for auth kind "xsuaa"'), { credentials })
47
+ throw Object.assign(
48
+ new Error('No or malformed credentials for auth kind "xsuaa". Make sure to bind the app to an "xsuaa" service'),
49
+ { credentials }
50
+ )
48
51
  }
49
52
  }
50
53
  }
@@ -178,6 +181,18 @@ module.exports = (srv, app, options) => {
178
181
  // > dummy or mock authentication (for development/testing)
179
182
  _mountMockAuth(srv, app, strategy, config)
180
183
  } else {
184
+ // if no restriction and no binding, don't mount passport middleware
185
+ if (!restricted && !config.credentials) {
186
+ if (!logged) {
187
+ const msg = `Service ${srv.name} is unrestricted`
188
+ if (process.env.NODE_ENV !== 'production') LOG._debug && LOG.debug(msg)
189
+ else LOG._info && LOG.info(`${msg}. This is not recommended in production.`)
190
+ }
191
+
192
+ // no auth wanted > return
193
+ return
194
+ }
195
+
181
196
  // > passport authentication
182
197
  _mountPassportAuth(srv, app, strategy, config)
183
198
  }
@@ -66,7 +66,12 @@ function _log(level, arg) {
66
66
 
67
67
  // reduce 4xx to warning
68
68
  if (isClientError(obj)) {
69
- if (!LOG._warn) return
69
+ if (!LOG._warn) {
70
+ // restore
71
+ obj.message = _message
72
+ if (_details) obj.details = _details
73
+ return
74
+ }
70
75
  level = 'warn'
71
76
  }
72
77
  }
@@ -89,7 +89,9 @@ class BatchRequestListBuilder {
89
89
  source
90
90
  .pipe(reader)
91
91
  .on('finish', () => {
92
- callback(null, this._requestInBatchList)
92
+ //Revisit: if statement needed in node v12 and v14, not in v16.
93
+ //Without it, finish callback reached in 12/14 after error handler was thrown
94
+ if (!source.res || !source.res.headersSent) callback(null, this._requestInBatchList)
93
95
  })
94
96
  .on('error', callback)
95
97
  }
@@ -1,5 +1,6 @@
1
1
  const cds = require('../../../../cds')
2
2
  const OData = require('../OData')
3
+ const DEBUG = cds.debug('extensibility')
3
4
 
4
5
  const { alias2ref } = require('../../../../common/utils/csn')
5
6
  const { BASE_TENANT } = require('../../../../common/utils/extensibilityUtils')
@@ -17,14 +18,18 @@ function createOdataService(service) {
17
18
  return odataService
18
19
  }
19
20
 
20
- const { Service } = require('../../../../../../lib/serve/factory')
21
- async function createNewService(name, csn, defaultOptions) {
22
- const options = Object.assign({}, defaultOptions)
23
- const service = new Service(name, csn, options)
24
- if (!service.path) service.path = cds.service.path4(service)
21
+ async function createNewService(name, model, options) {
22
+ const { constructor: Service, path } = cds.services[name]
23
+ const service = new Service(name, model, { ...options }) // cloning options to be safe
25
24
  if (service.init) await service.prepend(service.init)
26
25
  if (options.impl) await service.prepend(options.impl)
27
-
26
+ if (path) service.path = path
27
+ DEBUG &&
28
+ DEBUG('Created tenant-specific service:', service.name, '= new', Service.name, {
29
+ _handlers: {
30
+ on: service._handlers.on.map(h => ({ on: h.on, handler: () => {} }))
31
+ }
32
+ })
28
33
  return createOdataService(service)
29
34
  }
30
35
 
@@ -30,9 +30,7 @@ const _adaptSubSelectsDraft = select => {
30
30
  if (element.SELECT) {
31
31
  _adaptSubSelectsDraft(element)
32
32
  } else if (element.xpr) {
33
- for (const ele of element.xpr.filter(e => e.SELECT)) {
34
- _adaptSubSelectsDraft(ele)
35
- }
33
+ _adaptSubSelectsDraft({ SELECT: { from: {}, where: element.xpr } })
36
34
  }
37
35
  }
38
36
  }
@@ -8,7 +8,7 @@ const { postProcess } = require('../../common/utils/postProcessing')
8
8
  const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
9
9
 
10
10
  // for getRestrictions()
11
- const { getNormalizedRestrictions, getApplicableRestrictions } = require('./utils/restrictions')
11
+ const { getNormalizedRestrictions, getApplicableRestrictions } = require('../../common/generic/auth/restrictions.js')
12
12
  const WRITE_EVENTS = { CREATE: 1, NEW: 1, UPDATE: 1, PATCH: 1, DELETE: 1, CANCEL: 1, EDIT: 1 }
13
13
  const CRUD = Object.assign({ READ: 1 }, WRITE_EVENTS)
14
14
 
@@ -44,7 +44,9 @@ const hasPersonalData = entity => {
44
44
  for (const ele in entity.elements) {
45
45
  if (
46
46
  entity.elements[ele]['@PersonalData.IsPotentiallyPersonal'] ||
47
- entity.elements[ele]['@PersonalData.IsPotentiallySensitive']
47
+ entity.elements[ele]['@PersonalData.IsPotentiallySensitive'] ||
48
+ (entity.elements[ele]['@PersonalData.FieldSemantics'] &&
49
+ entity.elements[ele]['@PersonalData.FieldSemantics'] === 'DataSubjectID')
48
50
  ) {
49
51
  val = true
50
52
  break
@@ -58,7 +60,11 @@ const hasSensitiveData = entity => {
58
60
  let val
59
61
  if (entity['@PersonalData.DataSubjectRole'] && entity['@PersonalData.EntitySemantics']) {
60
62
  for (const ele in entity.elements) {
61
- if (entity.elements[ele]['@PersonalData.IsPotentiallySensitive']) {
63
+ if (
64
+ entity.elements[ele]['@PersonalData.IsPotentiallySensitive'] ||
65
+ (entity.elements[ele]['@PersonalData.FieldSemantics'] &&
66
+ entity.elements[ele]['@PersonalData.FieldSemantics'] === 'DataSubjectID')
67
+ ) {
62
68
  val = true
63
69
  break
64
70
  }
@@ -12,20 +12,13 @@ const { SELECT } = cds.ql
12
12
  * own utils
13
13
  */
14
14
 
15
- const _isSameEntity = (cqn, req) => {
16
- const where = cqn.UPDATE.where || []
17
- const persistentObj = Array.isArray(req._.partialPersistentState)
18
- ? req._.partialPersistentState[0]
19
- : req._.partialPersistentState
20
- if (!persistentObj) {
21
- // If no data was found we don't know if it is the same entity
22
- return false
23
- }
24
- const target = getDBTable(req.target)
25
- if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
26
- return false
27
- }
15
+ const _isSameEntityInWhere = (where, target, persistentObj) => {
28
16
  for (let i = 0; i < where.length; i++) {
17
+ if (where[i].xpr) {
18
+ const res = _isSameEntityInWhere(where[i].xpr, target, persistentObj)
19
+ if (!res) return res
20
+ continue
21
+ }
29
22
  if (!where[i] || !where[i].ref || !target.elements[where[i].ref]) {
30
23
  continue
31
24
  }
@@ -40,6 +33,22 @@ const _isSameEntity = (cqn, req) => {
40
33
  return true
41
34
  }
42
35
 
36
+ const _isSameEntity = (cqn, req) => {
37
+ const where = cqn.UPDATE.where || []
38
+ const persistentObj = Array.isArray(req._.partialPersistentState)
39
+ ? req._.partialPersistentState[0]
40
+ : req._.partialPersistentState
41
+ if (!persistentObj) {
42
+ // If no data was found we don't know if it is the same entity
43
+ return false
44
+ }
45
+ const target = getDBTable(req.target)
46
+ if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
47
+ return false
48
+ }
49
+ return _isSameEntityInWhere(where, target, persistentObj)
50
+ }
51
+
43
52
  const _getLinksOfCompTree = compositionTree => {
44
53
  const links = []
45
54
  for (const link of [...compositionTree.backLinks, ...compositionTree.customBackLinks]) {
@@ -216,6 +216,18 @@ const resolveNavigationTarget = (cqn, ref, model) => {
216
216
  return { target, elementName }
217
217
  }
218
218
 
219
+ const _getDataFromOncond = (onCond, parent) => {
220
+ return onCond.reduce((res, e) => {
221
+ if (e.xpr) {
222
+ return Object.assign(res, _getDataFromOncond(e.xpr, parent))
223
+ }
224
+ if (!e.ref || e.ref[0] !== '$$parent') return res
225
+ const fk = e.ref.slice(1).join('_')
226
+ if (!parent.keys[fk]) res[fk] = null
227
+ return res
228
+ }, {})
229
+ }
230
+
219
231
  // eslint-disable-next-line complexity
220
232
  const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
221
233
  const cqns = []
@@ -229,12 +241,7 @@ const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
229
241
  const element = parent.elements[elementName]
230
242
  if (element && element._isCompositionEffective && element.is2one) {
231
243
  const onCond = element && parent._relations[element.name].join('$$whatever', '$$parent')
232
- const data = onCond.reduce((res, e) => {
233
- if (!e.ref || e.ref[0] !== '$$parent') return res
234
- const fk = e.ref.slice(1).join('_')
235
- if (!parent.keys[fk]) res[fk] = null
236
- return res
237
- }, {})
244
+ const data = _getDataFromOncond(onCond, parent)
238
245
  cqn.data(data)
239
246
  cqn.__4delete = true
240
247
  if (Object.keys(data).length) cqns.push(cqn)
@@ -247,12 +254,7 @@ const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
247
254
  for (const element of comp2oneParents) {
248
255
  const parent = element.parent
249
256
  const onCond = parent._relations[element.name].join('$$child', '$$parent')
250
- const data = onCond.reduce((res, e) => {
251
- if (!e.ref || e.ref[0] !== '$$parent') return res
252
- const fk = e.ref.slice(1).join('_')
253
- if (!parent.keys[fk]) res[fk] = null
254
- return res
255
- }, {})
257
+ const data = _getDataFromOncond(onCond, parent)
256
258
  const columns = getColumns(parent, { _4db: true, onlyKeys: true }).map(c => ({ ref: ['$$parent', c.name] }))
257
259
  const as = query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
258
260
  const selectCQN = SELECT.from(`${parent.name} as $$parent`)
@@ -41,6 +41,7 @@ const _getRestrictedExpand = (columns, target, definitions) => {
41
41
  }
42
42
 
43
43
  function handler(req) {
44
+ if (!req.query) return
44
45
  const restricted = _getRestrictedExpand(
45
46
  req.query.SELECT && req.query.SELECT.columns,
46
47
  req.target,
@@ -2,6 +2,7 @@ const cds = require('../../../cds')
2
2
 
3
3
  const { reject, getRejectReason, resolveUserAttrs, getAuthRelevantEntity } = require('./utils')
4
4
  const { DRAFT_EVENTS, MOD_EVENTS } = require('./constants')
5
+ const { getNormalizedPlainRestrictions } = require('./restrictions')
5
6
 
6
7
  const { cqn2cqn4sql } = require('../../utils/cqn2cqn4sql')
7
8
 
@@ -250,7 +251,8 @@ async function handler(req) {
250
251
  // > no applicable restrictions -> 403
251
252
  reject(req, getRejectReason(req, '@restrict', definition))
252
253
  }
253
-
254
+ // normalize
255
+ restrictions = getNormalizedPlainRestrictions(restrictions, definition)
254
256
  // at least one if the user's roles grants unrestricted access => done
255
257
  if (restrictions.some(restrict => !restrict.where)) return
256
258
 
@@ -72,7 +72,14 @@ const getApplicableRestrictions = (restrictions, event, user) => {
72
72
  })
73
73
  }
74
74
 
75
+ const getNormalizedPlainRestrictions = (restrictions, definition) => {
76
+ const result = []
77
+ for (const restriction of restrictions) _addNormalizedRestrict(restriction, result, definition)
78
+ return result
79
+ }
80
+
75
81
  module.exports = {
76
82
  getNormalizedRestrictions,
77
- getApplicableRestrictions
83
+ getApplicableRestrictions,
84
+ getNormalizedPlainRestrictions
78
85
  }
@@ -184,6 +184,7 @@ const _getBoundActionBindingParameter = req => {
184
184
  }
185
185
 
186
186
  function _handler(req) {
187
+ if (!req.query) return // FIXME: the code below expects req.query to be defined
187
188
  if (!req.target) return
188
189
 
189
190
  const template = getTemplate('app-input', this, req.target, { pick: _pick })
@@ -57,6 +57,7 @@ const _pick = element => {
57
57
 
58
58
  function _handler(req) {
59
59
  if (req.method !== 'PUT') return
60
+ if (!req.query) return // FIXME: the code below expects req.query to be defined
60
61
  if (!req.target) return
61
62
 
62
63
  // not for payloads with stream properties
@@ -16,20 +16,16 @@ const getEntityNameFromUpdateCQN = cqn => {
16
16
  return (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
17
17
  }
18
18
 
19
- const addToWhere = (cqn, where) => {
20
- const partial = cqn.SELECT || cqn.UPDATE || cqn.DELETE
21
- if (!partial.where) partial.where = where
22
- else {
23
- partial.where.unshift('(')
24
- partial.where.push(')', 'and', '(', ...where, ')')
25
- }
26
- }
27
-
28
19
  // scope: simple wheres à la "[{ ref: ['foo'] }, '=', { val: 'bar' }, 'and', ... ]"
29
20
  function where2obj(where, target = null) {
30
21
  const data = {}
31
22
  for (let i = 0; i < where.length; i++) {
32
23
  const whereEl = where[i]
24
+
25
+ if (whereEl.xpr) {
26
+ where2obj(whereEl.xpr, target, data)
27
+ }
28
+
33
29
  const colName = whereEl.ref && whereEl.ref[whereEl.ref.length - 1]
34
30
  // optional validation if target is passed
35
31
  if (target) {
@@ -85,7 +81,6 @@ function isPathToDraft(path, model) {
85
81
  }
86
82
 
87
83
  module.exports = {
88
- addToWhere,
89
84
  getEntityNameFromDeleteCQN,
90
85
  getEntityNameFromUpdateCQN,
91
86
  where2obj,