-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathAccess.CLI.fs
More file actions
833 lines (716 loc) · 39.9 KB
/
Access.CLI.fs
File metadata and controls
833 lines (716 loc) · 39.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
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
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
namespace Grace.CLI.Command
open FSharpPlus
open Grace.CLI.Common
open Grace.CLI.Common.Validations
open Grace.CLI.Services
open Grace.CLI.Text
open Grace.SDK
open Grace.Shared
open Grace.Shared.Client.Configuration
open Grace.Shared.Parameters.Access
open Grace.Shared.Parameters.Common
open Grace.Shared.Utilities
open Grace.Shared.Validation.Errors
open Grace.Types.Authorization
open Grace.Types.Types
open Spectre.Console
open Spectre.Console.Json
open System
open System.Collections.Generic
open System.CommandLine
open System.CommandLine.Invocation
open System.CommandLine.Parsing
open System.Threading
open System.Threading.Tasks
module Access =
module private Options =
let ownerId =
new Option<OwnerId>(
OptionName.OwnerId,
Required = false,
Description = "The owner ID <Guid>.",
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = (fun _ -> OwnerId.Empty)
)
let organizationId =
new Option<OrganizationId>(
OptionName.OrganizationId,
Required = false,
Description = "The organization ID <Guid>.",
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = (fun _ -> OrganizationId.Empty)
)
let repositoryId =
new Option<RepositoryId>(
OptionName.RepositoryId,
[| "-r" |],
Required = false,
Description = "The repository ID <Guid>.",
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = (fun _ -> RepositoryId.Empty)
)
let branchId =
new Option<BranchId>(
OptionName.BranchId,
Required = false,
Description = "The branch ID <Guid>.",
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = (fun _ -> BranchId.Empty)
)
let principalTypeRequired =
(new Option<string>(
OptionName.PrincipalType,
Required = true,
Description = "The principal type (User, Group, Service).",
Arity = ArgumentArity.ExactlyOne
))
.AcceptOnlyFromAmong(listCases<PrincipalType> ())
let principalIdRequired =
new Option<string>(OptionName.PrincipalId, Required = true, Description = "The principal identifier.", Arity = ArgumentArity.ExactlyOne)
let principalTypeOptional =
(new Option<string>(
OptionName.PrincipalType,
Required = false,
Description = "Optional principal type filter (User, Group, Service).",
Arity = ArgumentArity.ZeroOrOne
))
.AcceptOnlyFromAmong(listCases<PrincipalType> ())
let principalIdOptional =
new Option<string>(OptionName.PrincipalId, Required = false, Description = "Optional principal identifier filter.", Arity = ArgumentArity.ZeroOrOne)
let scopeKindRequired =
(new Option<string>(
OptionName.ScopeKind,
Required = true,
Description = "Scope kind (system, owner, org, repo, branch).",
Arity = ArgumentArity.ExactlyOne
))
.AcceptOnlyFromAmong([| "system"; "owner"; "org"; "organization"; "repo"; "repository"; "branch" |])
let roleId = new Option<string>(OptionName.RoleId, Required = true, Description = "Role identifier.", Arity = ArgumentArity.ExactlyOne)
let source = new Option<string>(OptionName.Source, Required = false, Description = "Optional role assignment source.", Arity = ArgumentArity.ZeroOrOne)
let sourceDetail =
new Option<string>(
OptionName.SourceDetail,
Required = false,
Description = "Optional role assignment source detail.",
Arity = ArgumentArity.ZeroOrOne
)
let pathRequired = new Option<string>(OptionName.Path, Required = true, Description = "Repository relative path.", Arity = ArgumentArity.ExactlyOne)
let pathOptional =
new Option<string>(OptionName.Path, Required = false, Description = "Optional repository relative path filter.", Arity = ArgumentArity.ZeroOrOne)
let claim =
new Option<string[]>(
OptionName.Claim,
Required = true,
Description = "Claim to grant permissions for (repeatable).",
Arity = ArgumentArity.OneOrMore
)
let directoryPermission =
new Option<string[]>(
OptionName.DirectoryPermission,
Required = true,
Description = "Directory permission to apply (repeatable; match --claim order).",
Arity = ArgumentArity.OneOrMore
)
let operationRequired =
(new Option<string>(OptionName.Operation, Required = true, Description = "Operation to check.", Arity = ArgumentArity.ExactlyOne))
.AcceptOnlyFromAmong(listCases<Operation> ())
let resourceKindRequired =
(new Option<string>(
OptionName.ResourceKind,
Required = true,
Description = "Resource kind (system, owner, org, repo, branch, path).",
Arity = ArgumentArity.ExactlyOne
))
.AcceptOnlyFromAmong(
[| "system"
"owner"
"org"
"organization"
"repo"
"repository"
"branch"
"path" |]
)
let private formatScope (scope: Scope) =
match scope with
| Scope.System -> "System"
| Scope.Owner ownerId -> $"Owner:{ownerId}"
| Scope.Organization(ownerId, organizationId) -> $"Org:{ownerId}/{organizationId}"
| Scope.Repository(ownerId, organizationId, repositoryId) -> $"Repo:{ownerId}/{organizationId}/{repositoryId}"
| Scope.Branch(ownerId, organizationId, repositoryId, branchId) -> $"Branch:{ownerId}/{organizationId}/{repositoryId}/{branchId}"
let private formatClaimPermissions (permissions: IEnumerable<ClaimPermission>) =
permissions
|> Seq.map (fun permission -> $"{permission.Claim}:{permission.DirectoryPermission}")
|> String.concat ", "
let private renderAssignments (parseResult: ParseResult) (assignments: RoleAssignment list) =
if parseResult |> hasOutput then
if Seq.isEmpty assignments then
logToAnsiConsole Colors.Highlighted "No role assignments found."
else
let table = Table(Border = TableBorder.DoubleEdge)
table.AddColumns(
[| TableColumn($"[{Colors.Important}]Principal[/]")
TableColumn($"[{Colors.Important}]Scope[/]")
TableColumn($"[{Colors.Important}]Role[/]")
TableColumn($"[{Colors.Important}]Source[/]")
TableColumn($"[{Colors.Important}]Created[/]") |]
)
|> ignore
for assignment in assignments do
let principalText = $"{assignment.Principal.PrincipalType}:{assignment.Principal.PrincipalId}"
let sourceDetail = assignment.SourceDetail |> Option.defaultValue ""
let sourceText =
if String.IsNullOrWhiteSpace sourceDetail then
assignment.Source
else
$"{assignment.Source} ({sourceDetail})"
table.AddRow(
$"[{Colors.Deemphasized}]{principalText}[/]",
formatScope assignment.Scope,
assignment.RoleId,
sourceText,
formatInstantExtended assignment.CreatedAt
)
|> ignore
AnsiConsole.Write(table)
let private renderRoles (parseResult: ParseResult) (roles: RoleDefinition list) =
if parseResult |> hasOutput then
if Seq.isEmpty roles then
logToAnsiConsole Colors.Highlighted "No roles found."
else
let table = Table(Border = TableBorder.DoubleEdge)
table.AddColumns(
[| TableColumn($"[{Colors.Important}]Role[/]")
TableColumn($"[{Colors.Important}]Applies To[/]")
TableColumn($"[{Colors.Important}]Operations[/]") |]
)
|> ignore
for role in roles do
let appliesTo = role.AppliesTo |> Seq.sort |> String.concat ", "
let operations = role.AllowedOperations |> Seq.map string |> Seq.sort |> String.concat ", "
table.AddRow($"[{Colors.Deemphasized}]{role.RoleId}[/]", appliesTo, operations)
|> ignore
AnsiConsole.Write(table)
let private renderPathPermissions (parseResult: ParseResult) (pathPermissions: PathPermission list) =
if parseResult |> hasOutput then
if Seq.isEmpty pathPermissions then
logToAnsiConsole Colors.Highlighted "No path permissions found."
else
let table = Table(Border = TableBorder.DoubleEdge)
table.AddColumns(
[| TableColumn($"[{Colors.Important}]Path[/]")
TableColumn($"[{Colors.Important}]Claims[/]") |]
)
|> ignore
for pathPermission in pathPermissions do
let claims = formatClaimPermissions pathPermission.Permissions
table.AddRow($"[{Colors.Deemphasized}]{pathPermission.Path}[/]", claims)
|> ignore
AnsiConsole.Write(table)
let private renderPermissionCheck (parseResult: ParseResult) (result: PermissionCheckResult) =
if parseResult |> hasOutput then
match result with
| Allowed reason -> AnsiConsole.MarkupLine($"[{Colors.Highlighted}]Allowed[/]: {Markup.Escape(reason)}")
| Denied reason -> AnsiConsole.MarkupLine($"[{Colors.Error}]Denied[/]: {Markup.Escape(reason)}")
let private validateClaimPermissions (parseResult: ParseResult) =
let correlationId = getCorrelationId parseResult
let claims = parseResult.GetValue(Options.claim)
let permissions = parseResult.GetValue(Options.directoryPermission)
if isNull claims || claims.Length = 0 then
Error(GraceError.Create "At least one --claim value is required." correlationId)
elif isNull permissions || permissions.Length = 0 then
Error(GraceError.Create "At least one --dir-perm value is required." correlationId)
elif claims.Length <> permissions.Length then
Error(GraceError.Create "--claim and --dir-perm counts must match." correlationId)
else
let invalid =
permissions
|> Array.tryFind (fun value -> discriminatedUnionFromString<DirectoryPermission> value |> Option.isNone)
match invalid with
| Some value -> Error(GraceError.Create $"Invalid DirectoryPermission '{value}'." correlationId)
| None -> Ok parseResult
let private validatePrincipalFilter (parseResult: ParseResult) (principalType: string option) (principalId: string option) =
let correlationId = getCorrelationId parseResult
let principalTypeValue = principalType |> Option.defaultValue String.Empty
let principalIdValue = principalId |> Option.defaultValue String.Empty
if
String.IsNullOrWhiteSpace principalTypeValue
&& String.IsNullOrWhiteSpace principalIdValue
then
Ok parseResult
elif
String.IsNullOrWhiteSpace principalTypeValue
|| String.IsNullOrWhiteSpace principalIdValue
then
Error(GraceError.Create "PrincipalType and PrincipalId must be provided together." correlationId)
else
Ok parseResult
let private validatePathResource (parseResult: ParseResult) (resourceKind: string) (pathValue: string) =
if
resourceKind.Equals("path", StringComparison.InvariantCultureIgnoreCase)
&& String.IsNullOrWhiteSpace pathValue
then
Error(GraceError.Create "Path is required for Path resources." (getCorrelationId parseResult))
else
Ok parseResult
type GrantRole() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let validateIncomingParameters = parseResult |> CommonValidations
match validateIncomingParameters with
| Ok _ ->
let parameters =
GrantRoleParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
BranchId = graceIds.BranchIdString,
PrincipalType = parseResult.GetValue(Options.principalTypeRequired),
PrincipalId = parseResult.GetValue(Options.principalIdRequired),
ScopeKind = parseResult.GetValue(Options.scopeKindRequired),
RoleId = parseResult.GetValue(Options.roleId),
Source = parseResult.GetValue(Options.source),
SourceDetail = parseResult.GetValue(Options.sourceDetail),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.GrantRole(parameters)
t0.Increment(100.0)
return response
})
else
Access.GrantRole(parameters)
match result with
| Ok graceReturnValue ->
renderAssignments parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type RevokeRole() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let validateIncomingParameters = parseResult |> CommonValidations
match validateIncomingParameters with
| Ok _ ->
let parameters =
RevokeRoleParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
BranchId = graceIds.BranchIdString,
PrincipalType = parseResult.GetValue(Options.principalTypeRequired),
PrincipalId = parseResult.GetValue(Options.principalIdRequired),
ScopeKind = parseResult.GetValue(Options.scopeKindRequired),
RoleId = parseResult.GetValue(Options.roleId),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.RevokeRole(parameters)
t0.Increment(100.0)
return response
})
else
Access.RevokeRole(parameters)
match result with
| Ok graceReturnValue ->
renderAssignments parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type ListRoleAssignments() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let principalType = parseResult.GetValue(Options.principalTypeOptional) |> Option.ofObj
let principalId = parseResult.GetValue(Options.principalIdOptional) |> Option.ofObj
let validateIncomingParameters =
parseResult
|> CommonValidations
>>= (fun _ -> validatePrincipalFilter parseResult principalType principalId)
match validateIncomingParameters with
| Ok _ ->
let parameters =
ListRoleAssignmentsParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
BranchId = graceIds.BranchIdString,
PrincipalType = (principalType |> Option.defaultValue ""),
PrincipalId = (principalId |> Option.defaultValue ""),
ScopeKind = parseResult.GetValue(Options.scopeKindRequired),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.ListRoleAssignments(parameters)
t0.Increment(100.0)
return response
})
else
Access.ListRoleAssignments(parameters)
match result with
| Ok graceReturnValue ->
renderAssignments parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type UpsertPathPermission() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let validateIncomingParameters = parseResult |> CommonValidations >>= validateClaimPermissions
match validateIncomingParameters with
| Ok _ ->
let claims = parseResult.GetValue(Options.claim)
let permissions = parseResult.GetValue(Options.directoryPermission)
let claimPermissions = List<ClaimPermissionParameters>()
for index in 0 .. claims.Length - 1 do
claimPermissions.Add(ClaimPermissionParameters(Claim = claims[index], DirectoryPermission = permissions[index]))
let parameters =
UpsertPathPermissionParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
Path = parseResult.GetValue(Options.pathRequired),
ClaimPermissions = claimPermissions,
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.UpsertPathPermission(parameters)
t0.Increment(100.0)
return response
})
else
Access.UpsertPathPermission(parameters)
match result with
| Ok graceReturnValue ->
renderPathPermissions parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type RemovePathPermission() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let validateIncomingParameters = parseResult |> CommonValidations
match validateIncomingParameters with
| Ok _ ->
let parameters =
RemovePathPermissionParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
Path = parseResult.GetValue(Options.pathRequired),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.RemovePathPermission(parameters)
t0.Increment(100.0)
return response
})
else
Access.RemovePathPermission(parameters)
match result with
| Ok graceReturnValue ->
renderPathPermissions parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type ListPathPermissions() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let validateIncomingParameters = parseResult |> CommonValidations
match validateIncomingParameters with
| Ok _ ->
let parameters =
ListPathPermissionsParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
Path =
(parseResult.GetValue(Options.pathOptional)
|> Option.ofObj
|> Option.defaultValue ""),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.ListPathPermissions(parameters)
t0.Increment(100.0)
return response
})
else
Access.ListPathPermissions(parameters)
match result with
| Ok graceReturnValue ->
renderPathPermissions parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type CheckPermission() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let graceIds = parseResult |> getNormalizedIdsAndNames
let resourceKind = parseResult.GetValue(Options.resourceKindRequired)
let pathValue =
parseResult.GetValue(Options.pathOptional)
|> Option.ofObj
|> Option.defaultValue ""
let principalType = parseResult.GetValue(Options.principalTypeOptional) |> Option.ofObj
let principalId = parseResult.GetValue(Options.principalIdOptional) |> Option.ofObj
let validateIncomingParameters =
parseResult
|> CommonValidations
>>= (fun _ -> validatePrincipalFilter parseResult principalType principalId)
>>= (fun _ -> validatePathResource parseResult resourceKind pathValue)
match validateIncomingParameters with
| Ok _ ->
let parameters =
CheckPermissionParameters(
OwnerId = graceIds.OwnerIdString,
OrganizationId = graceIds.OrganizationIdString,
RepositoryId = graceIds.RepositoryIdString,
BranchId = graceIds.BranchIdString,
Operation = parseResult.GetValue(Options.operationRequired),
ResourceKind = resourceKind,
Path = pathValue,
PrincipalType = (principalType |> Option.defaultValue ""),
PrincipalId = (principalId |> Option.defaultValue ""),
CorrelationId = getCorrelationId parseResult
)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Checking permission.[/]")
let! response = Access.CheckPermission(parameters)
t0.Increment(100.0)
return response
})
else
Access.CheckPermission(parameters)
match result with
| Ok graceReturnValue ->
renderPermissionCheck parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
| Error error -> return Error error |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
type ListRoles() =
inherit AsynchronousCommandLineAction()
override _.InvokeAsync(parseResult: ParseResult, cancellationToken: CancellationToken) : Task<int> =
task {
try
if parseResult |> verbose then printParseResult parseResult
let parameters = CommonParameters(CorrelationId = getCorrelationId parseResult)
let! result =
if parseResult |> hasOutput then
progress
.Columns(progressColumns)
.StartAsync(fun progressContext ->
task {
let t0 = progressContext.AddTask($"[{Color.DodgerBlue1}]Sending command to the server.[/]")
let! response = Access.ListRoles(parameters)
t0.Increment(100.0)
return response
})
else
Access.ListRoles(parameters)
match result with
| Ok graceReturnValue ->
renderRoles parseResult graceReturnValue.ReturnValue
return result |> renderOutput parseResult
| Error error ->
logToAnsiConsole Colors.Error (Markup.Escape($"{error}"))
return result |> renderOutput parseResult
with ex ->
return
renderOutput
parseResult
(GraceResult.Error(GraceError.Create $"{Utilities.ExceptionResponse.Create ex}" (parseResult |> getCorrelationId)))
}
let Build =
let addScopeOptions (command: Command) =
command
|> addOption Options.ownerId
|> addOption Options.organizationId
|> addOption Options.repositoryId
|> addOption Options.branchId
let accessCommand = new Command("access", Description = "Manages access control and permissions.")
let grantRoleCommand =
new Command("grant-role", Description = "Grants a role to a principal at a scope.")
|> addScopeOptions
|> addOption Options.scopeKindRequired
|> addOption Options.principalTypeRequired
|> addOption Options.principalIdRequired
|> addOption Options.roleId
|> addOption Options.source
|> addOption Options.sourceDetail
grantRoleCommand.Action <- new GrantRole()
accessCommand.Subcommands.Add(grantRoleCommand)
let revokeRoleCommand =
new Command("revoke-role", Description = "Revokes a role from a principal at a scope.")
|> addScopeOptions
|> addOption Options.scopeKindRequired
|> addOption Options.principalTypeRequired
|> addOption Options.principalIdRequired
|> addOption Options.roleId
revokeRoleCommand.Action <- new RevokeRole()
accessCommand.Subcommands.Add(revokeRoleCommand)
let listRoleAssignmentsCommand =
new Command("list-role-assignments", Description = "Lists role assignments at a scope.")
|> addScopeOptions
|> addOption Options.scopeKindRequired
|> addOption Options.principalTypeOptional
|> addOption Options.principalIdOptional
listRoleAssignmentsCommand.Action <- new ListRoleAssignments()
accessCommand.Subcommands.Add(listRoleAssignmentsCommand)
let upsertPathPermissionCommand =
new Command("upsert-path-permission", Description = "Upserts repository path permissions.")
|> addScopeOptions
|> addOption Options.pathRequired
|> addOption Options.claim
|> addOption Options.directoryPermission
upsertPathPermissionCommand.Action <- new UpsertPathPermission()
accessCommand.Subcommands.Add(upsertPathPermissionCommand)
let removePathPermissionCommand =
new Command("remove-path-permission", Description = "Removes repository path permissions.")
|> addScopeOptions
|> addOption Options.pathRequired
removePathPermissionCommand.Action <- new RemovePathPermission()
accessCommand.Subcommands.Add(removePathPermissionCommand)
let listPathPermissionsCommand =
new Command("list-path-permissions", Description = "Lists repository path permissions.")
|> addScopeOptions
|> addOption Options.pathOptional
listPathPermissionsCommand.Action <- new ListPathPermissions()
accessCommand.Subcommands.Add(listPathPermissionsCommand)
let checkPermissionCommand =
new Command("check", Description = "Checks a permission for the current or specified principal.")
|> addScopeOptions
|> addOption Options.operationRequired
|> addOption Options.resourceKindRequired
|> addOption Options.pathOptional
|> addOption Options.principalTypeOptional
|> addOption Options.principalIdOptional
checkPermissionCommand.Action <- new CheckPermission()
accessCommand.Subcommands.Add(checkPermissionCommand)
let listRolesCommand = new Command("list-roles", Description = "Lists available roles.")
listRolesCommand.Action <- new ListRoles()
accessCommand.Subcommands.Add(listRolesCommand)
accessCommand