@sasjs/api 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/build/browser.js +2218 -118
- package/build/cjs/corpus/records.js +1222 -0
- package/build/cjs/corpus/runner.js +15 -6
- package/build/cjs/host/dispatch.js +190 -2
- package/build/cjs/identity/memory-store.js +196 -11
- package/build/cjs/identity/records.js +398 -0
- package/build/cjs/index.js +8 -1
- package/build/cjs/routes/generated.js +184 -4
- package/build/cjs/routes/overlay.js +53 -58
- package/build/esm/corpus/records.d.ts +25 -0
- package/build/esm/corpus/records.d.ts.map +1 -0
- package/build/esm/corpus/records.js +1219 -0
- package/build/esm/corpus/runner.d.ts.map +1 -1
- package/build/esm/corpus/runner.js +15 -6
- package/build/esm/host/dispatch.d.ts +9 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +190 -2
- package/build/esm/identity/fixture.d.ts +6 -0
- package/build/esm/identity/fixture.d.ts.map +1 -1
- package/build/esm/identity/memory-store.d.ts +29 -5
- package/build/esm/identity/memory-store.d.ts.map +1 -1
- package/build/esm/identity/memory-store.js +196 -11
- package/build/esm/identity/records.d.ts +102 -0
- package/build/esm/identity/records.d.ts.map +1 -0
- package/build/esm/identity/records.js +393 -0
- package/build/esm/identity/types.d.ts +102 -2
- package/build/esm/identity/types.d.ts.map +1 -1
- package/build/esm/index.d.ts +4 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +2 -0
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +184 -4
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +53 -58
- package/package.json +1 -1
package/build/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @sasjs/api 0.
|
|
1
|
+
/* @sasjs/api 0.7.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
|
|
2
2
|
"use strict";
|
|
3
3
|
var SASjsApi = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -22,6 +22,7 @@ var SASjsApi = (() => {
|
|
|
22
22
|
// src/index.ts
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
|
+
ADMIN_SIGNED_IN: () => ADMIN_SIGNED_IN,
|
|
25
26
|
ApiError: () => ApiError,
|
|
26
27
|
DEFAULT_IDENTITY: () => DEFAULT_IDENTITY,
|
|
27
28
|
DEPLOY_LIMITS: () => DEPLOY_LIMITS,
|
|
@@ -34,12 +35,15 @@ var SASjsApi = (() => {
|
|
|
34
35
|
MemoryDriveStore: () => MemoryDriveStore,
|
|
35
36
|
MemoryIdentityStore: () => MemoryIdentityStore,
|
|
36
37
|
OVERLAY: () => OVERLAY,
|
|
38
|
+
PROVIDER_MANAGED: () => PROVIDER_MANAGED,
|
|
39
|
+
RECORD_CORPUS: () => RECORD_CORPUS,
|
|
37
40
|
ROUTES: () => ROUTES,
|
|
38
41
|
TAG_CAPABILITIES: () => TAG_CAPABILITIES,
|
|
39
42
|
basename: () => basename,
|
|
40
43
|
checkDeployTree: () => checkDeployTree,
|
|
41
44
|
createDriveService: () => createDriveService,
|
|
42
45
|
createIdentityService: () => createIdentityService,
|
|
46
|
+
createRecordService: () => createRecordService,
|
|
43
47
|
decodeBase64: () => decodeBase64,
|
|
44
48
|
deployTree: () => deployTree,
|
|
45
49
|
dirname: () => dirname,
|
|
@@ -54,6 +58,7 @@ var SASjsApi = (() => {
|
|
|
54
58
|
parseIdentity: () => parseIdentity,
|
|
55
59
|
routeById: () => routeById,
|
|
56
60
|
runCase: () => runCase,
|
|
61
|
+
sanitiseDisplayName: () => sanitiseDisplayName,
|
|
57
62
|
servicesFor: () => servicesFor
|
|
58
63
|
});
|
|
59
64
|
|
|
@@ -678,7 +683,7 @@ var SASjsApi = (() => {
|
|
|
678
683
|
},
|
|
679
684
|
{
|
|
680
685
|
status: 401,
|
|
681
|
-
description: "The authorization code is invalid, expired, already used, or was issued for another client, or the client id is unknown"
|
|
686
|
+
description: "The authorization code is invalid, expired, already used, or was issued for another client, or the client id is unknown. This route is unauthenticated, so an unknown client id is a `401` here where `POST /SASLogon/authorize` (which requires a session) answers `403`"
|
|
682
687
|
}
|
|
683
688
|
]
|
|
684
689
|
},
|
|
@@ -772,6 +777,10 @@ var SASjsApi = (() => {
|
|
|
772
777
|
{
|
|
773
778
|
status: 200,
|
|
774
779
|
description: "Ok"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
status: 401,
|
|
783
|
+
description: "Admin account required"
|
|
775
784
|
}
|
|
776
785
|
]
|
|
777
786
|
},
|
|
@@ -812,6 +821,14 @@ var SASjsApi = (() => {
|
|
|
812
821
|
{
|
|
813
822
|
status: 200,
|
|
814
823
|
description: "Ok"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
status: 401,
|
|
827
|
+
description: "Admin account required"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
status: 403,
|
|
831
|
+
description: "Client ID already exists."
|
|
815
832
|
}
|
|
816
833
|
]
|
|
817
834
|
},
|
|
@@ -1301,7 +1318,7 @@ var SASjsApi = (() => {
|
|
|
1301
1318
|
},
|
|
1302
1319
|
{
|
|
1303
1320
|
status: 409,
|
|
1304
|
-
description: "Folder already exists"
|
|
1321
|
+
description: "Folder already exists. The body is that message as plain text."
|
|
1305
1322
|
}
|
|
1306
1323
|
]
|
|
1307
1324
|
},
|
|
@@ -1341,7 +1358,7 @@ var SASjsApi = (() => {
|
|
|
1341
1358
|
},
|
|
1342
1359
|
{
|
|
1343
1360
|
status: 409,
|
|
1344
|
-
description: "Something already exists at the new path"
|
|
1361
|
+
description: "Something already exists at the new path. The body is that message as plain text."
|
|
1345
1362
|
}
|
|
1346
1363
|
]
|
|
1347
1364
|
},
|
|
@@ -1391,6 +1408,14 @@ var SASjsApi = (() => {
|
|
|
1391
1408
|
{
|
|
1392
1409
|
status: 200,
|
|
1393
1410
|
description: "Ok"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
status: 401,
|
|
1414
|
+
description: "Admin account required"
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
status: 409,
|
|
1418
|
+
description: "Group name already exists."
|
|
1394
1419
|
}
|
|
1395
1420
|
]
|
|
1396
1421
|
},
|
|
@@ -1419,6 +1444,22 @@ var SASjsApi = (() => {
|
|
|
1419
1444
|
{
|
|
1420
1445
|
status: 200,
|
|
1421
1446
|
description: "Ok"
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
status: 400,
|
|
1450
|
+
description: "Can't add/remove user to 'public' group."
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
status: 401,
|
|
1454
|
+
description: "Admin account required"
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
status: 404,
|
|
1458
|
+
description: "Group not found. / User not found."
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
status: 405,
|
|
1462
|
+
description: "Can't add/remove user to group created by external auth provider."
|
|
1422
1463
|
}
|
|
1423
1464
|
]
|
|
1424
1465
|
},
|
|
@@ -1447,6 +1488,22 @@ var SASjsApi = (() => {
|
|
|
1447
1488
|
{
|
|
1448
1489
|
status: 200,
|
|
1449
1490
|
description: "Ok"
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
status: 400,
|
|
1494
|
+
description: "Can't add/remove user to 'public' group."
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
status: 401,
|
|
1498
|
+
description: "Admin account required"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
status: 404,
|
|
1502
|
+
description: "Group not found. / User not found."
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
status: 405,
|
|
1506
|
+
description: "Can't add/remove user to group created by external auth provider."
|
|
1450
1507
|
}
|
|
1451
1508
|
]
|
|
1452
1509
|
},
|
|
@@ -1469,6 +1526,14 @@ var SASjsApi = (() => {
|
|
|
1469
1526
|
{
|
|
1470
1527
|
status: 200,
|
|
1471
1528
|
description: "Ok"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
status: 401,
|
|
1532
|
+
description: "Admin account required"
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
status: 404,
|
|
1536
|
+
description: "Group not found."
|
|
1472
1537
|
}
|
|
1473
1538
|
]
|
|
1474
1539
|
},
|
|
@@ -1491,6 +1556,10 @@ var SASjsApi = (() => {
|
|
|
1491
1556
|
{
|
|
1492
1557
|
status: 200,
|
|
1493
1558
|
description: "Ok"
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
status: 404,
|
|
1562
|
+
description: "Group not found."
|
|
1494
1563
|
}
|
|
1495
1564
|
]
|
|
1496
1565
|
},
|
|
@@ -1513,6 +1582,10 @@ var SASjsApi = (() => {
|
|
|
1513
1582
|
{
|
|
1514
1583
|
status: 200,
|
|
1515
1584
|
description: "Ok"
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
status: 404,
|
|
1588
|
+
description: "Group not found."
|
|
1516
1589
|
}
|
|
1517
1590
|
]
|
|
1518
1591
|
},
|
|
@@ -1573,6 +1646,14 @@ var SASjsApi = (() => {
|
|
|
1573
1646
|
{
|
|
1574
1647
|
status: 200,
|
|
1575
1648
|
description: "Ok"
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
status: 401,
|
|
1652
|
+
description: "Admin account required, or no grant on the route"
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
status: 404,
|
|
1656
|
+
description: "User not found."
|
|
1576
1657
|
}
|
|
1577
1658
|
]
|
|
1578
1659
|
},
|
|
@@ -1619,6 +1700,22 @@ var SASjsApi = (() => {
|
|
|
1619
1700
|
{
|
|
1620
1701
|
status: 200,
|
|
1621
1702
|
description: "Ok"
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
status: 400,
|
|
1706
|
+
description: "Can not add permission for admin user. / Invalid principal type. Valid types are user or group."
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
status: 401,
|
|
1710
|
+
description: "Admin account required"
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
status: 404,
|
|
1714
|
+
description: "User not found. / Group not found."
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
status: 409,
|
|
1718
|
+
description: "Permission already exists with provided Path, Type and User. / Permission already exists with provided Path, Type and Group."
|
|
1622
1719
|
}
|
|
1623
1720
|
]
|
|
1624
1721
|
},
|
|
@@ -1641,6 +1738,14 @@ var SASjsApi = (() => {
|
|
|
1641
1738
|
{
|
|
1642
1739
|
status: 204,
|
|
1643
1740
|
description: "No content"
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
status: 401,
|
|
1744
|
+
description: "Admin account required"
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
status: 404,
|
|
1748
|
+
description: "Permission not found."
|
|
1644
1749
|
}
|
|
1645
1750
|
]
|
|
1646
1751
|
},
|
|
@@ -1669,6 +1774,14 @@ var SASjsApi = (() => {
|
|
|
1669
1774
|
{
|
|
1670
1775
|
status: 200,
|
|
1671
1776
|
description: "Ok"
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
status: 401,
|
|
1780
|
+
description: "Admin account required"
|
|
1781
|
+
},
|
|
1782
|
+
{
|
|
1783
|
+
status: 404,
|
|
1784
|
+
description: "Permission not found."
|
|
1672
1785
|
}
|
|
1673
1786
|
]
|
|
1674
1787
|
},
|
|
@@ -1933,6 +2046,14 @@ var SASjsApi = (() => {
|
|
|
1933
2046
|
{
|
|
1934
2047
|
status: 200,
|
|
1935
2048
|
description: "Ok"
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
status: 401,
|
|
2052
|
+
description: "Admin account required"
|
|
2053
|
+
},
|
|
2054
|
+
{
|
|
2055
|
+
status: 409,
|
|
2056
|
+
description: "Username already exists."
|
|
1936
2057
|
}
|
|
1937
2058
|
]
|
|
1938
2059
|
},
|
|
@@ -1961,6 +2082,14 @@ var SASjsApi = (() => {
|
|
|
1961
2082
|
{
|
|
1962
2083
|
status: 204,
|
|
1963
2084
|
description: "No content"
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
status: 401,
|
|
2088
|
+
description: "Admin account required, or invalid password"
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
status: 404,
|
|
2092
|
+
description: "User is not found."
|
|
1964
2093
|
}
|
|
1965
2094
|
]
|
|
1966
2095
|
},
|
|
@@ -1983,6 +2112,14 @@ var SASjsApi = (() => {
|
|
|
1983
2112
|
{
|
|
1984
2113
|
status: 200,
|
|
1985
2114
|
description: "Ok"
|
|
2115
|
+
},
|
|
2116
|
+
{
|
|
2117
|
+
status: 401,
|
|
2118
|
+
description: "Unauthorized"
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
status: 404,
|
|
2122
|
+
description: "User is not found."
|
|
1986
2123
|
}
|
|
1987
2124
|
]
|
|
1988
2125
|
},
|
|
@@ -2041,6 +2178,22 @@ var SASjsApi = (() => {
|
|
|
2041
2178
|
{
|
|
2042
2179
|
status: 200,
|
|
2043
2180
|
description: "Ok"
|
|
2181
|
+
},
|
|
2182
|
+
{
|
|
2183
|
+
status: 401,
|
|
2184
|
+
description: "Admin account required"
|
|
2185
|
+
},
|
|
2186
|
+
{
|
|
2187
|
+
status: 404,
|
|
2188
|
+
description: "Unable to find user with <id>"
|
|
2189
|
+
},
|
|
2190
|
+
{
|
|
2191
|
+
status: 405,
|
|
2192
|
+
description: "Can not update username of user that is created by an external auth provider. / Can not update display name of user that is created by an external auth provider."
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
status: 409,
|
|
2196
|
+
description: "Username already exists."
|
|
2044
2197
|
}
|
|
2045
2198
|
]
|
|
2046
2199
|
},
|
|
@@ -2069,6 +2222,14 @@ var SASjsApi = (() => {
|
|
|
2069
2222
|
{
|
|
2070
2223
|
status: 204,
|
|
2071
2224
|
description: "No content"
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
status: 401,
|
|
2228
|
+
description: "Admin account required, or invalid password"
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
status: 404,
|
|
2232
|
+
description: "User is not found."
|
|
2072
2233
|
}
|
|
2073
2234
|
]
|
|
2074
2235
|
},
|
|
@@ -2091,6 +2252,14 @@ var SASjsApi = (() => {
|
|
|
2091
2252
|
{
|
|
2092
2253
|
status: 200,
|
|
2093
2254
|
description: "Ok"
|
|
2255
|
+
},
|
|
2256
|
+
{
|
|
2257
|
+
status: 401,
|
|
2258
|
+
description: "Unauthorized"
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
status: 404,
|
|
2262
|
+
description: "User is not found."
|
|
2094
2263
|
}
|
|
2095
2264
|
]
|
|
2096
2265
|
},
|
|
@@ -2149,6 +2318,22 @@ var SASjsApi = (() => {
|
|
|
2149
2318
|
{
|
|
2150
2319
|
status: 200,
|
|
2151
2320
|
description: "Ok"
|
|
2321
|
+
},
|
|
2322
|
+
{
|
|
2323
|
+
status: 401,
|
|
2324
|
+
description: "Admin account required"
|
|
2325
|
+
},
|
|
2326
|
+
{
|
|
2327
|
+
status: 404,
|
|
2328
|
+
description: "Unable to find user with <username>"
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
status: 405,
|
|
2332
|
+
description: "Can not update username of user that is created by an external auth provider. / Can not update display name of user that is created by an external auth provider."
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
status: 409,
|
|
2336
|
+
description: "Username already exists."
|
|
2152
2337
|
}
|
|
2153
2338
|
]
|
|
2154
2339
|
},
|
|
@@ -2182,7 +2367,7 @@ var SASjsApi = (() => {
|
|
|
2182
2367
|
},
|
|
2183
2368
|
{
|
|
2184
2369
|
status: 403,
|
|
2185
|
-
description: "The client id is unknown"
|
|
2370
|
+
description: "The client id is unknown. This route runs under a signed-in session, so the caller is authenticated and an unknown client is refused as forbidden; the unauthenticated `POST /SASjsApi/auth/token` answers `401` for the same condition"
|
|
2186
2371
|
}
|
|
2187
2372
|
]
|
|
2188
2373
|
},
|
|
@@ -2267,7 +2452,7 @@ var SASjsApi = (() => {
|
|
|
2267
2452
|
Client: ["identity"],
|
|
2268
2453
|
Web: ["identity"]
|
|
2269
2454
|
};
|
|
2270
|
-
var
|
|
2455
|
+
var DIRECTORY = "Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.";
|
|
2271
2456
|
var EDITOR = "Linting and formatting come from @sasjs/lint, which this package does not depend on. They land with the code routes so an editor has one wiring.";
|
|
2272
2457
|
var RUNTIME = "Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.";
|
|
2273
2458
|
var SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
|
|
@@ -2322,18 +2507,20 @@ var SASjsApi = (() => {
|
|
|
2322
2507
|
},
|
|
2323
2508
|
"POST /SASjsApi/auth/logout": { implemented: true },
|
|
2324
2509
|
"PATCH /SASjsApi/auth/updatePassword": {
|
|
2325
|
-
implemented:
|
|
2326
|
-
|
|
2510
|
+
implemented: true,
|
|
2511
|
+
// As with the permission list: the document's 404 is a caller whose record
|
|
2512
|
+
// has gone, which a host's store can see and this package cannot.
|
|
2513
|
+
hostResponses: [404]
|
|
2327
2514
|
},
|
|
2328
2515
|
// Auth Config - the providers a host was started with, and an LDAP import
|
|
2329
2516
|
"GET /SASjsApi/authConfig": { implemented: false, reason: HOST_CONFIG },
|
|
2330
2517
|
"POST /SASjsApi/authConfig/synchroniseWithLDAP": {
|
|
2331
2518
|
implemented: false,
|
|
2332
|
-
reason:
|
|
2519
|
+
reason: DIRECTORY
|
|
2333
2520
|
},
|
|
2334
2521
|
// Client - the OAuth clients the identity store already holds for sign-in
|
|
2335
|
-
"GET /SASjsApi/client": { implemented:
|
|
2336
|
-
"POST /SASjsApi/client": { implemented:
|
|
2522
|
+
"GET /SASjsApi/client": { implemented: true },
|
|
2523
|
+
"POST /SASjsApi/client": { implemented: true },
|
|
2337
2524
|
// Code - the editor's three calls, and the runtime one
|
|
2338
2525
|
"POST /SASjsApi/code/execute": { implemented: false, reason: RUNTIME },
|
|
2339
2526
|
"POST /SASjsApi/code/trigger": { implemented: false, reason: RUNTIME },
|
|
@@ -2407,25 +2594,13 @@ var SASjsApi = (() => {
|
|
|
2407
2594
|
reason: UNPUBLISH
|
|
2408
2595
|
},
|
|
2409
2596
|
// Group - members and the groups themselves
|
|
2410
|
-
"GET /SASjsApi/group": { implemented:
|
|
2411
|
-
"GET /SASjsApi/group/{uid}": { implemented:
|
|
2412
|
-
"GET /SASjsApi/group/by/groupname/{name}": {
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
},
|
|
2416
|
-
"
|
|
2417
|
-
"POST /SASjsApi/group/{groupUid}/{userUid}": {
|
|
2418
|
-
implemented: false,
|
|
2419
|
-
reason: IDENTITY_RECORDS
|
|
2420
|
-
},
|
|
2421
|
-
"DELETE /SASjsApi/group/{groupUid}/{userUid}": {
|
|
2422
|
-
implemented: false,
|
|
2423
|
-
reason: IDENTITY_RECORDS
|
|
2424
|
-
},
|
|
2425
|
-
"DELETE /SASjsApi/group/{uid}": {
|
|
2426
|
-
implemented: false,
|
|
2427
|
-
reason: IDENTITY_RECORDS
|
|
2428
|
-
},
|
|
2597
|
+
"GET /SASjsApi/group": { implemented: true },
|
|
2598
|
+
"GET /SASjsApi/group/{uid}": { implemented: true },
|
|
2599
|
+
"GET /SASjsApi/group/by/groupname/{name}": { implemented: true },
|
|
2600
|
+
"POST /SASjsApi/group": { implemented: true },
|
|
2601
|
+
"POST /SASjsApi/group/{groupUid}/{userUid}": { implemented: true },
|
|
2602
|
+
"DELETE /SASjsApi/group/{groupUid}/{userUid}": { implemented: true },
|
|
2603
|
+
"DELETE /SASjsApi/group/{uid}": { implemented: true },
|
|
2429
2604
|
// Info - what the host is, and what this caller may use
|
|
2430
2605
|
"GET /SASjsApi/info": { implemented: false, reason: HOST_CONFIG },
|
|
2431
2606
|
"GET /SASjsApi/info/authorizedRoutes": {
|
|
@@ -2441,16 +2616,24 @@ var SASjsApi = (() => {
|
|
|
2441
2616
|
reason: PERMISSIONS
|
|
2442
2617
|
},
|
|
2443
2618
|
// Permission - the rules the inventory above is derived from
|
|
2444
|
-
"GET /SASjsApi/permission": {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
},
|
|
2619
|
+
"GET /SASjsApi/permission": {
|
|
2620
|
+
implemented: true,
|
|
2621
|
+
// The document states 404 for a caller whose record has gone - a token that
|
|
2622
|
+
// outlived its account. Here a token stops resolving when the account does,
|
|
2623
|
+
// so this package answers 401 before it gets that far; the host's own store
|
|
2624
|
+
// is what can still see the difference.
|
|
2625
|
+
hostResponses: [404]
|
|
2626
|
+
},
|
|
2627
|
+
"POST /SASjsApi/permission": { implemented: true },
|
|
2628
|
+
"PATCH /SASjsApi/permission/{uid}": { implemented: true },
|
|
2629
|
+
// The three deletes answer 204 here and 200 on the wire: the server's route
|
|
2630
|
+
// handlers send `res.status(200).send('Account Deleted!')` and
|
|
2631
|
+
// `'Permission Deleted!'`, while tsoa emitted 204 for a void handler, so the
|
|
2632
|
+
// document understates the status. Correcting it is a decorator in
|
|
2633
|
+
// sasjs/server (`@SuccessResponse('200', ...)`), and this package follows the
|
|
2634
|
+
// document until that release lands - a local 200 would fail the schema check
|
|
2635
|
+
// that keeps this table honest.
|
|
2636
|
+
"DELETE /SASjsApi/permission/{uid}": { implemented: true },
|
|
2454
2637
|
// Session - the caller's own session, then the record behind a triggered one
|
|
2455
2638
|
"GET /SASjsApi/session": {
|
|
2456
2639
|
implemented: true,
|
|
@@ -2477,29 +2660,26 @@ var SASjsApi = (() => {
|
|
|
2477
2660
|
"POST /SASjsApi/stp/execute": { implemented: false, reason: RUNTIME },
|
|
2478
2661
|
"POST /SASjsApi/stp/trigger": { implemented: false, reason: RUNTIME },
|
|
2479
2662
|
// User - the accounts the identity store already holds for sign-in
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
"
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
},
|
|
2491
|
-
"PATCH /SASjsApi/user/
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
},
|
|
2499
|
-
"DELETE /SASjsApi/user/by/username/{username}": {
|
|
2500
|
-
implemented: false,
|
|
2501
|
-
reason: IDENTITY_RECORDS
|
|
2502
|
-
}
|
|
2663
|
+
// Reading accounts is open to any authenticated caller, which is what
|
|
2664
|
+
// sasjs/server's routes do (`authenticateAccessToken` only): the document once
|
|
2665
|
+
// said `Admin account required` here, and sasjs/server#551 corrected it to
|
|
2666
|
+
// `Unauthorized` because that is the refusal a caller actually receives. The
|
|
2667
|
+
// breadth is the server's behaviour, copied deliberately - the auto-exec code
|
|
2668
|
+
// is the one field withheld from anyone but an administrator or the account
|
|
2669
|
+
// itself.
|
|
2670
|
+
"GET /SASjsApi/user": { implemented: true },
|
|
2671
|
+
"GET /SASjsApi/user/{uid}": { implemented: true },
|
|
2672
|
+
"GET /SASjsApi/user/by/username/{username}": { implemented: true },
|
|
2673
|
+
"POST /SASjsApi/user": { implemented: true },
|
|
2674
|
+
"PATCH /SASjsApi/user/{uid}": { implemented: true },
|
|
2675
|
+
"PATCH /SASjsApi/user/by/username/{username}": { implemented: true },
|
|
2676
|
+
// The document also marks `password` optional on these deletes, while the
|
|
2677
|
+
// server's schema requires it for anyone who is not an administrator, and for
|
|
2678
|
+
// an account deleting itself. A host should treat a passwordless delete from a
|
|
2679
|
+
// non-admin as the refusal it is; this package answers 401 `Invalid password.`
|
|
2680
|
+
// where the server's schema answers 400 `"password" is required`.
|
|
2681
|
+
"DELETE /SASjsApi/user/{uid}": { implemented: true },
|
|
2682
|
+
"DELETE /SASjsApi/user/by/username/{username}": { implemented: true }
|
|
2503
2683
|
};
|
|
2504
2684
|
|
|
2505
2685
|
// src/routes/index.ts
|
|
@@ -2591,6 +2771,7 @@ var SASjsApi = (() => {
|
|
|
2591
2771
|
};
|
|
2592
2772
|
|
|
2593
2773
|
// src/host/dispatch.ts
|
|
2774
|
+
var SECONDS_IN_A_DAY = 86400;
|
|
2594
2775
|
var validate = (route, request) => {
|
|
2595
2776
|
for (const param of route.params) {
|
|
2596
2777
|
if (!param.required || param.in === "formData" || param.in === "header")
|
|
@@ -2611,10 +2792,31 @@ var SASjsApi = (() => {
|
|
|
2611
2792
|
var dispatch = async (services, routeId, request = {}) => {
|
|
2612
2793
|
const route = routeById(routeId);
|
|
2613
2794
|
if (!route) throw new ApiError(404, `No such operation: ${routeId}`);
|
|
2795
|
+
const { drive, identity, records } = services;
|
|
2796
|
+
const identityScoped = ["User", "Group", "Permission", "Client"].includes(route.tag) || routeId === "PATCH /SASjsApi/auth/updatePassword";
|
|
2797
|
+
if (identityScoped && records) {
|
|
2798
|
+
const subject = request.subject ?? (identity ? await identity.authenticate(request.token) : void 0);
|
|
2799
|
+
if (!subject) {
|
|
2800
|
+
if (!identity)
|
|
2801
|
+
throw new ApiError(
|
|
2802
|
+
501,
|
|
2803
|
+
`This host has no identity service, so it cannot answer ${routeId}.`
|
|
2804
|
+
);
|
|
2805
|
+
throw new ApiError(401, "Unauthorized");
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2614
2808
|
validate(route, request);
|
|
2615
|
-
const { drive, identity } = services;
|
|
2616
2809
|
const query = request.query || {};
|
|
2617
2810
|
const body = request.body || {};
|
|
2811
|
+
const params = request.params || {};
|
|
2812
|
+
const required = () => {
|
|
2813
|
+
if (!records)
|
|
2814
|
+
throw new ApiError(
|
|
2815
|
+
501,
|
|
2816
|
+
`This host has no record service, so it cannot answer ${routeId}.`
|
|
2817
|
+
);
|
|
2818
|
+
return records;
|
|
2819
|
+
};
|
|
2618
2820
|
const caller = async () => {
|
|
2619
2821
|
if (request.subject) return request.subject;
|
|
2620
2822
|
return identity?.authenticate(request.token);
|
|
@@ -2706,6 +2908,169 @@ var SASjsApi = (() => {
|
|
|
2706
2908
|
}
|
|
2707
2909
|
case "GET /SASjsApi/drive/filetree":
|
|
2708
2910
|
return { status: 200, body: await drive.tree(query._folderPath) };
|
|
2911
|
+
// ------------------------------------------------------------- records
|
|
2912
|
+
//
|
|
2913
|
+
// The administration routes: users, groups, permission rules, clients and the
|
|
2914
|
+
// caller's own password. Each handler resolves the caller and passes it on;
|
|
2915
|
+
// the admin gate and the wording live in the service.
|
|
2916
|
+
case "GET /SASjsApi/user":
|
|
2917
|
+
return { status: 200, body: await required().listUsers(await caller()) };
|
|
2918
|
+
case "GET /SASjsApi/user/{uid}":
|
|
2919
|
+
return {
|
|
2920
|
+
status: 200,
|
|
2921
|
+
body: await required().getUser(await caller(), {
|
|
2922
|
+
uid: String(params.uid ?? "")
|
|
2923
|
+
})
|
|
2924
|
+
};
|
|
2925
|
+
case "GET /SASjsApi/user/by/username/{username}":
|
|
2926
|
+
return {
|
|
2927
|
+
status: 200,
|
|
2928
|
+
body: await required().getUser(await caller(), {
|
|
2929
|
+
username: String(params.username ?? "")
|
|
2930
|
+
})
|
|
2931
|
+
};
|
|
2932
|
+
case "POST /SASjsApi/user":
|
|
2933
|
+
return {
|
|
2934
|
+
status: 200,
|
|
2935
|
+
body: await required().createUser(await caller(), {
|
|
2936
|
+
username: String(body.username ?? ""),
|
|
2937
|
+
displayName: String(body.displayName ?? ""),
|
|
2938
|
+
password: String(body.password ?? ""),
|
|
2939
|
+
isAdmin: body.isAdmin,
|
|
2940
|
+
isActive: body.isActive,
|
|
2941
|
+
autoExec: body.autoExec
|
|
2942
|
+
})
|
|
2943
|
+
};
|
|
2944
|
+
case "PATCH /SASjsApi/user/{uid}":
|
|
2945
|
+
return {
|
|
2946
|
+
status: 200,
|
|
2947
|
+
body: await required().updateUser(
|
|
2948
|
+
await caller(),
|
|
2949
|
+
{ uid: String(params.uid ?? "") },
|
|
2950
|
+
body
|
|
2951
|
+
)
|
|
2952
|
+
};
|
|
2953
|
+
case "PATCH /SASjsApi/user/by/username/{username}":
|
|
2954
|
+
return {
|
|
2955
|
+
status: 200,
|
|
2956
|
+
body: await required().updateUser(
|
|
2957
|
+
await caller(),
|
|
2958
|
+
{ username: String(params.username ?? "") },
|
|
2959
|
+
body
|
|
2960
|
+
)
|
|
2961
|
+
};
|
|
2962
|
+
case "DELETE /SASjsApi/user/{uid}":
|
|
2963
|
+
await required().deleteUser(
|
|
2964
|
+
await caller(),
|
|
2965
|
+
{ uid: String(params.uid ?? "") },
|
|
2966
|
+
{ password: body.password }
|
|
2967
|
+
);
|
|
2968
|
+
return { status: 204 };
|
|
2969
|
+
case "DELETE /SASjsApi/user/by/username/{username}":
|
|
2970
|
+
await required().deleteUser(
|
|
2971
|
+
await caller(),
|
|
2972
|
+
{ username: String(params.username ?? "") },
|
|
2973
|
+
{ password: body.password }
|
|
2974
|
+
);
|
|
2975
|
+
return { status: 204 };
|
|
2976
|
+
case "GET /SASjsApi/group":
|
|
2977
|
+
return { status: 200, body: await required().listGroups(await caller()) };
|
|
2978
|
+
case "GET /SASjsApi/group/{uid}":
|
|
2979
|
+
return {
|
|
2980
|
+
status: 200,
|
|
2981
|
+
body: await required().getGroup(await caller(), {
|
|
2982
|
+
uid: String(params.uid ?? "")
|
|
2983
|
+
})
|
|
2984
|
+
};
|
|
2985
|
+
case "GET /SASjsApi/group/by/groupname/{name}":
|
|
2986
|
+
return {
|
|
2987
|
+
status: 200,
|
|
2988
|
+
body: await required().getGroup(await caller(), {
|
|
2989
|
+
name: String(params.name ?? "")
|
|
2990
|
+
})
|
|
2991
|
+
};
|
|
2992
|
+
case "POST /SASjsApi/group":
|
|
2993
|
+
return {
|
|
2994
|
+
status: 200,
|
|
2995
|
+
body: await required().createGroup(await caller(), {
|
|
2996
|
+
name: String(body.name ?? ""),
|
|
2997
|
+
description: String(body.description ?? ""),
|
|
2998
|
+
isActive: body.isActive
|
|
2999
|
+
})
|
|
3000
|
+
};
|
|
3001
|
+
case "POST /SASjsApi/group/{groupUid}/{userUid}":
|
|
3002
|
+
return {
|
|
3003
|
+
status: 200,
|
|
3004
|
+
body: await required().addUserToGroup(
|
|
3005
|
+
await caller(),
|
|
3006
|
+
String(params.groupUid ?? ""),
|
|
3007
|
+
String(params.userUid ?? "")
|
|
3008
|
+
)
|
|
3009
|
+
};
|
|
3010
|
+
case "DELETE /SASjsApi/group/{groupUid}/{userUid}":
|
|
3011
|
+
return {
|
|
3012
|
+
status: 200,
|
|
3013
|
+
body: await required().removeUserFromGroup(
|
|
3014
|
+
await caller(),
|
|
3015
|
+
String(params.groupUid ?? ""),
|
|
3016
|
+
String(params.userUid ?? "")
|
|
3017
|
+
)
|
|
3018
|
+
};
|
|
3019
|
+
case "DELETE /SASjsApi/group/{uid}":
|
|
3020
|
+
await required().deleteGroup(await caller(), String(params.uid ?? ""));
|
|
3021
|
+
return { status: 200 };
|
|
3022
|
+
case "GET /SASjsApi/permission":
|
|
3023
|
+
return {
|
|
3024
|
+
status: 200,
|
|
3025
|
+
body: await required().listPermissions(await caller())
|
|
3026
|
+
};
|
|
3027
|
+
case "POST /SASjsApi/permission":
|
|
3028
|
+
return {
|
|
3029
|
+
status: 200,
|
|
3030
|
+
body: await required().createPermission(await caller(), {
|
|
3031
|
+
path: String(body.path ?? ""),
|
|
3032
|
+
type: String(body.type ?? ""),
|
|
3033
|
+
setting: String(body.setting ?? ""),
|
|
3034
|
+
principalType: String(body.principalType ?? ""),
|
|
3035
|
+
principalId: String(body.principalId ?? "")
|
|
3036
|
+
})
|
|
3037
|
+
};
|
|
3038
|
+
case "PATCH /SASjsApi/permission/{uid}":
|
|
3039
|
+
return {
|
|
3040
|
+
status: 200,
|
|
3041
|
+
body: await required().updatePermission(
|
|
3042
|
+
await caller(),
|
|
3043
|
+
String(params.uid ?? ""),
|
|
3044
|
+
{ setting: String(body.setting ?? "") }
|
|
3045
|
+
)
|
|
3046
|
+
};
|
|
3047
|
+
case "DELETE /SASjsApi/permission/{uid}":
|
|
3048
|
+
await required().deletePermission(
|
|
3049
|
+
await caller(),
|
|
3050
|
+
String(params.uid ?? "")
|
|
3051
|
+
);
|
|
3052
|
+
return { status: 204 };
|
|
3053
|
+
case "GET /SASjsApi/client":
|
|
3054
|
+
return { status: 200, body: await required().listClients(await caller()) };
|
|
3055
|
+
case "POST /SASjsApi/client":
|
|
3056
|
+
return {
|
|
3057
|
+
status: 200,
|
|
3058
|
+
// An omitted lifetime is the schema's default, not NaN: the server's
|
|
3059
|
+
// Client model declares 86400 and 2592000, and a NaN lifetime would make
|
|
3060
|
+
// every token the client buys expire on arrival.
|
|
3061
|
+
body: await required().createClient(await caller(), {
|
|
3062
|
+
clientId: String(body.clientId ?? ""),
|
|
3063
|
+
clientSecret: String(body.clientSecret ?? ""),
|
|
3064
|
+
accessTokenExpiration: body.accessTokenExpiration === void 0 ? SECONDS_IN_A_DAY : Number(body.accessTokenExpiration),
|
|
3065
|
+
refreshTokenExpiration: body.refreshTokenExpiration === void 0 ? SECONDS_IN_A_DAY * 30 : Number(body.refreshTokenExpiration)
|
|
3066
|
+
})
|
|
3067
|
+
};
|
|
3068
|
+
case "PATCH /SASjsApi/auth/updatePassword":
|
|
3069
|
+
await required().updatePassword(await caller(), {
|
|
3070
|
+
currentPassword: String(body.currentPassword ?? ""),
|
|
3071
|
+
newPassword: String(body.newPassword ?? "")
|
|
3072
|
+
});
|
|
3073
|
+
return { status: 204 };
|
|
2709
3074
|
default: {
|
|
2710
3075
|
const reason = routeById(routeId)?.reason;
|
|
2711
3076
|
throw new ApiError(
|
|
@@ -2732,7 +3097,29 @@ var SASjsApi = (() => {
|
|
|
2732
3097
|
"POST /SASLogon/login",
|
|
2733
3098
|
"POST /SASLogon/authorize",
|
|
2734
3099
|
"GET /SASLogon/logout",
|
|
2735
|
-
"GET /SASjsApi/session"
|
|
3100
|
+
"GET /SASjsApi/session",
|
|
3101
|
+
"GET /SASjsApi/user",
|
|
3102
|
+
"GET /SASjsApi/user/{uid}",
|
|
3103
|
+
"GET /SASjsApi/user/by/username/{username}",
|
|
3104
|
+
"POST /SASjsApi/user",
|
|
3105
|
+
"PATCH /SASjsApi/user/{uid}",
|
|
3106
|
+
"PATCH /SASjsApi/user/by/username/{username}",
|
|
3107
|
+
"DELETE /SASjsApi/user/{uid}",
|
|
3108
|
+
"DELETE /SASjsApi/user/by/username/{username}",
|
|
3109
|
+
"GET /SASjsApi/group",
|
|
3110
|
+
"GET /SASjsApi/group/{uid}",
|
|
3111
|
+
"GET /SASjsApi/group/by/groupname/{name}",
|
|
3112
|
+
"POST /SASjsApi/group",
|
|
3113
|
+
"POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
3114
|
+
"DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
3115
|
+
"DELETE /SASjsApi/group/{uid}",
|
|
3116
|
+
"GET /SASjsApi/permission",
|
|
3117
|
+
"POST /SASjsApi/permission",
|
|
3118
|
+
"PATCH /SASjsApi/permission/{uid}",
|
|
3119
|
+
"DELETE /SASjsApi/permission/{uid}",
|
|
3120
|
+
"GET /SASjsApi/client",
|
|
3121
|
+
"POST /SASjsApi/client",
|
|
3122
|
+
"PATCH /SASjsApi/auth/updatePassword"
|
|
2736
3123
|
];
|
|
2737
3124
|
|
|
2738
3125
|
// src/identity/service.ts
|
|
@@ -2821,37 +3208,365 @@ var SASjsApi = (() => {
|
|
|
2821
3208
|
needsToUpdatePassword: user3.needsToUpdatePassword
|
|
2822
3209
|
});
|
|
2823
3210
|
|
|
2824
|
-
// src/identity/
|
|
2825
|
-
var
|
|
2826
|
-
var
|
|
2827
|
-
var
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
3211
|
+
// src/identity/records.ts
|
|
3212
|
+
var ALL_USERS_GROUP = "all-users";
|
|
3213
|
+
var PUBLIC_GROUP = "public";
|
|
3214
|
+
var unauthorized2 = () => new ApiError(401, "Unauthorized");
|
|
3215
|
+
var requireCaller = async (store, caller) => {
|
|
3216
|
+
if (!caller) throw unauthorized2();
|
|
3217
|
+
const user3 = await store.userById(caller.userId);
|
|
3218
|
+
if (!user3 || !user3.isActive) throw unauthorized2();
|
|
3219
|
+
return user3;
|
|
3220
|
+
};
|
|
3221
|
+
var requireAdmin = async (store, caller) => {
|
|
3222
|
+
const user3 = await requireCaller(store, caller);
|
|
3223
|
+
if (!user3.isAdmin) throw new ApiError(401, "Admin account required");
|
|
3224
|
+
return user3;
|
|
3225
|
+
};
|
|
3226
|
+
var requireSelfOrAdmin = async (store, caller, target) => {
|
|
3227
|
+
const user3 = await requireCaller(store, caller);
|
|
3228
|
+
if (!user3.isAdmin && user3.uid !== target?.uid)
|
|
3229
|
+
throw new ApiError(401, "Admin account required");
|
|
3230
|
+
return user3;
|
|
3231
|
+
};
|
|
3232
|
+
var MAX_DISPLAY_NAME_LENGTH = 100;
|
|
3233
|
+
var sanitiseDisplayName = (value) => String(value ?? "").replace(/[\u0000-\u001F\u007F]/g, "").trim().slice(0, MAX_DISPLAY_NAME_LENGTH);
|
|
3234
|
+
var asUser = (user3) => ({
|
|
3235
|
+
uid: user3.uid,
|
|
3236
|
+
username: user3.username,
|
|
3237
|
+
displayName: user3.displayName,
|
|
3238
|
+
isAdmin: user3.isAdmin
|
|
3239
|
+
});
|
|
3240
|
+
var asGroup = (group) => ({
|
|
3241
|
+
uid: group.uid,
|
|
3242
|
+
name: group.name,
|
|
3243
|
+
description: group.description
|
|
3244
|
+
});
|
|
3245
|
+
var createRecordService = (store) => {
|
|
3246
|
+
const groupDetails = async (group) => {
|
|
3247
|
+
const members = [];
|
|
3248
|
+
for (const uid of group.users) {
|
|
3249
|
+
const user3 = await store.userById(uid);
|
|
3250
|
+
if (user3) members.push(asUser(user3));
|
|
2849
3251
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3252
|
+
return { ...asGroup(group), isActive: group.isActive, users: members };
|
|
3253
|
+
};
|
|
3254
|
+
const userDetails = async (user3, withAutoExec) => {
|
|
3255
|
+
const groups = [];
|
|
3256
|
+
for (const uid of user3.groups) {
|
|
3257
|
+
const group = await store.groupById(uid);
|
|
3258
|
+
if (group) groups.push(asGroup(group));
|
|
3259
|
+
}
|
|
3260
|
+
const details = {
|
|
3261
|
+
...asUser(user3),
|
|
3262
|
+
isActive: user3.isActive,
|
|
3263
|
+
needsToUpdatePassword: user3.needsToUpdatePassword,
|
|
3264
|
+
groups
|
|
3265
|
+
};
|
|
3266
|
+
if (withAutoExec) details.autoExec = user3.autoExec ?? "";
|
|
3267
|
+
return details;
|
|
3268
|
+
};
|
|
3269
|
+
const findBy = async (by) => by.uid !== void 0 ? store.userById(by.uid) : store.userByName(by.username ?? "");
|
|
3270
|
+
const permissionDetails = async (permission) => {
|
|
3271
|
+
const response = {
|
|
3272
|
+
uid: permission.uid,
|
|
3273
|
+
path: permission.path,
|
|
3274
|
+
type: permission.type,
|
|
3275
|
+
setting: permission.setting
|
|
3276
|
+
};
|
|
3277
|
+
if (permission.user) {
|
|
3278
|
+
const user3 = await store.userById(permission.user);
|
|
3279
|
+
if (user3) response.user = asUser(user3);
|
|
3280
|
+
}
|
|
3281
|
+
if (permission.group) {
|
|
3282
|
+
const group = await store.groupById(permission.group);
|
|
3283
|
+
if (group) response.group = await groupDetails(group);
|
|
3284
|
+
}
|
|
3285
|
+
return response;
|
|
3286
|
+
};
|
|
3287
|
+
return {
|
|
3288
|
+
async listUsers(caller) {
|
|
3289
|
+
await requireCaller(store, caller);
|
|
3290
|
+
const users = await store.users();
|
|
3291
|
+
return users.map(asUser);
|
|
3292
|
+
},
|
|
3293
|
+
async getUser(caller, by) {
|
|
3294
|
+
const reader = await requireCaller(store, caller);
|
|
3295
|
+
const user3 = await findBy(by);
|
|
3296
|
+
if (!user3) throw new ApiError(404, "User is not found.");
|
|
3297
|
+
const withAutoExec = reader.isAdmin || reader.uid === user3.uid;
|
|
3298
|
+
return userDetails(user3, withAutoExec);
|
|
3299
|
+
},
|
|
3300
|
+
async createUser(caller, body) {
|
|
3301
|
+
await requireAdmin(store, caller);
|
|
3302
|
+
if (await store.userByName(body.username))
|
|
3303
|
+
throw new ApiError(409, "Username already exists.");
|
|
3304
|
+
const created = await store.createUser({
|
|
3305
|
+
username: body.username,
|
|
3306
|
+
displayName: sanitiseDisplayName(body.displayName),
|
|
3307
|
+
isAdmin: body.isAdmin === true,
|
|
3308
|
+
isActive: body.isActive !== false,
|
|
3309
|
+
autoExec: body.autoExec
|
|
3310
|
+
});
|
|
3311
|
+
await store.setPassword(created.uid, body.password);
|
|
3312
|
+
const allUsers = await store.groupByName(ALL_USERS_GROUP);
|
|
3313
|
+
if (allUsers) await store.addToGroup(allUsers.uid, created.uid);
|
|
3314
|
+
const stored = await store.userById(created.uid);
|
|
3315
|
+
return userDetails(stored ?? created, true);
|
|
3316
|
+
},
|
|
3317
|
+
async updateUser(caller, by, body) {
|
|
3318
|
+
const current = await findBy(by);
|
|
3319
|
+
await requireSelfOrAdmin(store, caller, current);
|
|
3320
|
+
const change = {};
|
|
3321
|
+
const displayName = sanitiseDisplayName(body.displayName);
|
|
3322
|
+
if (body.username && body.username !== current?.username && current?.authProvider)
|
|
3323
|
+
throw new ApiError(
|
|
3324
|
+
405,
|
|
3325
|
+
"Can not update username of user that is created by an external auth provider."
|
|
3326
|
+
);
|
|
3327
|
+
if (displayName && displayName !== current?.displayName && current?.authProvider)
|
|
3328
|
+
throw new ApiError(
|
|
3329
|
+
405,
|
|
3330
|
+
"Can not update display name of user that is created by an external auth provider."
|
|
3331
|
+
);
|
|
3332
|
+
if (body.username) {
|
|
3333
|
+
const holder = await store.userByName(body.username);
|
|
3334
|
+
if (holder && holder.uid !== current?.uid)
|
|
3335
|
+
throw new ApiError(409, "Username already exists.");
|
|
3336
|
+
change.username = body.username;
|
|
3337
|
+
}
|
|
3338
|
+
if (displayName) change.displayName = displayName;
|
|
3339
|
+
if (body.isAdmin !== void 0) change.isAdmin = body.isAdmin;
|
|
3340
|
+
if (body.isActive !== void 0) change.isActive = body.isActive;
|
|
3341
|
+
if (body.autoExec !== void 0) change.autoExec = body.autoExec;
|
|
3342
|
+
const updated = current ? await store.updateUser(current.uid, change) : void 0;
|
|
3343
|
+
if (!updated)
|
|
3344
|
+
throw new ApiError(
|
|
3345
|
+
404,
|
|
3346
|
+
`Unable to find user with ${by.uid || by.username}`
|
|
3347
|
+
);
|
|
3348
|
+
if (body.password) await store.setPassword(updated.uid, body.password);
|
|
3349
|
+
const stored = await store.userById(updated.uid);
|
|
3350
|
+
return userDetails(stored ?? updated, true);
|
|
3351
|
+
},
|
|
3352
|
+
async deleteUser(caller, by, body) {
|
|
3353
|
+
const current = await findBy(by);
|
|
3354
|
+
const actor = await requireSelfOrAdmin(store, caller, current);
|
|
3355
|
+
if (!current) throw new ApiError(404, "User is not found.");
|
|
3356
|
+
if (!actor.isAdmin) {
|
|
3357
|
+
if (!await store.passwordMatches(current, body.password ?? ""))
|
|
3358
|
+
throw new ApiError(401, "Invalid password.");
|
|
3359
|
+
}
|
|
3360
|
+
await store.deleteUser(current.uid);
|
|
3361
|
+
},
|
|
3362
|
+
async listGroups(caller) {
|
|
3363
|
+
await requireCaller(store, caller);
|
|
3364
|
+
const groups = await store.groups();
|
|
3365
|
+
return groups.map(asGroup);
|
|
3366
|
+
},
|
|
3367
|
+
async getGroup(caller, by) {
|
|
3368
|
+
await requireCaller(store, caller);
|
|
3369
|
+
const group = by.uid !== void 0 ? await store.groupById(by.uid) : await store.groupByName(by.name ?? "");
|
|
3370
|
+
if (!group) throw new ApiError(404, "Group not found.");
|
|
3371
|
+
return groupDetails(group);
|
|
3372
|
+
},
|
|
3373
|
+
async createGroup(caller, body) {
|
|
3374
|
+
await requireAdmin(store, caller);
|
|
3375
|
+
if (await store.groupByName(body.name))
|
|
3376
|
+
throw new ApiError(409, "Group name already exists.");
|
|
3377
|
+
const created = await store.createGroup({
|
|
3378
|
+
name: body.name,
|
|
3379
|
+
description: body.description,
|
|
3380
|
+
isActive: body.isActive !== false
|
|
3381
|
+
});
|
|
3382
|
+
return groupDetails(created);
|
|
3383
|
+
},
|
|
3384
|
+
async deleteGroup(caller, uid) {
|
|
3385
|
+
await requireAdmin(store, caller);
|
|
3386
|
+
if (!await store.groupById(uid))
|
|
3387
|
+
throw new ApiError(404, "Group not found.");
|
|
3388
|
+
await store.deleteGroup(uid);
|
|
3389
|
+
},
|
|
3390
|
+
async addUserToGroup(caller, groupUid, userUid) {
|
|
3391
|
+
await requireAdmin(store, caller);
|
|
3392
|
+
const group = await store.groupById(groupUid);
|
|
3393
|
+
if (!group) throw new ApiError(404, "Group not found.");
|
|
3394
|
+
if (group.name === PUBLIC_GROUP)
|
|
3395
|
+
throw new ApiError(
|
|
3396
|
+
400,
|
|
3397
|
+
`Can't add/remove user to '${PUBLIC_GROUP}' group.`
|
|
3398
|
+
);
|
|
3399
|
+
if (group.authProvider)
|
|
3400
|
+
throw new ApiError(
|
|
3401
|
+
405,
|
|
3402
|
+
"Can't add/remove user to group created by external auth provider."
|
|
3403
|
+
);
|
|
3404
|
+
const user3 = await store.userById(userUid);
|
|
3405
|
+
if (!user3) throw new ApiError(404, "User not found.");
|
|
3406
|
+
if (user3.authProvider)
|
|
3407
|
+
throw new ApiError(
|
|
3408
|
+
405,
|
|
3409
|
+
"Can't add/remove user to group created by external auth provider."
|
|
3410
|
+
);
|
|
3411
|
+
const updated = await store.addToGroup(groupUid, userUid);
|
|
3412
|
+
if (!updated) throw new ApiError(400, "Unable to update group.");
|
|
3413
|
+
return groupDetails(updated);
|
|
3414
|
+
},
|
|
3415
|
+
async removeUserFromGroup(caller, groupUid, userUid) {
|
|
3416
|
+
await requireAdmin(store, caller);
|
|
3417
|
+
const group = await store.groupById(groupUid);
|
|
3418
|
+
if (!group) throw new ApiError(404, "Group not found.");
|
|
3419
|
+
if (group.name === PUBLIC_GROUP)
|
|
3420
|
+
throw new ApiError(
|
|
3421
|
+
400,
|
|
3422
|
+
`Can't add/remove user to '${PUBLIC_GROUP}' group.`
|
|
3423
|
+
);
|
|
3424
|
+
if (group.authProvider)
|
|
3425
|
+
throw new ApiError(
|
|
3426
|
+
405,
|
|
3427
|
+
"Can't add/remove user to group created by external auth provider."
|
|
3428
|
+
);
|
|
3429
|
+
const user3 = await store.userById(userUid);
|
|
3430
|
+
if (!user3) throw new ApiError(404, "User not found.");
|
|
3431
|
+
if (user3.authProvider)
|
|
3432
|
+
throw new ApiError(
|
|
3433
|
+
405,
|
|
3434
|
+
"Can't add/remove user to group created by external auth provider."
|
|
3435
|
+
);
|
|
3436
|
+
const updated = await store.removeFromGroup(groupUid, userUid);
|
|
3437
|
+
if (!updated) throw new ApiError(400, "Unable to update group.");
|
|
3438
|
+
return groupDetails(updated);
|
|
3439
|
+
},
|
|
3440
|
+
async listPermissions(caller) {
|
|
3441
|
+
const reader = await requireCaller(store, caller);
|
|
3442
|
+
const rules = await store.permissions();
|
|
3443
|
+
const visible = reader.isAdmin ? rules : rules.filter(
|
|
3444
|
+
(rule) => rule.user === reader.uid || rule.group !== void 0 && reader.groups.includes(rule.group)
|
|
3445
|
+
);
|
|
3446
|
+
const out = [];
|
|
3447
|
+
for (const rule of visible) out.push(await permissionDetails(rule));
|
|
3448
|
+
return out;
|
|
3449
|
+
},
|
|
3450
|
+
async createPermission(caller, body) {
|
|
3451
|
+
await requireAdmin(store, caller);
|
|
3452
|
+
if (body.principalType === "user") {
|
|
3453
|
+
const user3 = await store.userById(body.principalId);
|
|
3454
|
+
if (!user3) throw new ApiError(404, "User not found.");
|
|
3455
|
+
if (user3.isAdmin)
|
|
3456
|
+
throw new ApiError(400, "Can not add permission for admin user.");
|
|
3457
|
+
const exists = (await store.permissions()).some(
|
|
3458
|
+
(rule) => rule.path === body.path && rule.type === body.type && rule.user === body.principalId
|
|
3459
|
+
);
|
|
3460
|
+
if (exists)
|
|
3461
|
+
throw new ApiError(
|
|
3462
|
+
409,
|
|
3463
|
+
"Permission already exists with provided Path, Type and User."
|
|
3464
|
+
);
|
|
3465
|
+
const created = await store.createPermission({
|
|
3466
|
+
path: body.path,
|
|
3467
|
+
type: body.type,
|
|
3468
|
+
setting: body.setting,
|
|
3469
|
+
user: body.principalId
|
|
3470
|
+
});
|
|
3471
|
+
return permissionDetails(created);
|
|
3472
|
+
}
|
|
3473
|
+
if (body.principalType === "group") {
|
|
3474
|
+
const group = await store.groupById(body.principalId);
|
|
3475
|
+
if (!group) throw new ApiError(404, "Group not found.");
|
|
3476
|
+
const exists = (await store.permissions()).some(
|
|
3477
|
+
(rule) => rule.path === body.path && rule.type === body.type && rule.group === body.principalId
|
|
3478
|
+
);
|
|
3479
|
+
if (exists)
|
|
3480
|
+
throw new ApiError(
|
|
3481
|
+
409,
|
|
3482
|
+
"Permission already exists with provided Path, Type and Group."
|
|
3483
|
+
);
|
|
3484
|
+
const created = await store.createPermission({
|
|
3485
|
+
path: body.path,
|
|
3486
|
+
type: body.type,
|
|
3487
|
+
setting: body.setting,
|
|
3488
|
+
group: body.principalId
|
|
3489
|
+
});
|
|
3490
|
+
return permissionDetails(created);
|
|
3491
|
+
}
|
|
3492
|
+
throw new ApiError(
|
|
3493
|
+
400,
|
|
3494
|
+
"Invalid principal type. Valid types are user or group."
|
|
3495
|
+
);
|
|
3496
|
+
},
|
|
3497
|
+
async updatePermission(caller, uid, body) {
|
|
3498
|
+
await requireAdmin(store, caller);
|
|
3499
|
+
const updated = await store.setPermissionSetting(uid, body.setting);
|
|
3500
|
+
if (!updated) throw new ApiError(404, "Permission not found.");
|
|
3501
|
+
return permissionDetails(updated);
|
|
3502
|
+
},
|
|
3503
|
+
async deletePermission(caller, uid) {
|
|
3504
|
+
await requireAdmin(store, caller);
|
|
3505
|
+
if (!await store.permissionById(uid))
|
|
3506
|
+
throw new ApiError(404, "Permission not found.");
|
|
3507
|
+
await store.deletePermission(uid);
|
|
3508
|
+
},
|
|
3509
|
+
async listClients(caller) {
|
|
3510
|
+
await requireAdmin(store, caller);
|
|
3511
|
+
return store.clients();
|
|
3512
|
+
},
|
|
3513
|
+
async createClient(caller, body) {
|
|
3514
|
+
await requireAdmin(store, caller);
|
|
3515
|
+
if (await store.client(body.clientId))
|
|
3516
|
+
throw new ApiError(403, "Error: Client ID already exists.");
|
|
3517
|
+
return store.createClient({
|
|
3518
|
+
clientId: body.clientId,
|
|
3519
|
+
clientSecret: body.clientSecret,
|
|
3520
|
+
accessTokenExpiration: body.accessTokenExpiration,
|
|
3521
|
+
refreshTokenExpiration: body.refreshTokenExpiration
|
|
3522
|
+
});
|
|
3523
|
+
},
|
|
3524
|
+
async updatePassword(caller, body) {
|
|
3525
|
+
const reader = await requireCaller(store, caller);
|
|
3526
|
+
if (reader.authProvider)
|
|
3527
|
+
throw new ApiError(
|
|
3528
|
+
405,
|
|
3529
|
+
"Can not update password of user that is created by an external auth provider."
|
|
3530
|
+
);
|
|
3531
|
+
if (!await store.passwordMatches(reader, body.currentPassword))
|
|
3532
|
+
throw new ApiError(403, "Invalid current password!");
|
|
3533
|
+
await store.setPassword(reader.uid, body.newPassword);
|
|
3534
|
+
await store.updateUser(reader.uid, { needsToUpdatePassword: false });
|
|
3535
|
+
}
|
|
3536
|
+
};
|
|
3537
|
+
};
|
|
3538
|
+
|
|
3539
|
+
// src/identity/fixture.ts
|
|
3540
|
+
var IDENTITY_FORMAT = 1;
|
|
3541
|
+
var SECONDS_IN_A_DAY2 = 86400;
|
|
3542
|
+
var DEFAULT_IDENTITY = {
|
|
3543
|
+
format: IDENTITY_FORMAT,
|
|
3544
|
+
users: [
|
|
3545
|
+
{
|
|
3546
|
+
uid: "user-admin",
|
|
3547
|
+
username: "admin",
|
|
3548
|
+
displayName: "Demo Admin",
|
|
3549
|
+
// long enough to pass the API's own password rule (six characters), so a
|
|
3550
|
+
// demo can sign in against a real server as well as a mock
|
|
3551
|
+
password: "admin123",
|
|
3552
|
+
isAdmin: true,
|
|
3553
|
+
isActive: true,
|
|
3554
|
+
needsToUpdatePassword: false
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
uid: "user-restricted",
|
|
3558
|
+
username: "user2",
|
|
3559
|
+
displayName: "Demo User",
|
|
3560
|
+
password: "user123",
|
|
3561
|
+
isAdmin: false,
|
|
3562
|
+
isActive: true,
|
|
3563
|
+
needsToUpdatePassword: false
|
|
3564
|
+
}
|
|
3565
|
+
],
|
|
3566
|
+
groups: [
|
|
3567
|
+
{
|
|
3568
|
+
uid: "group-data-controller",
|
|
3569
|
+
name: "DCGroup",
|
|
2855
3570
|
description: "Data Controller users",
|
|
2856
3571
|
isActive: true,
|
|
2857
3572
|
users: ["user2"]
|
|
@@ -2891,14 +3606,14 @@ var SASjsApi = (() => {
|
|
|
2891
3606
|
{
|
|
2892
3607
|
clientId: "web_app",
|
|
2893
3608
|
clientSecret: "web_app",
|
|
2894
|
-
accessTokenExpiration:
|
|
2895
|
-
refreshTokenExpiration:
|
|
3609
|
+
accessTokenExpiration: SECONDS_IN_A_DAY2,
|
|
3610
|
+
refreshTokenExpiration: SECONDS_IN_A_DAY2 * 30
|
|
2896
3611
|
},
|
|
2897
3612
|
{
|
|
2898
3613
|
clientId: "sasjs",
|
|
2899
3614
|
clientSecret: "sasjs",
|
|
2900
3615
|
accessTokenExpiration: 3600,
|
|
2901
|
-
refreshTokenExpiration:
|
|
3616
|
+
refreshTokenExpiration: SECONDS_IN_A_DAY2
|
|
2902
3617
|
}
|
|
2903
3618
|
]
|
|
2904
3619
|
};
|
|
@@ -2944,9 +3659,12 @@ var SASjsApi = (() => {
|
|
|
2944
3659
|
var live = (issued, now) => issued && (issued.expiresAt === void 0 || issued.expiresAt > now) ? issued.subject : void 0;
|
|
2945
3660
|
var MemoryIdentityStore = class {
|
|
2946
3661
|
constructor(fixture = DEFAULT_IDENTITY) {
|
|
2947
|
-
this.
|
|
3662
|
+
this.userList = [];
|
|
2948
3663
|
this.passwords = /* @__PURE__ */ new Map();
|
|
2949
|
-
this.
|
|
3664
|
+
this.groupList = /* @__PURE__ */ new Map();
|
|
3665
|
+
this.permissionList = /* @__PURE__ */ new Map();
|
|
3666
|
+
this.clientRecords = /* @__PURE__ */ new Map();
|
|
3667
|
+
this.clientMap = /* @__PURE__ */ new Map();
|
|
2950
3668
|
this.pairs = /* @__PURE__ */ new Map();
|
|
2951
3669
|
this.access = /* @__PURE__ */ new Map();
|
|
2952
3670
|
this.refresh = /* @__PURE__ */ new Map();
|
|
@@ -2958,24 +3676,56 @@ var SASjsApi = (() => {
|
|
|
2958
3676
|
`The identity fixture states format ${String(fixture.format)}; this store reads ${IDENTITY_FORMAT}.`
|
|
2959
3677
|
);
|
|
2960
3678
|
for (const user3 of fixture.users) {
|
|
2961
|
-
this.
|
|
3679
|
+
this.userList.push({
|
|
2962
3680
|
uid: user3.uid,
|
|
2963
3681
|
username: user3.username,
|
|
2964
3682
|
displayName: user3.displayName,
|
|
2965
3683
|
isAdmin: user3.isAdmin,
|
|
2966
3684
|
isActive: user3.isActive,
|
|
2967
|
-
needsToUpdatePassword: user3.needsToUpdatePassword
|
|
3685
|
+
needsToUpdatePassword: user3.needsToUpdatePassword,
|
|
3686
|
+
authProvider: user3.authProvider,
|
|
3687
|
+
autoExec: user3.autoExec
|
|
2968
3688
|
});
|
|
2969
3689
|
this.passwords.set(user3.uid, user3.password);
|
|
2970
3690
|
}
|
|
2971
|
-
for (const client of fixture.clients)
|
|
2972
|
-
this.
|
|
3691
|
+
for (const client of fixture.clients) {
|
|
3692
|
+
this.clientMap.set(client.clientId, {
|
|
3693
|
+
clientId: client.clientId,
|
|
3694
|
+
accessTokenExpiration: client.accessTokenExpiration,
|
|
3695
|
+
refreshTokenExpiration: client.refreshTokenExpiration
|
|
3696
|
+
});
|
|
3697
|
+
this.clientRecords.set(client.clientId, {
|
|
2973
3698
|
clientId: client.clientId,
|
|
3699
|
+
clientSecret: client.clientSecret,
|
|
2974
3700
|
accessTokenExpiration: client.accessTokenExpiration,
|
|
2975
3701
|
refreshTokenExpiration: client.refreshTokenExpiration
|
|
2976
3702
|
});
|
|
3703
|
+
}
|
|
3704
|
+
for (const group of fixture.groups) {
|
|
3705
|
+
const uid = group.uid;
|
|
3706
|
+
this.groupList.set(uid, {
|
|
3707
|
+
uid,
|
|
3708
|
+
name: group.name,
|
|
3709
|
+
description: group.description,
|
|
3710
|
+
isActive: group.isActive,
|
|
3711
|
+
authProvider: group.authProvider,
|
|
3712
|
+
users: group.users.map(
|
|
3713
|
+
(username) => this.userList.find((user3) => user3.username === username)?.uid
|
|
3714
|
+
).filter((member) => member !== void 0)
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
for (const permission of fixture.permissions) {
|
|
3718
|
+
this.permissionList.set(permission.uid, {
|
|
3719
|
+
uid: permission.uid,
|
|
3720
|
+
path: permission.path,
|
|
3721
|
+
type: permission.type,
|
|
3722
|
+
setting: permission.setting,
|
|
3723
|
+
user: permission.user ? this.userList.find((user3) => user3.uid === permission.user)?.uid : void 0,
|
|
3724
|
+
group: permission.group ? this.groupList.get(permission.group)?.uid : void 0
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
2977
3727
|
for (const token of fixture.tokens || []) {
|
|
2978
|
-
const user3 = this.
|
|
3728
|
+
const user3 = this.userList.find(
|
|
2979
3729
|
(candidate) => candidate.username === token.username
|
|
2980
3730
|
);
|
|
2981
3731
|
if (!user3) continue;
|
|
@@ -2989,7 +3739,7 @@ var SASjsApi = (() => {
|
|
|
2989
3739
|
});
|
|
2990
3740
|
}
|
|
2991
3741
|
for (const code of fixture.codes || []) {
|
|
2992
|
-
const user3 = this.
|
|
3742
|
+
const user3 = this.userList.find(
|
|
2993
3743
|
(candidate) => candidate.username === code.username
|
|
2994
3744
|
);
|
|
2995
3745
|
if (!user3) continue;
|
|
@@ -2999,17 +3749,21 @@ var SASjsApi = (() => {
|
|
|
2999
3749
|
}
|
|
3000
3750
|
}
|
|
3001
3751
|
async userByName(username) {
|
|
3002
|
-
|
|
3752
|
+
const user3 = this.userList.find(
|
|
3753
|
+
(candidate) => candidate.username === username
|
|
3754
|
+
);
|
|
3755
|
+
return user3 ? this.recordOf(user3) : void 0;
|
|
3003
3756
|
}
|
|
3004
3757
|
async userById(uid) {
|
|
3005
|
-
|
|
3758
|
+
const user3 = this.userList.find((candidate) => candidate.uid === uid);
|
|
3759
|
+
return user3 ? this.recordOf(user3) : void 0;
|
|
3006
3760
|
}
|
|
3007
3761
|
/** A fixture holds the password in clear, so the check is an equality. */
|
|
3008
3762
|
async passwordMatches(user3, password) {
|
|
3009
3763
|
return this.passwords.get(user3.uid) === password;
|
|
3010
3764
|
}
|
|
3011
3765
|
async client(clientId) {
|
|
3012
|
-
return this.
|
|
3766
|
+
return this.clientMap.get(clientId);
|
|
3013
3767
|
}
|
|
3014
3768
|
async issue(subject, client) {
|
|
3015
3769
|
const issuedAt = Date.now();
|
|
@@ -3087,6 +3841,134 @@ var SASjsApi = (() => {
|
|
|
3087
3841
|
this.claimed.add(code);
|
|
3088
3842
|
return true;
|
|
3089
3843
|
}
|
|
3844
|
+
// ---------------------------------------------------------------- records
|
|
3845
|
+
async users() {
|
|
3846
|
+
return this.userList.map((user3) => this.recordOf(user3));
|
|
3847
|
+
}
|
|
3848
|
+
/** A user as the administration routes report one: groups derived, not stored. */
|
|
3849
|
+
recordOf(user3) {
|
|
3850
|
+
return {
|
|
3851
|
+
...user3,
|
|
3852
|
+
groups: [...this.groupList.values()].filter((group) => group.users.includes(user3.uid)).map((group) => group.uid)
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
async createUser(input) {
|
|
3856
|
+
const user3 = {
|
|
3857
|
+
uid: `user-${randomHex(6)}`,
|
|
3858
|
+
username: input.username,
|
|
3859
|
+
displayName: input.displayName,
|
|
3860
|
+
isAdmin: input.isAdmin,
|
|
3861
|
+
isActive: input.isActive,
|
|
3862
|
+
needsToUpdatePassword: false,
|
|
3863
|
+
autoExec: input.autoExec,
|
|
3864
|
+
authProvider: input.authProvider
|
|
3865
|
+
};
|
|
3866
|
+
this.userList.push(user3);
|
|
3867
|
+
return this.recordOf(user3);
|
|
3868
|
+
}
|
|
3869
|
+
async updateUser(uid, change) {
|
|
3870
|
+
const user3 = this.userList.find((candidate) => candidate.uid === uid);
|
|
3871
|
+
if (!user3) return void 0;
|
|
3872
|
+
if (change.username !== void 0) user3.username = change.username;
|
|
3873
|
+
if (change.displayName !== void 0) user3.displayName = change.displayName;
|
|
3874
|
+
if (change.isAdmin !== void 0) user3.isAdmin = change.isAdmin;
|
|
3875
|
+
if (change.isActive !== void 0) user3.isActive = change.isActive;
|
|
3876
|
+
if (change.needsToUpdatePassword !== void 0)
|
|
3877
|
+
user3.needsToUpdatePassword = change.needsToUpdatePassword;
|
|
3878
|
+
if (change.autoExec !== void 0) user3.autoExec = change.autoExec;
|
|
3879
|
+
return this.recordOf(user3);
|
|
3880
|
+
}
|
|
3881
|
+
async deleteUser(uid) {
|
|
3882
|
+
const index = this.userList.findIndex((user3) => user3.uid === uid);
|
|
3883
|
+
if (index === -1) return;
|
|
3884
|
+
this.userList.splice(index, 1);
|
|
3885
|
+
this.passwords.delete(uid);
|
|
3886
|
+
for (const group of this.groupList.values())
|
|
3887
|
+
group.users = group.users.filter((member) => member !== uid);
|
|
3888
|
+
for (const [key, permission] of this.permissionList)
|
|
3889
|
+
if (permission.user === uid) this.permissionList.delete(key);
|
|
3890
|
+
for (const key of this.pairs.keys())
|
|
3891
|
+
if (key.startsWith(`${uid}|`)) this.pairs.delete(key);
|
|
3892
|
+
}
|
|
3893
|
+
async setPassword(uid, password) {
|
|
3894
|
+
this.passwords.set(uid, password);
|
|
3895
|
+
}
|
|
3896
|
+
async groups() {
|
|
3897
|
+
return [...this.groupList.values()];
|
|
3898
|
+
}
|
|
3899
|
+
async groupById(uid) {
|
|
3900
|
+
return this.groupList.get(uid);
|
|
3901
|
+
}
|
|
3902
|
+
async groupByName(name) {
|
|
3903
|
+
return [...this.groupList.values()].find((group) => group.name === name);
|
|
3904
|
+
}
|
|
3905
|
+
async createGroup(input) {
|
|
3906
|
+
const group = {
|
|
3907
|
+
uid: `group-${randomHex(6)}`,
|
|
3908
|
+
name: input.name,
|
|
3909
|
+
description: input.description,
|
|
3910
|
+
isActive: input.isActive,
|
|
3911
|
+
users: []
|
|
3912
|
+
};
|
|
3913
|
+
this.groupList.set(group.uid, group);
|
|
3914
|
+
return group;
|
|
3915
|
+
}
|
|
3916
|
+
async deleteGroup(uid) {
|
|
3917
|
+
this.groupList.delete(uid);
|
|
3918
|
+
for (const [key, permission] of this.permissionList)
|
|
3919
|
+
if (permission.group === uid) this.permissionList.delete(key);
|
|
3920
|
+
}
|
|
3921
|
+
async addToGroup(groupUid, userUid) {
|
|
3922
|
+
const group = this.groupList.get(groupUid);
|
|
3923
|
+
if (!group) return void 0;
|
|
3924
|
+
if (!group.users.includes(userUid)) group.users.push(userUid);
|
|
3925
|
+
return group;
|
|
3926
|
+
}
|
|
3927
|
+
async removeFromGroup(groupUid, userUid) {
|
|
3928
|
+
const group = this.groupList.get(groupUid);
|
|
3929
|
+
if (!group) return void 0;
|
|
3930
|
+
group.users = group.users.filter((member) => member !== userUid);
|
|
3931
|
+
return group;
|
|
3932
|
+
}
|
|
3933
|
+
async permissions() {
|
|
3934
|
+
return [...this.permissionList.values()];
|
|
3935
|
+
}
|
|
3936
|
+
async permissionById(uid) {
|
|
3937
|
+
return this.permissionList.get(uid);
|
|
3938
|
+
}
|
|
3939
|
+
async createPermission(input) {
|
|
3940
|
+
const permission = {
|
|
3941
|
+
uid: `rule-${randomHex(6)}`,
|
|
3942
|
+
path: input.path,
|
|
3943
|
+
type: input.type,
|
|
3944
|
+
setting: input.setting,
|
|
3945
|
+
user: input.user,
|
|
3946
|
+
group: input.group
|
|
3947
|
+
};
|
|
3948
|
+
this.permissionList.set(permission.uid, permission);
|
|
3949
|
+
return permission;
|
|
3950
|
+
}
|
|
3951
|
+
async setPermissionSetting(uid, setting) {
|
|
3952
|
+
const permission = this.permissionList.get(uid);
|
|
3953
|
+
if (!permission) return void 0;
|
|
3954
|
+
permission.setting = setting;
|
|
3955
|
+
return permission;
|
|
3956
|
+
}
|
|
3957
|
+
async deletePermission(uid) {
|
|
3958
|
+
this.permissionList.delete(uid);
|
|
3959
|
+
}
|
|
3960
|
+
async clients() {
|
|
3961
|
+
return [...this.clientRecords.values()];
|
|
3962
|
+
}
|
|
3963
|
+
async createClient(input) {
|
|
3964
|
+
this.clientRecords.set(input.clientId, input);
|
|
3965
|
+
this.clientMap.set(input.clientId, {
|
|
3966
|
+
clientId: input.clientId,
|
|
3967
|
+
accessTokenExpiration: input.accessTokenExpiration,
|
|
3968
|
+
refreshTokenExpiration: input.refreshTokenExpiration
|
|
3969
|
+
});
|
|
3970
|
+
return input;
|
|
3971
|
+
}
|
|
3090
3972
|
};
|
|
3091
3973
|
|
|
3092
3974
|
// src/corpus/drive.ts
|
|
@@ -3774,15 +4656,1233 @@ var SASjsApi = (() => {
|
|
|
3774
4656
|
}
|
|
3775
4657
|
];
|
|
3776
4658
|
|
|
3777
|
-
// src/corpus/
|
|
3778
|
-
var
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
4659
|
+
// src/corpus/records.ts
|
|
4660
|
+
var SIGNED_IN_TOKENS = [
|
|
4661
|
+
{
|
|
4662
|
+
username: "admin",
|
|
4663
|
+
clientId: "web_app",
|
|
4664
|
+
accessToken: "admin-access",
|
|
4665
|
+
refreshToken: "admin-refresh"
|
|
4666
|
+
},
|
|
4667
|
+
{
|
|
4668
|
+
username: "user2",
|
|
4669
|
+
clientId: "web_app",
|
|
4670
|
+
accessToken: "user-access",
|
|
4671
|
+
refreshToken: "user-refresh"
|
|
4672
|
+
}
|
|
4673
|
+
];
|
|
4674
|
+
var ADMIN_SIGNED_IN = {
|
|
4675
|
+
...DEFAULT_IDENTITY,
|
|
4676
|
+
tokens: SIGNED_IN_TOKENS
|
|
4677
|
+
};
|
|
4678
|
+
var PROVIDER_MANAGED = {
|
|
4679
|
+
...ADMIN_SIGNED_IN,
|
|
4680
|
+
tokens: [
|
|
4681
|
+
...SIGNED_IN_TOKENS,
|
|
4682
|
+
{
|
|
4683
|
+
username: "diruser",
|
|
4684
|
+
clientId: "web_app",
|
|
4685
|
+
accessToken: "dir-access",
|
|
4686
|
+
refreshToken: "dir-refresh"
|
|
4687
|
+
}
|
|
4688
|
+
],
|
|
4689
|
+
users: [
|
|
4690
|
+
...ADMIN_SIGNED_IN.users,
|
|
4691
|
+
{
|
|
4692
|
+
uid: "user-directory",
|
|
4693
|
+
username: "diruser",
|
|
4694
|
+
displayName: "Directory User",
|
|
4695
|
+
password: "dir123",
|
|
4696
|
+
isAdmin: false,
|
|
4697
|
+
isActive: true,
|
|
4698
|
+
needsToUpdatePassword: false,
|
|
4699
|
+
authProvider: "oidc"
|
|
4700
|
+
}
|
|
4701
|
+
],
|
|
4702
|
+
groups: [
|
|
4703
|
+
...ADMIN_SIGNED_IN.groups,
|
|
4704
|
+
{
|
|
4705
|
+
uid: "group-directory",
|
|
4706
|
+
name: "Directory",
|
|
4707
|
+
description: "Managed by the directory",
|
|
4708
|
+
isActive: true,
|
|
4709
|
+
users: [],
|
|
4710
|
+
authProvider: "oidc"
|
|
4711
|
+
},
|
|
4712
|
+
{
|
|
4713
|
+
uid: "group-public",
|
|
4714
|
+
name: "public",
|
|
4715
|
+
description: "Everyone",
|
|
4716
|
+
isActive: true,
|
|
4717
|
+
users: []
|
|
4718
|
+
}
|
|
4719
|
+
]
|
|
4720
|
+
};
|
|
4721
|
+
var ADMIN = "admin-access";
|
|
4722
|
+
var RESTRICTED = "user-access";
|
|
4723
|
+
var RESTRICTED_UID = "user-restricted";
|
|
4724
|
+
var ADMIN_UID = "user-admin";
|
|
4725
|
+
var RECORD_CORPUS = [
|
|
4726
|
+
// ------------------------------------------------------------------ users
|
|
4727
|
+
{
|
|
4728
|
+
id: "list-users",
|
|
4729
|
+
route: "GET /SASjsApi/user",
|
|
4730
|
+
description: "lists the accounts any signed-in caller may see",
|
|
4731
|
+
identity: ADMIN_SIGNED_IN,
|
|
4732
|
+
request: { token: ADMIN },
|
|
4733
|
+
expect: {
|
|
4734
|
+
status: 200,
|
|
4735
|
+
body: [
|
|
4736
|
+
{
|
|
4737
|
+
uid: ADMIN_UID,
|
|
4738
|
+
username: "admin",
|
|
4739
|
+
displayName: "Demo Admin",
|
|
4740
|
+
isAdmin: true
|
|
4741
|
+
},
|
|
4742
|
+
{
|
|
4743
|
+
uid: RESTRICTED_UID,
|
|
4744
|
+
username: "user2",
|
|
4745
|
+
displayName: "Demo User",
|
|
4746
|
+
isAdmin: false
|
|
4747
|
+
}
|
|
4748
|
+
]
|
|
4749
|
+
}
|
|
4750
|
+
},
|
|
4751
|
+
{
|
|
4752
|
+
id: "get-user-self",
|
|
4753
|
+
route: "GET /SASjsApi/user/{uid}",
|
|
4754
|
+
description: "returns the caller their own details, with the auto-exec code",
|
|
4755
|
+
identity: ADMIN_SIGNED_IN,
|
|
4756
|
+
request: { token: RESTRICTED, params: { uid: RESTRICTED_UID } },
|
|
4757
|
+
expect: {
|
|
4758
|
+
status: 200,
|
|
4759
|
+
partial: {
|
|
4760
|
+
uid: RESTRICTED_UID,
|
|
4761
|
+
username: "user2",
|
|
4762
|
+
isActive: true,
|
|
4763
|
+
isAdmin: false,
|
|
4764
|
+
autoExec: ""
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
},
|
|
4768
|
+
{
|
|
4769
|
+
id: "get-user-unauthenticated",
|
|
4770
|
+
route: "GET /SASjsApi/user/{uid}",
|
|
4771
|
+
description: "refuses a read with no caller at all",
|
|
4772
|
+
identity: ADMIN_SIGNED_IN,
|
|
4773
|
+
request: { params: { uid: RESTRICTED_UID } },
|
|
4774
|
+
expect: { status: 401, error: "Unauthorized" }
|
|
4775
|
+
},
|
|
4776
|
+
{
|
|
4777
|
+
id: "get-user-missing",
|
|
4778
|
+
route: "GET /SASjsApi/user/{uid}",
|
|
4779
|
+
description: "answers 404 for an identifier no account has",
|
|
4780
|
+
identity: ADMIN_SIGNED_IN,
|
|
4781
|
+
request: { token: ADMIN, params: { uid: "user-none" } },
|
|
4782
|
+
expect: { status: 404, error: "User is not found." }
|
|
4783
|
+
},
|
|
4784
|
+
{
|
|
4785
|
+
id: "get-user-by-username",
|
|
4786
|
+
route: "GET /SASjsApi/user/by/username/{username}",
|
|
4787
|
+
description: "finds an account by username",
|
|
4788
|
+
identity: ADMIN_SIGNED_IN,
|
|
4789
|
+
request: { token: ADMIN, params: { username: "user2" } },
|
|
4790
|
+
expect: {
|
|
4791
|
+
status: 200,
|
|
4792
|
+
partial: { uid: RESTRICTED_UID, username: "user2", isAdmin: false }
|
|
4793
|
+
}
|
|
4794
|
+
},
|
|
4795
|
+
{
|
|
4796
|
+
id: "get-user-by-username-unauthenticated",
|
|
4797
|
+
route: "GET /SASjsApi/user/by/username/{username}",
|
|
4798
|
+
description: "refuses the by-username read with no caller",
|
|
4799
|
+
identity: ADMIN_SIGNED_IN,
|
|
4800
|
+
request: { params: { username: "user2" } },
|
|
4801
|
+
expect: { status: 401, error: "Unauthorized" }
|
|
4802
|
+
},
|
|
4803
|
+
{
|
|
4804
|
+
id: "get-user-by-username-missing",
|
|
4805
|
+
route: "GET /SASjsApi/user/by/username/{username}",
|
|
4806
|
+
description: "answers 404 for a username no account has",
|
|
4807
|
+
identity: ADMIN_SIGNED_IN,
|
|
4808
|
+
request: { token: ADMIN, params: { username: "nobody" } },
|
|
4809
|
+
expect: { status: 404, error: "User is not found." }
|
|
4810
|
+
},
|
|
4811
|
+
{
|
|
4812
|
+
id: "create-user",
|
|
4813
|
+
route: "POST /SASjsApi/user",
|
|
4814
|
+
description: "creates an account, sanitised, and reports it",
|
|
4815
|
+
identity: ADMIN_SIGNED_IN,
|
|
4816
|
+
request: {
|
|
4817
|
+
token: ADMIN,
|
|
4818
|
+
body: {
|
|
4819
|
+
username: "newuser",
|
|
4820
|
+
displayName: " New User ",
|
|
4821
|
+
password: "newpass",
|
|
4822
|
+
isAdmin: false,
|
|
4823
|
+
isActive: true
|
|
4824
|
+
}
|
|
4825
|
+
},
|
|
4826
|
+
expect: {
|
|
4827
|
+
status: 200,
|
|
4828
|
+
partial: {
|
|
4829
|
+
username: "newuser",
|
|
4830
|
+
displayName: "New User",
|
|
4831
|
+
isAdmin: false,
|
|
4832
|
+
isActive: true
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
},
|
|
4836
|
+
{
|
|
4837
|
+
id: "create-user-not-admin",
|
|
4838
|
+
route: "POST /SASjsApi/user",
|
|
4839
|
+
description: "refuses a creation by a caller who is not an administrator",
|
|
4840
|
+
identity: ADMIN_SIGNED_IN,
|
|
4841
|
+
request: {
|
|
4842
|
+
token: RESTRICTED,
|
|
4843
|
+
body: {
|
|
4844
|
+
username: "newuser",
|
|
4845
|
+
displayName: "New User",
|
|
4846
|
+
password: "newpass"
|
|
4847
|
+
}
|
|
4848
|
+
},
|
|
4849
|
+
expect: { status: 401, error: "Admin account required" }
|
|
4850
|
+
},
|
|
4851
|
+
{
|
|
4852
|
+
id: "create-user-anonymous-incomplete",
|
|
4853
|
+
route: "POST /SASjsApi/user",
|
|
4854
|
+
description: "answers 401 before the payload schema, anonymously and empty",
|
|
4855
|
+
identity: ADMIN_SIGNED_IN,
|
|
4856
|
+
request: { body: {} },
|
|
4857
|
+
expect: { status: 401, error: "Unauthorized" }
|
|
4858
|
+
},
|
|
4859
|
+
{
|
|
4860
|
+
id: "create-user-duplicate",
|
|
4861
|
+
route: "POST /SASjsApi/user",
|
|
4862
|
+
description: "refuses a username that is taken",
|
|
4863
|
+
identity: ADMIN_SIGNED_IN,
|
|
4864
|
+
request: {
|
|
4865
|
+
token: ADMIN,
|
|
4866
|
+
body: { username: "user2", displayName: "Another", password: "newpass" }
|
|
4867
|
+
},
|
|
4868
|
+
expect: { status: 409, error: "Username already exists." }
|
|
4869
|
+
},
|
|
4870
|
+
{
|
|
4871
|
+
id: "update-user",
|
|
4872
|
+
route: "PATCH /SASjsApi/user/{uid}",
|
|
4873
|
+
description: "changes an account's display name",
|
|
4874
|
+
identity: ADMIN_SIGNED_IN,
|
|
4875
|
+
request: {
|
|
4876
|
+
token: ADMIN,
|
|
4877
|
+
params: { uid: RESTRICTED_UID },
|
|
4878
|
+
body: {
|
|
4879
|
+
displayName: "Renamed User",
|
|
4880
|
+
username: "user2",
|
|
4881
|
+
password: "user123"
|
|
4882
|
+
}
|
|
4883
|
+
},
|
|
4884
|
+
expect: {
|
|
4885
|
+
status: 200,
|
|
4886
|
+
partial: { uid: RESTRICTED_UID, displayName: "Renamed User" }
|
|
4887
|
+
}
|
|
4888
|
+
},
|
|
4889
|
+
{
|
|
4890
|
+
id: "update-user-not-admin",
|
|
4891
|
+
route: "PATCH /SASjsApi/user/{uid}",
|
|
4892
|
+
description: "refuses a change to somebody else's account",
|
|
4893
|
+
identity: ADMIN_SIGNED_IN,
|
|
4894
|
+
request: {
|
|
4895
|
+
token: RESTRICTED,
|
|
4896
|
+
params: { uid: ADMIN_UID },
|
|
4897
|
+
body: { displayName: "Hijacked", username: "admin", password: "admin123" }
|
|
4898
|
+
},
|
|
4899
|
+
expect: { status: 401, error: "Admin account required" }
|
|
4900
|
+
},
|
|
4901
|
+
{
|
|
4902
|
+
id: "update-user-missing",
|
|
4903
|
+
route: "PATCH /SASjsApi/user/{uid}",
|
|
4904
|
+
description: "answers 404 with the identifier it looked up",
|
|
4905
|
+
identity: ADMIN_SIGNED_IN,
|
|
4906
|
+
request: {
|
|
4907
|
+
token: ADMIN,
|
|
4908
|
+
params: { uid: "user-none" },
|
|
4909
|
+
body: { displayName: "Nobody", username: "nobody", password: "none123" }
|
|
4910
|
+
},
|
|
4911
|
+
expect: { status: 404, error: "Unable to find user with user-none" }
|
|
4912
|
+
},
|
|
4913
|
+
{
|
|
4914
|
+
id: "update-user-provider",
|
|
4915
|
+
route: "PATCH /SASjsApi/user/{uid}",
|
|
4916
|
+
description: "refuses a username change on a provider-managed account",
|
|
4917
|
+
identity: PROVIDER_MANAGED,
|
|
4918
|
+
request: {
|
|
4919
|
+
token: ADMIN,
|
|
4920
|
+
params: { uid: "user-directory" },
|
|
4921
|
+
body: {
|
|
4922
|
+
username: "renamed",
|
|
4923
|
+
displayName: "Directory User",
|
|
4924
|
+
password: "dir123"
|
|
4925
|
+
}
|
|
4926
|
+
},
|
|
4927
|
+
expect: {
|
|
4928
|
+
status: 405,
|
|
4929
|
+
error: "Can not update username of user that is created by an external auth provider."
|
|
4930
|
+
}
|
|
4931
|
+
},
|
|
4932
|
+
{
|
|
4933
|
+
id: "update-user-username-taken",
|
|
4934
|
+
route: "PATCH /SASjsApi/user/{uid}",
|
|
4935
|
+
description: "refuses a username another account already has",
|
|
4936
|
+
identity: ADMIN_SIGNED_IN,
|
|
4937
|
+
request: {
|
|
4938
|
+
token: ADMIN,
|
|
4939
|
+
params: { uid: RESTRICTED_UID },
|
|
4940
|
+
body: { username: "admin", displayName: "Demo User", password: "user123" }
|
|
4941
|
+
},
|
|
4942
|
+
expect: { status: 409, error: "Username already exists." }
|
|
4943
|
+
},
|
|
4944
|
+
{
|
|
4945
|
+
id: "update-user-by-username",
|
|
4946
|
+
route: "PATCH /SASjsApi/user/by/username/{username}",
|
|
4947
|
+
description: "changes an account looked up by username",
|
|
4948
|
+
identity: ADMIN_SIGNED_IN,
|
|
4949
|
+
request: {
|
|
4950
|
+
token: ADMIN,
|
|
4951
|
+
params: { username: "user2" },
|
|
4952
|
+
body: {
|
|
4953
|
+
isActive: false,
|
|
4954
|
+
username: "user2",
|
|
4955
|
+
displayName: "Demo User",
|
|
4956
|
+
password: "user123"
|
|
4957
|
+
}
|
|
4958
|
+
},
|
|
4959
|
+
expect: { status: 200, partial: { username: "user2", isActive: false } }
|
|
4960
|
+
},
|
|
4961
|
+
{
|
|
4962
|
+
id: "update-user-by-username-not-admin",
|
|
4963
|
+
route: "PATCH /SASjsApi/user/by/username/{username}",
|
|
4964
|
+
description: "refuses a change to another account by username",
|
|
4965
|
+
identity: ADMIN_SIGNED_IN,
|
|
4966
|
+
request: {
|
|
4967
|
+
token: RESTRICTED,
|
|
4968
|
+
params: { username: "admin" },
|
|
4969
|
+
body: { displayName: "Hijacked", username: "admin", password: "admin123" }
|
|
4970
|
+
},
|
|
4971
|
+
expect: { status: 401, error: "Admin account required" }
|
|
4972
|
+
},
|
|
4973
|
+
{
|
|
4974
|
+
id: "update-user-by-username-missing",
|
|
4975
|
+
route: "PATCH /SASjsApi/user/by/username/{username}",
|
|
4976
|
+
description: "answers 404 with the username it looked up",
|
|
4977
|
+
identity: ADMIN_SIGNED_IN,
|
|
4978
|
+
request: {
|
|
4979
|
+
token: ADMIN,
|
|
4980
|
+
params: { username: "nobody" },
|
|
4981
|
+
body: { displayName: "Nobody", username: "nobody", password: "none123" }
|
|
4982
|
+
},
|
|
4983
|
+
expect: { status: 404, error: "Unable to find user with nobody" }
|
|
4984
|
+
},
|
|
4985
|
+
{
|
|
4986
|
+
id: "update-user-by-username-provider",
|
|
4987
|
+
route: "PATCH /SASjsApi/user/by/username/{username}",
|
|
4988
|
+
description: "refuses a display-name change on a provider-managed account",
|
|
4989
|
+
identity: PROVIDER_MANAGED,
|
|
4990
|
+
request: {
|
|
4991
|
+
token: ADMIN,
|
|
4992
|
+
params: { username: "diruser" },
|
|
4993
|
+
body: { displayName: "Renamed", username: "diruser", password: "dir123" }
|
|
4994
|
+
},
|
|
4995
|
+
expect: {
|
|
4996
|
+
status: 405,
|
|
4997
|
+
error: "Can not update display name of user that is created by an external auth provider."
|
|
4998
|
+
}
|
|
4999
|
+
},
|
|
5000
|
+
{
|
|
5001
|
+
id: "update-user-by-username-taken",
|
|
5002
|
+
route: "PATCH /SASjsApi/user/by/username/{username}",
|
|
5003
|
+
description: "refuses a username another account already has",
|
|
5004
|
+
identity: ADMIN_SIGNED_IN,
|
|
5005
|
+
request: {
|
|
5006
|
+
token: ADMIN,
|
|
5007
|
+
params: { username: "user2" },
|
|
5008
|
+
body: { username: "admin", displayName: "Demo User", password: "user123" }
|
|
5009
|
+
},
|
|
5010
|
+
expect: { status: 409, error: "Username already exists." }
|
|
5011
|
+
},
|
|
5012
|
+
{
|
|
5013
|
+
id: "delete-user",
|
|
5014
|
+
route: "DELETE /SASjsApi/user/{uid}",
|
|
5015
|
+
description: "deletes an account as an administrator, without a password",
|
|
5016
|
+
identity: ADMIN_SIGNED_IN,
|
|
5017
|
+
request: { token: ADMIN, params: { uid: RESTRICTED_UID }, body: {} },
|
|
5018
|
+
expect: { status: 204 }
|
|
5019
|
+
},
|
|
5020
|
+
{
|
|
5021
|
+
id: "delete-user-not-admin",
|
|
5022
|
+
route: "DELETE /SASjsApi/user/{uid}",
|
|
5023
|
+
description: "refuses a deletion of somebody else's account",
|
|
5024
|
+
identity: ADMIN_SIGNED_IN,
|
|
5025
|
+
request: { token: RESTRICTED, params: { uid: ADMIN_UID }, body: {} },
|
|
5026
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5027
|
+
},
|
|
5028
|
+
{
|
|
5029
|
+
id: "delete-user-missing",
|
|
5030
|
+
route: "DELETE /SASjsApi/user/{uid}",
|
|
5031
|
+
description: "answers 404 for an identifier no account has",
|
|
5032
|
+
identity: ADMIN_SIGNED_IN,
|
|
5033
|
+
request: { token: ADMIN, params: { uid: "user-none" }, body: {} },
|
|
5034
|
+
expect: { status: 404, error: "User is not found." }
|
|
5035
|
+
},
|
|
5036
|
+
{
|
|
5037
|
+
id: "delete-user-by-username",
|
|
5038
|
+
route: "DELETE /SASjsApi/user/by/username/{username}",
|
|
5039
|
+
description: "deletes an account looked up by username",
|
|
5040
|
+
identity: ADMIN_SIGNED_IN,
|
|
5041
|
+
request: {
|
|
5042
|
+
token: ADMIN,
|
|
5043
|
+
params: { username: "user2" },
|
|
5044
|
+
body: {}
|
|
5045
|
+
},
|
|
5046
|
+
expect: { status: 204 }
|
|
5047
|
+
},
|
|
5048
|
+
{
|
|
5049
|
+
id: "delete-user-by-username-wrong-password",
|
|
5050
|
+
route: "DELETE /SASjsApi/user/by/username/{username}",
|
|
5051
|
+
description: "refuses a self-deletion whose password is wrong",
|
|
5052
|
+
identity: ADMIN_SIGNED_IN,
|
|
5053
|
+
request: {
|
|
5054
|
+
token: RESTRICTED,
|
|
5055
|
+
params: { username: "user2" },
|
|
5056
|
+
body: { password: "wrong" }
|
|
5057
|
+
},
|
|
5058
|
+
expect: { status: 401, error: "Invalid password." }
|
|
5059
|
+
},
|
|
5060
|
+
{
|
|
5061
|
+
id: "delete-user-by-username-missing",
|
|
5062
|
+
route: "DELETE /SASjsApi/user/by/username/{username}",
|
|
5063
|
+
description: "answers 404 for a username no account has",
|
|
5064
|
+
identity: ADMIN_SIGNED_IN,
|
|
5065
|
+
request: { token: ADMIN, params: { username: "nobody" }, body: {} },
|
|
5066
|
+
expect: { status: 404, error: "User is not found." }
|
|
5067
|
+
},
|
|
5068
|
+
// ----------------------------------------------------------------- groups
|
|
5069
|
+
{
|
|
5070
|
+
id: "list-groups",
|
|
5071
|
+
route: "GET /SASjsApi/group",
|
|
5072
|
+
description: "lists the groups",
|
|
5073
|
+
identity: ADMIN_SIGNED_IN,
|
|
5074
|
+
request: { token: RESTRICTED },
|
|
5075
|
+
expect: {
|
|
5076
|
+
status: 200,
|
|
5077
|
+
body: [
|
|
5078
|
+
{
|
|
5079
|
+
uid: "group-data-controller",
|
|
5080
|
+
name: "DCGroup",
|
|
5081
|
+
description: "Data Controller users"
|
|
5082
|
+
},
|
|
5083
|
+
{
|
|
5084
|
+
uid: "group-admins",
|
|
5085
|
+
name: "Admins",
|
|
5086
|
+
description: "Everything the API offers"
|
|
5087
|
+
}
|
|
5088
|
+
]
|
|
5089
|
+
}
|
|
5090
|
+
},
|
|
5091
|
+
{
|
|
5092
|
+
id: "get-group",
|
|
5093
|
+
route: "GET /SASjsApi/group/{uid}",
|
|
5094
|
+
description: "returns a group and its members",
|
|
5095
|
+
identity: ADMIN_SIGNED_IN,
|
|
5096
|
+
request: { token: RESTRICTED, params: { uid: "group-admins" } },
|
|
5097
|
+
expect: {
|
|
5098
|
+
status: 200,
|
|
5099
|
+
partial: {
|
|
5100
|
+
uid: "group-admins",
|
|
5101
|
+
name: "Admins",
|
|
5102
|
+
isActive: true,
|
|
5103
|
+
users: [
|
|
5104
|
+
{
|
|
5105
|
+
uid: ADMIN_UID,
|
|
5106
|
+
username: "admin",
|
|
5107
|
+
displayName: "Demo Admin",
|
|
5108
|
+
isAdmin: true
|
|
5109
|
+
}
|
|
5110
|
+
]
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
id: "get-group-missing",
|
|
5116
|
+
route: "GET /SASjsApi/group/{uid}",
|
|
5117
|
+
description: "answers 404 for an identifier no group has",
|
|
5118
|
+
identity: ADMIN_SIGNED_IN,
|
|
5119
|
+
request: { token: ADMIN, params: { uid: "group-none" } },
|
|
5120
|
+
expect: { status: 404, error: "Group not found." }
|
|
5121
|
+
},
|
|
5122
|
+
{
|
|
5123
|
+
id: "get-group-by-name",
|
|
5124
|
+
route: "GET /SASjsApi/group/by/groupname/{name}",
|
|
5125
|
+
description: "finds a group by name",
|
|
5126
|
+
identity: ADMIN_SIGNED_IN,
|
|
5127
|
+
request: { token: ADMIN, params: { name: "DCGroup" } },
|
|
5128
|
+
expect: {
|
|
5129
|
+
status: 200,
|
|
5130
|
+
partial: { uid: "group-data-controller", name: "DCGroup" }
|
|
5131
|
+
}
|
|
5132
|
+
},
|
|
5133
|
+
{
|
|
5134
|
+
id: "get-group-by-name-missing",
|
|
5135
|
+
route: "GET /SASjsApi/group/by/groupname/{name}",
|
|
5136
|
+
description: "answers 404 for a name no group has",
|
|
5137
|
+
identity: ADMIN_SIGNED_IN,
|
|
5138
|
+
request: { token: ADMIN, params: { name: "nope" } },
|
|
5139
|
+
expect: { status: 404, error: "Group not found." }
|
|
5140
|
+
},
|
|
5141
|
+
{
|
|
5142
|
+
id: "create-group",
|
|
5143
|
+
route: "POST /SASjsApi/group",
|
|
5144
|
+
description: "creates a group with no members",
|
|
5145
|
+
identity: ADMIN_SIGNED_IN,
|
|
5146
|
+
request: {
|
|
5147
|
+
token: ADMIN,
|
|
5148
|
+
body: { name: "NewGroup", description: "A new group", isActive: true }
|
|
5149
|
+
},
|
|
5150
|
+
expect: {
|
|
5151
|
+
status: 200,
|
|
5152
|
+
partial: {
|
|
5153
|
+
name: "NewGroup",
|
|
5154
|
+
description: "A new group",
|
|
5155
|
+
isActive: true,
|
|
5156
|
+
users: []
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
},
|
|
5160
|
+
{
|
|
5161
|
+
id: "create-group-not-admin",
|
|
5162
|
+
route: "POST /SASjsApi/group",
|
|
5163
|
+
description: "refuses a creation by a caller who is not an administrator",
|
|
5164
|
+
identity: ADMIN_SIGNED_IN,
|
|
5165
|
+
request: {
|
|
5166
|
+
token: RESTRICTED,
|
|
5167
|
+
body: { name: "NewGroup", description: "x" }
|
|
5168
|
+
},
|
|
5169
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5170
|
+
},
|
|
5171
|
+
{
|
|
5172
|
+
id: "create-group-duplicate",
|
|
5173
|
+
route: "POST /SASjsApi/group",
|
|
5174
|
+
description: "refuses a name that is taken",
|
|
5175
|
+
identity: ADMIN_SIGNED_IN,
|
|
5176
|
+
request: { token: ADMIN, body: { name: "DCGroup", description: "again" } },
|
|
5177
|
+
expect: { status: 409, error: "Group name already exists." }
|
|
5178
|
+
},
|
|
5179
|
+
{
|
|
5180
|
+
id: "add-user-to-group",
|
|
5181
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5182
|
+
description: "adds a member and returns the group",
|
|
5183
|
+
identity: ADMIN_SIGNED_IN,
|
|
5184
|
+
request: {
|
|
5185
|
+
token: ADMIN,
|
|
5186
|
+
params: { groupUid: "group-admins", userUid: RESTRICTED_UID }
|
|
5187
|
+
},
|
|
5188
|
+
expect: {
|
|
5189
|
+
status: 200,
|
|
5190
|
+
partial: { uid: "group-admins", name: "Admins" }
|
|
5191
|
+
}
|
|
5192
|
+
},
|
|
5193
|
+
{
|
|
5194
|
+
id: "add-user-to-public-group",
|
|
5195
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5196
|
+
description: "refuses a change to the reserved public group",
|
|
5197
|
+
identity: PROVIDER_MANAGED,
|
|
5198
|
+
request: {
|
|
5199
|
+
token: ADMIN,
|
|
5200
|
+
params: { groupUid: "group-public", userUid: RESTRICTED_UID }
|
|
5201
|
+
},
|
|
5202
|
+
expect: { status: 400, error: "Can't add/remove user to 'public' group." }
|
|
5203
|
+
},
|
|
5204
|
+
{
|
|
5205
|
+
id: "add-user-to-group-not-admin",
|
|
5206
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5207
|
+
description: "refuses a membership change by a non-administrator",
|
|
5208
|
+
identity: ADMIN_SIGNED_IN,
|
|
5209
|
+
request: {
|
|
5210
|
+
token: RESTRICTED,
|
|
5211
|
+
params: { groupUid: "group-admins", userUid: RESTRICTED_UID }
|
|
5212
|
+
},
|
|
5213
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5214
|
+
},
|
|
5215
|
+
{
|
|
5216
|
+
id: "add-user-to-group-missing-group",
|
|
5217
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5218
|
+
description: "answers 404 for a group that does not exist",
|
|
5219
|
+
identity: ADMIN_SIGNED_IN,
|
|
5220
|
+
request: {
|
|
5221
|
+
token: ADMIN,
|
|
5222
|
+
params: { groupUid: "group-none", userUid: RESTRICTED_UID }
|
|
5223
|
+
},
|
|
5224
|
+
expect: { status: 404, error: "Group not found." }
|
|
5225
|
+
},
|
|
5226
|
+
{
|
|
5227
|
+
id: "add-user-to-group-provider",
|
|
5228
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5229
|
+
description: "refuses a membership change on a provider-managed group",
|
|
5230
|
+
identity: PROVIDER_MANAGED,
|
|
5231
|
+
request: {
|
|
5232
|
+
token: ADMIN,
|
|
5233
|
+
params: { groupUid: "group-directory", userUid: RESTRICTED_UID }
|
|
5234
|
+
},
|
|
5235
|
+
expect: {
|
|
5236
|
+
status: 405,
|
|
5237
|
+
error: "Can't add/remove user to group created by external auth provider."
|
|
5238
|
+
}
|
|
5239
|
+
},
|
|
5240
|
+
{
|
|
5241
|
+
id: "remove-user-from-group",
|
|
5242
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5243
|
+
description: "removes a member and returns the group",
|
|
5244
|
+
identity: ADMIN_SIGNED_IN,
|
|
5245
|
+
request: {
|
|
5246
|
+
token: ADMIN,
|
|
5247
|
+
params: { groupUid: "group-data-controller", userUid: RESTRICTED_UID }
|
|
5248
|
+
},
|
|
5249
|
+
expect: {
|
|
5250
|
+
status: 200,
|
|
5251
|
+
partial: { uid: "group-data-controller", name: "DCGroup", users: [] }
|
|
5252
|
+
}
|
|
5253
|
+
},
|
|
5254
|
+
{
|
|
5255
|
+
id: "add-provider-user-to-group",
|
|
5256
|
+
route: "POST /SASjsApi/group/{groupUid}/{userUid}",
|
|
5257
|
+
description: "refuses a membership change for a provider-managed user",
|
|
5258
|
+
identity: PROVIDER_MANAGED,
|
|
5259
|
+
request: {
|
|
5260
|
+
token: ADMIN,
|
|
5261
|
+
params: { groupUid: "group-admins", userUid: "user-directory" }
|
|
5262
|
+
},
|
|
5263
|
+
expect: {
|
|
5264
|
+
status: 405,
|
|
5265
|
+
error: "Can't add/remove user to group created by external auth provider."
|
|
5266
|
+
}
|
|
5267
|
+
},
|
|
5268
|
+
{
|
|
5269
|
+
id: "remove-provider-user-from-group",
|
|
5270
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5271
|
+
description: "refuses a membership change for a provider-managed user",
|
|
5272
|
+
identity: PROVIDER_MANAGED,
|
|
5273
|
+
request: {
|
|
5274
|
+
token: ADMIN,
|
|
5275
|
+
params: { groupUid: "group-admins", userUid: "user-directory" }
|
|
5276
|
+
},
|
|
5277
|
+
expect: {
|
|
5278
|
+
status: 405,
|
|
5279
|
+
error: "Can't add/remove user to group created by external auth provider."
|
|
5280
|
+
}
|
|
5281
|
+
},
|
|
5282
|
+
{
|
|
5283
|
+
id: "remove-user-from-public-group",
|
|
5284
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5285
|
+
description: "refuses a change to the reserved public group",
|
|
5286
|
+
identity: PROVIDER_MANAGED,
|
|
5287
|
+
request: {
|
|
5288
|
+
token: ADMIN,
|
|
5289
|
+
params: { groupUid: "group-public", userUid: RESTRICTED_UID }
|
|
5290
|
+
},
|
|
5291
|
+
expect: { status: 400, error: "Can't add/remove user to 'public' group." }
|
|
5292
|
+
},
|
|
5293
|
+
{
|
|
5294
|
+
id: "remove-user-from-group-not-admin",
|
|
5295
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5296
|
+
description: "refuses a membership change by a non-administrator",
|
|
5297
|
+
identity: ADMIN_SIGNED_IN,
|
|
5298
|
+
request: {
|
|
5299
|
+
token: RESTRICTED,
|
|
5300
|
+
params: { groupUid: "group-data-controller", userUid: RESTRICTED_UID }
|
|
5301
|
+
},
|
|
5302
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5303
|
+
},
|
|
5304
|
+
{
|
|
5305
|
+
id: "remove-user-from-group-missing-user",
|
|
5306
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5307
|
+
description: "answers 404 for a member that does not exist",
|
|
5308
|
+
identity: ADMIN_SIGNED_IN,
|
|
5309
|
+
request: {
|
|
5310
|
+
token: ADMIN,
|
|
5311
|
+
params: { groupUid: "group-admins", userUid: "user-none" }
|
|
5312
|
+
},
|
|
5313
|
+
expect: { status: 404, error: "User not found." }
|
|
5314
|
+
},
|
|
5315
|
+
{
|
|
5316
|
+
id: "remove-user-from-group-provider",
|
|
5317
|
+
route: "DELETE /SASjsApi/group/{groupUid}/{userUid}",
|
|
5318
|
+
description: "refuses a membership change on a provider-managed group",
|
|
5319
|
+
identity: PROVIDER_MANAGED,
|
|
5320
|
+
request: {
|
|
5321
|
+
token: ADMIN,
|
|
5322
|
+
params: { groupUid: "group-directory", userUid: RESTRICTED_UID }
|
|
5323
|
+
},
|
|
5324
|
+
expect: {
|
|
5325
|
+
status: 405,
|
|
5326
|
+
error: "Can't add/remove user to group created by external auth provider."
|
|
5327
|
+
}
|
|
5328
|
+
},
|
|
5329
|
+
{
|
|
5330
|
+
id: "delete-group",
|
|
5331
|
+
route: "DELETE /SASjsApi/group/{uid}",
|
|
5332
|
+
description: "deletes a group",
|
|
5333
|
+
identity: ADMIN_SIGNED_IN,
|
|
5334
|
+
request: { token: ADMIN, params: { uid: "group-data-controller" } },
|
|
5335
|
+
expect: { status: 200 }
|
|
5336
|
+
},
|
|
5337
|
+
{
|
|
5338
|
+
id: "delete-group-not-admin",
|
|
5339
|
+
route: "DELETE /SASjsApi/group/{uid}",
|
|
5340
|
+
description: "refuses a deletion by a caller who is not an administrator",
|
|
5341
|
+
identity: ADMIN_SIGNED_IN,
|
|
5342
|
+
request: { token: RESTRICTED, params: { uid: "group-admins" } },
|
|
5343
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5344
|
+
},
|
|
5345
|
+
{
|
|
5346
|
+
id: "delete-group-missing",
|
|
5347
|
+
route: "DELETE /SASjsApi/group/{uid}",
|
|
5348
|
+
description: "answers 404 for an identifier no group has",
|
|
5349
|
+
identity: ADMIN_SIGNED_IN,
|
|
5350
|
+
request: { token: ADMIN, params: { uid: "group-none" } },
|
|
5351
|
+
expect: { status: 404, error: "Group not found." }
|
|
5352
|
+
},
|
|
5353
|
+
// ------------------------------------------------------------ permissions
|
|
5354
|
+
{
|
|
5355
|
+
id: "list-permissions-as-admin",
|
|
5356
|
+
route: "GET /SASjsApi/permission",
|
|
5357
|
+
description: "returns every rule to an administrator",
|
|
5358
|
+
identity: ADMIN_SIGNED_IN,
|
|
5359
|
+
request: { token: ADMIN },
|
|
5360
|
+
expect: {
|
|
5361
|
+
status: 200,
|
|
5362
|
+
body: [
|
|
5363
|
+
{
|
|
5364
|
+
uid: "rule-routes",
|
|
5365
|
+
path: "/SASjsApi/info",
|
|
5366
|
+
type: "Route",
|
|
5367
|
+
setting: "Grant",
|
|
5368
|
+
group: {
|
|
5369
|
+
uid: "group-data-controller",
|
|
5370
|
+
name: "DCGroup",
|
|
5371
|
+
description: "Data Controller users",
|
|
5372
|
+
isActive: true,
|
|
5373
|
+
users: [
|
|
5374
|
+
{
|
|
5375
|
+
uid: RESTRICTED_UID,
|
|
5376
|
+
username: "user2",
|
|
5377
|
+
displayName: "Demo User",
|
|
5378
|
+
isAdmin: false
|
|
5379
|
+
}
|
|
5380
|
+
]
|
|
5381
|
+
}
|
|
5382
|
+
},
|
|
5383
|
+
{
|
|
5384
|
+
uid: "rule-execute",
|
|
5385
|
+
path: "/SASjsApi/stp/execute",
|
|
5386
|
+
type: "Route",
|
|
5387
|
+
setting: "Grant",
|
|
5388
|
+
group: {
|
|
5389
|
+
uid: "group-data-controller",
|
|
5390
|
+
name: "DCGroup",
|
|
5391
|
+
description: "Data Controller users",
|
|
5392
|
+
isActive: true,
|
|
5393
|
+
users: [
|
|
5394
|
+
{
|
|
5395
|
+
uid: RESTRICTED_UID,
|
|
5396
|
+
username: "user2",
|
|
5397
|
+
displayName: "Demo User",
|
|
5398
|
+
isAdmin: false
|
|
5399
|
+
}
|
|
5400
|
+
]
|
|
5401
|
+
}
|
|
5402
|
+
},
|
|
5403
|
+
{
|
|
5404
|
+
uid: "rule-code",
|
|
5405
|
+
path: "/SASjsApi/code/execute",
|
|
5406
|
+
type: "Route",
|
|
5407
|
+
setting: "Deny",
|
|
5408
|
+
user: {
|
|
5409
|
+
uid: RESTRICTED_UID,
|
|
5410
|
+
username: "user2",
|
|
5411
|
+
displayName: "Demo User",
|
|
5412
|
+
isAdmin: false
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
]
|
|
5416
|
+
}
|
|
5417
|
+
},
|
|
5418
|
+
{
|
|
5419
|
+
id: "list-permissions-as-user",
|
|
5420
|
+
route: "GET /SASjsApi/permission",
|
|
5421
|
+
description: "returns a caller only their own rules and their groups'",
|
|
5422
|
+
identity: ADMIN_SIGNED_IN,
|
|
5423
|
+
request: { token: RESTRICTED },
|
|
5424
|
+
expect: {
|
|
5425
|
+
status: 200,
|
|
5426
|
+
body: [
|
|
5427
|
+
{
|
|
5428
|
+
uid: "rule-routes",
|
|
5429
|
+
path: "/SASjsApi/info",
|
|
5430
|
+
type: "Route",
|
|
5431
|
+
setting: "Grant",
|
|
5432
|
+
group: {
|
|
5433
|
+
uid: "group-data-controller",
|
|
5434
|
+
name: "DCGroup",
|
|
5435
|
+
description: "Data Controller users",
|
|
5436
|
+
isActive: true,
|
|
5437
|
+
users: [
|
|
5438
|
+
{
|
|
5439
|
+
uid: RESTRICTED_UID,
|
|
5440
|
+
username: "user2",
|
|
5441
|
+
displayName: "Demo User",
|
|
5442
|
+
isAdmin: false
|
|
5443
|
+
}
|
|
5444
|
+
]
|
|
5445
|
+
}
|
|
5446
|
+
},
|
|
5447
|
+
{
|
|
5448
|
+
uid: "rule-execute",
|
|
5449
|
+
path: "/SASjsApi/stp/execute",
|
|
5450
|
+
type: "Route",
|
|
5451
|
+
setting: "Grant",
|
|
5452
|
+
group: {
|
|
5453
|
+
uid: "group-data-controller",
|
|
5454
|
+
name: "DCGroup",
|
|
5455
|
+
description: "Data Controller users",
|
|
5456
|
+
isActive: true,
|
|
5457
|
+
users: [
|
|
5458
|
+
{
|
|
5459
|
+
uid: RESTRICTED_UID,
|
|
5460
|
+
username: "user2",
|
|
5461
|
+
displayName: "Demo User",
|
|
5462
|
+
isAdmin: false
|
|
5463
|
+
}
|
|
5464
|
+
]
|
|
5465
|
+
}
|
|
5466
|
+
},
|
|
5467
|
+
{
|
|
5468
|
+
uid: "rule-code",
|
|
5469
|
+
path: "/SASjsApi/code/execute",
|
|
5470
|
+
type: "Route",
|
|
5471
|
+
setting: "Deny",
|
|
5472
|
+
user: {
|
|
5473
|
+
uid: RESTRICTED_UID,
|
|
5474
|
+
username: "user2",
|
|
5475
|
+
displayName: "Demo User",
|
|
5476
|
+
isAdmin: false
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
]
|
|
5480
|
+
}
|
|
5481
|
+
},
|
|
5482
|
+
{
|
|
5483
|
+
id: "list-permissions-unauthenticated",
|
|
5484
|
+
route: "GET /SASjsApi/permission",
|
|
5485
|
+
description: "refuses the list with no caller",
|
|
5486
|
+
identity: ADMIN_SIGNED_IN,
|
|
5487
|
+
request: {},
|
|
5488
|
+
expect: { status: 401, error: "Unauthorized" }
|
|
5489
|
+
},
|
|
5490
|
+
{
|
|
5491
|
+
id: "create-permission-user",
|
|
5492
|
+
route: "POST /SASjsApi/permission",
|
|
5493
|
+
description: "creates a rule for a user",
|
|
5494
|
+
identity: ADMIN_SIGNED_IN,
|
|
5495
|
+
request: {
|
|
5496
|
+
token: ADMIN,
|
|
5497
|
+
body: {
|
|
5498
|
+
path: "/SASjsApi/code/lint",
|
|
5499
|
+
type: "Route",
|
|
5500
|
+
setting: "Grant",
|
|
5501
|
+
principalType: "user",
|
|
5502
|
+
principalId: RESTRICTED_UID
|
|
5503
|
+
}
|
|
5504
|
+
},
|
|
5505
|
+
expect: {
|
|
5506
|
+
status: 200,
|
|
5507
|
+
partial: { path: "/SASjsApi/code/lint", setting: "Grant" }
|
|
5508
|
+
}
|
|
5509
|
+
},
|
|
5510
|
+
{
|
|
5511
|
+
id: "create-permission-group",
|
|
5512
|
+
route: "POST /SASjsApi/permission",
|
|
5513
|
+
description: "creates a rule for a group",
|
|
5514
|
+
identity: ADMIN_SIGNED_IN,
|
|
5515
|
+
request: {
|
|
5516
|
+
token: ADMIN,
|
|
5517
|
+
body: {
|
|
5518
|
+
path: "/SASjsApi/code/lint",
|
|
5519
|
+
type: "Route",
|
|
5520
|
+
setting: "Grant",
|
|
5521
|
+
principalType: "group",
|
|
5522
|
+
principalId: "group-data-controller"
|
|
5523
|
+
}
|
|
5524
|
+
},
|
|
5525
|
+
expect: {
|
|
5526
|
+
status: 200,
|
|
5527
|
+
partial: {
|
|
5528
|
+
path: "/SASjsApi/code/lint",
|
|
5529
|
+
setting: "Grant",
|
|
5530
|
+
group: { uid: "group-data-controller", name: "DCGroup" }
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
},
|
|
5534
|
+
{
|
|
5535
|
+
id: "create-permission-invalid-principal-type",
|
|
5536
|
+
route: "POST /SASjsApi/permission",
|
|
5537
|
+
description: "refuses a principal type that is neither a user nor a group",
|
|
5538
|
+
identity: ADMIN_SIGNED_IN,
|
|
5539
|
+
request: {
|
|
5540
|
+
token: ADMIN,
|
|
5541
|
+
body: {
|
|
5542
|
+
path: "/SASjsApi/code/lint",
|
|
5543
|
+
type: "Route",
|
|
5544
|
+
setting: "Grant",
|
|
5545
|
+
principalType: "role",
|
|
5546
|
+
principalId: "anything"
|
|
5547
|
+
}
|
|
5548
|
+
},
|
|
5549
|
+
expect: {
|
|
5550
|
+
status: 400,
|
|
5551
|
+
error: "Invalid principal type. Valid types are user or group."
|
|
5552
|
+
}
|
|
5553
|
+
},
|
|
5554
|
+
{
|
|
5555
|
+
id: "create-permission-admin-principal",
|
|
5556
|
+
route: "POST /SASjsApi/permission",
|
|
5557
|
+
description: "refuses a rule assigned to an administrator",
|
|
5558
|
+
identity: ADMIN_SIGNED_IN,
|
|
5559
|
+
request: {
|
|
5560
|
+
token: ADMIN,
|
|
5561
|
+
body: {
|
|
5562
|
+
path: "/SASjsApi/code/lint",
|
|
5563
|
+
type: "Route",
|
|
5564
|
+
setting: "Grant",
|
|
5565
|
+
principalType: "user",
|
|
5566
|
+
principalId: ADMIN_UID
|
|
5567
|
+
}
|
|
5568
|
+
},
|
|
5569
|
+
expect: {
|
|
5570
|
+
status: 400,
|
|
5571
|
+
error: "Can not add permission for admin user."
|
|
5572
|
+
}
|
|
5573
|
+
},
|
|
5574
|
+
{
|
|
5575
|
+
id: "create-permission-not-admin",
|
|
5576
|
+
route: "POST /SASjsApi/permission",
|
|
5577
|
+
description: "refuses a creation by a non-administrator",
|
|
5578
|
+
identity: ADMIN_SIGNED_IN,
|
|
5579
|
+
request: {
|
|
5580
|
+
token: RESTRICTED,
|
|
5581
|
+
body: {
|
|
5582
|
+
path: "/SASjsApi/code/lint",
|
|
5583
|
+
type: "Route",
|
|
5584
|
+
setting: "Grant",
|
|
5585
|
+
principalType: "user",
|
|
5586
|
+
principalId: RESTRICTED_UID
|
|
5587
|
+
}
|
|
5588
|
+
},
|
|
5589
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5590
|
+
},
|
|
5591
|
+
{
|
|
5592
|
+
id: "create-permission-missing-principal",
|
|
5593
|
+
route: "POST /SASjsApi/permission",
|
|
5594
|
+
description: "answers 404 for a principal that does not exist",
|
|
5595
|
+
identity: ADMIN_SIGNED_IN,
|
|
5596
|
+
request: {
|
|
5597
|
+
token: ADMIN,
|
|
5598
|
+
body: {
|
|
5599
|
+
path: "/SASjsApi/code/lint",
|
|
5600
|
+
type: "Route",
|
|
5601
|
+
setting: "Grant",
|
|
5602
|
+
principalType: "group",
|
|
5603
|
+
principalId: "group-none"
|
|
5604
|
+
}
|
|
5605
|
+
},
|
|
5606
|
+
expect: { status: 404, error: "Group not found." }
|
|
5607
|
+
},
|
|
5608
|
+
{
|
|
5609
|
+
id: "create-permission-duplicate-group",
|
|
5610
|
+
route: "POST /SASjsApi/permission",
|
|
5611
|
+
description: "refuses a group rule that already exists",
|
|
5612
|
+
identity: ADMIN_SIGNED_IN,
|
|
5613
|
+
request: {
|
|
5614
|
+
token: ADMIN,
|
|
5615
|
+
body: {
|
|
5616
|
+
path: "/SASjsApi/info",
|
|
5617
|
+
type: "Route",
|
|
5618
|
+
setting: "Grant",
|
|
5619
|
+
principalType: "group",
|
|
5620
|
+
principalId: "group-data-controller"
|
|
5621
|
+
}
|
|
5622
|
+
},
|
|
5623
|
+
expect: {
|
|
5624
|
+
status: 409,
|
|
5625
|
+
error: "Permission already exists with provided Path, Type and Group."
|
|
5626
|
+
}
|
|
5627
|
+
},
|
|
5628
|
+
{
|
|
5629
|
+
id: "create-permission-duplicate",
|
|
5630
|
+
route: "POST /SASjsApi/permission",
|
|
5631
|
+
description: "refuses a rule that already exists",
|
|
5632
|
+
identity: ADMIN_SIGNED_IN,
|
|
5633
|
+
request: {
|
|
5634
|
+
token: ADMIN,
|
|
5635
|
+
body: {
|
|
5636
|
+
path: "/SASjsApi/code/execute",
|
|
5637
|
+
type: "Route",
|
|
5638
|
+
setting: "Deny",
|
|
5639
|
+
principalType: "user",
|
|
5640
|
+
principalId: RESTRICTED_UID
|
|
5641
|
+
}
|
|
5642
|
+
},
|
|
5643
|
+
expect: {
|
|
5644
|
+
status: 409,
|
|
5645
|
+
error: "Permission already exists with provided Path, Type and User."
|
|
5646
|
+
}
|
|
5647
|
+
},
|
|
5648
|
+
{
|
|
5649
|
+
id: "update-permission",
|
|
5650
|
+
route: "PATCH /SASjsApi/permission/{uid}",
|
|
5651
|
+
description: "flips a rule from Deny to Grant",
|
|
5652
|
+
identity: ADMIN_SIGNED_IN,
|
|
5653
|
+
request: {
|
|
5654
|
+
token: ADMIN,
|
|
5655
|
+
params: { uid: "rule-code" },
|
|
5656
|
+
body: { setting: "Grant" }
|
|
5657
|
+
},
|
|
5658
|
+
expect: { status: 200, partial: { uid: "rule-code", setting: "Grant" } }
|
|
5659
|
+
},
|
|
5660
|
+
{
|
|
5661
|
+
id: "update-permission-not-admin",
|
|
5662
|
+
route: "PATCH /SASjsApi/permission/{uid}",
|
|
5663
|
+
description: "refuses a change by a non-administrator",
|
|
5664
|
+
identity: ADMIN_SIGNED_IN,
|
|
5665
|
+
request: {
|
|
5666
|
+
token: RESTRICTED,
|
|
5667
|
+
params: { uid: "rule-code" },
|
|
5668
|
+
body: { setting: "Grant" }
|
|
5669
|
+
},
|
|
5670
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5671
|
+
},
|
|
5672
|
+
{
|
|
5673
|
+
id: "update-permission-missing",
|
|
5674
|
+
route: "PATCH /SASjsApi/permission/{uid}",
|
|
5675
|
+
description: "answers 404 for a rule that does not exist",
|
|
5676
|
+
identity: ADMIN_SIGNED_IN,
|
|
5677
|
+
request: {
|
|
5678
|
+
token: ADMIN,
|
|
5679
|
+
params: { uid: "rule-none" },
|
|
5680
|
+
body: { setting: "Grant" }
|
|
5681
|
+
},
|
|
5682
|
+
expect: { status: 404, error: "Permission not found." }
|
|
5683
|
+
},
|
|
5684
|
+
{
|
|
5685
|
+
id: "delete-permission",
|
|
5686
|
+
route: "DELETE /SASjsApi/permission/{uid}",
|
|
5687
|
+
description: "deletes a rule",
|
|
5688
|
+
identity: ADMIN_SIGNED_IN,
|
|
5689
|
+
request: { token: ADMIN, params: { uid: "rule-code" } },
|
|
5690
|
+
expect: { status: 204 }
|
|
5691
|
+
},
|
|
5692
|
+
{
|
|
5693
|
+
id: "delete-permission-not-admin",
|
|
5694
|
+
route: "DELETE /SASjsApi/permission/{uid}",
|
|
5695
|
+
description: "refuses a deletion by a non-administrator",
|
|
5696
|
+
identity: ADMIN_SIGNED_IN,
|
|
5697
|
+
request: { token: RESTRICTED, params: { uid: "rule-code" } },
|
|
5698
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5699
|
+
},
|
|
5700
|
+
{
|
|
5701
|
+
id: "delete-permission-missing",
|
|
5702
|
+
route: "DELETE /SASjsApi/permission/{uid}",
|
|
5703
|
+
description: "answers 404 for a rule that does not exist",
|
|
5704
|
+
identity: ADMIN_SIGNED_IN,
|
|
5705
|
+
request: { token: ADMIN, params: { uid: "rule-none" } },
|
|
5706
|
+
expect: { status: 404, error: "Permission not found." }
|
|
5707
|
+
},
|
|
5708
|
+
// --------------------------------------------------------------- clients
|
|
5709
|
+
{
|
|
5710
|
+
id: "list-clients",
|
|
5711
|
+
route: "GET /SASjsApi/client",
|
|
5712
|
+
description: "lists the registered clients",
|
|
5713
|
+
identity: ADMIN_SIGNED_IN,
|
|
5714
|
+
request: { token: ADMIN },
|
|
5715
|
+
expect: {
|
|
5716
|
+
status: 200,
|
|
5717
|
+
body: [
|
|
5718
|
+
{
|
|
5719
|
+
clientId: "web_app",
|
|
5720
|
+
clientSecret: "web_app",
|
|
5721
|
+
accessTokenExpiration: 86400,
|
|
5722
|
+
refreshTokenExpiration: 2592e3
|
|
5723
|
+
},
|
|
5724
|
+
{
|
|
5725
|
+
clientId: "sasjs",
|
|
5726
|
+
clientSecret: "sasjs",
|
|
5727
|
+
accessTokenExpiration: 3600,
|
|
5728
|
+
refreshTokenExpiration: 86400
|
|
5729
|
+
}
|
|
5730
|
+
]
|
|
5731
|
+
}
|
|
5732
|
+
},
|
|
5733
|
+
{
|
|
5734
|
+
id: "list-clients-not-admin",
|
|
5735
|
+
route: "GET /SASjsApi/client",
|
|
5736
|
+
description: "refuses the list to a caller who is not an administrator",
|
|
5737
|
+
identity: ADMIN_SIGNED_IN,
|
|
5738
|
+
request: { token: RESTRICTED },
|
|
5739
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5740
|
+
},
|
|
5741
|
+
{
|
|
5742
|
+
id: "create-client",
|
|
5743
|
+
route: "POST /SASjsApi/client",
|
|
5744
|
+
description: "registers a client",
|
|
5745
|
+
identity: ADMIN_SIGNED_IN,
|
|
5746
|
+
request: {
|
|
5747
|
+
token: ADMIN,
|
|
5748
|
+
body: {
|
|
5749
|
+
clientId: "new_app",
|
|
5750
|
+
clientSecret: "new_secret",
|
|
5751
|
+
accessTokenExpiration: 3600,
|
|
5752
|
+
refreshTokenExpiration: 86400
|
|
5753
|
+
}
|
|
5754
|
+
},
|
|
5755
|
+
expect: {
|
|
5756
|
+
status: 200,
|
|
5757
|
+
body: {
|
|
5758
|
+
clientId: "new_app",
|
|
5759
|
+
clientSecret: "new_secret",
|
|
5760
|
+
accessTokenExpiration: 3600,
|
|
5761
|
+
refreshTokenExpiration: 86400
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
},
|
|
5765
|
+
{
|
|
5766
|
+
id: "create-client-defaults",
|
|
5767
|
+
route: "POST /SASjsApi/client",
|
|
5768
|
+
description: "applies the schema defaults when the lifetimes are omitted",
|
|
5769
|
+
identity: ADMIN_SIGNED_IN,
|
|
5770
|
+
request: {
|
|
5771
|
+
token: ADMIN,
|
|
5772
|
+
body: { clientId: "default_app", clientSecret: "default_secret" }
|
|
5773
|
+
},
|
|
5774
|
+
expect: {
|
|
5775
|
+
status: 200,
|
|
5776
|
+
body: {
|
|
5777
|
+
clientId: "default_app",
|
|
5778
|
+
clientSecret: "default_secret",
|
|
5779
|
+
accessTokenExpiration: 86400,
|
|
5780
|
+
refreshTokenExpiration: 2592e3
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
},
|
|
5784
|
+
{
|
|
5785
|
+
id: "create-client-not-admin",
|
|
5786
|
+
route: "POST /SASjsApi/client",
|
|
5787
|
+
description: "refuses a registration by a non-administrator",
|
|
5788
|
+
identity: ADMIN_SIGNED_IN,
|
|
5789
|
+
request: {
|
|
5790
|
+
token: RESTRICTED,
|
|
5791
|
+
body: {
|
|
5792
|
+
clientId: "new_app",
|
|
5793
|
+
clientSecret: "new_secret",
|
|
5794
|
+
accessTokenExpiration: 3600,
|
|
5795
|
+
refreshTokenExpiration: 86400
|
|
5796
|
+
}
|
|
5797
|
+
},
|
|
5798
|
+
expect: { status: 401, error: "Admin account required" }
|
|
5799
|
+
},
|
|
5800
|
+
{
|
|
5801
|
+
id: "create-client-duplicate",
|
|
5802
|
+
route: "POST /SASjsApi/client",
|
|
5803
|
+
description: "refuses a client id that is registered, with the 403 the server sends",
|
|
5804
|
+
identity: ADMIN_SIGNED_IN,
|
|
5805
|
+
request: {
|
|
5806
|
+
token: ADMIN,
|
|
5807
|
+
body: {
|
|
5808
|
+
clientId: "web_app",
|
|
5809
|
+
clientSecret: "other",
|
|
5810
|
+
accessTokenExpiration: 3600,
|
|
5811
|
+
refreshTokenExpiration: 86400
|
|
5812
|
+
}
|
|
5813
|
+
},
|
|
5814
|
+
expect: { status: 403, error: "Error: Client ID already exists." }
|
|
5815
|
+
},
|
|
5816
|
+
// -------------------------------------------------------------- passwords
|
|
5817
|
+
{
|
|
5818
|
+
id: "update-password",
|
|
5819
|
+
route: "PATCH /SASjsApi/auth/updatePassword",
|
|
5820
|
+
description: "changes the caller’s own password",
|
|
5821
|
+
identity: ADMIN_SIGNED_IN,
|
|
5822
|
+
request: {
|
|
5823
|
+
token: RESTRICTED,
|
|
5824
|
+
body: { currentPassword: "user123", newPassword: "changed123" }
|
|
5825
|
+
},
|
|
5826
|
+
expect: { status: 204 }
|
|
5827
|
+
},
|
|
5828
|
+
{
|
|
5829
|
+
id: "update-password-missing-field",
|
|
5830
|
+
route: "PATCH /SASjsApi/auth/updatePassword",
|
|
5831
|
+
description: "refuses a body without the current password",
|
|
5832
|
+
identity: ADMIN_SIGNED_IN,
|
|
5833
|
+
request: { token: RESTRICTED, body: { newPassword: "changed123" } },
|
|
5834
|
+
expect: { status: 400, error: '"currentPassword" is required' }
|
|
5835
|
+
},
|
|
5836
|
+
{
|
|
5837
|
+
id: "update-password-unauthenticated",
|
|
5838
|
+
route: "PATCH /SASjsApi/auth/updatePassword",
|
|
5839
|
+
description: "refuses the change with no caller",
|
|
5840
|
+
identity: ADMIN_SIGNED_IN,
|
|
5841
|
+
request: {
|
|
5842
|
+
body: { currentPassword: "user123", newPassword: "changed123" }
|
|
5843
|
+
},
|
|
5844
|
+
expect: { status: 401, error: "Unauthorized" }
|
|
5845
|
+
},
|
|
5846
|
+
{
|
|
5847
|
+
id: "update-password-wrong-current",
|
|
5848
|
+
route: "PATCH /SASjsApi/auth/updatePassword",
|
|
5849
|
+
description: "refuses a change whose current password is wrong",
|
|
5850
|
+
identity: ADMIN_SIGNED_IN,
|
|
5851
|
+
request: {
|
|
5852
|
+
token: RESTRICTED,
|
|
5853
|
+
body: { currentPassword: "wrong", newPassword: "changed123" }
|
|
5854
|
+
},
|
|
5855
|
+
expect: { status: 403, error: "Invalid current password!" }
|
|
5856
|
+
},
|
|
5857
|
+
{
|
|
5858
|
+
id: "update-password-provider",
|
|
5859
|
+
route: "PATCH /SASjsApi/auth/updatePassword",
|
|
5860
|
+
description: "refuses a password change on a provider-managed account",
|
|
5861
|
+
identity: PROVIDER_MANAGED,
|
|
5862
|
+
request: {
|
|
5863
|
+
token: "dir-access",
|
|
5864
|
+
body: { currentPassword: "dir123", newPassword: "changed123" }
|
|
5865
|
+
},
|
|
5866
|
+
expect: {
|
|
5867
|
+
status: 405,
|
|
5868
|
+
error: "Can not update password of user that is created by an external auth provider."
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
];
|
|
5872
|
+
|
|
5873
|
+
// src/corpus/runner.ts
|
|
5874
|
+
var servicesFor = (testCase) => {
|
|
5875
|
+
const identity = new MemoryIdentityStore(
|
|
5876
|
+
testCase.identity || DEFAULT_IDENTITY
|
|
5877
|
+
);
|
|
5878
|
+
return {
|
|
5879
|
+
drive: createDriveService(
|
|
5880
|
+
testCase.unwritable ? new RefusingDriveStore(testCase.seed || {}) : new MemoryDriveStore(testCase.seed || {})
|
|
5881
|
+
),
|
|
5882
|
+
identity: createIdentityService(identity),
|
|
5883
|
+
records: createRecordService(identity)
|
|
5884
|
+
};
|
|
5885
|
+
};
|
|
3786
5886
|
var RefusingDriveStore = class extends MemoryDriveStore {
|
|
3787
5887
|
async write() {
|
|
3788
5888
|
throw new Error("the store refused the write");
|