@sap/cds 5.5.4 → 5.6.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 +138 -1
- package/apis/services.d.ts +27 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +4 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +33 -5
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/serve/Service-methods.js +1 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/update.js +6 -1
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +15 -13
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/arrayed.js +13 -28
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/db/utils/deep.js +5 -7
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +3 -3
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +17 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +20 -4
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +29 -7
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -3,6 +3,13 @@ const LOG = cds.log('audit-log')
|
|
|
3
3
|
|
|
4
4
|
const v2utils = require('./utils/v2')
|
|
5
5
|
|
|
6
|
+
const ANONYMOUS = 'anonymous'
|
|
7
|
+
|
|
8
|
+
const _getTenantAndUser = () => ({
|
|
9
|
+
user: (cds.context && cds.context.user && cds.context.user.id) || ANONYMOUS,
|
|
10
|
+
tenant: (cds.context && cds.context.tenant) || ANONYMOUS
|
|
11
|
+
})
|
|
12
|
+
|
|
6
13
|
module.exports = class AuditLogService extends cds.MessagingService {
|
|
7
14
|
async init() {
|
|
8
15
|
// call MessagingService's init, which handles outboxing
|
|
@@ -13,7 +20,8 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
13
20
|
this.ready = !!this.alc
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
async emit(
|
|
23
|
+
async emit(first, second) {
|
|
24
|
+
const { event, data } = typeof first === 'object' ? first : { event: first, data: second }
|
|
17
25
|
if (!this.options.outbox) return this.send(event, data)
|
|
18
26
|
|
|
19
27
|
if (this.ready && this[event]) {
|
|
@@ -33,10 +41,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
33
41
|
async dataAccessLog({ accesses }) {
|
|
34
42
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
35
43
|
|
|
36
|
-
const {
|
|
37
|
-
tenant,
|
|
38
|
-
user: { id: user }
|
|
39
|
-
} = cds.context
|
|
44
|
+
const { tenant, user } = _getTenantAndUser()
|
|
40
45
|
|
|
41
46
|
// build the logs
|
|
42
47
|
const { entries, errors } = v2utils.buildDataAccessLogs(this.alc, accesses, tenant, user)
|
|
@@ -59,10 +64,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
59
64
|
async dataModificationLog({ modifications }) {
|
|
60
65
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
61
66
|
|
|
62
|
-
const {
|
|
63
|
-
tenant,
|
|
64
|
-
user: { id: user }
|
|
65
|
-
} = cds.context
|
|
67
|
+
const { tenant, user } = _getTenantAndUser()
|
|
66
68
|
|
|
67
69
|
// build the logs
|
|
68
70
|
const { entries, errors } = v2utils.buildDataModificationLogs(this.alc, modifications, tenant, user)
|
|
@@ -87,8 +89,9 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
87
89
|
// cds.context not always set on auth-related errors -> try to extract from data
|
|
88
90
|
let user, tenant
|
|
89
91
|
if (cds.context) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
const tenantAndUser = _getTenantAndUser()
|
|
93
|
+
tenant = tenantAndUser.tenant
|
|
94
|
+
user = tenantAndUser.user
|
|
92
95
|
} else {
|
|
93
96
|
try {
|
|
94
97
|
const parsed = JSON.parse(data)
|
|
@@ -101,10 +104,10 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
101
104
|
delete parsed.user
|
|
102
105
|
}
|
|
103
106
|
data = JSON.stringify(parsed)
|
|
104
|
-
} catch (e) {
|
|
105
|
-
// ignore
|
|
106
|
-
}
|
|
107
|
+
} catch (e) {}
|
|
107
108
|
}
|
|
109
|
+
if (!tenant) tenant = ANONYMOUS
|
|
110
|
+
if (!user) user = ANONYMOUS
|
|
108
111
|
|
|
109
112
|
// build the log
|
|
110
113
|
const entry = v2utils.buildSecurityLog(this.alc, action, data, tenant, user)
|
|
@@ -117,10 +120,7 @@ module.exports = class AuditLogService extends cds.MessagingService {
|
|
|
117
120
|
async configChangeLog({ action, success, configurations }) {
|
|
118
121
|
if (!this.ready) throw new Error('AuditLogService not connected')
|
|
119
122
|
|
|
120
|
-
const {
|
|
121
|
-
tenant,
|
|
122
|
-
user: { id: user }
|
|
123
|
-
} = cds.context
|
|
123
|
+
const { tenant, user } = _getTenantAndUser()
|
|
124
124
|
|
|
125
125
|
// build the logs
|
|
126
126
|
const { entries, errors } = v2utils.buildConfigChangeLogs(this.alc, configurations, tenant, user)
|
|
@@ -40,7 +40,7 @@ const _processorFnAccess = (accessLogs, model, req) => {
|
|
|
40
40
|
element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
|
|
41
41
|
accessLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
|
|
42
42
|
) {
|
|
43
|
-
addDataSubjectForDetailsEntity(row, accessLog, req, entity, model)
|
|
43
|
+
addDataSubjectForDetailsEntity(row, accessLog, req, entity, model, element)
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -41,7 +41,8 @@ const _getOldAndNew = (action, row, key) => {
|
|
|
41
41
|
const _addAttribute = (log, action, row, key) => {
|
|
42
42
|
if (!log.attributes.find(ele => ele.name === key)) {
|
|
43
43
|
const { oldValue, newValue } = _getOldAndNew(action, row, key)
|
|
44
|
-
if (oldValue !== newValue)
|
|
44
|
+
if (oldValue !== newValue)
|
|
45
|
+
log.attributes.push({ name: key, oldValue: String(oldValue), newValue: String(newValue) })
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -75,7 +76,7 @@ const _processorFnModification = (modificationLogs, model, req, beforeWrite) =>
|
|
|
75
76
|
element.parent['@PersonalData.EntitySemantics'] === 'DataSubjectDetails' &&
|
|
76
77
|
modificationLog.dataSubject.id.length === 0 // > id still an array -> promise not yet set
|
|
77
78
|
) {
|
|
78
|
-
addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model)
|
|
79
|
+
addDataSubjectForDetailsEntity(row, modificationLog, req, entity, model, element)
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
2
|
|
|
3
3
|
const { getDataSubject } = require('../../../common/utils/csn')
|
|
4
|
-
const { getBackLinks, isSelfManaged } = require('../../../common/utils/backlinks')
|
|
5
4
|
|
|
6
5
|
const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
7
6
|
const ASPECTS = { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpdate', DELETE: '_auditDelete' }
|
|
@@ -23,8 +22,11 @@ const getPick = event => {
|
|
|
23
22
|
const categories = []
|
|
24
23
|
if (!element.isAssociation && element.key) categories.push('ObjectID')
|
|
25
24
|
if (
|
|
26
|
-
element
|
|
27
|
-
element
|
|
25
|
+
element['@PersonalData.FieldSemantics'] === 'DataSubjectID' &&
|
|
26
|
+
// item element of arrayed element has no parent, but
|
|
27
|
+
// at the moment annotation on item level is not supported
|
|
28
|
+
element.parent &&
|
|
29
|
+
element.parent['@PersonalData.EntitySemantics'] === 'DataSubject'
|
|
28
30
|
)
|
|
29
31
|
categories.push('DataSubjectID')
|
|
30
32
|
if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
|
|
@@ -55,14 +57,15 @@ const createLogEntry = (logs, entity, row) => {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const addObjectID = (log, row, key) => {
|
|
58
|
-
if (!log.dataObject.id.find(ele => ele.keyName === key))
|
|
60
|
+
if (!log.dataObject.id.find(ele => ele.keyName === key))
|
|
61
|
+
log.dataObject.id.push({ keyName: key, value: String(row[key]) })
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
const addDataSubject = (log, row, key, entity) => {
|
|
62
65
|
if (!log.dataSubject.type) log.dataSubject.type = entity.name
|
|
63
66
|
if (!log.dataSubject.id.find(ele => ele.key === key)) {
|
|
64
67
|
const value = row[key] || (row._old && row._old[key])
|
|
65
|
-
log.dataSubject.id.push({ keyName: key, value })
|
|
68
|
+
log.dataSubject.id.push({ keyName: key, value: String(value) })
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -77,82 +80,39 @@ const _addKeysToWhere = (child, row) => {
|
|
|
77
80
|
return keysWithValue
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const backlinks = getBackLinks(assoc)
|
|
83
|
-
let parentName, childName
|
|
84
|
-
if (assoc.isComposition && assoc.is2one && !assoc.on) {
|
|
85
|
-
// look for foreign keys in parent
|
|
86
|
-
parentName = parent.name
|
|
87
|
-
childName = child.name
|
|
88
|
-
} else if (assoc.is2many && isSelfManaged(assoc)) {
|
|
89
|
-
// look for foreign keys in child
|
|
90
|
-
parentName = child.name
|
|
91
|
-
childName = parent.name
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
backlinks.forEach(el => {
|
|
95
|
-
if (foreignKeys.length > 0) foreignKeys.push('and')
|
|
96
|
-
foreignKeys.push({ ref: [parentName, el.entityKey] }, '=', {
|
|
97
|
-
ref: [childName, el.targetKey]
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
return foreignKeys
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const _buildWhere = (child, dataSubjectInfo, row) => {
|
|
104
|
-
const where = []
|
|
105
|
-
where.push(
|
|
106
|
-
..._addForeignKeysToWhere(dataSubjectInfo.entity, child, dataSubjectInfo.assoc),
|
|
107
|
-
'and',
|
|
108
|
-
..._addKeysToWhere(child, row)
|
|
109
|
-
)
|
|
110
|
-
return where
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const _buildSubSelect = (child, dataSubjectInfo, row) => {
|
|
114
|
-
let previousCqn
|
|
83
|
+
const _buildSubSelect = (model, child, { element, up }, row, previousCqn) => {
|
|
84
|
+
const entity = element.parent
|
|
115
85
|
const childCqn = SELECT.from(child.name)
|
|
116
86
|
.columns(Object.keys(child.keys))
|
|
117
|
-
.where(
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const nextEl = dataSubjectInfo.previous[i + 1]
|
|
123
|
-
const args = nextEl
|
|
124
|
-
? [nextEl.entity, el.entity, nextEl.assoc]
|
|
125
|
-
: [dataSubjectInfo.entity, el.entity, dataSubjectInfo.assoc]
|
|
126
|
-
|
|
127
|
-
currentCQN = SELECT.from(el.entity.name)
|
|
128
|
-
.columns(Object.keys(el.entity.keys))
|
|
129
|
-
.where([..._addForeignKeysToWhere(...args), 'and', 'exists', previousCqn || childCqn])
|
|
130
|
-
|
|
131
|
-
previousCqn = currentCQN
|
|
132
|
-
})
|
|
87
|
+
.where(entity._relations[element.name].join(child.name, entity.name))
|
|
88
|
+
if (previousCqn) {
|
|
89
|
+
childCqn.where('exists', previousCqn)
|
|
90
|
+
} else {
|
|
91
|
+
childCqn.where(_addKeysToWhere(child, row))
|
|
133
92
|
}
|
|
134
|
-
return
|
|
93
|
+
if (up) return _buildSubSelect(model, entity, up, {}, childCqn)
|
|
94
|
+
return childCqn
|
|
135
95
|
}
|
|
136
96
|
|
|
137
|
-
const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req) => {
|
|
97
|
+
const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req, model) => {
|
|
138
98
|
const root = dataSubjectInfo.entity
|
|
139
99
|
const cqn = SELECT.from(root.name)
|
|
140
100
|
.columns(Object.keys(root.keys))
|
|
141
|
-
.where(['exists', _buildSubSelect(child, dataSubjectInfo, row)])
|
|
101
|
+
.where(['exists', _buildSubSelect(model, child, dataSubjectInfo.up, row)])
|
|
142
102
|
return cds
|
|
143
103
|
.tx(req)
|
|
144
104
|
.run(cqn)
|
|
145
105
|
.then(res => {
|
|
146
106
|
const id = []
|
|
147
|
-
for (const k in res[0]) id.push({ keyName: k, value: res[0][k] })
|
|
107
|
+
for (const k in res[0]) id.push({ keyName: k, value: String(res[0][k]) })
|
|
148
108
|
return id
|
|
149
109
|
})
|
|
150
110
|
}
|
|
151
111
|
|
|
152
|
-
const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
112
|
+
const addDataSubjectForDetailsEntity = (row, log, req, entity, model, element) => {
|
|
153
113
|
const role = entity['@PersonalData.DataSubjectRole']
|
|
154
114
|
|
|
155
|
-
const dataSubjectInfo = getDataSubject(entity, model, role)
|
|
115
|
+
const dataSubjectInfo = getDataSubject(entity, model, role, element)
|
|
156
116
|
|
|
157
117
|
log.dataSubject.type = dataSubjectInfo.entity.name
|
|
158
118
|
|
|
@@ -165,7 +125,7 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
|
165
125
|
if (!req.context._audit.dataSubjects.has(mapKey)) req.context._audit.dataSubjects.set(mapKey, new Map())
|
|
166
126
|
const map = req.context._audit.dataSubjects.get(mapKey)
|
|
167
127
|
if (map.has(role)) log.dataSubject.id = map.get(role)
|
|
168
|
-
else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req))
|
|
128
|
+
else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req, model))
|
|
169
129
|
}
|
|
170
130
|
|
|
171
131
|
const resolveDataSubjectPromises = async logs => {
|
|
@@ -3,6 +3,8 @@ const LOG = cds.log('odata')
|
|
|
3
3
|
|
|
4
4
|
const OData = require('./OData')
|
|
5
5
|
|
|
6
|
+
const { alias2ref } = require('../../../common/utils/csn')
|
|
7
|
+
|
|
6
8
|
function _createNewService(name, csn, defaultOptions) {
|
|
7
9
|
const reflectedModel = cds.linked(cds.compile.for.odata(csn))
|
|
8
10
|
const options = Object.assign({}, defaultOptions, { reflectedModel })
|
|
@@ -13,6 +15,8 @@ function _createNewService(name, csn, defaultOptions) {
|
|
|
13
15
|
service._isExtended = true
|
|
14
16
|
|
|
15
17
|
const edm = cds.compile.to.edm(csn, { service: name })
|
|
18
|
+
alias2ref(service, edm)
|
|
19
|
+
|
|
16
20
|
const odataService = new OData(edm, csn, options)
|
|
17
21
|
odataService.addCDSServiceToChannel(service)
|
|
18
22
|
|
|
@@ -159,6 +159,7 @@ class OData {
|
|
|
159
159
|
|
|
160
160
|
this._odataService.on(ATOMICITY_GROUP_START, (odataContext, done) => {
|
|
161
161
|
const data = odataContext.applicationData
|
|
162
|
+
|
|
162
163
|
// start tx
|
|
163
164
|
const txs = (data.txs = data.txs || {})
|
|
164
165
|
const {
|
|
@@ -179,28 +180,14 @@ class OData {
|
|
|
179
180
|
this._odataService.on(ATOMICITY_GROUP_END, async (odataErr, odataContext, done) => {
|
|
180
181
|
const tx = odataContext.applicationData.txs[odataContext.id]
|
|
181
182
|
let errors = odataErr || odataContext.failedRequests.length > 0
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await tx.commit(odataContext.applicationData.results[odataContext.id])
|
|
185
|
-
done()
|
|
186
|
-
} catch (e) {
|
|
187
|
-
// tx gets rolled back automatically
|
|
188
|
-
// set error on each request of changeset, if commit failed
|
|
189
|
-
const changesetResults = odataContext.applicationData.results[odataContext.id]
|
|
190
|
-
const failedRequests = changesetResults.reduce((obj, resultEntry) => {
|
|
191
|
-
const requestId = resultEntry.req._.odataReq.getOdataRequestId()
|
|
192
|
-
const { error, statusCode } = normalizeError(e, resultEntry.req)
|
|
193
|
-
obj[requestId] = Object.assign(error, { statusCode })
|
|
194
|
-
return obj
|
|
195
|
-
}, {})
|
|
196
|
-
done(e, { failedRequests })
|
|
197
|
-
}
|
|
198
|
-
} else {
|
|
183
|
+
|
|
184
|
+
if (errors) {
|
|
199
185
|
// rollback without errors to not trigger srv.on('error') with array
|
|
200
186
|
await tx.rollback()
|
|
201
187
|
// invoke srv.on('error') for each error and build failedRequests that reflects error modifications
|
|
202
188
|
errors = odataContext.applicationData.errors[odataContext.id]
|
|
203
189
|
const failedRequests = {}
|
|
190
|
+
|
|
204
191
|
for (const e of errors) {
|
|
205
192
|
const { error: err, req } = e
|
|
206
193
|
for (const each of cdsService._handlers._error) each.handler.call(cdsService, err, req)
|
|
@@ -208,7 +195,26 @@ class OData {
|
|
|
208
195
|
const { error, statusCode } = normalizeError(err, req)
|
|
209
196
|
failedRequests[requestId] = Object.assign(error, { statusCode })
|
|
210
197
|
}
|
|
198
|
+
|
|
211
199
|
done(new Error(`Atomicity group "${odataContext.id}" failed`), { failedRequests })
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await tx.commit(odataContext.applicationData.results[odataContext.id])
|
|
205
|
+
done()
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// tx gets rolled back automatically
|
|
208
|
+
// set error on each request of changeset, if commit failed
|
|
209
|
+
const changesetResults = odataContext.applicationData.results[odataContext.id]
|
|
210
|
+
const failedRequests = changesetResults.reduce((obj, resultEntry) => {
|
|
211
|
+
const requestId = resultEntry.req._.odataReq.getOdataRequestId()
|
|
212
|
+
const { error, statusCode } = normalizeError(e, resultEntry.req)
|
|
213
|
+
obj[requestId] = Object.assign(error, { statusCode })
|
|
214
|
+
return obj
|
|
215
|
+
}, {})
|
|
216
|
+
|
|
217
|
+
done(e, { failedRequests })
|
|
212
218
|
}
|
|
213
219
|
})
|
|
214
220
|
|
|
@@ -233,37 +239,33 @@ class OData {
|
|
|
233
239
|
/**
|
|
234
240
|
* Process request.
|
|
235
241
|
*
|
|
236
|
-
* @param req
|
|
237
|
-
* @param res
|
|
242
|
+
* @param {http.IncomingMessage} req
|
|
243
|
+
* @param {http.ServerResponse} res
|
|
238
244
|
* @private
|
|
239
245
|
*/
|
|
240
246
|
// REVISIT: Remove this when we replaced Okra
|
|
241
247
|
process(req, res) {
|
|
248
|
+
const headers = req.headers
|
|
249
|
+
const acceptHeader = headers && headers.accept
|
|
250
|
+
|
|
242
251
|
// default to combination [...];IEEE754Compatible=true;ExponentialDecimals=true if one is omitted
|
|
243
|
-
if (
|
|
244
|
-
if (
|
|
245
|
-
req.headers.accept.includes('IEEE754Compatible=true') &&
|
|
246
|
-
!req.headers.accept.includes('ExponentialDecimals')
|
|
247
|
-
) {
|
|
252
|
+
if (acceptHeader && acceptHeader.startsWith('application/json')) {
|
|
253
|
+
if (acceptHeader.includes('IEEE754Compatible=true') && !acceptHeader.includes('ExponentialDecimals')) {
|
|
248
254
|
req.headers.accept += ';ExponentialDecimals=true'
|
|
249
|
-
} else if (
|
|
250
|
-
req.headers.accept.includes('ExponentialDecimals=true') &&
|
|
251
|
-
!req.headers.accept.includes('IEEE754Compatible')
|
|
252
|
-
) {
|
|
255
|
+
} else if (acceptHeader.includes('ExponentialDecimals=true') && !acceptHeader.includes('IEEE754Compatible')) {
|
|
253
256
|
req.headers.accept += ';IEEE754Compatible=true'
|
|
254
257
|
}
|
|
255
258
|
|
|
259
|
+
const contentType = headers['content-type']
|
|
260
|
+
|
|
256
261
|
// add IEEE754Compatible=true if !strict_numbers
|
|
257
262
|
if (
|
|
258
263
|
!cds.env.features.strict_numbers &&
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
!
|
|
264
|
+
contentType &&
|
|
265
|
+
contentType.includes('application/json') &&
|
|
266
|
+
!contentType.includes('IEEE754Compatible')
|
|
262
267
|
) {
|
|
263
|
-
req.headers['content-type'] =
|
|
264
|
-
'application/json',
|
|
265
|
-
'application/json;IEEE754Compatible=true'
|
|
266
|
-
)
|
|
268
|
+
req.headers['content-type'] = contentType.replace('application/json', 'application/json;IEEE754Compatible=true')
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
|
|
@@ -9,7 +9,7 @@ const { getSapMessages } = require('../../../../common/error/frontend')
|
|
|
9
9
|
const { validateResourcePath } = require('../utils/request')
|
|
10
10
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
11
11
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
12
|
-
const { toODataResult, postProcess } = require('../utils/result')
|
|
12
|
+
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
13
13
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -44,6 +44,8 @@ const create = service => {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
postProcess(req, odataRes, service, result)
|
|
47
|
+
} else {
|
|
48
|
+
postProcessMinimal(req, result)
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (changeset) {
|
|
@@ -93,6 +93,11 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
93
93
|
req = odataReq.getIncomingRequest()
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
if (err.getRootCause && typeof err.getRootCause === 'function') {
|
|
97
|
+
// > an OKRA error
|
|
98
|
+
err = _betterOkraError(err)
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
97
102
|
// REVISIT: if for compat reasons, remove once cds^5.1
|
|
98
103
|
if (srv._handlers._error) {
|
|
@@ -107,11 +112,6 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
|
|
110
|
-
if (err.getRootCause && typeof err.getRootCause === 'function') {
|
|
111
|
-
// > an OKRA error
|
|
112
|
-
err = _betterOkraError(err)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
115
|
// add content id if not generated by okra ("~...")
|
|
116
116
|
const contentId = odataReq.getOdataRequestId()
|
|
117
117
|
if (contentId && !contentId.match(/^~/)) err['@Core.ContentID'] = contentId
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
const ODataRequest = require('../ODataRequest')
|
|
4
|
+
const { rewriteExpandAsterisk } = require('../../../../common/utils/rewriteAsterisks')
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
QueryOptions,
|
|
@@ -78,7 +79,7 @@ const _getCount = async (tx, readReq) => {
|
|
|
78
79
|
|
|
79
80
|
// Copy CQN including from and where and changing columns
|
|
80
81
|
const select = SELECT.from(readReq.query.SELECT.from)
|
|
81
|
-
select.SELECT.columns = [{ func: 'count', args: [{ val: '1' }], as: '
|
|
82
|
+
select.SELECT.columns = [{ func: 'count', args: [{ val: '1' }], as: '$count' }]
|
|
82
83
|
|
|
83
84
|
if (readReq.query.SELECT.where) select.SELECT.where = readReq.query.SELECT.where
|
|
84
85
|
if (readReq.query.SELECT.search) select.SELECT.search = readReq.query.SELECT.search
|
|
@@ -92,13 +93,13 @@ const _getCount = async (tx, readReq) => {
|
|
|
92
93
|
|
|
93
94
|
// Define new CQN
|
|
94
95
|
req.query = select
|
|
96
|
+
// todo check limit
|
|
97
|
+
const result = await tx.dispatch(req)
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
const count = (result[0] && (result[0].$count || result[0]._counted_)) || 0
|
|
97
100
|
|
|
98
101
|
// Transform into scalar result
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return toODataResult(result)
|
|
102
|
+
return toODataResult(count)
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/**
|
|
@@ -318,6 +319,7 @@ const _readStream = async (tx, req, segments) => {
|
|
|
318
319
|
if (
|
|
319
320
|
headers &&
|
|
320
321
|
headers.accept &&
|
|
322
|
+
contentType &&
|
|
321
323
|
!headers.accept.includes('*/*') &&
|
|
322
324
|
!headers.accept.includes(contentType) &&
|
|
323
325
|
!headers.accept.includes(contentType.split('/')[0] + '/*')
|
|
@@ -327,7 +329,7 @@ const _readStream = async (tx, req, segments) => {
|
|
|
327
329
|
|
|
328
330
|
if (contentType) streamObj['*@odata.mediaContentType'] = contentType
|
|
329
331
|
if (contentDisposition) {
|
|
330
|
-
req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${contentDisposition}"`)
|
|
332
|
+
req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(contentDisposition)}"`)
|
|
331
333
|
}
|
|
332
334
|
return streamObj
|
|
333
335
|
}
|
|
@@ -381,6 +383,7 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
381
383
|
break
|
|
382
384
|
}
|
|
383
385
|
}
|
|
386
|
+
|
|
384
387
|
return _readStream(tx, req, segments)
|
|
385
388
|
}
|
|
386
389
|
|
|
@@ -431,11 +434,13 @@ const _getTarget = (ref, target, definitions) => {
|
|
|
431
434
|
}
|
|
432
435
|
|
|
433
436
|
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
434
|
-
if (!columns || !target) return
|
|
437
|
+
if (!columns || !target || columns === '*') return
|
|
435
438
|
|
|
436
439
|
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
437
440
|
const restrictions = annotation && annotation.map(element => element['='])
|
|
438
441
|
|
|
442
|
+
rewriteExpandAsterisk(columns, target)
|
|
443
|
+
|
|
439
444
|
for (const col of columns) {
|
|
440
445
|
if (col.expand) {
|
|
441
446
|
if (restrictions && restrictions.length !== 0) {
|
|
@@ -473,6 +478,7 @@ const read = service => {
|
|
|
473
478
|
return next(e)
|
|
474
479
|
}
|
|
475
480
|
|
|
481
|
+
// REVISIT: this should be in common/generic/auth.js with the rest of the access control stuff
|
|
476
482
|
const restricted = _getRestrictedExpand(
|
|
477
483
|
req.query.SELECT && req.query.SELECT.columns,
|
|
478
484
|
req.target,
|