@sap/cds 5.8.4 → 5.9.2
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 +198 -77
- package/app/fiori/preview.js +16 -11
- package/app/fiori/routes.js +15 -8
- package/app/index.js +1 -1
- package/bin/build/buildTaskFactory.js +3 -3
- package/bin/build/buildTaskProviderFactory.js +1 -1
- package/bin/build/constants.js +1 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
- package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +8 -2
- package/bin/build/provider/hana/2migration.js +27 -24
- package/bin/build/provider/hana/index.js +17 -18
- package/bin/build/provider/hana/migrationtable.js +9 -10
- package/bin/build/provider/java-cf/index.js +4 -5
- package/bin/build/provider/node-cf/index.js +99 -6
- package/bin/cds.js +17 -18
- package/bin/deploy/to-hana/cfUtil.js +16 -19
- package/bin/deploy/to-hana/hana.js +7 -24
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
- package/bin/mtx/in-cds.js +2 -2
- package/bin/serve.js +10 -3
- package/bin/utils/modules.js +7 -0
- package/bin/version.js +56 -3
- package/lib/compile/cdsc.js +7 -2
- package/lib/compile/etc/_localized.js +37 -25
- package/lib/compile/etc/csv.js +8 -8
- package/lib/compile/for/drafts.js +9 -0
- package/lib/compile/for/java.js +16 -0
- package/lib/compile/for/nodejs.js +12 -0
- package/lib/compile/index.js +3 -0
- package/lib/compile/minify.js +16 -2
- package/lib/compile/parse.js +2 -2
- package/lib/compile/resolve.js +35 -18
- package/lib/compile/to/json.js +3 -1
- package/lib/compile/to/sql.js +2 -2
- package/lib/compile/to/srvinfo.js +4 -2
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +3 -4
- package/lib/core/entities.js +15 -14
- package/lib/core/index.js +39 -36
- package/lib/core/reflect.js +4 -2
- package/lib/deploy.js +114 -127
- package/lib/env/defaults.js +1 -0
- package/lib/env/index.js +165 -165
- package/lib/env/presets.js +1 -0
- package/lib/env/requires.js +121 -50
- package/lib/index.js +2 -0
- package/lib/log/format/kibana.js +2 -2
- package/lib/ql/SELECT.js +10 -0
- package/lib/ql/parse.js +1 -0
- package/lib/req/cds-context.js +4 -1
- package/lib/req/context.js +50 -56
- package/lib/req/event.js +1 -6
- package/lib/req/locale.js +6 -5
- package/lib/req/request.js +2 -0
- package/lib/req/user.js +7 -5
- package/lib/serve/Service-api.js +10 -7
- package/lib/serve/Service-dispatch.js +9 -11
- package/lib/serve/Service-methods.js +30 -41
- package/lib/serve/Transaction.js +10 -7
- package/lib/serve/adapters.js +11 -9
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +28 -15
- package/lib/utils/data.js +1 -1
- package/lib/utils/index.js +27 -30
- package/lib/utils/resources/index.js +101 -0
- package/lib/utils/resources/tar.js +71 -0
- package/lib/utils/resources/utils.js +11 -0
- package/libx/_runtime/audit/Service.js +36 -39
- package/libx/_runtime/audit/generic/personal/access.js +3 -4
- package/libx/_runtime/audit/generic/personal/modification.js +3 -4
- package/libx/_runtime/audit/utils/v2.js +1 -2
- package/libx/_runtime/auth/index.js +126 -84
- package/libx/_runtime/auth/strategies/JWT.js +12 -19
- package/libx/_runtime/auth/strategies/dummy.js +1 -5
- package/libx/_runtime/auth/strategies/dwc.js +11 -9
- package/libx/_runtime/auth/strategies/mock.js +0 -4
- package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
- package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
- package/libx/_runtime/auth/utils.js +22 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
- package/libx/_runtime/cds-services/services/Service.js +40 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
- package/libx/_runtime/cds-services/util/assert.js +20 -14
- package/libx/_runtime/cds.js +9 -1
- package/libx/_runtime/common/aspects/any.js +5 -0
- package/libx/_runtime/common/aspects/entity.js +25 -7
- package/libx/_runtime/common/aspects/utils.js +2 -2
- package/libx/_runtime/common/composition/data.js +6 -0
- package/libx/_runtime/common/composition/insert.js +3 -2
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +4 -4
- package/libx/_runtime/common/constants/draft.js +29 -26
- package/libx/_runtime/common/error/constants.js +2 -2
- package/libx/_runtime/common/error/frontend.js +7 -15
- package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
- package/libx/_runtime/common/generic/auth/constants.js +20 -0
- package/libx/_runtime/common/generic/auth/expand.js +54 -0
- package/libx/_runtime/common/generic/auth/index.js +32 -0
- package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
- package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
- package/libx/_runtime/common/generic/auth/requires.js +34 -0
- package/libx/_runtime/common/generic/auth/restrict.js +298 -0
- package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
- package/libx/_runtime/common/generic/auth/utils.js +213 -0
- package/libx/_runtime/common/generic/crud.js +8 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +35 -35
- package/libx/_runtime/common/generic/sorting.js +2 -3
- package/libx/_runtime/common/generic/temporal.js +2 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/common/toggles/handler.js +21 -0
- package/libx/_runtime/common/utils/copy.js +10 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
- package/libx/_runtime/common/utils/csn.js +63 -1
- package/libx/_runtime/common/utils/dollar.js +10 -1
- package/libx/_runtime/common/utils/draft.js +46 -7
- package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
- package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
- package/libx/_runtime/common/utils/generateOnCond.js +4 -1
- package/libx/_runtime/common/utils/quotingStyles.js +2 -0
- package/libx/_runtime/common/utils/resolveStructured.js +25 -9
- package/libx/_runtime/common/utils/resolveView.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
- package/libx/_runtime/common/utils/structured.js +33 -37
- package/libx/_runtime/common/utils/template.js +17 -8
- package/libx/_runtime/common/utils/templateProcessor.js +28 -28
- package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
- package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
- package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
- package/libx/_runtime/db/generic/index.js +1 -3
- package/libx/_runtime/db/generic/input.js +5 -10
- package/libx/_runtime/db/generic/rewrite.js +5 -2
- package/libx/_runtime/db/generic/structured.js +2 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +1 -1
- package/libx/_runtime/db/query/update.js +9 -14
- package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
- package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
- package/libx/_runtime/db/utils/columns.js +3 -3
- package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
- package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
- package/libx/_runtime/extensibility/mps/index.js +5 -0
- package/libx/_runtime/extensibility/mps/service.js +111 -0
- package/libx/_runtime/extensibility/mps/tar.js +42 -0
- package/libx/_runtime/extensibility/mps/utils.js +11 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/extensibility/uiflex/index.js +54 -0
- package/libx/_runtime/extensibility/uiflex/service.js +276 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +4 -4
- package/libx/_runtime/fiori/generic/new.js +3 -3
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +58 -66
- package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
- package/libx/_runtime/fiori/utils/handler.js +6 -13
- package/libx/_runtime/fiori/utils/where.js +6 -5
- package/libx/_runtime/hana/Service.js +4 -10
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
- package/libx/_runtime/hana/driver.js +2 -2
- package/libx/_runtime/hana/execute.js +45 -75
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/streaming.js +2 -1
- package/libx/_runtime/index.js +6 -6
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
- package/libx/_runtime/messaging/Outbox.js +2 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
- package/libx/_runtime/messaging/common-utils/connections.js +5 -7
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
- package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +14 -12
- package/libx/_runtime/messaging/outbox/utils.js +18 -19
- package/libx/_runtime/messaging/redis-messaging.js +91 -0
- package/libx/_runtime/messaging/service.js +8 -6
- package/libx/_runtime/remote/Service.js +44 -8
- package/libx/_runtime/remote/utils/client.js +24 -19
- package/libx/_runtime/remote/utils/data.js +11 -11
- package/libx/_runtime/sqlite/Service.js +6 -9
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
- package/libx/_runtime/types/api.js +10 -2
- package/libx/common/utils/ucsn.js +109 -0
- package/libx/gql/resolvers/crud/update.js +5 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
- package/libx/gql/schema/typeDefMap.js +2 -2
- package/libx/odata/afterburner.js +110 -16
- package/libx/odata/cqn2odata.js +24 -27
- package/libx/odata/grammar.pegjs +9 -1
- package/libx/odata/parseToCqn.js +39 -0
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +9 -1
- package/libx/rest/middleware/input.js +54 -0
- package/libx/rest/middleware/operation.js +14 -1
- package/libx/rest/middleware/parse.js +11 -7
- package/package.json +2 -2
- package/server.js +34 -19
- package/srv/audit-log.cds +2 -2
- package/srv/flex.cds +8 -2
- package/srv/flex.js +1 -1
- package/srv/mps.cds +23 -0
- package/srv/mps.js +1 -0
- package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
- package/libx/_runtime/common/generic/auth.js +0 -874
- package/libx/_runtime/common/toggles/alpha.js +0 -43
- package/libx/_runtime/db/generic/arrayed.js +0 -33
- package/libx/_runtime/fiori/uiflex/index.js +0 -35
- package/libx/_runtime/fiori/uiflex/service.js +0 -150
- package/libx/rest/utils/data.js +0 -60
|
@@ -45,14 +45,16 @@ const assertError = (code, element, value, key, pathSegments = []) => {
|
|
|
45
45
|
code = code.code
|
|
46
46
|
}
|
|
47
47
|
const { name, type, precision, scale } = element
|
|
48
|
-
|
|
48
|
+
pathSegments = pathSegments.slice(0)
|
|
49
|
+
pathSegments.push({ key: name || key })
|
|
50
|
+
const path = pathSegments.map(({ key, url }) => url || key).join('/')
|
|
49
51
|
|
|
50
52
|
const e = new Error()
|
|
51
53
|
const error = Object.assign(e, getEntry({ code, message: code, target: path, args: args || [name || key] }))
|
|
52
54
|
Object.assign(error, {
|
|
53
55
|
entity: element.parent && element.parent.name,
|
|
54
56
|
element: name, // > REVISIT: when is error.element needed?
|
|
55
|
-
type: element.items ? element.items.
|
|
57
|
+
type: element.items ? element.items._type : type,
|
|
56
58
|
value
|
|
57
59
|
})
|
|
58
60
|
|
|
@@ -154,7 +156,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
|
154
156
|
for (const objKey in elements) {
|
|
155
157
|
if (objKey.startsWith(`${key}_`)) {
|
|
156
158
|
const element = elements[objKey]
|
|
157
|
-
const check = CDS_TYPE_CHECKS[element.
|
|
159
|
+
const check = CDS_TYPE_CHECKS[element._type]
|
|
158
160
|
found = true
|
|
159
161
|
|
|
160
162
|
const nestedData = value[objKey.substring(key.length + 1)]
|
|
@@ -168,7 +170,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
|
168
170
|
return found || ignoreNonModelledData
|
|
169
171
|
}
|
|
170
172
|
|
|
171
|
-
const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
|
|
173
|
+
const _checkStaticElementByKey = (definition, key, value, result = [], ignoreNonModelledData = true) => {
|
|
172
174
|
const elementsOrParameters = definition.elements || definition.params
|
|
173
175
|
if (!elementsOrParameters) return result
|
|
174
176
|
const elementOrParameter = elementsOrParameters[key]
|
|
@@ -182,16 +184,16 @@ const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModel
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
let check
|
|
185
|
-
if (elementOrParameter.
|
|
187
|
+
if (elementOrParameter.isUUID && definition.name === 'ProvisioningService.tenant') {
|
|
186
188
|
// > old SCP accounts don't have UUID ids
|
|
187
189
|
check = CDS_TYPE_CHECKS['cds.String']
|
|
188
190
|
} else {
|
|
189
|
-
check = CDS_TYPE_CHECKS[elementOrParameter.
|
|
191
|
+
check = CDS_TYPE_CHECKS[elementOrParameter._type]
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
if (check && !check(value, elementOrParameter)) {
|
|
193
195
|
// code, entity, element, value
|
|
194
|
-
const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter.
|
|
196
|
+
const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter._type]
|
|
195
197
|
result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
|
|
196
198
|
}
|
|
197
199
|
|
|
@@ -274,18 +276,18 @@ const checkIfAssocDeep = (element, value, req) => {
|
|
|
274
276
|
/**
|
|
275
277
|
* @param {import('../../types/api').InputConstraints} constraints
|
|
276
278
|
*/
|
|
277
|
-
const checkInputConstraints = ({ element, value, errors, key,
|
|
279
|
+
const checkInputConstraints = ({ element, value, errors, key, path, event }) => {
|
|
278
280
|
if (!element) return errors
|
|
279
281
|
|
|
280
|
-
_checkMandatoryElement(element, value, errors, key,
|
|
282
|
+
_checkMandatoryElement(element, value, errors, key, path)
|
|
281
283
|
|
|
282
284
|
if (value == null) return errors
|
|
283
285
|
|
|
284
|
-
_checkEnumElement(element, value, errors, key,
|
|
286
|
+
_checkEnumElement(element, value, errors, key, path)
|
|
285
287
|
|
|
286
|
-
_checkRangeElement(element, value, errors, key,
|
|
288
|
+
_checkRangeElement(element, value, errors, key, path)
|
|
287
289
|
|
|
288
|
-
_checkFormatElement(element, value, errors, key,
|
|
290
|
+
_checkFormatElement(element, value, errors, key, path)
|
|
289
291
|
|
|
290
292
|
return errors
|
|
291
293
|
}
|
|
@@ -310,13 +312,15 @@ const checkKeys = (entity, data) => {
|
|
|
310
312
|
const entityKeys = Object.keys(entity.keys)
|
|
311
313
|
return data.reduce((result, row) => {
|
|
312
314
|
for (const key of entityKeys) {
|
|
313
|
-
if (row[key] === undefined && entity.elements[key].
|
|
315
|
+
if (row[key] === undefined && !entity.elements[key].isAssociation)
|
|
314
316
|
result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
|
|
315
317
|
}
|
|
316
318
|
return result
|
|
317
319
|
}, [])
|
|
318
320
|
}
|
|
319
321
|
|
|
322
|
+
const assertNotNullError = element => assertError(ASSERT_NOT_NULL, element)
|
|
323
|
+
|
|
320
324
|
module.exports = {
|
|
321
325
|
CDS_TYPE_CHECKS,
|
|
322
326
|
checkComplexType,
|
|
@@ -324,5 +328,7 @@ module.exports = {
|
|
|
324
328
|
checkInputConstraints,
|
|
325
329
|
checkKeys,
|
|
326
330
|
assertError,
|
|
327
|
-
checkIfAssocDeep
|
|
331
|
+
checkIfAssocDeep,
|
|
332
|
+
checkStaticElementByKey: _checkStaticElementByKey,
|
|
333
|
+
assertNotNullError
|
|
328
334
|
}
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -14,7 +14,14 @@ cds.extend(entity).with(require('./common/aspects/entity'))
|
|
|
14
14
|
* mtx?
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(cds, '_mtxEnabled', {
|
|
17
|
-
get: () => cds.mtx && typeof cds.mtx.in === 'function',
|
|
17
|
+
get: () => cds.mtx && typeof cds.mtx.in === 'function' && !cds.env.features.streamlined_mtx,
|
|
18
|
+
configurable: true
|
|
19
|
+
})
|
|
20
|
+
Object.defineProperty(cds, '_mpsEnabled', {
|
|
21
|
+
get: () =>
|
|
22
|
+
cds.requires.extensibility ||
|
|
23
|
+
cds.requires.toggles ||
|
|
24
|
+
(cds.requires.multitenancy && cds.env.features.streamlined_mtx),
|
|
18
25
|
configurable: true
|
|
19
26
|
})
|
|
20
27
|
|
|
@@ -23,6 +30,7 @@ Object.defineProperty(cds, '_mtxEnabled', {
|
|
|
23
30
|
*/
|
|
24
31
|
// referential integrity
|
|
25
32
|
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
33
|
+
// REVISIT: Do not access cds.env too early!
|
|
26
34
|
Object.defineProperty(cds.env.features, '_db_foreign_key_constraints', {
|
|
27
35
|
get: () => {
|
|
28
36
|
const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { getRelations, isMandatory, isReadOnly } = require('./utils')
|
|
2
|
+
const { foreignKey4 } = require('../../common/utils/foreignKeyPropagations')
|
|
2
3
|
|
|
3
4
|
// NOTE: Please only add things which are relevant to _any_ type,
|
|
4
5
|
// use specialized types otherwise (entity, Association, ...).
|
|
@@ -19,4 +20,8 @@ module.exports = class {
|
|
|
19
20
|
get _relations() {
|
|
20
21
|
return this.own('__relations') || this.set('__relations', getRelations(this))
|
|
21
22
|
}
|
|
23
|
+
|
|
24
|
+
get _foreignKey4() {
|
|
25
|
+
return this.own('__foreignKey4') || this.set('__foreignKey4', foreignKey4(this))
|
|
26
|
+
}
|
|
22
27
|
}
|
|
@@ -7,13 +7,23 @@ const { getETag, hasPersonalData, hasSensitiveData } = require('./utils')
|
|
|
7
7
|
|
|
8
8
|
let getSearchableColumns
|
|
9
9
|
|
|
10
|
+
const _flat2struct = (def, prefix = '') => {
|
|
11
|
+
const map = {}
|
|
12
|
+
for (const ele in def.elements) {
|
|
13
|
+
if (def.elements[ele].elements)
|
|
14
|
+
Object.assign(map, _flat2struct(def.elements[ele], prefix ? prefix + '$$$' + ele : ele))
|
|
15
|
+
else if (prefix) map[prefix.replace(/\$\$\$/g, '_') + '_' + ele] = [...prefix.split('$$$'), ele]
|
|
16
|
+
}
|
|
17
|
+
return map
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
module.exports = class {
|
|
11
21
|
get _isSingleton() {
|
|
12
22
|
return (
|
|
13
23
|
this.own('__isSingleton') ||
|
|
14
24
|
this.set(
|
|
15
25
|
'__isSingleton',
|
|
16
|
-
this['@odata.singleton'] || (this['@odata.singleton.nullable'] && this['@odata.singleton'] !== false)
|
|
26
|
+
!!(this['@odata.singleton'] || (this['@odata.singleton.nullable'] && this['@odata.singleton'] !== false))
|
|
17
27
|
)
|
|
18
28
|
)
|
|
19
29
|
}
|
|
@@ -23,7 +33,7 @@ module.exports = class {
|
|
|
23
33
|
this.own('__hasPersistenceSkip') ||
|
|
24
34
|
this.set(
|
|
25
35
|
'__hasPersistenceSkip',
|
|
26
|
-
this.own('@cds.persistence.skip') && this.own('@cds.persistence.skip') !== 'if-unused'
|
|
36
|
+
!!(this.own('@cds.persistence.skip') && this.own('@cds.persistence.skip') !== 'if-unused')
|
|
27
37
|
)
|
|
28
38
|
)
|
|
29
39
|
}
|
|
@@ -33,9 +43,11 @@ module.exports = class {
|
|
|
33
43
|
this.own('__isDraftEnabled') ||
|
|
34
44
|
this.set(
|
|
35
45
|
'__isDraftEnabled',
|
|
36
|
-
(
|
|
46
|
+
!!(
|
|
47
|
+
(this.associations && this.associations.DraftAdministrativeData) ||
|
|
37
48
|
this.name.match(/\.DraftAdministrativeData$/) ||
|
|
38
49
|
(this.own('@odata.draft.enabled') && this.own('@Common.DraftRoot.ActivationAction'))
|
|
50
|
+
)
|
|
39
51
|
)
|
|
40
52
|
)
|
|
41
53
|
}
|
|
@@ -66,25 +78,31 @@ module.exports = class {
|
|
|
66
78
|
get _auditCreate() {
|
|
67
79
|
return (
|
|
68
80
|
this.own('__auditCreate') ||
|
|
69
|
-
this.set('__auditCreate', this._hasPersonalData && this['@AuditLog.Operation.Insert'])
|
|
81
|
+
this.set('__auditCreate', !!(this._hasPersonalData && this['@AuditLog.Operation.Insert']))
|
|
70
82
|
)
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
get _auditRead() {
|
|
74
|
-
return
|
|
86
|
+
return (
|
|
87
|
+
this.own('__auditRead') || this.set('__auditRead', !!(this._hasPersonalData && this['@AuditLog.Operation.Read']))
|
|
88
|
+
)
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
get _auditUpdate() {
|
|
78
92
|
return (
|
|
79
93
|
this.own('__auditUpdate') ||
|
|
80
|
-
this.set('__auditUpdate', this._hasPersonalData && this['@AuditLog.Operation.Update'])
|
|
94
|
+
this.set('__auditUpdate', !!(this._hasPersonalData && this['@AuditLog.Operation.Update']))
|
|
81
95
|
)
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
get _auditDelete() {
|
|
85
99
|
return (
|
|
86
100
|
this.own('__auditDelete') ||
|
|
87
|
-
this.set('__auditDelete', this._hasPersonalData && this['@AuditLog.Operation.Delete'])
|
|
101
|
+
this.set('__auditDelete', !!(this._hasPersonalData && this['@AuditLog.Operation.Delete']))
|
|
88
102
|
)
|
|
89
103
|
}
|
|
104
|
+
|
|
105
|
+
get _flat2struct() {
|
|
106
|
+
return this.own('_flat2struct') || this.set('_flat2struct', _flat2struct(this))
|
|
107
|
+
}
|
|
90
108
|
}
|
|
@@ -51,7 +51,7 @@ const hasPersonalData = entity => {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
return val
|
|
54
|
+
return !!val
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const hasSensitiveData = entity => {
|
|
@@ -64,7 +64,7 @@ const hasSensitiveData = entity => {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
return val
|
|
67
|
+
return !!val
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
|
|
@@ -236,6 +236,11 @@ const _selectDeepUpdateData = async args => {
|
|
|
236
236
|
return _mergeResults(result, selectData || [], root, model, compositionTree, entityName)
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
// if a view has an orderBy with renamed field, we need to resolve it
|
|
240
|
+
const _resolveOrderBy = (orderBy, transitions) => {
|
|
241
|
+
if (orderBy && transitions.length > 0) orderBy.map(el => (el.ref[0] = transitions[0].mapping.get(el.ref[0]).ref[0]))
|
|
242
|
+
}
|
|
243
|
+
|
|
239
244
|
/*
|
|
240
245
|
* exports
|
|
241
246
|
*/
|
|
@@ -255,6 +260,7 @@ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) =>
|
|
|
255
260
|
const entityName = ensureNoDraftsSuffix(from)
|
|
256
261
|
const draft = entityName !== from
|
|
257
262
|
const orderBy = req && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
|
|
263
|
+
_resolveOrderBy(orderBy, sqlQuery.UPDATE._transitions)
|
|
258
264
|
const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
|
|
259
265
|
const compositionTree = getCompositionTree({
|
|
260
266
|
definitions: model.definitions,
|
|
@@ -55,7 +55,8 @@ const _addSubDeepInsertCQN = (model, compositionTree, data, cqns, draft) => {
|
|
|
55
55
|
|
|
56
56
|
const _entityFromINSERT = (model, INSERT) => {
|
|
57
57
|
if (INSERT && INSERT.into) {
|
|
58
|
-
const
|
|
58
|
+
const into = (INSERT.into.ref && INSERT.into.ref[0]) || INSERT.into.name || INSERT.into
|
|
59
|
+
const entityName = ensureNoDraftsSuffix(into)
|
|
59
60
|
return model.definitions[entityName]
|
|
60
61
|
}
|
|
61
62
|
}
|
|
@@ -75,7 +76,7 @@ const hasDeepInsert = (model, cqn) => {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
const getDeepInsertCQNs = (model, cqn) => {
|
|
78
|
-
const into = cqn.INSERT.into.name || cqn.INSERT.into
|
|
79
|
+
const into = (cqn.INSERT.into.ref && cqn.INSERT.into.ref[0]) || cqn.INSERT.into.name || cqn.INSERT.into
|
|
79
80
|
const entityName = ensureNoDraftsSuffix(into)
|
|
80
81
|
const draft = entityName !== into
|
|
81
82
|
const dataEntries = cqn.INSERT.entries ? deepCopyArray(cqn.INSERT.entries) : []
|
|
@@ -5,6 +5,7 @@ const { isRootEntity } = require('../utils/csn')
|
|
|
5
5
|
const { getTransition, getDBTable } = require('../utils/resolveView')
|
|
6
6
|
|
|
7
7
|
const getError = require('../../common/error')
|
|
8
|
+
const { prefixForStruct } = require('../../common/utils/csn')
|
|
8
9
|
|
|
9
10
|
/*
|
|
10
11
|
* own utils
|
|
@@ -17,19 +18,12 @@ const _foreignKeysToLinks = (element, inverse) =>
|
|
|
17
18
|
childElement: e.parentElement,
|
|
18
19
|
parentElement: e.childElement,
|
|
19
20
|
childFieldValue: e.parentFieldValue,
|
|
20
|
-
parentFieldValue: e.childFieldValue
|
|
21
|
-
prefix: e.prefix
|
|
21
|
+
parentFieldValue: e.childFieldValue
|
|
22
22
|
}
|
|
23
23
|
: e
|
|
24
24
|
const link = {}
|
|
25
|
-
if (e.parentElement)
|
|
26
|
-
|
|
27
|
-
e.prefix && !e.parentElement.name.includes(e.prefix)
|
|
28
|
-
? `${e.prefix}_${e.parentElement.name}`
|
|
29
|
-
: e.parentElement.name
|
|
30
|
-
if (e.childElement)
|
|
31
|
-
link.targetKey =
|
|
32
|
-
e.prefix && !e.childElement.name.includes(e.prefix) ? `${e.prefix}_${e.childElement.name}` : e.childElement.name
|
|
25
|
+
if (e.parentElement) link.entityKey = prefixForStruct(e.parentElement) + e.parentElement.name
|
|
26
|
+
if (e.childElement) link.targetKey = prefixForStruct(e.childElement) + e.childElement.name
|
|
33
27
|
if (e.parentFieldValue !== undefined) link.entityVal = e.parentFieldValue
|
|
34
28
|
if (e.childFieldValue !== undefined) link.targetVal = e.childFieldValue
|
|
35
29
|
return link
|
|
@@ -58,11 +58,11 @@ const _unwrapVal = obj => {
|
|
|
58
58
|
|
|
59
59
|
function _fillLinkFromStructuredData(entity, entry) {
|
|
60
60
|
for (const elementName in entity.elements) {
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
61
|
+
const _foreignKey4 = entity.elements._foreignKey4
|
|
62
|
+
if (_foreignKey4 && entry[_foreignKey4]) {
|
|
63
63
|
const foreignKey = entity.elements[elementName].name
|
|
64
64
|
const childKey = foreignKey.split('_')[1]
|
|
65
|
-
const val = _unwrapVal(entry[
|
|
65
|
+
const val = _unwrapVal(entry[_foreignKey4])[childKey]
|
|
66
66
|
if (val !== undefined) entry[foreignKey] = val
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -89,7 +89,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, model) => {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// comp2one removed?
|
|
92
|
-
const fk = entity.elements[key] && entity.elements[key]
|
|
92
|
+
const fk = entity.elements[key] && entity.elements[key]._foreignKey4
|
|
93
93
|
if (fk && newVal === undefined && oldVal !== undefined) {
|
|
94
94
|
const nav = entity.elements[fk]
|
|
95
95
|
// REVISIT: why check @cds.persistence.skip needed? bad tests?
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
+
const DRAFT_COLUMNS = ['IsActiveEntity', 'HasActiveEntity', 'HasDraftEntity', 'DraftAdministrativeData_DraftUUID']
|
|
2
|
+
const DRAFT_COLUMNS_UNION = [
|
|
3
|
+
'IsActiveEntity',
|
|
4
|
+
'HasActiveEntity',
|
|
5
|
+
'HasDraftEntity',
|
|
6
|
+
'DraftAdministrativeData_DraftUUID',
|
|
7
|
+
'SiblingEntity',
|
|
8
|
+
'DraftAdministrativeData'
|
|
9
|
+
]
|
|
10
|
+
const DRAFT_COLUMNS_ADMIN = [
|
|
11
|
+
'DraftUUID',
|
|
12
|
+
'CreatedByUser',
|
|
13
|
+
'InProcessByUser',
|
|
14
|
+
'CreationDateTime',
|
|
15
|
+
'LastChangeDateTime',
|
|
16
|
+
'LastChangedByUser',
|
|
17
|
+
'DraftIsProcessedByMe',
|
|
18
|
+
'DraftIsCreatedByMe'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const objectify = arr =>
|
|
22
|
+
arr.reduce((acc, cur) => {
|
|
23
|
+
acc[cur] = 1
|
|
24
|
+
return acc
|
|
25
|
+
}, {})
|
|
26
|
+
|
|
1
27
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
HasActiveEntity: true,
|
|
6
|
-
HasDraftEntity: true,
|
|
7
|
-
DraftAdministrativeData_DraftUUID: true,
|
|
8
|
-
DraftUUID: true
|
|
9
|
-
},
|
|
10
|
-
DRAFT_COLUMNS_UNION: [
|
|
11
|
-
'IsActiveEntity',
|
|
12
|
-
'HasActiveEntity',
|
|
13
|
-
'HasDraftEntity',
|
|
14
|
-
'DraftAdministrativeData_DraftUUID',
|
|
15
|
-
'SiblingEntity',
|
|
16
|
-
'DraftAdministrativeData'
|
|
17
|
-
],
|
|
18
|
-
DRAFT_COLUMNS_ADMIN: [
|
|
19
|
-
'DraftUUID',
|
|
20
|
-
'CreatedByUser',
|
|
21
|
-
'InProcessByUser',
|
|
22
|
-
'CreationDateTime',
|
|
23
|
-
'LastChangeDateTime',
|
|
24
|
-
'LastChangedByUser',
|
|
25
|
-
'DraftIsProcessedByMe',
|
|
26
|
-
'DraftIsCreatedByMe'
|
|
27
|
-
],
|
|
28
|
+
DRAFT_COLUMNS_MAP: objectify(DRAFT_COLUMNS),
|
|
29
|
+
DRAFT_COLUMNS_UNION_MAP: objectify(DRAFT_COLUMNS_UNION),
|
|
30
|
+
DRAFT_COLUMNS_ADMIN_MAP: objectify(DRAFT_COLUMNS_ADMIN),
|
|
28
31
|
DRAFT_COLUMNS_CQN: [
|
|
29
32
|
{ ref: ['IsActiveEntity'], cast: { type: 'cds.Boolean' } },
|
|
30
33
|
{ ref: ['HasActiveEntity'], cast: { type: 'cds.Boolean' } },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ALLOWED_PROPERTIES_MAP: { code: 1, message: 1, target: 1, details: 1, innererror: 1 },
|
|
3
|
+
ADDITIONAL_MSG_PROPERTIES_MAP: { numericSeverity: 1, longtextUrl: 1, transition: 1 },
|
|
4
4
|
/*
|
|
5
5
|
* severities:
|
|
6
6
|
* 1: success
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
* Error responses MAY contain annotations in any of its JSON objects.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const cds = require('../../cds')
|
|
10
|
-
|
|
11
9
|
let _i18n
|
|
12
10
|
const i18n = (...args) => {
|
|
13
11
|
if (!_i18n) _i18n = require('../i18n')
|
|
@@ -15,12 +13,13 @@ const i18n = (...args) => {
|
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
const {
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
ALLOWED_PROPERTIES_MAP,
|
|
17
|
+
ADDITIONAL_MSG_PROPERTIES_MAP,
|
|
20
18
|
DEFAULT_SEVERITY,
|
|
21
19
|
MIN_SEVERITY,
|
|
22
20
|
MAX_SEVERITY
|
|
23
21
|
} = require('./constants')
|
|
22
|
+
const ADDITIONAL_MSG_PROPERTIES = Object.keys(ADDITIONAL_MSG_PROPERTIES_MAP)
|
|
24
23
|
|
|
25
24
|
const SKIP_SANITIZATION = '@cds.skip_sanitization'
|
|
26
25
|
|
|
@@ -32,7 +31,7 @@ const _getFiltered = err => {
|
|
|
32
31
|
.forEach(k => {
|
|
33
32
|
// REVISIT: do not remove innererror with cds^6
|
|
34
33
|
if (k === 'innererror' && process.env.NODE_ENV === 'production' && !err[SKIP_SANITIZATION]) return
|
|
35
|
-
if (
|
|
34
|
+
if (k in ALLOWED_PROPERTIES_MAP || k.startsWith('@')) {
|
|
36
35
|
error[k] = err[k]
|
|
37
36
|
} else if (k === 'numericSeverity') {
|
|
38
37
|
error['@Common.numericSeverity'] = err[k]
|
|
@@ -84,10 +83,7 @@ const _isAllowedError = errorCode => {
|
|
|
84
83
|
return errorCode >= 300 && errorCode < 505
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
const
|
|
88
|
-
// if no authentication used, we create a new user object in order to get the correct locale
|
|
89
|
-
return Object.defineProperty(new cds.User(), '_req', { enumerable: false, value: req })
|
|
90
|
-
}
|
|
86
|
+
const localeFrom = require('../../../../lib/req/locale')
|
|
91
87
|
|
|
92
88
|
// - for one unique value, we use it
|
|
93
89
|
// - if at least one 5xx exists, we use 500
|
|
@@ -104,9 +100,7 @@ const _statusCodeFromDetails = details => {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
const normalizeError = (err, req) => {
|
|
107
|
-
const
|
|
108
|
-
const locale = user.locale
|
|
109
|
-
|
|
103
|
+
const locale = req.locale || (req.locale = localeFrom(req))
|
|
110
104
|
const error = _normalize(err, locale)
|
|
111
105
|
|
|
112
106
|
// derive status code from err status OR root code OR matching detail codes
|
|
@@ -160,9 +154,7 @@ const _normalizeMessage = (message, locale) => {
|
|
|
160
154
|
}
|
|
161
155
|
|
|
162
156
|
const getSapMessages = (messages, req) => {
|
|
163
|
-
const
|
|
164
|
-
const locale = user.locale
|
|
165
|
-
|
|
157
|
+
const locale = req.locale || (req.locale = localeFrom(req))
|
|
166
158
|
const s = JSON.stringify(messages.map(message => _normalizeMessage(message, locale)))
|
|
167
159
|
// convert non ascii to unicode
|
|
168
160
|
return s.replace(/[\u007F-\uFFFF]/g, chr => {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { cqnFrom } = require('./utils')
|
|
2
|
+
const { RESTRICTIONS } = require('./constants')
|
|
3
|
+
|
|
4
|
+
const _isRestricted = (req, capability, capabilityReadByKey) => {
|
|
5
|
+
if (capabilityReadByKey !== undefined && req.query.SELECT.one) {
|
|
6
|
+
return capabilityReadByKey === false
|
|
7
|
+
}
|
|
8
|
+
return capability === false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const _isNavigationRestricted = (target, path, annotation, req) => {
|
|
12
|
+
if (!target) return
|
|
13
|
+
const parts = annotation.split('.')
|
|
14
|
+
if (target && Array.isArray(target['@Capabilities.NavigationRestrictions.RestrictedProperties'])) {
|
|
15
|
+
for (const r of target['@Capabilities.NavigationRestrictions.RestrictedProperties']) {
|
|
16
|
+
if (r.NavigationProperty['='] === path && r[parts[0]]) {
|
|
17
|
+
return _isRestricted(
|
|
18
|
+
req,
|
|
19
|
+
r[parts[0]][parts[1]],
|
|
20
|
+
r.ReadRestrictions && r.ReadRestrictions['ReadByKeyRestrictions.Readable']
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _localName = entity => entity.name.replace(entity._service.name + '.', '')
|
|
28
|
+
|
|
29
|
+
function handler(req) {
|
|
30
|
+
// TODO: Determine auth-relevant entity
|
|
31
|
+
const annotation = RESTRICTIONS[req.event]
|
|
32
|
+
if (!req.target || !annotation) return
|
|
33
|
+
|
|
34
|
+
const action = annotation.split('.').pop().toUpperCase()
|
|
35
|
+
const from = cqnFrom(req)
|
|
36
|
+
const nav = (from && from.ref && from.ref.map(el => el.id || el)) || []
|
|
37
|
+
|
|
38
|
+
if (nav.length > 1) {
|
|
39
|
+
const path = nav.slice(1).join('.')
|
|
40
|
+
const target = this.model.definitions[nav[0]]
|
|
41
|
+
if (_isNavigationRestricted(target, path, annotation, req)) {
|
|
42
|
+
// REVISIT: rework exception with using target
|
|
43
|
+
const trgt = `${_localName(target)}.${path}`
|
|
44
|
+
req.reject(405, 'ENTITY_IS_NOT_CRUD_VIA_NAVIGATION', [_localName(req.target), action, trgt])
|
|
45
|
+
}
|
|
46
|
+
} else if (
|
|
47
|
+
_isRestricted(
|
|
48
|
+
req,
|
|
49
|
+
req.target['@Capabilities.' + annotation],
|
|
50
|
+
req.target['@Capabilities.' + RESTRICTIONS.READABLE_BY_KEY]
|
|
51
|
+
)
|
|
52
|
+
) {
|
|
53
|
+
req.reject(405, 'ENTITY_IS_NOT_CRUD', [_localName(req.target), action])
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handler._initial = true
|
|
58
|
+
|
|
59
|
+
module.exports = handler
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const MOD_EVENTS = { UPDATE: 1, DELETE: 1, EDIT: 1 }
|
|
2
|
+
const WRITE_EVENTS = Object.assign({ CREATE: 1, NEW: 1, PATCH: 1, CANCEL: 1 }, MOD_EVENTS)
|
|
3
|
+
const CRUD_EVENTS = Object.assign({ READ: 1 }, WRITE_EVENTS)
|
|
4
|
+
const DRAFT_EVENTS = { PATCH: 1, CANCEL: 1, draftActivate: 1, draftPrepare: 1 }
|
|
5
|
+
|
|
6
|
+
const RESTRICTIONS = {
|
|
7
|
+
CREATE: 'InsertRestrictions.Insertable',
|
|
8
|
+
READ: 'ReadRestrictions.Readable',
|
|
9
|
+
READABLE_BY_KEY: 'ReadRestrictions.ReadByKeyRestrictions.Readable',
|
|
10
|
+
UPDATE: 'UpdateRestrictions.Updatable',
|
|
11
|
+
DELETE: 'DeleteRestrictions.Deletable'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
MOD_EVENTS,
|
|
16
|
+
WRITE_EVENTS,
|
|
17
|
+
CRUD_EVENTS,
|
|
18
|
+
DRAFT_EVENTS,
|
|
19
|
+
RESTRICTIONS
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
|
|
3
|
+
const { rewriteExpandAsterisk } = require('../../utils/rewriteAsterisks')
|
|
4
|
+
|
|
5
|
+
const { ensureNoDraftsSuffix } = require('../../../fiori/utils/handler')
|
|
6
|
+
|
|
7
|
+
const _getTarget = (ref, target, definitions) => {
|
|
8
|
+
if (cds.env.effective.odata.proxies) {
|
|
9
|
+
const target_ = target.elements[ref[0]]
|
|
10
|
+
if (ref.length === 1) return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
11
|
+
return _getTarget(ref.slice(1), target_, definitions)
|
|
12
|
+
}
|
|
13
|
+
const target_ = target.elements[ref.join('_')]
|
|
14
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
18
|
+
if (!columns || !target || columns === '*') return
|
|
19
|
+
|
|
20
|
+
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
21
|
+
const restrictions = annotation && annotation.map(element => element['='])
|
|
22
|
+
|
|
23
|
+
rewriteExpandAsterisk(columns, target)
|
|
24
|
+
|
|
25
|
+
for (const col of columns) {
|
|
26
|
+
if (col.expand) {
|
|
27
|
+
if (restrictions && restrictions.length !== 0) {
|
|
28
|
+
const ref = col.ref.join('_')
|
|
29
|
+
const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
|
|
30
|
+
if (ref_) return ref_
|
|
31
|
+
}
|
|
32
|
+
// expand: '**' or '*3' is only possible within custom handler, no check needed
|
|
33
|
+
if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
|
|
34
|
+
continue
|
|
35
|
+
} else {
|
|
36
|
+
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
37
|
+
if (restricted) return restricted
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handler(req) {
|
|
44
|
+
const restricted = _getRestrictedExpand(
|
|
45
|
+
req.query.SELECT && req.query.SELECT.columns,
|
|
46
|
+
req.target,
|
|
47
|
+
this.model.definitions
|
|
48
|
+
)
|
|
49
|
+
if (restricted) req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
handler._initial = true
|
|
53
|
+
|
|
54
|
+
module.exports = handler
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
|
|
3
|
+
const requiresHandler = require('./requires')
|
|
4
|
+
const readOnlyHandler = require('./readOnly')
|
|
5
|
+
const insertOnlyHandler = require('./insertOnly')
|
|
6
|
+
const capabilitiesHandler = require('./capabilities')
|
|
7
|
+
const restrictHandler = require('./restrict')
|
|
8
|
+
const restrictExpandHandler = require('./expand')
|
|
9
|
+
|
|
10
|
+
module.exports = cds.service.impl(function authorization() {
|
|
11
|
+
/*
|
|
12
|
+
* @requires
|
|
13
|
+
*/
|
|
14
|
+
this.before('*', requiresHandler)
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* access control (cheaper than @restrict -> do first)
|
|
18
|
+
*/
|
|
19
|
+
this.before('*', readOnlyHandler)
|
|
20
|
+
this.before('*', insertOnlyHandler)
|
|
21
|
+
this.before('*', capabilitiesHandler)
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* @restrict
|
|
25
|
+
*/
|
|
26
|
+
this.before('*', restrictHandler)
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* expand restrictions
|
|
30
|
+
*/
|
|
31
|
+
this.before('READ', '*', restrictExpandHandler)
|
|
32
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { getAuthRelevantEntity } = require('./utils')
|
|
2
|
+
|
|
3
|
+
function handler(req) {
|
|
4
|
+
const entity = getAuthRelevantEntity(req, this.model, ['@insertonly'])
|
|
5
|
+
if (!entity || !entity['@insertonly']) return
|
|
6
|
+
|
|
7
|
+
const allowed = entity._isDraftEnabled ? { NEW: 1, PATCH: 1 } : { CREATE: 1 }
|
|
8
|
+
if (!(req.event in allowed)) {
|
|
9
|
+
req.reject(405, 'ENTITY_IS_INSERT_ONLY', [entity.name])
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
handler._initial = true
|
|
14
|
+
|
|
15
|
+
module.exports = handler
|