@sap/cds 6.1.2 → 6.2.1
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 +92 -8
- package/apis/cds.d.ts +18 -6
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/log.d.ts +23 -5
- package/apis/ql.d.ts +128 -61
- package/apis/services.d.ts +11 -0
- package/apis/test.d.ts +61 -0
- package/apis/utils.d.ts +15 -0
- package/app/fiori/preview.js +1 -0
- package/bin/build/buildTaskEngine.js +70 -22
- package/bin/build/buildTaskFactory.js +18 -11
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/buildTaskProviderFactory.js +3 -13
- package/bin/build/constants.js +0 -1
- package/bin/build/index.js +14 -6
- package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
- package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
- package/bin/build/provider/buildTaskProviderInternal.js +51 -39
- package/bin/build/provider/fiori/index.js +3 -3
- package/bin/build/provider/hana/2migration.js +1 -1
- package/bin/build/provider/hana/index.js +34 -27
- package/bin/build/provider/java/index.js +6 -7
- package/bin/build/provider/mtx/index.js +20 -18
- package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
- package/bin/build/provider/mtx-sidecar/index.js +13 -17
- package/bin/build/provider/nodejs/index.js +8 -7
- package/bin/build/util.js +22 -4
- package/bin/cds.js +8 -4
- package/bin/deploy/to-hana/cfUtil.js +53 -18
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +37 -30
- package/lib/auth/basic-auth.js +33 -0
- package/lib/auth/dummy-auth.js +7 -0
- package/lib/auth/ias-auth.js +2 -0
- package/lib/auth/index.js +31 -0
- package/lib/auth/jwt-auth.js +3 -0
- package/lib/auth/mocked-users.js +72 -0
- package/lib/auth/passport-basic.js +12 -0
- package/lib/auth/passport-digest.js +14 -0
- package/lib/auth/xsuaa-auth.js +3 -0
- package/lib/compile/cds-compile.js +3 -3
- package/lib/compile/to/cdl.js +5 -1
- package/lib/compile/to/edm.js +8 -0
- package/lib/compile/to/gql.js +1 -0
- package/lib/compile/to/json.js +30 -5
- package/lib/compile/to/sql.js +3 -1
- package/lib/core/index.js +5 -1
- package/lib/dbs/cds-deploy.js +36 -6
- package/lib/env/cds-env.js +15 -5
- package/lib/env/cds-requires.js +51 -58
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/cds-package.json +4 -0
- package/lib/env/schemas/cds-rc.json +63 -77
- package/lib/i18n/localize.js +16 -5
- package/lib/index.js +9 -4
- package/lib/log/cds-error.js +4 -6
- package/lib/log/cds-log.js +89 -53
- package/lib/log/service/index.js +1 -0
- package/lib/ql/CREATE.js +2 -5
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +1 -3
- package/lib/ql/INSERT.js +3 -3
- package/lib/ql/Query.js +10 -23
- package/lib/ql/SELECT.js +1 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/Whereable.js +7 -15
- package/lib/ql/cds-ql.js +9 -3
- package/lib/req/cds-context.js +11 -3
- package/lib/req/context.js +29 -23
- package/lib/req/locale.js +9 -5
- package/lib/req/request.js +1 -0
- package/lib/req/user.js +2 -1
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +21 -14
- package/lib/srv/middlewares/cds-context.js +29 -0
- package/lib/srv/middlewares/ctx-model.js +24 -0
- package/lib/srv/middlewares/errors.js +9 -0
- package/lib/srv/middlewares/index.js +22 -0
- package/lib/srv/middlewares/sap-statistics.js +13 -0
- package/lib/srv/middlewares/trace.js +102 -0
- package/lib/srv/protocols/_legacy.js +42 -0
- package/lib/srv/protocols/graphql.js +39 -0
- package/lib/srv/protocols/hcql.js +37 -0
- package/lib/srv/protocols/index.js +86 -0
- package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
- package/lib/srv/protocols/odata-v2.js +26 -0
- package/lib/srv/protocols/odata-v4.js +16 -0
- package/lib/srv/protocols/rest.js +13 -0
- package/lib/srv/srv-api.js +5 -0
- package/lib/srv/srv-models.js +4 -6
- package/lib/utils/axios.js +3 -2
- package/lib/utils/cds-test.js +27 -21
- package/lib/utils/cds-utils.js +19 -20
- package/lib/utils/tar.js +175 -0
- package/libx/_runtime/audit/generic/personal/utils.js +18 -7
- package/libx/_runtime/audit/utils/v2.js +1 -0
- package/libx/_runtime/auth/index.js +4 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
- package/libx/_runtime/cds-services/util/assert.js +7 -0
- package/libx/_runtime/common/aspects/relation.js +1 -1
- package/libx/_runtime/common/composition/data.js +61 -15
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +0 -1
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +44 -21
- package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
- package/libx/_runtime/common/generic/crud.js +1 -2
- package/libx/_runtime/common/generic/etag.js +4 -4
- package/libx/_runtime/common/generic/input.js +21 -6
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/put.js +7 -4
- package/libx/_runtime/common/generic/sorting.js +4 -4
- package/libx/_runtime/common/generic/temporal.js +3 -6
- package/libx/_runtime/common/i18n/messages.properties +0 -7
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/csn.js +0 -28
- package/libx/_runtime/common/utils/draft.js +8 -1
- package/libx/_runtime/common/utils/path.js +7 -1
- package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/template.js +2 -3
- package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
- package/libx/_runtime/db/generic/input.js +6 -6
- package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +40 -72
- package/libx/_runtime/fiori/generic/cancel.js +2 -2
- package/libx/_runtime/fiori/generic/delete.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +3 -5
- package/libx/_runtime/fiori/generic/patch.js +49 -43
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +27 -37
- package/libx/_runtime/fiori/utils/where.js +4 -2
- package/libx/_runtime/hana/Service.js +1 -3
- package/libx/_runtime/hana/conversion.js +3 -0
- package/libx/_runtime/hana/driver.js +33 -3
- package/libx/_runtime/hana/dynatrace.js +1 -0
- package/libx/_runtime/hana/search2Contains.js +12 -1
- package/libx/_runtime/hana/search2cqn4sql.js +10 -27
- package/libx/_runtime/hana/streaming.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/redis-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +2 -2
- package/libx/_runtime/remote/utils/client.js +35 -11
- package/libx/_runtime/remote/utils/data.js +7 -2
- package/libx/_runtime/sqlite/Service.js +18 -7
- package/libx/_runtime/sqlite/conversion.js +3 -0
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
- package/libx/_runtime/sqlite/localized.js +8 -8
- package/libx/odata/afterburner.js +39 -7
- package/libx/odata/cqn2odata.js +6 -3
- package/libx/odata/grammar.pegjs +66 -18
- package/libx/odata/index.js +3 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -0
- package/libx/rest/RestAdapter.js +62 -43
- package/libx/rest/middleware/input.js +2 -3
- package/libx/rest/middleware/parse.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +2 -2
- package/server.js +5 -4
- package/srv/mtx.cds +1 -1
- package/srv/mtx.js +4 -24
- package/lib/srv/adapters.js +0 -85
- package/lib/utils/resources/index.js +0 -48
- package/lib/utils/resources/tar.js +0 -49
- package/lib/utils/resources/utils.js +0 -11
- package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
- package/libx/_runtime/extensibility/activate.js +0 -69
- package/libx/_runtime/extensibility/add.js +0 -50
- package/libx/_runtime/extensibility/addExtension.js +0 -72
- package/libx/_runtime/extensibility/defaults.js +0 -34
- package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
- package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
- package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
- package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
- package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
- package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
- package/libx/_runtime/extensibility/linter.js +0 -32
- package/libx/_runtime/extensibility/push.js +0 -118
- package/libx/_runtime/extensibility/service.js +0 -38
- package/libx/_runtime/extensibility/token.js +0 -57
- package/libx/_runtime/extensibility/utils.js +0 -131
- package/libx/_runtime/extensibility/validation.js +0 -50
- package/libx/_runtime/extensibility/views.js +0 -12
- package/srv/extensibility-service.cds +0 -59
- package/srv/extensibility-service.js +0 -1
- package/srv/extensions.cds +0 -8
- package/srv/model-provider.cds +0 -61
- package/srv/model-provider.js +0 -143
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const cds = require('../../index'), { decodeURIComponent } = cds.utils
|
|
2
|
+
const LOG = cds.log('odata-v2')
|
|
3
|
+
const logger = function cap_legacy_req_logger (req,_,next) {
|
|
4
|
+
if (/\$batch$/.test(req.url)) {
|
|
5
|
+
const prefix = decodeURIComponent(req.originalUrl).replace('$batch','')
|
|
6
|
+
req.on ('dispatch', (req) => {
|
|
7
|
+
LOG && LOG (req.event, prefix+decodeURIComponent(req._path), req._query||'')
|
|
8
|
+
if (LOG._debug && req.query) LOG.debug (req.query)
|
|
9
|
+
})
|
|
10
|
+
} else {
|
|
11
|
+
LOG && LOG (req.method, decodeURIComponent(req.originalUrl), req.body||'')
|
|
12
|
+
}
|
|
13
|
+
next()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ODataV2Proxy = require('./odata-v2-proxy') // ('@sap/cds-odata-v2-adapter-proxy')
|
|
17
|
+
module.exports = function ODataV2Adapter (srv) {
|
|
18
|
+
const proxy = new ODataV2Proxy ({
|
|
19
|
+
sourcePath: srv.path,
|
|
20
|
+
targetPath: '/odata/v4',
|
|
21
|
+
target: 'auto', // to detect server url + port dynamically
|
|
22
|
+
logLevel: 'warn',
|
|
23
|
+
...srv.options, path:""
|
|
24
|
+
})
|
|
25
|
+
return [ logger, proxy ]
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const cds = require('../../index'), { User } = cds, { decodeURIComponent } = cds.utils
|
|
2
|
+
const libx = require('../../../libx/_runtime')
|
|
3
|
+
const LOG = cds.log('odata')
|
|
4
|
+
|
|
5
|
+
module.exports = function ODataAdapter (srv) { return [
|
|
6
|
+
(req, _, next) => {
|
|
7
|
+
let u = req.user
|
|
8
|
+
req.user = u instanceof User ? u : new User(u)
|
|
9
|
+
req.on ('dispatch', (eve) => {
|
|
10
|
+
LOG && LOG (req.method, req.baseUrl + decodeURIComponent(eve._path), eve._query||'')
|
|
11
|
+
if (LOG._debug && eve.query) LOG.debug (eve.query)
|
|
12
|
+
})
|
|
13
|
+
next()
|
|
14
|
+
},
|
|
15
|
+
libx.to.odata_v4 (srv)
|
|
16
|
+
]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const cds = require('../../index'), { User } = cds, { decodeURIComponent } = cds.utils
|
|
2
|
+
const libx = require('../../../libx/_runtime')
|
|
3
|
+
const LOG = cds.log('rest')
|
|
4
|
+
|
|
5
|
+
module.exports = function RestAdapter (srv) { return [
|
|
6
|
+
(req, _, next) => {
|
|
7
|
+
let u = req.user
|
|
8
|
+
req.user = u instanceof User ? u : new User(u)
|
|
9
|
+
LOG (req.method, decodeURIComponent(req.originalUrl), req.body||'')
|
|
10
|
+
next()
|
|
11
|
+
},
|
|
12
|
+
libx.to.rest (srv)
|
|
13
|
+
]}
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -15,6 +15,11 @@ class Service extends require('./srv-handlers') {
|
|
|
15
15
|
if (model) this.model = model
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Subclasses commonly override this to register event handlers
|
|
20
|
+
*/
|
|
21
|
+
init() {}
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Subclasses may override this to prepare the given model appropriately
|
|
20
25
|
*/
|
package/lib/srv/srv-models.js
CHANGED
|
@@ -53,7 +53,7 @@ class ExtendedModels {
|
|
|
53
53
|
|
|
54
54
|
const {cache} = ExtendedModels, key = cache.key4 (tenant, features)
|
|
55
55
|
const cached = cache.at(key); if (cached) return cached
|
|
56
|
-
if (key === ':') return cache.add (':', cds.compile.for.nodejs(cds.model))
|
|
56
|
+
else if (key === ':') return cache.add (':', cds.compile.for.nodejs(cds.model))
|
|
57
57
|
else return cache[key] = (async()=>{ // temporarily add promise to cache to avoid race conditions...
|
|
58
58
|
|
|
59
59
|
// If tenant doesn't have extensions check cache with tenant = undefined
|
|
@@ -99,7 +99,6 @@ class ExtendedModels {
|
|
|
99
99
|
if (model.then) return model //> promised model to avoid race conditions
|
|
100
100
|
|
|
101
101
|
const {_cached} = model, interval = ExtendedModels.checkInterval
|
|
102
|
-
if (!_cached.touched) return model
|
|
103
102
|
if (Date.now() - _cached.touched < interval) return model //> checked recently
|
|
104
103
|
|
|
105
104
|
else return this[key] = (async()=>{ // temporarily replace cache entry by promise to avoid race conditions...
|
|
@@ -132,7 +131,7 @@ class ExtendedModels {
|
|
|
132
131
|
*/
|
|
133
132
|
add (key, model, touched = Date.now()) {
|
|
134
133
|
if (model) {
|
|
135
|
-
if (!model._cached) Object.defineProperty (model,'_cached',{ value:{
|
|
134
|
+
if (!model._cached) Object.defineProperty (model,'_cached',{ value: { touched } })
|
|
136
135
|
return this[key] = model
|
|
137
136
|
}
|
|
138
137
|
}
|
|
@@ -192,8 +191,7 @@ if (!extensibility) {
|
|
|
192
191
|
// helper to get model for tenant/features
|
|
193
192
|
const _is_extended = old_mtx ? t => old_mtx.isExtended(t) : extensibility ? ()=> cds.db.exists('cds.xt.Extensions') : ()=> false
|
|
194
193
|
const _get_model4 = old_mtx ? async (tenant) => {
|
|
195
|
-
|
|
196
|
-
if (isExtended) return old_mtx.getCsn(tenant) .then (cds.compile.for.nodejs)
|
|
194
|
+
return old_mtx.getCsn(tenant) .then (cds.compile.for.nodejs)
|
|
197
195
|
} : (tenant, toggles) => {
|
|
198
196
|
const { 'cds.xt.ModelProviderService':mps } = cds.services
|
|
199
197
|
return mps.getCsn (tenant, toggles) .then (cds.compile.for.nodejs)
|
|
@@ -203,5 +201,5 @@ const _get_model4 = old_mtx ? async (tenant) => {
|
|
|
203
201
|
// ---------------------------------------------------------------------------
|
|
204
202
|
// Optimizations for single-tenancy modes
|
|
205
203
|
|
|
206
|
-
if (cds.requires.multitenancy && typeof global.it === 'undefined') ExtendedModels.cache.startSentinel()
|
|
204
|
+
if (cds.requires.multitenancy && typeof global.it === 'undefined') cds.once ('listening', ()=> ExtendedModels.cache.startSentinel())
|
|
207
205
|
// REVISIT: how to do ^that^ correctly with jest?
|
package/lib/utils/axios.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
class Axios {
|
|
2
2
|
|
|
3
3
|
get axios() {
|
|
4
|
-
|
|
4
|
+
// eslint-disable-next-line cds/no-missing-dependencies
|
|
5
|
+
return super.axios = require('axios').create ({
|
|
5
6
|
headers: { 'Content-Type': 'application/json' },
|
|
6
7
|
baseURL: this.url,
|
|
7
8
|
})
|
|
@@ -37,7 +38,7 @@ const _args = (args) => {
|
|
|
37
38
|
|
|
38
39
|
const _error = (e) => {
|
|
39
40
|
Error.captureStackTrace (e,_error) //> adds the stack trace from caller code
|
|
40
|
-
if (e.code
|
|
41
|
+
if (e.code && e.port === 80 /* default port */) throw Object.assign (e, {
|
|
41
42
|
message: e.message + '\nIt seems that the server was not started. Make sure to call \'cds.test(...)\' or \'cds.test.run(...)\'.',
|
|
42
43
|
stack: null // stack is just clutter here
|
|
43
44
|
})
|
package/lib/utils/cds-test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { is_mocha } = support_jest_and_mocha()
|
|
1
|
+
const { is_mocha, is_jest } = support_jest_and_mocha()
|
|
2
2
|
|
|
3
3
|
class Test extends require('./axios') {
|
|
4
4
|
|
|
@@ -64,9 +64,8 @@ class Test extends require('./axios') {
|
|
|
64
64
|
/**
|
|
65
65
|
* Switch on/off console log output.
|
|
66
66
|
*/
|
|
67
|
-
verbose(v) {
|
|
68
|
-
|
|
69
|
-
initLogging()
|
|
67
|
+
verbose (v) {
|
|
68
|
+
initLogging({ is_mocha, is_jest, verbose: v })
|
|
70
69
|
return this
|
|
71
70
|
}
|
|
72
71
|
|
|
@@ -79,6 +78,10 @@ class Test extends require('./axios') {
|
|
|
79
78
|
get cds() { return require('../index') }
|
|
80
79
|
get spy() { return spy }
|
|
81
80
|
|
|
81
|
+
then(r) {
|
|
82
|
+
const {cds} = this
|
|
83
|
+
cds.once('listening',r)
|
|
84
|
+
}
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
function support_jest_and_mocha() {
|
|
@@ -110,9 +113,8 @@ function support_jest_and_mocha() {
|
|
|
110
113
|
const repl = global.cds.repl
|
|
111
114
|
repl && repl.on('exit',fn)
|
|
112
115
|
}
|
|
113
|
-
process.env.CDS_TEST_VERBOSE = true
|
|
114
116
|
}
|
|
115
|
-
initLogging()
|
|
117
|
+
initLogging ({ is_jest, is_mocha })
|
|
116
118
|
return { is_jest, is_mocha }
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -122,24 +124,28 @@ function load_chai() {
|
|
|
122
124
|
Failed to load required package '${mod}'. Please add it thru:
|
|
123
125
|
npm add -D chai chai-as-promised chai-subset
|
|
124
126
|
`)}}
|
|
125
|
-
const chai = require('chai')
|
|
126
|
-
chai.use (require('chai-subset'))
|
|
127
|
-
chai.use (require('chai-as-promised'))
|
|
127
|
+
const chai = require('chai') // eslint-disable-line cds/no-missing-dependencies
|
|
128
|
+
chai.use (require('chai-subset')) // eslint-disable-line cds/no-missing-dependencies
|
|
129
|
+
chai.use (require('chai-as-promised')) // eslint-disable-line cds/no-missing-dependencies
|
|
128
130
|
return chai
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
function initLogging() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
function initLogging ({ verbose }={}) {
|
|
134
|
+
if (verbose && global.console.logs) return global.console = global.console.__proto__
|
|
135
|
+
if (process.env.CDS_TEST_SILENT) {
|
|
136
|
+
const console = global.console, logs = []
|
|
137
|
+
const {format} = require('util')
|
|
138
|
+
global.console = { __proto__: console, logs,
|
|
139
|
+
time: ()=>{}, timeEnd: (...args)=> logs.push(args),
|
|
140
|
+
debug: (...args)=> logs.push(args),
|
|
141
|
+
info: (...args)=> logs.push(args),
|
|
142
|
+
log: (...args)=> logs.push(args),
|
|
143
|
+
warn: (...args)=> logs.push(args),
|
|
144
|
+
trace: (...args)=> logs.push(args),
|
|
145
|
+
error: (...args)=> logs.push(args),
|
|
146
|
+
dump(){ for (let each of logs) process.stdout.write (format(...each)+'\n') },
|
|
147
|
+
}
|
|
148
|
+
afterAll (()=> global.console = console)
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const cwd = process.env._original_cwd || process.cwd()
|
|
2
|
-
const path = require ('path'), { dirname, extname, join, resolve, relative } = path
|
|
3
|
-
const fs = require('fs')
|
|
4
2
|
const cds = require('../index')
|
|
5
3
|
|
|
6
|
-
const
|
|
7
|
-
get inspect() { return
|
|
8
|
-
get uuid() { return
|
|
4
|
+
const ux = module.exports = exports = new class {
|
|
5
|
+
get inspect() { return super.inspect = require('util').inspect }
|
|
6
|
+
get uuid() { return super.uuid = require('@sap/cds-foss').uuid }
|
|
7
|
+
get tar() { return super.tar = require('./tar') }
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
exports.
|
|
12
|
-
exports.
|
|
10
|
+
const path = exports.path = require('path'), { dirname, extname, join, resolve, relative } = path
|
|
11
|
+
const fs = exports.fs = Object.assign (ux,require('fs')) //> for compatibility
|
|
12
|
+
|
|
13
|
+
exports.decodeURIComponent = s => { try { return decodeURIComponent(s) } catch { return s } }
|
|
14
|
+
exports.decodeURI = s => { try { return decodeURI(s) } catch { return s } }
|
|
13
15
|
|
|
14
16
|
exports.local = (file) => relative(cwd,file)
|
|
15
17
|
|
|
@@ -59,7 +61,7 @@ exports.write = function write (file, data, o) {
|
|
|
59
61
|
if (typeof data === 'object' && !Buffer.isBuffer(data))
|
|
60
62
|
data = JSON.stringify(data, null, ' '.repeat(o && o.spaces))
|
|
61
63
|
const f = resolve (cds.root,file)
|
|
62
|
-
return mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
|
|
64
|
+
return fs.mkdirp (dirname(f)).then (()=> fs.promises.writeFile (f,data,o))
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
exports.mkdirp = async function (...path) {
|
|
@@ -87,8 +89,8 @@ exports.copy = async function copy (x,y) {
|
|
|
87
89
|
const src = resolve (cds.root,x)
|
|
88
90
|
const dst = resolve (cds.root,y)
|
|
89
91
|
if (fs.promises.cp) return fs.promises.cp (src,dst,{recursive:true})
|
|
90
|
-
await mkdirp (dirname(dst))
|
|
91
|
-
if (isdir(src)) {
|
|
92
|
+
await fs.mkdirp (dirname(dst))
|
|
93
|
+
if (fs.isdir(src)) {
|
|
92
94
|
const entries = await fs.promises.readdir(src)
|
|
93
95
|
return Promise.all (entries.map (async each => {
|
|
94
96
|
const e = join (src,each)
|
|
@@ -133,14 +135,11 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
|
|
|
133
135
|
|
|
134
136
|
|
|
135
137
|
// internal utility to load a file through ESM or CommonJs. TODO find a better place.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
exports._import = id => require(id)
|
|
139
|
+
if (!global.test && !global.it) {
|
|
140
|
+
const { pathToFileURL } = require('url')
|
|
141
|
+
exports._import = id => {
|
|
142
|
+
if (extname(id) === '.ts') return require(id) // ts-node w/ ESM not working (cap/issues#11980)
|
|
143
|
+
else return import (pathToFileURL(id).href) // must use a file: URL, esp. on Windows for C:\... paths
|
|
144
|
+
}
|
|
141
145
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
/** @type <T>(o,p,v:T) => T */
|
|
145
|
-
const $set = (o,p,v) => { Object.defineProperty(o,p,{value:v}); return v }
|
|
146
|
-
const { mkdirp, isdir } = exports
|
package/lib/utils/tar.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const child_process = require('child_process')
|
|
2
|
+
const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
3
|
+
Error.captureStackTrace(spawn,spawn)
|
|
4
|
+
process.stderr.write(cmd +' ', args.join(' ') +' '+ spawn.stack.slice(7) + '\n')
|
|
5
|
+
return child_process.spawn(cmd, args, options)
|
|
6
|
+
} : child_process.spawn
|
|
7
|
+
|
|
8
|
+
const cds = require('../index'), { fs, path, mkdirp } = cds.utils
|
|
9
|
+
const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
10
|
+
|
|
11
|
+
// tar does not work properly on Windows (by npm/jest tests) w/o this change
|
|
12
|
+
const win = path => {
|
|
13
|
+
if (!path) return path
|
|
14
|
+
if (typeof path === 'string') return path.replace('C:', '//localhost/c$')
|
|
15
|
+
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a tar archive, to an in-memory Buffer, or piped to write stream or file.
|
|
20
|
+
* @example ```js
|
|
21
|
+
* const buffer = await tar.c('src/dir')
|
|
22
|
+
* await tar.c('src/dir') .to (fs.createWriteStream('t.tar'))
|
|
23
|
+
* await tar.c('src/dir') .to ('t.tar')
|
|
24
|
+
* await tar.c('src/dir','-f t.tar *')
|
|
25
|
+
* ```
|
|
26
|
+
* @param {string} dir - the directory to archive, used as `cwd` for the tar process
|
|
27
|
+
* @param {string} [args] - additional arguments passed to tar (default: `'*'`)
|
|
28
|
+
* @param {string[]} [more] - more of such additional arguments like `args`
|
|
29
|
+
* @example ```js
|
|
30
|
+
* // Passing additional arguments to tar
|
|
31
|
+
* tar.c('src/dir','-v *')
|
|
32
|
+
* tar.c('src/dir','-v -f t.tar *')
|
|
33
|
+
* tar.c('src/dir','-vf','t.tar','*')
|
|
34
|
+
* tar.c('src/dir','-vf','t.tar','file1','file2')
|
|
35
|
+
* ```
|
|
36
|
+
* @returns A `ChildProcess` as returned by [`child_process.spawn()`](
|
|
37
|
+
* https://nodejs.org/api/child_process.html#child_processspawncommand-args-options),
|
|
38
|
+
* augmented by two methods:
|
|
39
|
+
* - `.then()` collects the tar output into an in-memory `Buffer`
|
|
40
|
+
* - `.to()` is a convenient shortcut to pipe the output into a write stream
|
|
41
|
+
*/
|
|
42
|
+
exports.create = (dir='.', ...args) => {
|
|
43
|
+
|
|
44
|
+
if (typeof dir === 'string') dir = _resolve(dir)
|
|
45
|
+
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
46
|
+
if (Array.isArray(args[0])) args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
47
|
+
else args.push('.')
|
|
48
|
+
const c = process.platform === 'linux'
|
|
49
|
+
? spawn (`tar c -C ${dir}`, args, { shell:true })
|
|
50
|
+
: spawn (`tar cf - -C ${win(dir)}`, args, { shell:true })
|
|
51
|
+
|
|
52
|
+
return {__proto__:c, // returning a thenable + fluent ChildProcess...
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Turns the returned `ChildProcess` into a thenable, resolving to an
|
|
56
|
+
* in-memory Buffer holding the tar output, hence enabling this usage:
|
|
57
|
+
* @example const buffer = await tar.c('src/dir')
|
|
58
|
+
*/
|
|
59
|
+
then (r,e) {
|
|
60
|
+
const bb=[]; c.stdout.on('data', b => bb.push(b))
|
|
61
|
+
c.on('close', ()=>r(Buffer.concat(bb)))
|
|
62
|
+
c.on('error', e)
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Turns the returned `ChildProcess` into fluent API, allowing to pipe
|
|
67
|
+
* the tar's `stdout` into a write stream. If the argument is a string,
|
|
68
|
+
* it will be interpreted as a filename and a write stream opened on it.
|
|
69
|
+
* In that case, more filenames can be specified which are path.joined.
|
|
70
|
+
*/
|
|
71
|
+
to (out, ...etc) {
|
|
72
|
+
if (typeof out === 'string') {
|
|
73
|
+
// fs.mkdirSync(path.dirname(out),{recursive:true})
|
|
74
|
+
out = fs.createWriteStream (_resolve(out,...etc))
|
|
75
|
+
}
|
|
76
|
+
// Returning a thenable ChildProcess.stdout
|
|
77
|
+
return {__proto__: c.stdout.pipe (out),
|
|
78
|
+
then(r,e) {
|
|
79
|
+
out.on('close',r)
|
|
80
|
+
c.on('error',e)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extracts a tar archive, from an in-memory Buffer, or piped from a read stream or file.
|
|
89
|
+
* @example ```js
|
|
90
|
+
* await tar.x(buffer) .to ('dest')
|
|
91
|
+
* await tar.x(fs.createReadStream('t.tar')) .to ('dest')
|
|
92
|
+
* await tar.x('t.tar') .to ('dest')
|
|
93
|
+
* await tar.x('t.tar','-C dest')
|
|
94
|
+
* ```
|
|
95
|
+
* @param {String|Buffer|ReadableStream} [archive] - the tar file or content to extract
|
|
96
|
+
* @param {String[]} [args] - additional arguments passed to tar, .e.g. '-C dest'
|
|
97
|
+
*/
|
|
98
|
+
exports.extract = (archive, ...args) => ({
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Fluent API method to actually start the tar x command.
|
|
102
|
+
* @param {...string} dest - path names to a target dir → get `path.resolved` from `cds.root`.
|
|
103
|
+
* @returns A `ChildProcess` as returned by [`child_process.spawn()`](
|
|
104
|
+
* https://nodejs.org/api/child_process.html#child_processspawncommand-args-options),
|
|
105
|
+
* augmented by a method `.then()` to allow `await`ing finish.
|
|
106
|
+
*/
|
|
107
|
+
to (...dest) {
|
|
108
|
+
if (typeof dest === 'string') dest = _resolve(...dest)
|
|
109
|
+
const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive)
|
|
110
|
+
const x = spawn(`tar xf ${win(input)} -C ${win(dest)}`, args, { shell:true })
|
|
111
|
+
if (archive === '-') return x.stdin
|
|
112
|
+
if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive)
|
|
113
|
+
if (typeof archive !== 'string') archive.pipe (x.stdin)
|
|
114
|
+
if (args.includes('-v')) {
|
|
115
|
+
var list = '';
|
|
116
|
+
;(process.platform === 'linux' ? x.stdout : x.stderr) .on ('data', d => list+=d)
|
|
117
|
+
}
|
|
118
|
+
return {__proto__:x,
|
|
119
|
+
then(r,e) {
|
|
120
|
+
x.on('close',()=>r(list ? list.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')) : undefined))
|
|
121
|
+
x.on('error',e)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Shortcut to extract to current working directory, i.e. `cds.root`,
|
|
128
|
+
* or for this kind of usage:
|
|
129
|
+
* @example await tar.x(...,'-C _out')
|
|
130
|
+
* @returns `stdin` of the tar child process
|
|
131
|
+
*/
|
|
132
|
+
then (r,e) { return this.to('.') .then(r,e) },
|
|
133
|
+
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
exports.list = (archive, ...more) => {
|
|
138
|
+
const input = typeof archive !== 'string' ? '-' : archive === '-' ? archive : _resolve(archive)
|
|
139
|
+
const x = spawn(`tar tf`, [ input, ...more ], { shell:true })
|
|
140
|
+
let list = ''; x.stdout.on ('data', d => list+=d)
|
|
141
|
+
return {__proto__:x,
|
|
142
|
+
then(r,e) {
|
|
143
|
+
x.on('close',()=>r(list.split('\n').slice(0,-1)))
|
|
144
|
+
x.on('error',e)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Common tar command shortcuts
|
|
150
|
+
const tar = exports
|
|
151
|
+
exports.c = tar.create
|
|
152
|
+
exports.cz = (d,...args) => tar.c (d, ...args, '-z')
|
|
153
|
+
exports.cf = (t,d,...args) => tar.c (d, ...args, '-f',t)
|
|
154
|
+
exports.czf = (t,d,...args) => tar.c (d, ...args, '-z', '-f',t)
|
|
155
|
+
exports.czfd = (t,...args) => mkdirp(path.dirname(t)).then (()=> tar.czf (t,...args))
|
|
156
|
+
exports.x = tar.xf = tar.extract
|
|
157
|
+
exports.xz = tar.xzf = (a,...args) => tar.x (a, ...args, '-z')
|
|
158
|
+
exports.xv = tar.xvf = (a,...args) => tar.x (a, ...args, '-v')
|
|
159
|
+
exports.xvz = tar.xvzf = (a,...args) => tar.x (a, ...args, '-v', '-z')
|
|
160
|
+
exports.t = tar.tf = tar.list
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Shortcut for that kind of usage:
|
|
164
|
+
* @example fs.createReadStream('t.tar') .pipe (tar.x.to('dest/dir'))
|
|
165
|
+
* @returns `stdin` of the tar child process
|
|
166
|
+
*/
|
|
167
|
+
exports.extract.to = function (..._) { return this().to(..._) }
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------------
|
|
172
|
+
// Compatibility...
|
|
173
|
+
|
|
174
|
+
exports.packTarArchive = (files,d) => d ? tar.cz (d,files) : tar.cz (files)
|
|
175
|
+
exports.unpackTarArchive = (x,dir) => tar.xz(x).to(dir)
|
|
@@ -16,15 +16,26 @@ const getRootEntity = element => {
|
|
|
16
16
|
return entity
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const _hasPersonalData = e => {
|
|
20
|
+
if (!e['@PersonalData.DataSubjectRole']) return
|
|
21
|
+
if (!e['@PersonalData.EntitySemantics']) return
|
|
22
|
+
return !!Object.values(e.elements).some(
|
|
23
|
+
e => e['@PersonalData.IsPotentiallyPersonal'] || e['@PersonalData.IsPotentiallySensitive']
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const auditAnnotations = {
|
|
28
|
+
CREATE: '@AuditLog.Operation.Insert',
|
|
29
|
+
UPDATE: '@AuditLog.Operation.Update',
|
|
30
|
+
DELETE: '@AuditLog.Operation.Delete',
|
|
31
|
+
READ: '@AuditLog.Operation.Read'
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
const getPick = event => {
|
|
20
35
|
return (element, target) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DELETE: '@AuditLog.Operation.Delete',
|
|
25
|
-
READ: '@AuditLog.Operation.Read'
|
|
26
|
-
}[event]
|
|
27
|
-
if (!annotation || !target[annotation]) return
|
|
36
|
+
if (!_hasPersonalData(target)) return
|
|
37
|
+
if (!auditAnnotations[event] || !target[auditAnnotations[event]]) return
|
|
38
|
+
|
|
28
39
|
const categories = []
|
|
29
40
|
if (!element.isAssociation && element.key) categories.push('ObjectID')
|
|
30
41
|
if (
|
|
@@ -7,6 +7,7 @@ function connect(credentials) {
|
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
8
|
let auditLogging
|
|
9
9
|
try {
|
|
10
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
10
11
|
auditLogging = require('@sap/audit-logging')
|
|
11
12
|
} catch (e) {
|
|
12
13
|
// not able to require lib -> no audit logging ootb
|
|
@@ -167,6 +167,10 @@ module.exports = (srv, options = srv.options) => {
|
|
|
167
167
|
if (config.impl) {
|
|
168
168
|
// mount custom authentication middleware
|
|
169
169
|
_mountCustomAuth(srv, app, config)
|
|
170
|
+
} else if (config.kind === 'ias-auth') {
|
|
171
|
+
// ias-auth follows the new implementation pattern for auth middlewares
|
|
172
|
+
const iasAuth = require('./strategies/ias-auth')(config)
|
|
173
|
+
if (iasAuth) app.use(iasAuth)
|
|
170
174
|
} else {
|
|
171
175
|
// mount our authentication strategies (legacy style)
|
|
172
176
|
const strategy = _strategy4(config)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const cds = require('../../../../lib')
|
|
2
|
+
const _require = require('../../common/utils/require')
|
|
3
|
+
// _require for better error message
|
|
4
|
+
const express = _require('express')
|
|
5
|
+
const passport = _require('passport')
|
|
6
|
+
const { JWTStrategy } = _require('@sap/xssec')
|
|
7
|
+
const LOG = cds.log('auth')
|
|
8
|
+
|
|
9
|
+
const RESERVED_ATTRIBUTES = new Set([
|
|
10
|
+
'aud',
|
|
11
|
+
'azp',
|
|
12
|
+
'exp',
|
|
13
|
+
'ext_attr',
|
|
14
|
+
'iat',
|
|
15
|
+
'ias_iss',
|
|
16
|
+
'iss',
|
|
17
|
+
'jti',
|
|
18
|
+
'sub',
|
|
19
|
+
'user_uuid',
|
|
20
|
+
'zone_uuid',
|
|
21
|
+
'zid'
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
module.exports = function ias_auth(config) {
|
|
25
|
+
// warn if no credentials
|
|
26
|
+
if (!config.credentials) {
|
|
27
|
+
LOG._warn &&
|
|
28
|
+
LOG.warn(`
|
|
29
|
+
No IAS instance bound to application, but "${config.kind}" configured.
|
|
30
|
+
This is NOT recommended in production!
|
|
31
|
+
`)
|
|
32
|
+
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
passport.use('IAS', new JWTStrategy(config.credentials))
|
|
37
|
+
return express
|
|
38
|
+
.Router()
|
|
39
|
+
.use(passport.authenticate('IAS', { session: false, failWithError: true }))
|
|
40
|
+
.use((req, res, next) => {
|
|
41
|
+
// grant_type === client_credentials or x509
|
|
42
|
+
if (req.tokenInfo.getClientId() === req.tokenInfo.getSubject()) {
|
|
43
|
+
req.user = new cds.User({
|
|
44
|
+
id: 'system',
|
|
45
|
+
roles: ['system-user', 'authenticated-user'],
|
|
46
|
+
attr: {}
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
// add all unknown attributes to req.user.attr in order to keep public API small
|
|
50
|
+
const payload = req.tokenInfo.getPayload()
|
|
51
|
+
const attributes = Object.keys(payload)
|
|
52
|
+
.filter(k => !RESERVED_ATTRIBUTES.has(k))
|
|
53
|
+
.reduce((attrs, k) => {
|
|
54
|
+
attrs[k] = payload[k]
|
|
55
|
+
return attrs
|
|
56
|
+
}, {})
|
|
57
|
+
|
|
58
|
+
req.user = new cds.User({
|
|
59
|
+
id: req.user.id,
|
|
60
|
+
roles: ['authenticated-user'],
|
|
61
|
+
attr: attributes
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
req.tenant = req.tokenInfo.getZoneId()
|
|
66
|
+
next()
|
|
67
|
+
})
|
|
68
|
+
.use((err, req, res, next) => {
|
|
69
|
+
if (req.tokenInfo) {
|
|
70
|
+
LOG?.debug('error during token validation', req.tokenInfo.getErrorObject())
|
|
71
|
+
}
|
|
72
|
+
// REVISIT: reject request immediately as our other auth strategies do
|
|
73
|
+
// should we call next(err)? -> I don't think so; it's not an error, is it?
|
|
74
|
+
res.status(401).json({ code: '401', message: 'Unauthorized' }) // REVISIT: this is OData style?
|
|
75
|
+
})
|
|
76
|
+
}
|
|
@@ -115,14 +115,19 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
115
115
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
116
116
|
// REVISIT: if for compat reasons, remove once cds^5.1
|
|
117
117
|
if (srv._handlers._error.length) {
|
|
118
|
+
// REVISIT: move to error middleware
|
|
118
119
|
let ctx = cds.context
|
|
119
120
|
if (!ctx) {
|
|
121
|
+
// FIXME: this implementation only worked because Okra's BufferedWriter.on('finish')
|
|
122
|
+
// lost cds.context -> as we fixed that we don't get into this if branch anymore,
|
|
123
|
+
// but then the ctx in the else branch below isn't the ODataRequest anymore
|
|
120
124
|
// > error before req was dispatched
|
|
121
|
-
|
|
122
|
-
for (const each of srv._handlers._error) each.handler.call(srv, err,
|
|
125
|
+
const creq = new cds.Request({ req, res: req.res, user: req.user || new cds.User.Anonymous() })
|
|
126
|
+
for (const each of srv._handlers._error) each.handler.call(srv, err, creq)
|
|
123
127
|
} else {
|
|
124
128
|
// > error after req was dispatched, e.g., serialization error in okra
|
|
125
|
-
|
|
129
|
+
const creq = /* odataReq.req || */ new cds.Request({ req, res: req.res, user: ctx.user, tenant: ctx.tenant })
|
|
130
|
+
for (const each of srv._handlers._error) each.handler.call(srv, err, creq)
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
@@ -19,16 +19,27 @@ module.exports = srv => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// in case of $batch we need to challenge directly, as the header is not processed if in $batch response body
|
|
22
|
-
if (path.endsWith('/$batch')
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (restricted && path.endsWith('/$batch')) {
|
|
23
|
+
if (user?._challenges) {
|
|
24
|
+
res.set('WWW-Authenticate', user._challenges.join(';'))
|
|
25
|
+
return next(UNAUTHORIZED)
|
|
26
|
+
} else if (user._is_anonymous && req.login) {
|
|
27
|
+
res.set('WWW-Authenticate', `Basic realm="Users"`) // REVISIT: should be req.login(), and fiori app works with that but cds/tests/_runtime/auth/__tests__/strategies.test.js fails
|
|
28
|
+
return next(UNAUTHORIZED)
|
|
29
|
+
// return req.login()
|
|
30
|
+
}
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
// check @requires as soon as possible (DoS)
|
|
28
34
|
if (requires && !requires.some(r => user.is(r))) {
|
|
29
35
|
// > unauthorized or forbidden?
|
|
30
36
|
if (user._is_anonymous) {
|
|
31
|
-
if (user._challenges)
|
|
37
|
+
if (user._challenges) {
|
|
38
|
+
res.set('WWW-Authenticate', user._challenges.join(';'))
|
|
39
|
+
} else if (req.login) {
|
|
40
|
+
res.set('WWW-Authenticate', `Basic realm="Users"`) // REVISIT: should be req.login(), and fiori app works with that but cds/tests/_runtime/auth/__tests__/strategies.test.js fails
|
|
41
|
+
// return req.login()
|
|
42
|
+
}
|
|
32
43
|
// REVISIT: security log in else case?
|
|
33
44
|
return next(UNAUTHORIZED)
|
|
34
45
|
}
|
|
@@ -28,7 +28,7 @@ const _getExpandItem = (isAll, expandItems, name) => {
|
|
|
28
28
|
return expandItems.find(item => {
|
|
29
29
|
const pathSegments = item.getPathSegments()
|
|
30
30
|
if (pathSegments[pathSegments.length - 1].getKind() === 'COUNT') {
|
|
31
|
-
throw getError(501, '
|
|
31
|
+
throw getError(501, '"/$count" is not supported for expand operation')
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const navigation = pathSegments[pathSegments.length - 1].getNavigationProperty()
|