Files
go-onlyoffice/users.go
T
eSlider 1dc99d6997
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 1.25) (pull_request) Successful in 1m2s
Tests / Test (Go stable) (pull_request) Successful in 1m40s
feat(oo): project team CRUD and user lifecycle (block/unblock/password/delete)
- project team: oo projects team list|add|remove|set (portal users)
- users: get|create|update|delete|block|unblock|password
- lib: CreateUser, DeleteUser (auto-suspend before delete), Add/Remove/
  SetProjectTeam, ListProjectTeam; deleteJSON reused; jsonBodyReader helper
- tests: unmarshalResponseArray
2026-09-22 14:26:23 +01:00

176 lines
6.7 KiB
Go

package onlyoffice
// User / People endpoints and associated entity types.
import (
"context"
"encoding/json"
"fmt"
"net/url"
"time"
)
// User represents an OnlyOffice portal user. Fields reflect the full
// /api/2.0/people/filter.json response; most are optional and returned
// only in user-detail responses.
type User struct {
ID *string `json:"id,omitempty"`
UserName *string `json:"userName,omitempty"`
IsVisitor *bool `json:"isVisitor,omitempty"`
FirstName *string `json:"firstName,omitempty"`
LastName *string `json:"lastName,omitempty"`
Email *string `json:"email,omitempty"`
Status *int `json:"status,omitempty"`
ActivationStatus *int `json:"activationStatus,omitempty"`
Terminated any `json:"terminated,omitempty"`
Department *string `json:"department,omitempty"`
WorkFrom *time.Time `json:"workFrom,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
AvatarMedium *string `json:"avatarMedium,omitempty"`
Avatar *string `json:"avatar,omitempty"`
IsAdmin *bool `json:"isAdmin,omitempty"`
IsLDAP *bool `json:"isLDAP,omitempty"`
ListAdminModules []string `json:"listAdminModules,omitempty"`
IsOwner *bool `json:"isOwner,omitempty"`
CultureName *string `json:"cultureName,omitempty"`
IsSSO *bool `json:"isSSO,omitempty"`
AvatarSmall *string `json:"avatarSmall,omitempty"`
QuotaLimit *int `json:"quotaLimit,omitempty"`
UsedSpace *int `json:"usedSpace,omitempty"`
DocsSpace *int `json:"docsSpace,omitempty"`
MailSpace *int `json:"mailSpace,omitempty"`
TalkSpace *int `json:"talkSpace,omitempty"`
ProfileURL *string `json:"profileUrl,omitempty"`
RegistrationDate *time.Time `json:"registrationDate,omitempty"`
Title *string `json:"title,omitempty"`
Sex *string `json:"sex,omitempty"`
Lead *string `json:"lead,omitempty"`
Birthday *time.Time `json:"birthday,omitempty"`
Location *string `json:"location,omitempty"`
Notes *string `json:"notes,omitempty"`
Contacts []Contact `json:"contacts,omitempty"`
Groups []Group `json:"groups,omitempty"`
}
// Contact is a typed contact entry attached to a User.
type Contact struct {
Type *string `json:"type,omitempty"`
Value *string `json:"value,omitempty"`
}
// Group is a portal user group.
type Group struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Manager any `json:"manager,omitempty"`
}
// GetUsers lists all portal users.
func (c *Client) GetUsers() (list []*User, err error) {
return list, c.Query(Request{Uri: "/api/2.0/people/filter.json"},
&struct {
MetaResponse `json:",inline"`
Response *[]*User `json:"response"`
}{Response: &list})
}
// GetUser returns one portal user profile by ID.
func (c *Client) GetUser(ctx context.Context, userID string) (map[string]any, error) {
return c.ResponseObject(ctx, fmt.Sprintf("/api/2.0/people/%s.json", url.PathEscape(userID)))
}
// UpdateUser updates portal user profile fields (admin ACL, etc.). Do not send
// employee status here — use ChangeUserStatus instead.
func (c *Client) UpdateUser(ctx context.Context, userID string, body map[string]any) (map[string]any, error) {
return c.putJSONObject(ctx, fmt.Sprintf("/api/2.0/people/%s", url.PathEscape(userID)), body)
}
// ChangeUserStatus activates or terminates a user via the status API.
func (c *Client) ChangeUserStatus(ctx context.Context, userID string, active bool) error {
status := "Terminated"
if active {
status = "Active"
}
_, err := c.putJSONObject(ctx, fmt.Sprintf("/api/2.0/people/status/%s", status), map[string]any{
"userIds": []string{userID},
"resendAll": false,
})
return err
}
// ChangeUserPassword sets a new password for the user.
func (c *Client) ChangeUserPassword(ctx context.Context, userID, password string) error {
_, err := c.putJSONObject(ctx, fmt.Sprintf("/api/2.0/people/%s/password", url.PathEscape(userID)), map[string]string{
"password": password,
})
return err
}
// NewUserRequest is the payload for CreateUser. Field names follow the
// OnlyOffice REST contract (lowercase firstname/lastname).
type NewUserRequest struct {
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Title string `json:"title,omitempty"`
Location string `json:"location,omitempty"`
Sex string `json:"sex,omitempty"`
Comment string `json:"comment,omitempty"`
IsVisitor *bool `json:"isVisitor,omitempty"`
Department []string `json:"department,omitempty"`
}
// CreateUser adds a portal user (POST /api/2.0/people). Returns the created
// user profile. When Password is empty the portal generates one and the account
// stays NotActivated until the user follows the activation link.
func (c *Client) CreateUser(ctx context.Context, req NewUserRequest) (map[string]any, error) {
return c.postJSONObject(ctx, "/api/2.0/people", req)
}
// DeleteUser removes a portal user permanently (DELETE /api/2.0/people/{id}).
// OnlyOffice refuses to delete an active user ("The user is not suspended"),
// so a blocked/terminated account is deactivated first and deletion retried.
func (c *Client) DeleteUser(ctx context.Context, userID string) (map[string]any, error) {
u, err := c.deleteObject(ctx, fmt.Sprintf("/api/2.0/people/%s", url.PathEscape(userID)))
if err == nil {
return u, nil
}
if bErr := c.BlockUser(ctx, userID); bErr != nil {
return nil, err
}
return c.deleteObject(ctx, fmt.Sprintf("/api/2.0/people/%s", url.PathEscape(userID)))
}
// BlockUser terminates (blocks) the user: login is denied but the profile and
// data are kept. Reversible with UnblockUser.
func (c *Client) BlockUser(ctx context.Context, userID string) error {
return c.ChangeUserStatus(ctx, userID, false)
}
// UnblockUser reactivates a terminated/blocked user.
func (c *Client) UnblockUser(ctx context.Context, userID string) error {
return c.ChangeUserStatus(ctx, userID, true)
}
// SelfUserID returns the ID of the authenticated user (people/@self), cached.
func (c *Client) SelfUserID(ctx context.Context) (string, error) {
if c.selfID != "" {
return c.selfID, nil
}
raw, err := c.getJSON(ctx, "/api/2.0/people/@self.json")
if err != nil {
return "", err
}
var env struct {
Response struct {
ID string `json:"id"`
} `json:"response"`
}
if err := json.Unmarshal(raw, &env); err != nil {
return "", err
}
c.selfID = env.Response.ID
return c.selfID, nil
}