forked from danielgtaylor/openapi-cli-generator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
credentials.go
304 lines (258 loc) · 8.97 KB
/
credentials.go
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
package cli
import (
"fmt"
"net/http"
"os"
"path"
"strings"
"github.com/olekukonko/tablewriter"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/h2non/gentleman.v2/context"
)
// AuthHandler describes a handler that can be called on a request to inject
// auth information and is agnostic to the type of auth.
type AuthHandler interface {
// ProfileKeys returns the key names for fields to store in the profile.
ProfileKeys() []string
// OnRequest gets run before the request goes out on the wire.
OnRequest(log *zerolog.Logger, request *http.Request) error
}
// AuthHandlers is the map of registered auth type names to handlers
var AuthHandlers = make(map[string]AuthHandler)
var authInitialized bool
var authCommand *cobra.Command
var authAddCommand *cobra.Command
// initAuth sets up basic commands and the credentials file so that new auth
// handlers can be registered. This is safe to call many times.
func initAuth() {
if authInitialized {
return
}
authInitialized = true
// Set up the credentials file
InitCredentialsFile()
// Add base auth commands
authCommand = &cobra.Command{
Use: "auth",
Short: "Authentication settings",
}
Root.AddCommand(authCommand)
authAddCommand = &cobra.Command{
Use: "add-profile",
Aliases: []string{"add"},
Short: "Add user profile for authentication",
}
authCommand.AddCommand(authAddCommand)
authCommand.AddCommand(&cobra.Command{
Use: "list-profiles",
Aliases: []string{"ls"},
Short: "List available configured authentication profiles",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
profiles := Creds.GetStringMap("profiles")
if profiles != nil {
// Use a map as a set to find the available auth type names.
types := make(map[string]bool)
for _, v := range profiles {
if typeName := v.(map[string]interface{})["type"]; typeName != nil {
types[typeName.(string)] = true
}
}
// For each type name, draw a table with the relevant profile keys
for typeName := range types {
handler := AuthHandlers[typeName]
if handler == nil {
continue
}
listKeys := handler.ProfileKeys()
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader(append([]string{fmt.Sprintf("%s Profile Name", typeName)}, listKeys...))
for name, p := range profiles {
profile := p.(map[string]interface{})
if ptype := profile["type"]; ptype == nil || ptype.(string) != typeName {
continue
}
row := []string{name}
for _, key := range listKeys {
row = append(row, profile[strings.Replace(key, "-", "_", -1)].(string))
}
table.Append(row)
}
table.Render()
}
} else {
fmt.Printf("No profiles configured. Use `%s auth add-profile` to add one.\n", Root.CommandPath())
}
},
})
// Install auth middleware
Client.UseRequest(func(ctx *context.Context, h context.Handler) {
profile := GetProfile()
handler := AuthHandlers[profile["type"]]
if handler == nil {
h.Error(ctx, fmt.Errorf("no handler for auth type %s", profile["type"]))
return
}
if err := handler.OnRequest(ctx.Get("log").(*zerolog.Logger), ctx.Request); err != nil {
h.Error(ctx, err)
return
}
h.Next(ctx)
})
}
// UseAuth registers a new auth handler for a given type name. For backward-
// compatibility, the auth type name can be a blank string. It is recommended
// to always pass a value for the type name.
func UseAuth(typeName string, handler AuthHandler) {
// Initialize auth system if it isn't already set up.
initAuth()
// Register the handler by its type.
AuthHandlers[typeName] = handler
// Set up the add-profile command.
keys := handler.ProfileKeys()
use := " [flags] <name>"
for _, name := range keys {
use += " <" + strings.Replace(name, "_", "-", -1) + ">"
}
run := func(cmd *cobra.Command, args []string) {
name := strings.Replace(args[0], ".", "-", -1)
Creds.Set("profiles."+name+".type", typeName)
for i, key := range keys {
// Replace periods in the name since Viper will create nested structures
// in the config and this isn't what we want!
Creds.Set("profiles."+name+"."+strings.Replace(key, "-", "_", -1), args[i+1])
}
filename := path.Join(viper.GetString("config-directory"), "credentials.json")
if err := Creds.WriteConfigAs(filename); err != nil {
panic(err)
}
}
if typeName == "" {
// Backward-compatibility use-case without an explicit type. Set up the
// `add-profile` command as the only way to authenticate.
if authAddCommand.Run != nil {
// This fallback code path was already used, so we must be registering
// a *second* anonymous auth type, which is not allowed.
panic("register auth type names to use multi-auth")
}
authAddCommand.Use = "add-profile" + use
authAddCommand.Short = "Add a new named authentication profile"
authAddCommand.Args = cobra.ExactArgs(1 + len(keys))
authAddCommand.Run = run
} else {
// Add a new type-specific `add-profile` subcommand.
authAddCommand.AddCommand(&cobra.Command{
Use: typeName + use,
Short: "Add a new named " + typeName + " authentication profile",
Args: cobra.ExactArgs(1 + len(keys)),
Run: run,
})
}
}
// CredentialsFile holds credential-related information.
type CredentialsFile struct {
*viper.Viper
keys []string
listKeys []string
}
// Creds represents a configuration file storing credential-related information.
// Use this only after `InitCredentials` has been called.
var Creds *CredentialsFile
// GetProfile returns the current profile's configuration.
func GetProfile() map[string]string {
return Creds.GetStringMapString("profiles." + strings.Replace(viper.GetString("profile"), ".", "-", -1))
}
// ProfileKeys lets you specify authentication profile keys to be used in
// the credentials file.
// This is deprecated and you should use `cli.UseAuth` instead.
func ProfileKeys(keys ...string) func(*CredentialsFile) error {
return func(cf *CredentialsFile) error {
cf.keys = keys
return nil
}
}
// ProfileListKeys sets which keys will be shown in the table when calling
// the `auth list-profiles` command.
// This is deprecated and you should use `cli.UseAuth` instead.
func ProfileListKeys(keys ...string) func(*CredentialsFile) error {
return func(cf *CredentialsFile) error {
cf.listKeys = keys
return nil
}
}
// InitCredentialsFile sets up the creds file and `profile` global parameter.
func InitCredentialsFile() {
// Setup a credentials file, kept separate from configuration which might
// get checked into source control.
Creds = &CredentialsFile{viper.New(), []string{}, []string{}}
Creds.SetConfigName("credentials")
Creds.AddConfigPath("$HOME/." + viper.GetString("app-name") + "/")
Creds.ReadInConfig()
// Register a new `--profile` flag.
AddGlobalFlag("profile", "", "Credentials profile to use for authentication", "default")
}
// InitCredentials sets up the profile/auth commands. Must be called *after* you
// have called `cli.Init()`.
//
// // Initialize an API key
// cli.InitCredentials(cli.ProfileKeys("api-key"))
// This is deprecated and you should use `cli.UseAuth` instead.
func InitCredentials(options ...func(*CredentialsFile) error) {
InitCredentialsFile()
for _, option := range options {
option(Creds)
}
// Register auth management commands to create and list profiles.
cmd := &cobra.Command{
Use: "auth",
Short: "Authentication settings",
}
Root.AddCommand(cmd)
use := "add-profile [flags] <name>"
for _, name := range Creds.keys {
use += " <" + strings.Replace(name, "_", "-", -1) + ">"
}
cmd.AddCommand(&cobra.Command{
Use: use,
Aliases: []string{"add"},
Short: "Add a new named authentication profile",
Args: cobra.ExactArgs(1 + len(Creds.keys)),
Run: func(cmd *cobra.Command, args []string) {
for i, key := range Creds.keys {
// Replace periods in the name since Viper will create nested structures
// in the config and this isn't what we want!
name := strings.Replace(args[0], ".", "-", -1)
Creds.Set("profiles."+name+"."+strings.Replace(key, "-", "_", -1), args[i+1])
}
filename := path.Join(viper.GetString("config-directory"), "credentials.json")
if err := Creds.WriteConfigAs(filename); err != nil {
panic(err)
}
},
})
cmd.AddCommand(&cobra.Command{
Use: "list-profiles",
Aliases: []string{"ls"},
Short: "List available configured authentication profiles",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
profiles := Creds.GetStringMap("profiles")
if profiles != nil {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader(append([]string{"Profile Name"}, Creds.listKeys...))
for name, profile := range profiles {
row := []string{name}
for _, key := range Creds.listKeys {
row = append(row, profile.(map[string]interface{})[strings.Replace(key, "-", "_", -1)].(string))
}
table.Append(row)
}
table.Render()
} else {
fmt.Printf("No profiles configured. Use `%s auth add-profile` to add one.\n", Root.CommandPath())
}
},
})
}