Skip to content

Commit 3fc36f6

Browse files
authored
Merge pull request #127 from StackVista/stac-24194
Stac-24194: Add cli commands to retrieve permissions and subjects by source
2 parents 7669432 + d292a89 commit 3fc36f6

87 files changed

Lines changed: 12351 additions & 795 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/rbac/flags.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package rbac
22

3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
38
const (
49
Subject = "subject"
10+
Source = "source"
511
Permission = "permission"
612
Resource = "resource"
713
Scope = "scope"
@@ -21,3 +27,8 @@ const (
2127

2228
DefaultResource = "system"
2329
)
30+
31+
var (
32+
SourceChoices = []string{"static", "observability", "kubernetes"}
33+
SourceUsage = "Get the subject from a specific authorization source" + fmt.Sprintf(" (must be { %s })", strings.Join(SourceChoices, " | "))
34+
)

cmd/rbac/rbac_describe_permissions.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package rbac
22

33
import (
4+
"fmt"
45
"sort"
6+
"strings"
57

68
"github.com/spf13/cobra"
79
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
810
"github.com/stackvista/stackstate-cli/internal/common"
911
"github.com/stackvista/stackstate-cli/internal/di"
1012
"github.com/stackvista/stackstate-cli/internal/printer"
13+
"github.com/stackvista/stackstate-cli/pkg/pflags"
1114
)
1215

1316
type DescribePermissionsArgs struct {
1417
Subject string
1518
Permission string
1619
Resource string
20+
Source string
1721
}
1822

1923
func DescribePermissionsCommand(deps *di.Deps) *cobra.Command {
@@ -35,6 +39,7 @@ sts rbac describe-permissions --subject my-team --permission access-view`,
3539

3640
cmd.Flags().StringVar(&args.Permission, Permission, "", PermissionDescribeUsage)
3741
cmd.Flags().StringVar(&args.Resource, Resource, "", ResourceDescribeUsage)
42+
pflags.EnumVar(cmd.Flags(), &args.Source, Source, "", SourceChoices, SourceUsage)
3843

3944
return cmd
4045
}
@@ -46,37 +51,64 @@ func RunDescribePermissionsCommand(args *DescribePermissionsArgs) di.CmdWithApiF
4651
api *stackstate_api.APIClient,
4752
serverInfo *stackstate_api.ServerInfo,
4853
) common.CLIError {
49-
description, resp, err := describePermissions(cli, api, args.Subject, args.Permission, args.Resource).Execute()
54+
description, resp, err := describePermissions(cli, api, args.Subject, args.Permission, args.Resource, args.Source).Execute()
5055

5156
if err != nil {
5257
return common.NewResponseError(err, resp)
5358
}
5459

60+
sourceStrings := make([]string, 0)
61+
if description.FromSources != nil {
62+
for _, s := range description.FromSources {
63+
sourceStrings = append(sourceStrings, string(s))
64+
}
65+
} else {
66+
sourceStrings = nil
67+
}
68+
5569
if cli.IsJson() {
56-
cli.Printer.PrintJson(map[string]interface{}{
57-
"subject": description.SubjectHandle,
58-
"permissions": description.Permissions,
59-
})
70+
if sourceStrings != nil {
71+
cli.Printer.PrintJson(map[string]interface{}{
72+
"subject": description.SubjectHandle,
73+
"permissions": description.Permissions,
74+
"sources": sourceStrings,
75+
})
76+
} else {
77+
cli.Printer.PrintJson(map[string]interface{}{
78+
"subject": description.SubjectHandle,
79+
"permissions": description.Permissions,
80+
})
81+
}
6082
} else {
61-
printPermissionsTable(cli, description.Permissions)
83+
printPermissionsTable(cli, sourceStrings, description.Permissions)
6284
}
6385

6486
return nil
6587
}
6688
}
6789

68-
func describePermissions(cli *di.Deps, api *stackstate_api.APIClient, subject string, permission string, resource string) stackstate_api.ApiDescribePermissionsRequest {
90+
func describePermissions(cli *di.Deps, api *stackstate_api.APIClient, subject string, permission string, resource string, source string) stackstate_api.ApiDescribePermissionsRequest {
6991
request := api.PermissionsApi.DescribePermissions(cli.Context, subject)
7092
if permission != "" {
7193
request = request.Permission(permission)
7294
}
7395
if resource != "" {
7496
request = request.Resource(resource)
7597
}
98+
if source != "" {
99+
request = request.Source(stackstate_api.SubjectSource(capitalizeFirst(source)))
100+
}
76101
return request
77102
}
78103

79-
func printPermissionsTable(cli *di.Deps, permissionsList map[string][]string) {
104+
func capitalizeFirst(s string) string {
105+
if s == "" {
106+
return s
107+
}
108+
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
109+
}
110+
111+
func printPermissionsTable(cli *di.Deps, sources []string, permissionsList map[string][]string) {
80112
keys := make([]string, len(permissionsList))
81113
for key := range permissionsList {
82114
keys = append(keys, key)
@@ -97,6 +129,11 @@ func printPermissionsTable(cli *di.Deps, permissionsList map[string][]string) {
97129
}
98130
}
99131

132+
if sources != nil {
133+
cli.Printer.PrintLn(fmt.Sprintf("Got subject from the following subject sources: %s", strings.Join(sources, ", ")))
134+
cli.Printer.PrintLn("")
135+
}
136+
100137
cli.Printer.Table(printer.TableData{
101138
Header: []string{"permission", "resource"},
102139
Data: data,

cmd/rbac/rbac_describe_permissions_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ var (
4444
"subject": SubjectHandle,
4545
"permissions": AllPermissions,
4646
}
47+
ExpectedSourcesJson = map[string]interface{}{
48+
"subject": SubjectHandle,
49+
"permissions": AllPermissions,
50+
"sources": []string{"Static"},
51+
}
4752
)
4853

4954
func TestPermissionsDescribeJson(t *testing.T) {
@@ -112,6 +117,22 @@ func TestPermissionsDescribeFilterPermission(t *testing.T) {
112117
assert.Nil(t, calls[0].Presource)
113118
}
114119

120+
func TestPermissionsDescribeFilterSource(t *testing.T) {
121+
cli := di.NewMockDeps(t)
122+
cmd := DescribePermissionsCommand(&cli.Deps)
123+
124+
cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsResponse.Result = *Description
125+
126+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--subject", SubjectHandle, "--source", "static")
127+
128+
s := stackstate_api.SUBJECTSOURCE_STATIC
129+
calls := *cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsCalls
130+
assert.Len(t, calls, 1)
131+
assert.Equal(t, SubjectHandle, calls[0].Psubject)
132+
assert.Equal(t, &s, calls[0].Psource)
133+
assert.Nil(t, calls[0].Presource)
134+
}
135+
115136
func TestPermissionsDescribeFilterResourceAndPermission(t *testing.T) {
116137
cli := di.NewMockDeps(t)
117138
cmd := DescribePermissionsCommand(&cli.Deps)
@@ -126,3 +147,40 @@ func TestPermissionsDescribeFilterResourceAndPermission(t *testing.T) {
126147
assert.Equal(t, "foo", *calls[0].Ppermission)
127148
assert.Equal(t, Resource1, *calls[0].Presource)
128149
}
150+
151+
func TestPermissionsDescribeWithSourcesJson(t *testing.T) {
152+
cli := di.NewMockDeps(t)
153+
cmd := DescribePermissionsCommand(&cli.Deps)
154+
155+
cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsResponse.Result = *Description
156+
cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsResponse.Result.FromSources = []stackstate_api.SubjectSource{stackstate_api.SUBJECTSOURCE_STATIC}
157+
158+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--subject", SubjectHandle, "-o", "json")
159+
160+
calls := *cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsCalls
161+
assert.Len(t, calls, 1)
162+
163+
expected := []map[string]interface{}{
164+
ExpectedSourcesJson,
165+
}
166+
assert.Equal(t, expected, *cli.MockPrinter.PrintJsonCalls)
167+
}
168+
169+
func TestPermissionsDescribeWithSourcesTable(t *testing.T) {
170+
cli := di.NewMockDeps(t)
171+
cmd := DescribePermissionsCommand(&cli.Deps)
172+
173+
cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsResponse.Result = *Description
174+
cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsResponse.Result.FromSources = []stackstate_api.SubjectSource{stackstate_api.SUBJECTSOURCE_STATIC}
175+
176+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--subject", SubjectHandle)
177+
178+
assert.Len(t, *cli.MockClient.ApiMocks.PermissionsApi.DescribePermissionsCalls, 1)
179+
180+
expected := []printer.TableData{
181+
ExpectedTable,
182+
}
183+
184+
assert.Equal(t, expected, *cli.MockPrinter.TableCalls)
185+
assert.Equal(t, []string{"Got subject from the following subject sources: Static", ""}, *cli.MockPrinter.PrintLnCalls)
186+
}

cmd/rbac/rbac_describe_subjects.go

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package rbac
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/spf13/cobra"
58
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
69
"github.com/stackvista/stackstate-cli/internal/common"
710
"github.com/stackvista/stackstate-cli/internal/di"
811
"github.com/stackvista/stackstate-cli/internal/printer"
12+
"github.com/stackvista/stackstate-cli/pkg/pflags"
913
)
1014

1115
type DescribeSubjectsArgs struct {
1216
Subject string
17+
Source string
1318
}
1419

1520
func DescribeSubjectsCommand(deps *di.Deps) *cobra.Command {
@@ -27,6 +32,7 @@ sts rbac describe-subjects --subject my-team`,
2732
}
2833

2934
cmd.Flags().StringVar(&args.Subject, Subject, "", SubjectUsage)
35+
pflags.EnumVar(cmd.Flags(), &args.Source, Source, "", SourceChoices, SourceUsage)
3036

3137
return cmd
3238
}
@@ -38,45 +44,34 @@ func RunDescribeSubjectsCommand(args *DescribeSubjectsArgs) di.CmdWithApiFn {
3844
api *stackstate_api.APIClient,
3945
serverInfo *stackstate_api.ServerInfo,
4046
) common.CLIError {
41-
if args.Subject != "" {
42-
subject, resp, err := api.SubjectApi.GetSubject(cli.Context, args.Subject).Execute()
47+
// Why do wre use the list api instead of the GetSubject api for retrieving a subject? The reason is that due to rbac
48+
// subjects can come from multiple sources. Instead of updating the GetSubject api in a breaking way, we have deprecated that
49+
// api and use the list api with a client side filter to get all subjects.
50+
subjects, resp, err := api.SubjectApi.ListSubjects(cli.Context).Execute()
4351

44-
if err != nil {
45-
return common.NewResponseError(err, resp)
46-
}
52+
if err != nil {
53+
return common.NewResponseError(err, resp)
54+
}
4755

48-
if cli.IsJson() {
49-
cli.Printer.PrintJson(map[string]interface{}{
50-
"handle": subject.Handle,
51-
"source": subject.Source,
52-
})
53-
} else {
54-
cli.Printer.Table(printer.TableData{
55-
Header: []string{"Subject", "Source"},
56-
Data: [][]interface{}{
57-
{
58-
subject.Handle,
59-
subject.Source,
60-
},
61-
},
62-
MissingTableDataMsg: printer.NotFoundMsg{Types: "matching subjects"},
63-
})
64-
}
65-
} else {
66-
subjects, resp, err := api.SubjectApi.ListSubjects(cli.Context).Execute()
56+
filteredSubjects := make([]stackstate_api.SubjectConfig, 0)
6757

68-
if err != nil {
69-
return common.NewResponseError(err, resp)
58+
for _, subject := range subjects {
59+
if (args.Subject == "" || subject.Handle == args.Subject) && (args.Source == "" || strings.EqualFold(string(subject.Source), args.Source)) {
60+
filteredSubjects = append(filteredSubjects, subject)
7061
}
62+
}
7163

64+
if len(filteredSubjects) == 0 && args.Subject != "" {
65+
return common.NewNotFoundError(fmt.Errorf("could not find subject: '%s'", args.Subject))
66+
} else {
7267
if cli.IsJson() {
7368
cli.Printer.PrintJson(map[string]interface{}{
74-
"subjects": subjects,
69+
"subjects": filteredSubjects,
7570
})
7671
} else {
7772
data := make([][]interface{}, 0)
7873

79-
for _, subject := range subjects {
74+
for _, subject := range filteredSubjects {
8075
data = append(data, []interface{}{subject.Handle, subject.Source})
8176
}
8277

0 commit comments

Comments
 (0)