@@ -3,77 +3,201 @@ package cmd
33import (
44 "context"
55 "encoding/json"
6- "log"
6+ "fmt"
7+ "io"
78 "sort"
9+ "strings"
810
911 "github.com/calypr/data-client/g3client"
1012 "github.com/calypr/data-client/logs"
1113 "github.com/spf13/cobra"
1214)
1315
16+ const authSummaryResourceLimit = 10
17+
1418func init () {
1519 var profile string
20+ var showAll bool
21+ var jsonOutput bool
1622 var authCmd = & cobra.Command {
17- Use : "auth" ,
18- Short : "Return resource access privileges from profile" ,
19- Long : `Gets resource access privileges for specified profile.` ,
20- Example : `./data-client auth --profile=<profile-name>` ,
21- Run : func (cmd * cobra.Command , args []string ) {
23+ Use : "auth" ,
24+ Short : "Return resource access privileges from profile" ,
25+ Long : `Gets resource access privileges for specified profile.` ,
26+ Example : `./data-client auth --profile=<profile-name>
27+ ./data-client auth --profile=<profile-name> --all
28+ ./data-client auth --profile=<profile-name> --json` ,
29+ RunE : func (cmd * cobra.Command , args []string ) error {
2230 // don't initialize transmission logs for non-uploading related commands
2331
24- logger , logCloser := logs .New (profile , logs .WithConsole ())
32+ logger , logCloser := logs .New (profile , logs .WithNoConsole ())
2533 defer logCloser ()
2634
2735 g3i , err := g3client .NewGen3Interface (
2836 profile , logger ,
2937 g3client .WithClients (g3client .FenceClient ),
3038 )
3139 if err != nil {
32- log . Fatalf ( "Fatal NewGen3Interface error : %s \n " , err )
40+ return fmt . Errorf ( "new Gen3 interface : %w " , err )
3341 }
3442
3543 resourceAccess , err := g3i .FenceClient ().CheckPrivileges (context .Background ())
3644 if err != nil {
37- g3i .Logger ().Fatalf ("Fatal authentication error: %s\n " , err )
38- } else {
39- if len (resourceAccess ) == 0 {
40- g3i .Logger ().Printf ("\n You don't currently have access to any resources at %s\n " , g3i .Credentials ().Current ().APIEndpoint )
41- } else {
42- g3i .Logger ().Printf ("\n You have access to the following resource(s) at %s:\n " , g3i .Credentials ().Current ().APIEndpoint )
43-
44- // Sort by resource name
45- resources := make ([]string , 0 , len (resourceAccess ))
46- for resource := range resourceAccess {
47- resources = append (resources , resource )
48- }
49- sort .Strings (resources )
50-
51- for _ , project := range resources {
52- // Sort by access name if permissions are from Fence
53- permissions := resourceAccess [project ].([]any )
54- _ , isFencePermission := permissions [0 ].(string )
55- if isFencePermission {
56- access := make ([]string , 0 , len (permissions ))
57- for _ , permission := range permissions {
58- access = append (access , permission .(string ))
59- }
60- sort .Strings (access )
61- g3i .Logger ().Printf ("%s %s\n " , project , access )
62- } else {
63- // Permissions from Arborist already sorted, just pretty print them
64- marshalledPermissions , err := json .MarshalIndent (permissions , "" , " " )
65- if err != nil {
66- g3i .Logger ().Printf ("%s (error occurred when marshalling permissions): %s\n " , project , err )
67- }
68- g3i .Logger ().Printf ("%s %s\n " , project , marshalledPermissions )
69- }
70- }
71- }
45+ return fmt .Errorf ("authentication: %w" , err )
46+ }
47+
48+ if jsonOutput {
49+ encoder := json .NewEncoder (cmd .OutOrStdout ())
50+ encoder .SetIndent ("" , " " )
51+ return encoder .Encode (resourceAccess )
7252 }
53+
54+ writeAuthSummary (cmd .OutOrStdout (), g3i .Credentials ().Current ().APIEndpoint , resourceAccess , showAll )
55+ return nil
7356 },
7457 }
7558
7659 authCmd .Flags ().StringVar (& profile , "profile" , "" , "Specify the profile to check your access privileges" )
60+ authCmd .Flags ().BoolVar (& showAll , "all" , false , "Show every resource in each permission group" )
61+ authCmd .Flags ().BoolVar (& jsonOutput , "json" , false , "Output raw resource access JSON" )
7762 authCmd .MarkFlagRequired ("profile" ) // nolint: errcheck
7863 RootCmd .AddCommand (authCmd )
7964}
65+
66+ func writeAuthSummary (w io.Writer , endpoint string , resourceAccess map [string ]any , showAll bool ) {
67+ if len (resourceAccess ) == 0 {
68+ fmt .Fprintf (w , "No resource access found for %s\n " , endpoint )
69+ return
70+ }
71+
72+ groups := make (map [string ][]string )
73+ for resource , permissions := range resourceAccess {
74+ signature := authPermissionSignature (permissions )
75+ groups [signature ] = append (groups [signature ], resource )
76+ }
77+
78+ type accessGroup struct {
79+ signature string
80+ resources []string
81+ }
82+
83+ orderedGroups := make ([]accessGroup , 0 , len (groups ))
84+ for signature , resources := range groups {
85+ sort .Strings (resources )
86+ orderedGroups = append (orderedGroups , accessGroup {
87+ signature : signature ,
88+ resources : resources ,
89+ })
90+ }
91+
92+ sort .Slice (orderedGroups , func (i , j int ) bool {
93+ if len (orderedGroups [i ].resources ) != len (orderedGroups [j ].resources ) {
94+ return len (orderedGroups [i ].resources ) > len (orderedGroups [j ].resources )
95+ }
96+ return orderedGroups [i ].signature < orderedGroups [j ].signature
97+ })
98+
99+ fmt .Fprintf (w , "Access for %s\n " , endpoint )
100+ fmt .Fprintf (w , "%d resources in %d permission groups\n \n " , len (resourceAccess ), len (orderedGroups ))
101+
102+ for _ , group := range orderedGroups {
103+ fmt .Fprintf (w , "%d %s: %s\n " , len (group .resources ), pluralize ("resource" , len (group .resources )), group .signature )
104+
105+ limit := len (group .resources )
106+ if ! showAll && limit > authSummaryResourceLimit {
107+ limit = authSummaryResourceLimit
108+ }
109+
110+ for _ , resource := range group .resources [:limit ] {
111+ fmt .Fprintf (w , " %s\n " , resource )
112+ }
113+ if ! showAll && len (group .resources ) > limit {
114+ fmt .Fprintf (w , " ... %d more (use --all to show every resource)\n " , len (group .resources )- limit )
115+ }
116+ fmt .Fprintln (w )
117+ }
118+ }
119+
120+ func pluralize (word string , count int ) string {
121+ if count == 1 {
122+ return word
123+ }
124+ return word + "s"
125+ }
126+
127+ func authPermissionSignature (value any ) string {
128+ permissions , ok := value .([]any )
129+ if ! ok {
130+ return compactJSON (value )
131+ }
132+ if len (permissions ) == 0 {
133+ return "no permissions"
134+ }
135+
136+ if _ , ok := permissions [0 ].(string ); ok {
137+ access := make ([]string , 0 , len (permissions ))
138+ for _ , permission := range permissions {
139+ access = append (access , fmt .Sprint (permission ))
140+ }
141+ sort .Strings (access )
142+ return strings .Join (access , ", " )
143+ }
144+
145+ serviceMethods := make (map [string ]map [string ]struct {})
146+ unknown := make ([]string , 0 )
147+
148+ for _ , permission := range permissions {
149+ method , service , ok := authPermissionFields (permission )
150+ if ! ok {
151+ unknown = append (unknown , compactJSON (permission ))
152+ continue
153+ }
154+
155+ if serviceMethods [service ] == nil {
156+ serviceMethods [service ] = make (map [string ]struct {})
157+ }
158+ serviceMethods [service ][method ] = struct {}{}
159+ }
160+
161+ parts := make ([]string , 0 , len (serviceMethods )+ len (unknown ))
162+ services := make ([]string , 0 , len (serviceMethods ))
163+ for service := range serviceMethods {
164+ services = append (services , service )
165+ }
166+ sort .Strings (services )
167+
168+ for _ , service := range services {
169+ methods := make ([]string , 0 , len (serviceMethods [service ]))
170+ for method := range serviceMethods [service ] {
171+ methods = append (methods , method )
172+ }
173+ sort .Strings (methods )
174+ parts = append (parts , fmt .Sprintf ("%s: %s" , service , strings .Join (methods , ", " )))
175+ }
176+
177+ sort .Strings (unknown )
178+ parts = append (parts , unknown ... )
179+ return strings .Join (parts , "; " )
180+ }
181+
182+ func authPermissionFields (value any ) (method string , service string , ok bool ) {
183+ switch permission := value .(type ) {
184+ case map [string ]any :
185+ method , methodOK := permission ["method" ].(string )
186+ service , serviceOK := permission ["service" ].(string )
187+ return method , service , methodOK && serviceOK
188+ case map [string ]string :
189+ method , methodOK := permission ["method" ]
190+ service , serviceOK := permission ["service" ]
191+ return method , service , methodOK && serviceOK
192+ default :
193+ return "" , "" , false
194+ }
195+ }
196+
197+ func compactJSON (value any ) string {
198+ data , err := json .Marshal (value )
199+ if err != nil {
200+ return fmt .Sprint (value )
201+ }
202+ return string (data )
203+ }
0 commit comments