@sasjs/api 0.6.0 → 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 +2231 -118
- package/build/cjs/corpus/records.js +1222 -0
- package/build/cjs/corpus/runner.js +15 -6
- package/build/cjs/drive/deploy.js +49 -10
- 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/drive/deploy.d.ts +10 -0
- package/build/esm/drive/deploy.d.ts.map +1 -1
- package/build/esm/drive/deploy.js +48 -9
- 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
|
|
|
@@ -111,6 +116,7 @@ var SASjsApi = (() => {
|
|
|
111
116
|
status: "success",
|
|
112
117
|
message: "Files deployed successfully to @sasjs/server."
|
|
113
118
|
};
|
|
119
|
+
var MAX_DEPTH = 32;
|
|
114
120
|
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
115
121
|
var base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, "");
|
|
116
122
|
var base64Length = (value) => Math.floor(base64Body(value).length * 3 / 4);
|
|
@@ -145,7 +151,9 @@ var SASjsApi = (() => {
|
|
|
145
151
|
};
|
|
146
152
|
var memberName = (member) => member.type === "service" ? `${member.name}.sas` : member.name;
|
|
147
153
|
var memberBytes = (member) => member.type === "file" ? base64Length(member.code) : utf8Length(member.code);
|
|
148
|
-
var
|
|
154
|
+
var MEGABYTE = 1024 * 1024;
|
|
155
|
+
var describeSize = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.ceil(bytes2 / MEGABYTE * 10) / 10} MB`;
|
|
156
|
+
var describeLimit = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.round(bytes2 / MEGABYTE * 10) / 10} MB`;
|
|
149
157
|
var emptyWalk = () => ({
|
|
150
158
|
entries: 0,
|
|
151
159
|
totalBytes: 0,
|
|
@@ -153,7 +161,7 @@ var SASjsApi = (() => {
|
|
|
153
161
|
});
|
|
154
162
|
var isSafeSegment = (name) => name !== "" && name !== "." && name !== ".." && !/[/\\]/.test(name) && // a Windows drive or a NUL byte is never a segment either
|
|
155
163
|
!/[\0]/.test(name);
|
|
156
|
-
var walk = (members, parentPath, acc) => {
|
|
164
|
+
var walk = (members, parentPath, acc, depth = 1) => {
|
|
157
165
|
for (const member of members) {
|
|
158
166
|
const name = memberName(member);
|
|
159
167
|
const memberPath = parentPath ? `${parentPath}/${name}` : name;
|
|
@@ -165,7 +173,11 @@ var SASjsApi = (() => {
|
|
|
165
173
|
};
|
|
166
174
|
acc.entries++;
|
|
167
175
|
if (member.type === "folder") {
|
|
168
|
-
|
|
176
|
+
if (depth + 1 > MAX_DEPTH) {
|
|
177
|
+
if (!acc.tooDeep) acc.tooDeep = depth + 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
walk(member.members, memberPath, acc, depth + 1);
|
|
169
181
|
continue;
|
|
170
182
|
}
|
|
171
183
|
const bytes2 = memberBytes(member);
|
|
@@ -186,6 +198,12 @@ var SASjsApi = (() => {
|
|
|
186
198
|
const measured = emptyWalk();
|
|
187
199
|
walk(members, "", measured);
|
|
188
200
|
if (measured.nameRefusal) return measured.nameRefusal;
|
|
201
|
+
if (measured.tooDeep)
|
|
202
|
+
return {
|
|
203
|
+
code: 413,
|
|
204
|
+
status: "failure",
|
|
205
|
+
message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${MAX_DEPTH}).`
|
|
206
|
+
};
|
|
189
207
|
if (measured.entries > limits.entries)
|
|
190
208
|
return {
|
|
191
209
|
code: 413,
|
|
@@ -196,13 +214,13 @@ var SASjsApi = (() => {
|
|
|
196
214
|
return {
|
|
197
215
|
code: 413,
|
|
198
216
|
status: "failure",
|
|
199
|
-
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${
|
|
217
|
+
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
|
|
200
218
|
};
|
|
201
219
|
if (measured.largest.bytes > limits.fileBytes)
|
|
202
220
|
return {
|
|
203
221
|
code: 413,
|
|
204
222
|
status: "failure",
|
|
205
|
-
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${
|
|
223
|
+
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
|
|
206
224
|
};
|
|
207
225
|
return null;
|
|
208
226
|
};
|
|
@@ -665,7 +683,7 @@ var SASjsApi = (() => {
|
|
|
665
683
|
},
|
|
666
684
|
{
|
|
667
685
|
status: 401,
|
|
668
|
-
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`"
|
|
669
687
|
}
|
|
670
688
|
]
|
|
671
689
|
},
|
|
@@ -759,6 +777,10 @@ var SASjsApi = (() => {
|
|
|
759
777
|
{
|
|
760
778
|
status: 200,
|
|
761
779
|
description: "Ok"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
status: 401,
|
|
783
|
+
description: "Admin account required"
|
|
762
784
|
}
|
|
763
785
|
]
|
|
764
786
|
},
|
|
@@ -799,6 +821,14 @@ var SASjsApi = (() => {
|
|
|
799
821
|
{
|
|
800
822
|
status: 200,
|
|
801
823
|
description: "Ok"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
status: 401,
|
|
827
|
+
description: "Admin account required"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
status: 403,
|
|
831
|
+
description: "Client ID already exists."
|
|
802
832
|
}
|
|
803
833
|
]
|
|
804
834
|
},
|
|
@@ -1288,7 +1318,7 @@ var SASjsApi = (() => {
|
|
|
1288
1318
|
},
|
|
1289
1319
|
{
|
|
1290
1320
|
status: 409,
|
|
1291
|
-
description: "Folder already exists"
|
|
1321
|
+
description: "Folder already exists. The body is that message as plain text."
|
|
1292
1322
|
}
|
|
1293
1323
|
]
|
|
1294
1324
|
},
|
|
@@ -1328,7 +1358,7 @@ var SASjsApi = (() => {
|
|
|
1328
1358
|
},
|
|
1329
1359
|
{
|
|
1330
1360
|
status: 409,
|
|
1331
|
-
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."
|
|
1332
1362
|
}
|
|
1333
1363
|
]
|
|
1334
1364
|
},
|
|
@@ -1378,6 +1408,14 @@ var SASjsApi = (() => {
|
|
|
1378
1408
|
{
|
|
1379
1409
|
status: 200,
|
|
1380
1410
|
description: "Ok"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
status: 401,
|
|
1414
|
+
description: "Admin account required"
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
status: 409,
|
|
1418
|
+
description: "Group name already exists."
|
|
1381
1419
|
}
|
|
1382
1420
|
]
|
|
1383
1421
|
},
|
|
@@ -1406,6 +1444,22 @@ var SASjsApi = (() => {
|
|
|
1406
1444
|
{
|
|
1407
1445
|
status: 200,
|
|
1408
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."
|
|
1409
1463
|
}
|
|
1410
1464
|
]
|
|
1411
1465
|
},
|
|
@@ -1434,6 +1488,22 @@ var SASjsApi = (() => {
|
|
|
1434
1488
|
{
|
|
1435
1489
|
status: 200,
|
|
1436
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."
|
|
1437
1507
|
}
|
|
1438
1508
|
]
|
|
1439
1509
|
},
|
|
@@ -1456,6 +1526,14 @@ var SASjsApi = (() => {
|
|
|
1456
1526
|
{
|
|
1457
1527
|
status: 200,
|
|
1458
1528
|
description: "Ok"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
status: 401,
|
|
1532
|
+
description: "Admin account required"
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
status: 404,
|
|
1536
|
+
description: "Group not found."
|
|
1459
1537
|
}
|
|
1460
1538
|
]
|
|
1461
1539
|
},
|
|
@@ -1478,6 +1556,10 @@ var SASjsApi = (() => {
|
|
|
1478
1556
|
{
|
|
1479
1557
|
status: 200,
|
|
1480
1558
|
description: "Ok"
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
status: 404,
|
|
1562
|
+
description: "Group not found."
|
|
1481
1563
|
}
|
|
1482
1564
|
]
|
|
1483
1565
|
},
|
|
@@ -1500,6 +1582,10 @@ var SASjsApi = (() => {
|
|
|
1500
1582
|
{
|
|
1501
1583
|
status: 200,
|
|
1502
1584
|
description: "Ok"
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
status: 404,
|
|
1588
|
+
description: "Group not found."
|
|
1503
1589
|
}
|
|
1504
1590
|
]
|
|
1505
1591
|
},
|
|
@@ -1560,6 +1646,14 @@ var SASjsApi = (() => {
|
|
|
1560
1646
|
{
|
|
1561
1647
|
status: 200,
|
|
1562
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."
|
|
1563
1657
|
}
|
|
1564
1658
|
]
|
|
1565
1659
|
},
|
|
@@ -1606,6 +1700,22 @@ var SASjsApi = (() => {
|
|
|
1606
1700
|
{
|
|
1607
1701
|
status: 200,
|
|
1608
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."
|
|
1609
1719
|
}
|
|
1610
1720
|
]
|
|
1611
1721
|
},
|
|
@@ -1628,6 +1738,14 @@ var SASjsApi = (() => {
|
|
|
1628
1738
|
{
|
|
1629
1739
|
status: 204,
|
|
1630
1740
|
description: "No content"
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
status: 401,
|
|
1744
|
+
description: "Admin account required"
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
status: 404,
|
|
1748
|
+
description: "Permission not found."
|
|
1631
1749
|
}
|
|
1632
1750
|
]
|
|
1633
1751
|
},
|
|
@@ -1656,6 +1774,14 @@ var SASjsApi = (() => {
|
|
|
1656
1774
|
{
|
|
1657
1775
|
status: 200,
|
|
1658
1776
|
description: "Ok"
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
status: 401,
|
|
1780
|
+
description: "Admin account required"
|
|
1781
|
+
},
|
|
1782
|
+
{
|
|
1783
|
+
status: 404,
|
|
1784
|
+
description: "Permission not found."
|
|
1659
1785
|
}
|
|
1660
1786
|
]
|
|
1661
1787
|
},
|
|
@@ -1920,6 +2046,14 @@ var SASjsApi = (() => {
|
|
|
1920
2046
|
{
|
|
1921
2047
|
status: 200,
|
|
1922
2048
|
description: "Ok"
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
status: 401,
|
|
2052
|
+
description: "Admin account required"
|
|
2053
|
+
},
|
|
2054
|
+
{
|
|
2055
|
+
status: 409,
|
|
2056
|
+
description: "Username already exists."
|
|
1923
2057
|
}
|
|
1924
2058
|
]
|
|
1925
2059
|
},
|
|
@@ -1948,6 +2082,14 @@ var SASjsApi = (() => {
|
|
|
1948
2082
|
{
|
|
1949
2083
|
status: 204,
|
|
1950
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."
|
|
1951
2093
|
}
|
|
1952
2094
|
]
|
|
1953
2095
|
},
|
|
@@ -1970,6 +2112,14 @@ var SASjsApi = (() => {
|
|
|
1970
2112
|
{
|
|
1971
2113
|
status: 200,
|
|
1972
2114
|
description: "Ok"
|
|
2115
|
+
},
|
|
2116
|
+
{
|
|
2117
|
+
status: 401,
|
|
2118
|
+
description: "Unauthorized"
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
status: 404,
|
|
2122
|
+
description: "User is not found."
|
|
1973
2123
|
}
|
|
1974
2124
|
]
|
|
1975
2125
|
},
|
|
@@ -2028,6 +2178,22 @@ var SASjsApi = (() => {
|
|
|
2028
2178
|
{
|
|
2029
2179
|
status: 200,
|
|
2030
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."
|
|
2031
2197
|
}
|
|
2032
2198
|
]
|
|
2033
2199
|
},
|
|
@@ -2056,6 +2222,14 @@ var SASjsApi = (() => {
|
|
|
2056
2222
|
{
|
|
2057
2223
|
status: 204,
|
|
2058
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."
|
|
2059
2233
|
}
|
|
2060
2234
|
]
|
|
2061
2235
|
},
|
|
@@ -2078,6 +2252,14 @@ var SASjsApi = (() => {
|
|
|
2078
2252
|
{
|
|
2079
2253
|
status: 200,
|
|
2080
2254
|
description: "Ok"
|
|
2255
|
+
},
|
|
2256
|
+
{
|
|
2257
|
+
status: 401,
|
|
2258
|
+
description: "Unauthorized"
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
status: 404,
|
|
2262
|
+
description: "User is not found."
|
|
2081
2263
|
}
|
|
2082
2264
|
]
|
|
2083
2265
|
},
|
|
@@ -2136,6 +2318,22 @@ var SASjsApi = (() => {
|
|
|
2136
2318
|
{
|
|
2137
2319
|
status: 200,
|
|
2138
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."
|
|
2139
2337
|
}
|
|
2140
2338
|
]
|
|
2141
2339
|
},
|
|
@@ -2169,7 +2367,7 @@ var SASjsApi = (() => {
|
|
|
2169
2367
|
},
|
|
2170
2368
|
{
|
|
2171
2369
|
status: 403,
|
|
2172
|
-
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"
|
|
2173
2371
|
}
|
|
2174
2372
|
]
|
|
2175
2373
|
},
|
|
@@ -2254,7 +2452,7 @@ var SASjsApi = (() => {
|
|
|
2254
2452
|
Client: ["identity"],
|
|
2255
2453
|
Web: ["identity"]
|
|
2256
2454
|
};
|
|
2257
|
-
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.";
|
|
2258
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.";
|
|
2259
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.";
|
|
2260
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.";
|
|
@@ -2309,18 +2507,20 @@ var SASjsApi = (() => {
|
|
|
2309
2507
|
},
|
|
2310
2508
|
"POST /SASjsApi/auth/logout": { implemented: true },
|
|
2311
2509
|
"PATCH /SASjsApi/auth/updatePassword": {
|
|
2312
|
-
implemented:
|
|
2313
|
-
|
|
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]
|
|
2314
2514
|
},
|
|
2315
2515
|
// Auth Config - the providers a host was started with, and an LDAP import
|
|
2316
2516
|
"GET /SASjsApi/authConfig": { implemented: false, reason: HOST_CONFIG },
|
|
2317
2517
|
"POST /SASjsApi/authConfig/synchroniseWithLDAP": {
|
|
2318
2518
|
implemented: false,
|
|
2319
|
-
reason:
|
|
2519
|
+
reason: DIRECTORY
|
|
2320
2520
|
},
|
|
2321
2521
|
// Client - the OAuth clients the identity store already holds for sign-in
|
|
2322
|
-
"GET /SASjsApi/client": { implemented:
|
|
2323
|
-
"POST /SASjsApi/client": { implemented:
|
|
2522
|
+
"GET /SASjsApi/client": { implemented: true },
|
|
2523
|
+
"POST /SASjsApi/client": { implemented: true },
|
|
2324
2524
|
// Code - the editor's three calls, and the runtime one
|
|
2325
2525
|
"POST /SASjsApi/code/execute": { implemented: false, reason: RUNTIME },
|
|
2326
2526
|
"POST /SASjsApi/code/trigger": { implemented: false, reason: RUNTIME },
|
|
@@ -2394,25 +2594,13 @@ var SASjsApi = (() => {
|
|
|
2394
2594
|
reason: UNPUBLISH
|
|
2395
2595
|
},
|
|
2396
2596
|
// Group - members and the groups themselves
|
|
2397
|
-
"GET /SASjsApi/group": { implemented:
|
|
2398
|
-
"GET /SASjsApi/group/{uid}": { implemented:
|
|
2399
|
-
"GET /SASjsApi/group/by/groupname/{name}": {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
},
|
|
2403
|
-
"
|
|
2404
|
-
"POST /SASjsApi/group/{groupUid}/{userUid}": {
|
|
2405
|
-
implemented: false,
|
|
2406
|
-
reason: IDENTITY_RECORDS
|
|
2407
|
-
},
|
|
2408
|
-
"DELETE /SASjsApi/group/{groupUid}/{userUid}": {
|
|
2409
|
-
implemented: false,
|
|
2410
|
-
reason: IDENTITY_RECORDS
|
|
2411
|
-
},
|
|
2412
|
-
"DELETE /SASjsApi/group/{uid}": {
|
|
2413
|
-
implemented: false,
|
|
2414
|
-
reason: IDENTITY_RECORDS
|
|
2415
|
-
},
|
|
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 },
|
|
2416
2604
|
// Info - what the host is, and what this caller may use
|
|
2417
2605
|
"GET /SASjsApi/info": { implemented: false, reason: HOST_CONFIG },
|
|
2418
2606
|
"GET /SASjsApi/info/authorizedRoutes": {
|
|
@@ -2428,16 +2616,24 @@ var SASjsApi = (() => {
|
|
|
2428
2616
|
reason: PERMISSIONS
|
|
2429
2617
|
},
|
|
2430
2618
|
// Permission - the rules the inventory above is derived from
|
|
2431
|
-
"GET /SASjsApi/permission": {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
},
|
|
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 },
|
|
2441
2637
|
// Session - the caller's own session, then the record behind a triggered one
|
|
2442
2638
|
"GET /SASjsApi/session": {
|
|
2443
2639
|
implemented: true,
|
|
@@ -2464,29 +2660,26 @@ var SASjsApi = (() => {
|
|
|
2464
2660
|
"POST /SASjsApi/stp/execute": { implemented: false, reason: RUNTIME },
|
|
2465
2661
|
"POST /SASjsApi/stp/trigger": { implemented: false, reason: RUNTIME },
|
|
2466
2662
|
// User - the accounts the identity store already holds for sign-in
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
"
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
},
|
|
2478
|
-
"PATCH /SASjsApi/user/
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
},
|
|
2486
|
-
"DELETE /SASjsApi/user/by/username/{username}": {
|
|
2487
|
-
implemented: false,
|
|
2488
|
-
reason: IDENTITY_RECORDS
|
|
2489
|
-
}
|
|
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 }
|
|
2490
2683
|
};
|
|
2491
2684
|
|
|
2492
2685
|
// src/routes/index.ts
|
|
@@ -2578,6 +2771,7 @@ var SASjsApi = (() => {
|
|
|
2578
2771
|
};
|
|
2579
2772
|
|
|
2580
2773
|
// src/host/dispatch.ts
|
|
2774
|
+
var SECONDS_IN_A_DAY = 86400;
|
|
2581
2775
|
var validate = (route, request) => {
|
|
2582
2776
|
for (const param of route.params) {
|
|
2583
2777
|
if (!param.required || param.in === "formData" || param.in === "header")
|
|
@@ -2598,10 +2792,31 @@ var SASjsApi = (() => {
|
|
|
2598
2792
|
var dispatch = async (services, routeId, request = {}) => {
|
|
2599
2793
|
const route = routeById(routeId);
|
|
2600
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
|
+
}
|
|
2601
2808
|
validate(route, request);
|
|
2602
|
-
const { drive, identity } = services;
|
|
2603
2809
|
const query = request.query || {};
|
|
2604
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
|
+
};
|
|
2605
2820
|
const caller = async () => {
|
|
2606
2821
|
if (request.subject) return request.subject;
|
|
2607
2822
|
return identity?.authenticate(request.token);
|
|
@@ -2693,6 +2908,169 @@ var SASjsApi = (() => {
|
|
|
2693
2908
|
}
|
|
2694
2909
|
case "GET /SASjsApi/drive/filetree":
|
|
2695
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 };
|
|
2696
3074
|
default: {
|
|
2697
3075
|
const reason = routeById(routeId)?.reason;
|
|
2698
3076
|
throw new ApiError(
|
|
@@ -2719,7 +3097,29 @@ var SASjsApi = (() => {
|
|
|
2719
3097
|
"POST /SASLogon/login",
|
|
2720
3098
|
"POST /SASLogon/authorize",
|
|
2721
3099
|
"GET /SASLogon/logout",
|
|
2722
|
-
"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"
|
|
2723
3123
|
];
|
|
2724
3124
|
|
|
2725
3125
|
// src/identity/service.ts
|
|
@@ -2808,31 +3208,359 @@ var SASjsApi = (() => {
|
|
|
2808
3208
|
needsToUpdatePassword: user3.needsToUpdatePassword
|
|
2809
3209
|
});
|
|
2810
3210
|
|
|
2811
|
-
// src/identity/
|
|
2812
|
-
var
|
|
2813
|
-
var
|
|
2814
|
-
var
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
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));
|
|
3251
|
+
}
|
|
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
|
|
2836
3564
|
}
|
|
2837
3565
|
],
|
|
2838
3566
|
groups: [
|
|
@@ -2878,14 +3606,14 @@ var SASjsApi = (() => {
|
|
|
2878
3606
|
{
|
|
2879
3607
|
clientId: "web_app",
|
|
2880
3608
|
clientSecret: "web_app",
|
|
2881
|
-
accessTokenExpiration:
|
|
2882
|
-
refreshTokenExpiration:
|
|
3609
|
+
accessTokenExpiration: SECONDS_IN_A_DAY2,
|
|
3610
|
+
refreshTokenExpiration: SECONDS_IN_A_DAY2 * 30
|
|
2883
3611
|
},
|
|
2884
3612
|
{
|
|
2885
3613
|
clientId: "sasjs",
|
|
2886
3614
|
clientSecret: "sasjs",
|
|
2887
3615
|
accessTokenExpiration: 3600,
|
|
2888
|
-
refreshTokenExpiration:
|
|
3616
|
+
refreshTokenExpiration: SECONDS_IN_A_DAY2
|
|
2889
3617
|
}
|
|
2890
3618
|
]
|
|
2891
3619
|
};
|
|
@@ -2931,9 +3659,12 @@ var SASjsApi = (() => {
|
|
|
2931
3659
|
var live = (issued, now) => issued && (issued.expiresAt === void 0 || issued.expiresAt > now) ? issued.subject : void 0;
|
|
2932
3660
|
var MemoryIdentityStore = class {
|
|
2933
3661
|
constructor(fixture = DEFAULT_IDENTITY) {
|
|
2934
|
-
this.
|
|
3662
|
+
this.userList = [];
|
|
2935
3663
|
this.passwords = /* @__PURE__ */ new Map();
|
|
2936
|
-
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();
|
|
2937
3668
|
this.pairs = /* @__PURE__ */ new Map();
|
|
2938
3669
|
this.access = /* @__PURE__ */ new Map();
|
|
2939
3670
|
this.refresh = /* @__PURE__ */ new Map();
|
|
@@ -2945,24 +3676,56 @@ var SASjsApi = (() => {
|
|
|
2945
3676
|
`The identity fixture states format ${String(fixture.format)}; this store reads ${IDENTITY_FORMAT}.`
|
|
2946
3677
|
);
|
|
2947
3678
|
for (const user3 of fixture.users) {
|
|
2948
|
-
this.
|
|
3679
|
+
this.userList.push({
|
|
2949
3680
|
uid: user3.uid,
|
|
2950
3681
|
username: user3.username,
|
|
2951
3682
|
displayName: user3.displayName,
|
|
2952
3683
|
isAdmin: user3.isAdmin,
|
|
2953
3684
|
isActive: user3.isActive,
|
|
2954
|
-
needsToUpdatePassword: user3.needsToUpdatePassword
|
|
3685
|
+
needsToUpdatePassword: user3.needsToUpdatePassword,
|
|
3686
|
+
authProvider: user3.authProvider,
|
|
3687
|
+
autoExec: user3.autoExec
|
|
2955
3688
|
});
|
|
2956
3689
|
this.passwords.set(user3.uid, user3.password);
|
|
2957
3690
|
}
|
|
2958
|
-
for (const client of fixture.clients)
|
|
2959
|
-
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, {
|
|
2960
3698
|
clientId: client.clientId,
|
|
3699
|
+
clientSecret: client.clientSecret,
|
|
2961
3700
|
accessTokenExpiration: client.accessTokenExpiration,
|
|
2962
3701
|
refreshTokenExpiration: client.refreshTokenExpiration
|
|
2963
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
|
+
}
|
|
2964
3727
|
for (const token of fixture.tokens || []) {
|
|
2965
|
-
const user3 = this.
|
|
3728
|
+
const user3 = this.userList.find(
|
|
2966
3729
|
(candidate) => candidate.username === token.username
|
|
2967
3730
|
);
|
|
2968
3731
|
if (!user3) continue;
|
|
@@ -2976,7 +3739,7 @@ var SASjsApi = (() => {
|
|
|
2976
3739
|
});
|
|
2977
3740
|
}
|
|
2978
3741
|
for (const code of fixture.codes || []) {
|
|
2979
|
-
const user3 = this.
|
|
3742
|
+
const user3 = this.userList.find(
|
|
2980
3743
|
(candidate) => candidate.username === code.username
|
|
2981
3744
|
);
|
|
2982
3745
|
if (!user3) continue;
|
|
@@ -2986,17 +3749,21 @@ var SASjsApi = (() => {
|
|
|
2986
3749
|
}
|
|
2987
3750
|
}
|
|
2988
3751
|
async userByName(username) {
|
|
2989
|
-
|
|
3752
|
+
const user3 = this.userList.find(
|
|
3753
|
+
(candidate) => candidate.username === username
|
|
3754
|
+
);
|
|
3755
|
+
return user3 ? this.recordOf(user3) : void 0;
|
|
2990
3756
|
}
|
|
2991
3757
|
async userById(uid) {
|
|
2992
|
-
|
|
3758
|
+
const user3 = this.userList.find((candidate) => candidate.uid === uid);
|
|
3759
|
+
return user3 ? this.recordOf(user3) : void 0;
|
|
2993
3760
|
}
|
|
2994
3761
|
/** A fixture holds the password in clear, so the check is an equality. */
|
|
2995
3762
|
async passwordMatches(user3, password) {
|
|
2996
3763
|
return this.passwords.get(user3.uid) === password;
|
|
2997
3764
|
}
|
|
2998
3765
|
async client(clientId) {
|
|
2999
|
-
return this.
|
|
3766
|
+
return this.clientMap.get(clientId);
|
|
3000
3767
|
}
|
|
3001
3768
|
async issue(subject, client) {
|
|
3002
3769
|
const issuedAt = Date.now();
|
|
@@ -3074,6 +3841,134 @@ var SASjsApi = (() => {
|
|
|
3074
3841
|
this.claimed.add(code);
|
|
3075
3842
|
return true;
|
|
3076
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
|
+
}
|
|
3077
3972
|
};
|
|
3078
3973
|
|
|
3079
3974
|
// src/corpus/drive.ts
|
|
@@ -3761,15 +4656,1233 @@ var SASjsApi = (() => {
|
|
|
3761
4656
|
}
|
|
3762
4657
|
];
|
|
3763
4658
|
|
|
3764
|
-
// src/corpus/
|
|
3765
|
-
var
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
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
|
+
};
|
|
3773
5886
|
var RefusingDriveStore = class extends MemoryDriveStore {
|
|
3774
5887
|
async write() {
|
|
3775
5888
|
throw new Error("the store refused the write");
|