From 209afecbada998aa41421656ed252ea8a4def0fe Mon Sep 17 00:00:00 2001 From: Chris Scott Date: Wed, 27 Jan 2021 11:52:17 -0500 Subject: [PATCH] Basic user get/search command --- internal/auth/auth.go | 1 + internal/auth0/auth0.go | 2 + internal/auth0/user.go | 13 ++++ internal/auth0/user_mock.go | 150 ++++++++++++++++++++++++++++++++++++ internal/cli/root.go | 1 + internal/cli/users.go | 98 +++++++++++++++++++++++ internal/display/users.go | 39 ++++++++++ 7 files changed, 304 insertions(+) create mode 100644 internal/auth0/user.go create mode 100644 internal/auth0/user_mock.go create mode 100644 internal/cli/users.go create mode 100644 internal/display/users.go diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 1745e1cfc..f97dcaf6d 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -46,6 +46,7 @@ var requiredScopes = []string{ "read:client_keys", "read:logs", "create:roles", "delete:roles", "read:roles", "update:roles", "create:custom_domains", "delete:custom_domains", "read:custom_domains", "update:custom_domains", + "read:users", } type Authenticator struct { diff --git a/internal/auth0/auth0.go b/internal/auth0/auth0.go index 5e1f04b7a..12c427e92 100644 --- a/internal/auth0/auth0.go +++ b/internal/auth0/auth0.go @@ -18,6 +18,7 @@ type API struct { ResourceServer ResourceServerAPI Role RoleAPI CustomDomain CustomDomainAPI + User UserAPI } func NewAPI(m *management.Management) *API { @@ -32,6 +33,7 @@ func NewAPI(m *management.Management) *API { Rule: m.Rule, Role: m.Role, CustomDomain: m.CustomDomain, + User: m.User, } } diff --git a/internal/auth0/user.go b/internal/auth0/user.go new file mode 100644 index 000000000..003e84f9f --- /dev/null +++ b/internal/auth0/user.go @@ -0,0 +1,13 @@ +//go:generate mockgen -source=user.go -destination=user_mock.go -package=auth0 + +package auth0 + +import "gopkg.in/auth0.v5/management" + +type UserAPI interface { + // Read a user by its id. + Read(id string, opts ...management.RequestOption) (u *management.User, err error) + + // List users by email. + ListByEmail(email string, opts ...management.RequestOption) (us []*management.User, err error) +} diff --git a/internal/auth0/user_mock.go b/internal/auth0/user_mock.go new file mode 100644 index 000000000..86b2ef4f4 --- /dev/null +++ b/internal/auth0/user_mock.go @@ -0,0 +1,150 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: user.go + +// Package auth0 is a generated GoMock package. +package auth0 + +import ( + gomock "github.com/golang/mock/gomock" + management "gopkg.in/auth0.v5/management" + reflect "reflect" +) + +// MockUserAPI is a mock of UserAPI interface +type MockUserAPI struct { + ctrl *gomock.Controller + recorder *MockUserAPIMockRecorder +} + +// MockUserAPIMockRecorder is the mock recorder for MockUserAPI +type MockUserAPIMockRecorder struct { + mock *MockUserAPI +} + +// NewMockUserAPI creates a new mock instance +func NewMockUserAPI(ctrl *gomock.Controller) *MockUserAPI { + mock := &MockUserAPI{ctrl: ctrl} + mock.recorder = &MockUserAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUserAPI) EXPECT() *MockUserAPIMockRecorder { + return m.recorder +} + +// Create mocks base method +func (m *MockUserAPI) Create(c *management.User, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{c} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create +func (mr *MockUserAPIMockRecorder) Create(c interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{c}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUserAPI)(nil).Create), varargs...) +} + +// Read mocks base method +func (m *MockUserAPI) Read(id string, opts ...management.RequestOption) (*management.UserManager, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Read", varargs...) + ret0, _ := ret[0].(*management.UserManager) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read +func (mr *MockUserAPIMockRecorder) Read(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockUserAPI)(nil).Read), varargs...) +} + +// List mocks base method +func (m *MockUserAPI) List(opts ...management.RequestOption) (*management.UserManager, error) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(*management.UserManager) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List +func (mr *MockUserAPIMockRecorder) List(opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockUserAPI)(nil).List), opts...) +} + +// ListByEmail mocks base method +func (m *MockUserAPI) ListByEmail(email string, opts ...management.RequestOption) (*management.UserManager, error) { + m.ctrl.T.Helper() + varargs := []interface{}{email} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListByEmail", varargs...) + ret0, _ := ret[0].(*management.UserManager) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListByEmail indicates an expected call of ListByEmail +func (mr *MockUserAPIMockRecorder) ListByEmail(email interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{email}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByEmail", reflect.TypeOf((*MockUserAPI)(nil).ListByEmail), varargs...) +} + +// Update mocks base method +func (m *MockUserAPI) Update(id string, c *management.UserManager, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id, c} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockUserAPIMockRecorder) Update(id, c interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id, c}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserAPI)(nil).Update), varargs...) +} + +// Delete mocks base method +func (m *MockUserAPI) Delete(id string, opts ...management.User) error { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete +func (mr *MockUserAPIMockRecorder) Delete(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUserAPI)(nil).Delete), varargs...) +} diff --git a/internal/cli/root.go b/internal/cli/root.go index c8487f78c..a3b750523 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -68,6 +68,7 @@ func Execute() { rootCmd.AddCommand(rolesCmd(cli)) rootCmd.AddCommand(customDomainsCmd(cli)) rootCmd.AddCommand(getTokenCmd(cli)) + rootCmd.AddCommand(usersCmd(cli)) // TODO(cyx): backport this later on using latest auth0/v5. // rootCmd.AddCommand(actionsCmd(cli)) diff --git a/internal/cli/users.go b/internal/cli/users.go new file mode 100644 index 000000000..e625876c3 --- /dev/null +++ b/internal/cli/users.go @@ -0,0 +1,98 @@ +package cli + +import ( + "errors" + + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/spf13/cobra" + "gopkg.in/auth0.v5/management" +) + +func usersCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "users", + Short: "manage users.", + } + + cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(getusersCmd(cli)) + + return cmd +} + +func getusersCmd(cli *cli) *cobra.Command { + var flags struct { + id string + email string + fields string + } + + cmd := &cobra.Command{ + Use: "get", + Short: "Get a user's details", + Long: `$ auth0 users get +Get a user +`, + RunE: func(cmd *cobra.Command, args []string) error { + userID, err := cmd.LocalFlags().GetString("id") + if err != nil { + return err + } + + userEmail, err := cmd.LocalFlags().GetString("email") + if err != nil { + return err + } + + if userID == "" && userEmail == "" { + return errors.New("User id or email flag must be specified") + } + + if userID != "" && userEmail != "" { + return errors.New("User id and email flags cannot be combined") + } + + var users []*management.User + var user *management.User + + if userID != "" { + err := ansi.Spinner("Getting user", func() error { + var err error + user, err = cli.api.User.Read(flags.id) + return err + }) + + if err != nil { + return err + } + + users = append(users, user) + + cli.renderer.UserList(users) + return nil + } + + if userEmail != "" { + err := ansi.Spinner("Getting user(s)", func() error { + var err error + users, err = cli.api.User.ListByEmail(userEmail) + return err + }) + + if err != nil { + return err + } + + cli.renderer.UserList(users) + return nil + } + + return nil + }, + } + + cmd.Flags().StringVarP(&flags.id, "id", "i", "", "User ID of user to get.") + cmd.Flags().StringVarP(&flags.email, "email", "e", "", "Email of user to get.") + + return cmd +} diff --git a/internal/display/users.go b/internal/display/users.go new file mode 100644 index 000000000..7ba43623f --- /dev/null +++ b/internal/display/users.go @@ -0,0 +1,39 @@ +package display + +import ( + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/auth0/auth0-cli/internal/auth0" + "gopkg.in/auth0.v5/management" +) + +type userView struct { + UserID string + Connection string + Name string + Email string + LatestLogin string +} + +func (v *userView) AsTableHeader() []string { + return []string{"User ID", "Name", "Email", "Latest Login"} +} + +func (v *userView) AsTableRow() []string { + return []string{ansi.Faint(v.UserID), v.Name, v.Email, v.LatestLogin} +} + +func (r *Renderer) UserList(users []*management.User) { + r.Heading(ansi.Bold(r.Tenant), "users\n") + + var res []View + for _, u := range users { + res = append(res, &userView{ + UserID: auth0.StringValue(u.ID), + Name: auth0.StringValue(u.Name), + Email: auth0.StringValue(u.Email), + LatestLogin: u.LastLogin.String(), + }) + } + + r.Results(res) +}