package gonextcloud import ( "encoding/json" "github.com/fatih/structs" req "github.com/levigross/grequests" "github.com/pkg/errors" "gitlab.adphi.fr/partitio/Nextcloud-Partitio/gonextcloud/types" "net/http" "path" "strconv" "strings" "sync" ) //UsersI available methods type UsersI interface { List() ([]string, error) ListDetails() (map[string]types.User, error) Get(name string) (*types.User, error) Search(search string) ([]string, error) Create(username string, password string, user *types.User) error CreateWithoutPassword(username, email, displayName string) error Delete(name string) error Enable(name string) error Disable(name string) error SendWelcomeEmail(name string) error Update(user *types.User) error UpdateEmail(name string, email string) error UpdateDisplayName(name string, displayName string) error UpdatePhone(name string, phone string) error UpdateAddress(name string, address string) error UpdateWebSite(name string, website string) error UpdateTwitter(name string, twitter string) error UpdatePassword(name string, password string) error UpdateQuota(name string, quota int) error GroupList(name string) ([]string, error) GroupAdd(name string, group string) error GroupRemove(name string, group string) error GroupPromote(name string, group string) error GroupDemote(name string, group string) error GroupSubAdminList(name string) ([]string, error) } //Users contains all Users available actions type Users struct { c *Client } // List return the Nextcloud'user list func (u *Users) List() ([]string, error) { res, err := u.c.baseRequest(http.MethodGet, routes.users, nil) //res, err := c.session.Get(u.String(), nil) if err != nil { return nil, err } var r types.UserListResponse res.JSON(&r) return r.Ocs.Data.Users, nil } //ListDetails return a map of user with details func (u *Users) ListDetails() (map[string]types.User, error) { res, err := u.c.baseRequest(http.MethodGet, routes.users, nil, "details") //res, err := c.session.Get(u.String(), nil) if err != nil { return nil, err } var r types.UserListDetailsResponse res.JSON(&r) return r.Ocs.Data.Users, nil } // Get return the details about the specified user func (u *Users) Get(name string) (*types.User, error) { if name == "" { return nil, &types.APIError{Message: "name cannot be empty"} } res, err := u.c.baseRequest(http.MethodGet, routes.users, nil, name) if err != nil { return nil, err } var r types.UserResponse js := res.String() // Nextcloud does not encode JSON properly js = reformatJSON(js) if err := json.Unmarshal([]byte(js), &r); err != nil { return nil, err } return &r.Ocs.Data, nil } // Search returns the users whose name match the search string func (u *Users) Search(search string) ([]string, error) { ro := &req.RequestOptions{ Params: map[string]string{"search": search}, } res, err := u.c.baseRequest(http.MethodGet, routes.users, ro) if err != nil { return nil, err } var r types.UserListResponse res.JSON(&r) return r.Ocs.Data.Users, nil } // Create create a new user func (u *Users) Create(username string, password string, user *types.User) error { // Create base Users ro := &req.RequestOptions{ Data: map[string]string{ "userid": username, "password": password, }, } if err := u.baseRequest(http.MethodPost, ro); err != nil { return err } // Check if we need to add user details information if user == nil { return nil } // Add user details information return u.Update(user) } // CreateWithoutPassword create a user without provisioning a password, the email address must be provided to send // an init password email func (u *Users) CreateWithoutPassword(username, email, displayName string) error { if u.c.version.Major < 14 { return errors.New("unsupported method: requires Nextcloud 14+") } if username == "" || email == "" { return errors.New("username and email cannot be empty") } ro := &req.RequestOptions{ Data: map[string]string{ "userid": username, "email": email, "displayName": displayName, }, } if err := u.baseRequest(http.MethodPost, ro); err != nil { return err } return nil } //Delete delete the user func (u *Users) Delete(name string) error { return u.baseRequest(http.MethodDelete, nil, name) } //Enable enables the user func (u *Users) Enable(name string) error { ro := &req.RequestOptions{ Data: map[string]string{}, } return u.baseRequest(http.MethodPut, ro, name, "enable") } //Disable disables the user func (u *Users) Disable(name string) error { ro := &req.RequestOptions{ Data: map[string]string{}, } return u.baseRequest(http.MethodPut, ro, name, "disable") } //SendWelcomeEmail (re)send the welcome mail to the user (return an error if the user has not configured his email) func (u *Users) SendWelcomeEmail(name string) error { return u.baseRequest(http.MethodPost, nil, name, "welcome") } //Update takes a *types.Users struct to update the user's information func (u *Users) Update(user *types.User) error { m := structs.Map(user) errs := make(chan types.UpdateError) var wg sync.WaitGroup for k := range m { if !ignoredUserField(k) && m[k].(string) != "" { wg.Add(1) go func(key string, value string) { defer wg.Done() if err := u.updateAttribute(user.ID, strings.ToLower(key), value); err != nil { errs <- types.UpdateError{ Field: key, Error: err, } } }(k, m[k].(string)) } } go func() { wg.Wait() close(errs) }() return types.NewUpdateError(errs) } //UpdateEmail update the user's email func (u *Users) UpdateEmail(name string, email string) error { return u.updateAttribute(name, "email", email) } //UpdateDisplayName update the user's display name func (u *Users) UpdateDisplayName(name string, displayName string) error { return u.updateAttribute(name, "displayname", displayName) } //UpdatePhone update the user's phone func (u *Users) UpdatePhone(name string, phone string) error { return u.updateAttribute(name, "phone", phone) } //UpdateAddress update the user's address func (u *Users) UpdateAddress(name string, address string) error { return u.updateAttribute(name, "address", address) } //UpdateWebSite update the user's website func (u *Users) UpdateWebSite(name string, website string) error { return u.updateAttribute(name, "website", website) } //UpdateTwitter update the user's twitter func (u *Users) UpdateTwitter(name string, twitter string) error { return u.updateAttribute(name, "twitter", twitter) } //UpdatePassword update the user's password func (u *Users) UpdatePassword(name string, password string) error { return u.updateAttribute(name, "password", password) } //UpdateQuota update the user's quota (bytes) func (u *Users) UpdateQuota(name string, quota int) error { return u.updateAttribute(name, "quota", strconv.Itoa(quota)) } //GroupList lists the user's groups func (u *Users) GroupList(name string) ([]string, error) { res, err := u.c.baseRequest(http.MethodGet, routes.users, nil, name, "groups") if err != nil { return nil, err } var r types.GroupListResponse res.JSON(&r) return r.Ocs.Data.Groups, nil } //GroupAdd adds a the user to the group func (u *Users) GroupAdd(name string, group string) error { ro := &req.RequestOptions{ Data: map[string]string{ "groupid": group, }, } return u.baseRequest(http.MethodPost, ro, name, "groups") } //GroupRemove removes the user from the group func (u *Users) GroupRemove(name string, group string) error { ro := &req.RequestOptions{ Data: map[string]string{ "groupid": group, }, } return u.baseRequest(http.MethodDelete, ro, name, "groups") } //GroupPromote promotes the user as group admin func (u *Users) GroupPromote(name string, group string) error { ro := &req.RequestOptions{ Data: map[string]string{ "groupid": group, }, } return u.baseRequest(http.MethodPost, ro, name, "subadmins") } //GroupDemote demotes the user func (u *Users) GroupDemote(name string, group string) error { ro := &req.RequestOptions{ Data: map[string]string{ "groupid": group, }, } return u.baseRequest(http.MethodDelete, ro, name, "subadmins") } //GroupSubAdminList lists the groups where he is subadmin func (u *Users) GroupSubAdminList(name string) ([]string, error) { if !u.c.loggedIn() { return nil, errUnauthorized } ur := u.c.baseURL.ResolveReference(routes.users) ur.Path = path.Join(ur.Path, name, "subadmins") res, err := u.c.session.Get(ur.String(), nil) if err != nil { return nil, err } var r types.BaseResponse res.JSON(&r) return r.Ocs.Data, nil } func (u *Users) updateAttribute(name string, key string, value string) error { ro := &req.RequestOptions{ Data: map[string]string{ "key": key, "value": value, }, } return u.baseRequest(http.MethodPut, ro, name) } func (u *Users) baseRequest(method string, ro *req.RequestOptions, subRoutes ...string) error { _, err := u.c.baseRequest(method, routes.users, ro, subRoutes...) return err } func ignoredUserField(key string) bool { keys := []string{"ID", "Quota", "Enabled", "Groups", "Language"} for _, k := range keys { if key == k { return true } } return false }