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 role permissions #329

Merged
merged 6 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
15 changes: 15 additions & 0 deletions internal/auth0/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,19 @@ type RoleAPI interface {

// Delete a role.
Delete(id string, opts ...management.RequestOption) (err error)

// AssociatePermissions associates permissions to a role.
//
// See: https://auth0.com/docs/api/management/v2#!/Roles/post_role_permission_assignment
AssociatePermissions(id string, permissions []*management.Permission, opts ...management.RequestOption) error

// Permissions retrieves all permissions granted by a role.
//
// See: https://auth0.com/docs/api/management/v2#!/Roles/get_role_permission
Permissions(id string, opts ...management.RequestOption) (p *management.PermissionList, err error)

// RemovePermissions removes permissions associated to a role.
//
// See: https://auth0.com/docs/api/management/v2#!/Roles/delete_role_permission_assignment
RemovePermissions(id string, permissions []*management.Permission, opts ...management.RequestOption) error
}
21 changes: 15 additions & 6 deletions internal/cli/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ auth0 apis create -n myapi -e 6100 --offline-access=true`,
return err
}

if err :=apiOfflineAccess.AskBool(cmd, &inputs.AllowOfflineAccess, nil); err != nil {
if err := apiOfflineAccess.AskBool(cmd, &inputs.AllowOfflineAccess, nil); err != nil {
return err
}

Expand Down Expand Up @@ -240,11 +240,11 @@ auth0 apis create -n myapi -e 6100 --offline-access=true`,

func updateApiCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
Name string
Scopes []string
TokenLifetime int
AllowOfflineAccess bool
ID string
Name string
Scopes []string
TokenLifetime int
AllowOfflineAccess bool
}

cmd := &cobra.Command{
Expand Down Expand Up @@ -497,6 +497,12 @@ func apiDefaultTokenLifetime() int {
}

func (c *cli) apiPickerOptions() (pickerOptions, error) {
return c.filteredAPIPickerOptions(func(r *management.ResourceServer) bool {
return true
})
}

func (c *cli) filteredAPIPickerOptions(include func(r *management.ResourceServer) bool) (pickerOptions, error) {
list, err := c.api.ResourceServer.List()
if err != nil {
return nil, err
Expand All @@ -506,6 +512,9 @@ func (c *cli) apiPickerOptions() (pickerOptions, error) {
// labels.
var opts pickerOptions
for _, r := range list.ResourceServers {
if !include(r) {
continue
}
label := fmt.Sprintf("%s %s", r.GetName(), ansi.Faint("("+r.GetIdentifier()+")"))

opts = append(opts, pickerOption{value: r.GetID(), label: label})
Expand Down
23 changes: 23 additions & 0 deletions internal/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/auth0/auth0-cli/internal/ansi"
"github.com/auth0/auth0-cli/internal/auth0"
"github.com/auth0/auth0-cli/internal/prompt"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -70,6 +71,28 @@ func (f *Flag) SelectU(cmd *cobra.Command, value interface{}, options []string,
return selectFlag(cmd, f, value, options, defaultValue, true)
}

func (f *Flag) Pick(cmd *cobra.Command, result *string, fn pickerOptionsFunc) error {
var opts pickerOptions
err := ansi.Waiting(func() error {
var err error
opts, err = fn()
return err
})

if err != nil {
return err
}

defaultLabel := opts.defaultLabel()
var val string
if err := selectFlag(cmd, f, &val, opts.labels(), &defaultLabel, false); err != nil {
return err
}

*result = opts.getValue(val)
return nil
}

func (f *Flag) EditorPrompt(cmd *cobra.Command, value *string, initialValue, filename string, infoFn func()) error {
out, err := prompt.CaptureInputViaEditor(
initialValue,
Expand Down
1 change: 1 addition & 0 deletions internal/cli/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func rolesCmd(cli *cli) *cobra.Command {
cmd.AddCommand(createRoleCmd(cli))
cmd.AddCommand(updateRoleCmd(cli))
cmd.AddCommand(deleteRoleCmd(cli))
cmd.AddCommand(rolePermissionsCmd(cli))

return cmd
}
Expand Down
268 changes: 268 additions & 0 deletions internal/cli/roles_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package cli

import (
"fmt"
"net/url"

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

var (
roleAPIIdentifier = Flag{
Name: "API",
LongForm: "api-id",
Widcket marked this conversation as resolved.
Show resolved Hide resolved
ShortForm: "a",
Help: "API Identifier.",
IsRequired: true,
}

roleAPIPermissions = Flag{
Name: "Permissions",
LongForm: "permissions",
ShortForm: "p",
Help: "Permissions.",
IsRequired: true,
}
)

func rolePermissionsCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "permissions",
Short: "Manage permissions within the role resource",
Long: "Manage permissions within the role resource.",
}

cmd.SetUsageTemplate(resourceUsageTemplate())
cmd.AddCommand(listRolePermissionsCmd(cli))
cmd.AddCommand(addRolePermissionsCmd(cli))
cmd.AddCommand(removeRolePermissionsCmd(cli))

return cmd
}

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

cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.MaximumNArgs(1),
Short: "List permissions defined within a role",
Long: `List existing permissions defined in a role. To add a permission try:
auth0 roles permissions add <role-id>`,
Example: `auth0 roles permissions list <role-id>
auth0 roles permissions ls`,
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]
}

var list *management.PermissionList

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

cli.renderer.RolePermissionList(list.Permissions)
return nil
},
}

return cmd
}

func addRolePermissionsCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
APIIdentifier string
Permissions []string
}

cmd := &cobra.Command{
Use: "add",
Args: cobra.MaximumNArgs(1),
Short: "Add a permission to a role",
Long: `Add an existing permission defined in one of your APIs.
To add a permission try:

auth0 roles permissions add <role-id> -p <permission-name>`,
Example: `auth0 roles permissions add <role-id> -p <permission-name>
auth0 roles permissions assoc`,
cyx marked this conversation as resolved.
Show resolved Hide resolved
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 err := roleAPIIdentifier.Pick(cmd, &inputs.APIIdentifier, cli.apiPickerOptionsWithoutAuth0); err != nil {
return err
}

var rs *management.ResourceServer

if len(inputs.Permissions) == 0 {
var err error
rs, err = cli.pickRolePermissions(inputs.APIIdentifier, &inputs.Permissions)
if err != nil {
return err
}
}

ps := makePermissions(rs.GetIdentifier(), inputs.Permissions)
if err := cli.api.Role.AssociatePermissions(inputs.ID, ps); err != nil {
return err
}

role, err := cli.api.Role.Read(inputs.ID)
if err != nil {
return err
}

cli.renderer.RolePermissionAdd(role, rs, inputs.Permissions)
return nil
},
}

roleAPIIdentifier.RegisterString(cmd, &inputs.APIIdentifier, "")
Widcket marked this conversation as resolved.
Show resolved Hide resolved
roleAPIPermissions.RegisterStringSlice(cmd, &inputs.Permissions, nil)
return cmd
}

func removeRolePermissionsCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
APIIdentifier string
Permissions []string
}

cmd := &cobra.Command{
Use: "remove",
Aliases: []string{"rm"},
Args: cobra.MaximumNArgs(1),
Short: "Remove a permission from a role",
Long: `Remove an existing permission defined in one of your APIs.
To remove a permission try:

auth0 roles permissions remove <role-id> -p <permission-name>`,
Example: `auth0 roles permissions remove <role-id> -p <permission-name>
auth0 roles permissions rm`,
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 err := roleAPIIdentifier.Pick(cmd, &inputs.APIIdentifier, cli.apiPickerOptionsWithoutAuth0); err != nil {
return err
}

var rs *management.ResourceServer

if len(inputs.Permissions) == 0 {
var err error
rs, err = cli.pickRolePermissions(inputs.APIIdentifier, &inputs.Permissions)
if err != nil {
return err
}
}

ps := makePermissions(rs.GetIdentifier(), inputs.Permissions)
if err := cli.api.Role.RemovePermissions(inputs.ID, ps); err != nil {
return err
}

role, err := cli.api.Role.Read(inputs.ID)
if err != nil {
return err
}

cli.renderer.RolePermissionRemove(role, rs, inputs.Permissions)
return nil
},
}

roleAPIIdentifier.RegisterString(cmd, &inputs.APIIdentifier, "")
Widcket marked this conversation as resolved.
Show resolved Hide resolved
roleAPIPermissions.RegisterStringSlice(cmd, &inputs.Permissions, nil)
return cmd
}

func (c *cli) apiPickerOptionsWithoutAuth0() (pickerOptions, error) {
ten, err := c.getTenant()
if err != nil {
return nil, err
}

return c.filteredAPIPickerOptions(func(r *management.ResourceServer) bool {
u, err := url.Parse(r.GetIdentifier())
if err != nil {
// We really should't get an error here, but for
// correctness it's indeterminate, therefore we return
// false.
return false
}

// We only allow API Identifiers not matching the tenant
// domain, similar to the dashboard UX.
return u.Host != ten.Domain
})
}

func (c *cli) pickRolePermissions(id string, permissions *[]string) (*management.ResourceServer, error) {
// NOTE(cyx): We're inlining this for now since we have no generic
// usecase for this particular picker type yet.
var err error
rs, err := c.api.ResourceServer.Read(id)
if err != nil {
return nil, err
}

var options []string
for _, s := range rs.Scopes {
options = append(options, s.GetValue())
}

p := &survey.MultiSelect{
Message: "Permissions",
Options: options,
}

if err := survey.AskOne(p, permissions); err != nil {
return nil, err
}

return rs, nil
}

func makePermissions(id string, permissions []string) []*management.Permission {
var result []*management.Permission
for _, p := range permissions {
result = append(result, &management.Permission{
ResourceServerIdentifier: auth0.String(id),
Name: auth0.String(p),
})
}
return result
}
Loading