@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.
- package/CHANGELOG.md +48 -0
- package/lib/compile/etc/_localized.js +3 -2
- package/lib/compile/for/drafts.js +1 -1
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +2 -3
- package/lib/env/requires.js +1 -1
- package/lib/index.js +2 -1
- package/lib/serve/Service-methods.js +28 -1
- package/lib/serve/adapters.js +6 -6
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +4 -3
- package/libx/_runtime/auth/index.js +16 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +11 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/common/aspects/utils.js +8 -2
- package/libx/_runtime/common/composition/data.js +22 -13
- package/libx/_runtime/common/composition/delete.js +14 -12
- package/libx/_runtime/common/generic/auth/expand.js +1 -0
- package/libx/_runtime/common/generic/auth/restrict.js +3 -1
- package/libx/_runtime/{cds-services/services/utils → common/generic/auth}/restrictions.js +8 -1
- package/libx/_runtime/common/generic/input.js +1 -0
- package/libx/_runtime/common/generic/put.js +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -10
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +37 -63
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
- package/libx/_runtime/common/utils/path.js +3 -3
- package/libx/_runtime/common/utils/require.js +2 -1
- package/libx/_runtime/common/utils/resolveView.js +3 -0
- package/libx/_runtime/common/utils/structured.js +6 -1
- package/libx/_runtime/db/Service.js +10 -0
- package/libx/_runtime/db/expand/expand-v2.js +13 -5
- package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
- package/libx/_runtime/db/utils/generateAliases.js +9 -0
- package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/fiori/generic/read.js +83 -31
- package/libx/_runtime/fiori/generic/readOverDraft.js +31 -19
- package/libx/_runtime/fiori/utils/handler.js +3 -0
- package/libx/_runtime/fiori/utils/where.js +38 -25
- package/libx/_runtime/hana/execute.js +18 -1
- package/libx/_runtime/hana/search2cqn4sql.js +4 -1
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
- 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
|
|
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
|
-
|
|
7
|
+
csn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
|
|
8
8
|
return unfold (csn,o||{})
|
|
9
9
|
}
|
package/lib/connect/bindings.js
CHANGED
package/lib/connect/index.js
CHANGED
|
@@ -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]
|
package/lib/env/requires.js
CHANGED
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/
|
|
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
|
|
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] : $ ]
|
package/lib/serve/adapters.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const lib = require('../../libx/_runtime')
|
|
2
2
|
const registry = {
|
|
3
|
-
rest
|
|
4
|
-
new_rest
|
|
5
|
-
odata
|
|
6
|
-
odata_v2
|
|
7
|
-
odata_v4
|
|
8
|
-
fiori
|
|
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
|
|
package/lib/serve/factory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const cds = require('..')
|
|
2
|
-
const
|
|
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 (
|
|
32
|
+
if (_require[kind]) return _require[kind]
|
|
31
33
|
const {impl} = cds.requires[kind] || cds.error (`No configuration found for 'cds.requires.${kind}'`)
|
|
32
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
package/lib/serve/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
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
|
-
|
|
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('
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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`)
|
|
@@ -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 })
|
|
@@ -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,
|