Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go stable) (pull_request) Successful in 1m21s
Tests / Test (Go 1.25) (pull_request) Successful in 1m27s
The conversion / link / sheet commits predate the tree cleanup, so their comments and README/test examples still carried internal refs. Re-apply the genericization (no internal hosts, personal names or client file ids).
402 lines
11 KiB
Go
402 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
onlyoffice "github.com/eslider/go-onlyoffice"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var usersCmd = &cobra.Command{
|
|
Use: "users",
|
|
Short: "Portal / workspace users",
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(usersCmd)
|
|
usersCmd.AddCommand(usersListCmd())
|
|
usersCmd.AddCommand(usersSelfCmd())
|
|
usersCmd.AddCommand(usersGetCmd())
|
|
usersCmd.AddCommand(usersCreateCmd())
|
|
usersCmd.AddCommand(usersUpdateCmd())
|
|
usersCmd.AddCommand(usersDeleteCmd())
|
|
usersCmd.AddCommand(usersBlockCmd())
|
|
usersCmd.AddCommand(usersUnblockCmd())
|
|
usersCmd.AddCommand(usersPasswordCmd())
|
|
usersCmd.AddCommand(usersCheckCmd())
|
|
rootCmd.AddCommand(whoamiCmd())
|
|
}
|
|
|
|
func usersListCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "list",
|
|
Short: "List portal users",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
users, err := c.GetUsers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows := make([]map[string]any, 0, len(users))
|
|
for _, u := range users {
|
|
rows = append(rows, map[string]any{
|
|
"id": derefString(u.ID),
|
|
"userName": derefString(u.UserName),
|
|
"displayName": derefString(u.DisplayName),
|
|
"email": derefString(u.Email),
|
|
"isAdmin": derefBool(u.IsAdmin),
|
|
"status": derefInt(u.Status),
|
|
})
|
|
}
|
|
printTable([]string{"id", "userName", "displayName", "email", "isAdmin", "status"}, rows)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersSelfCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "self",
|
|
Short: "Show current user id (people/@self)",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, err := c.SelfUserID(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printObject(map[string]any{"id": id})
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersGetCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "get USER_ID",
|
|
Short: "Show one portal user profile",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u, err := c.GetUser(cmd.Context(), args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printObject(u)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersCreateCmd() *cobra.Command {
|
|
var first, last, email, password, title, location, sex, comment string
|
|
var visitor bool
|
|
cmd := &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a portal user",
|
|
Long: `Create a portal user (POST /api/2.0/people).
|
|
|
|
Without --password the portal generates one and the account stays NotActivated
|
|
until the user follows the activation link. With --password the account is
|
|
Active immediately. Use --visitor for a guest account.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if email == "" || first == "" || last == "" {
|
|
return fmt.Errorf("--email, --first and --last are required")
|
|
}
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req := onlyoffice.NewUserRequest{
|
|
FirstName: first,
|
|
LastName: last,
|
|
Email: email,
|
|
Password: password,
|
|
Title: title,
|
|
Location: location,
|
|
Sex: sex,
|
|
Comment: comment,
|
|
}
|
|
if cmd.Flags().Changed("visitor") {
|
|
req.IsVisitor = &visitor
|
|
}
|
|
u, err := c.CreateUser(cmd.Context(), req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printObject(map[string]any{
|
|
"id": idString(u, "id"),
|
|
"displayName": idString(u, "displayName"),
|
|
"email": idString(u, "email"),
|
|
"status": u["status"],
|
|
})
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(&first, "first", "", "first name (required)")
|
|
cmd.Flags().StringVar(&last, "last", "", "last name (required)")
|
|
cmd.Flags().StringVar(&email, "email", "", "email (required)")
|
|
cmd.Flags().StringVar(&password, "password", "", "initial password (default: portal-generated)")
|
|
cmd.Flags().StringVar(&title, "title", "", "job title")
|
|
cmd.Flags().StringVar(&location, "location", "", "location")
|
|
cmd.Flags().StringVar(&sex, "sex", "", "sex: male|female")
|
|
cmd.Flags().StringVar(&comment, "comment", "", "comment")
|
|
cmd.Flags().BoolVar(&visitor, "visitor", false, "create as guest (isVisitor=true)")
|
|
return cmd
|
|
}
|
|
|
|
func usersUpdateCmd() *cobra.Command {
|
|
var first, last, email, title, location, sex, comment string
|
|
cmd := &cobra.Command{
|
|
Use: "update USER_ID",
|
|
Short: "Update portal user profile fields (only flags passed)",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
body := map[string]any{}
|
|
if cmd.Flags().Changed("first") {
|
|
body["firstname"] = first
|
|
}
|
|
if cmd.Flags().Changed("last") {
|
|
body["lastname"] = last
|
|
}
|
|
if cmd.Flags().Changed("email") {
|
|
body["email"] = email
|
|
}
|
|
if cmd.Flags().Changed("title") {
|
|
body["title"] = title
|
|
}
|
|
if cmd.Flags().Changed("location") {
|
|
body["location"] = location
|
|
}
|
|
if cmd.Flags().Changed("sex") {
|
|
body["sex"] = sex
|
|
}
|
|
if cmd.Flags().Changed("comment") {
|
|
body["comment"] = comment
|
|
}
|
|
if len(body) == 0 {
|
|
return fmt.Errorf("nothing to update: pass at least one of --first/--last/--email/--title/--location/--sex/--comment")
|
|
}
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u, err := c.UpdateUser(cmd.Context(), args[0], body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printObject(map[string]any{
|
|
"id": idString(u, "id"),
|
|
"displayName": idString(u, "displayName"),
|
|
})
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(&first, "first", "", "first name")
|
|
cmd.Flags().StringVar(&last, "last", "", "last name")
|
|
cmd.Flags().StringVar(&email, "email", "", "email")
|
|
cmd.Flags().StringVar(&title, "title", "", "job title")
|
|
cmd.Flags().StringVar(&location, "location", "", "location")
|
|
cmd.Flags().StringVar(&sex, "sex", "", "sex: male|female")
|
|
cmd.Flags().StringVar(&comment, "comment", "", "comment")
|
|
return cmd
|
|
}
|
|
|
|
func usersDeleteCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "delete USER_ID [USER_ID...]",
|
|
Aliases: []string{"rm"},
|
|
Short: "Delete portal user(s) permanently",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, id := range args {
|
|
u, err := c.DeleteUser(cmd.Context(), id)
|
|
if err != nil {
|
|
return fmt.Errorf("delete %s: %w", id, err)
|
|
}
|
|
printObject(map[string]any{"id": id, "deleted": true, "displayName": idString(u, "displayName")})
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersBlockCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "block USER_ID [USER_ID...]",
|
|
Aliases: []string{"disable"},
|
|
Short: "Block (terminate) user(s): login denied, profile kept",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, id := range args {
|
|
if err := c.BlockUser(cmd.Context(), id); err != nil {
|
|
return fmt.Errorf("block %s: %w", id, err)
|
|
}
|
|
printObject(map[string]any{"id": id, "blocked": true})
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersUnblockCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "unblock USER_ID [USER_ID...]",
|
|
Aliases: []string{"enable", "activate"},
|
|
Short: "Unblock (activate) user(s)",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, id := range args {
|
|
if err := c.UnblockUser(cmd.Context(), id); err != nil {
|
|
return fmt.Errorf("unblock %s: %w", id, err)
|
|
}
|
|
printObject(map[string]any{"id": id, "unblocked": true})
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func usersPasswordCmd() *cobra.Command {
|
|
var password string
|
|
cmd := &cobra.Command{
|
|
Use: "password USER_ID",
|
|
Short: "Set a user password (reads stdin when --password is empty)",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
pwd := password
|
|
if pwd == "" {
|
|
b, err := readLine(os.Stdin)
|
|
if err != nil {
|
|
return fmt.Errorf("read password: %w", err)
|
|
}
|
|
pwd = b
|
|
}
|
|
if pwd == "" {
|
|
return fmt.Errorf("password is empty")
|
|
}
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := c.ChangeUserPassword(cmd.Context(), args[0], pwd); err != nil {
|
|
return err
|
|
}
|
|
printObject(map[string]any{"id": args[0], "password_changed": true})
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(&password, "password", "", "new password (omit to read one line from stdin)")
|
|
return cmd
|
|
}
|
|
|
|
func usersCheckCmd() *cobra.Command {
|
|
var login, password string
|
|
cmd := &cobra.Command{
|
|
Use: "check",
|
|
Short: "Check that a login can authenticate (userName or email)",
|
|
Long: `Probes POST /api/2.0/authentication.json with the given credentials and
|
|
discards the token.
|
|
|
|
Where this is used: before handing portal credentials to an external party
|
|
(e.g. a guest given read access to a document pack), verify the login actually
|
|
works. On some portals the account email is the reliable login identifier — the
|
|
userName login fails with 500 for a freshly created user — so share the email,
|
|
not the userName.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if login == "" {
|
|
return fmt.Errorf("--login is required (userName or email)")
|
|
}
|
|
if password == "" {
|
|
if b, err := readLine(os.Stdin); err == nil {
|
|
password = b
|
|
}
|
|
}
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := c.AuthenticateAs(cmd.Context(), login, password); err != nil {
|
|
printObject(map[string]any{"login": login, "ok": false, "error": trimAuthErr(err)})
|
|
return fmt.Errorf("login failed for %s", login)
|
|
}
|
|
printObject(map[string]any{"login": login, "ok": true})
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(&login, "login", "", "userName or email")
|
|
cmd.Flags().StringVar(&password, "password", "", "password (omit to read one line from stdin)")
|
|
return cmd
|
|
}
|
|
|
|
// trimAuthErr keeps the error short for table output.
|
|
func trimAuthErr(err error) string {
|
|
s := err.Error()
|
|
if len(s) > 160 {
|
|
s = s[:160] + "…"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// readLine reads a single trimmed line from r.
|
|
func readLine(r *os.File) (string, error) {
|
|
sc := bufio.NewScanner(r)
|
|
if !sc.Scan() {
|
|
if err := sc.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
return "", nil
|
|
}
|
|
return strings.TrimSpace(sc.Text()), nil
|
|
}
|
|
|
|
// whoamiCmd is a convenience shortcut at the root level.
|
|
func whoamiCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "whoami",
|
|
Short: "Alias for `oo users self`",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, err := c.SelfUserID(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
printObject(map[string]any{"id": id})
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func derefBool(p *bool) bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
return *p
|
|
}
|