Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add roles #293

Merged
merged 6 commits into from
May 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ var requiredScopes = []string{
"offline_access", // <-- to get a refresh token.
"create:clients", "delete:clients", "read:clients", "update:clients",
"create:resource_servers", "delete:resource_servers", "read:resource_servers", "update:resource_servers",
"create:roles", "delete:roles", "read:roles", "update:roles",
"create:rules", "delete:rules", "read:rules", "update:rules",
"read:client_keys", "read:logs", "read:connections", "update:connections",
"create:users", "delete:users", "read:users", "update:users",
"read:branding", "update:branding",
"read:connections", "update:connections",
"read:client_keys", "read:logs", "read:tenant_settings", "read:custom_domains",
}

Expand Down
6 changes: 4 additions & 2 deletions internal/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ func TestRequiredScopes(t *testing.T) {
crudResources := []string{
"clients",
"resource_servers",
"roles",
"rules",
"users",
}
crudPrefixes := []string{"create:", "delete:", "read:", "update:"}

Expand All @@ -24,9 +26,9 @@ func TestRequiredScopes(t *testing.T) {

t.Run("verify special scopes", func(t *testing.T) {
list := []string{
"read:client_keys", "read:logs",
"read:users", "update:users",
"read:branding", "update:branding",
"read:connections", "update:connections",
"read:custom_domains",
"read:client_keys", "read:logs", "read:tenant_settings",
}

Expand Down
2 changes: 2 additions & 0 deletions internal/auth0/auth0.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type API struct {
CustomDomain CustomDomainAPI
Log LogAPI
ResourceServer ResourceServerAPI
Role RoleAPI
Rule RuleAPI
Tenant TenantAPI
User UserAPI
Expand All @@ -26,6 +27,7 @@ func NewAPI(m *management.Management) *API {
CustomDomain: m.CustomDomain,
Log: m.Log,
ResourceServer: m.ResourceServer,
Role: m.Role,
Rule: m.Rule,
Tenant: m.Tenant,
User: m.User,
Expand Down
22 changes: 22 additions & 0 deletions internal/auth0/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//go:generate mockgen -source=role.go -destination=role_mock.go -package=auth0

package auth0

import "gopkg.in/auth0.v5/management"

type RoleAPI interface {
// Create a new role.
Create(r *management.Role, opts ...management.RequestOption) (err error)

// Retrieve a role.
Read(id string, opts ...management.RequestOption) (r *management.Role, err error)

// List all roles that can be assigned to users or groups.
List(opts ...management.RequestOption) (r *management.RoleList, err error)

// Update a role.
Update(id string, r *management.Role, opts ...management.RequestOption) (err error)

// Delete a role.
Delete(id string, opts ...management.RequestOption) (err error)
}
303 changes: 303 additions & 0 deletions internal/cli/roles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
package cli

import (
"errors"
"fmt"

"github.com/auth0/auth0-cli/internal/ansi"
"github.com/auth0/auth0-cli/internal/prompt"
"github.com/spf13/cobra"
"gopkg.in/auth0.v5/management"
)

// errNoRoles signifies no roles exist in a tenant
var errNoRoles = errors.New("there are currently no roles")

var (
roleID = Argument{
Name: "Role ID",
Help: "Id of the role.",
}
roleName = Flag{
Name: "Name",
LongForm: "name",
ShortForm: "n",
Help: "Name of the role.",
IsRequired: true,
}
roleDescription = Flag{
Name: "Description",
LongForm: "description",
ShortForm: "d",
Help: "Description of the role.",
// IsRequired: true,
}
)

func rolesCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "roles",
Short: "Manage resources for roles",
Long: "Manage resources for roles.",
Aliases: []string{"role"},
}

cmd.SetUsageTemplate(resourceUsageTemplate())
cmd.AddCommand(listRolesCmd(cli))
cmd.AddCommand(showRoleCmd(cli))
cmd.AddCommand(createRoleCmd(cli))
cmd.AddCommand(updateRoleCmd(cli))
cmd.AddCommand(deleteRoleCmd(cli))

return cmd
}

func listRolesCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.NoArgs,
Short: "List your roles",
Long: `List your existing roles. To create one try:
cyx marked this conversation as resolved.
Show resolved Hide resolved
auth0 roles create`,
Example: `auth0 roles list
auth0 roles ls`,
RunE: func(cmd *cobra.Command, args []string) error {
var list *management.RoleList

if err := ansi.Waiting(func() error {
var err error
list, err = cli.api.Role.List()
return err
}); err != nil {
return fmt.Errorf("An unexpected error occurred: %w", err)
}

cli.renderer.RoleList(list.Roles)
return nil
},
}

return cmd
}

func showRoleCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
}

cmd := &cobra.Command{
Use: "show",
Args: cobra.MaximumNArgs(1),
Short: "Show a role",
Long: "Show a role.",
Example: `auth0 roles show
auth0 roles show <id>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
err := roleID.Pick(cmd, &inputs.ID, cli.rolePickerOptions)
if err != nil {
return err
}
} else {
inputs.ID = args[0]
}

r := &management.Role{ID: &inputs.ID}

if err := ansi.Waiting(func() error {
var err error
r, err = cli.api.Role.Read(inputs.ID)
return err
}); err != nil {
return fmt.Errorf("Unable to load role. The Id %v specified doesn't exist", inputs.ID)
}

cli.renderer.RoleShow(r)
return nil
},
}

return cmd
}

func createRoleCmd(cli *cli) *cobra.Command {
var inputs struct {
Name string
Description string
}

cmd := &cobra.Command{
Use: "create",
Args: cobra.NoArgs,
Short: "Create a new role",
Long: "Create a new role.",
Example: `auth0 roles create
auth0 roles create --name myrole
auth0 roles create -n myrole --description "awesome role"`,
RunE: func(cmd *cobra.Command, args []string) error {
// Prompt for role name
if err := roleName.Ask(cmd, &inputs.Name, nil); err != nil {
return err
}

// Prompt for role description
if err := roleDescription.Ask(cmd, &inputs.Description, nil); err != nil {
return err
}

// Load values into a fresh role instance
r := &management.Role{
Name: &inputs.Name,
Description: &inputs.Description,
}

// Create role
if err := ansi.Waiting(func() error {
return cli.api.Role.Create(r)
}); err != nil {
return fmt.Errorf("Unable to create role: %v", err)
}

// Render role creation specific view
cli.renderer.RoleCreate(r)
return nil
},
}

roleName.RegisterString(cmd, &inputs.Name, "")
roleDescription.RegisterString(cmd, &inputs.Description, "")

return cmd
}

func updateRoleCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
Name string
Description string
}

cmd := &cobra.Command{
Use: "update",
Args: cobra.MaximumNArgs(1),
Short: "Update a role",
Long: "Update a role.",
Example: `auth0 roles update
auth0 roles update <id> --name myrole
auth0 roles update <id> -n myrole --description "awesome role"`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
err := roleID.Pick(cmd, &inputs.ID, cli.rolePickerOptions)
if err != nil {
return err
}
} else {
inputs.ID = args[0]
}

// Prompt for role name
if err := roleName.AskU(cmd, &inputs.Name, nil); err != nil {
return err
}

// Prompt for role description
if err := roleDescription.AskU(cmd, &inputs.Description, nil); err != nil {
return err
}

// Start with an empty role object. We'll conditionally
// hydrate it based on the provided parameters since
// we'll do PATCH semantics.
r := &management.Role{}

if inputs.Name != "" {
r.Name = &inputs.Name
}

if inputs.Description != "" {
r.Description = &inputs.Description
}

// Update role
if err := ansi.Waiting(func() error {
return cli.api.Role.Update(inputs.ID, r)
}); err != nil {
return fmt.Errorf("Unable to update role: %v", err)
}

// Render role creation specific view
cli.renderer.RoleUpdate(r)
return nil
},
}

roleName.RegisterStringU(cmd, &inputs.Name, "")
roleDescription.RegisterStringU(cmd, &inputs.Description, "")

return cmd
}

func deleteRoleCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
}

cmd := &cobra.Command{
Use: "delete",
Args: cobra.MaximumNArgs(1),
Short: "Delete an role",
Long: "Delete an role.",
Example: `auth0 roles delete
auth0 roles delete <id>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
err := roleID.Pick(cmd, &inputs.ID, cli.rolePickerOptions)
if err != nil {
return err
}
} else {
inputs.ID = args[0]
}

if !cli.force && canPrompt(cmd) {
if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed {
return nil
}
}

return ansi.Spinner("Deleting Role", func() error {
_, err := cli.api.Role.Read(inputs.ID)

if err != nil {
return fmt.Errorf("Unable to delete role. The specified Id: %v doesn't exist", inputs.ID)
}

return cli.api.Role.Delete(inputs.ID)
})
},
}

return cmd
}

func (c *cli) rolePickerOptions() (pickerOptions, error) {
list, err := c.api.Role.List()
if err != nil {
return nil, err
}

var opts pickerOptions

for _, c := range list.Roles {
value := c.GetID()
label := fmt.Sprintf("%s %s", c.GetName(), ansi.Faint("("+value+")"))
opts = append(opts, pickerOption{value: value, label: label})
}

if len(opts) == 0 {
return nil, errNoRoles
}

return opts, nil
}
1 change: 1 addition & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func Execute() {
rootCmd.AddCommand(logsCmd(cli))
rootCmd.AddCommand(logoutCmd(cli))
rootCmd.AddCommand(brandingCmd(cli))
rootCmd.AddCommand(rolesCmd(cli))

// keep completion at the bottom:
rootCmd.AddCommand(completionCmd(cli))
Expand Down
Loading