From 16d9ed786de2958b26d3fe2e8da22689c18c7022 Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Fri, 26 Mar 2021 16:44:30 -0700 Subject: [PATCH] CLI-80: user-blocks commands with user_id --- internal/auth/auth.go | 2 +- internal/auth0/auth0.go | 2 + internal/auth0/user.go | 14 ++++ internal/cli/root.go | 1 + internal/cli/user_blocks.go | 110 ++++++++++++++++++++++++++++++++ internal/display/user_blocks.go | 56 ++++++++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 internal/auth0/user.go create mode 100644 internal/cli/user_blocks.go create mode 100644 internal/display/user_blocks.go diff --git a/internal/auth/auth.go b/internal/auth/auth.go index a85c4426e..bd1782451 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -28,7 +28,7 @@ var requiredScopes = []string{ "create:clients", "delete:clients", "read:clients", "update:clients", "create:resource_servers", "delete:resource_servers", "read:resource_servers", "update:resource_servers", "create:rules", "delete:rules", "read:rules", "update:rules", - "read:client_keys", "read:logs", + "read:client_keys", "read:logs", "read:users", "update:users", } // SecretStore provides secure storage for sensitive data diff --git a/internal/auth0/auth0.go b/internal/auth0/auth0.go index 5ba7c7201..6afe2f232 100644 --- a/internal/auth0/auth0.go +++ b/internal/auth0/auth0.go @@ -16,6 +16,7 @@ type API struct { Log LogAPI ResourceServer ResourceServerAPI Rule RuleAPI + User UserAPI } func NewAPI(m *management.Management) *API { @@ -28,6 +29,7 @@ func NewAPI(m *management.Management) *API { Log: m.Log, ResourceServer: m.ResourceServer, Rule: m.Rule, + User: m.User, } } diff --git a/internal/auth0/user.go b/internal/auth0/user.go new file mode 100644 index 000000000..1a8017210 --- /dev/null +++ b/internal/auth0/user.go @@ -0,0 +1,14 @@ +//go:generate mockgen -source=user.go -destination=user_mock.go -package=auth0 + +package auth0 + +import "gopkg.in/auth0.v5/management" + +type UserAPI interface { + // Retrieves a list of blocked IP addresses of a particular user. + Blocks(id string, opts ...management.RequestOption) ([]*management.UserBlock, error) + + // Unblock a user that was blocked due to an excessive amount of incorrectly + // provided credentials. + Unblock(id string, opts ...management.RequestOption) error +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 22278c77a..1e5157367 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -82,6 +82,7 @@ func Execute() { rootCmd.AddCommand(rulesCmd(cli)) rootCmd.AddCommand(quickstartsCmd(cli)) rootCmd.AddCommand(apisCmd(cli)) + rootCmd.AddCommand(userBlocksCmd(cli)) rootCmd.AddCommand(testCmd(cli)) rootCmd.AddCommand(logsCmd(cli)) rootCmd.AddCommand(logoutCmd(cli)) diff --git a/internal/cli/user_blocks.go b/internal/cli/user_blocks.go new file mode 100644 index 000000000..2c7266aa8 --- /dev/null +++ b/internal/cli/user_blocks.go @@ -0,0 +1,110 @@ +package cli + +import ( + "errors" + "fmt" + + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/spf13/cobra" + "gopkg.in/auth0.v5/management" +) + +var ( + userID = Argument{ + Name: "user_id", + Help: "user_id of the user.", + } +) + +func userBlocksCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "user-blocks", + Short: "Manage brute-force protection user blocks.", + } + + cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(listUserBlocksByUserIdCmd(cli)) + cmd.AddCommand(deleteUserBlocksByUserIdCmd(cli)) + return cmd +} + +func listUserBlocksByUserIdCmd(cli *cli) *cobra.Command { + var inputs struct { + user_id string + } + + cmd := &cobra.Command{ + Use: "listByUserId", + Args: cobra.MaximumNArgs(1), + Short: "List user-blocks by user_id", + Long: `List user-blocks by user_id: + +auth0 user-blocks listByUserId +`, + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + inputs.user_id = args[0] + } else { + return errors.New("user_id is required.") + } + + var userBlocks []*management.UserBlock + + err := ansi.Waiting(func() error { + var err error + userBlocks, err = cli.api.User.Blocks(inputs.user_id) + return err + }) + + if err != nil { + return fmt.Errorf("Unable to load user blocks %v, error: %w", inputs.user_id, err) + } + + cli.renderer.UserBlocksList(userBlocks) + return nil + }, + } + + return cmd +} + +func deleteUserBlocksByUserIdCmd(cli *cli) *cobra.Command { + var inputs struct { + user_id string + } + + cmd := &cobra.Command{ + Use: "deleteByUserId", + Args: cobra.MaximumNArgs(1), + Short: "Delete all user-blocks by user_id", + Long: `Delete all user-blocks by user_id: + +auth0 user-blocks deleteByUserId +`, + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + inputs.user_id = args[0] + } else { + return errors.New("user_id is required.") + } + + err := ansi.Spinner("Deleting blocks for user...", func() error { + return cli.api.User.Unblock(inputs.user_id) + }) + + if err != nil { + return err + } + + return nil + }, + } + + return cmd +} diff --git a/internal/display/user_blocks.go b/internal/display/user_blocks.go new file mode 100644 index 000000000..b7eb13aa4 --- /dev/null +++ b/internal/display/user_blocks.go @@ -0,0 +1,56 @@ +package display + +import ( + "gopkg.in/auth0.v5/management" +) + +type userBlockView struct { + Identifier string + IP string +} + +func (v *userBlockView) AsTableHeader() []string { + return []string{"Identifier", "IP"} +} + +func (v *userBlockView) AsTableRow() []string { + return []string{v.Identifier, v.IP} +} + +func (v *userBlockView) KeyValues() [][]string { + return [][]string{ + []string{"Identifier", v.Identifier}, + []string{"IP", v.IP}, + } +} + +func (r *Renderer) UserBlocksList(userBlocks []*management.UserBlock) { + resource := "userBlocks" + + r.Heading(resource) + + if len(userBlocks) == 0 { + r.EmptyState(resource) + r.Infof("No blocks for user.") + return + } + + var res []View + + for _, userBlock := range userBlocks { + res = append(res, &userBlockView{ + Identifier: *userBlock.Identifier, + IP: *userBlock.IP, + }) + } + + r.Results(res) + +} + +func makeUserBlockView(userBlock *management.UserBlock) *userBlockView { + return &userBlockView{ + Identifier: userBlock.GetIdentifier(), + IP: userBlock.GetIP(), + } +}