@sap/cds 6.8.3 → 7.0.0
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 +61 -2
- package/README.md +0 -1
- package/bin/cds-serve.js +50 -3
- package/bin/deploy/to-hana.js +1 -0
- package/bin/serve.js +16 -20
- package/lib/auth/basic-auth.js +6 -4
- package/lib/auth/index.js +4 -3
- package/lib/auth/jwt-auth.js +2 -5
- package/lib/compile/cds-compile.js +34 -89
- package/lib/compile/cdsc.js +11 -0
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/lean_drafts.js +36 -69
- package/lib/compile/for/nodejs.js +2 -1
- package/lib/compile/load.js +1 -1
- package/lib/compile/minify.js +2 -0
- package/lib/compile/to/csn.js +74 -0
- package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +8 -6
- package/lib/dbs/cds-deploy.js +174 -114
- package/lib/env/cds-env.js +64 -79
- package/lib/env/cds-requires.js +11 -28
- package/lib/env/defaults.js +13 -3
- package/lib/env/plugins.js +1 -12
- package/lib/env/presets.js +25 -21
- package/lib/index.js +121 -147
- package/lib/{core/reflect.js → linked/models.js} +2 -2
- package/lib/{core/infer.js → linked/queries.js} +2 -0
- package/lib/{core/index.js → linked/types.js} +2 -1
- package/lib/log/cds-error.js +13 -7
- package/lib/log/format/cf.js +1 -1
- package/lib/plugins.js +49 -0
- package/lib/ql/Query.js +0 -9
- package/lib/ql/STREAM.js +0 -1
- package/lib/req/context.js +2 -7
- package/lib/req/request.js +6 -2
- package/lib/req/response.js +23 -10
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/middlewares/errors.js +1 -1
- package/lib/srv/protocols/_legacy.js +1 -0
- package/lib/srv/protocols/graphql.js +7 -16
- package/lib/srv/protocols/index.js +59 -45
- package/lib/srv/protocols/odata-v2-proxy.js +2 -70
- package/lib/srv/srv-api.js +9 -3
- package/lib/srv/srv-dispatch.js +12 -9
- package/lib/srv/srv-models.js +4 -21
- package/lib/srv/srv-tx.js +15 -12
- package/lib/utils/cds-test.js +14 -9
- package/lib/utils/cds-utils.js +2 -12
- package/lib/utils/check-version.js +17 -0
- package/{bin/build → lib/utils}/csv-reader.js +23 -24
- package/libx/_runtime/auth/index.js +27 -23
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
- package/libx/_runtime/cds-services/services/Service.js +79 -107
- package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
- package/libx/_runtime/cds-services/util/assert.js +65 -2
- package/libx/_runtime/common/composition/data.js +1 -0
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/restrict.js +5 -10
- package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
- package/libx/_runtime/common/generic/auth/utils.js +1 -2
- package/libx/_runtime/common/generic/crud.js +32 -16
- package/libx/_runtime/common/generic/etag.js +133 -104
- package/libx/_runtime/common/generic/input.js +6 -21
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/stream.js +52 -0
- package/libx/_runtime/common/generic/temporal.js +25 -8
- package/libx/_runtime/common/i18n/messages.properties +0 -2
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/common/utils/csn.js +0 -51
- package/libx/_runtime/common/utils/etag.js +30 -0
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
- package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
- package/libx/_runtime/common/utils/stream.js +140 -0
- package/libx/_runtime/common/utils/streamProp.js +29 -12
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
- package/libx/_runtime/db/generic/index.js +0 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +2 -2
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/query/run.js +2 -2
- package/libx/_runtime/db/query/update.js +2 -2
- package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
- package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
- package/libx/_runtime/fiori/draft.js +2 -0
- package/libx/_runtime/fiori/generic/activate.js +8 -9
- package/libx/_runtime/fiori/generic/before.js +30 -20
- package/libx/_runtime/fiori/generic/cancel.js +5 -3
- package/libx/_runtime/fiori/generic/delete.js +5 -3
- package/libx/_runtime/fiori/generic/edit.js +7 -7
- package/libx/_runtime/fiori/generic/index.js +10 -16
- package/libx/_runtime/fiori/generic/new.js +5 -3
- package/libx/_runtime/fiori/generic/patch.js +11 -8
- package/libx/_runtime/fiori/generic/prepare.js +13 -6
- package/libx/_runtime/fiori/generic/read.js +12 -6
- package/libx/_runtime/fiori/lean-draft.js +207 -152
- package/libx/_runtime/fiori/utils/delete.js +10 -5
- package/libx/_runtime/fiori/utils/req.js +17 -5
- package/libx/_runtime/fiori/utils/stream.js +36 -0
- package/libx/_runtime/hana/Service.js +12 -9
- package/libx/_runtime/hana/conversion.js +10 -15
- package/libx/_runtime/hana/driver.js +2 -0
- package/libx/_runtime/hana/execute.js +28 -6
- package/libx/_runtime/hana/pool.js +36 -122
- package/libx/_runtime/hana/search2cqn4sql.js +34 -36
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/remote/Service.js +20 -1
- package/libx/_runtime/remote/utils/client.js +3 -5
- package/libx/_runtime/sqlite/Service.js +4 -6
- package/libx/_runtime/sqlite/conversion.js +3 -13
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
- package/libx/_runtime/sqlite/execute.js +5 -16
- package/libx/odata/afterburner.js +22 -6
- package/libx/odata/grammar.pegjs +6 -1
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +16 -9
- package/libx/rest/RestRequest.js +1 -1
- package/libx/rest/middleware/input.js +2 -1
- package/libx/rest/middleware/operation.js +1 -0
- package/libx/rest/middleware/parse.js +3 -2
- package/libx/rest/middleware/payload.js +9 -8
- package/libx/rest/middleware/read.js +1 -0
- package/package.json +9 -16
- package/app/fiori/preview.js +0 -270
- package/app/fiori/routes.js +0 -59
- package/bin/build/buildTaskEngine.js +0 -360
- package/bin/build/buildTaskFactory.js +0 -283
- package/bin/build/buildTaskHandler.js +0 -241
- package/bin/build/buildTaskProvider.js +0 -22
- package/bin/build/buildTaskProviderFactory.js +0 -175
- package/bin/build/cds.js +0 -5
- package/bin/build/constants.js +0 -66
- package/bin/build/index.js +0 -58
- package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
- package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
- package/bin/build/provider/buildTaskProviderInternal.js +0 -383
- package/bin/build/provider/fiori/index.js +0 -171
- package/bin/build/provider/hana/2migration.js +0 -179
- package/bin/build/provider/hana/index.js +0 -505
- package/bin/build/provider/hana/migrationtable.js +0 -472
- package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
- package/bin/build/provider/hana/template/.hdinamespace +0 -4
- package/bin/build/provider/hana/template/package.json +0 -12
- package/bin/build/provider/hana/template/undeploy.json +0 -5
- package/bin/build/provider/java/index.js +0 -111
- package/bin/build/provider/java-cf/index.js +0 -1
- package/bin/build/provider/mtx/index.js +0 -268
- package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
- package/bin/build/provider/mtx-extension/index.js +0 -131
- package/bin/build/provider/mtx-sidecar/index.js +0 -137
- package/bin/build/provider/node-cf/index.js +0 -1
- package/bin/build/provider/nodejs/index.js +0 -192
- package/bin/build/util.js +0 -299
- package/bin/cds.js +0 -125
- package/bin/deploy/to-hana/cfUtil.js +0 -355
- package/bin/deploy/to-hana/gitUtil.js +0 -57
- package/bin/deploy/to-hana/hana.js +0 -306
- package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
- package/bin/deploy/to-hana/index.js +0 -16
- package/bin/deploy/to-hana/mtaUtil.js +0 -170
- package/bin/mtx/in-cds.js +0 -17
- package/bin/plugins.js +0 -32
- package/bin/run.js +0 -24
- package/bin/utils/log.js +0 -24
- package/bin/version.js +0 -178
- package/libx/_runtime/audit/Service.js +0 -222
- package/libx/_runtime/audit/generic/personal/access.js +0 -61
- package/libx/_runtime/audit/generic/personal/index.js +0 -56
- package/libx/_runtime/audit/generic/personal/modification.js +0 -132
- package/libx/_runtime/audit/generic/personal/utils.js +0 -186
- package/libx/_runtime/audit/utils/log.js +0 -23
- package/libx/_runtime/audit/utils/v2.js +0 -176
- package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
- package/libx/_runtime/db/generic/integrity.js +0 -455
- package/srv/audit-log.cds +0 -87
- package/srv/mtx.cds +0 -2
- package/srv/mtx.js +0 -8
- /package/lib/{core → linked}/classes.js +0 -0
- /package/lib/{core → linked}/entities.js +0 -0
|
@@ -57,27 +57,30 @@ const _log = (req, challenges) => {
|
|
|
57
57
|
LOG.debug(`User "${req.user.id}" request URL`, req.url, '\n', ...challengesLog)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const cap_auth_callback = (req, res, next, internalError, user,
|
|
60
|
+
const cap_auth_callback = (req, res, next, internalError, user, arg) => {
|
|
61
61
|
// An internal error occurs during the authentication process
|
|
62
62
|
if (internalError) {
|
|
63
|
-
|
|
64
|
-
return res.status(401).json(UNAUTHORIZED) // no details to client
|
|
63
|
+
return res.status(401).json({ error: UNAUTHORIZED }) // no details to client
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
let
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
let challenges
|
|
67
|
+
if (arg) {
|
|
68
|
+
if (!user) {
|
|
69
|
+
// > challenges
|
|
70
|
+
if (Array.isArray(arg)) {
|
|
71
|
+
challenges = arg.filter(ele => ele)
|
|
72
|
+
challenges = challenges.length ? challenges : undefined
|
|
73
|
+
} else {
|
|
74
|
+
challenges = [arg]
|
|
75
|
+
}
|
|
73
76
|
} else {
|
|
74
|
-
req.authInfo
|
|
77
|
+
// REVISIT: req._.req.authInfo compat
|
|
78
|
+
if (arg.verifyToken) req.authInfo = arg
|
|
75
79
|
}
|
|
76
80
|
}
|
|
81
|
+
req.user = user || Object.defineProperty(new cds.User(), '_challenges', { enumerable: false, value: challenges })
|
|
82
|
+
_log(req, challenges)
|
|
77
83
|
|
|
78
|
-
req.user = user || Object.defineProperty(new cds.User(), '_challenges', { enumerable: false, value: infoChallenges })
|
|
79
|
-
Object.defineProperty(req.user, '_req', { enumerable: false, value: req })
|
|
80
|
-
_log(req, infoChallenges)
|
|
81
84
|
next()
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -102,14 +105,13 @@ const _mountMockAuth = (srv, app, strategy, config) => {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
105
|
-
if (!config.credentials)
|
|
106
|
-
|
|
107
|
-
LOG.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
108
|
+
if (strategy in { jwt: 1, xsuaa: 1 } && !config.credentials) {
|
|
109
|
+
LOG._warn &&
|
|
110
|
+
LOG.warn(`Authentication kind "${config.kind}" configured, but no XSUAA instance bound to application.
|
|
111
|
+
This is NOT recommended in production!`)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
113
115
|
if (!passport) passport = _require('passport')
|
|
114
116
|
|
|
115
117
|
// initialize strategy
|
|
@@ -134,6 +136,7 @@ const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
|
134
136
|
module.exports = (srv, options = srv.options) => {
|
|
135
137
|
const handlers = [],
|
|
136
138
|
app = { use: h => handlers.push(h) }
|
|
139
|
+
|
|
137
140
|
// NOTE: options.auth is not an official API
|
|
138
141
|
let config = 'auth' in options ? options.auth : cds.env.requires.auth
|
|
139
142
|
if (!config) {
|
|
@@ -192,6 +195,7 @@ module.exports = (srv, options = srv.options) => {
|
|
|
192
195
|
|
|
193
196
|
// so we don't log the same stuff multiple times
|
|
194
197
|
logged = true
|
|
198
|
+
|
|
195
199
|
return handlers
|
|
196
200
|
}
|
|
197
201
|
|
|
@@ -207,8 +211,8 @@ const cap_enforce_login = (req, res, next) => {
|
|
|
207
211
|
if (req.user && req.user.is('authenticated-user')) return next() // pass if user is authenticated
|
|
208
212
|
if (!req.user || req.user._is_anonymous) {
|
|
209
213
|
if (req.user && req.user._challenges) res.set('WWW-Authenticate', req.user._challenges.join(';'))
|
|
210
|
-
return res.status(401).json(UNAUTHORIZED) // no details to client
|
|
214
|
+
return res.status(401).json({ error: UNAUTHORIZED }) // no details to client
|
|
211
215
|
} else {
|
|
212
|
-
return res.status(403).json(FORBIDDEN) // no details to client
|
|
216
|
+
return res.status(403).json({ error: FORBIDDEN }) // no details to client
|
|
213
217
|
}
|
|
214
218
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
1
2
|
const cds = require('../../../cds')
|
|
2
3
|
// requesting logger without module on purpose!
|
|
3
4
|
const LOG = cds.log()
|
|
@@ -15,6 +16,8 @@ const metaInfo = require('./utils/metaInfo')
|
|
|
15
16
|
const odataToCQN = require('./odata-to-cqn')
|
|
16
17
|
const { getData, getParams } = require('./utils/data')
|
|
17
18
|
const { isCustomOperation } = require('./utils/request')
|
|
19
|
+
const { isStreaming } = require('./utils/stream')
|
|
20
|
+
const { handleStreamProperties } = require('../../../common/utils/streamProp')
|
|
18
21
|
|
|
19
22
|
function _isCorrectCallToViewWithParams(csdlStructuredType) {
|
|
20
23
|
return (
|
|
@@ -75,10 +78,11 @@ class ODataRequest extends cds.Request {
|
|
|
75
78
|
? odataReq.getBatchApplicationData().req
|
|
76
79
|
: odataReq.getIncomingRequest()
|
|
77
80
|
const res = req.res
|
|
81
|
+
const segments = odataReq.getUriInfo().getPathSegments()
|
|
78
82
|
|
|
79
83
|
if (cds.env.features.odata_new_parser) {
|
|
80
84
|
// REVISIT need to resolve target after replacing okra <= maybe just take one from afterburner?
|
|
81
|
-
const target = _getTarget(service, [...
|
|
85
|
+
const target = _getTarget(service, [...segments])
|
|
82
86
|
// REVISIT payload after replacing okra
|
|
83
87
|
const data = getData(type, odataReq, service, target)
|
|
84
88
|
const query = odataToCQN(type, service, target, data, odataReq, upsert)
|
|
@@ -88,7 +92,11 @@ class ODataRequest extends cds.Request {
|
|
|
88
92
|
const { user } = req
|
|
89
93
|
const info = metaInfo(query, type, service, data, req, upsert)
|
|
90
94
|
const { event, unbound } = info
|
|
91
|
-
if (event === 'READ')
|
|
95
|
+
if (event === 'READ') {
|
|
96
|
+
_add4Odata(query)
|
|
97
|
+
if (query.SELECT && !query.SELECT.columns) query.SELECT.columns = ['*']
|
|
98
|
+
if (!isStreaming(segments)) handleStreamProperties(target, query, service.model, true)
|
|
99
|
+
}
|
|
92
100
|
const _queryOptions = odataReq.getQueryOptions()
|
|
93
101
|
super({ event, target, data, query: unbound ? {} : query, user, method, headers, req, res, _queryOptions })
|
|
94
102
|
this._metaInfo = info.metadata
|
|
@@ -96,7 +104,7 @@ class ODataRequest extends cds.Request {
|
|
|
96
104
|
/*
|
|
97
105
|
* target
|
|
98
106
|
*/
|
|
99
|
-
const target = _getTarget(service, [...
|
|
107
|
+
const target = _getTarget(service, [...segments])
|
|
100
108
|
|
|
101
109
|
/*
|
|
102
110
|
* data
|
|
@@ -106,9 +114,7 @@ class ODataRequest extends cds.Request {
|
|
|
106
114
|
/*
|
|
107
115
|
* query
|
|
108
116
|
*/
|
|
109
|
-
const operation = isCustomOperation(odataReq.getUriInfo().
|
|
110
|
-
? odataReq.getUriInfo().getLastSegment().getKind()
|
|
111
|
-
: type
|
|
117
|
+
const operation = isCustomOperation(segments) ? odataReq.getUriInfo().getLastSegment().getKind() : type
|
|
112
118
|
const query = odataToCQN(operation, service, target, data, odataReq, upsert)
|
|
113
119
|
|
|
114
120
|
/*
|
|
@@ -147,6 +153,8 @@ class ODataRequest extends cds.Request {
|
|
|
147
153
|
else if (type === 'DELETE' && data.IsActiveEntity !== true) event = 'CANCEL'
|
|
148
154
|
}
|
|
149
155
|
|
|
156
|
+
if (query.STREAM) event = 'STREAM'
|
|
157
|
+
|
|
150
158
|
// mark query as for an OData READ
|
|
151
159
|
if (event === 'READ') Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
152
160
|
|
|
@@ -168,23 +176,6 @@ class ODataRequest extends cds.Request {
|
|
|
168
176
|
super({ event, target, data, query, user, method, headers, req, res, _queryOptions, tenant })
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
/*
|
|
172
|
-
* req.run
|
|
173
|
-
*/
|
|
174
|
-
Object.defineProperty(this, 'run', {
|
|
175
|
-
configurable: true,
|
|
176
|
-
get:
|
|
177
|
-
() =>
|
|
178
|
-
(...args) => {
|
|
179
|
-
if (!cds._deprecationWarningForRun) {
|
|
180
|
-
LOG._warn && LOG.warn('req.run is deprecated and will be removed.')
|
|
181
|
-
cds._deprecationWarningForRun = true
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return cds.tx(this).run(...args)
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
|
|
188
179
|
/*
|
|
189
180
|
* req.params
|
|
190
181
|
*/
|
|
@@ -233,55 +224,7 @@ class ODataRequest extends cds.Request {
|
|
|
233
224
|
}
|
|
234
225
|
})
|
|
235
226
|
|
|
236
|
-
|
|
237
|
-
* req.isConcurrentResource
|
|
238
|
-
*/
|
|
239
|
-
// REVISIT: re-implement in runtime w/o using okra
|
|
240
|
-
Object.defineProperty(this, 'isConcurrentResource', {
|
|
241
|
-
get() {
|
|
242
|
-
this._isConcurrentResource = this._isConcurrentResource || odataReq.getConcurrentResource() !== null
|
|
243
|
-
return this._isConcurrentResource
|
|
244
|
-
}
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
/*
|
|
248
|
-
* req.isConditional
|
|
249
|
-
*/
|
|
250
|
-
// REVISIT: re-implement in runtime w/o using okra
|
|
251
|
-
Object.defineProperty(this, 'isConditional', {
|
|
252
|
-
get() {
|
|
253
|
-
this._isConditional = this._isConditional || odataReq.isConditional()
|
|
254
|
-
return this._isConditional
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
Object.defineProperty(this, '_isOData', { value: true })
|
|
259
|
-
|
|
260
|
-
/*
|
|
261
|
-
* req.validateEtag()
|
|
262
|
-
*/
|
|
263
|
-
// REVISIT: re-implement in runtime w/o using okra
|
|
264
|
-
this.validateEtag = (...args) => {
|
|
265
|
-
return odataReq.validateEtag(...args)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/*
|
|
269
|
-
* req.getUriInfo()
|
|
270
|
-
* req.getUrlObject()
|
|
271
|
-
*
|
|
272
|
-
* In draft context req object is cloned.
|
|
273
|
-
* Defining a property here will not work.
|
|
274
|
-
*/
|
|
275
|
-
// REVISIT: re-implement in runtime w/o using okra
|
|
276
|
-
this.getUriInfo = () => {
|
|
277
|
-
this._uriInfo = this._uriInfo || odataReq.getUriInfo()
|
|
278
|
-
return this._uriInfo
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
this.getUrlObject = () => {
|
|
282
|
-
this._urlObject = this._urlObject || odataReq.getUrlObject()
|
|
283
|
-
return this._urlObject
|
|
284
|
-
}
|
|
227
|
+
Object.defineProperty(this, 'protocol', { value: 'odata-v4' })
|
|
285
228
|
}
|
|
286
229
|
}
|
|
287
230
|
|
|
@@ -22,8 +22,6 @@ const metadata = service => {
|
|
|
22
22
|
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
23
23
|
let edmx = mps
|
|
24
24
|
? await mps.getEdmx({ tenant, model: service.model, service: service.definition.name, locale })
|
|
25
|
-
: cds.mtx && cds.mtx.isExtended(tenant)
|
|
26
|
-
? await cds.mtx.getEdmx(tenant, service.definition.name, locale)
|
|
27
25
|
: cds.localize(
|
|
28
26
|
service.model,
|
|
29
27
|
locale,
|
|
@@ -17,12 +17,13 @@ const { isCustomOperation } = require('../utils/request')
|
|
|
17
17
|
const { getActionOrFunctionReturnType } = require('../utils/handlerUtils')
|
|
18
18
|
const { validateResourcePath } = require('../utils/request')
|
|
19
19
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
20
|
-
const { isStreaming
|
|
20
|
+
const { isStreaming } = require('../utils/stream')
|
|
21
21
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
22
22
|
const getError = require('../../../../common/error')
|
|
23
23
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
24
24
|
const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
|
|
25
25
|
const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
|
|
26
|
+
const { isNewStream, transformRedirectProperties } = require('../../../../common/utils/stream')
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Checks whether a bound function or function import is invoked.
|
|
@@ -144,43 +145,6 @@ const _isNavigationToOne = segments =>
|
|
|
144
145
|
segments[segments.length - 1].getKind() === NAVIGATION_TO_ONE &&
|
|
145
146
|
segments[segments.length - 1].getKeyPredicates().length === 0
|
|
146
147
|
|
|
147
|
-
const _hasRedirectProperty = elements => {
|
|
148
|
-
return Object.values(elements).some(val => {
|
|
149
|
-
return val['@Core.IsURL']
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const _addMediaType = (key, entry, mediaType) => {
|
|
154
|
-
if (mediaType) {
|
|
155
|
-
if (typeof mediaType === 'object') {
|
|
156
|
-
entry[`${key}@odata.mediaContentType`] = entry[Object.values(mediaType)[0]]
|
|
157
|
-
} else {
|
|
158
|
-
entry[`${key}@odata.mediaContentType`] = mediaType
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const _transformRedirectProperties = (req, result) => {
|
|
164
|
-
if (!Array.isArray(result) || result.length === 0) {
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// optimization
|
|
169
|
-
if (!_hasRedirectProperty(req.target.elements)) {
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
for (const entry of result) {
|
|
174
|
-
for (const key in entry) {
|
|
175
|
-
if (entry[key] !== undefined && req.target.elements[key]['@Core.IsURL']) {
|
|
176
|
-
entry[`${key}@odata.mediaReadLink`] = entry[key]
|
|
177
|
-
_addMediaType(key, entry, req.target.elements[key]['@Core.MediaType'])
|
|
178
|
-
delete entry[key]
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
148
|
const _getResult = (nameArr, result) => {
|
|
185
149
|
if (nameArr.length === 0) return result
|
|
186
150
|
return _getResult(nameArr.slice(1), result[nameArr[0]])
|
|
@@ -211,6 +175,10 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
211
175
|
* If no entity is related, the service returns 204 No Content.
|
|
212
176
|
*/
|
|
213
177
|
if (result == null) {
|
|
178
|
+
if (req.headers['if-none-match']) {
|
|
179
|
+
req._.odataRes.setStatusCode(304)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
214
182
|
if (_isNavigationToOne(segments)) return toODataResult(null)
|
|
215
183
|
throw getError(404)
|
|
216
184
|
}
|
|
@@ -226,8 +194,6 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
226
194
|
const propertyElement = segments[segments.length - index].getProperty()
|
|
227
195
|
|
|
228
196
|
if (propertyElement === null) {
|
|
229
|
-
_transformRedirectProperties(req, result)
|
|
230
|
-
|
|
231
197
|
return toODataResult(result[0], req)
|
|
232
198
|
}
|
|
233
199
|
|
|
@@ -323,8 +289,6 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
323
289
|
|
|
324
290
|
const odataResult = toODataResult(result, req)
|
|
325
291
|
|
|
326
|
-
_transformRedirectProperties(req, result)
|
|
327
|
-
|
|
328
292
|
return odataResult
|
|
329
293
|
}
|
|
330
294
|
|
|
@@ -341,22 +305,26 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
341
305
|
const _readStream = async (tx, req) => {
|
|
342
306
|
req.query._streaming = true
|
|
343
307
|
|
|
344
|
-
const { contentType, contentDispositionFilename, contentDispositionType } = await getStreamProperties(req, tx.model)
|
|
345
|
-
|
|
346
308
|
let result = await tx.dispatch(req)
|
|
347
309
|
|
|
348
310
|
// REVISIT: compat, should actually be treated as object
|
|
349
311
|
if (!Array.isArray(result)) result = [result]
|
|
350
312
|
|
|
351
313
|
// Reading one entity or a property of it should yield only a result length of one.
|
|
352
|
-
if (result.length === 0 || result[0] === undefined)
|
|
314
|
+
if (result.length === 0 || result[0] === undefined) {
|
|
315
|
+
if (req.headers['if-none-match']) {
|
|
316
|
+
req._.odataRes.setStatusCode(304)
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
throw getError(404)
|
|
320
|
+
}
|
|
353
321
|
|
|
354
322
|
if (result.length > 1) throw getError(400)
|
|
355
323
|
|
|
356
324
|
if (result[0] === null) return null
|
|
357
325
|
|
|
358
326
|
result = result[0]
|
|
359
|
-
const
|
|
327
|
+
const readable = result.value
|
|
360
328
|
|
|
361
329
|
if (readable) {
|
|
362
330
|
readable.on('error', () => {
|
|
@@ -367,6 +335,7 @@ const _readStream = async (tx, req) => {
|
|
|
367
335
|
}
|
|
368
336
|
|
|
369
337
|
const headers = req._.odataReq.getHeaders()
|
|
338
|
+
const contentType = result.$mediaContentType
|
|
370
339
|
|
|
371
340
|
if (
|
|
372
341
|
contentType &&
|
|
@@ -379,12 +348,6 @@ const _readStream = async (tx, req) => {
|
|
|
379
348
|
req.reject(406, `Content type "${contentType}" not listed in accept header "${headers.accept}".`)
|
|
380
349
|
}
|
|
381
350
|
|
|
382
|
-
if (contentType) result.$mediaContentType = contentType
|
|
383
|
-
if (contentDispositionFilename) {
|
|
384
|
-
result.$mediaContentDispositionFilename = contentDispositionFilename
|
|
385
|
-
if (contentDispositionType) result.$mediaContentDispositionType = contentDispositionType
|
|
386
|
-
}
|
|
387
|
-
|
|
388
351
|
return toODataResult(result, req)
|
|
389
352
|
}
|
|
390
353
|
|
|
@@ -424,20 +387,26 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
424
387
|
return _readCollection(tx, req, odataReq)
|
|
425
388
|
}
|
|
426
389
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (req.target.elements[k]['@Core.MediaType']) {
|
|
431
|
-
req.query.SELECT.columns = [{ ref: [k] }]
|
|
432
|
-
break
|
|
433
|
-
}
|
|
390
|
+
if (isNewStream()) {
|
|
391
|
+
if (req.query.STREAM) {
|
|
392
|
+
return _readStream(tx, req)
|
|
434
393
|
}
|
|
394
|
+
} else {
|
|
395
|
+
// REVISIT: move to afterburner
|
|
396
|
+
if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
397
|
+
for (const k in req.target.elements) {
|
|
398
|
+
if (req.target.elements[k]['@Core.MediaType']) {
|
|
399
|
+
req.query.SELECT.columns = [{ ref: [k] }]
|
|
400
|
+
break
|
|
401
|
+
}
|
|
402
|
+
}
|
|
435
403
|
|
|
436
|
-
|
|
437
|
-
|
|
404
|
+
return _readStream(tx, req)
|
|
405
|
+
}
|
|
438
406
|
|
|
439
|
-
|
|
440
|
-
|
|
407
|
+
if (isStreaming(segments)) {
|
|
408
|
+
return _readStream(tx, req)
|
|
409
|
+
}
|
|
441
410
|
}
|
|
442
411
|
|
|
443
412
|
if (req.target._isSingleton) {
|
|
@@ -448,6 +417,7 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
448
417
|
}
|
|
449
418
|
|
|
450
419
|
const _postProcess = (odataReq, req, odataRes, service, result) => {
|
|
420
|
+
transformRedirectProperties(req, service, result.value)
|
|
451
421
|
const functionReturnType = getActionOrFunctionReturnType(
|
|
452
422
|
odataReq.getUriInfo().getPathSegments(),
|
|
453
423
|
service.model.definitions
|
|
@@ -10,6 +10,11 @@ module.exports = srv => {
|
|
|
10
10
|
const req = odataReq.getBatchApplicationData()
|
|
11
11
|
? odataReq.getBatchApplicationData().req
|
|
12
12
|
: odataReq.getIncomingRequest()
|
|
13
|
+
|
|
14
|
+
// REVISIT: ensure there always is a user (should be the case with new middlewares -> remove with old middlewares)
|
|
15
|
+
// prettier-ignore
|
|
16
|
+
if (!req.user) req.user = new cds.User.default
|
|
17
|
+
|
|
13
18
|
const { res, user, path, headers } = req
|
|
14
19
|
|
|
15
20
|
const { protectMetadata } = cds.env.odata
|
|
@@ -19,31 +24,22 @@ module.exports = srv => {
|
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
// in case of $batch we need to challenge directly, as the header is not processed if in $batch response body
|
|
22
|
-
if (restricted && path.endsWith('/$batch')) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
res.set('WWW-Authenticate', `Basic realm="Users"`) // REVISIT: should be req.login(), and fiori app works with that but cds/tests/_runtime/auth/__tests__/strategies.test.js fails
|
|
28
|
-
return next(UNAUTHORIZED)
|
|
29
|
-
// return req.login()
|
|
30
|
-
}
|
|
27
|
+
if (restricted && path.endsWith('/$batch') && req.user._is_anonymous) {
|
|
28
|
+
// NOTE: "return req._login()" would not invoke custom error handlers
|
|
29
|
+
if (req._login) res.set('WWW-Authenticate', `Basic realm="Users"`)
|
|
30
|
+
else if (user._challenges) res.set('WWW-Authenticate', user._challenges.join(';'))
|
|
31
|
+
return next(UNAUTHORIZED)
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// check @requires as soon as possible (DoS)
|
|
34
35
|
if (requires && !requires.some(r => user.is(r))) {
|
|
35
36
|
// > unauthorized or forbidden?
|
|
36
|
-
if (user._is_anonymous) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
res.set('WWW-Authenticate', `Basic realm="Users"`) // REVISIT: should be req.login(), and fiori app works with that but cds/tests/_runtime/auth/__tests__/strategies.test.js fails
|
|
41
|
-
// return req.login()
|
|
42
|
-
}
|
|
43
|
-
// REVISIT: security log in else case?
|
|
37
|
+
if (req.user._is_anonymous) {
|
|
38
|
+
// NOTE: "return req._login()" would not invoke custom error handlers
|
|
39
|
+
if (req._login) res.set('WWW-Authenticate', `Basic realm="Users"`)
|
|
40
|
+
else if (user._challenges) res.set('WWW-Authenticate', user._challenges.join(';'))
|
|
44
41
|
return next(UNAUTHORIZED)
|
|
45
42
|
}
|
|
46
|
-
// REVISIT: security log?
|
|
47
43
|
return next(FORBIDDEN)
|
|
48
44
|
}
|
|
49
45
|
|
|
@@ -72,7 +72,7 @@ const _create = async (req, odataReq, odataRes, tx) => {
|
|
|
72
72
|
}
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
odataRes.setStatusCode(201)
|
|
75
|
+
odataRes.setStatusCode(201, { overwrite: true })
|
|
76
76
|
req = new ODataRequest(DATA_CREATE_HANDLER, tx, odataReq, odataRes, true)
|
|
77
77
|
result = await tx.dispatch(req)
|
|
78
78
|
|
|
@@ -93,9 +93,14 @@ const _updateThenCreate = async (req, odataReq, odataRes, tx) => {
|
|
|
93
93
|
try {
|
|
94
94
|
result = await tx.dispatch(req)
|
|
95
95
|
} catch (e) {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const is404 = e.code === 404 || e.status === 404 || e.statusCode === 404
|
|
97
|
+
if (is404 && _isUpsertAllowed(req.target)) {
|
|
98
|
+
// PUT/ PATCH with if-match header means "only if already exists", i.e., no insert if not
|
|
99
|
+
if (req.headers['if-match'])
|
|
100
|
+
throw Object.assign(new Error('412'), { statusCode: 412 })
|
|
101
|
+
// REVISIT: remove error (and child?) from tx.context? -> would require a unique req.id
|
|
98
102
|
;[result, req] = await _create(req, odataReq, odataRes, tx)
|
|
103
|
+
odataRes.setStatusCode(201, { overwrite: true })
|
|
99
104
|
} else {
|
|
100
105
|
throw e
|
|
101
106
|
}
|
|
@@ -109,6 +114,10 @@ const _shouldReadPreviousResult = req =>
|
|
|
109
114
|
!isReturnMinimal(req) &&
|
|
110
115
|
(hasOmitValuesPreference(req.headers.prefer, 'defaults') || hasOmitValuesPreference(req.headers.prefer, 'nulls'))
|
|
111
116
|
|
|
117
|
+
const _hasEtag = target => {
|
|
118
|
+
return target._etag
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
/**
|
|
113
122
|
* The handler that will be registered with odata-v4.
|
|
114
123
|
*
|
|
@@ -149,12 +158,13 @@ const update = service => {
|
|
|
149
158
|
// try UPDATE and, on 404 error, try CREATE
|
|
150
159
|
;[result, req] = await _updateThenCreate(req, odataReq, odataRes, tx)
|
|
151
160
|
|
|
152
|
-
if (!primitive && req._.readAfterWrite) {
|
|
161
|
+
if (!(primitive && !_hasEtag(req.target)) && req._.readAfterWrite) {
|
|
153
162
|
// REVISIT:
|
|
154
163
|
// Performance: For `isReturnMinimal` it's enough to just read the etag.
|
|
155
164
|
// Note: Without read access, one cannot return the etag.
|
|
156
165
|
result = await readAfterWrite(req, service, { operation: { result } })
|
|
157
166
|
}
|
|
167
|
+
|
|
158
168
|
if (!isReturnMinimal(req)) {
|
|
159
169
|
postProcess(req, odataRes, service, result, previousResult)
|
|
160
170
|
} else {
|
|
@@ -169,7 +179,7 @@ const update = service => {
|
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
if (result == null || isReturnMinimal(req)) {
|
|
172
|
-
odataRes.setStatusCode(204)
|
|
182
|
+
odataRes.setStatusCode(204, { overwrite: true })
|
|
173
183
|
}
|
|
174
184
|
} catch (e) {
|
|
175
185
|
err = e
|
|
@@ -7,6 +7,7 @@ const ResourceKind = odata.uri.UriResource.ResourceKind
|
|
|
7
7
|
const EdmPrimitiveTypeKind = odata.edm.EdmPrimitiveTypeKind
|
|
8
8
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
9
9
|
const { getSegmentKeyValue } = require('./utils')
|
|
10
|
+
const normalizeTimestamp = require('../../../../common/utils/normalizeTimestamp')
|
|
10
11
|
|
|
11
12
|
const _binaryOperatorToCQN = new Map([
|
|
12
13
|
[BinaryOperatorKind.EQ, '='],
|
|
@@ -46,10 +47,10 @@ class ExpressionToCQN {
|
|
|
46
47
|
return { val: parseFloat(value) }
|
|
47
48
|
case EdmPrimitiveTypeKind.DateTimeOffset: {
|
|
48
49
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { val }
|
|
50
|
+
if (expression._cdsType === 'cds.DateTime')
|
|
51
|
+
// cut off ms if cds.DateTime
|
|
52
|
+
return { val: new Date(value).toISOString().replace(/\.\d\d\dZ$/, 'Z') }
|
|
53
|
+
return { val: normalizeTimestamp(value) }
|
|
53
54
|
} catch (e) {
|
|
54
55
|
throw Object.assign(new Error(`The type 'Edm.DateTimeOffset' is not compatible with '${value}'`), {
|
|
55
56
|
status: 400
|