@cap-js/ord 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,7 +114,7 @@ This will output something like `admin:$2y$05$...` - use only the hash part (sta
114
114
 
115
115
  #### CF mTLS Authentication
116
116
 
117
- Configure Cloud Foundry mutual TLS authentication for SAP BTP Cloud Foundry environments.
117
+ Configure Cloud Foundry mutual TLS authentication for SAP BTP Cloud Foundry environments. Possible values include 'sap:cmp-mtls:v1' and 'sap.businesshub:mtls:v1'.
118
118
 
119
119
  **Production Configuration with UCL (Recommended)**
120
120
 
@@ -131,9 +131,11 @@ For SAP UCL (Unified Customer Landscape) integration, enable mTLS in `.cdsrc.jso
131
131
  ```
132
132
 
133
133
  ```bash
134
+ # Example configuration of the CF_MTLS_TRUSTED_CERTS environment variable
134
135
  export CF_MTLS_TRUSTED_CERTS='{
135
136
  "configEndpoints": ["https://your-ucl-endpoint/v1/info"],
136
- "rootCaDn": ["CN=SAP Cloud Root CA,O=SAP SE,L=Walldorf,C=DE"]
137
+ "rootCaDn": ["CN=SAP Cloud Root CA,O=SAP SE,L=Walldorf,C=DE"],
138
+ "accessStrategies": ["sap:cmp-mtls:v1", "sap.businesshub:mtls:v1"]
137
139
  }'
138
140
  ```
139
141
 
@@ -142,9 +144,11 @@ export CF_MTLS_TRUSTED_CERTS='{
142
144
  For custom certificates without UCL:
143
145
 
144
146
  ```bash
147
+ # Example configuration of the CF_MTLS_TRUSTED_CERTS environment variable
145
148
  export CF_MTLS_TRUSTED_CERTS='{
146
149
  "certs": [{"issuer": "CN=My CA,O=MyOrg", "subject": "CN=my-service,O=MyOrg"}],
147
- "rootCaDn": ["CN=My Root CA,O=MyOrg"]
150
+ "rootCaDn": ["CN=My Root CA,O=MyOrg"],
151
+ "accessStrategies": ["sap:cmp-mtls:v1", "sap.businesshub:mtls:v1"]
148
152
  }'
149
153
  ```
150
154
 
@@ -157,13 +161,14 @@ For local development, configure the full mTLS settings directly in `.cdsrc.json
157
161
  "ord": {
158
162
  "authentication": {
159
163
  "cfMtls": {
164
+ "rootCaDn": ["CN=Test Root CA,O=MyOrg,C=DE"],
165
+ "accessStrategies": ["sap:cmp-mtls:v1","sap.businesshub:mtls:v1"],
160
166
  "certs": [
161
167
  {
162
168
  "issuer": "CN=Test CA,O=MyOrg,C=DE",
163
169
  "subject": "CN=test-client,O=MyOrg,C=DE"
164
170
  }
165
- ],
166
- "rootCaDn": ["CN=Test Root CA,O=MyOrg,C=DE"]
171
+ ]
167
172
  }
168
173
  }
169
174
  }
@@ -23,9 +23,8 @@
23
23
  */
24
24
 
25
25
  const cds = require("@sap/cds");
26
- const { AUTHENTICATION_TYPE, BASIC_AUTH_HEADER_KEY, AUTH_STRINGS, CF_MTLS_HEADERS } = require("../constants");
26
+ const { ORD_ACCESS_STRATEGY, BASIC_AUTH_HEADER_KEY, AUTH_STRINGS, CF_MTLS_HEADERS } = require("../constants");
27
27
  const Logger = require("../logger");
28
- const { getAccessStrategiesFromAuthConfig } = require("../access-strategies");
29
28
  const bcrypt = require("bcryptjs");
30
29
  const { createCfMtlsConfig, handleCfMtlsAuthentication } = require("./cf-mtls");
31
30
 
@@ -102,10 +101,10 @@ async function ensureCfMtlsValidator(authConfig) {
102
101
  * 1. Environment variables (BASIC_AUTH, CF_MTLS_TRUSTED_CERTS) - for production deployments
103
102
  * 2. .cdsrc.json settings (cds.env.ord.authentication.basic, cds.env.ord.authentication.cfMtls) - for development and testing
104
103
  *
105
- * Authentication types are automatically detected based on the presence of configuration:
106
- * - If cds.env.ord.authentication.basic exists → Basic authentication enabled
104
+ * Access strategies are automatically detected based on the presence of configuration:
105
+ * - If cds.env.ord.authentication.basic exists → 'basic-auth' is enabled
107
106
  * - If cds.env.ord.authentication.cfMtls exists → CF mTLS authentication enabled
108
- * - Multiple authentication types can coexist and are tried in order
107
+ * - Multiple access strategies can coexist and are tried in order
109
108
  * - Open authentication is the default when no secure authentication is configured
110
109
  *
111
110
  * This approach follows the 12-Factor App principles where environment variables
@@ -118,12 +117,11 @@ async function ensureCfMtlsValidator(authConfig) {
118
117
  */
119
118
  function createAuthConfig() {
120
119
  const defaultAuthConfig = {
121
- types: [AUTHENTICATION_TYPE.Open],
122
- accessStrategies: [{ type: AUTHENTICATION_TYPE.Open }],
120
+ accessStrategies: [ORD_ACCESS_STRATEGY.Open],
123
121
  };
124
122
 
125
123
  try {
126
- const authConfig = { types: [] };
124
+ const authConfig = { accessStrategies: [] };
127
125
  const ordAuth = cds.env.ord?.authentication || {};
128
126
 
129
127
  // Detect Basic authentication by checking for credentials
@@ -145,30 +143,30 @@ function createAuthConfig() {
145
143
  }
146
144
  }
147
145
 
148
- authConfig.types.push(AUTHENTICATION_TYPE.Basic);
146
+ authConfig.accessStrategies.push(ORD_ACCESS_STRATEGY.Basic);
149
147
  authConfig.credentials = credentials;
150
148
  }
151
149
 
152
150
  // Detect CF mTLS authentication by checking for cfMtls config
153
151
  if (process.env.CF_MTLS_TRUSTED_CERTS || ordAuth.cfMtls) {
154
- authConfig.types.push(AUTHENTICATION_TYPE.CfMtls);
152
+ const { accessStrategies } = JSON.parse(
153
+ process.env.CF_MTLS_TRUSTED_CERTS ??
154
+ JSON.stringify(typeof ordAuth.cfMtls === "object" ? ordAuth.cfMtls : {}),
155
+ );
156
+
155
157
  // Mark for lazy initialization - validator will be loaded on first use
156
158
  authConfig.cfMtlsValidator = null;
157
159
  authConfig._cfMtlsInitPromise = null;
160
+ authConfig.accessStrategies.push(...(accessStrategies ?? [ORD_ACCESS_STRATEGY.CmpMtls]));
158
161
  }
159
162
 
160
- // If no authentication types detected, default to Open
161
- if (authConfig.types.length === 0) {
163
+ // If no access strategies detected, default to Open
164
+ if (authConfig.accessStrategies.length === 0) {
162
165
  Logger.info("createAuthConfig:", 'No authentication configured. Defaulting to "Open" authentication');
163
166
  return defaultAuthConfig;
164
167
  }
165
168
 
166
- // Build accessStrategies for ORD document using centralized mapping logic
167
- // This ensures consistent mapping between auth types and ORD access strategies
168
- // All mapping logic is centralized in lib/access-strategies.js
169
- authConfig.accessStrategies = getAccessStrategiesFromAuthConfig(authConfig);
170
-
171
- Logger.info("createAuthConfig:", `Configured authentication types: ${authConfig.types.join(", ")}`);
169
+ Logger.info("createAuthConfig:", `Configured access strategies: ${authConfig.accessStrategies.join(", ")}`);
172
170
  return authConfig;
173
171
  } catch (error) {
174
172
  Logger.error("createAuthConfig:", `Configuration error: ${error.message}`);
@@ -264,9 +262,10 @@ async function openAuthStrategy() {
264
262
  * Strategy registry mapping authentication types to their handlers
265
263
  */
266
264
  const AUTH_STRATEGIES = {
267
- [AUTHENTICATION_TYPE.Basic]: basicAuthStrategy,
268
- [AUTHENTICATION_TYPE.CfMtls]: cfMtlsAuthStrategy,
269
- [AUTHENTICATION_TYPE.Open]: openAuthStrategy,
265
+ [ORD_ACCESS_STRATEGY.Open]: openAuthStrategy,
266
+ [ORD_ACCESS_STRATEGY.Basic]: basicAuthStrategy,
267
+ [ORD_ACCESS_STRATEGY.CmpMtls]: cfMtlsAuthStrategy,
268
+ [ORD_ACCESS_STRATEGY.BahMtls]: cfMtlsAuthStrategy,
270
269
  };
271
270
 
272
271
  /**
@@ -279,13 +278,13 @@ const AUTH_STRATEGIES = {
279
278
  function createAuthMiddleware(authConfig) {
280
279
  return async function authenticate(req, res, next) {
281
280
  // Handle invalid configuration
282
- if (!authConfig || !authConfig.types || !Array.isArray(authConfig.types)) {
281
+ if (!authConfig || !authConfig.accessStrategies || !Array.isArray(authConfig.accessStrategies)) {
283
282
  Logger.error("Invalid auth configuration:", authConfig);
284
283
  return res.status(401).send("Not authorized");
285
284
  }
286
285
 
287
286
  // If open authentication, allow immediately
288
- if (authConfig.types.includes(AUTHENTICATION_TYPE.Open)) {
287
+ if (authConfig.accessStrategies.includes(ORD_ACCESS_STRATEGY.Open)) {
289
288
  res.status(200);
290
289
  return next();
291
290
  }
@@ -293,17 +292,15 @@ function createAuthMiddleware(authConfig) {
293
292
  // Try each registered authentication strategy
294
293
  const results = [];
295
294
 
296
- for (const authType of authConfig.types) {
297
- const strategy = AUTH_STRATEGIES[authType];
298
-
299
- if (!strategy) {
300
- Logger.warn(`Unknown authentication type: ${authType}`);
295
+ for (const strategy of authConfig.accessStrategies) {
296
+ if (!Object.values(ORD_ACCESS_STRATEGY).includes(strategy)) {
297
+ Logger.warn(`Unknown access strategy: ${strategy}`);
301
298
  continue;
302
299
  }
303
300
 
304
301
  try {
305
- const result = await strategy(req, res, authConfig);
306
- results.push({ type: authType, ...result });
302
+ const result = await AUTH_STRATEGIES[strategy](req, res, authConfig);
303
+ results.push({ type: strategy, ...result });
307
304
 
308
305
  if (result.success) {
309
306
  res.status(200);
@@ -315,8 +312,8 @@ function createAuthMiddleware(authConfig) {
315
312
  return;
316
313
  }
317
314
  } catch (error) {
318
- Logger.error(`Error in ${authType} authentication:`, error.message);
319
- results.push({ type: authType, success: false, handled: true, error: error.message });
315
+ Logger.error(`Error in ${strategy} authentication:`, error.message);
316
+ results.push({ type: strategy, success: false, handled: true, error: error.message });
320
317
  }
321
318
  }
322
319
 
@@ -328,7 +325,7 @@ function createAuthMiddleware(authConfig) {
328
325
  // No authentication method was attempted
329
326
  const wwwAuthHeaders = [];
330
327
 
331
- if (authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
328
+ if (authConfig.accessStrategies.includes(ORD_ACCESS_STRATEGY.Basic)) {
332
329
  wwwAuthHeaders.push(AUTH_STRINGS.WWW_AUTHENTICATE_REALM);
333
330
  }
334
331
 
@@ -341,12 +341,12 @@ async function createCfMtlsConfig(cds, Logger) {
341
341
  * @param {Object} res - Express response object
342
342
  * @param {Object} authConfig - Authentication configuration
343
343
  * @param {Function} authConfig.cfMtlsValidator - CF mTLS validator function
344
- * @param {Array} authConfig.types - Array of enabled authentication types
344
+ * @param {Array<string>} authConfig.accessStrategies - Array of enabled access strategies
345
345
  * @param {Object} Logger - Logger instance for error messages
346
346
  * @returns {Object} Result object with success flag and optional next() call
347
347
  */
348
348
  function handleCfMtlsAuthentication(req, res, authConfig, Logger) {
349
- const { AUTHENTICATION_TYPE, CF_MTLS_ERROR_REASON, AUTH_STRINGS } = require("../constants");
349
+ const { ORD_ACCESS_STRATEGY, CF_MTLS_ERROR_REASON, AUTH_STRINGS } = require("../constants");
350
350
  const result = authConfig.cfMtlsValidator(req);
351
351
 
352
352
  if (result.ok) {
@@ -361,7 +361,7 @@ function handleCfMtlsAuthentication(req, res, authConfig, Logger) {
361
361
  if (result.reason === CF_MTLS_ERROR_REASON.XFCC_VERIFICATION_FAILED) {
362
362
  Logger.error("CF mTLS authentication failed: Missing proxy verification");
363
363
  // If Basic auth is also configured, provide fallback
364
- if (authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
364
+ if (authConfig.accessStrategies.includes(ORD_ACCESS_STRATEGY.Basic)) {
365
365
  res.status(401)
366
366
  .setHeader("WWW-Authenticate", AUTH_STRINGS.WWW_AUTHENTICATE_REALM)
367
367
  .send("Authentication required.");
@@ -373,7 +373,7 @@ function handleCfMtlsAuthentication(req, res, authConfig, Logger) {
373
373
 
374
374
  if (result.reason === CF_MTLS_ERROR_REASON.NO_HEADERS) {
375
375
  // If Basic auth is also configured, provide a more informative message
376
- if (authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
376
+ if (authConfig.accessStrategies.includes(ORD_ACCESS_STRATEGY.Basic)) {
377
377
  res.status(401)
378
378
  .setHeader("WWW-Authenticate", AUTH_STRINGS.WWW_AUTHENTICATE_REALM)
379
379
  .send("Authentication required.");
@@ -386,7 +386,7 @@ function handleCfMtlsAuthentication(req, res, authConfig, Logger) {
386
386
  if (result.reason === CF_MTLS_ERROR_REASON.HEADER_MISSING) {
387
387
  Logger.error(`CF mTLS authentication failed: Missing header ${result.missing}`);
388
388
  // If Basic auth is also configured, provide fallback
389
- if (authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
389
+ if (authConfig.accessStrategies.includes(ORD_ACCESS_STRATEGY.Basic)) {
390
390
  res.status(401)
391
391
  .setHeader("WWW-Authenticate", AUTH_STRINGS.WWW_AUTHENTICATE_REALM)
392
392
  .send("Authentication required.");
package/lib/build.js CHANGED
@@ -58,9 +58,7 @@ module.exports = class OrdBuildPlugin extends cds.build.Plugin {
58
58
  }
59
59
 
60
60
  _createRelativePath(url) {
61
- let relative = url.split("/ord/v1").pop();
62
- if (relative.startsWith("/")) relative = relative.slice(1);
63
- return path.join(...relative.replace(/:/g, "_").split("/"));
61
+ return url.replace(/^\/ord\/v1\//, "").replace(/:/g, "_");
64
62
  }
65
63
 
66
64
  _createWorkerPool(tasks, model) {
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ replace: (value, context) => {
3
+ return Object.entries(context) //
4
+ .reduce((result, [key, value]) => result?.replaceAll(`{${key}}`, value), value);
5
+ },
6
+ };
@@ -0,0 +1,72 @@
1
+ const ordMainPartKeys = Object.freeze([
2
+ "groups",
3
+ "$schema",
4
+ "packages",
5
+ "products",
6
+ "groupTypes",
7
+ "description",
8
+ "perspective",
9
+ "policyLevel",
10
+ "policyLevels",
11
+ "customPolicyLevel",
12
+ "consumptionBundles",
13
+ "describedSystemType",
14
+ "openResourceDiscovery",
15
+ "describedSystemVersion",
16
+ "describedSystemInstance",
17
+ ]);
18
+
19
+ function getFileSizeInBytes(document) {
20
+ return Buffer.byteLength(JSON.stringify(document), "utf8");
21
+ }
22
+
23
+ function slice(document, limit, chunk = 100) {
24
+ if (getFileSizeInBytes(document) < limit) {
25
+ return [document];
26
+ }
27
+
28
+ const slices = [];
29
+ const ord = Object.fromEntries(
30
+ Object.entries(document) //
31
+ .filter(([key, value]) => ordMainPartKeys.includes(key) || !Array.isArray(value) || !value.length),
32
+ );
33
+
34
+ let slice = {};
35
+ let sliceSize = 2; // getFileSizeInBytes({});
36
+ const adjustedLimit = limit - getFileSizeInBytes(ord);
37
+
38
+ Object.entries(document)
39
+ .filter(([key]) => !(key in ord))
40
+ .forEach(([key, value]) => {
41
+ let start = 0;
42
+
43
+ for (let i = 0; i < value.length; i += chunk) {
44
+ const remaining = adjustedLimit - sliceSize;
45
+ const additional = getFileSizeInBytes({
46
+ [key]: value.slice(start, i + chunk),
47
+ });
48
+
49
+ if (remaining < additional) {
50
+ if (i > 0 || Object.keys(slice).length > 0) {
51
+ slices.push(Object.assign(slice, i === 0 ? {} : { [key]: value.slice(start, i) }));
52
+ }
53
+
54
+ // prepare for the next slice
55
+ start = i;
56
+ slice = {};
57
+ sliceSize = 2; // getFileSizeInBytes({});
58
+ }
59
+ }
60
+
61
+ slice = Object.assign(slice, { [key]: value.slice(start) });
62
+ sliceSize = getFileSizeInBytes(slice);
63
+ });
64
+
65
+ slices.push(slice);
66
+
67
+ return slices.map((slice) => Object.assign(slice, ord));
68
+ }
69
+
70
+ module.exports = {
71
+ slice,
72
+ };
@@ -0,0 +1,190 @@
1
+ const _ = require("lodash");
2
+ const cds = require("@sap/cds");
3
+
4
+ const Logger = require("../logger");
5
+ const {
6
+ ENTITY_RELATIONSHIP_ANNOTATION,
7
+ DATA_PRODUCT_ANNOTATION,
8
+ DATA_PRODUCT_TYPE,
9
+ DATA_PRODUCT_SIMPLE_ANNOTATION,
10
+ ORD_EXTENSIONS_PREFIX,
11
+ RESOURCE_VISIBILITY,
12
+ ALLOWED_VISIBILITY,
13
+ SUPPORTED_IMPLEMENTATIONSTANDARD_VERSIONS,
14
+ EXTERNAL_SERVICE_ANNOTATION,
15
+ EXTERNAL_DP_ORD_ID_ANNOTATION,
16
+ CDS_ELEMENT_KIND,
17
+ ORD_ACCESS_STRATEGY,
18
+ } = require("../constants");
19
+
20
+ const DEFAULT_ACCESS_STRATEGIES = Object.freeze([ORD_ACCESS_STRATEGY.Open]);
21
+
22
+ function prune(object) {
23
+ return Object.fromEntries(
24
+ Object.entries(object)
25
+ .filter(([, value]) => value !== null && value !== undefined) // remove empty fields
26
+ .filter(([, value]) => typeof value !== "object" || Object.keys(value).length > 0), // remove empty arrays/objects
27
+ );
28
+ }
29
+
30
+ function getRFC3339Date() {
31
+ const now = new Date();
32
+ const year = now.getUTCFullYear();
33
+ const month = String(now.getUTCMonth() + 1).padStart(2, "0");
34
+ const day = String(now.getUTCDate()).padStart(2, "0");
35
+ const hours = String(now.getUTCHours()).padStart(2, "0");
36
+ const minutes = String(now.getUTCMinutes()).padStart(2, "0");
37
+ const seconds = String(now.getUTCSeconds()).padStart(2, "0");
38
+
39
+ return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}+01:00`;
40
+ }
41
+
42
+ function isPrimaryDataProductService(service) {
43
+ return service[DATA_PRODUCT_ANNOTATION] === DATA_PRODUCT_TYPE.primary || !!service[DATA_PRODUCT_SIMPLE_ANNOTATION];
44
+ }
45
+
46
+ function resolveVisibility(appConfig, service) {
47
+ const explicit = service["@ORD.Extensions.visibility"];
48
+ const standard = service["@ORD.Extensions.implementationStandard"];
49
+ let defaultVisibility = appConfig.env?.defaultVisibility ?? RESOURCE_VISIBILITY.public;
50
+
51
+ //check for supported custom visibility value in defaultVisibility variable
52
+ if (!ALLOWED_VISIBILITY.includes(defaultVisibility)) {
53
+ Logger.warn(
54
+ `Default visibility ${defaultVisibility} is not supported. Using ${RESOURCE_VISIBILITY.public} as fallback`,
55
+ );
56
+ defaultVisibility = RESOURCE_VISIBILITY.public;
57
+ }
58
+
59
+ // Determine visibility
60
+ if (isPrimaryDataProductService(service)) {
61
+ return RESOURCE_VISIBILITY.internal;
62
+ } else if (explicit) {
63
+ return service["@ORD.Extensions.visibility"];
64
+ } else if (SUPPORTED_IMPLEMENTATIONSTANDARD_VERSIONS.includes(standard)) {
65
+ // if the implementationStandard is for example sap:ord-document-api:v1, it should be public by default
66
+ return RESOURCE_VISIBILITY.public;
67
+ }
68
+
69
+ return defaultVisibility;
70
+ }
71
+
72
+ function resolveServiceName(appConfig, { name }) {
73
+ return (
74
+ [
75
+ appConfig.internalNamespace, //
76
+ appConfig.ordNamespace, //
77
+ ]
78
+ .filter(Boolean)
79
+ .filter((namespace) => name === namespace || name.startsWith(`${namespace}.`))
80
+ .map((namespace) => name.substring(namespace.length))
81
+ .shift()
82
+ ?.replace(/^\./, "") ?? name
83
+ );
84
+ }
85
+
86
+ function flattenEntityGraph(current, processed = []) {
87
+ return [
88
+ current, //
89
+ ...Object.values(current.associations ?? []) //
90
+ .filter(({ target }) => !processed.includes(target))
91
+ .flatMap(({ target, _target }) => {
92
+ processed.push(target);
93
+ return flattenEntityGraph(_target, processed);
94
+ }),
95
+ ];
96
+ }
97
+
98
+ function readORDExtensions(model, prefix = ORD_EXTENSIONS_PREFIX) {
99
+ return Object.entries(model) //
100
+ .filter(([key]) => key.startsWith(prefix))
101
+ .map(([key, value]) => [key.substring(prefix.length), value])
102
+ .reduce((result, [key, value]) => _.set(result, key, value), {});
103
+ }
104
+
105
+ function isExternalDataProduct(definition) {
106
+ return !!(
107
+ definition.kind === "service" &&
108
+ definition[EXTERNAL_SERVICE_ANNOTATION] &&
109
+ definition[DATA_PRODUCT_SIMPLE_ANNOTATION] &&
110
+ definition[EXTERNAL_DP_ORD_ID_ANNOTATION]
111
+ );
112
+ }
113
+
114
+ function isExposedEntityType(definition) {
115
+ return (
116
+ !definition.name.includes(".texts") &&
117
+ definition[ENTITY_RELATIONSHIP_ANNOTATION] &&
118
+ definition.kind === CDS_ELEMENT_KIND.entity &&
119
+ definition["_service"]?.["@protocol"] !== "none" &&
120
+ !isBlockedServiceName(definition["_service"]?.name)
121
+ );
122
+ }
123
+
124
+ function isBlockedServiceName(name) {
125
+ return ["cds.xt.MTXServices", "MtxOrdProviderService", "OpenResourceDiscoveryService"] //
126
+ .some((blocked) => name?.includes(blocked));
127
+ }
128
+
129
+ function isValidService(definition) {
130
+ const isExternalService =
131
+ Object.keys(cds).includes("requires") && Object.keys(cds.requires).includes(definition.name);
132
+
133
+ return (
134
+ !isExternalService &&
135
+ !definition["@cds.external"] &&
136
+ definition["@protocol"] !== "none" &&
137
+ !isBlockedServiceName(definition.name) &&
138
+ definition.kind === CDS_ELEMENT_KIND.service
139
+ );
140
+ }
141
+
142
+ function isEventsOnlyService(definition) {
143
+ return (
144
+ Object.keys(definition.events || {}).length > 0 &&
145
+ ["actions", "entities", "functions"].every((key) => Object.keys(definition[key] || {}).length === 0)
146
+ );
147
+ }
148
+
149
+ function resolveAccessStrategies(authConfig, options = {}) {
150
+ const isStrict = options?.strict ?? cds.env.ord?.strictAccessStrategies === true;
151
+ const strategies = _.uniq(authConfig.accessStrategies ?? [])
152
+ .flatMap((strategy) => {
153
+ if (!Object.values(ORD_ACCESS_STRATEGY).includes(strategy)) {
154
+ Logger.warn("resolveAccessStrategies:", `Unknown access strategy type '${strategy}', skipping`);
155
+ return null;
156
+ }
157
+
158
+ return strategy;
159
+ })
160
+ .filter(Boolean); // Remove null entries
161
+
162
+ if (isStrict && strategies.length === 0) {
163
+ throw new Error("[ORD] accessStrategies missing or empty for resource. Strict mode is enabled");
164
+ }
165
+
166
+ if (strategies.length > 1 && strategies.includes(ORD_ACCESS_STRATEGY.Open)) {
167
+ throw new Error(
168
+ "Invalid access strategies: 'open' cannot coexist with authenticated strategies (basic-auth, sap:cmp-mtls:v1, sap.businesshub:mtls:v1)",
169
+ );
170
+ }
171
+
172
+ return (strategies.length > 0 ? strategies : DEFAULT_ACCESS_STRATEGIES) //
173
+ .map((strategy) => ({ type: strategy }));
174
+ }
175
+
176
+ module.exports = {
177
+ prune,
178
+ isValidService,
179
+ getRFC3339Date,
180
+ readORDExtensions,
181
+ resolveVisibility,
182
+ flattenEntityGraph,
183
+ resolveServiceName,
184
+ isExposedEntityType,
185
+ isEventsOnlyService,
186
+ isBlockedServiceName,
187
+ isExternalDataProduct,
188
+ resolveAccessStrategies,
189
+ isPrimaryDataProductService,
190
+ };