Skip to content

Commit

Permalink
feat: add roles (#293)
Browse files Browse the repository at this point in the history
* Add roles to auth0 layer

* Update scopes to add roles; update tests

* Add list rolesCmd

* Complete rest of CRUD commands

* Reorder ID/name for role list
  • Loading branch information
cyx authored May 15, 2021
1 parent d297e9f commit 564f3ca
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 3 deletions.
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:
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

0 comments on commit 564f3ca

Please sign in to comment.