Skip to content

Commit

Permalink
feat: Local Identity CLI (#1269)
Browse files Browse the repository at this point in the history
* feat: local user credentials

- login with an infra user via the cli
- create a user in the infra cli
- update a users password in the the cli
- CLI user create, edit, and delete
- identities list users and machines
- use provider ID for user and group look up
- email not required on list users request, do not validate
- move CLI errors to a single file
- split up login to separate functions
- do not bind env vars for identity commands
- don't update user info for local users on token create
  • Loading branch information
BruceMacD authored Mar 21, 2022
1 parent f58187d commit a5159b2
Show file tree
Hide file tree
Showing 12 changed files with 606 additions and 248 deletions.
8 changes: 8 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ func (c Client) CreateUser(req *CreateUserRequest) (*CreateUserResponse, error)
return post[CreateUserRequest, CreateUserResponse](c, "/v1/users", req)
}

func (c Client) UpdateUser(req *UpdateUserRequest) (*User, error) {
return put[UpdateUserRequest, User](c, fmt.Sprintf("/v1/users/%s", req.ID.String()), req)
}

func (c Client) DeleteUser(id uid.ID) error {
return delete(c, fmt.Sprintf("/v1/users/%s", id))
}

func (c Client) ListUserGrants(id uid.ID) ([]Grant, error) {
return list[Grant](c, fmt.Sprintf("/v1/users/%s/grants", id), nil)
}
Expand Down
2 changes: 1 addition & 1 deletion api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type User struct {
}

type ListUsersRequest struct {
Email string `form:"email" validate:"email"`
Email string `form:"email"`
ProviderID uid.ID `form:"provider_id"`
}

Expand Down
10 changes: 10 additions & 0 deletions internal/access/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import (
"github.com/infrahq/infra/uid"
)

// CurrentIdentityProvider returns the provider for the current identity in the request context
func CurrentIdentityProvider(c *gin.Context) (*models.Provider, error) {
u, ok := c.MustGet("user").(*models.User)
if !ok {
return nil, fmt.Errorf("no user in request context")
}

return GetProvider(c, u.ProviderID)
}

func CreateProvider(c *gin.Context, provider *models.Provider) error {
db, err := requireInfraRole(c, models.InfraAdminRole)
if err != nil {
Expand Down
35 changes: 19 additions & 16 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,26 @@ func parseOptions(cmd *cobra.Command, options interface{}, envPrefix string) err
}
}

v.SetEnvPrefix(envPrefix)
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.AutomaticEnv()

// workaround for viper not correctly binding env vars
// https://github.com/spf13/viper/issues/761
envKeys := make(map[string]interface{})
if err := mapstructure.Decode(options, &envKeys); err != nil {
return err
}

// bind file options (lower camel case) to environment options (envPrefix + upper snake case)
// e.g. accessKey -> INFRA_CONNECTOR_ACCESS_KEY
for envKey := range envKeys {
fullEnvKey := fmt.Sprintf("%s_%s", envPrefix, envKey)
if err := v.BindEnv(envKey, strcase.ToScreamingSnake(fullEnvKey)); err != nil {
if envPrefix != "" {
v.SetEnvPrefix(envPrefix)
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.AutomaticEnv()

// workaround for viper not correctly binding env vars
// https://github.com/spf13/viper/issues/761
envKeys := make(map[string]interface{})
if err := mapstructure.Decode(options, &envKeys); err != nil {
return err
}

// bind file options (lower camel case) to environment options (envPrefix + upper snake case)
// e.g. accessKey -> INFRA_CONNECTOR_ACCESS_KEY
for envKey := range envKeys {
fullEnvKey := fmt.Sprintf("%s_%s", envPrefix, envKey)
if err := v.BindEnv(envKey, strcase.ToScreamingSnake(fullEnvKey)); err != nil {
return err
}
}
}

errs := make([]error, 0)
Expand Down Expand Up @@ -518,6 +520,7 @@ func newIdentitiesCmd() *cobra.Command {
}

cmd.AddCommand(newIdentitiesAddCmd())
cmd.AddCommand(newIdentitiesEditCmd())
cmd.AddCommand(newIdentitiesListCmd())
cmd.AddCommand(newIdentitiesRemoveCmd())

Expand Down
4 changes: 0 additions & 4 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -31,9 +30,6 @@ func (c *ClientHostConfig) isLoggedIn() bool {
return c.AccessKey != ""
}

//lint:ignore ST1005, user facing error
var ErrConfigNotFound = errors.New(`Could not read local credentials. Are you logged in? Use "infra login" to login`)

func NewClientConfig() *ClientConfig {
return &ClientConfig{
Version: "0.3",
Expand Down
10 changes: 10 additions & 0 deletions internal/cmd/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cmd

import "errors"

var (
//lint:ignore ST1005, user facing error
ErrConfigNotFound = errors.New(`Could not read local credentials. Are you logged in? Use "infra login" to login`)
ErrProviderNotUnique = errors.New(`more than one provider exists with this name`)
ErrUserNotFound = errors.New(`no users found with this name`)
)
42 changes: 16 additions & 26 deletions internal/cmd/grants.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,17 @@ $ infra grants add -u [email protected] -r admin infra
return err
}

var providers []api.Provider
var provider *api.Provider

if options.Machine == "" {
providers, err = client.ListProviders(options.Provider)
provider, err = GetProviderByName(client, options.Provider)
if err != nil {
if errors.Is(err, ErrProviderNotUnique) {
return fmt.Errorf("specify provider with -p or --provider: %w", err)
}
return err
}

if len(providers) == 0 {
return errors.New("no identity providers connected")
}

if len(providers) > 1 {
return errors.New("specify provider with -p or --provider")
}

if options.User != "" && options.Group != "" {
return errors.New("only allowed one of --user or --group")
}
Expand All @@ -131,15 +126,15 @@ $ infra grants add -u [email protected] -r admin infra

if options.User != "" {
// create user if they don't exist
users, err := client.ListUsers(api.ListUsersRequest{Email: options.User})
users, err := client.ListUsers(api.ListUsersRequest{Email: options.User, ProviderID: provider.ID})
if err != nil {
return err
}

if len(users) == 0 {
newUser, err := client.CreateUser(&api.CreateUserRequest{
Email: options.User,
ProviderID: providers[0].ID,
ProviderID: provider.ID,
})
if err != nil {
return err
Expand All @@ -153,15 +148,15 @@ $ infra grants add -u [email protected] -r admin infra

if options.Group != "" {
// create group if they don't exist
groups, err := client.ListGroups(api.ListGroupsRequest{Name: options.Group})
groups, err := client.ListGroups(api.ListGroupsRequest{Name: options.Group, ProviderID: provider.ID})
if err != nil {
return err
}

if len(groups) == 0 {
newGroup, err := client.CreateGroup(&api.CreateGroupRequest{
Name: options.Group,
ProviderID: providers[0].ID,
ProviderID: provider.ID,
})
if err != nil {
return err
Expand Down Expand Up @@ -234,22 +229,17 @@ func newGrantRemoveCmd() *cobra.Command {
return err
}

var providers []api.Provider
var provider *api.Provider

if options.Machine == "" {
providers, err = client.ListProviders(options.Provider)
provider, err = GetProviderByName(client, options.Provider)
if err != nil {
if errors.Is(err, ErrProviderNotUnique) {
return fmt.Errorf("specify provider with -p or --provider: %w", err)
}
return err
}

if len(providers) == 0 {
return errors.New("No identity providers connected")
}

if len(providers) > 1 {
return errors.New("Specify provider with -p or --provider")
}

if options.User != "" && options.Group != "" {
return errors.New("only allowed one of --user or --group")
}
Expand All @@ -260,7 +250,7 @@ func newGrantRemoveCmd() *cobra.Command {
var id uid.PolymorphicID

if options.User != "" {
users, err := client.ListUsers(api.ListUsersRequest{Email: options.User})
users, err := client.ListUsers(api.ListUsersRequest{Email: options.User, ProviderID: provider.ID})
if err != nil {
return err
}
Expand All @@ -273,7 +263,7 @@ func newGrantRemoveCmd() *cobra.Command {
}

if options.Group != "" {
groups, err := client.ListGroups(api.ListGroupsRequest{Name: options.Group})
groups, err := client.ListGroups(api.ListGroupsRequest{Name: options.Group, ProviderID: provider.ID})
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit a5159b2

Please sign in to comment.