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 +1 -0
- package/docs/configuration-custom-registry.md +206 -6
- package/docs/middleware/mt.md +18 -0
- package/docs/multi-tenant-security.md +1 -0
- package/docs/tenant-access-resolver.md +81 -0
- package/mw/mt/lib.js +2 -2
- package/mw/mt/tenantAccess.js +173 -0
- package/mw/mt/urac.js +61 -12
- package/package.json +2 -2
- package/test/data/provision/customRegistry/urac.js +22 -0
- package/test/data/provision/hosts/departments.js +12 -0
- package/test/data/provision/marketplace/departments.js +38 -0
- package/test/data/provision/oauth/token/department_member_token.js +72 -0
- package/test/data/provision/oauth/token/department_outsider_token.js +72 -0
- package/test/data/provision/tenants/dbtn.js +6 -0
- package/test/integration/UC5-tenant-access/index.js +153 -0
- package/test/integration/index.js +4 -0
- package/test/unit/index.js +1 -0
- package/test/unit/mw/mt/lib.js +22 -0
- package/test/unit/mw/mt/tenantAccess.js +338 -0
- package/test/unit/mw/mt/urac.js +229 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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 (
|
|
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]` |
|
package/docs/middleware/mt.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
33
|
+
"soajs.urac.driver": "3.2.18",
|
|
34
34
|
"useragent": "2.3.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|