@sap/cds 7.9.4 → 8.0.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 +128 -3659
- package/_i18n/i18n_en_US_saptrc.properties +113 -0
- package/_i18n/i18n_zh_CN.properties +7 -4
- package/app/index.css +129 -0
- package/app/index.html +16 -64
- package/app/index.js +14 -9
- package/bin/args.js +34 -0
- package/bin/serve.js +18 -24
- package/bin/test.js +97 -0
- package/common.cds +5 -12
- package/eslint.config.mjs +133 -0
- package/lib/auth/basic-auth.js +16 -20
- package/lib/auth/dummy-auth.js +1 -1
- package/lib/auth/ias-auth.js +9 -41
- package/lib/auth/index.js +1 -14
- package/lib/auth/jwt-auth.js +10 -40
- package/lib/compile/cds-compile.js +1 -2
- package/lib/compile/cdsc.js +21 -26
- package/lib/compile/etc/_localized.js +1 -6
- package/lib/compile/etc/csv.js +1 -1
- package/lib/compile/etc/properties.js +1 -1
- package/lib/compile/for/java.js +1 -1
- package/lib/compile/for/lean_drafts.js +4 -6
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/compile/parse.js +4 -0
- package/lib/compile/resolve.js +4 -4
- package/lib/compile/to/edm-files.js +16 -23
- package/lib/compile/to/hana.js +27 -0
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +5 -1
- package/lib/compile/to/yaml.js +3 -3
- package/lib/dbs/cds-deploy.js +4 -2
- package/lib/env/cds-env.js +10 -14
- package/lib/env/cds-requires.js +30 -13
- package/lib/env/defaults.js +46 -16
- package/lib/env/plugins.js +1 -1
- package/lib/env/schemas/cds-rc.js +8 -4
- package/lib/env/schemas/index.js +7 -7
- package/lib/env/serviceBindings.js +1 -1
- package/lib/index.js +12 -10
- package/lib/lazy.js +1 -1
- package/lib/linked/classes.js +36 -8
- package/lib/linked/entities.js +2 -10
- package/lib/linked/models.js +2 -1
- package/lib/linked/validate.js +292 -0
- package/lib/log/cds-error.js +0 -6
- package/lib/log/cds-log.js +3 -3
- package/lib/log/format/json.js +1 -1
- package/lib/log/service/index.js +0 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/Query.js +2 -10
- package/lib/ql/SELECT.js +1 -1
- package/lib/ql/Whereable.js +3 -2
- package/lib/req/cds-context.js +14 -25
- package/lib/req/context.js +23 -25
- package/lib/req/request.js +1 -34
- package/lib/req/user.js +47 -35
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/cds-connect.js +4 -4
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/factory.js +1 -1
- package/lib/srv/middlewares/cds-context.js +11 -22
- package/lib/srv/middlewares/ctx-model.js +2 -3
- package/lib/srv/middlewares/errors.js +41 -8
- package/lib/srv/middlewares/index.js +3 -3
- package/lib/srv/middlewares/trace.js +0 -2
- package/lib/srv/protocols/hcql.js +15 -10
- package/lib/srv/protocols/http.js +44 -49
- package/lib/srv/protocols/index.js +1 -23
- package/lib/srv/protocols/odata-v4.js +12 -74
- package/lib/srv/protocols/rest.js +1 -13
- package/lib/srv/srv-api.js +0 -20
- package/lib/srv/srv-dispatch.js +3 -2
- package/lib/srv/srv-handlers.js +22 -11
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +3 -36
- package/lib/test/expect.js +343 -0
- package/lib/test/index.js +2 -0
- package/lib/test/reporter.js +176 -0
- package/lib/utils/axios.js +10 -9
- package/lib/utils/cds-test.js +85 -36
- package/lib/utils/cds-utils.js +54 -7
- package/lib/utils/check-version.js +0 -4
- package/lib/utils/colors.js +49 -0
- package/lib/utils/data.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
- 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/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
- 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/edm/AbstractEdmStructuredType.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
- package/libx/_runtime/cds-services/util/assert.js +1 -1
- package/libx/_runtime/cds.js +10 -3
- package/libx/_runtime/common/Service.js +12 -32
- package/libx/_runtime/common/aspects/any.js +1 -0
- package/libx/_runtime/common/code-ext/execute.js +1 -1
- package/libx/_runtime/common/code-ext/worker.js +0 -1
- package/libx/_runtime/common/composition/data.js +0 -1
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/tree.js +0 -1
- package/libx/_runtime/common/composition/update.js +3 -3
- package/libx/_runtime/common/error/frontend.js +21 -12
- package/libx/_runtime/common/error/log.js +36 -0
- package/libx/_runtime/common/error/utils.js +2 -5
- package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
- package/libx/_runtime/common/generic/auth/restrict.js +23 -42
- package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
- package/libx/_runtime/common/generic/auth/utils.js +91 -88
- package/libx/_runtime/common/generic/crud.js +6 -5
- package/libx/_runtime/common/generic/etag.js +7 -12
- package/libx/_runtime/common/generic/input.js +70 -68
- package/libx/_runtime/common/generic/paging.js +1 -0
- package/libx/_runtime/common/generic/sorting.js +1 -0
- package/libx/_runtime/common/generic/temporal.js +8 -2
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +3 -1
- package/libx/_runtime/common/utils/binary.js +8 -2
- package/libx/_runtime/common/utils/compareJson.js +5 -1
- package/libx/_runtime/common/utils/copy.js +6 -11
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
- package/libx/_runtime/common/utils/differ.js +3 -6
- package/libx/_runtime/common/utils/keys.js +77 -18
- package/libx/_runtime/common/utils/postProcess.js +12 -15
- package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/restrictions.js +45 -17
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
- package/libx/_runtime/common/utils/stream.js +3 -16
- package/libx/_runtime/common/utils/streamProp.js +8 -18
- package/libx/_runtime/common/utils/structured.js +1 -1
- package/libx/_runtime/common/utils/ucsn.js +0 -2
- package/libx/_runtime/db/Service.js +0 -72
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
- package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
- package/libx/_runtime/db/generic/input.js +3 -8
- package/libx/_runtime/db/generic/rewrite.js +1 -0
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +2 -6
- package/libx/_runtime/fiori/lean-draft.js +138 -56
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/driver.js +1 -1
- package/libx/_runtime/hana/dynatrace.js +1 -2
- package/libx/_runtime/hana/pool.js +11 -21
- package/libx/_runtime/hana/streaming.js +0 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
- package/libx/_runtime/messaging/event-broker.js +54 -27
- package/libx/_runtime/messaging/file-based.js +3 -3
- package/libx/_runtime/messaging/http-utils/token.js +1 -1
- package/libx/_runtime/messaging/kafka.js +2 -2
- package/libx/_runtime/messaging/redis-messaging.js +0 -1
- package/libx/_runtime/remote/Service.js +25 -25
- package/libx/_runtime/remote/utils/client.js +4 -5
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
- package/libx/_runtime/remote/utils/data.js +0 -1
- package/libx/_runtime/sqlite/Service.js +1 -2
- package/libx/_runtime/ucl/Service.js +37 -78
- package/libx/common/assert/index.js +22 -21
- package/libx/common/assert/type-relaxed.js +39 -0
- package/libx/common/assert/utils.js +3 -2
- package/libx/common/assert/validation.js +3 -8
- package/libx/common/utils/index.js +5 -0
- package/libx/common/utils/path.js +51 -0
- package/libx/odata/ODataAdapter.js +126 -0
- package/libx/odata/index.js +15 -2
- package/libx/odata/middleware/batch.js +320 -84
- package/libx/odata/middleware/body-parser.js +33 -0
- package/libx/odata/middleware/create.js +44 -59
- package/libx/odata/middleware/delete.js +23 -12
- package/libx/odata/middleware/error.js +30 -6
- package/libx/odata/middleware/metadata.js +38 -26
- package/libx/odata/middleware/operation.js +93 -69
- package/libx/odata/middleware/parse.js +6 -8
- package/libx/odata/middleware/read.js +117 -93
- package/libx/odata/middleware/service-document.js +22 -19
- package/libx/odata/middleware/stream.js +54 -56
- package/libx/odata/middleware/update.js +79 -87
- package/libx/odata/parse/afterburner.js +191 -175
- package/libx/odata/parse/cqn2odata.js +5 -5
- package/libx/odata/parse/grammar.peggy +27 -20
- package/libx/odata/parse/multipartToJson.js +17 -9
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +14 -6
- package/libx/odata/utils/index.js +84 -12
- package/libx/odata/utils/metadata.js +161 -0
- package/libx/odata/utils/postProcess.js +89 -0
- package/libx/odata/utils/readAfterWrite.js +134 -17
- package/libx/odata/utils/result.js +36 -142
- package/libx/outbox/index.js +4 -3
- package/libx/rest/RestAdapter.js +115 -182
- package/libx/rest/middleware/create.js +28 -24
- package/libx/rest/middleware/delete.js +7 -10
- package/libx/rest/middleware/error.js +26 -16
- package/libx/rest/middleware/operation.js +48 -41
- package/libx/rest/middleware/parse.js +128 -126
- package/libx/rest/middleware/read.js +20 -27
- package/libx/rest/middleware/update.js +26 -31
- package/package.json +17 -8
- package/server.js +4 -2
- package/apis/cds.d.ts +0 -3
- package/apis/core.d.ts +0 -21
- package/apis/cqn.d.ts +0 -18
- package/apis/csn.d.ts +0 -21
- package/apis/events.d.ts +0 -18
- package/apis/internal/inference.d.ts +0 -18
- package/apis/linked.d.ts +0 -18
- package/apis/log.d.ts +0 -20
- package/apis/models.d.ts +0 -18
- package/apis/ql.d.ts +0 -18
- package/apis/reflect.d.ts +0 -32
- package/apis/server.d.ts +0 -18
- package/apis/services.d.ts +0 -22
- package/bin/cds-serve.js +0 -56
- package/lib/compile/to/gql.js +0 -15
- package/lib/srv/protocols/_legacy.js +0 -44
- package/lib/utils/jest.js +0 -43
- package/libx/_runtime/auth/index.js +0 -193
- package/libx/_runtime/auth/strategies/JWT.js +0 -37
- package/libx/_runtime/auth/strategies/basic.js +0 -20
- package/libx/_runtime/auth/strategies/dummy.js +0 -14
- package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
- package/libx/_runtime/auth/strategies/mock.js +0 -77
- package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
- package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
- package/libx/_runtime/common/perf/index.js +0 -19
- package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
- package/libx/_runtime/fiori/draft.js +0 -2
- package/libx/_runtime/fiori/generic/activate.js +0 -190
- package/libx/_runtime/fiori/generic/before.js +0 -201
- package/libx/_runtime/fiori/generic/cancel.js +0 -19
- package/libx/_runtime/fiori/generic/delete.js +0 -21
- package/libx/_runtime/fiori/generic/edit.js +0 -157
- package/libx/_runtime/fiori/generic/index.js +0 -25
- package/libx/_runtime/fiori/generic/new.js +0 -82
- package/libx/_runtime/fiori/generic/patch.js +0 -101
- package/libx/_runtime/fiori/generic/prepare.js +0 -57
- package/libx/_runtime/fiori/generic/read.js +0 -1340
- package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
- package/libx/_runtime/fiori/utils/csn.js +0 -13
- package/libx/_runtime/fiori/utils/delete.js +0 -114
- package/libx/_runtime/fiori/utils/handler.js +0 -264
- package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
- package/libx/_runtime/fiori/utils/req.js +0 -23
- package/libx/_runtime/fiori/utils/stream.js +0 -36
- package/libx/_runtime/fiori/utils/where.js +0 -254
- package/libx/_runtime/index.js +0 -22
- package/libx/odata/utils/handler.js +0 -120
- package/libx/odata/utils/metaInfo.js +0 -410
- package/libx/odata/utils/path.js +0 -75
- package/libx/rest/RestRequest.js +0 -32
- package/libx/rest/index.js +0 -3
- package/libx/rest/readme.md +0 -1
- /package/libx/common/assert/{type.js → type-strict.js} +0 -0
|
@@ -6,7 +6,7 @@ const { getDeepInsertCQNs } = require('./insert')
|
|
|
6
6
|
const { getDeepDeleteCQNs } = require('./delete')
|
|
7
7
|
const ctUtils = require('./utils')
|
|
8
8
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
9
|
-
const {
|
|
9
|
+
const { deepCopy } = require('../utils/copy')
|
|
10
10
|
const getError = require('../../common/error')
|
|
11
11
|
const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
|
|
12
12
|
|
|
@@ -310,8 +310,8 @@ const getDeepUpdateCQNs = async (model, req, selectData) => {
|
|
|
310
310
|
const from = getEntityNameFromUpdateCQN(query)
|
|
311
311
|
const entityName = ensureNoDraftsSuffix(from)
|
|
312
312
|
const draft = entityName !== from
|
|
313
|
-
const data = query.UPDATE.data ?
|
|
314
|
-
const withObj = query.UPDATE.with ?
|
|
313
|
+
const data = query.UPDATE.data ? deepCopy(query.UPDATE.data) : {}
|
|
314
|
+
const withObj = query.UPDATE.with ? deepCopy(query.UPDATE.with) : {}
|
|
315
315
|
const entity = model.definitions[entityName]
|
|
316
316
|
const entry = Object.assign({}, data, withObj, ctUtils.key(entity, selectData[0]))
|
|
317
317
|
const compositionTree = getCompositionTree({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// REVISIT: Requires thorough cleanup and refactoring, or elimination
|
|
1
2
|
/*
|
|
2
3
|
* OData spec:
|
|
3
4
|
* This object MUST contain name/value pairs with the names code and message,
|
|
@@ -24,17 +25,15 @@ const ADDITIONAL_MSG_PROPERTIES = Object.keys(ADDITIONAL_MSG_PROPERTIES_MAP)
|
|
|
24
25
|
|
|
25
26
|
const _getFiltered = err => {
|
|
26
27
|
const error = {}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
|
|
28
|
+
for (let k in err) {
|
|
29
|
+
// IMPORTANT: do not use Object.keys(err) here as that ignores enumerable properties from __proto__!
|
|
30
|
+
if (k in ALLOWED_PROPERTIES_MAP || k.startsWith('@')) {
|
|
31
|
+
error[k] = err[k]
|
|
32
|
+
} else if (k === 'numericSeverity') {
|
|
33
|
+
error['@Common.numericSeverity'] = err[k]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if ('message' in err) error.message = err.message
|
|
38
37
|
return error
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -173,8 +172,18 @@ const isClientError = e => {
|
|
|
173
172
|
return numericCode >= 400 && numericCode < 500
|
|
174
173
|
}
|
|
175
174
|
|
|
175
|
+
const unwrapMultipleErrors = error => {
|
|
176
|
+
// According to the Fiori Elements Failed Message specification, the format must be:
|
|
177
|
+
// Root level: First error, Details: Other errors
|
|
178
|
+
const [firstDetail, ...restDetails] = error.details
|
|
179
|
+
Object.assign(error, firstDetail)
|
|
180
|
+
if (restDetails.length) error.details = restDetails
|
|
181
|
+
else delete error.details
|
|
182
|
+
}
|
|
183
|
+
|
|
176
184
|
module.exports = {
|
|
177
185
|
normalizeError,
|
|
178
186
|
getSapMessages,
|
|
179
|
-
isClientError
|
|
187
|
+
isClientError,
|
|
188
|
+
unwrapMultipleErrors
|
|
180
189
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// REVISIT: I think we can remove this file. Log output doesn't need localization.
|
|
2
|
+
const cds = require('../../cds')
|
|
3
|
+
const LOG = cds.log()
|
|
4
|
+
|
|
5
|
+
let _i18n
|
|
6
|
+
const i18n = (...args) => {
|
|
7
|
+
if (!_i18n) _i18n = require('../i18n')
|
|
8
|
+
return _i18n(...args)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { isClientError } = require('./frontend')
|
|
12
|
+
|
|
13
|
+
module.exports = err => {
|
|
14
|
+
// REVISIT: how does level behave compared to _log in (legacy) odata adapter?
|
|
15
|
+
const level = isClientError(err) ? 'warn' : 'error'
|
|
16
|
+
if ((level === 'warn' && !LOG._warn) || (level === 'error' && !LOG._error)) return
|
|
17
|
+
|
|
18
|
+
// replace messages in toLog with developer texts (i.e., undefined locale)
|
|
19
|
+
const _message = err.message
|
|
20
|
+
const _details = err.details
|
|
21
|
+
err.message = i18n(err.message || err.code, undefined, err.args) || err.message
|
|
22
|
+
if (err.details) {
|
|
23
|
+
const details = []
|
|
24
|
+
for (const d of err.details) {
|
|
25
|
+
details.push(Object.assign({}, d, { message: i18n(d.message || d.code, undefined, d.args) || d.message }))
|
|
26
|
+
}
|
|
27
|
+
err.details = details
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// log it
|
|
31
|
+
LOG[level](err)
|
|
32
|
+
|
|
33
|
+
// restore
|
|
34
|
+
err.message = _message
|
|
35
|
+
if (_details) err.details = _details
|
|
36
|
+
}
|
|
@@ -12,11 +12,8 @@ const i18n = (...args) => {
|
|
|
12
12
|
* @returns localized error message
|
|
13
13
|
*/
|
|
14
14
|
function getErrorMessage(error, locale) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
error.message ||
|
|
18
|
-
`${error.code || error.status || error.statusCode}`
|
|
19
|
-
)
|
|
15
|
+
const txt = i18n(error.message || error.code || error.status || error.statusCode, locale, error.args)
|
|
16
|
+
return txt || error.message || String(error.code || error.status || error.statusCode)
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
module.exports = {
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
const cds = require('../../../../../lib')
|
|
2
2
|
|
|
3
3
|
function noah_handler(req) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
if (!req.subject) return
|
|
5
|
+
const root = this.model.definitions[req.subject.ref[0].id || req.subject.ref[0]]
|
|
6
|
+
if (!root) return
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* For auto-exposed Compositions all direct CRUD requests are rejected in non-draft case.
|
|
10
|
+
* For other auto-exposed entities in non-draft case only C_UD are rejected. Direct READ is allowed.
|
|
11
|
+
* Draft case is an exception. Direct requests are allowed.
|
|
12
|
+
*/
|
|
13
|
+
if (!root._isDraftEnabled && root['@cds.autoexposed']) {
|
|
14
|
+
if (root['@cds.autoexpose']) {
|
|
15
|
+
if (req.event === 'READ') return //> allow read for value help requests
|
|
16
|
+
req.reject(405, 'ENTITY_IS_AUTOEXPOSE_READONLY', [root.name])
|
|
17
|
+
}
|
|
18
|
+
req.reject(405, 'ENTITY_IS_AUTOEXPOSED', [root.name])
|
|
10
19
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (target['@cds.autoexpose']) {
|
|
16
|
-
if (req.event === 'READ') return //> allow read for value help requests
|
|
17
|
-
req.reject(405, 'ENTITY_IS_AUTOEXPOSE_READONLY', [target.name])
|
|
20
|
+
const isAutoexposed = _isAutoexposed(req.target)
|
|
21
|
+
if (isAutoexposed && req.event !== 'READ') {
|
|
22
|
+
req.reject(405, 'ENTITY_IS_AUTOEXPOSE_READONLY', [req.target.name])
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
// here, we are autoexposed (i.e., a composition)
|
|
21
|
-
// -> reject all direct changes (i.e., only via navigation is allowed)
|
|
22
|
-
if (req.subject?.ref.length === 1) req.reject(405, 'ENTITY_IS_AUTOEXPOSED', [target.name])
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const _isAutoexposed = entity => {
|
|
@@ -2,7 +2,7 @@ const cds = require('../../../cds')
|
|
|
2
2
|
|
|
3
3
|
const { rewriteExpandAsterisk } = require('../../utils/rewriteAsterisks')
|
|
4
4
|
|
|
5
|
-
const { ensureNoDraftsSuffix } = require('
|
|
5
|
+
const { ensureNoDraftsSuffix } = require('../../utils/draft')
|
|
6
6
|
|
|
7
7
|
const _getTarget = (ref, target, definitions) => {
|
|
8
8
|
if (cds.env.effective.odata.proxies) {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
const cds = require('../../../cds')
|
|
2
1
|
const { getAuthRelevantEntity } = require('./utils')
|
|
3
2
|
const { WRITE_EVENTS } = require('./constants')
|
|
4
3
|
|
|
5
4
|
function handler(req) {
|
|
6
5
|
// @read-only
|
|
7
6
|
let entity = getAuthRelevantEntity(req, this.model, ['@readonly'])
|
|
8
|
-
|
|
7
|
+
entity = entity?.actives || entity
|
|
9
8
|
|
|
10
9
|
if (!entity || !entity['@readonly']) return
|
|
11
10
|
if (entity['@readonly'] && req.event in WRITE_EVENTS) req.reject(405, 'ENTITY_IS_READ_ONLY', [entity.name])
|
|
@@ -6,32 +6,32 @@ const { getNormalizedPlainRestrictions } = require('./restrictions')
|
|
|
6
6
|
|
|
7
7
|
const { cqn2cqn4sql } = require('../../utils/cqn2cqn4sql')
|
|
8
8
|
|
|
9
|
-
const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../../fiori/utils/where')
|
|
10
|
-
|
|
11
9
|
const _getResolvedApplicables = (applicables, req) => {
|
|
12
10
|
const resolvedApplicables = []
|
|
13
11
|
|
|
14
12
|
// REVISIT: the static portion of "mixed wheres" could already grant access -> optimization potential
|
|
15
13
|
for (const restrict of applicables) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
let resolved
|
|
15
|
+
if (restrict.where) {
|
|
16
|
+
let xpr
|
|
17
|
+
if (typeof restrict.where === 'string') {
|
|
18
|
+
xpr = cds.parse.expr(restrict.where).xpr
|
|
19
|
+
if (!xpr)
|
|
20
|
+
req.reject(400, `Exists predicate is missing in the association path "${restrict.where}" in @restrict.where`)
|
|
21
|
+
} else {
|
|
22
|
+
xpr = JSON.parse(JSON.stringify(restrict.where))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolved = {
|
|
26
|
+
grant: restrict.grant,
|
|
27
|
+
target: restrict.target,
|
|
28
|
+
where: restrict.where,
|
|
29
|
+
// replace $user.x with respective values
|
|
30
|
+
_xpr: resolveUserAttrs(xpr, req)
|
|
32
31
|
}
|
|
33
|
-
resolvedApplicables.push(resolved)
|
|
34
32
|
}
|
|
33
|
+
|
|
34
|
+
resolvedApplicables.push(resolved || restrict)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return resolvedApplicables
|
|
@@ -140,11 +140,6 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
143
|
-
if (!cds.env.fiori.lean_draft && req.target._isDraftEnabled) {
|
|
144
|
-
req.query._draftRestrictions = resolvedApplicables
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
143
|
// in case of $apply take a query from sub SELECT
|
|
149
144
|
let query = req.query
|
|
150
145
|
while (query.SELECT.from.SELECT) {
|
|
@@ -159,17 +154,6 @@ const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
|
159
154
|
query.where(restrictionForTarget)
|
|
160
155
|
}
|
|
161
156
|
|
|
162
|
-
const _getFromWithIsActiveEntityRemoved = from => {
|
|
163
|
-
if (!from.ref) return from
|
|
164
|
-
for (const element of from.ref) {
|
|
165
|
-
if (element.where && isActiveEntityRequested(element.where)) {
|
|
166
|
-
element.where = removeIsActiveEntityRecursively(element.where)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return from
|
|
171
|
-
}
|
|
172
|
-
|
|
173
157
|
const _getUnrestrictedCount = async req => {
|
|
174
158
|
const dbtx = cds.tx(req)
|
|
175
159
|
const target =
|
|
@@ -208,8 +192,7 @@ const _getRestrictedCount = async (req, model, resolvedApplicables) => {
|
|
|
208
192
|
return n
|
|
209
193
|
}
|
|
210
194
|
|
|
211
|
-
|
|
212
|
-
async function handler(req) {
|
|
195
|
+
async function check_roles(req) {
|
|
213
196
|
if (req.user._is_privileged || DRAFT_EVENTS[req.event]) {
|
|
214
197
|
// > skip checks (events in DRAFT_EVENTS are checked in draft handlers via InProcessByUser)
|
|
215
198
|
return
|
|
@@ -265,15 +248,13 @@ async function handler(req) {
|
|
|
265
248
|
// no modification -> nothing more to do
|
|
266
249
|
if (!MOD_EVENTS[req.event]) return
|
|
267
250
|
|
|
268
|
-
if (req.query.DELETE) req.query.DELETE.from = _getFromWithIsActiveEntityRemoved(req.query.DELETE.from)
|
|
269
|
-
if (req.query.SELECT) req.query.SELECT.from = _getFromWithIsActiveEntityRemoved(req.query.SELECT.from)
|
|
270
|
-
|
|
271
251
|
// REVISIT: selected data could be used for etag check, diff, etc.
|
|
272
252
|
|
|
273
253
|
/*
|
|
274
254
|
* Here we check if UPDATE/DELETE requests add additional restrictions
|
|
275
255
|
* Note: Needs to happen sequentially because of side effects
|
|
276
256
|
*/
|
|
257
|
+
// REVISIT: Do we really need to do that? Always?
|
|
277
258
|
const unrestrictedCount = await _getUnrestrictedCount(req)
|
|
278
259
|
if (unrestrictedCount === 0) req.reject(404)
|
|
279
260
|
|
|
@@ -284,6 +265,6 @@ async function handler(req) {
|
|
|
284
265
|
}
|
|
285
266
|
}
|
|
286
267
|
|
|
287
|
-
|
|
268
|
+
check_roles._initial = true
|
|
288
269
|
|
|
289
|
-
module.exports =
|
|
270
|
+
module.exports = check_roles
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const WRITE_EVENTS = { CREATE: 1, NEW: 1, UPDATE: 1, PATCH: 1, DELETE: 1, CANCEL: 1, EDIT: 1 }
|
|
2
2
|
const CRUD = Object.assign({ READ: 1 }, WRITE_EVENTS)
|
|
3
|
-
const cds = require('../../../cds')
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Returns the applicable restrictions for the current request as follows:
|
|
@@ -68,10 +67,7 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
const _addNormalizedRestrict = (restrict, restricts, definition) => {
|
|
71
|
-
const where = restrict.where
|
|
72
|
-
? (restrict.where['='] || restrict.where).replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
|
|
73
|
-
: undefined
|
|
74
|
-
|
|
70
|
+
const where = restrict.where?.xpr ?? restrict.where
|
|
75
71
|
restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
|
|
76
72
|
restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
|
|
77
73
|
}
|
|
@@ -117,8 +113,7 @@ const _isToAccessAllowed = (user, restrict) => restrict.to.some(role => user.is(
|
|
|
117
113
|
|
|
118
114
|
const getApplicableRestrictions = (restrictions, event, user) => {
|
|
119
115
|
return restrictions.filter(restrict => {
|
|
120
|
-
|
|
121
|
-
return _isGrantAccessAllowed(eventName, restrict) && _isToAccessAllowed(user, restrict)
|
|
116
|
+
return _isGrantAccessAllowed(event, restrict) && _isToAccessAllowed(user, restrict)
|
|
122
117
|
})
|
|
123
118
|
}
|
|
124
119
|
|
|
@@ -23,6 +23,7 @@ const reject = (req, reason = null) => {
|
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// REVISIT: Do we really need that? -> if not, let's eliminate it
|
|
26
27
|
const getRejectReason = (req, annotation, definition, restrictedCount, unrestrictedCount) => {
|
|
27
28
|
if (!LOG._debug) return
|
|
28
29
|
// it is not possible to specify the reason further than the source as there are multiple factors
|
|
@@ -35,109 +36,111 @@ const getRejectReason = (req, annotation, definition, restrictedCount, unrestric
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const _processUserAttr = (next, restrict, userAttrs, attr) => {
|
|
59
|
-
const clause = _getCurrentSubClause(next, restrict)
|
|
60
|
-
const valOrRef = clause[1] || clause[4]
|
|
61
|
-
|
|
62
|
-
if (clause[0].match(/ is\s*null/)) {
|
|
63
|
-
restrict.where = restrict.where.replace(clause[0], _isNull(userAttrs, attr) ? '1 = 1' : '1 = 2')
|
|
64
|
-
} else if (clause[0].match(/ is\s*not\s*null/)) {
|
|
65
|
-
restrict.where = restrict.where.replace(clause[0], _isNotNull(userAttrs, attr) ? '1 = 1' : '1 = 2')
|
|
66
|
-
} else {
|
|
67
|
-
if (_isNull(userAttrs, attr)) {
|
|
68
|
-
restrict.where = restrict.where.replace(clause[0], '1 = 2')
|
|
69
|
-
} else if (clause[0].match(/ in /)) {
|
|
70
|
-
if (userAttrs[attr].length === 1) {
|
|
71
|
-
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = '${userAttrs[attr][0]}'`)
|
|
72
|
-
} else {
|
|
73
|
-
restrict.where = restrict.where.replace(
|
|
74
|
-
clause[0],
|
|
75
|
-
`${valOrRef} in (${userAttrs[attr].map(ele => `'${ele}'`).join(', ')})`
|
|
76
|
-
)
|
|
39
|
+
const _isNull = element => element.val === null || element.list?.length === 0
|
|
40
|
+
const _isNotNull = element => element.val !== null && (!element.list || element.list.length)
|
|
41
|
+
|
|
42
|
+
const _processNullAttr = where => {
|
|
43
|
+
if (!where) return
|
|
44
|
+
|
|
45
|
+
for (let i = where.length - 1; i >= 0; i--) {
|
|
46
|
+
if (where[i] === 'null') {
|
|
47
|
+
if (where[i - 2] === 'is' && where[i - 1] === 'not' && _isNull(where[i - 3])) {
|
|
48
|
+
where.splice(i - 3, 4, { val: '1' }, '=', { val: '2' })
|
|
49
|
+
i = i - 3
|
|
50
|
+
} else if (where[i - 2] === 'is' && where[i - 1] === 'not' && _isNotNull(where[i - 3])) {
|
|
51
|
+
where.splice(i - 3, 4, { val: '1' }, '=', { val: '1' })
|
|
52
|
+
i = i - 3
|
|
53
|
+
} else if (where[i - 1] === 'is' && _isNull(where[i - 2])) {
|
|
54
|
+
where.splice(i - 2, 3, { val: '1' }, '=', { val: '1' })
|
|
55
|
+
i = i - 2
|
|
56
|
+
} else if (where[i - 1] === 'is' && _isNotNull(where[i - 2])) {
|
|
57
|
+
where.splice(i - 2, 3, { val: '1' }, '=', { val: '2' })
|
|
58
|
+
i = i - 2
|
|
77
59
|
}
|
|
78
|
-
} else if (valOrRef.startsWith("'") && userAttrs[attr].includes(valOrRef.split("'")[1])) {
|
|
79
|
-
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = ${valOrRef}`)
|
|
80
|
-
} else {
|
|
81
|
-
restrict.where = restrict.where.replace(
|
|
82
|
-
clause[0],
|
|
83
|
-
`(${userAttrs[attr].map(ele => `${valOrRef} = '${ele}'`).join(' or ')})`
|
|
84
|
-
)
|
|
85
60
|
}
|
|
86
61
|
}
|
|
87
62
|
}
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
64
|
+
// NOTE: arrayed attr with "=" as operator is a valid expression (see authorization guide)
|
|
65
|
+
const _arrayComparison = (arr, where, index) => {
|
|
66
|
+
if (arr.length === 0) where[index] = { val: null }
|
|
67
|
+
else if (arr.length === 1) where[index] = { val: arr[0] }
|
|
68
|
+
else {
|
|
69
|
+
let start, element
|
|
70
|
+
if (where[index - 1] === '=' && where[index - 2]) {
|
|
71
|
+
start = index - 2
|
|
72
|
+
element = where[index - 2]
|
|
73
|
+
} else if (where[index + 1] === '=' && where[index + 2]) {
|
|
74
|
+
start = index
|
|
75
|
+
element = where[index + 2]
|
|
76
|
+
}
|
|
77
|
+
if (start !== undefined) {
|
|
78
|
+
const expr = []
|
|
79
|
+
arr.forEach(el => {
|
|
80
|
+
if (expr.length) expr.push('or')
|
|
81
|
+
expr.push(element, '=', { val: el })
|
|
82
|
+
})
|
|
83
|
+
where.splice(start, 3, { xpr: expr })
|
|
84
|
+
} else {
|
|
85
|
+
if (where[index + 1] !== 'is')
|
|
86
|
+
throw new Error('user attribute array must be used with operator "=", "in", "is null", or "is not null"')
|
|
87
|
+
where[index] = {
|
|
88
|
+
list: arr.map(v => {
|
|
89
|
+
return { val: v }
|
|
90
|
+
})
|
|
99
91
|
}
|
|
100
92
|
}
|
|
101
|
-
|
|
93
|
+
}
|
|
102
94
|
}
|
|
103
95
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const parts = next[1].split('.')
|
|
113
|
-
let skip
|
|
114
|
-
let val
|
|
115
|
-
let attrs = _getAttrsAsProxy(req.user.attr, { id: req.user.id })
|
|
116
|
-
let attr = parts.shift()
|
|
117
|
-
|
|
118
|
-
while (attr) {
|
|
119
|
-
if (attrs[attr] === undefined || Array.isArray(attrs[attr])) {
|
|
120
|
-
_processUserAttr(next, restrict, attrs, attr)
|
|
121
|
-
skip = true
|
|
122
|
-
break
|
|
96
|
+
const _handleArray = (arr, where, index) => {
|
|
97
|
+
if (where[index - 1] === 'in') {
|
|
98
|
+
if (arr.length === 0) where[index] = { list: [{ val: '__dummy__' }] }
|
|
99
|
+
else
|
|
100
|
+
where[index] = {
|
|
101
|
+
list: arr.map(v => {
|
|
102
|
+
return { val: v }
|
|
103
|
+
})
|
|
123
104
|
}
|
|
105
|
+
} else _arrayComparison(arr, where, index)
|
|
106
|
+
}
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
108
|
+
const resolveUserAttrs = (where, req) => {
|
|
109
|
+
let non_existing
|
|
110
|
+
for (let i = 0; i < where.length; i++) {
|
|
111
|
+
const r = where[i]
|
|
112
|
+
if (r.xpr) r.xpr = resolveUserAttrs(r.xpr, req)
|
|
113
|
+
else if (r.SELECT?.where) r.SELECT.where = resolveUserAttrs(r.SELECT.where, req)
|
|
114
|
+
else if (r?.ref?.[0] === '$user') {
|
|
115
|
+
if (r.ref.length === 1 || r.ref[1] === 'id') r.val = req.user.id
|
|
116
|
+
else {
|
|
117
|
+
let val = req.user.attr
|
|
118
|
+
for (let j = 1; j < r.ref.length; j++) {
|
|
119
|
+
const attr = r.ref[j]
|
|
120
|
+
if (!Object.prototype.hasOwnProperty.call(val, attr)) {
|
|
121
|
+
non_existing = true
|
|
122
|
+
break
|
|
123
|
+
} else val = val?.[attr]
|
|
124
|
+
}
|
|
125
|
+
if (non_existing) break
|
|
126
|
+
if (val === undefined) val = null
|
|
127
|
+
if (val === null && where[i - 1] === 'in') where[i] = { list: [{ val: '__dummy__' }] }
|
|
128
|
+
else if (Array.isArray(val)) _handleArray(val, where, i)
|
|
129
|
+
else r.val = val
|
|
130
|
+
}
|
|
131
|
+
delete r.ref
|
|
132
|
+
} else if (r.ref) {
|
|
133
|
+
r.ref.forEach(el => {
|
|
134
|
+
if (el.where) el.where = resolveUserAttrs(el.where, req)
|
|
135
|
+
})
|
|
130
136
|
}
|
|
137
|
+
}
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
const v = val === undefined ? null : typeof val === 'string' && val.match(/^\d*$/) ? `'${val}'` : val
|
|
134
|
-
restrict.where = restrict.where.replace(next[0], v).replace('in null', 'is null')
|
|
135
|
-
}
|
|
139
|
+
if (non_existing) return [{ val: '1' }, '=', { val: '2' }]
|
|
136
140
|
|
|
137
|
-
|
|
138
|
-
}
|
|
141
|
+
_processNullAttr(where)
|
|
139
142
|
|
|
140
|
-
return
|
|
143
|
+
return where
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
const _authDependsOnAncestor = (entity, annotations) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { deepCopy } = require('../utils/copy')
|
|
5
5
|
const { getColumns } = require('../utils/columns')
|
|
6
6
|
const { enhanceStreamResult } = require('../utils/stream')
|
|
7
7
|
const getError = require('../error')
|
|
@@ -12,7 +12,6 @@ const _targetEntityDoesNotExist = async req => {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
exports.impl = cds.service.impl(function () {
|
|
15
|
-
// eslint-disable-next-line complexity
|
|
16
15
|
this.on(['CREATE', 'READ', 'UPDATE', 'DELETE', 'UPSERT'], '*', async function (req) {
|
|
17
16
|
if (!req.query) {
|
|
18
17
|
throw getError({
|
|
@@ -39,7 +38,7 @@ exports.impl = cds.service.impl(function () {
|
|
|
39
38
|
|
|
40
39
|
const { ref } = (req.query.INSERT && req.query.INSERT.into) || (req.query.SELECT && req.query.SELECT.from) || {}
|
|
41
40
|
// REVISIT: why is copy necessary?
|
|
42
|
-
if (ref && ref.length > 1) pathExistsQuery = SELECT(1).from({ ref:
|
|
41
|
+
if (ref && ref.length > 1) pathExistsQuery = SELECT(1).from({ ref: deepCopy(ref.slice(0, -1)) })
|
|
43
42
|
|
|
44
43
|
if (req.event === 'CREATE' && pathExistsQuery) {
|
|
45
44
|
const res = await pathExistsQuery
|
|
@@ -101,8 +100,10 @@ exports.impl = cds.service.impl(function () {
|
|
|
101
100
|
if (await _targetEntityDoesNotExist(req)) req.reject(404) // REVISIT: add a reasonable error message
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
// flag to trigger read after write in
|
|
105
|
-
req._.readAfterWrite = true
|
|
103
|
+
// flag to trigger read after write in legacy odata adapter
|
|
104
|
+
if (req.constructor.name in { ODataRequest: 1 }) req._.readAfterWrite = true
|
|
105
|
+
if (req.protocol?.match(/odata/)) req._.readAfterWrite = true //> REVISIT for noah
|
|
106
|
+
|
|
106
107
|
return req.data
|
|
107
108
|
})
|
|
108
109
|
|
|
@@ -73,10 +73,10 @@ const _getValidationStmt = (ifMatchEtags, ifNoneMatchEtags, req, model) => {
|
|
|
73
73
|
const commonGenericValidateETag = async function (req) {
|
|
74
74
|
/*
|
|
75
75
|
* currently, etag is only supported for OData requests
|
|
76
|
-
* REST requests should be added later
|
|
76
|
+
* REST requests should be added later -> REVISIT!!!
|
|
77
77
|
* other protocols, such as graphql, need to be checked
|
|
78
78
|
*/
|
|
79
|
-
if (req.protocol !== 'odata
|
|
79
|
+
if (req.protocol !== 'odata') return
|
|
80
80
|
|
|
81
81
|
// automatically add etag columns if not already there
|
|
82
82
|
if (req.query.SELECT) addEtagColumns(req.query.SELECT.columns, req.target)
|
|
@@ -148,14 +148,10 @@ module.exports = cds.service.impl(function () {
|
|
|
148
148
|
if (!entity._etag) continue
|
|
149
149
|
|
|
150
150
|
if (entity._isDraftEnabled) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.before(events, entity.drafts, commonGenericValidateETag)
|
|
156
|
-
} else {
|
|
157
|
-
this.before(['READ', 'PATCH', 'CANCEL', 'DELETE'], entity, commonGenericValidateETag)
|
|
158
|
-
}
|
|
151
|
+
this.before(['READ', 'DELETE'], entity, commonGenericValidateETag)
|
|
152
|
+
// if draft compat is on, the read handler is automatically registered for <entity> and <entity>.drafts
|
|
153
|
+
const events = cds.env.fiori.draft_compat ? ['UPDATE', 'CANCEL'] : ['READ', 'UPDATE', 'CANCEL']
|
|
154
|
+
this.before(events, entity.drafts, commonGenericValidateETag)
|
|
159
155
|
} else {
|
|
160
156
|
this.before(['READ', 'UPDATE', 'DELETE'], entity, commonGenericValidateETag)
|
|
161
157
|
}
|
|
@@ -164,8 +160,7 @@ module.exports = cds.service.impl(function () {
|
|
|
164
160
|
// etag not applicable to functions and unbound actions
|
|
165
161
|
if (entity.actions[action].kind !== 'action') continue
|
|
166
162
|
if (entity._isDraftEnabled) {
|
|
167
|
-
|
|
168
|
-
if (cds.env.fiori?.lean_draft) _entity = action === 'draftEdit' ? entity : entity.drafts
|
|
163
|
+
const _entity = action === 'draftEdit' ? entity : entity.drafts
|
|
169
164
|
this.before(action === 'draftEdit' ? 'EDIT' : action, _entity, commonGenericValidateETag)
|
|
170
165
|
} else {
|
|
171
166
|
this.before(action, entity, commonGenericValidateETag)
|