diff --git a/docs/auth0_users_roles.md b/docs/auth0_users_roles.md index 3aaa3c0c3..f56c426f7 100644 --- a/docs/auth0_users_roles.md +++ b/docs/auth0_users_roles.md @@ -7,5 +7,6 @@ Manage a user's assigned roles. To learn more about roles and their behavior, re ## Commands +- [auth0 users roles assign](auth0_users_roles_assign.md) - Assign roles to a user - [auth0 users roles show](auth0_users_roles_show.md) - Show a user's roles diff --git a/docs/auth0_users_roles_assign.md b/docs/auth0_users_roles_assign.md new file mode 100644 index 000000000..19f8f5174 --- /dev/null +++ b/docs/auth0_users_roles_assign.md @@ -0,0 +1,45 @@ +--- +layout: default +--- +# auth0 users roles assign + +Assign existing roles to a user. + +## Usage +``` +auth0 users roles assign [flags] +``` + +## Examples + +``` + auth0 users roles assign + auth0 users roles add --roles + auth0 users roles add -r "rol_1eKJp3jV04SiU04h,rol_2eKJp3jV04SiU04h" --json +``` + + +## Flags + +``` + --json Output in json format. + -r, --roles strings Roles to assign to a user. +``` + + +## InheritedFlags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 users roles assign](auth0_users_roles_assign.md) - Assign roles to a user +- [auth0 users roles show](auth0_users_roles_show.md) - Show a user's roles + + diff --git a/docs/auth0_users_roles_show.md b/docs/auth0_users_roles_show.md index 5766b5b6b..07083f266 100644 --- a/docs/auth0_users_roles_show.md +++ b/docs/auth0_users_roles_show.md @@ -40,6 +40,7 @@ auth0 users roles show [flags] ## Related Commands +- [auth0 users roles assign](auth0_users_roles_assign.md) - Assign roles to a user - [auth0 users roles show](auth0_users_roles_show.md) - Show a user's roles diff --git a/internal/auth0/user.go b/internal/auth0/user.go index 3ffd29b9d..f48bc83ac 100644 --- a/internal/auth0/user.go +++ b/internal/auth0/user.go @@ -30,4 +30,7 @@ type UserAPI interface { // Roles lists all roles associated with a user. Roles(id string, opts ...management.RequestOption) (r *management.RoleList, err error) + + // AssignRoles assigns roles to a user. + AssignRoles(id string, roles []*management.Role, opts ...management.RequestOption) error } diff --git a/internal/cli/users_roles.go b/internal/cli/users_roles.go index 281c6ea93..422877b59 100644 --- a/internal/cli/users_roles.go +++ b/internal/cli/users_roles.go @@ -2,9 +2,14 @@ package cli import ( "fmt" + "strings" + "github.com/AlecAivazis/survey/v2" "github.com/auth0/go-auth0/management" "github.com/spf13/cobra" + + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/auth0/auth0-cli/internal/auth0" ) var ( @@ -16,6 +21,22 @@ var ( } ) +var ( + userRoles = Flag{ + Name: "Roles", + LongForm: "roles", + ShortForm: "r", + Help: "Roles to assign to a user.", + IsRequired: true, + } +) + +type userRolesInput struct { + ID string + Number int + Roles []string +} + func userRolesCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "roles", @@ -26,15 +47,13 @@ func userRolesCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(showUserRolesCmd(cli)) + cmd.AddCommand(addUserRolesCmd(cli)) return cmd } func showUserRolesCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - Number int - } + var inputs userRolesInput cmd := &cobra.Command{ Use: "show", @@ -95,3 +114,123 @@ func showUserRolesCmd(cli *cli) *cobra.Command { return cmd } + +func addUserRolesCmd(cli *cli) *cobra.Command { + var inputs userRolesInput + + cmd := &cobra.Command{ + Use: "assign", + Aliases: []string{"add"}, + Args: cobra.MaximumNArgs(1), + Short: "Assign roles to a user", + Long: "Assign existing roles to a user.", + Example: ` auth0 users roles assign + auth0 users roles add --roles + auth0 users roles add -r "rol_1eKJp3jV04SiU04h,rol_2eKJp3jV04SiU04h" --json`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + if err := userID.Ask(cmd, &inputs.ID); err != nil { + return err + } + } else { + inputs.ID = args[0] + } + + if len(inputs.Roles) == 0 { + if err := cli.pickUserRoles(&inputs); err != nil { + return err + } + } + + var rolesToAssign []*management.Role + for _, roleID := range inputs.Roles { + rolesToAssign = append(rolesToAssign, &management.Role{ + ID: auth0.String(roleID), + }) + } + + if err := ansi.Waiting(func() (err error) { + return cli.api.User.AssignRoles(inputs.ID, rolesToAssign) + }); err != nil { + return fmt.Errorf("failed to assign roles for user with ID %s: %w", inputs.ID, err) + } + + var userRoleList *management.RoleList + if err := ansi.Waiting(func() (err error) { + userRoleList, err = cli.api.User.Roles(inputs.ID) + return err + }); err != nil { + return fmt.Errorf("failed to find roles for user with ID %s: %w", inputs.ID, err) + } + + cli.renderer.UserRoleList(userRoleList.Roles) + + return nil + }, + } + + userRoles.RegisterStringSlice(cmd, &inputs.Roles, nil) + cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") + + return cmd +} + +func (cli *cli) pickUserRoles(inputs *userRolesInput) error { + var currentUserRoleList *management.RoleList + if err := ansi.Waiting(func() (err error) { + currentUserRoleList, err = cli.api.User.Roles(inputs.ID, management.PerPage(100)) + return err + }); err != nil { + return fmt.Errorf("failed to find the current roles for user with ID %s: %w", inputs.ID, err) + } + + var roleList *management.RoleList + if err := ansi.Waiting(func() (err error) { + roleList, err = cli.api.Role.List() + return err + }); err != nil { + return fmt.Errorf("failed to list all roles: %w", err) + } + + if len(roleList.Roles) == len(currentUserRoleList.Roles) { + return fmt.Errorf("the user with ID %q has all roles assigned already", inputs.ID) + } + + const emptySpace = " " + var options []string + for _, role := range roleList.Roles { + if !containsRole(currentUserRoleList.Roles, role.GetID()) { + options = append(options, fmt.Sprintf("%s%s(Name: %s)", role.GetID(), emptySpace, role.GetName())) + } + } + + rolesPrompt := &survey.MultiSelect{ + Message: "Roles", + Options: options, + } + + var selectedRoles []string + if err := survey.AskOne(rolesPrompt, &selectedRoles); err != nil { + return err + } + + for _, selectedRole := range selectedRoles { + indexOfFirstEmptySpace := strings.Index(selectedRole, emptySpace) + inputs.Roles = append(inputs.Roles, selectedRole[:indexOfFirstEmptySpace]) + } + + if len(inputs.Roles) == 0 { + return fmt.Errorf("required to select at least one role") + } + + return nil +} + +func containsRole(roles []*management.Role, roleID string) bool { + for _, role := range roles { + if role.GetID() == roleID { + return true + } + } + return false +} diff --git a/test/integration/test-cases.yaml b/test/integration/test-cases.yaml index 6c496dec2..3597d456c 100644 --- a/test/integration/test-cases.yaml +++ b/test/integration/test-cases.yaml @@ -464,10 +464,6 @@ tests: email: betteruser@example.com # Name is not being displayed, hence using email exit-code: 0 - users roles show: - command: auth0 users roles show $(cat ./test/integration/identifiers/user-id) - exit-code: 0 - # Test 'roles create' roles create and check data: command: auth0 roles create --name integration-test-role-new1 --description testRole --json --no-input @@ -736,3 +732,11 @@ tests: update universal login branding prompts (mfa-push): command: cat ./test/integration/fixtures/update-ul-prompts-mfa-push.json | auth0 ul prompts update mfa-push exit-code: 0 + + users roles show: + command: auth0 users roles show $(cat ./test/integration/identifiers/user-id) + exit-code: 0 + + users roles add: + command: auth0 users roles add $(cat ./test/integration/identifiers/user-id) -r $(cat ./test/integration/identifiers/role-id) + exit-code: 0