soajs.controller 4.3.22 → 4.3.24

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/ARCHITECTURE.md CHANGED
@@ -158,7 +158,8 @@ The gateway processes requests through a sequential middleware pipeline:
158
158
  | [Custom Registry Configuration](docs/configuration-custom-registry.md) | Environment-specific gateway settings |
159
159
  | [Tenant Access Resolver Guide](docs/tenant-access-resolver.md) | Step by step: give a group of users access to a tenant through an API of your app |
160
160
  | [Services Config](docs/configuration-services-config.md) | Tenant key-specific configuration |
161
- | [Gateway Routes](docs/gateway-routes.md) | Built-in gateway routes (/soajs/acl, /soajs/proxy) |
161
+ | [Gateway Routes](docs/gateway-routes.md) | Built-in gateway routes (/soajs/acl, /soajs/mcp, /soajs/proxy) |
162
+ | [MCP Endpoint](docs/mcp.md) | The caller's APIs as MCP tools for AI agents |
162
163
  | [Maintenance Routes](docs/maintenance-routes.md) | Health check and admin endpoints |
163
164
 
164
165
  ---
@@ -32,6 +32,9 @@ registry.custom
32
32
  │ │ └── [main product] # Its sub products
33
33
  │ ├── oauth
34
34
  │ │ └── deviceIdCheck # Turn the deviceId check off
35
+ │ ├── mcp # MCP endpoint /soajs/mcp for AI agents
36
+ │ │ ├── enabled # true turns it on
37
+ │ │ └── ttl, timeout, maxTools
35
38
  │ └── gotoService
36
39
  │ ├── monitor # Request monitoring config
37
40
  │ └── renewReqMonitorOff # Disable timeout renewal
@@ -760,6 +763,33 @@ Anything but a well formed access answer is no access, and the urac driver decid
760
763
 
761
764
  ---
762
765
 
766
+ ## 16. MCP Endpoint
767
+
768
+ `registry.custom.gateway.value.mcp` turns on `POST /soajs/mcp`: AI agents see the APIs their
769
+ caller may use as tools, and a tool call is processed as the API call it stands for.
770
+
771
+ ```json
772
+ {
773
+ "mcp": {
774
+ "enabled": true,
775
+ "ttl": 300000,
776
+ "timeout": 10000,
777
+ "maxTools": 300
778
+ }
779
+ }
780
+ ```
781
+
782
+ | Field | Default | Description |
783
+ |-------|---------|-------------|
784
+ | `enabled` | — | `true` turns the endpoint on; otherwise it answers 404/190 |
785
+ | `ttl` | `300000` | How long the tools of a service are cached, ms |
786
+ | `timeout` | `10000` | Fetching tools and calling a tool, ms |
787
+ | `maxTools` | `300` | At most this many tools in a list |
788
+
789
+ See [MCP Endpoint](mcp.md).
790
+
791
+ ---
792
+
763
793
  ## Complete Configuration Example
764
794
 
765
795
  ```javascript
@@ -17,6 +17,12 @@ The SOAJS Controller provides special built-in routes that are handled directly
17
17
  │ ├── Requires: External key (header), OAuth token (query) │
18
18
  │ └── Returns: ACL, packages, user info, tenant info │
19
19
  │ │
20
+ │ /soajs/mcp │
21
+ │ ├── Method: POST │
22
+ │ ├── Purpose: MCP endpoint, the caller's APIs as tools for AI agents │
23
+ │ ├── Requires: External key, OAuth token, mcp.enabled in custom registry │
24
+ │ └── Returns: JSON-RPC answers (initialize, tools/list, tools/call) │
25
+ │ │
20
26
  │ /soajs/proxy │
21
27
  │ ├── Method: Any │
22
28
  │ ├── Purpose: Proxy requests to external URLs or cross-environment │
@@ -246,7 +252,19 @@ curl -X POST "http://api.example.com/soajs/proxy?proxyRoute=%2Fservice%2Fapi&__e
246
252
 
247
253
  ---
248
254
 
249
- ## 3. Deprecated: `/proxy/redirect`
255
+ ## 3. MCP Route (`/soajs/mcp`)
256
+
257
+ **Endpoint:** `POST /soajs/mcp`
258
+
259
+ **Purpose:** AI agents connect with the Model Context Protocol and see the APIs their caller may
260
+ use as tools; a tool call is processed as the API call it stands for. Same key, OAuth and user
261
+ checks as `/soajs/acl`; off unless `mcp.enabled` is set in the gateway custom registry.
262
+
263
+ See [MCP Endpoint](mcp.md).
264
+
265
+ ---
266
+
267
+ ## 4. Deprecated: `/proxy/redirect`
250
268
 
251
269
  **Status:** DEPRECATED
252
270
 
package/docs/mcp.md ADDED
@@ -0,0 +1,140 @@
1
+ # MCP Endpoint (`/soajs/mcp`)
2
+
3
+ AI agents connect to the gateway with the Model Context Protocol (MCP) and see the APIs their
4
+ caller may use as tools. A tool call is processed as the API call it stands for, so it gets every
5
+ check an API call gets.
6
+
7
+ ## How It Works
8
+
9
+ ```
10
+ Agent (MCP client)
11
+ │ POST /soajs/mcp key + token
12
+ ▼
13
+ Gateway
14
+ ├─ key, OAuth and user checks (as for /soajs/acl)
15
+ ├─ tools/list → the caller's ACL (/soajs/acl logic)
16
+ │ + GET /mcp/tools of each allowed service, cached per service and version
17
+ └─ tools/call → the API call, sent to the gateway's own port with the caller's credentials:
18
+ key, OAuth, tenant access resolver, ACL, throttling, idempotency, cache, service
19
+ ```
20
+
21
+ ### Where the tools come from
22
+
23
+ Every service answers `GET /mcp/tools` on its maintenance port (the port awareness checks the
24
+ heartbeat on: `maintenance`, `inherit` or a custom port):
25
+
26
+ | Service | Answer |
27
+ |---------|--------|
28
+ | Built on `soajs` (4.1.24+) | `data.tools`: built from its `config.js` schema |
29
+ | Built with an SDK (`soajs.nodejs` 2.0.4+, `soajs.python` 1.0.1+, `soajs.golang` v1.0.5+) | `data.swagger`: its Swagger 2.0 / OpenAPI 3 document, an object or YAML text, converted with `soajs.core.libs` `mcp.fromSwagger` |
30
+ | Older, or no answer | no tools |
31
+
32
+ Tools are cached per service and version for `ttl`; a service that did not answer is asked again
33
+ after 30 seconds.
34
+
35
+ ### Which tools a caller sees
36
+
37
+ The ACL the gateway computes for `/soajs/acl`: the user's ACL and packages, else the tenant
38
+ application's, else the package's. For each service, the latest allowed version; a restricted
39
+ service shows only the APIs its ACL lists, per method. To give agents a different list from your
40
+ apps, give the agent its own application, key and package in the tenant.
41
+
42
+ A tool call is checked again by the gateway like any API call, so a tool shown by mistake is still
43
+ refused.
44
+
45
+ ## Configuration
46
+
47
+ Off unless enabled, in the `gateway` custom registry (`registry.custom.gateway.value.mcp`):
48
+
49
+ ```json
50
+ {
51
+ "mcp": {
52
+ "enabled": true,
53
+ "ttl": 300000,
54
+ "timeout": 10000,
55
+ "maxTools": 300
56
+ }
57
+ }
58
+ ```
59
+
60
+ | Field | Default | Description |
61
+ |-------|---------|-------------|
62
+ | `enabled` | — | `true` turns the endpoint on; anything else answers 404/190 |
63
+ | `ttl` | `300000` | How long the tools of a service are cached, ms |
64
+ | `timeout` | `10000` | Fetching tools and calling a tool, ms |
65
+ | `maxTools` | `300` | At most this many tools in a list |
66
+
67
+ ## Protocol
68
+
69
+ MCP over HTTP: JSON-RPC 2.0, `POST /soajs/mcp`, one JSON answer per request, no session and no
70
+ streaming. `GET` answers 405.
71
+
72
+ | Method | Answer |
73
+ |--------|--------|
74
+ | `initialize` | `protocolVersion` (the client's when supported: `2025-06-18`, `2025-03-26`, `2024-11-05`, else `2025-06-18`), `capabilities.tools`, `serverInfo` |
75
+ | `notifications/*` (no `id`) | HTTP 202, no body |
76
+ | `ping` | `{}` |
77
+ | `tools/list` | `{tools}` |
78
+ | `tools/call` | `{content, structuredContent, isError}` |
79
+
80
+ ### Tools
81
+
82
+ | Field | Value |
83
+ |-------|-------|
84
+ | `name` | `<service>_<operationId>`, or `<service>_<method>_<path>`; `[A-Za-z0-9_-]`, 64 characters at most, unique |
85
+ | `title` | The API label |
86
+ | `description` | The label and `[service: METHOD /path]` |
87
+ | `inputSchema` | JSON Schema of the inputs: path, query and body fields in one object |
88
+ | `annotations` | `readOnlyHint` for GET, `destructiveHint` for DELETE |
89
+
90
+ ### Tool calls
91
+
92
+ The arguments are split back into the path, the query string and the body, and the call goes to
93
+ `http://127.0.0.1:<gateway port>/<service>/v<version><path>` with the caller's `key`,
94
+ `authorization` / `access_token`, `device-id`, `user-agent`, `accept-language` and
95
+ `idempotency-key` headers and `x-forwarded-for`.
96
+
97
+ - **Success:** `content` has the response `data` as JSON text, `structuredContent` the same when it
98
+ is an object.
99
+ - **Error** (HTTP 4xx/5xx or `result: false`): `isError: true`, `content` has the `errors`.
100
+
101
+ ### Errors
102
+
103
+ | Case | Answer |
104
+ |------|--------|
105
+ | MCP not enabled | HTTP 404, SOAJS error 190 |
106
+ | No `key` header | SOAJS error 132 |
107
+ | No signed-in user | as `/soajs/acl` |
108
+ | Not POST | HTTP 405, JSON-RPC -32600 |
109
+ | Body not JSON | HTTP 400, JSON-RPC -32700 |
110
+ | Batch, invalid request | HTTP 400, JSON-RPC -32600 |
111
+ | Body over 1 MB | HTTP 413 |
112
+ | Unknown method | JSON-RPC -32601 |
113
+ | Unknown tool, no tool name | JSON-RPC -32602 |
114
+
115
+ ## Client Setup
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "soajs": {
121
+ "url": "https://api.example.com/soajs/mcp",
122
+ "headers": {
123
+ "key": "<tenant external key>",
124
+ "Authorization": "Bearer <access token>"
125
+ }
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ The key can also be sent in the query string (`/soajs/mcp?key=...`) for clients that only take a
132
+ URL.
133
+
134
+ ## Files
135
+
136
+ | File | Role |
137
+ |------|------|
138
+ | `mw/soajsRoutes/mcp/index.js` | The route: JSON-RPC handling |
139
+ | `mw/soajsRoutes/mcp/tools.js` | Tools: fetch, cache, ACL filter, naming, the call |
140
+ | `mw/soajsRoutes/lib/acl.js` | The ACL computation shared with `/soajs/acl` |
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  const gtw_keyACL = require("./../soajsRoutes/keyACL/index.js");
12
+ const gtw_mcp = require("./../soajsRoutes/mcp/index.js");
12
13
 
13
14
  /**
14
15
  *
@@ -26,6 +27,13 @@ module.exports = (configuration) => {
26
27
  "core": core
27
28
  });
28
29
 
30
+ let soajs_mcp = gtw_mcp({
31
+ "provision": provision,
32
+ "serviceName": configuration.serviceName,
33
+ "serviceVersion": configuration.serviceVersion,
34
+ "core": core
35
+ });
36
+
29
37
  let extractBuildParameters = require("./lib/extractBuildParameters.js");
30
38
 
31
39
  let simpleRTS = require("./simpleRTS.js")(configuration);
@@ -43,8 +51,11 @@ module.exports = (configuration) => {
43
51
 
44
52
  //check if route is soajs/acl then you also need to bypass the exctract Build Param BL
45
53
  let url_keyACL = (serviceInfo[1] === 'soajs' && serviceInfo[2] === 'acl');
54
+ let url_mcp = (serviceInfo[1] === 'soajs' && serviceInfo[2] === 'mcp');
46
55
  if (url_keyACL) {
47
56
  return soajs_keyACL(req, res, next);
57
+ } else if (url_mcp) {
58
+ return soajs_mcp(req, res, next);
48
59
  } else {
49
60
  //check if proxy/redirect
50
61
  //create proxy info object before calling extractbuildparams
package/mw/mt/index.js CHANGED
@@ -42,7 +42,9 @@ module.exports = (configuration) => {
42
42
  } else if (urlInfo[1] === 'soajs' && urlInfo[2] === 'proxy') {
43
43
  proxy = true;
44
44
  }
45
- let url_keyACL = (urlInfo[1] === 'soajs' && urlInfo[2] === 'acl');
45
+ //NOTE: /soajs/acl and /soajs/mcp are apis of the gateway: key, oauth and urac checks only, the
46
+ // route itself works with the caller's ACL
47
+ let url_keyACL = (urlInfo[1] === 'soajs' && (urlInfo[2] === 'acl' || urlInfo[2] === 'mcp'));
46
48
 
47
49
  let serviceParam = {};
48
50
  if (!proxy) {
package/mw/mt/utils.js CHANGED
@@ -15,7 +15,7 @@ const coreLibs = require("soajs.core.libs");
15
15
 
16
16
  const async = require("async");
17
17
  const Netmask = require('netmask').Netmask;
18
- const useragent = require('useragent');
18
+ const UAParser = require('ua-parser-js');
19
19
  const merge = require('merge');
20
20
 
21
21
  const UracDriver = require("./urac.js");
@@ -49,6 +49,91 @@ function getCachedNetmask(addr) {
49
49
  return netmaskCache.get(addr);
50
50
  }
51
51
 
52
+ // Device rules were written against the useragent package names; map ua-parser-js names back to them
53
+ const UA_MAX_LENGTH = 512;
54
+ const UA_OS_FAMILIES = {"Mac OS": "Mac OS X", "Chromium OS": "Chrome OS"};
55
+ const UA_ANDROID_FAMILIES = {"Firefox": "Firefox Mobile", "Opera": "Opera Mobile", "Chrome WebView": "Chrome Mobile WebView"};
56
+ const UA_IOS_FAMILIES = {"Chrome": "Chrome Mobile iOS", "Firefox": "Firefox iOS", "WebKit": "Mobile Safari UI/WKWebView"};
57
+ const UA_FAMILIES = {"Yandex": "Yandex Browser"};
58
+
59
+ // Own keys only: the family comes from the caller and must not resolve to Object.prototype members
60
+ function mapName(map, name) {
61
+ return Object.prototype.hasOwnProperty.call(map, name) ? map[name] : name;
62
+ }
63
+
64
+ function splitVersion(version) {
65
+ let parts = (version || "").split(".");
66
+ return {
67
+ "major": parts[0] || "0",
68
+ "minor": parts[1] || "0",
69
+ "patch": parts[2] || "0"
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Parses a User-Agent into {family, major, minor, patch, os: {family, major, minor, patch}}
75
+ *
76
+ * @param {String} ua
77
+ * @returns {Object}
78
+ */
79
+ function parseUserAgent(ua) {
80
+ ua = String(ua).substring(0, UA_MAX_LENGTH);
81
+ let result = new UAParser(ua).getResult();
82
+ let family = result.browser.name;
83
+ let version = result.browser.version;
84
+ let osFamily = result.os.name;
85
+ let osVersion = result.os.version;
86
+ let match;
87
+
88
+ if (!family) {
89
+ // ua-parser-js guesses iOS from Darwin/CFNetwork without a version
90
+ if (osFamily === "iOS" && !osVersion) {
91
+ osFamily = null;
92
+ }
93
+ // non browser clients: curl/8.4.0, MyApp/142 CFNetwork/...
94
+ match = /^Mozilla\//.test(ua) ? null : /^([^\/\s]+)\/([\d.]+)/.exec(ua);
95
+ family = match ? match[1] : "Other";
96
+ version = match ? match[2] : null;
97
+ }
98
+ if (family === "Edge") {
99
+ if (/ Edg(A)?\//.test(ua)) {
100
+ match = /Chrome\/([\d.]+)/.exec(ua);
101
+ family = "Chrome";
102
+ version = match ? match[1] : null;
103
+ } else if (/ EdgiOS\//.test(ua)) {
104
+ match = /Version\/([\d.]+)/.exec(ua);
105
+ family = "Mobile Safari";
106
+ version = match ? match[1] : null;
107
+ }
108
+ }
109
+ if (osFamily === "Android") {
110
+ // useragent reported every Android version as 0.0.0, keep that so os version rules do not change
111
+ osVersion = null;
112
+ if (family === "Chrome" && / Mobile /.test(ua)) {
113
+ family = "Chrome Mobile";
114
+ } else {
115
+ family = mapName(UA_ANDROID_FAMILIES, family);
116
+ }
117
+ } else if (osFamily === "iOS") {
118
+ if (family === "WebKit") {
119
+ version = osVersion;
120
+ }
121
+ family = mapName(UA_IOS_FAMILIES, family);
122
+ }
123
+ family = mapName(UA_FAMILIES, family);
124
+
125
+ let uaObj = splitVersion(version);
126
+ uaObj.family = String(family);
127
+ if (osFamily === "Windows") {
128
+ // Windows versions are names: 10, 8.1, XP, Vista
129
+ uaObj.os = {"family": osFamily, "major": osVersion || "0", "minor": "0", "patch": "0"};
130
+ } else {
131
+ uaObj.os = splitVersion(osVersion);
132
+ uaObj.os.family = osFamily ? String(mapName(UA_OS_FAMILIES, osFamily)) : "Other";
133
+ }
134
+ return uaObj;
135
+ }
136
+
52
137
  let sensitiveEnvCodes = ["dashboard"];
53
138
  if (process.env.SOAJS_SENSITIVE_ENVS) {
54
139
  let temp_sensitiveEnvCodes = null;
@@ -381,7 +466,7 @@ let utils = {
381
466
  };
382
467
 
383
468
  let checkAccess = (deviceAccessArr, ua) => {
384
- let uaObj = useragent.lookup(ua);
469
+ let uaObj = parseUserAgent(ua);
385
470
  //if (uaObj && uaObj.family && uaObj.os && uaObj.os.family) {
386
471
  if (uaObj && uaObj.family) {
387
472
  return (deviceAccessArr.some(function (da) {
@@ -8,71 +8,13 @@
8
8
  * found in the LICENSE file at the root of this repository
9
9
  */
10
10
 
11
- const coreLibs = require("soajs.core.libs");
11
+ const aclLib = require("../lib/acl.js");
12
12
 
13
13
  module.exports = (configuration) => {
14
14
  let core = configuration.core;
15
15
 
16
16
  let return_SOAJS_keyACL = (req) => {
17
- let ACL = null;
18
- let ALLOWED_PACKAGES = null;
19
- if (req.soajs.uracDriver) {
20
- ACL = req.soajs.uracDriver.getAcl();
21
- ALLOWED_PACKAGES = req.soajs.uracDriver.getAllowedPackages();
22
- req.soajs.log.debug("soajsRoute keyACL: uracDriver detected.", ALLOWED_PACKAGES);
23
- }
24
- if (!ALLOWED_PACKAGES && req.soajs.controller.serviceParams.keyObj.application.package) {
25
- ALLOWED_PACKAGES = [req.soajs.controller.serviceParams.keyObj.application.package];
26
- req.soajs.log.debug("soajsRoute keyACL: Found PACKAGE ACL at Tenant Application level, overriding default PACKAGE ACL configuration.", ALLOWED_PACKAGES);
27
- }
28
- if (!ACL && req.soajs.controller.serviceParams.keyObj.application.acl) {
29
- ACL = req.soajs.controller.serviceParams.keyObj.application.acl;
30
- req.soajs.log.debug("soajsRoute keyACL: Found ACL at Tenant Application level, overriding default ACL configuration.");
31
- }
32
- if (!ACL && req.soajs.controller.serviceParams.packObj.acl) {
33
- req.soajs.log.debug("soajsRoute keyACL: Found Default ACL at Package level, setting default ACL configuration.");
34
- ACL = coreLibs.utils.cloneObj(req.soajs.controller.serviceParams.packObj.acl);
35
- }
36
- let finalACL = null;
37
- if (ACL) {
38
- finalACL = {};
39
- for (let s in ACL) {
40
- if (ACL.hasOwnProperty(s)) {
41
- for (let v in ACL[s]) {
42
- if (ACL[s].hasOwnProperty(v)) {
43
- let sv = ACL[s][v];
44
- if (finalACL[s] && finalACL[s].version) {
45
- let version = coreLibs.version.getLatest(finalACL[s].version, v);
46
- if (version !== v) {
47
- continue;
48
- }
49
- }
50
- finalACL[s] = {
51
- "version": v
52
- };
53
- if (sv.apisPermission && sv.apisPermission === "restricted") {
54
- for (let m in sv) {
55
- if (sv.hasOwnProperty(m)) {
56
- if (m !== "access" && m !== "apisPermission") {
57
- if (sv[m].apis) {
58
- for (let a in sv[m].apis) {
59
- if (sv[m].apis.hasOwnProperty(a)) {
60
- if (!finalACL[s][m]) {
61
- finalACL[s][m] = [];
62
- }
63
- finalACL[s][m].push(a);
64
- }
65
- }
66
- }
67
- }
68
- }
69
- }
70
- }
71
- }
72
- }
73
- }
74
- }
75
- }
17
+ let computed = aclLib.computeACL(req);
76
18
 
77
19
  let urac = null;
78
20
  if (req.soajs.uracDriver) {
@@ -98,9 +40,9 @@ module.exports = (configuration) => {
98
40
  }
99
41
 
100
42
  let response = {
101
- "acl": ACL,
102
- "finalACL": finalACL,
103
- "packages": ALLOWED_PACKAGES,
43
+ "acl": computed.acl,
44
+ "finalACL": computed.finalACL,
45
+ "packages": computed.packages,
104
46
  "urac": urac,
105
47
  "tenant": tenant
106
48
  };
@@ -114,30 +56,12 @@ module.exports = (configuration) => {
114
56
  return req.soajs.controllerResponse(core.error.getError(132));
115
57
  }
116
58
 
117
- //mimic a service named controller with route /key/permission/get
118
- let serviceName = configuration.serviceName;
119
- req.soajs.controller.serviceParams.registry = req.soajs.registry.services[serviceName];
120
- req.soajs.controller.serviceParams.name = serviceName;
121
- req.soajs.controller.serviceParams.url = "/soajs/acl";
122
- req.soajs.controller.serviceParams.path = "/soajs/acl";
123
- req.soajs.controller.serviceParams.version = configuration.serviceVersion;
124
- req.soajs.controller.serviceParams.extKeyRequired = true;
125
- req.soajs.controller.serviceParams.registry.versions = {
126
- [configuration.serviceVersion]: {
127
- "extKeyRequired": true,
128
- "oauth": true,
129
- "urac": true,
130
- "urac_ACL": true,
131
- "provision_ACL": true,
132
- "apis": [
133
- {
134
- "l": "Get ACL",
135
- "v": "/soajs/acl",
136
- "m": "get"
137
- }
138
- ]
139
- }
140
- };
59
+ //mimic an api of the gateway, so mt runs the key, oauth and urac checks
60
+ aclLib.mimicGatewayApi(req, configuration, {
61
+ "l": "Get ACL",
62
+ "v": "/soajs/acl",
63
+ "m": "get"
64
+ });
141
65
 
142
66
  //assign the correct method to gotoservice in controller
143
67
  req.soajs.controller.gotoservice = return_SOAJS_keyACL;
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @license
5
+ * Copyright SOAJS All Rights Reserved.
6
+ *
7
+ * Use of this source code is governed by an Apache license that can be
8
+ * found in the LICENSE file at the root of this repository
9
+ */
10
+
11
+ const coreLibs = require("soajs.core.libs");
12
+
13
+ /**
14
+ * What the caller may use, the way the gateway decides it: the user's ACL and packages, else the
15
+ * tenant application's, else the package's. Shared by /soajs/acl and /soajs/mcp.
16
+ *
17
+ * finalACL is, per service, the latest allowed version and, for a restricted service, the allowed
18
+ * apis per method: {"urac": {"version": "3", "get": ["/user"]}}. A service that is not restricted
19
+ * has only its version: all its apis are allowed.
20
+ *
21
+ * @param req
22
+ * @returns {{acl: object|null, finalACL: object|null, packages: Array|null}}
23
+ */
24
+ function computeACL(req) {
25
+ let ACL = null;
26
+ let ALLOWED_PACKAGES = null;
27
+ if (req.soajs.uracDriver) {
28
+ ACL = req.soajs.uracDriver.getAcl();
29
+ ALLOWED_PACKAGES = req.soajs.uracDriver.getAllowedPackages();
30
+ req.soajs.log.debug("soajsRoute ACL: uracDriver detected.", ALLOWED_PACKAGES);
31
+ }
32
+ if (!ALLOWED_PACKAGES && req.soajs.controller.serviceParams.keyObj.application.package) {
33
+ ALLOWED_PACKAGES = [req.soajs.controller.serviceParams.keyObj.application.package];
34
+ req.soajs.log.debug("soajsRoute ACL: Found PACKAGE ACL at Tenant Application level, overriding default PACKAGE ACL configuration.", ALLOWED_PACKAGES);
35
+ }
36
+ if (!ACL && req.soajs.controller.serviceParams.keyObj.application.acl) {
37
+ ACL = req.soajs.controller.serviceParams.keyObj.application.acl;
38
+ req.soajs.log.debug("soajsRoute ACL: Found ACL at Tenant Application level, overriding default ACL configuration.");
39
+ }
40
+ if (!ACL && req.soajs.controller.serviceParams.packObj.acl) {
41
+ req.soajs.log.debug("soajsRoute ACL: Found Default ACL at Package level, setting default ACL configuration.");
42
+ ACL = coreLibs.utils.cloneObj(req.soajs.controller.serviceParams.packObj.acl);
43
+ }
44
+ let finalACL = null;
45
+ if (ACL) {
46
+ finalACL = {};
47
+ for (let s in ACL) {
48
+ if (ACL.hasOwnProperty(s)) {
49
+ for (let v in ACL[s]) {
50
+ if (ACL[s].hasOwnProperty(v)) {
51
+ let sv = ACL[s][v];
52
+ if (finalACL[s] && finalACL[s].version) {
53
+ let version = coreLibs.version.getLatest(finalACL[s].version, v);
54
+ if (version !== v) {
55
+ continue;
56
+ }
57
+ }
58
+ finalACL[s] = {
59
+ "version": v
60
+ };
61
+ if (sv.apisPermission && sv.apisPermission === "restricted") {
62
+ for (let m in sv) {
63
+ if (sv.hasOwnProperty(m)) {
64
+ if (m !== "access" && m !== "apisPermission") {
65
+ // {apis: {...}} or, grouped, [{group, apis: {...}}]
66
+ let groups = Array.isArray(sv[m]) ? sv[m] : [sv[m]];
67
+ for (let g = 0; g < groups.length; g++) {
68
+ let group = groups[g];
69
+ if (group && group.apis) {
70
+ for (let a in group.apis) {
71
+ if (group.apis.hasOwnProperty(a)) {
72
+ if (!finalACL[s][m]) {
73
+ finalACL[s][m] = [];
74
+ }
75
+ if (finalACL[s][m].indexOf(a) === -1) {
76
+ finalACL[s][m].push(a);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return {
92
+ "acl": ACL,
93
+ "finalACL": finalACL,
94
+ "packages": ALLOWED_PACKAGES
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Makes the request look like a call to an api of the gateway itself, so mt runs the key, oauth
100
+ * and urac checks for it. The registry entry is a copy: the shared registry is not changed.
101
+ *
102
+ * @param req
103
+ * @param configuration {serviceName, serviceVersion}
104
+ * @param api {l, v, m}
105
+ */
106
+ function mimicGatewayApi(req, configuration, api) {
107
+ let serviceName = configuration.serviceName;
108
+ let registry = Object.assign({}, req.soajs.registry.services[serviceName]);
109
+ registry.versions = {
110
+ [configuration.serviceVersion]: {
111
+ "extKeyRequired": true,
112
+ "oauth": true,
113
+ "urac": true,
114
+ "urac_ACL": true,
115
+ "provision_ACL": true,
116
+ "apis": [api]
117
+ }
118
+ };
119
+ req.soajs.controller.serviceParams.registry = registry;
120
+ req.soajs.controller.serviceParams.name = serviceName;
121
+ req.soajs.controller.serviceParams.url = api.v;
122
+ req.soajs.controller.serviceParams.path = api.v;
123
+ req.soajs.controller.serviceParams.version = configuration.serviceVersion;
124
+ req.soajs.controller.serviceParams.extKeyRequired = true;
125
+ }
126
+
127
+ module.exports = {
128
+ computeACL,
129
+ mimicGatewayApi
130
+ };