Skip to content
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,30 @@ onecli user list --user-id 123
# Add a new user
onecli user add "John" "Doe" "john.doe@example.com"

# Set a password for an existing user
onecli user set-password --email user@example.com --password "$PASSWORD"

# Set a user's status (1=Active, 2=Suspended, 4=PasswordExpired, 5=AwaitingPasswordReset)
onecli user set-status --email user@example.com --status 4

# Send a password setup/reset invite link via email
onecli user send-invite --email user@example.com
onecli user send-invite --email user@example.com --personal-email personal@example.com

# Modify user email
onecli user modify email "newemail@example.com" --email "oldemail@example.com"
```

These single-purpose commands can be chained to create and invite a user in one go.
Setting status `4` (PasswordExpired) forces a password change on first login:

```bash
onecli user add "John" "Doe" "john.doe@example.com" && \
onecli user set-password --email john.doe@example.com --password "$PASSWORD" && \
onecli user set-status --email john.doe@example.com --status 4 && \
onecli user send-invite --email john.doe@example.com
```

### App Management

```bash
Expand Down
167 changes: 146 additions & 21 deletions cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ var (
userQueryLastname string
userQueryUserID string
output string

sendInvitePersonalEmail string
setPasswordValue string
setStatusValue int32
)

// initClient initializes the OneLogin client
Expand Down Expand Up @@ -84,24 +88,13 @@ var modifyEmailCmd = &cobra.Command{
return err
}

users, err := client.GetUsers(query)
user, err := findUserByQuery(client, query)
if err != nil {
return fmt.Errorf("error getting users: %v", err)
}

if len(users) == 0 {
return fmt.Errorf("no users found matching the query")
}

if len(users) > 1 {
return fmt.Errorf("multiple users found matching the query. Please be more specific")
return err
}

user := users[0]
user.Email = newEmail

err = client.UpdateUser(int(user.ID), user)
if err != nil {
if err := client.UpdateUser(int(user.ID), user); err != nil {
return fmt.Errorf("error updating user: %v", err)
}

Expand All @@ -111,18 +104,19 @@ var modifyEmailCmd = &cobra.Command{
}

var addCmd = &cobra.Command{
Use: "add <first-name> <last-name> <email>",
Short: "Add a new user",
Long: `Add a new user to your OneLogin organization`,
Args: cobra.ExactArgs(3),
Use: "add <first-name> <last-name> <email>",
Short: "Add a new user",
Long: `Add a new user to your OneLogin organization`,
Args: cobra.ExactArgs(3),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
firstName := args[0]
lastName := args[1]
email := args[2]

client, err := initClient()
if err != nil {
return fmt.Errorf("error initializing OneLogin client: %v", err)
return err
}

newUser := onelogin.User{
Expand All @@ -131,8 +125,7 @@ var addCmd = &cobra.Command{
Email: email,
}

err = client.CreateUser(newUser)
if err != nil {
if _, err := client.CreateUser(newUser); err != nil {
return fmt.Errorf("error creating user: %v", err)
}

Expand All @@ -141,6 +134,96 @@ var addCmd = &cobra.Command{
},
}

var setPasswordCmd = &cobra.Command{
Use: "set-password",
Short: "Set a password for a user",
Long: `Set a password for an existing OneLogin user`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
query := getUserQuery()
if isQueryParamsEmpty(query) {
return fmt.Errorf("at least one query parameter (email, username, firstname, lastname, or user-id) must be specified")
}

client, err := initClient()
if err != nil {
return err
}

user, err := findUserByQuery(client, query)
if err != nil {
return err
}

if err := client.SetPassword(int(user.ID), setPasswordValue); err != nil {
return fmt.Errorf("error setting password: %v", err)
}

fmt.Printf("Successfully set password for %s\n", user.Email)
return nil
},
}

var setStatusCmd = &cobra.Command{
Use: "set-status",
Short: "Set the status of a user",
Long: `Set the status of an existing OneLogin user (1=Active, 2=Suspended, 4=PasswordExpired, 5=AwaitingPasswordReset)`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
query := getUserQuery()
if isQueryParamsEmpty(query) {
return fmt.Errorf("at least one query parameter (email, username, firstname, lastname, or user-id) must be specified")
}

client, err := initClient()
if err != nil {
return err
}

user, err := findUserByQuery(client, query)
if err != nil {
return err
}

if err := client.UpdateUser(int(user.ID), onelogin.User{Status: setStatusValue}); err != nil {
return fmt.Errorf("error setting user status: %v", err)
}

fmt.Printf("Successfully set status %d for %s\n", setStatusValue, user.Email)
return nil
},
}

var sendInviteCmd = &cobra.Command{
Use: "send-invite",
Short: "Send a password setup/reset invite link to a user",
Long: `Send a password setup/reset invite link to a OneLogin user via email.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
query := getUserQuery()
if isQueryParamsEmpty(query) {
return fmt.Errorf("at least one query parameter (email, username, firstname, lastname, or user-id) must be specified")
}

client, err := initClient()
if err != nil {
return err
}

user, err := findUserByQuery(client, query)
if err != nil {
return err
}

if err := client.SendInviteLink(user.Email, sendInvitePersonalEmail); err != nil {
return fmt.Errorf("error sending invite link: %v", err)
}

fmt.Printf("Successfully sent invite link for %s\n", user.Email)
return nil
},
}

func getUserQuery() onelogin.UserQuery {
query := onelogin.UserQuery{}

Expand Down Expand Up @@ -172,11 +255,30 @@ func isQueryParamsEmpty(params onelogin.UserQuery) bool {
return params.Email == nil && params.Username == nil && params.Firstname == nil && params.Lastname == nil && params.UserIDs == nil
}

// findUserByQuery looks up a single user matching the query and returns it.
// Errors if no user matches or if more than one matches.
func findUserByQuery(client *onelogin.Onelogin, query onelogin.UserQuery) (onelogin.User, error) {
users, err := client.GetUsers(query)
if err != nil {
return onelogin.User{}, fmt.Errorf("error getting users: %v", err)
}
if len(users) == 0 {
return onelogin.User{}, fmt.Errorf("no users found matching the query")
}
if len(users) > 1 {
return onelogin.User{}, fmt.Errorf("multiple users found matching the query. Please be more specific")
}
return users[0], nil
}

func init() {
userCmd.AddCommand(listCmd)
userCmd.AddCommand(modifyCmd)
modifyCmd.AddCommand(modifyEmailCmd)
userCmd.AddCommand(addCmd)
userCmd.AddCommand(setPasswordCmd)
userCmd.AddCommand(setStatusCmd)
userCmd.AddCommand(sendInviteCmd)

listCmd.Flags().StringVarP(&output, "output", "o", "yaml", "Output format (yaml, json, csv)")
listCmd.Flags().StringVar(&userQueryEmail, "email", "", "Filter users by email")
Expand All @@ -190,4 +292,27 @@ func init() {
modifyEmailCmd.Flags().StringVar(&userQueryFirstname, "firstname", "", "Query by first name")
modifyEmailCmd.Flags().StringVar(&userQueryLastname, "lastname", "", "Query by last name")
modifyEmailCmd.Flags().StringVar(&userQueryUserID, "user-id", "", "Query by user ID")

setPasswordCmd.Flags().StringVar(&userQueryEmail, "email", "", "Query by email")
setPasswordCmd.Flags().StringVar(&userQueryUsername, "username", "", "Query by username")
setPasswordCmd.Flags().StringVar(&userQueryFirstname, "firstname", "", "Query by first name")
setPasswordCmd.Flags().StringVar(&userQueryLastname, "lastname", "", "Query by last name")
setPasswordCmd.Flags().StringVar(&userQueryUserID, "user-id", "", "Query by user ID")
setPasswordCmd.Flags().StringVar(&setPasswordValue, "password", "", "New password (required)")
_ = setPasswordCmd.MarkFlagRequired("password")

setStatusCmd.Flags().StringVar(&userQueryEmail, "email", "", "Query by email")
setStatusCmd.Flags().StringVar(&userQueryUsername, "username", "", "Query by username")
setStatusCmd.Flags().StringVar(&userQueryFirstname, "firstname", "", "Query by first name")
setStatusCmd.Flags().StringVar(&userQueryLastname, "lastname", "", "Query by last name")
setStatusCmd.Flags().StringVar(&userQueryUserID, "user-id", "", "Query by user ID")
setStatusCmd.Flags().Int32Var(&setStatusValue, "status", 0, "Status value (1=Active, 2=Suspended, 4=PasswordExpired, 5=AwaitingPasswordReset) (required)")
_ = setStatusCmd.MarkFlagRequired("status")

sendInviteCmd.Flags().StringVar(&userQueryEmail, "email", "", "Query by email")
sendInviteCmd.Flags().StringVar(&userQueryUsername, "username", "", "Query by username")
sendInviteCmd.Flags().StringVar(&userQueryFirstname, "firstname", "", "Query by first name")
sendInviteCmd.Flags().StringVar(&userQueryLastname, "lastname", "", "Query by last name")
sendInviteCmd.Flags().StringVar(&userQueryUserID, "user-id", "", "Query by user ID")
sendInviteCmd.Flags().StringVar(&sendInvitePersonalEmail, "personal-email", "", "Optional alternate email to send the invite link to")
}
3 changes: 3 additions & 0 deletions onelogin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
type (
User = models.User
UserQuery = models.UserQuery
Invite = models.Invite
)

type (
Expand All @@ -24,6 +25,8 @@ type Client interface {
GetUsers(query models.Queryable) (any, error)
UpdateUser(userID int, user models.User) (any, error)
CreateUser(user models.User) (any, error)
UpdatePasswordInsecure(userID int, requestBody any) (any, error)
SendInviteLink(invite models.Invite) (any, error)
GetApps(query models.Queryable) (any, error)
GetAppUsers(appID int, query models.Queryable) (any, error)
ListEvents(query models.Queryable) (any, error)
Expand Down
53 changes: 29 additions & 24 deletions onelogin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package onelogin
import (
"fmt"
"strconv"
"time"

"github.com/pepabo/onecli/utils"
)
Expand All @@ -23,36 +22,42 @@ func (o *Onelogin) GetUsers(query UserQuery) ([]User, error) {
// UpdateUser updates a user in Onelogin
func (o *Onelogin) UpdateUser(userID int, user User) error {
_, err := o.client.UpdateUser(userID, user)
if err != nil {
return err
}
return nil
return err
}

// SetUserState sets the user state to active and updates the last login time
func (o *Onelogin) SetUserState(userID int) error {
user := User{
Status: 1,
LastLogin: time.Now(),
}
err := o.UpdateUser(userID, user)
// CreateUser creates a new user in Onelogin and returns the created user's ID
func (o *Onelogin) CreateUser(user User) (int, error) {
result, err := o.client.CreateUser(user)
if err != nil {
return fmt.Errorf("error setting user state: %v", err)
return 0, err
}

return nil
resultMap, ok := result.(map[string]any)
if !ok {
return 0, fmt.Errorf("unexpected response type from create user: %T", result)
}
idFloat, ok := resultMap["id"].(float64)
if !ok {
return 0, fmt.Errorf("missing or invalid id in create user response")
}
return int(idFloat), nil
}

// CreateUser creates a new user in Onelogin
func (o *Onelogin) CreateUser(user User) error {
result, err := o.client.CreateUser(user)
if err != nil {
return fmt.Errorf("error creating user: %v", err)
// SetPassword sets a password for a user
func (o *Onelogin) SetPassword(userID int, password string) error {
body := map[string]string{
"password": password,
"password_confirmation": password,
}
_, err := o.client.UpdatePasswordInsecure(userID, body)
return err
}

if err := o.SetUserState(int(result.(User).ID)); err != nil {
return err
// SendInviteLink sends a password setup/reset invite link to a user
func (o *Onelogin) SendInviteLink(email, personalEmail string) error {
invite := Invite{
Email: email,
PersonalEmail: personalEmail,
}

return nil
_, err := o.client.SendInviteLink(invite)
return err
}
Loading
Loading