opal-security 3.1.0 → 3.1.1-beta.4a79f20
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 +18 -34
- package/lib/commands/request/create.js +8 -6
- package/lib/commands/request/get.d.ts +6 -1
- package/lib/commands/request/get.js +99 -5
- package/lib/commands/request/list.d.ts +6 -2
- package/lib/commands/request/list.js +87 -6
- package/lib/graphql/gql.d.ts +22 -2
- package/lib/graphql/gql.js +5 -1
- package/lib/graphql/graphql.d.ts +254 -117
- package/lib/graphql/graphql.js +782 -4
- package/lib/lib/requests.d.ts +29 -9
- package/lib/lib/requests.js +255 -56
- package/lib/utils/displays.d.ts +3 -0
- package/lib/utils/displays.js +68 -4
- package/oclif.manifest.json +51 -7
- package/package.json +2 -1
package/lib/lib/requests.d.ts
CHANGED
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
import type { NormalizedCacheObject } from "@apollo/client/core";
|
|
2
2
|
import type { ApolloClient } from "@apollo/client/core/ApolloClient";
|
|
3
3
|
import type { Command } from "@oclif/core/lib/command";
|
|
4
|
-
|
|
4
|
+
interface AppNode {
|
|
5
5
|
appName: string;
|
|
6
|
-
assets:
|
|
6
|
+
assets: Record<string, AssetNode>;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
interface AssetNode {
|
|
9
9
|
assetName: string;
|
|
10
|
-
roles?:
|
|
10
|
+
roles?: Record<string, RoleNode>;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
interface RoleNode {
|
|
13
13
|
roleName: string;
|
|
14
14
|
}
|
|
15
|
-
export type RequestMap =
|
|
15
|
+
export type RequestMap = Record<string, AppNode>;
|
|
16
|
+
interface DurationOption {
|
|
17
|
+
durationInMinutes: number;
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
interface RequestDefaults {
|
|
21
|
+
durationOptions?: DurationOption[];
|
|
22
|
+
recommendedDurationInMinutes?: number | null;
|
|
23
|
+
defaultDurationInMinutes?: number;
|
|
24
|
+
maxDurationInMinutes?: number | null;
|
|
25
|
+
requireSupportTicket?: boolean;
|
|
26
|
+
reasonOptional?: boolean;
|
|
27
|
+
requesterIsAdmin?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface RequestMetadata {
|
|
30
|
+
requestMap: RequestMap;
|
|
31
|
+
requestDefaults: RequestDefaults;
|
|
32
|
+
}
|
|
33
|
+
export declare function createEmptyRequestMetadata(): RequestMetadata;
|
|
16
34
|
export declare function selectRequestableItems(cmd: Command, client: ApolloClient<NormalizedCacheObject>, requestMap: RequestMap): Promise<void>;
|
|
17
35
|
export declare function chooseAssets(cmd: Command, client: ApolloClient<NormalizedCacheObject>, appId: string, requestMap: RequestMap): Promise<void>;
|
|
18
|
-
export declare function chooseRoles(appId: string, assetId: string, requestMap: RequestMap): Promise<void>;
|
|
36
|
+
export declare function chooseRoles(cmd: Command, client: ApolloClient<NormalizedCacheObject>, appId: string, assetId: string, requestMap: RequestMap): Promise<void>;
|
|
19
37
|
export declare function doneSelectingAssets(): Promise<boolean>;
|
|
20
|
-
export declare function
|
|
21
|
-
export declare function
|
|
38
|
+
export declare function setRequestDefaults(cmd: Command, client: ApolloClient<NormalizedCacheObject>, metadata: RequestMetadata): Promise<void>;
|
|
39
|
+
export declare function promptForReason(metadata: RequestMetadata): Promise<any>;
|
|
40
|
+
export declare function promptForExpiration(metadata: RequestMetadata): Promise<any>;
|
|
22
41
|
export declare function submitFinalRequest(cmd: Command): Promise<void>;
|
|
42
|
+
export {};
|
package/lib/lib/requests.js
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEmptyRequestMetadata = createEmptyRequestMetadata;
|
|
3
4
|
exports.selectRequestableItems = selectRequestableItems;
|
|
4
5
|
exports.chooseAssets = chooseAssets;
|
|
5
6
|
exports.chooseRoles = chooseRoles;
|
|
6
7
|
exports.doneSelectingAssets = doneSelectingAssets;
|
|
8
|
+
exports.setRequestDefaults = setRequestDefaults;
|
|
7
9
|
exports.promptForReason = promptForReason;
|
|
8
10
|
exports.promptForExpiration = promptForExpiration;
|
|
9
11
|
exports.submitFinalRequest = submitFinalRequest;
|
|
10
12
|
const inquirer = require("inquirer");
|
|
11
13
|
const graphql_1 = require("../graphql");
|
|
12
14
|
inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt"));
|
|
15
|
+
function createEmptyRequestMetadata() {
|
|
16
|
+
// Initialize with empty defaults
|
|
17
|
+
const requestDefaults = {
|
|
18
|
+
durationOptions: [],
|
|
19
|
+
recommendedDurationInMinutes: undefined,
|
|
20
|
+
defaultDurationInMinutes: undefined,
|
|
21
|
+
maxDurationInMinutes: undefined,
|
|
22
|
+
requireSupportTicket: false,
|
|
23
|
+
reasonOptional: false,
|
|
24
|
+
requesterIsAdmin: false,
|
|
25
|
+
};
|
|
26
|
+
// Initialize with empty map
|
|
27
|
+
const requestMap = {};
|
|
28
|
+
return {
|
|
29
|
+
requestMap,
|
|
30
|
+
requestDefaults,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
13
33
|
// Queries and Mutations
|
|
34
|
+
// TODO: add pagination ability from CLI. (Load more...) option
|
|
14
35
|
const GET_REQUESTABLE_APPS_QUERY = (0, graphql_1.graphql)(`
|
|
15
36
|
query GetRequestableAppsQuery($searchQuery: String) {
|
|
16
37
|
appsV2(
|
|
@@ -40,39 +61,6 @@ const GET_REQUESTABLE_APPS_QUERY = (0, graphql_1.graphql)(`
|
|
|
40
61
|
}
|
|
41
62
|
}
|
|
42
63
|
`);
|
|
43
|
-
const GET_ASSETS_QUERY = (0, graphql_1.graphql)(`
|
|
44
|
-
query PaginatedEntityDropdown(
|
|
45
|
-
$id: UUID!
|
|
46
|
-
$searchQuery: String
|
|
47
|
-
) {
|
|
48
|
-
app(id: $id) {
|
|
49
|
-
__typename
|
|
50
|
-
... on App {
|
|
51
|
-
id
|
|
52
|
-
items(
|
|
53
|
-
input: {
|
|
54
|
-
access: REQUESTABLE
|
|
55
|
-
searchQuery: $searchQuery
|
|
56
|
-
includeOnlyRequestable: true
|
|
57
|
-
}
|
|
58
|
-
) {
|
|
59
|
-
items {
|
|
60
|
-
key
|
|
61
|
-
resource {
|
|
62
|
-
id
|
|
63
|
-
name
|
|
64
|
-
}
|
|
65
|
-
group {
|
|
66
|
-
id
|
|
67
|
-
name
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
cursor
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
`);
|
|
76
64
|
async function queryRequestableApps(cmd, client, input) {
|
|
77
65
|
var _a, _b;
|
|
78
66
|
try {
|
|
@@ -107,8 +95,44 @@ async function queryRequestableApps(cmd, client, input) {
|
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
97
|
}
|
|
98
|
+
const GET_ASSETS_QUERY = (0, graphql_1.graphql)(`
|
|
99
|
+
query PaginatedEntityDropdown(
|
|
100
|
+
$id: UUID!
|
|
101
|
+
$searchQuery: String
|
|
102
|
+
) {
|
|
103
|
+
app(id: $id) {
|
|
104
|
+
__typename
|
|
105
|
+
... on App {
|
|
106
|
+
id
|
|
107
|
+
items(
|
|
108
|
+
input: {
|
|
109
|
+
access: REQUESTABLE
|
|
110
|
+
searchQuery: $searchQuery
|
|
111
|
+
includeOnlyRequestable: true
|
|
112
|
+
}
|
|
113
|
+
) {
|
|
114
|
+
items {
|
|
115
|
+
key
|
|
116
|
+
resource {
|
|
117
|
+
id
|
|
118
|
+
name
|
|
119
|
+
}
|
|
120
|
+
group {
|
|
121
|
+
id
|
|
122
|
+
name
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
cursor
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
... on AppNotFoundError {
|
|
129
|
+
message
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`);
|
|
110
134
|
async function queryRequestableAssets(cmd, client, appId, input) {
|
|
111
|
-
var _a, _b, _c, _d;
|
|
135
|
+
var _a, _b, _c, _d, _e, _f;
|
|
112
136
|
try {
|
|
113
137
|
const resp = await client.query({
|
|
114
138
|
query: GET_ASSETS_QUERY,
|
|
@@ -137,10 +161,10 @@ async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
137
161
|
};
|
|
138
162
|
});
|
|
139
163
|
case "AppNotFoundError":
|
|
140
|
-
x = cmd.error(
|
|
164
|
+
x = cmd.error((_f = (_e = resp.data) === null || _e === void 0 ? void 0 : _e.app) === null || _f === void 0 ? void 0 : _f.message);
|
|
141
165
|
break;
|
|
142
166
|
default:
|
|
143
|
-
cmd.error("Unknown error occurred.");
|
|
167
|
+
cmd.error(resp.error || "Unknown error occurred.");
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
catch (error) {
|
|
@@ -149,6 +173,104 @@ async function queryRequestableAssets(cmd, client, appId, input) {
|
|
|
149
173
|
}
|
|
150
174
|
}
|
|
151
175
|
}
|
|
176
|
+
const RESOURCE_ROLES_QUERY = (0, graphql_1.graphql)(`
|
|
177
|
+
query ResourceAccessLevels($resourceId: ResourceId!) {
|
|
178
|
+
accessLevels(input: {
|
|
179
|
+
resourceId: $resourceId,
|
|
180
|
+
onlyMine: false,
|
|
181
|
+
}) {
|
|
182
|
+
__typename
|
|
183
|
+
... on ResourceAccessLevelsResult {
|
|
184
|
+
accessLevels {
|
|
185
|
+
__typename
|
|
186
|
+
... on ResourceAccessLevel {
|
|
187
|
+
accessLevelName
|
|
188
|
+
accessLevelRemoteId
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
... on ResourceNotFoundError {
|
|
193
|
+
message
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`);
|
|
198
|
+
async function queryResourceRoles(cmd, client, resourceId) {
|
|
199
|
+
var _a, _b, _c, _d, _e;
|
|
200
|
+
try {
|
|
201
|
+
const resp = await client.query({
|
|
202
|
+
query: RESOURCE_ROLES_QUERY,
|
|
203
|
+
variables: {
|
|
204
|
+
resourceId: resourceId,
|
|
205
|
+
},
|
|
206
|
+
fetchPolicy: "network-only", // to avoid caching
|
|
207
|
+
});
|
|
208
|
+
// no fall through doesn't consider process.exit();
|
|
209
|
+
let x;
|
|
210
|
+
switch (resp.data.accessLevels.__typename) {
|
|
211
|
+
case "ResourceAccessLevelsResult":
|
|
212
|
+
return (_c = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.accessLevels) === null || _b === void 0 ? void 0 : _b.accessLevels) === null || _c === void 0 ? void 0 : _c.map((role) => {
|
|
213
|
+
return {
|
|
214
|
+
name: role.accessLevelName,
|
|
215
|
+
value: {
|
|
216
|
+
name: role.accessLevelName,
|
|
217
|
+
id: role.accessLevelRemoteId,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
case "ResourceNotFoundError":
|
|
222
|
+
x = cmd.error((_e = (_d = resp.data) === null || _d === void 0 ? void 0 : _d.accessLevels) === null || _e === void 0 ? void 0 : _e.message);
|
|
223
|
+
break;
|
|
224
|
+
default:
|
|
225
|
+
cmd.error(resp.error || "Unknown error occurred.");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (error instanceof Error || typeof error === "string") {
|
|
230
|
+
cmd.error(error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const REQUEST_DEFAULTS_QUERY = (0, graphql_1.graphql)(`
|
|
235
|
+
query RequestDefaults(
|
|
236
|
+
$requestedResources: [RequestConfigurationResourceInput!]!
|
|
237
|
+
$requestedGroups: [RequestConfigurationGroupInput!]!
|
|
238
|
+
) {
|
|
239
|
+
requestDefaults(input: {
|
|
240
|
+
requestedResources: $requestedResources,
|
|
241
|
+
requestedGroups: $requestedGroups,
|
|
242
|
+
}
|
|
243
|
+
) {
|
|
244
|
+
durationOptions {
|
|
245
|
+
durationInMinutes
|
|
246
|
+
label
|
|
247
|
+
}
|
|
248
|
+
recommendedDurationInMinutes
|
|
249
|
+
defaultDurationInMinutes
|
|
250
|
+
maxDurationInMinutes
|
|
251
|
+
requireSupportTicket
|
|
252
|
+
reasonOptional
|
|
253
|
+
requesterIsAdmin
|
|
254
|
+
}
|
|
255
|
+
}`);
|
|
256
|
+
async function queryRequestDefaults(cmd, client, requestedResources, requestedGroups) {
|
|
257
|
+
try {
|
|
258
|
+
const resp = await client.query({
|
|
259
|
+
query: REQUEST_DEFAULTS_QUERY,
|
|
260
|
+
variables: {
|
|
261
|
+
requestedResources: requestedResources,
|
|
262
|
+
requestedGroups: requestedGroups,
|
|
263
|
+
},
|
|
264
|
+
fetchPolicy: "network-only", // to avoid caching
|
|
265
|
+
});
|
|
266
|
+
return resp.data.requestDefaults;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error instanceof Error || typeof error === "string") {
|
|
270
|
+
cmd.error(error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
152
274
|
// Helper functions
|
|
153
275
|
async function selectRequestableItems(cmd, client, requestMap) {
|
|
154
276
|
const { App } = await inquirer.prompt([
|
|
@@ -164,11 +286,11 @@ async function selectRequestableItems(cmd, client, requestMap) {
|
|
|
164
286
|
},
|
|
165
287
|
]);
|
|
166
288
|
// Set the app in the requestMap and call choose assets step
|
|
167
|
-
if (!
|
|
168
|
-
requestMap
|
|
289
|
+
if (!(App.id in requestMap)) {
|
|
290
|
+
requestMap[App.id] = {
|
|
169
291
|
appName: App.name,
|
|
170
|
-
assets:
|
|
171
|
-
}
|
|
292
|
+
assets: {},
|
|
293
|
+
};
|
|
172
294
|
}
|
|
173
295
|
await chooseAssets(cmd, client, App.id, requestMap);
|
|
174
296
|
}
|
|
@@ -187,37 +309,52 @@ async function chooseAssets(cmd, client, appId, requestMap) {
|
|
|
187
309
|
return true;
|
|
188
310
|
},
|
|
189
311
|
});
|
|
190
|
-
const entry = requestMap
|
|
312
|
+
const entry = requestMap[appId];
|
|
191
313
|
for (const asset of Assets) {
|
|
192
314
|
if (entry === undefined) {
|
|
193
315
|
throw new Error(`App ${appId} not found in requestMap`);
|
|
194
316
|
}
|
|
195
|
-
if (!
|
|
196
|
-
entry.assets
|
|
317
|
+
if (!(asset.id in entry.assets)) {
|
|
318
|
+
entry.assets[asset.id] = {
|
|
197
319
|
assetName: asset.name,
|
|
198
|
-
roles:
|
|
199
|
-
}
|
|
320
|
+
roles: {},
|
|
321
|
+
};
|
|
200
322
|
}
|
|
201
|
-
await chooseRoles(appId, asset.id, requestMap);
|
|
323
|
+
await chooseRoles(cmd, client, appId, asset.id, requestMap);
|
|
202
324
|
}
|
|
203
325
|
}
|
|
204
|
-
async function chooseRoles(appId, assetId, requestMap) {
|
|
326
|
+
async function chooseRoles(cmd, client, appId, assetId, requestMap) {
|
|
205
327
|
var _a;
|
|
328
|
+
const resourceRoles = (_a = (await queryResourceRoles(cmd, client, assetId))) !== null && _a !== void 0 ? _a : [];
|
|
329
|
+
if (resourceRoles !== undefined &&
|
|
330
|
+
(resourceRoles.length === 0 ||
|
|
331
|
+
(resourceRoles.length === 1 && resourceRoles[0].name === ""))) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
206
334
|
const { roles } = await inquirer.prompt({
|
|
207
335
|
name: "roles",
|
|
208
336
|
type: "checkbox",
|
|
209
337
|
message: `Select one or more roles for ${assetId}:`,
|
|
210
|
-
choices:
|
|
338
|
+
choices: resourceRoles,
|
|
339
|
+
validate: (answer) => {
|
|
340
|
+
if ((resourceRoles === null || resourceRoles === void 0 ? void 0 : resourceRoles.length) > 1 && answer.length < 1) {
|
|
341
|
+
return "You must select at least one role.";
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
},
|
|
211
345
|
});
|
|
212
|
-
const entry = requestMap
|
|
213
|
-
const assetEntry = entry === null || entry === void 0 ? void 0 : entry.assets
|
|
346
|
+
const entry = requestMap[appId];
|
|
347
|
+
const assetEntry = entry === null || entry === void 0 ? void 0 : entry.assets[assetId];
|
|
214
348
|
if (entry === undefined || assetEntry === undefined) {
|
|
215
349
|
throw new Error(`App ${appId} or Asset ${assetId} not found in requestMap`);
|
|
216
350
|
}
|
|
351
|
+
if (!assetEntry.roles) {
|
|
352
|
+
assetEntry.roles = {};
|
|
353
|
+
}
|
|
217
354
|
for (const role of roles) {
|
|
218
|
-
|
|
219
|
-
roleName: role,
|
|
220
|
-
}
|
|
355
|
+
assetEntry.roles[role.id] = {
|
|
356
|
+
roleName: role.name,
|
|
357
|
+
};
|
|
221
358
|
}
|
|
222
359
|
}
|
|
223
360
|
async function doneSelectingAssets() {
|
|
@@ -233,22 +370,84 @@ async function doneSelectingAssets() {
|
|
|
233
370
|
]);
|
|
234
371
|
return submitOrAdd === submitMessage;
|
|
235
372
|
}
|
|
236
|
-
async function
|
|
373
|
+
async function setRequestDefaults(cmd, client, metadata) {
|
|
374
|
+
const requestMap = metadata.requestMap;
|
|
375
|
+
const requestedResources = [];
|
|
376
|
+
const requestedGroups = [];
|
|
377
|
+
for (const appNode of Object.values(requestMap)) {
|
|
378
|
+
for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
|
|
379
|
+
if (assetNode.roles !== undefined) {
|
|
380
|
+
for (const roleId of Object.keys(assetNode.roles)) {
|
|
381
|
+
requestedResources.push({
|
|
382
|
+
resourceId: assetId,
|
|
383
|
+
accessLevelRemoteId: roleId,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const requestDefaults = await queryRequestDefaults(cmd, client, requestedResources, requestedGroups);
|
|
391
|
+
if ((requestDefaults === null || requestDefaults === void 0 ? void 0 : requestDefaults.__typename) === "RequestDefaults") {
|
|
392
|
+
metadata.requestDefaults.durationOptions =
|
|
393
|
+
requestDefaults.durationOptions;
|
|
394
|
+
metadata.requestDefaults.recommendedDurationInMinutes =
|
|
395
|
+
requestDefaults.recommendedDurationInMinutes;
|
|
396
|
+
metadata.requestDefaults.defaultDurationInMinutes =
|
|
397
|
+
requestDefaults.defaultDurationInMinutes;
|
|
398
|
+
metadata.requestDefaults.maxDurationInMinutes =
|
|
399
|
+
requestDefaults.maxDurationInMinutes;
|
|
400
|
+
metadata.requestDefaults.requireSupportTicket =
|
|
401
|
+
requestDefaults.requireSupportTicket;
|
|
402
|
+
metadata.requestDefaults.reasonOptional = requestDefaults.reasonOptional;
|
|
403
|
+
metadata.requestDefaults.requesterIsAdmin =
|
|
404
|
+
requestDefaults.requesterIsAdmin;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (_a) {
|
|
408
|
+
cmd.error("Error fetching request defaults.");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function promptForReason(metadata) {
|
|
237
412
|
return await inquirer.prompt([
|
|
238
413
|
{
|
|
239
414
|
name: "reason",
|
|
240
415
|
message: "I need access to this because...",
|
|
241
416
|
type: "input",
|
|
417
|
+
validate: (answer) => {
|
|
418
|
+
if (metadata.requestDefaults.reasonOptional && answer.length < 1) {
|
|
419
|
+
return "A reason for requesting these assets is required.";
|
|
420
|
+
}
|
|
421
|
+
return true;
|
|
422
|
+
},
|
|
242
423
|
},
|
|
243
424
|
]);
|
|
244
425
|
}
|
|
245
|
-
async function promptForExpiration() {
|
|
426
|
+
async function promptForExpiration(metadata) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const durations = ((_b = (_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.durationOptions) === null || _b === void 0 ? void 0 : _b.map((option) => {
|
|
429
|
+
return {
|
|
430
|
+
name: option.durationInMinutes ===
|
|
431
|
+
metadata.requestDefaults.recommendedDurationInMinutes
|
|
432
|
+
? `${option.label} (Recommended)`
|
|
433
|
+
: option.label,
|
|
434
|
+
value: {
|
|
435
|
+
label: option.label,
|
|
436
|
+
durationInMinutes: option.durationInMinutes,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
})) || [];
|
|
440
|
+
// TODO: Sort durations by minutes
|
|
441
|
+
// durations = durations.sort(
|
|
442
|
+
// durations.filter((option) => option.value.durationInMinutes),
|
|
443
|
+
// );
|
|
246
444
|
return await inquirer.prompt([
|
|
247
445
|
{
|
|
248
446
|
name: "expiration",
|
|
249
447
|
message: "When should access expire?",
|
|
250
448
|
type: "list",
|
|
251
|
-
choices:
|
|
449
|
+
choices: durations,
|
|
450
|
+
pageSize: 15,
|
|
252
451
|
},
|
|
253
452
|
]);
|
|
254
453
|
}
|
package/lib/utils/displays.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { ApolloQueryResult } from "@apollo/client";
|
|
1
2
|
import type { Command } from "@oclif/core/lib/command";
|
|
3
|
+
import type { GetRequestQuery } from "../graphql/graphql";
|
|
2
4
|
import type { RequestMap } from "../lib/requests";
|
|
3
5
|
export declare function headerMessage(cmd: Command): void;
|
|
4
6
|
export declare function treeifyRequestMap(requestMap: RequestMap): string;
|
|
5
7
|
export declare function displayFinalRequestSummary(cmd: Command, requestMap: RequestMap, reason: string, expiration: string): void;
|
|
8
|
+
export declare function displayRequestDetails(cmd: Command, requestResp: ApolloQueryResult<GetRequestQuery>): void;
|
package/lib/utils/displays.js
CHANGED
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.headerMessage = headerMessage;
|
|
4
4
|
exports.treeifyRequestMap = treeifyRequestMap;
|
|
5
5
|
exports.displayFinalRequestSummary = displayFinalRequestSummary;
|
|
6
|
+
exports.displayRequestDetails = displayRequestDetails;
|
|
7
|
+
const chalk_1 = require("chalk");
|
|
8
|
+
const prettyjson_1 = require("prettyjson");
|
|
6
9
|
const treeify = require("object-treeify");
|
|
7
10
|
const Table = require("cli-table3");
|
|
8
11
|
const tableStyle = {
|
|
@@ -32,18 +35,18 @@ function treeifyRequestMap(requestMap) {
|
|
|
32
35
|
const requestTree = {};
|
|
33
36
|
// Create a tree structure from the requestMap
|
|
34
37
|
// Iterate over apps
|
|
35
|
-
for (const [_appId, appNode] of
|
|
38
|
+
for (const [_appId, appNode] of Object.entries(requestMap)) {
|
|
36
39
|
const appKey = `🔧${appNode.appName}`;
|
|
37
40
|
requestTree[appKey] = {}; // Initialize the app key
|
|
38
41
|
// Iterate over assets
|
|
39
|
-
for (const [_assetId, assetNode] of appNode.assets
|
|
42
|
+
for (const [_assetId, assetNode] of Object.entries(appNode.assets)) {
|
|
40
43
|
const assetKey = `📦${assetNode.assetName}`;
|
|
41
44
|
if (assetNode.roles !== undefined) {
|
|
42
45
|
// If no roles were previously selected
|
|
43
46
|
requestTree[appKey][assetKey] = {}; // Initialize the asset key
|
|
44
47
|
// Iterate over roles
|
|
45
|
-
for (const [
|
|
46
|
-
requestTree[appKey][assetKey][roleName] = null; // Initialize the role key
|
|
48
|
+
for (const [_roleId, roleNode] of Object.entries(assetNode.roles)) {
|
|
49
|
+
requestTree[appKey][assetKey][roleNode.roleName] = null; // Initialize the role key
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
else {
|
|
@@ -63,3 +66,64 @@ function displayFinalRequestSummary(cmd, requestMap, reason, expiration) {
|
|
|
63
66
|
table.push(["Requested Assets", requestedAssets], ["Reason", reason], ["Expiration", expiration]);
|
|
64
67
|
cmd.log(table.toString());
|
|
65
68
|
}
|
|
69
|
+
function displayRequestDetails(cmd, requestResp) {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
switch (requestResp.data.request.__typename) {
|
|
72
|
+
case "RequestResult": {
|
|
73
|
+
cmd.log(`REQUEST ${requestResp.data.request.request.id}`);
|
|
74
|
+
const status = requestResp.data.request.request.status;
|
|
75
|
+
// If status is "PENDING" or "APPROVED", display it
|
|
76
|
+
// If status is "DENIED", display it in red
|
|
77
|
+
if (status === "PENDING") {
|
|
78
|
+
cmd.log(`Status: ${chalk_1.default.yellowBright(status)}`);
|
|
79
|
+
}
|
|
80
|
+
else if (status === "APPROVED") {
|
|
81
|
+
cmd.log(`Status: ${chalk_1.default.greenBright(status)}`);
|
|
82
|
+
}
|
|
83
|
+
else if (status === "DENIED") {
|
|
84
|
+
cmd.log(`Status: ${chalk_1.default.redBright(status)}`);
|
|
85
|
+
}
|
|
86
|
+
else if (status === "CANCELED") {
|
|
87
|
+
cmd.log(`Status: ${chalk_1.default.redBright(status)}`);
|
|
88
|
+
}
|
|
89
|
+
// Request users "Requester: <requester> -> Target: <targetUser>"
|
|
90
|
+
const requester = (_a = requestResp.data.request.request.requester) === null || _a === void 0 ? void 0 : _a.displayName;
|
|
91
|
+
const targetUser = (_b = requestResp.data.request.request.targetUser) === null || _b === void 0 ? void 0 : _b.displayName;
|
|
92
|
+
if (requester && targetUser) {
|
|
93
|
+
cmd.log(`Requester: ${requester} -> Target: ${targetUser}`);
|
|
94
|
+
}
|
|
95
|
+
const durationInMinutes = requestResp.data.request.request.durationInMinutes;
|
|
96
|
+
if (durationInMinutes) {
|
|
97
|
+
const days = Math.floor(durationInMinutes / 1440);
|
|
98
|
+
const remainingMinutes = durationInMinutes % 1440;
|
|
99
|
+
const hours = Math.floor(remainingMinutes / 60);
|
|
100
|
+
const minutes = remainingMinutes % 60;
|
|
101
|
+
let durationStr = "";
|
|
102
|
+
if (days > 0)
|
|
103
|
+
durationStr += `${days}d`;
|
|
104
|
+
if (hours > 0)
|
|
105
|
+
durationStr += `${hours}h`;
|
|
106
|
+
if (minutes > 0)
|
|
107
|
+
durationStr += `${minutes}m`;
|
|
108
|
+
if (durationStr === "")
|
|
109
|
+
durationStr = `${durationInMinutes}m`;
|
|
110
|
+
durationStr += ` (${durationInMinutes}m)`;
|
|
111
|
+
cmd.log(`Duration: ${durationStr}`);
|
|
112
|
+
}
|
|
113
|
+
const reason = requestResp.data.request.request.reason;
|
|
114
|
+
if (reason) {
|
|
115
|
+
cmd.log(`Reason: "${reason}"`);
|
|
116
|
+
}
|
|
117
|
+
const requestedResources = requestResp.data.request.request.requestedResources;
|
|
118
|
+
const requestedGroups = requestResp.data.request.request.requestedGroups;
|
|
119
|
+
if (requestedResources && requestedResources.length > 0) {
|
|
120
|
+
cmd.log("Requested Resources:");
|
|
121
|
+
cmd.log((0, prettyjson_1.render)(requestedResources));
|
|
122
|
+
}
|
|
123
|
+
if (requestedGroups && requestedGroups.length > 0) {
|
|
124
|
+
cmd.log("Requested Groups:");
|
|
125
|
+
cmd.log((0, prettyjson_1.render)(requestedGroups));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -620,7 +620,30 @@
|
|
|
620
620
|
"aliases": [],
|
|
621
621
|
"args": {},
|
|
622
622
|
"description": "Lists access requests",
|
|
623
|
-
"flags": {
|
|
623
|
+
"flags": {
|
|
624
|
+
"help": {
|
|
625
|
+
"char": "h",
|
|
626
|
+
"description": "Show CLI help.",
|
|
627
|
+
"name": "help",
|
|
628
|
+
"allowNo": false,
|
|
629
|
+
"type": "boolean"
|
|
630
|
+
},
|
|
631
|
+
"id": {
|
|
632
|
+
"char": "i",
|
|
633
|
+
"description": "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
|
|
634
|
+
"name": "id",
|
|
635
|
+
"hasDynamicHelp": false,
|
|
636
|
+
"multiple": false,
|
|
637
|
+
"type": "option"
|
|
638
|
+
},
|
|
639
|
+
"verbose": {
|
|
640
|
+
"char": "v",
|
|
641
|
+
"description": "Enable verbose output",
|
|
642
|
+
"name": "verbose",
|
|
643
|
+
"allowNo": false,
|
|
644
|
+
"type": "boolean"
|
|
645
|
+
}
|
|
646
|
+
},
|
|
624
647
|
"hasDynamicHelp": false,
|
|
625
648
|
"hidden": true,
|
|
626
649
|
"hiddenAliases": [],
|
|
@@ -639,12 +662,33 @@
|
|
|
639
662
|
]
|
|
640
663
|
},
|
|
641
664
|
"request:list": {
|
|
642
|
-
"aliases": [
|
|
643
|
-
"request:ls"
|
|
644
|
-
],
|
|
665
|
+
"aliases": [],
|
|
645
666
|
"args": {},
|
|
646
|
-
"description": "Lists access requests",
|
|
647
|
-
"flags": {
|
|
667
|
+
"description": "Lists your recent outgoing access requests. \n--pageSize flag sets number of requests to be returned. Defaults to 10 requests. \n--showPendingOnly flag will show only pending requests. Defaults to false.",
|
|
668
|
+
"flags": {
|
|
669
|
+
"help": {
|
|
670
|
+
"char": "h",
|
|
671
|
+
"description": "Show CLI help.",
|
|
672
|
+
"name": "help",
|
|
673
|
+
"allowNo": false,
|
|
674
|
+
"type": "boolean"
|
|
675
|
+
},
|
|
676
|
+
"id": {
|
|
677
|
+
"char": "i",
|
|
678
|
+
"description": "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
|
|
679
|
+
"name": "id",
|
|
680
|
+
"hasDynamicHelp": false,
|
|
681
|
+
"multiple": false,
|
|
682
|
+
"type": "option"
|
|
683
|
+
},
|
|
684
|
+
"pageSize": {
|
|
685
|
+
"description": "Sets number of requests to be returned. Defaults to 10 requests.",
|
|
686
|
+
"name": "pageSize",
|
|
687
|
+
"hasDynamicHelp": false,
|
|
688
|
+
"multiple": false,
|
|
689
|
+
"type": "option"
|
|
690
|
+
}
|
|
691
|
+
},
|
|
648
692
|
"hasDynamicHelp": false,
|
|
649
693
|
"hidden": true,
|
|
650
694
|
"hiddenAliases": [],
|
|
@@ -909,5 +953,5 @@
|
|
|
909
953
|
]
|
|
910
954
|
}
|
|
911
955
|
},
|
|
912
|
-
"version": "3.1.
|
|
956
|
+
"version": "3.1.1-beta.4a79f20"
|
|
913
957
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opal-security",
|
|
3
3
|
"description": "Opal allows you to centrally manage access to all of your sensitive systems.",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.1-beta.4a79f20",
|
|
5
5
|
"author": "Stephen Cobbe",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opal": "./bin/run"
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@types/lodash": "^4.14.169",
|
|
40
40
|
"@types/node": "^22.14.0",
|
|
41
41
|
"@types/prettyjson": "0.0.29",
|
|
42
|
+
"@types/react": "^19.1.4",
|
|
42
43
|
"@types/semver": "^7.3.8",
|
|
43
44
|
"better-npm-audit": "^3.7.3",
|
|
44
45
|
"get-graphql-schema": "^2.1.2",
|