soajs.controller 4.3.16 → 4.3.18

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
@@ -156,6 +156,7 @@ The gateway processes requests through a sequential middleware pipeline:
156
156
  | [External Integrations](docs/external-integrations.md) | OAuth, URAC, and infrastructure integrations |
157
157
  | [Deployment Architecture](docs/deployment.md) | High availability deployment patterns |
158
158
  | [Custom Registry Configuration](docs/configuration-custom-registry.md) | Environment-specific gateway settings |
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 |
159
160
  | [Services Config](docs/configuration-services-config.md) | Tenant key-specific configuration |
160
161
  | [Gateway Routes](docs/gateway-routes.md) | Built-in gateway routes (/soajs/acl, /soajs/proxy) |
161
162
  | [Maintenance Routes](docs/maintenance-routes.md) | Health check and admin endpoints |
@@ -4,7 +4,7 @@ Environment-specific gateway settings stored in the Core DB custom registry.
4
4
 
5
5
  ## Configuration Structure Overview
6
6
 
7
- These settings are accessed via `registry.custom.gateway.value.*` and `registry.custom.oauth.value.*`.
7
+ These settings are accessed via `registry.custom.gateway.value.*`, `registry.custom.oauth.value.*` and `registry.custom.urac.value.*`.
8
8
 
9
9
  ```
10
10
  registry.custom
@@ -35,11 +35,15 @@ registry.custom
35
35
  │ └── gotoService
36
36
  │ ├── monitor # Request monitoring config
37
37
  │ └── renewReqMonitorOff # Disable timeout renewal
38
- └── oauth
38
+ ├── oauth
39
+ │ └── value
40
+ │ ├── roaming # Cross-environment access
41
+ │ ├── pinWrapper # PIN-based login endpoint
42
+ │ └── pinWhitelist # PIN login API whitelist
43
+ └── urac
39
44
  └── value
40
- ├── roaming # Cross-environment access
41
- ├── pinWrapper # PIN-based login endpoint
42
- └── pinWhitelist # PIN login API whitelist
45
+ └── tenantAccessResolvers # APIs that grant access to a tenant
46
+ └── [resolver] # One API per resolver name
43
47
  ```
44
48
 
45
49
  ---
@@ -577,6 +581,185 @@ loaded from urac per request and the membership is current.
577
581
 
578
582
  ---
579
583
 
584
+ ## 15. Tenant Access Resolver
585
+
586
+ **Path:** `registry.custom.urac.value.tenantAccessResolvers`
587
+
588
+ **Middleware:** `mw/mt/urac.js`, called by `mw/mt/tenantAccess.js`
589
+
590
+ Gives a group of users access to a tenant without inviting each of them, for example
591
+ every user of a department of an app. The tenant names a resolver, and the resolver is
592
+ an API of that app that answers, per user, whether they have access and with what
593
+ groups. Nothing is written on the users, `allowedTenants` stays untouched.
594
+
595
+ Users always log in at their home tenant and then switch, by calling the gateway with
596
+ their token and the key of the other tenant. The resolver is asked on those calls.
597
+
598
+ Setting it up takes three steps:
599
+
600
+ 1. Build the API in a service of the app, see [The API to build](#the-api-to-build)
601
+ 2. Add the resolver to the `urac` custom registry, see [The resolver](#the-resolver)
602
+ 3. Name the resolver in the profile of each tenant, see [The tenant](#the-tenant)
603
+
604
+ ### The API to build
605
+
606
+ Any route of a service in the registry. The gateway calls it directly, not through the
607
+ gateway, so it does not need to be granted in any package. Keep it out of every ACL,
608
+ its answer is trusted as is.
609
+
610
+ It receives:
611
+
612
+ ```javascript
613
+ // Body
614
+ {
615
+ "user": {
616
+ "id": "5c8d0c505653de3985aa1001",
617
+ "username": "ann",
618
+ "tenant": { "id": "...", "code": "HOME" } // Home tenant of the user
619
+ },
620
+ "tenant": {
621
+ "id": "...",
622
+ "code": "DEPT",
623
+ "main": { "id": "...", "code": "MAIN" } // Only for a sub tenant
624
+ },
625
+ "subject": "dept_1" // From the tenant profile
626
+ }
627
+ ```
628
+
629
+ The `soajsinjectobj` header carries the tenant, the key with `commonFields` and the
630
+ config of the resolver service, and the application of the request, so a service with
631
+ `extKeyRequired` accepts the call. It carries no user and no ACL.
632
+
633
+ It answers with a standard SOAJS response:
634
+
635
+ ```javascript
636
+ // Access
637
+ {
638
+ "result": true,
639
+ "data": {
640
+ "access": true,
641
+ "groups": ["member"],
642
+ "groupsConfig": {
643
+ "allowedPackages": { "PROD": ["PROD_USER"] }
644
+ }
645
+ }
646
+ }
647
+
648
+ // No access
649
+ { "result": true, "data": { "access": false } }
650
+ ```
651
+
652
+ | Field | Required | Description |
653
+ |-------|----------|-------------|
654
+ | `access` | yes | `true` to grant access |
655
+ | `groupsConfig.allowedPackages` | yes, with access | Packages per product, the shape urac builds from the user's groups |
656
+ | `groups` | no | Group codes services see on the urac object, `[]` when absent |
657
+
658
+ The answer is the ACL. Nothing caps it, a package the API returns is granted. Only the
659
+ entry of the product of the key is used.
660
+
661
+ It runs on every call under the tenant, keep it fast.
662
+
663
+ ### The resolver
664
+
665
+ ```javascript
666
+ {
667
+ "name": "urac",
668
+ "value": {
669
+ "tenantAccessResolvers": {
670
+ "department": {
671
+ "service": "departments",
672
+ "version": "1",
673
+ "method": "post",
674
+ "api": "/tenant/access",
675
+ "timeout": 2000
676
+ }
677
+ }
678
+ }
679
+ }
680
+ ```
681
+
682
+ | Field | Type | Default | Description |
683
+ |-------|------|---------|-------------|
684
+ | `service` | string | - | Service that answers, must be in the registry |
685
+ | `version` | string | latest | Version of the service |
686
+ | `method` | string | `post` | HTTP method |
687
+ | `api` | string | - | Route on the service, without the service name |
688
+ | `timeout` | number | `2000` | Milliseconds before the call counts as no access |
689
+
690
+ The tenant names the resolver and the registry defines the call, so the API can move
691
+ without touching any tenant. A tenant naming a resolver that is not defined here is
692
+ logged as an error and treated as having none.
693
+
694
+ ### The tenant
695
+
696
+ The tenant profile names the resolver and the subject the API should check, here the
697
+ department:
698
+
699
+ ```javascript
700
+ {
701
+ "profile": {
702
+ "access": {
703
+ "resolver": "department", // A key of tenantAccessResolvers
704
+ "subject": "dept_1" // Passed to the API as is
705
+ }
706
+ }
707
+ }
708
+ ```
709
+
710
+ The profile is loaded with the key, so a change applies once the key cache refreshes.
711
+ `PUT /tenant/profile` of multitenant replaces the whole profile, whatever edits it must
712
+ keep `access`.
713
+
714
+ ### When it is asked
715
+
716
+ For every call made with a token under a tenant that is not the user's home tenant, in
717
+ this order:
718
+
719
+ | Order | Condition | Result |
720
+ |-------|-----------|--------|
721
+ | 1 | The token's `config.allowedTenants` lists the tenant | Invited, urac driver as before, the resolver is not asked |
722
+ | 2 | The tenant profile names a resolver | The resolver is asked |
723
+ | 3 | The resolver says no, fails or times out, or there is no resolver | urac driver as before |
724
+
725
+ It is never asked for the user's home tenant, for a token issued for this tenant, for
726
+ roaming, or for `loginMode: "oauth"` tokens.
727
+
728
+ On access the gateway takes the user from the token, sets its tenant to the tenant of
729
+ the key, its `groups` and `groupsConfig` to the answer, and builds the ACL as for any
730
+ user. The packages come from the provision cache, so urac is not read at all.
731
+
732
+ The fallback catches an invitation made after the token was issued, the token does not
733
+ show it yet. Everyone else gets what they got before, error 146.
734
+
735
+ ### When access is refused
736
+
737
+ Anything but a well formed access answer is no access, and the urac driver decides:
738
+
739
+ | | |
740
+ |---|---|
741
+ | the service is not in the registry, or has no host | logged as an error |
742
+ | non 2xx status, `result: false`, or not JSON | logged as an error |
743
+ | no answer within `timeout` | logged as an error |
744
+ | `access: true` without `groupsConfig.allowedPackages` | logged as an error |
745
+ | `access` is not `true` | not logged, a normal answer |
746
+
747
+ ### Behaviour
748
+
749
+ - **Cost.** One API call per call under the tenant, in place of the user and groups
750
+ reads of urac. Invited users cost what they did before.
751
+ - **Freshness.** Nothing is cached, adding or removing a user in the app applies on
752
+ their next call.
753
+ - **Outage.** While the service is down, users of the resolver get 146 under that
754
+ tenant, each call after waiting up to `timeout`, keep it short. Their home tenant and
755
+ invited users are not affected, except users invited after their token was issued,
756
+ who also wait for the timeout before the fallback lets them in.
757
+ - **Invitations win.** A user both invited and covered by the resolver gets the groups
758
+ of the invitation. If the invitation was made after the token was issued, they get
759
+ the resolver's until their next login.
760
+
761
+ ---
762
+
580
763
  ## Complete Configuration Example
581
764
 
582
765
  ```javascript
@@ -681,6 +864,22 @@ loaded from urac per request and the membership is current.
681
864
  }
682
865
  }
683
866
  }
867
+
868
+ // Separate URAC custom registry entry
869
+ {
870
+ "_id": ObjectId("..."),
871
+ "name": "urac",
872
+ "value": {
873
+ "tenantAccessResolvers": {
874
+ "department": {
875
+ "service": "departments",
876
+ "version": "1",
877
+ "api": "/tenant/access",
878
+ "timeout": 2000
879
+ }
880
+ }
881
+ }
882
+ }
684
883
  ```
685
884
 
686
885
  ---
@@ -698,7 +897,8 @@ Custom registry configurations are processed in this order:
698
897
  | 5 | gotoService/redirectToService | `gotoService.monitor` |
699
898
  | 6 | mt (security checks) | `mt.whitelist`, `oauth.value.pinWrapper`, `oauth.value.pinWhitelist` |
700
899
  | 6a | oauth (called from mt) | `oauth.deviceIdCheck` |
701
- | 6b | mt (inject object) | `membership` |
900
+ | 6b | mt (uracCheck) | `urac.value.tenantAccessResolvers` |
901
+ | 6c | mt (inject object) | `membership` |
702
902
  | 7 | idempotency | `idempotency.model`, `idempotency.services.[name]` |
703
903
  | 8 | traffic | `traffic.model`, `traffic.throttling` |
704
904
  | 9 | cache | `cache.model`, `cache.defaultTTL`, `cache.services.[name]` |
@@ -285,6 +285,23 @@ PIN login is a two-factor authentication mechanism where users must enter a PIN
285
285
  - Skipped if `obj.skipOAUTH = true` or no bearer token present
286
286
  - Creates `req.soajs.uracDriver` instance
287
287
  - Handles **roaming** across environments (e.g., dashboard token used in dev environment)
288
+ - Asks the **tenant access resolver** when a user switches to a tenant whose profile names one
289
+
290
+ **Tenant Access Resolver:**
291
+
292
+ A tenant can name a resolver in its profile (`profile.access`), an API of an app that answers per
293
+ user whether they have access to the tenant and with what groups, so a group of users, a department
294
+ for example, gets access without being invited one by one.
295
+
296
+ For a call under a tenant that is not the user's home tenant, and that the token's
297
+ `config.allowedTenants` does not list, `mw/mt/urac.js` calls the API defined in
298
+ `registry.custom.urac.value.tenantAccessResolvers` (`mw/mt/tenantAccess.js`). On access the user
299
+ comes from the token with that tenant and the groups of the answer, and urac is not read. No
300
+ access, an error or a timeout falls back to the urac driver as before.
301
+
302
+ See [Custom Registry, Tenant Access Resolver](../configuration-custom-registry.md#15-tenant-access-resolver)
303
+ for the API to build, the registry entry and the tenant profile.
304
+ To add one step by step, see the [Tenant Access Resolver Guide](../tenant-access-resolver.md).
288
305
 
289
306
  **Roaming Support:**
290
307
 
@@ -560,6 +577,7 @@ Service-level configuration that affects MT processing:
560
577
 
561
578
  - `mw/mt/utils.js` - Security check implementations
562
579
  - `mw/mt/urac.js` - URAC driver integration
580
+ - `mw/mt/tenantAccess.js` - Tenant access resolver call
563
581
 
564
582
  ## Usage Notes
565
583
 
@@ -111,6 +111,7 @@ Loads user profile from URAC for authenticated requests.
111
111
  - Resolves user groups
112
112
  - Applies user-specific ACL overrides
113
113
  - Handles network package override for user packages
114
+ - Asks the tenant access resolver for a user switching to a tenant that names one, see [Custom Registry](configuration-custom-registry.md#15-tenant-access-resolver)
114
115
 
115
116
  ## IP Whitelist Configuration
116
117
 
@@ -0,0 +1,81 @@
1
+ # Adding a Tenant Access Resolver
2
+
3
+ A step by step guide to give a group of users, a department of your app for example, access
4
+ to a tenant without inviting them one by one.
5
+
6
+ Every detail lives in [Custom Registry, Tenant Access Resolver](configuration-custom-registry.md#15-tenant-access-resolver),
7
+ this guide only walks through it in order.
8
+
9
+ ## Before you start
10
+
11
+ - Users log in at their home tenant and switch inside the app, the resolver is asked on the
12
+ calls they make under the other tenant. See [When it is asked](configuration-custom-registry.md#when-it-is-asked).
13
+ - Your app is the source of truth for who belongs to what, and its answer becomes the ACL of
14
+ the user under that tenant.
15
+
16
+ ## Step 1: Build the API
17
+
18
+ In a service of your app that is in the registry, add a route that tells the gateway if a user
19
+ has access to a tenant and with what groups.
20
+
21
+ 1. Read the body the gateway sends, `user`, `tenant` and `subject`.
22
+ 2. Look up whether `user` belongs to `subject` in your app.
23
+ 3. Answer with `access` and, when granted, `groups` and `groupsConfig.allowedPackages` for the
24
+ product of the tenant.
25
+
26
+ The request and the response are in [The API to build](configuration-custom-registry.md#the-api-to-build).
27
+
28
+ Keep in mind:
29
+
30
+ - Keep the route out of every package ACL, the gateway calls the service directly.
31
+ - It runs on every call the user makes under the tenant, keep it fast.
32
+ - Anything but a well formed yes is no access, see [When access is refused](configuration-custom-registry.md#when-access-is-refused).
33
+
34
+ ## Step 2: Add the resolver to the custom registry
35
+
36
+ In the environment's custom registry, add or edit the entry named `urac` and add your resolver
37
+ under `tenantAccessResolvers`, giving it a name, the service, the route and a short timeout.
38
+
39
+ The fields are in [The resolver](configuration-custom-registry.md#the-resolver).
40
+
41
+ The name you give it is what tenants refer to, keep it stable. The service, route or timeout can
42
+ change later without touching any tenant.
43
+
44
+ ## Step 3: Point the tenant at the resolver
45
+
46
+ On each tenant the group should reach, set `profile.access` with the resolver name from step 2
47
+ and the `subject` your API checks, the department id for example.
48
+
49
+ See [The tenant](configuration-custom-registry.md#the-tenant).
50
+
51
+ Whatever edits tenant profiles must keep `access`, `PUT /tenant/profile` replaces the whole
52
+ profile.
53
+
54
+ ## Step 4: Check it
55
+
56
+ With a user of the group, logged in at their home tenant:
57
+
58
+ 1. Call an API of the tenant with their token and the tenant's key. It should succeed with the
59
+ packages your API returned.
60
+ 2. Call it with a user outside the group. It should fail with error 146, as before.
61
+ 3. Call it with a user invited to the tenant. Your API is not called, the invitation decides.
62
+
63
+ If a user of the group gets 146, look for `Tenant access resolver` in the gateway logs, every
64
+ failed call is logged with the resolver, the user and the tenant.
65
+
66
+ Changes in your app apply on the next call. Changes to the tenant profile apply once the key
67
+ cache refreshes.
68
+
69
+ ## Before going live
70
+
71
+ Read [Behaviour](configuration-custom-registry.md#behaviour): what each call costs, what users
72
+ see while your service is down, and how invitations and the resolver combine.
73
+
74
+ ## Reference
75
+
76
+ | Topic | Where |
77
+ |-------|-------|
78
+ | Full reference | [Custom Registry, Tenant Access Resolver](configuration-custom-registry.md#15-tenant-access-resolver) |
79
+ | Where it runs in the pipeline | [MT middleware, uracCheck](middleware/mt.md#6-uraccheck) |
80
+ | Code | `mw/mt/urac.js`, `mw/mt/tenantAccess.js` |
81
+ | Example test | `test/integration/UC5-tenant-access/index.js`, a mock resolver |
package/mw/mt/lib.js CHANGED
@@ -45,7 +45,7 @@ module.exports = {
45
45
  async.eachOfSeries(service, function (version, versionValue, versionCall) {
46
46
  if (!acl.acl[serviceName][versionValue]) {
47
47
  acl.acl[serviceName][versionValue] = _.cloneDeep(version);
48
- return serviceCall();
48
+ return versionCall();
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") {
@@ -89,7 +89,7 @@ module.exports = {
89
89
  async.eachOf(service, function (version, versionValue, versionCall) {
90
90
  if (!acl.acl_all_env[envName][serviceName][versionValue]) {
91
91
  acl.acl_all_env[envName][serviceName][versionValue] = _.cloneDeep(version);
92
- return serviceCall();
92
+ return versionCall();
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") {
@@ -0,0 +1,173 @@
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 { httpRequestLight } = require("../../lib/request.js");
12
+
13
+ const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
14
+
15
+ const DEFAULT_TIMEOUT = 2000;
16
+
17
+ /**
18
+ * Find the resolver that decides who can access the tenant of this request.
19
+ *
20
+ * NOTE: the tenant profile names the resolver, the urac custom registry defines what to call,
21
+ * so the call can move without touching every tenant
22
+ *
23
+ * @param soajs
24
+ * @returns {null|{name, subject, service, version, method, api, timeout}}
25
+ */
26
+ function getResolver(soajs) {
27
+ let access = get(["tenant", "profile", "access"], soajs);
28
+ if (!access || !access.resolver) {
29
+ return null;
30
+ }
31
+ let resolver = get(["registry", "custom", "urac", "value", "tenantAccessResolvers", access.resolver], soajs);
32
+ if (!resolver || !resolver.service || !resolver.api) {
33
+ if (soajs.log && soajs.log.error) {
34
+ soajs.log.error("Tenant access resolver [" + access.resolver + "] of tenant [" + soajs.tenant.code + "] is not defined in the urac custom registry");
35
+ }
36
+ return null;
37
+ }
38
+ return {
39
+ "name": access.resolver,
40
+ "subject": access.subject || null,
41
+ "service": resolver.service,
42
+ "version": resolver.version || null,
43
+ "method": resolver.method || "post",
44
+ "api": resolver.api,
45
+ "timeout": resolver.timeout || DEFAULT_TIMEOUT
46
+ };
47
+ }
48
+
49
+ /**
50
+ * The service behind the resolver has not been through the gateway, give it the tenant and key
51
+ * of this request the same way the gateway does, without any ACL or user
52
+ *
53
+ * @param soajs
54
+ * @param serviceName
55
+ * @returns {string}
56
+ */
57
+ function buildInjectObj(soajs, serviceName) {
58
+ let tenant = soajs.tenant;
59
+ let injectObj = {
60
+ "tenant": {
61
+ "id": tenant.id,
62
+ "name": tenant.name,
63
+ "code": tenant.code,
64
+ "locked": tenant.locked,
65
+ "type": tenant.type
66
+ },
67
+ "key": {
68
+ "config": {},
69
+ "iKey": tenant.key.iKey,
70
+ "eKey": tenant.key.eKey
71
+ },
72
+ "application": {
73
+ "product": tenant.application.product,
74
+ "package": tenant.application.package,
75
+ "appId": tenant.application.appId
76
+ }
77
+ };
78
+ if (tenant.main) {
79
+ injectObj.tenant.main = tenant.main;
80
+ }
81
+ if (soajs.servicesConfig) {
82
+ if (soajs.servicesConfig.commonFields) {
83
+ injectObj.key.config.commonFields = soajs.servicesConfig.commonFields;
84
+ }
85
+ if (soajs.servicesConfig[serviceName]) {
86
+ injectObj.key.config[serviceName] = soajs.servicesConfig[serviceName];
87
+ }
88
+ }
89
+ return JSON.stringify(injectObj);
90
+ }
91
+
92
+ /**
93
+ * Ask the resolver if the user has access to the tenant of this request and with what groups.
94
+ *
95
+ * NOTE: anything but a well formed yes is no access, the caller falls back to the urac driver
96
+ * so an invitation made after the token was issued still works
97
+ *
98
+ * @param soajs
99
+ * @param user the user from the token
100
+ * @param resolver from getResolver
101
+ * @param cb (result) result is {groups, groupsConfig} or null
102
+ */
103
+ function resolve(soajs, user, resolver, cb) {
104
+ let done = (result) => {
105
+ //NOTE: out of the promise, an error thrown further down must not come back here as a rejection
106
+ process.nextTick(() => {
107
+ return cb(result);
108
+ });
109
+ };
110
+ let label = "Tenant access resolver [" + resolver.name + "] for user [" + user.username + "] and tenant [" + soajs.tenant.code + "]";
111
+
112
+ if (!soajs.registry || !soajs.registry.services ||
113
+ !soajs.registry.services[resolver.service] || !soajs.registry.services[resolver.service].port) {
114
+ soajs.log.error(label + " failed, service [" + resolver.service + "] is not in the registry");
115
+ return done(null);
116
+ }
117
+ soajs.awareness.getHost(resolver.service, resolver.version, (host) => {
118
+ if (!host) {
119
+ soajs.log.error(label + " failed, no host for [" + resolver.service + "@" + resolver.version + "]");
120
+ return done(null);
121
+ }
122
+ let data = {
123
+ "user": {
124
+ "id": user.id || (user._id ? user._id.toString() : null),
125
+ "username": user.username,
126
+ "tenant": {
127
+ "id": user.tenant.id,
128
+ "code": user.tenant.code
129
+ }
130
+ },
131
+ "tenant": {
132
+ "id": soajs.tenant.id,
133
+ "code": soajs.tenant.code
134
+ },
135
+ "subject": resolver.subject
136
+ };
137
+ if (soajs.tenant.main) {
138
+ data.tenant.main = soajs.tenant.main;
139
+ }
140
+ httpRequestLight({
141
+ "uri": "http://" + host + ":" + soajs.registry.services[resolver.service].port + resolver.api,
142
+ "method": resolver.method,
143
+ "data": data,
144
+ "headers": { "soajsinjectobj": buildInjectObj(soajs, resolver.service) },
145
+ "timeout": resolver.timeout
146
+ }).then((response) => {
147
+ let answer = (response && response.result === true) ? response.data : null;
148
+ if (!answer || typeof answer !== "object") {
149
+ soajs.log.error(label + " returned an invalid response");
150
+ return done(null);
151
+ }
152
+ if (answer.access !== true) {
153
+ return done(null);
154
+ }
155
+ if (!answer.groupsConfig || !answer.groupsConfig.allowedPackages || typeof answer.groupsConfig.allowedPackages !== "object") {
156
+ soajs.log.error(label + " granted access without groupsConfig.allowedPackages");
157
+ return done(null);
158
+ }
159
+ return done({
160
+ "groups": Array.isArray(answer.groups) ? answer.groups : [],
161
+ "groupsConfig": answer.groupsConfig
162
+ });
163
+ }).catch((error) => {
164
+ soajs.log.error(label + " failed: " + error.message);
165
+ return done(null);
166
+ });
167
+ });
168
+ }
169
+
170
+ module.exports = {
171
+ "getResolver": getResolver,
172
+ "resolve": resolve
173
+ };
package/mw/mt/urac.js CHANGED
@@ -13,6 +13,7 @@ const coreModules = require("soajs.core.modules");
13
13
  let provision = coreModules.provision;
14
14
 
15
15
  const lib = require("./lib.js");
16
+ const tenantAccess = require("./tenantAccess.js");
16
17
 
17
18
  const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
18
19
 
@@ -57,6 +58,31 @@ function Urac(param) {
57
58
  _self.username = param.username;
58
59
  }
59
60
  }
61
+
62
+ //NOTE: a user switching to a tenant that names a resolver in its profile is decided by the resolver,
63
+ // unless the token already shows an invitation to that tenant, invitations keep going through
64
+ // the urac driver
65
+ _self.tenantAccess = null;
66
+ if (!_self.userRecord && _self.id && _self.bearerTokenUser && _self.soajs.tenant && !_self.soajs.tenant.roaming) {
67
+ let user = _self.bearerTokenUser;
68
+ if (user.tenant && user.tenant.id !== _self.soajs.tenant.id && !isInvited(user, _self.soajs.tenant.id)) {
69
+ _self.tenantAccess = tenantAccess.getResolver(_self.soajs);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Check the allowedTenants of the user from the token
76
+ * @param user
77
+ * @param tenantId
78
+ * @returns {boolean}
79
+ */
80
+ function isInvited(user, tenantId) {
81
+ let allowedTenants = get(["config", "allowedTenants"], user);
82
+ if (!Array.isArray(allowedTenants)) {
83
+ return false;
84
+ }
85
+ return allowedTenants.some((item) => item && item.tenant && item.tenant.id === tenantId);
60
86
  }
61
87
 
62
88
  /**
@@ -75,20 +101,43 @@ Urac.prototype.init = function (cb) {
75
101
  }
76
102
  }
77
103
  else if (_self.id) {
78
- uracDriver.getRecord(_self.soajs, { id: _self.id.toString(), username: _self.username }, function (err, record) {
79
- if (record) {
80
- _self.userRecord = record;
81
- _self.resolveACL(() => {
82
- return cb(null, _self.userRecord);
83
- });
84
- } else {
85
- if (err && err.msg) {
86
- const code = err.code;
87
- err = new Error(err.msg);
88
- err.code = code;
104
+ let getRecord = () => {
105
+ uracDriver.getRecord(_self.soajs, { id: _self.id.toString(), username: _self.username }, function (err, record) {
106
+ if (record) {
107
+ _self.userRecord = record;
108
+ _self.resolveACL(() => {
109
+ return cb(null, _self.userRecord);
110
+ });
111
+ } else {
112
+ if (err && err.msg) {
113
+ const code = err.code;
114
+ err = new Error(err.msg);
115
+ err.code = code;
116
+ }
117
+ return cb(err, null);
89
118
  }
90
- return cb(err, null);
119
+ });
120
+ };
121
+ if (!_self.tenantAccess) {
122
+ return getRecord();
123
+ }
124
+ tenantAccess.resolve(_self.soajs, _self.bearerTokenUser, _self.tenantAccess, (result) => {
125
+ //NOTE: no access from the resolver falls back to the urac driver, it catches an invitation
126
+ // made after the token was issued and answers everyone else as before
127
+ if (!result) {
128
+ return getRecord();
91
129
  }
130
+ _self.userRecord = Object.assign({}, _self.bearerTokenUser, {
131
+ "tenant": {
132
+ "id": _self.soajs.tenant.id,
133
+ "code": _self.soajs.tenant.code
134
+ },
135
+ "groups": result.groups,
136
+ "groupsConfig": result.groupsConfig
137
+ });
138
+ _self.resolveACL(() => {
139
+ return cb(null, _self.userRecord);
140
+ });
92
141
  });
93
142
  } else {
94
143
  let error = new Error('oAuth userId is not available to pull URAC profile');
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.16",
4
+ "version": "4.3.18",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -30,7 +30,7 @@
30
30
  "path-to-regexp": "8.4.2",
31
31
  "soajs.core.libs": "1.2.2",
32
32
  "soajs.core.modules": "5.2.21",
33
- "soajs.urac.driver": "3.2.17",
33
+ "soajs.urac.driver": "3.2.18",
34
34
  "useragent": "2.3.0"
35
35
  },
36
36
  "devDependencies": {