soajs.controller 4.3.2 → 4.3.4
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/lib/parseURL.js +16 -3
- package/mw/lastSeen/index.js +18 -7
- package/mw/mt/lib.js +17 -17
- package/mw/soajsRoutes/keyACL/index.js +1 -1
- package/mw/url/index.js +16 -2
- package/package.json +3 -3
- package/server/maintenance.js +17 -3
- package/test/integration/UC4-multiuser-acl/readme.md +11 -0
- package/test/unit/lib/parseURL.js +18 -4
package/lib/parseURL.js
CHANGED
|
@@ -8,7 +8,20 @@
|
|
|
8
8
|
* found in the LICENSE file at the root of this repository
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
|
|
13
|
+
* @param {string} urlString - URL string to parse
|
|
14
|
+
* @returns {Object} - Object with pathname, path, and query properties
|
|
15
|
+
*/
|
|
16
|
+
const parseURL = (urlString) => {
|
|
17
|
+
const urlObj = new URL(urlString, 'http://localhost');
|
|
18
|
+
const search = urlObj.search || '';
|
|
19
|
+
return {
|
|
20
|
+
pathname: urlObj.pathname,
|
|
21
|
+
path: urlObj.pathname + search,
|
|
22
|
+
query: Object.fromEntries(urlObj.searchParams)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
12
25
|
|
|
13
26
|
|
|
14
27
|
module.exports = (uri, parsedUrl) => {
|
|
@@ -16,7 +29,7 @@ module.exports = (uri, parsedUrl) => {
|
|
|
16
29
|
let service_nv = serviceInfo[1];
|
|
17
30
|
let service_n = service_nv;
|
|
18
31
|
let service_v = null;
|
|
19
|
-
|
|
32
|
+
|
|
20
33
|
//check if there is /v1 or v1.1 in the url
|
|
21
34
|
let matches = uri.match(/\/v[0-9]+(.[0-9]+)?\//);
|
|
22
35
|
if (matches && matches.length > 0) {
|
|
@@ -25,7 +38,7 @@ module.exports = (uri, parsedUrl) => {
|
|
|
25
38
|
service_v = hit.replace("v", '');
|
|
26
39
|
serviceInfo.splice(2, 1);
|
|
27
40
|
uri = uri.replace(matches[0], "/");
|
|
28
|
-
parsedUrl =
|
|
41
|
+
parsedUrl = parseURL(uri);
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
|
package/mw/lastSeen/index.js
CHANGED
|
@@ -52,11 +52,12 @@ function getRouteRegExp(route) {
|
|
|
52
52
|
* Check if the current request should trigger lastSeen
|
|
53
53
|
* @param {Object} include - The include whitelist configuration
|
|
54
54
|
* @param {string} serviceName - The current service name
|
|
55
|
-
* @param {string} apiPath - The current API path
|
|
55
|
+
* @param {string} apiPath - The current API path (relative to service, e.g., "/active")
|
|
56
|
+
* @param {string} fullPath - The full API path (e.g., "/connectspaces/active")
|
|
56
57
|
* @param {string} method - The HTTP method (GET, POST, etc.)
|
|
57
58
|
* @returns {boolean} - true if lastSeen should be triggered
|
|
58
59
|
*/
|
|
59
|
-
function shouldTriggerLastSeen(include, serviceName, apiPath, method) {
|
|
60
|
+
function shouldTriggerLastSeen(include, serviceName, apiPath, fullPath, method) {
|
|
60
61
|
// No include filter = trigger for all (backward compatible)
|
|
61
62
|
if (!include) {
|
|
62
63
|
return true;
|
|
@@ -74,9 +75,17 @@ function shouldTriggerLastSeen(include, serviceName, apiPath, method) {
|
|
|
74
75
|
return true;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
// API-level match
|
|
78
|
+
// API-level match - check both relative path and full path for backward compatibility
|
|
78
79
|
if (typeof serviceConfig === 'object' && serviceConfig.apis) {
|
|
79
|
-
|
|
80
|
+
// Try relative path first (e.g., "/active")
|
|
81
|
+
if (apiPath && isApiMethodMatch(serviceConfig.apis, apiPath, method)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// Fallback to full path for backward compatibility (e.g., "/connectspaces/active")
|
|
85
|
+
if (fullPath && fullPath !== apiPath && isApiMethodMatch(serviceConfig.apis, fullPath, method)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
return false;
|
|
@@ -150,11 +159,13 @@ module.exports = () => {
|
|
|
150
159
|
|
|
151
160
|
// Check include filter
|
|
152
161
|
let currentService = get(["soajs", "controller", "serviceParams", "name"], req);
|
|
153
|
-
|
|
162
|
+
// Get both relative path (e.g., "/active") and full path (e.g., "/connectspaces/active")
|
|
163
|
+
let currentApi = get(["soajs", "controller", "serviceParams", "path"], req);
|
|
164
|
+
let fullPath = get(["soajs", "controller", "serviceParams", "parsedUrl", "pathname"], req);
|
|
154
165
|
let currentMethod = req.method;
|
|
155
166
|
|
|
156
|
-
if (!shouldTriggerLastSeen(lastSeen.include, currentService, currentApi, currentMethod)) {
|
|
157
|
-
req.soajs.log.debug("lastSeen skipped for [" + currentService + " " + currentMethod + " " + currentApi + "]");
|
|
167
|
+
if (!shouldTriggerLastSeen(lastSeen.include, currentService, currentApi, fullPath, currentMethod)) {
|
|
168
|
+
req.soajs.log.debug("lastSeen skipped for [" + currentService + " " + currentMethod + " " + (currentApi || fullPath) + "]");
|
|
158
169
|
return;
|
|
159
170
|
}
|
|
160
171
|
|
package/mw/mt/lib.js
CHANGED
|
@@ -13,7 +13,7 @@ let _ = require("lodash");
|
|
|
13
13
|
|
|
14
14
|
module.exports = {
|
|
15
15
|
"mergeACLArray": function (arrayACL, cb) {
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
//if only one return it
|
|
18
18
|
if (arrayACL.length === 1) {
|
|
19
19
|
return cb(null, arrayACL[0]);
|
|
@@ -30,30 +30,30 @@ module.exports = {
|
|
|
30
30
|
function () {
|
|
31
31
|
return cb(null, acl);
|
|
32
32
|
});
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
function mergePackageAcl(oneAcl, cb) {
|
|
35
35
|
//first object
|
|
36
36
|
if (!acl.acl) {
|
|
37
|
-
acl.acl = oneAcl.acl;
|
|
37
|
+
acl.acl = _.cloneDeep(oneAcl.acl);
|
|
38
38
|
return cb();
|
|
39
39
|
}
|
|
40
40
|
async.eachOfSeries(oneAcl.acl, function (service, serviceName, serviceCall) {
|
|
41
41
|
if (!acl.acl[serviceName]) {
|
|
42
|
-
acl.acl[serviceName] = service;
|
|
42
|
+
acl.acl[serviceName] = _.cloneDeep(service);
|
|
43
43
|
return serviceCall();
|
|
44
44
|
}
|
|
45
45
|
async.eachOfSeries(service, function (version, versionValue, versionCall) {
|
|
46
46
|
if (!acl.acl[serviceName][versionValue]) {
|
|
47
|
-
acl.acl[serviceName][versionValue] = version;
|
|
47
|
+
acl.acl[serviceName][versionValue] = _.cloneDeep(version);
|
|
48
48
|
return serviceCall();
|
|
49
49
|
}
|
|
50
50
|
if (version.apisPermission === "restricted") {
|
|
51
51
|
if (acl.acl[serviceName] && acl.acl[serviceName][versionValue] && acl.acl[serviceName][versionValue].apisPermission && acl.acl[serviceName][versionValue].apisPermission === "restricted") {
|
|
52
52
|
//only restricted allowed to merge
|
|
53
|
-
acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], version);
|
|
53
|
+
acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], _.cloneDeep(version));
|
|
54
54
|
return versionCall();
|
|
55
55
|
} else {
|
|
56
|
-
acl.acl[serviceName][versionValue] = version;
|
|
56
|
+
acl.acl[serviceName][versionValue] = _.cloneDeep(version);
|
|
57
57
|
return versionCall();
|
|
58
58
|
}
|
|
59
59
|
} else {
|
|
@@ -62,42 +62,42 @@ module.exports = {
|
|
|
62
62
|
return versionCall();
|
|
63
63
|
} else {
|
|
64
64
|
//only non restricted allowed to merge
|
|
65
|
-
acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], version);
|
|
65
|
+
acl.acl[serviceName][versionValue] = _.merge(acl.acl[serviceName][versionValue], _.cloneDeep(version));
|
|
66
66
|
return versionCall();
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}, serviceCall);
|
|
70
70
|
}, cb);
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
function mergePackageAclAllEnv(oneAcl, cb) {
|
|
74
74
|
//first object
|
|
75
75
|
if (!acl.acl_all_env) {
|
|
76
|
-
acl.acl_all_env = oneAcl.acl_all_env;
|
|
76
|
+
acl.acl_all_env = _.cloneDeep(oneAcl.acl_all_env);
|
|
77
77
|
return cb();
|
|
78
78
|
}
|
|
79
79
|
async.eachOf(oneAcl.acl_all_env, function (environment, envName, envCall) {
|
|
80
80
|
if (!acl.acl_all_env[envName]) {
|
|
81
|
-
acl.acl_all_env[envName] = environment;
|
|
81
|
+
acl.acl_all_env[envName] = _.cloneDeep(environment);
|
|
82
82
|
return envCall();
|
|
83
83
|
}
|
|
84
84
|
async.eachOf(environment, function (service, serviceName, serviceCall) {
|
|
85
85
|
if (!acl.acl_all_env[envName][serviceName]) {
|
|
86
|
-
acl.acl_all_env[envName][serviceName] = service;
|
|
86
|
+
acl.acl_all_env[envName][serviceName] = _.cloneDeep(service);
|
|
87
87
|
return serviceCall();
|
|
88
88
|
}
|
|
89
89
|
async.eachOf(service, function (version, versionValue, versionCall) {
|
|
90
90
|
if (!acl.acl_all_env[envName][serviceName][versionValue]) {
|
|
91
|
-
acl.acl_all_env[envName][serviceName][versionValue] = version;
|
|
91
|
+
acl.acl_all_env[envName][serviceName][versionValue] = _.cloneDeep(version);
|
|
92
92
|
return serviceCall();
|
|
93
93
|
}
|
|
94
94
|
if (version.apisPermission === "restricted") {
|
|
95
95
|
if (acl.acl_all_env[envName][serviceName][versionValue].apisPermission && acl.acl_all_env[envName][serviceName][versionValue].apisPermission === "restricted") {
|
|
96
96
|
//only restricted allowed to merge
|
|
97
|
-
acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], version);
|
|
97
|
+
acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], _.cloneDeep(version));
|
|
98
98
|
return versionCall();
|
|
99
99
|
} else {
|
|
100
|
-
acl.acl_all_env[envName][serviceName][versionValue] = version;
|
|
100
|
+
acl.acl_all_env[envName][serviceName][versionValue] = _.cloneDeep(version);
|
|
101
101
|
return versionCall();
|
|
102
102
|
}
|
|
103
103
|
} else {
|
|
@@ -106,7 +106,7 @@ module.exports = {
|
|
|
106
106
|
return versionCall();
|
|
107
107
|
} else {
|
|
108
108
|
//only non restricted allowed to merge
|
|
109
|
-
acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], version);
|
|
109
|
+
acl.acl_all_env[envName][serviceName][versionValue] = _.merge(acl.acl_all_env[envName][serviceName][versionValue], _.cloneDeep(version));
|
|
110
110
|
return versionCall();
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -115,4 +115,4 @@ module.exports = {
|
|
|
115
115
|
}, cb);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
};
|
|
118
|
+
};
|
|
@@ -31,7 +31,7 @@ module.exports = (configuration) => {
|
|
|
31
31
|
}
|
|
32
32
|
if (!ACL && req.soajs.controller.serviceParams.packObj.acl) {
|
|
33
33
|
req.soajs.log.debug("soajsRoute keyACL: Found Default ACL at Package level, setting default ACL configuration.");
|
|
34
|
-
ACL = req.soajs.controller.serviceParams.packObj.acl;
|
|
34
|
+
ACL = coreLibs.utils.cloneObj(req.soajs.controller.serviceParams.packObj.acl);
|
|
35
35
|
}
|
|
36
36
|
let finalACL = null;
|
|
37
37
|
if (ACL) {
|
package/mw/url/index.js
CHANGED
|
@@ -8,9 +8,23 @@
|
|
|
8
8
|
* found in the LICENSE file at the root of this repository
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const url = require('url');
|
|
12
11
|
let libParseURL = require('../../lib/parseURL');
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
|
|
15
|
+
* @param {string} urlString - URL string to parse
|
|
16
|
+
* @returns {Object} - Object with pathname, path, and query properties
|
|
17
|
+
*/
|
|
18
|
+
const parseURL = (urlString) => {
|
|
19
|
+
const urlObj = new URL(urlString, 'http://localhost');
|
|
20
|
+
const search = urlObj.search || '';
|
|
21
|
+
return {
|
|
22
|
+
pathname: urlObj.pathname,
|
|
23
|
+
path: urlObj.pathname + search,
|
|
24
|
+
query: Object.fromEntries(urlObj.searchParams)
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
14
28
|
/**
|
|
15
29
|
* Assure req param (soajs & soajs.controller)
|
|
16
30
|
* Parse url and fetch service info (soajs.controller.serviceParams)
|
|
@@ -30,7 +44,7 @@ module.exports = (configuration) => {
|
|
|
30
44
|
req.soajs.controller = {};
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
let parsedUrl =
|
|
47
|
+
let parsedUrl = parseURL(req.url);
|
|
34
48
|
if (!req.query && parsedUrl.query) {
|
|
35
49
|
req.query = parsedUrl.query;
|
|
36
50
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soajs.controller",
|
|
3
3
|
"description": "soajs multi tenant API gateway",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "soajs team",
|
|
7
7
|
"email": "team@soajs.org"
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"merge": "2.1.1",
|
|
28
28
|
"netmask": "2.0.2",
|
|
29
29
|
"oauth2-server": "2.4.1",
|
|
30
|
-
"path-to-regexp": "8.
|
|
30
|
+
"path-to-regexp": "8.4.0",
|
|
31
31
|
"soajs.core.libs": "1.2.2",
|
|
32
|
-
"soajs.core.modules": "5.2.
|
|
32
|
+
"soajs.core.modules": "5.2.19",
|
|
33
33
|
"soajs.urac.driver": "3.2.15",
|
|
34
34
|
"useragent": "2.3.0"
|
|
35
35
|
},
|
package/server/maintenance.js
CHANGED
|
@@ -14,8 +14,22 @@ const registryModule = require("./../modules/registry");
|
|
|
14
14
|
|
|
15
15
|
const httpProxy = require('http-proxy');
|
|
16
16
|
const http = require('http');
|
|
17
|
-
const url = require('url');
|
|
18
17
|
const soajsLib = require("soajs.core.libs");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
|
|
21
|
+
* @param {string} urlString - URL string to parse
|
|
22
|
+
* @returns {Object} - Object with pathname, path, and query properties
|
|
23
|
+
*/
|
|
24
|
+
const parseURL = (urlString) => {
|
|
25
|
+
const urlObj = new URL(urlString, 'http://localhost');
|
|
26
|
+
const search = urlObj.search || '';
|
|
27
|
+
return {
|
|
28
|
+
pathname: urlObj.pathname,
|
|
29
|
+
path: urlObj.pathname + search,
|
|
30
|
+
query: Object.fromEntries(urlObj.searchParams)
|
|
31
|
+
};
|
|
32
|
+
};
|
|
19
33
|
const soajsUtils = soajsLib.utils;
|
|
20
34
|
|
|
21
35
|
const packageInfo = require("../package.json");
|
|
@@ -148,7 +162,7 @@ let Maintenance = (core, log, param, serviceIp, regEnvironment, awareness_mw, so
|
|
|
148
162
|
log.error('Internal proxy error: ' + error);
|
|
149
163
|
|
|
150
164
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
151
|
-
let parsedUrl =
|
|
165
|
+
let parsedUrl = parseURL(req.url);
|
|
152
166
|
let response = maintenanceResponse(parsedUrl, param, '/proxySocket');
|
|
153
167
|
return res.end(JSON.stringify(response));
|
|
154
168
|
});
|
|
@@ -159,7 +173,7 @@ let Maintenance = (core, log, param, serviceIp, regEnvironment, awareness_mw, so
|
|
|
159
173
|
res.writeHead(200, {'Content-Type': 'image/x-icon'});
|
|
160
174
|
return res.end();
|
|
161
175
|
}
|
|
162
|
-
let parsedUrl =
|
|
176
|
+
let parsedUrl = parseURL(req.url);
|
|
163
177
|
|
|
164
178
|
if (parsedUrl.pathname === '/reloadRegistry') {
|
|
165
179
|
reloadRegistry(parsedUrl, core, log, param, serviceIp, (response) => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- i have 2 users with access to the same tenant
|
|
2
|
+
- under this tenant i have 2 groups as follow:
|
|
3
|
+
- owner with the following packages [GRPCH_DEFAU, GRPCH_OWNER]
|
|
4
|
+
- member with the following packages [GRPCH_DEFAU, GRPCH_AMMBR, GRPCH_EGDTL, GRPCH_SMDIA, GRPCH_SMSSG, GRPCH_VMMBR]
|
|
5
|
+
- i have microservice connectonboarding with an api DELETE /group/chats/users/:userId and this api is only under package GRPCH_OWNER
|
|
6
|
+
|
|
7
|
+
i have the following usecase:
|
|
8
|
+
- load the provision by hitting the maintenance route /loadProvision
|
|
9
|
+
- user1 with member group login first and hit the acl api /soajs/acl, then we check the response and we see that he has the right packages and api DELETE /group/chats/users/:userId is not part of finalACL which is correct
|
|
10
|
+
- user2 with owner group login and hit the acl api /soajs/acl, then we check the response and we see that he has the right packages and api DELETE /group/chats/users/:userId is part of finalACL which is correct
|
|
11
|
+
- user1 with member group hit the acl api /soajs/acl again, then we check the response and we see that he has the right packages but api DELETE /group/chats/users/:userId is now part of finalACL which is not correct
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const url = require('url');
|
|
4
3
|
const helper = require("../../helper.js");
|
|
5
4
|
const lib = helper.requireModule('./lib/parseURL');
|
|
6
5
|
const assert = require('assert');
|
|
7
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Parse URL using WHATWG URL API and return object compatible with legacy url.parse()
|
|
9
|
+
* @param {string} urlString - URL string to parse
|
|
10
|
+
* @returns {Object} - Object with pathname, path, and query properties
|
|
11
|
+
*/
|
|
12
|
+
const parseURL = (urlString) => {
|
|
13
|
+
const urlObj = new URL(urlString, 'http://localhost');
|
|
14
|
+
const search = urlObj.search || '';
|
|
15
|
+
return {
|
|
16
|
+
pathname: urlObj.pathname,
|
|
17
|
+
path: urlObj.pathname + search,
|
|
18
|
+
query: Object.fromEntries(urlObj.searchParams)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
8
22
|
describe("Unit test for: lib - parseURL", () => {
|
|
9
23
|
let uri = '/micro2/v1.1/hello';
|
|
10
|
-
let parsedURL =
|
|
24
|
+
let parsedURL = parseURL(uri);
|
|
11
25
|
it("Vanilla test with v1.1", (done) => {
|
|
12
26
|
let response = lib(uri, parsedURL);
|
|
13
27
|
assert.deepStrictEqual(response.name, 'micro2', "lib.parseURL vanilla test with v1.1 failed for name");
|
|
@@ -16,10 +30,10 @@ describe("Unit test for: lib - parseURL", () => {
|
|
|
16
30
|
});
|
|
17
31
|
it("Vanilla test with :1.1", (done) => {
|
|
18
32
|
uri = '/micro2:1.1/hello';
|
|
19
|
-
parsedURL =
|
|
33
|
+
parsedURL = parseURL(uri);
|
|
20
34
|
let response = lib(uri, parsedURL);
|
|
21
35
|
assert.deepStrictEqual(response.name, 'micro2', "lib.parseURL vanilla test with 1:1 failed for name");
|
|
22
36
|
assert.deepStrictEqual(response.service_v, '1.1', "lib.parseURL vanilla test with 1:1 failed version");
|
|
23
37
|
done();
|
|
24
38
|
});
|
|
25
|
-
});
|
|
39
|
+
});
|