opal-security 3.1.1-beta.9cc3edb ā 3.1.1-beta.b652df3
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 +44 -26
- package/lib/commands/login.js +6 -2
- package/lib/commands/request/create.d.ts +6 -0
- package/lib/commands/request/create.js +57 -14
- package/lib/commands/whoami.d.ts +8 -0
- package/lib/commands/whoami.js +34 -0
- package/lib/graphql/gql.d.ts +10 -0
- package/lib/graphql/gql.js +2 -0
- package/lib/graphql/graphql.d.ts +85 -0
- package/lib/graphql/graphql.js +539 -1
- package/lib/lib/apollo.js +3 -4
- package/lib/lib/credentials/index.d.ts +2 -1
- package/lib/lib/credentials/index.js +2 -1
- package/lib/lib/flags.js +1 -1
- package/lib/lib/requests.d.ts +22 -15
- package/lib/lib/requests.js +458 -121
- package/lib/utils/displays.d.ts +1 -1
- package/lib/utils/displays.js +21 -30
- package/oclif.manifest.json +71 -11
- package/package.json +1 -1
package/lib/lib/requests.js
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DISPLAY_LABELS = void 0;
|
|
3
4
|
exports.initEmptyRequestMetadata = initEmptyRequestMetadata;
|
|
4
5
|
exports.selectRequestableItems = selectRequestableItems;
|
|
5
|
-
exports.chooseAssets = chooseAssets;
|
|
6
|
-
exports.chooseRoles = chooseRoles;
|
|
7
6
|
exports.doneSelectingAssets = doneSelectingAssets;
|
|
8
7
|
exports.setRequestDefaults = setRequestDefaults;
|
|
9
8
|
exports.promptForReason = promptForReason;
|
|
10
9
|
exports.promptForExpiration = promptForExpiration;
|
|
10
|
+
exports.promptRequestSubmission = promptRequestSubmission;
|
|
11
11
|
exports.submitFinalRequest = submitFinalRequest;
|
|
12
|
+
exports.bypassRequestSelection = bypassRequestSelection;
|
|
13
|
+
exports.bypassDuration = bypassDuration;
|
|
12
14
|
const chalk_1 = require("chalk");
|
|
13
|
-
const inquirer = require("inquirer");
|
|
14
15
|
const graphql_1 = require("../graphql");
|
|
16
|
+
const graphql_2 = require("../graphql/graphql");
|
|
15
17
|
const displays_1 = require("../utils/displays");
|
|
16
18
|
const config_1 = require("./config");
|
|
17
19
|
const { AutoComplete, Select, prompt, Form } = require("enquirer");
|
|
20
|
+
function entityTypeFromString(str) {
|
|
21
|
+
const capStr = str === null || str === void 0 ? void 0 : str.toLocaleUpperCase();
|
|
22
|
+
if (capStr === "RESOURCE") {
|
|
23
|
+
return graphql_2.EntityType.Resource;
|
|
24
|
+
}
|
|
25
|
+
if (capStr === "GROUP") {
|
|
26
|
+
return graphql_2.EntityType.Group;
|
|
27
|
+
}
|
|
28
|
+
// if type unknown, default to resource
|
|
29
|
+
return graphql_2.EntityType.Resource;
|
|
30
|
+
}
|
|
31
|
+
exports.DISPLAY_LABELS = {
|
|
32
|
+
[graphql_2.EntityType.Resource]: "Resource",
|
|
33
|
+
[graphql_2.EntityType.Group]: "Group",
|
|
34
|
+
};
|
|
18
35
|
function initEmptyRequestMetadata() {
|
|
19
36
|
// Initialize with empty defaults
|
|
20
37
|
const requestDefaults = {
|
|
@@ -94,6 +111,7 @@ async function queryRequestableApps(cmd, client, input) {
|
|
|
94
111
|
value: {
|
|
95
112
|
id: edge.node.id,
|
|
96
113
|
name: edge.node.displayName,
|
|
114
|
+
type: type,
|
|
97
115
|
toString: () => edge.node.displayName,
|
|
98
116
|
},
|
|
99
117
|
};
|
|
@@ -162,11 +180,11 @@ async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
162
180
|
const id = ((_c = item.resource) === null || _c === void 0 ? void 0 : _c.id) || ((_d = item.group) === null || _d === void 0 ? void 0 : _d.id);
|
|
163
181
|
const type = ((_e = item.resource) === null || _e === void 0 ? void 0 : _e.__typename) || ((_f = item.group) === null || _f === void 0 ? void 0 : _f.__typename);
|
|
164
182
|
return {
|
|
165
|
-
message: `${name}
|
|
183
|
+
message: `${name} [${type}]`,
|
|
166
184
|
value: {
|
|
167
185
|
name: name || "",
|
|
168
186
|
id: id || "",
|
|
169
|
-
type: ((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename)
|
|
187
|
+
type: entityTypeFromString(((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename)),
|
|
170
188
|
},
|
|
171
189
|
};
|
|
172
190
|
});
|
|
@@ -225,7 +243,7 @@ async function queryAssetRoles(cmd, client, assetType, assetId) {
|
|
|
225
243
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
226
244
|
try {
|
|
227
245
|
switch (assetType) {
|
|
228
|
-
case
|
|
246
|
+
case graphql_2.EntityType.Resource: {
|
|
229
247
|
const resp = await client.query({
|
|
230
248
|
query: RESOURCE_ROLES_QUERY,
|
|
231
249
|
variables: {
|
|
@@ -254,7 +272,7 @@ async function queryAssetRoles(cmd, client, assetType, assetId) {
|
|
|
254
272
|
}
|
|
255
273
|
return;
|
|
256
274
|
}
|
|
257
|
-
case
|
|
275
|
+
case graphql_2.EntityType.Group: {
|
|
258
276
|
const resp = await client.query({
|
|
259
277
|
query: GROUP_ROLES_QUERY,
|
|
260
278
|
variables: {
|
|
@@ -426,58 +444,181 @@ async function createRequest(cmd, client, requestedResources, requestedGroups, r
|
|
|
426
444
|
durationInMinutes: durationInMinutes,
|
|
427
445
|
},
|
|
428
446
|
});
|
|
429
|
-
let x;
|
|
430
447
|
switch ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.createRequest.__typename) {
|
|
431
448
|
case "CreateRequestResult":
|
|
432
449
|
return (_b = resp.data) === null || _b === void 0 ? void 0 : _b.createRequest.request;
|
|
433
450
|
case "RequestDurationTooLargeError":
|
|
434
|
-
|
|
451
|
+
cmd.log((_c = resp.data) === null || _c === void 0 ? void 0 : _c.createRequest.message);
|
|
435
452
|
break;
|
|
436
453
|
case "RequestRequiresUserAuthTokenForConnectionError":
|
|
437
|
-
|
|
454
|
+
cmd.log((_d = resp.data) === null || _d === void 0 ? void 0 : _d.createRequest.message);
|
|
438
455
|
break;
|
|
439
456
|
case "NoReviewersSetForOwnerError":
|
|
440
|
-
|
|
457
|
+
cmd.log((_e = resp.data) === null || _e === void 0 ? void 0 : _e.createRequest.message);
|
|
441
458
|
break;
|
|
442
459
|
case "NoReviewersSetForResourceError":
|
|
443
|
-
|
|
460
|
+
cmd.log((_f = resp.data) === null || _f === void 0 ? void 0 : _f.createRequest.message);
|
|
444
461
|
break;
|
|
445
462
|
case "NoReviewersSetForGroupError":
|
|
446
|
-
|
|
463
|
+
cmd.log((_g = resp.data) === null || _g === void 0 ? void 0 : _g.createRequest.message);
|
|
447
464
|
break;
|
|
448
465
|
case "NoManagerSetForRequestingUserError":
|
|
449
|
-
|
|
466
|
+
cmd.log((_h = resp.data) === null || _h === void 0 ? void 0 : _h.createRequest.message);
|
|
450
467
|
break;
|
|
451
468
|
case "MfaInvalidError":
|
|
452
|
-
|
|
469
|
+
cmd.log((_j = resp.data) === null || _j === void 0 ? void 0 : _j.createRequest.message);
|
|
453
470
|
break;
|
|
454
471
|
case "BulkRequestTooLargeError":
|
|
455
|
-
|
|
472
|
+
cmd.log((_k = resp.data) === null || _k === void 0 ? void 0 : _k.createRequest.message);
|
|
456
473
|
break;
|
|
457
474
|
case "ItemCannotBeRequestedError":
|
|
458
|
-
|
|
475
|
+
cmd.log((_l = resp.data) === null || _l === void 0 ? void 0 : _l.createRequest.message);
|
|
459
476
|
break;
|
|
460
477
|
case "UserCannotRequestAccessForTargetGroupError":
|
|
461
|
-
|
|
478
|
+
cmd.log((_m = resp.data) === null || _m === void 0 ? void 0 : _m.createRequest.message);
|
|
462
479
|
break;
|
|
463
480
|
case "GroupNestingNotAllowedError":
|
|
464
|
-
|
|
481
|
+
cmd.log((_o = resp.data) === null || _o === void 0 ? void 0 : _o.createRequest.message);
|
|
465
482
|
break;
|
|
466
483
|
case "TargetUserHasNestedAccessError":
|
|
467
|
-
|
|
484
|
+
cmd.log((_p = resp.data) === null || _p === void 0 ? void 0 : _p.createRequest.message);
|
|
468
485
|
break;
|
|
469
486
|
case "RequestReasonMissingError":
|
|
470
|
-
|
|
487
|
+
cmd.log((_q = resp.data) === null || _q === void 0 ? void 0 : _q.createRequest.message);
|
|
471
488
|
break;
|
|
472
489
|
case "RequestFieldValueMissingError":
|
|
473
|
-
|
|
490
|
+
cmd.log((_r = resp.data) === null || _r === void 0 ? void 0 : _r.createRequest.message);
|
|
474
491
|
break;
|
|
475
492
|
case "LinkedGroupNotRequestableError":
|
|
476
|
-
|
|
493
|
+
cmd.log((_s = resp.data) === null || _s === void 0 ? void 0 : _s.createRequest.message);
|
|
477
494
|
break;
|
|
478
495
|
case "RequestReasonBelowMinLengthError":
|
|
479
|
-
|
|
496
|
+
cmd.log((_t = resp.data) === null || _t === void 0 ? void 0 : _t.createRequest.message);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
if (error instanceof Error || typeof error === "string") {
|
|
502
|
+
cmd.error(error);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const CATALOG_ITEM = (0, graphql_1.graphql)(`
|
|
507
|
+
query GetCatalogItem($uuid: UUID!) {
|
|
508
|
+
catalogItem(id: $uuid) {
|
|
509
|
+
__typename
|
|
510
|
+
... on Connection {
|
|
511
|
+
id
|
|
512
|
+
displayName
|
|
513
|
+
}
|
|
514
|
+
... on Resource {
|
|
515
|
+
id
|
|
516
|
+
displayName
|
|
517
|
+
connection {
|
|
518
|
+
id
|
|
519
|
+
displayName
|
|
520
|
+
}
|
|
521
|
+
accessLevels{
|
|
522
|
+
accessLevelName
|
|
523
|
+
accessLevelRemoteId
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
...on Group {
|
|
527
|
+
id
|
|
528
|
+
name
|
|
529
|
+
connection {
|
|
530
|
+
id
|
|
531
|
+
displayName
|
|
532
|
+
}
|
|
533
|
+
accessLevels{
|
|
534
|
+
accessLevelName
|
|
535
|
+
accessLevelRemoteId
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
... on UserFacingError {
|
|
539
|
+
message
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
`);
|
|
544
|
+
const ASSOCIATED_ITEMS_QUERY = (0, graphql_1.graphql)(`
|
|
545
|
+
query GetAssociatedItems($resourceId: ResourceId!, $searchQuery: String) {
|
|
546
|
+
resource(input: {
|
|
547
|
+
id: $resourceId
|
|
548
|
+
}) {
|
|
549
|
+
__typename
|
|
550
|
+
... on ResourceResult {
|
|
551
|
+
__typename
|
|
552
|
+
resource {
|
|
553
|
+
associatedItems(
|
|
554
|
+
first: 200
|
|
555
|
+
filters: {
|
|
556
|
+
searchQuery: {
|
|
557
|
+
contains: $searchQuery
|
|
558
|
+
}
|
|
559
|
+
access: REQUESTABLE
|
|
560
|
+
endUserVisible: true
|
|
561
|
+
entityType: {
|
|
562
|
+
in: [GROUP, RESOURCE]
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
) {
|
|
566
|
+
edges {
|
|
567
|
+
__typename
|
|
568
|
+
... on ResourceAssociatedItemEdge {
|
|
569
|
+
alias
|
|
570
|
+
node {
|
|
571
|
+
__typename
|
|
572
|
+
id
|
|
573
|
+
name
|
|
574
|
+
... on Resource {
|
|
575
|
+
accessLevels(
|
|
576
|
+
filters: {
|
|
577
|
+
skipRemoteAccessLevels: false # azure app roles are remote
|
|
578
|
+
}
|
|
579
|
+
) {
|
|
580
|
+
__typename
|
|
581
|
+
accessLevelName
|
|
582
|
+
accessLevelRemoteId
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
... on ResourceNotFoundError {
|
|
592
|
+
message
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
`);
|
|
597
|
+
async function queryAssociatedItems(cmd, client, id, input) {
|
|
598
|
+
var _a, _b;
|
|
599
|
+
try {
|
|
600
|
+
const resp = await client.query({
|
|
601
|
+
query: ASSOCIATED_ITEMS_QUERY,
|
|
602
|
+
variables: {
|
|
603
|
+
resourceId: id || "",
|
|
604
|
+
searchQuery: input || "",
|
|
605
|
+
},
|
|
606
|
+
fetchPolicy: "network-only", // to avoid caching
|
|
607
|
+
});
|
|
608
|
+
switch (resp.data.resource.__typename) {
|
|
609
|
+
case "ResourceResult": {
|
|
610
|
+
const associatedItems = resp.data.resource.resource.associatedItems.edges.filter((edge) => edge.__typename === "ResourceAssociatedItemEdge");
|
|
611
|
+
const initial = [];
|
|
612
|
+
for (const edge of associatedItems) {
|
|
613
|
+
initial.push(...appRolesFromEdge(edge));
|
|
614
|
+
}
|
|
615
|
+
return initial;
|
|
616
|
+
}
|
|
617
|
+
case "ResourceNotFoundError":
|
|
618
|
+
cmd.log((_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.resource) === null || _b === void 0 ? void 0 : _b.message);
|
|
480
619
|
break;
|
|
620
|
+
default:
|
|
621
|
+
cmd.error(resp.error || "Unknown error occurred.");
|
|
481
622
|
}
|
|
482
623
|
}
|
|
483
624
|
catch (error) {
|
|
@@ -487,38 +628,125 @@ async function createRequest(cmd, client, requestedResources, requestedGroups, r
|
|
|
487
628
|
}
|
|
488
629
|
}
|
|
489
630
|
// Helper functions
|
|
631
|
+
const selectInstructions = chalk_1.default.dim("[āā] Navigate Ā· [Enter] Select Ā· Type to filter");
|
|
632
|
+
const multiSelectInstructions = chalk_1.default.dim("[āā] Navigate Ā· [Space] Select Ā· [Enter] Confirm Ā· Type to filter");
|
|
490
633
|
async function selectRequestableItems(cmd, client, requestMap) {
|
|
491
|
-
const
|
|
634
|
+
const initial = (await queryRequestableApps(cmd, client, "")) || [];
|
|
492
635
|
const appPrompt = new AutoComplete({
|
|
493
636
|
name: "App",
|
|
494
|
-
message: "Select an app
|
|
637
|
+
message: "Select an app",
|
|
638
|
+
hint: selectInstructions,
|
|
495
639
|
limit: 15,
|
|
496
|
-
choices:
|
|
640
|
+
choices: initial,
|
|
497
641
|
async suggest(input) {
|
|
498
642
|
const filteredChoices = await queryRequestableApps(cmd, client, input || "");
|
|
499
|
-
return filteredChoices ||
|
|
643
|
+
return filteredChoices || initial;
|
|
500
644
|
},
|
|
501
645
|
});
|
|
502
646
|
const App = await appPrompt.run();
|
|
503
647
|
// Set the app in the requestMap and call choose assets step
|
|
504
648
|
if (!(App.id in requestMap)) {
|
|
505
649
|
requestMap[App.id] = {
|
|
650
|
+
appId: App.id,
|
|
506
651
|
appName: App.name,
|
|
507
652
|
assets: {},
|
|
508
653
|
};
|
|
509
654
|
}
|
|
655
|
+
if (App.type === "OKTA_APP" || App.type === "AZURE_ENTERPRISE_APP") {
|
|
656
|
+
await chooseOktaAzureRoles(cmd, client, App, requestMap);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
510
659
|
await chooseAssets(cmd, client, App.id, requestMap);
|
|
511
660
|
}
|
|
661
|
+
async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
|
|
662
|
+
const associatedItems = (await queryAssociatedItems(cmd, client, app.id, "")) || [];
|
|
663
|
+
const rolePrompt = new AutoComplete({
|
|
664
|
+
name: "Roles",
|
|
665
|
+
message: `Select a role for ${app.name}:`,
|
|
666
|
+
hint: multiSelectInstructions,
|
|
667
|
+
limit: 15,
|
|
668
|
+
multiple: true,
|
|
669
|
+
async choices(input) {
|
|
670
|
+
if (!input)
|
|
671
|
+
return associatedItems;
|
|
672
|
+
const filteredChoices = await queryAssociatedItems(cmd, client, app.id, input);
|
|
673
|
+
return filteredChoices || associatedItems;
|
|
674
|
+
},
|
|
675
|
+
validate: (answer) => {
|
|
676
|
+
if (answer.length !== 1) {
|
|
677
|
+
return "Only one role is allowed to be requested for on Okta or Azure apps.";
|
|
678
|
+
}
|
|
679
|
+
return true;
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
const Roles = await rolePrompt.run();
|
|
683
|
+
const entry = requestMap[app.id];
|
|
684
|
+
for (const role of Roles) {
|
|
685
|
+
if (!(role.id in entry.assets)) {
|
|
686
|
+
entry.assets[role.id] = {
|
|
687
|
+
assetId: role.id,
|
|
688
|
+
assetName: role.name,
|
|
689
|
+
type: entityTypeFromString(role.type),
|
|
690
|
+
roles: {},
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function appRolesFromEdge(edge) {
|
|
696
|
+
var _a, _b, _c, _d;
|
|
697
|
+
switch (edge.node.__typename) {
|
|
698
|
+
case "Resource": {
|
|
699
|
+
if (edge.node.accessLevels && edge.node.accessLevels.length > 0) {
|
|
700
|
+
return edge.node.accessLevels.map((accessLevel) => ({
|
|
701
|
+
message: accessLevel.accessLevelName || "No Role (Direct access)",
|
|
702
|
+
value: {
|
|
703
|
+
id: edge.node.id + accessLevel.accessLevelRemoteId,
|
|
704
|
+
name: accessLevel.accessLevelName,
|
|
705
|
+
type: exports.DISPLAY_LABELS[graphql_2.EntityType.Resource],
|
|
706
|
+
toString: () => accessLevel.accessLevelName,
|
|
707
|
+
},
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
return [
|
|
711
|
+
{
|
|
712
|
+
message: (_a = edge.alias) !== null && _a !== void 0 ? _a : edge.node.name,
|
|
713
|
+
value: {
|
|
714
|
+
id: edge.node.id,
|
|
715
|
+
name: (_b = edge.alias) !== null && _b !== void 0 ? _b : edge.node.name,
|
|
716
|
+
type: exports.DISPLAY_LABELS[graphql_2.EntityType.Resource],
|
|
717
|
+
toString: () => { var _a; return (_a = edge.alias) !== null && _a !== void 0 ? _a : edge.node.name; },
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
];
|
|
721
|
+
}
|
|
722
|
+
case "Group":
|
|
723
|
+
return [
|
|
724
|
+
{
|
|
725
|
+
message: `${(_c = edge.alias) !== null && _c !== void 0 ? _c : edge.node.name} ${graphql_2.EntityType.Group}`,
|
|
726
|
+
value: {
|
|
727
|
+
id: edge.node.id,
|
|
728
|
+
name: (_d = edge.alias) !== null && _d !== void 0 ? _d : edge.node.name,
|
|
729
|
+
type: exports.DISPLAY_LABELS[graphql_2.EntityType.Group],
|
|
730
|
+
toString: () => { var _a; return (_a = edge.alias) !== null && _a !== void 0 ? _a : edge.node.name; },
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
512
736
|
async function chooseAssets(cmd, client, appId, requestMap) {
|
|
513
|
-
const
|
|
737
|
+
const initial = (await queryRequestableAssets(cmd, client, appId, "")) || [];
|
|
514
738
|
const assetPrompt = new AutoComplete({
|
|
515
739
|
name: "Assets",
|
|
516
740
|
message: "Select one or more assets:",
|
|
741
|
+
hint: multiSelectInstructions,
|
|
517
742
|
limit: 15,
|
|
518
743
|
multiple: true,
|
|
519
744
|
async choices(input) {
|
|
745
|
+
if (!input) {
|
|
746
|
+
return initial;
|
|
747
|
+
}
|
|
520
748
|
const filteredChoices = await queryRequestableAssets(cmd, client, appId, input);
|
|
521
|
-
return filteredChoices ||
|
|
749
|
+
return filteredChoices || initial;
|
|
522
750
|
},
|
|
523
751
|
validate: (answer) => {
|
|
524
752
|
if (answer.length < 1) {
|
|
@@ -535,6 +763,7 @@ async function chooseAssets(cmd, client, appId, requestMap) {
|
|
|
535
763
|
}
|
|
536
764
|
if (!(asset.id in entry.assets)) {
|
|
537
765
|
entry.assets[asset.id] = {
|
|
766
|
+
assetId: asset.id,
|
|
538
767
|
assetName: asset.name,
|
|
539
768
|
type: asset.type,
|
|
540
769
|
roles: {},
|
|
@@ -559,6 +788,7 @@ async function chooseRoles(cmd, client, appId, assetId, requestMap) {
|
|
|
559
788
|
const rolePrompt = new AutoComplete({
|
|
560
789
|
name: "Roles",
|
|
561
790
|
message: `Select one or more roles for ${assetEntry.assetName}:`,
|
|
791
|
+
hint: multiSelectInstructions,
|
|
562
792
|
limit: 15,
|
|
563
793
|
multiple: true,
|
|
564
794
|
choices: assetRoles,
|
|
@@ -575,6 +805,7 @@ async function chooseRoles(cmd, client, appId, assetId, requestMap) {
|
|
|
575
805
|
}
|
|
576
806
|
for (const role of roles) {
|
|
577
807
|
assetEntry.roles[role.id] = {
|
|
808
|
+
roleId: role.id,
|
|
578
809
|
roleName: role.name,
|
|
579
810
|
};
|
|
580
811
|
}
|
|
@@ -603,14 +834,14 @@ async function setRequestDefaults(cmd, client, metadata) {
|
|
|
603
834
|
const roleIds = mappedRoles.length ? mappedRoles : [""];
|
|
604
835
|
for (const roleId of roleIds) {
|
|
605
836
|
switch (assetNode.type) {
|
|
606
|
-
case
|
|
837
|
+
case graphql_2.EntityType.Resource: {
|
|
607
838
|
requestedResources.push({
|
|
608
839
|
resourceId: assetId,
|
|
609
840
|
accessLevelRemoteId: roleId,
|
|
610
841
|
});
|
|
611
842
|
break;
|
|
612
843
|
}
|
|
613
|
-
case
|
|
844
|
+
case graphql_2.EntityType.Group: {
|
|
614
845
|
requestedGroups.push({
|
|
615
846
|
groupId: assetId,
|
|
616
847
|
accessLevelRemoteId: roleId,
|
|
@@ -648,7 +879,7 @@ async function promptForReason(metadata) {
|
|
|
648
879
|
const { reason } = await prompt([
|
|
649
880
|
{
|
|
650
881
|
name: "reason",
|
|
651
|
-
message: "
|
|
882
|
+
message: "Why do you need access?",
|
|
652
883
|
type: "input",
|
|
653
884
|
validate: (answer) => {
|
|
654
885
|
if (!metadata.requestDefaults.reasonOptional && answer.length < 1) {
|
|
@@ -663,10 +894,16 @@ async function promptForReason(metadata) {
|
|
|
663
894
|
async function promptForExpiration(metadata) {
|
|
664
895
|
var _a, _b;
|
|
665
896
|
const durations = ((_b = (_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.durationOptions) === null || _b === void 0 ? void 0 : _b.map((option) => {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
:
|
|
897
|
+
var _a;
|
|
898
|
+
let label = option.label;
|
|
899
|
+
if (option.durationInMinutes ===
|
|
900
|
+
((_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.maxDurationInMinutes)) {
|
|
901
|
+
label = `${label} (MAX)`;
|
|
902
|
+
}
|
|
903
|
+
if (option.durationInMinutes ===
|
|
904
|
+
metadata.requestDefaults.recommendedDurationInMinutes) {
|
|
905
|
+
label = `${label} (RECOMMENDED)`;
|
|
906
|
+
}
|
|
670
907
|
return {
|
|
671
908
|
message: label,
|
|
672
909
|
value: {
|
|
@@ -681,6 +918,7 @@ async function promptForExpiration(metadata) {
|
|
|
681
918
|
const expirationSelect = new AutoComplete({
|
|
682
919
|
name: "expiration",
|
|
683
920
|
message: "When should access expire?",
|
|
921
|
+
hint: "Type to filter",
|
|
684
922
|
type: "list",
|
|
685
923
|
choices: durations,
|
|
686
924
|
pageSize: 15,
|
|
@@ -688,7 +926,7 @@ async function promptForExpiration(metadata) {
|
|
|
688
926
|
let selected = await expirationSelect.run();
|
|
689
927
|
switch (selected.label) {
|
|
690
928
|
case "Custom": {
|
|
691
|
-
selected = await setCustomDuration();
|
|
929
|
+
selected = await setCustomDuration(metadata);
|
|
692
930
|
break;
|
|
693
931
|
}
|
|
694
932
|
case "Permanent": {
|
|
@@ -699,7 +937,36 @@ async function promptForExpiration(metadata) {
|
|
|
699
937
|
metadata.durationInMinutes = selected.durationInMinutes;
|
|
700
938
|
metadata.durationLabel = selected.label;
|
|
701
939
|
}
|
|
702
|
-
|
|
940
|
+
function getDurationNumbers(duration) {
|
|
941
|
+
const d = +duration.days || 0;
|
|
942
|
+
const h = +duration.hours || 0;
|
|
943
|
+
const m = +duration.minutes || 0;
|
|
944
|
+
return { d, h, m };
|
|
945
|
+
}
|
|
946
|
+
function getDurationInMinutes(duration) {
|
|
947
|
+
const { d, h, m } = getDurationNumbers(duration);
|
|
948
|
+
const minutesInDay = 1440; // 24 hours * 60 minutes
|
|
949
|
+
const minutesInHour = 60;
|
|
950
|
+
return d * minutesInDay + h * minutesInHour + m;
|
|
951
|
+
}
|
|
952
|
+
function getDHMFromMinutes(minutes) {
|
|
953
|
+
const label = [];
|
|
954
|
+
const d = Math.floor(minutes / 1440);
|
|
955
|
+
if (d > 0) {
|
|
956
|
+
label.push(`${d}d`);
|
|
957
|
+
}
|
|
958
|
+
const remainingMinutes = minutes % 1440;
|
|
959
|
+
const h = Math.floor(remainingMinutes / 60);
|
|
960
|
+
if (h > 0) {
|
|
961
|
+
label.push(`${h}h`);
|
|
962
|
+
}
|
|
963
|
+
const m = remainingMinutes % 60;
|
|
964
|
+
if (m > 0) {
|
|
965
|
+
label.push(`${m}m`);
|
|
966
|
+
}
|
|
967
|
+
return label.join(" ");
|
|
968
|
+
}
|
|
969
|
+
async function setCustomDuration(metadata) {
|
|
703
970
|
const durationForm = new Form({
|
|
704
971
|
name: "user",
|
|
705
972
|
message: "Please set a custom access duration:",
|
|
@@ -709,107 +976,177 @@ async function setCustomDuration() {
|
|
|
709
976
|
{ name: "minutes", message: "Minutes", initial: "0" },
|
|
710
977
|
],
|
|
711
978
|
validate: (answer) => {
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
hours < 0 ||
|
|
717
|
-
minutes < 0 ||
|
|
718
|
-
(days === 0 && hours === 0 && minutes === 0)) {
|
|
979
|
+
var _a, _b, _c;
|
|
980
|
+
const { d, h, m } = getDurationNumbers(answer);
|
|
981
|
+
const durationInMinutes = getDurationInMinutes(answer);
|
|
982
|
+
if (d < 0 || h < 0 || m < 0 || d + h + m === 0 || (h > 23 && m > 59)) {
|
|
719
983
|
return "Please enter a valid duration.";
|
|
720
984
|
}
|
|
985
|
+
if (((_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.maxDurationInMinutes) &&
|
|
986
|
+
durationInMinutes > ((_b = metadata.requestDefaults) === null || _b === void 0 ? void 0 : _b.maxDurationInMinutes)) {
|
|
987
|
+
const maxDHM = getDHMFromMinutes((_c = metadata.requestDefaults) === null || _c === void 0 ? void 0 : _c.maxDurationInMinutes);
|
|
988
|
+
return `The max duration for the selected assets is ${maxDHM}.`;
|
|
989
|
+
}
|
|
721
990
|
return true;
|
|
722
991
|
},
|
|
992
|
+
return: (answer) => {
|
|
993
|
+
return getDurationInMinutes(answer);
|
|
994
|
+
},
|
|
723
995
|
});
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
const durationInMinutes =
|
|
727
|
-
|
|
728
|
-
if (duration.days > 0) {
|
|
729
|
-
durationLabel += `${duration.days}d `;
|
|
730
|
-
}
|
|
731
|
-
if (duration.hours > 0) {
|
|
732
|
-
durationLabel += `${duration.hours}h `;
|
|
733
|
-
}
|
|
734
|
-
if (duration.minutes > 0) {
|
|
735
|
-
durationLabel += `${duration.minutes}m`;
|
|
736
|
-
}
|
|
996
|
+
const durationResult = await durationForm.run();
|
|
997
|
+
const { d, h, m } = getDurationNumbers(durationResult);
|
|
998
|
+
const durationInMinutes = getDurationInMinutes(durationResult);
|
|
999
|
+
const durationLabel = getDHMFromMinutes(durationInMinutes);
|
|
737
1000
|
return {
|
|
738
1001
|
durationInMinutes: durationInMinutes,
|
|
739
1002
|
label: durationLabel,
|
|
740
1003
|
};
|
|
741
1004
|
}
|
|
742
|
-
async function
|
|
743
|
-
|
|
1005
|
+
async function promptRequestSubmission(cmd, metadata) {
|
|
1006
|
+
(0, displays_1.displayFinalRequestSummary)(cmd, metadata);
|
|
744
1007
|
const submitMessage = "ā
Yes, submit request";
|
|
745
1008
|
const cancelMessage = "ā No, cancel request";
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
}
|
|
1009
|
+
const prompt = new Select({
|
|
1010
|
+
name: "submitOrCancel",
|
|
1011
|
+
message: "Is this all you want to request?",
|
|
1012
|
+
choices: [submitMessage, cancelMessage],
|
|
1013
|
+
});
|
|
1014
|
+
const submitOrCancel = await prompt.run();
|
|
1015
|
+
return submitOrCancel === submitMessage;
|
|
1016
|
+
}
|
|
1017
|
+
async function submitFinalRequest(cmd, client, metadata) {
|
|
1018
|
+
var _a, _b, _c, _d;
|
|
1019
|
+
// Build requested assets lists for the mutation
|
|
1020
|
+
const requestedResources = [];
|
|
1021
|
+
const requestedGroups = [];
|
|
1022
|
+
for (const appNode of Object.values(metadata.requestMap)) {
|
|
1023
|
+
// This extraction is different than the one in setRequestDefaults.
|
|
1024
|
+
// Both extract the requestedResources and requestedGroups,
|
|
1025
|
+
// use different formats.
|
|
1026
|
+
for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
|
|
1027
|
+
if (assetNode.roles) {
|
|
1028
|
+
const mappedRoles = Object.entries(assetNode.roles).map(([roleId, _roleNode]) => {
|
|
1029
|
+
return roleId;
|
|
1030
|
+
});
|
|
1031
|
+
const roleIds = mappedRoles.length > 0 ? mappedRoles : [""];
|
|
1032
|
+
for (const roleId of roleIds) {
|
|
1033
|
+
switch (assetNode.type) {
|
|
1034
|
+
case graphql_2.EntityType.Resource: {
|
|
1035
|
+
requestedResources.push({
|
|
1036
|
+
resourceId: assetId,
|
|
1037
|
+
accessLevel: {
|
|
1038
|
+
accessLevelName: ((_b = (_a = assetNode.roles) === null || _a === void 0 ? void 0 : _a[roleId]) === null || _b === void 0 ? void 0 : _b.roleName) || "",
|
|
1039
|
+
accessLevelRemoteId: roleId,
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
case graphql_2.EntityType.Group: {
|
|
1045
|
+
requestedGroups.push({
|
|
1046
|
+
groupId: assetId,
|
|
1047
|
+
accessLevel: {
|
|
1048
|
+
accessLevelName: ((_d = (_c = assetNode.roles) === null || _c === void 0 ? void 0 : _c[roleId]) === null || _d === void 0 ? void 0 : _d.roleName) || "",
|
|
1049
|
+
accessLevelRemoteId: roleId,
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
break;
|
|
789
1053
|
}
|
|
790
1054
|
}
|
|
791
1055
|
}
|
|
792
1056
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const resp = await createRequest(cmd, client, requestedResources, requestedGroups, metadata.reason, metadata.durationInMinutes);
|
|
1060
|
+
// Build link to request
|
|
1061
|
+
const configData = (0, config_1.getOrCreateConfigData)(cmd.config.configDir);
|
|
1062
|
+
if (resp === null || resp === void 0 ? void 0 : resp.id) {
|
|
1063
|
+
cmd.log("\nš Your Access Request has been submitted!\n");
|
|
1064
|
+
cmd.log(`${chalk_1.default.bold("ID: ")} ${chalk_1.default.cyan(resp === null || resp === void 0 ? void 0 : resp.id)}`);
|
|
1065
|
+
if (resp === null || resp === void 0 ? void 0 : resp.status) {
|
|
1066
|
+
cmd.log((0, displays_1.getStyledStatus)(resp === null || resp === void 0 ? void 0 : resp.status));
|
|
1067
|
+
}
|
|
1068
|
+
const requestLink = `${configData[config_1.urlKey]}/requests/sent/${resp === null || resp === void 0 ? void 0 : resp.id}`;
|
|
1069
|
+
cmd.log(`${chalk_1.default.bold("Link:")} ${chalk_1.default.underline(requestLink)}\n`);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
async function bypassRequestSelection(cmd, client, flagValue, metadata) {
|
|
1074
|
+
var _a, _b;
|
|
1075
|
+
try {
|
|
1076
|
+
// Query Catalog Item endpoint to identify what the id belongs to (resource or group)
|
|
1077
|
+
for (const val of flagValue) {
|
|
1078
|
+
const delimiterIndex = val.indexOf(":");
|
|
1079
|
+
const assetId = val.substring(0, delimiterIndex);
|
|
1080
|
+
const roleName = val.substring(delimiterIndex + 1);
|
|
1081
|
+
const resp = await client.query({
|
|
1082
|
+
query: CATALOG_ITEM,
|
|
1083
|
+
variables: {
|
|
1084
|
+
uuid: assetId || "",
|
|
1085
|
+
},
|
|
1086
|
+
fetchPolicy: "network-only", // to avoid caching
|
|
1087
|
+
});
|
|
1088
|
+
switch (resp.data.catalogItem.__typename) {
|
|
1089
|
+
case "Group":
|
|
1090
|
+
case "Resource": {
|
|
1091
|
+
const item = resp.data.catalogItem;
|
|
1092
|
+
const assetName = item.__typename === "Resource" ? item.displayName : item.name;
|
|
1093
|
+
const requestableRoles = (item.accessLevels || [])
|
|
1094
|
+
// TODO: Support okta azure apps ?.filter((role) => role.accessLevelName !== "") // This assumes length == 1
|
|
1095
|
+
.map((role) => ({
|
|
1096
|
+
id: role.accessLevelRemoteId,
|
|
1097
|
+
name: role.accessLevelName,
|
|
1098
|
+
}));
|
|
1099
|
+
const appId = ((_a = item.connection) === null || _a === void 0 ? void 0 : _a.id) || "";
|
|
1100
|
+
if (!(appId in metadata.requestMap)) {
|
|
1101
|
+
metadata.requestMap[appId] = {
|
|
1102
|
+
appName: ((_b = item.connection) === null || _b === void 0 ? void 0 : _b.displayName) || "",
|
|
1103
|
+
appId: appId,
|
|
1104
|
+
assets: {},
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
const assetEntry = metadata.requestMap[appId].assets[assetId];
|
|
1108
|
+
if (!assetEntry) {
|
|
1109
|
+
metadata.requestMap[appId].assets[assetId] = {
|
|
1110
|
+
assetId: assetId,
|
|
1111
|
+
assetName: assetName,
|
|
1112
|
+
type: entityTypeFromString(item.__typename),
|
|
1113
|
+
roles: {},
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
if (requestableRoles.length > 0 &&
|
|
1117
|
+
!(requestableRoles.length === 1 && requestableRoles[0].name === "")) {
|
|
1118
|
+
const selectedRole = requestableRoles.find((role) => role.name === roleName);
|
|
1119
|
+
if (selectedRole !== undefined) {
|
|
1120
|
+
if (!metadata.requestMap[appId].assets[assetId].roles) {
|
|
1121
|
+
metadata.requestMap[appId].assets[assetId].roles = {};
|
|
1122
|
+
}
|
|
1123
|
+
metadata.requestMap[appId].assets[assetId].roles[selectedRole.id] = {
|
|
1124
|
+
roleId: selectedRole.id,
|
|
1125
|
+
roleName: selectedRole.name,
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
cmd.error(`Access level specified does not match one of ${assetName}'s defined access levels: ${requestableRoles.map((role) => `"${role.name}"`)}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
break;
|
|
801
1133
|
}
|
|
802
|
-
|
|
803
|
-
|
|
1134
|
+
default:
|
|
1135
|
+
cmd.error("Invalid asset id was passed in using the --id flag.");
|
|
804
1136
|
}
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
case cancelMessage: {
|
|
808
|
-
cmd.log("š« Access Request has been cancelled.");
|
|
809
|
-
return;
|
|
810
1137
|
}
|
|
811
|
-
|
|
812
|
-
|
|
1138
|
+
}
|
|
1139
|
+
catch (error) {
|
|
1140
|
+
if (error instanceof Error || typeof error === "string") {
|
|
1141
|
+
cmd.error(error);
|
|
813
1142
|
}
|
|
814
1143
|
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
function bypassDuration(cmd, duration, metadata) {
|
|
1147
|
+
const maxDuration = metadata.requestDefaults.maxDurationInMinutes;
|
|
1148
|
+
if (maxDuration && duration > maxDuration) {
|
|
1149
|
+
cmd.error(`The requested duration exceeds the allowed limit of ${maxDuration}`);
|
|
1150
|
+
}
|
|
1151
|
+
metadata.durationInMinutes = duration;
|
|
815
1152
|
}
|