diff --git a/apps_impl.go b/apps_impl.go index a2b9e87..1464217 100644 --- a/apps_impl.go +++ b/apps_impl.go @@ -13,7 +13,7 @@ type apps struct { //List return the list of the Nextcloud apps func (a *apps) List() ([]string, error) { - res, err := a.c.baseRequest(http.MethodGet, routes.apps, nil) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.apps, nil) if err != nil { return nil, err } @@ -27,7 +27,7 @@ func (a *apps) ListEnabled() ([]string, error) { ro := &req.RequestOptions{ Params: map[string]string{"filter": "enabled"}, } - res, err := a.c.baseRequest(http.MethodGet, routes.apps, ro) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.apps, ro) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (a *apps) ListDisabled() ([]string, error) { ro := &req.RequestOptions{ Params: map[string]string{"filter": "disabled"}, } - res, err := a.c.baseRequest(http.MethodGet, routes.apps, ro) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.apps, ro) if err != nil { return nil, err } @@ -52,7 +52,7 @@ func (a *apps) ListDisabled() ([]string, error) { //Infos return the app's details func (a *apps) Infos(name string) (App, error) { - res, err := a.c.baseRequest(http.MethodGet, routes.apps, nil, name) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.apps, nil, name) if err != nil { return App{}, err } @@ -63,12 +63,12 @@ func (a *apps) Infos(name string) (App, error) { //Enable enables an app func (a *apps) Enable(name string) error { - _, err := a.c.baseRequest(http.MethodPost, routes.apps, nil, name) + _, err := a.c.baseOcsRequest(http.MethodPost, routes.apps, nil, name) return err } //Disable disables an app func (a *apps) Disable(name string) error { - _, err := a.c.baseRequest(http.MethodDelete, routes.apps, nil, name) + _, err := a.c.baseOcsRequest(http.MethodDelete, routes.apps, nil, name) return err } diff --git a/appsconfig_impl.go b/appsconfig_impl.go index 0adb385..f0c9215 100644 --- a/appsconfig_impl.go +++ b/appsconfig_impl.go @@ -14,7 +14,7 @@ type appsConfig struct { //List lists all the available apps func (a *appsConfig) List() (apps []string, err error) { - res, err := a.c.baseRequest(http.MethodGet, routes.appsConfig, nil) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.appsConfig, nil) if err != nil { return nil, err } @@ -25,7 +25,7 @@ func (a *appsConfig) List() (apps []string, err error) { //Keys returns the app's config keys func (a *appsConfig) Keys(id string) (keys []string, err error) { - res, err := a.c.baseRequest(http.MethodGet, routes.appsConfig, nil, id) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.appsConfig, nil, id) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func (a *appsConfig) Keys(id string) (keys []string, err error) { //Value get the config value for the given app's key func (a *appsConfig) Value(id, key string) (string, error) { - res, err := a.c.baseRequest(http.MethodGet, routes.appsConfig, nil, id, key) + res, err := a.c.baseOcsRequest(http.MethodGet, routes.appsConfig, nil, id, key) if err != nil { return "", err } @@ -52,13 +52,13 @@ func (a *appsConfig) SetValue(id, key, value string) error { "value": value, }, } - _, err := a.c.baseRequest(http.MethodPost, routes.appsConfig, ro, id, key) + _, err := a.c.baseOcsRequest(http.MethodPost, routes.appsConfig, ro, id, key) return err } //DeleteValue delete the config value and (!! be careful !!) the key func (a *appsConfig) DeleteValue(id, key, value string) error { - _, err := a.c.baseRequest(http.MethodDelete, routes.appsConfig, nil, id, key) + _, err := a.c.baseOcsRequest(http.MethodDelete, routes.appsConfig, nil, id, key) return err } diff --git a/client_impl.go b/client_impl.go index abf3ffa..bb50778 100644 --- a/client_impl.go +++ b/client_impl.go @@ -25,6 +25,7 @@ type client struct { users *users groups *groups webdav *webDav + passwords *passwords } // newClient create a new client from the Nextcloud Instance URL @@ -52,6 +53,7 @@ func newClient(hostname string) (*client, error) { c.shares = &shares{c} c.users = &users{c} c.groups = &groups{c} + c.passwords = &passwords{c} // Create empty webdav client // It will be replaced after login c.webdav = &webDav{Client: &gowebdav.Client{}} @@ -97,3 +99,8 @@ func (c *client) Groups() Groups { func (c *client) WebDav() WebDav { return c.webdav } + +// Password return the Password client Interface +func (c *client) Passwords() Passwords { + return c.passwords +} diff --git a/gonextcloud.go b/gonextcloud.go index cac1563..7e8dfad 100644 --- a/gonextcloud.go +++ b/gonextcloud.go @@ -31,6 +31,8 @@ type Client interface { WebDav() WebDav // Nextcloud Monitoring client Monitoring() (*Monitoring, error) + // Nextcloud Password app client + Passwords() Passwords // Login authorize client Login(username string, password string) error // Logout clear connetion and session @@ -180,3 +182,7 @@ type WebDav interface { // directory in the tree, including root. Walk(path string, walkFunc filepath.WalkFunc) error } + +type Passwords interface { + List() ([]Password, error) +} diff --git a/gonextcloud_test.go b/gonextcloud_test.go index 690261f..ee28259 100644 --- a/gonextcloud_test.go +++ b/gonextcloud_test.go @@ -380,7 +380,7 @@ var ( "TestInvalidBaseRequest", func(t *testing.T) { c.baseURL = &url.URL{} - _, err := c.baseRequest(http.MethodGet, routes.capabilities, nil, "admin", "invalid") + _, err := c.baseOcsRequest(http.MethodGet, routes.capabilities, nil, "admin", "invalid") c = nil assert.Error(t, err) }, @@ -424,7 +424,7 @@ var ( "TestBaseRequest", func(t *testing.T) { c, _ = newClient("") - _, err := c.baseRequest(http.MethodGet, routes.capabilities, nil, "admin", "invalid") + _, err := c.baseOcsRequest(http.MethodGet, routes.capabilities, nil, "admin", "invalid") assert.Error(t, err) }, }, diff --git a/groupfolders_impl.go b/groupfolders_impl.go index 1f99723..bad5d52 100644 --- a/groupfolders_impl.go +++ b/groupfolders_impl.go @@ -15,7 +15,7 @@ type groupFolders struct { //List returns the groups folders func (g *groupFolders) List() (map[int]GroupFolder, error) { - res, err := g.c.baseRequest(http.MethodGet, routes.groupfolders, nil) + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groupfolders, nil) if err != nil { return nil, err } @@ -27,7 +27,7 @@ func (g *groupFolders) List() (map[int]GroupFolder, error) { //Get returns the group folder details func (g *groupFolders) Get(id int) (GroupFolder, error) { - res, err := g.c.baseRequest(http.MethodGet, routes.groupfolders, nil, strconv.Itoa(id)) + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groupfolders, nil, strconv.Itoa(id)) if err != nil { return GroupFolder{}, err } @@ -47,7 +47,7 @@ func (g *groupFolders) Create(name string) (id int, err error) { "mountpoint": name, }, } - res, err := g.c.baseRequest(http.MethodPost, routes.groupfolders, ro) + res, err := g.c.baseOcsRequest(http.MethodPost, routes.groupfolders, ro) if err != nil { return 0, err } @@ -65,7 +65,7 @@ func (g *groupFolders) Rename(groupID int, name string) error { }, } // groupFolders's response does not give any clues about success or failure - _, err := g.c.baseRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(groupID), "mountpoint") + _, err := g.c.baseOcsRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(groupID), "mountpoint") if err != nil { return err } @@ -82,7 +82,7 @@ func (g *groupFolders) AddGroup(folderID int, groupName string) error { }, } // groupFolders's response does not give any clues about success or failure - _, err := g.c.baseRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "groups") + _, err := g.c.baseOcsRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "groups") if err != nil { return err } @@ -92,7 +92,7 @@ func (g *groupFolders) AddGroup(folderID int, groupName string) error { //RemoveGroup remove a group from the group folder func (g *groupFolders) RemoveGroup(folderID int, groupName string) error { // groupFolders's response does not give any clues about success or failure - _, err := g.c.baseRequest(http.MethodDelete, routes.groupfolders, nil, strconv.Itoa(folderID), "groups", groupName) + _, err := g.c.baseOcsRequest(http.MethodDelete, routes.groupfolders, nil, strconv.Itoa(folderID), "groups", groupName) if err != nil { return err } @@ -107,7 +107,7 @@ func (g *groupFolders) SetGroupPermissions(folderID int, groupName string, permi }, } // groupFolders's response does not give any clues about success or failure - _, err := g.c.baseRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "groups", groupName) + _, err := g.c.baseOcsRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "groups", groupName) if err != nil { return err } @@ -122,7 +122,7 @@ func (g *groupFolders) SetQuota(folderID int, quota int) error { }, } // groupFolders's response does not give any clues about success or failure - _, err := g.c.baseRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "quota") + _, err := g.c.baseOcsRequest(http.MethodPost, routes.groupfolders, ro, strconv.Itoa(folderID), "quota") if err != nil { return err } diff --git a/groups_impl.go b/groups_impl.go index eb1d30e..3dfc6cb 100644 --- a/groups_impl.go +++ b/groups_impl.go @@ -13,7 +13,7 @@ type groups struct { //List lists the Nextcloud groups func (g *groups) List() ([]string, error) { - res, err := g.c.baseRequest(http.MethodGet, routes.groups, nil) + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groups, nil) if err != nil { return nil, err } @@ -29,7 +29,7 @@ func (g *groups) ListDetails(search string) ([]Group, error) { "search": search, }, } - res, err := g.c.baseRequest(http.MethodGet, routes.groups, ro, "details") + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groups, ro, "details") if err != nil { return nil, err } @@ -40,7 +40,7 @@ func (g *groups) ListDetails(search string) ([]Group, error) { //users list the group's users func (g *groups) Users(name string) ([]string, error) { - res, err := g.c.baseRequest(http.MethodGet, routes.groups, nil, name) + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groups, nil, name) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func (g *groups) Search(search string) ([]string, error) { ro := &req.RequestOptions{ Params: map[string]string{"search": search}, } - res, err := g.c.baseRequest(http.MethodGet, routes.groups, ro) + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groups, ro) if err != nil { return nil, err } @@ -80,7 +80,7 @@ func (g *groups) Delete(name string) error { //SubAdminList lists the group's subadmins func (g *groups) SubAdminList(name string) ([]string, error) { - res, err := g.c.baseRequest(http.MethodGet, routes.groups, nil, name, "subadmins") + res, err := g.c.baseOcsRequest(http.MethodGet, routes.groups, nil, name, "subadmins") if err != nil { return nil, err } @@ -90,6 +90,6 @@ func (g *groups) SubAdminList(name string) ([]string, error) { } func (g *groups) baseRequest(method string, ro *req.RequestOptions, subRoute ...string) error { - _, err := g.c.baseRequest(method, routes.groups, ro, subRoute...) + _, err := g.c.baseOcsRequest(method, routes.groups, ro, subRoute...) return err } diff --git a/monitoring_impl.go b/monitoring_impl.go index 6c70a6c..ef4bab6 100644 --- a/monitoring_impl.go +++ b/monitoring_impl.go @@ -6,7 +6,7 @@ import ( //Monitoring return nextcloud monitoring statistics func (c *client) Monitoring() (*Monitoring, error) { - res, err := c.baseRequest(http.MethodGet, routes.monitor, nil) + res, err := c.baseOcsRequest(http.MethodGet, routes.monitor, nil) if err != nil { return nil, err } diff --git a/notifications_impl.go b/notifications_impl.go index 53ff7db..9cfe95f 100644 --- a/notifications_impl.go +++ b/notifications_impl.go @@ -18,7 +18,7 @@ func (n *notifications) List() ([]Notification, error) { if err := n.Available(); err != nil { return nil, err } - res, err := n.c.baseRequest(http.MethodGet, routes.notifications, nil) + res, err := n.c.baseOcsRequest(http.MethodGet, routes.notifications, nil) if err != nil { return nil, err } @@ -32,7 +32,7 @@ func (n *notifications) Get(id int) (Notification, error) { if err := n.Available(); err != nil { return Notification{}, err } - res, err := n.c.baseRequest(http.MethodGet, routes.notifications, nil, strconv.Itoa(id)) + res, err := n.c.baseOcsRequest(http.MethodGet, routes.notifications, nil, strconv.Itoa(id)) if err != nil { return Notification{}, err } @@ -46,7 +46,7 @@ func (n *notifications) Delete(id int) error { if err := n.Available(); err != nil { return err } - _, err := n.c.baseRequest(http.MethodDelete, routes.notifications, nil, strconv.Itoa(id)) + _, err := n.c.baseOcsRequest(http.MethodDelete, routes.notifications, nil, strconv.Itoa(id)) return err } @@ -55,7 +55,7 @@ func (n *notifications) DeleteAll() error { if err := n.Available(); err != nil { return err } - _, err := n.c.baseRequest(http.MethodDelete, routes.notifications, nil) + _, err := n.c.baseOcsRequest(http.MethodDelete, routes.notifications, nil) return err } @@ -70,7 +70,7 @@ func (n *notifications) Create(userID, title, message string) error { "longMessage": message, }, } - _, err := n.c.baseRequest(http.MethodPost, routes.adminNotifications, ro, userID) + _, err := n.c.baseOcsRequest(http.MethodPost, routes.adminNotifications, ro, userID) return err } diff --git a/password_impl.go b/password_impl.go new file mode 100644 index 0000000..4d7df83 --- /dev/null +++ b/password_impl.go @@ -0,0 +1,62 @@ +package gonextcloud + +import ( + "net/http" + "strconv" + "time" +) + +//passwords contains some passwords app available actions +type passwords struct { + c *client +} + +func (p *passwords) List() ([]Password, error) { + res, err := p.c.baseRequest(http.MethodGet, routes.passwords, nil) + if err != nil { + return nil, err + } + var r []Password + err = res.JSON(&r) + if err != nil { + return nil, err + } + return r, nil +} + +type passwordTime struct { + time.Time +} + +func (pt *passwordTime) UnmarshalJSON(b []byte) (err error) { + s := string(b) + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + t := time.Unix(i, 0) + pt.Time = t + return +} + +type Password struct { + Id string `json:"id"` + Label string `json:"label"` + Username string `json:"username"` + Password string `json:"password"` + Url string `json:"url"` + Notes string `json:"notes"` + Status int `json:"status"` + StatusCode string `json:"statusCode"` + Hash string `json:"hash"` + Folder string `json:"foler"` + Revision string `json:"revision"` + Share string `json:"share"` + CseType string `json:"cseType"` + SseType string `json:"ssetype"` + Favorite bool `json:"favorite"` + Editable bool `json:"editable"` + Edited passwordTime `json:"edited"` + Created passwordTime `json:"created"` + Updated passwordTime `json:"updated"` +} diff --git a/routes.go b/routes.go index 2359869..13df298 100644 --- a/routes.go +++ b/routes.go @@ -14,6 +14,7 @@ type apiRoutes struct { appsConfig *url.URL notifications *url.URL adminNotifications *url.URL + passwords *url.URL } const badRequest = 998 @@ -31,5 +32,6 @@ var ( appsConfig: &url.URL{Path: apiPath.Path + "/apps/provisioning_api/api/v1/config/apps"}, notifications: &url.URL{Path: apiPath.Path + "/apps/notifications/api/v2/notifications"}, adminNotifications: &url.URL{Path: apiPath.Path + "/apps/admin_notifications/api/v2/notifications"}, + passwords: &url.URL{Path: "/index.php/apps/passwords/api/1.0/password/list"}, } ) diff --git a/shares_impl.go b/shares_impl.go index 34184f0..c87d58d 100644 --- a/shares_impl.go +++ b/shares_impl.go @@ -16,7 +16,7 @@ type shares struct { //List list all shares of the logged in user func (s *shares) List() ([]Share, error) { - res, err := s.c.baseRequest(http.MethodGet, routes.shares, nil) + res, err := s.c.baseOcsRequest(http.MethodGet, routes.shares, nil) if err != nil { return nil, err } @@ -34,7 +34,7 @@ func (s *shares) GetFromPath(path string, reshares bool, subfiles bool) ([]Share "subfiles": strconv.FormatBool(subfiles), }, } - res, err := s.c.baseRequest(http.MethodGet, routes.shares, ro) + res, err := s.c.baseOcsRequest(http.MethodGet, routes.shares, ro) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func (s *shares) GetFromPath(path string, reshares bool, subfiles bool) ([]Share //Get information about a known Share func (s *shares) Get(shareID string) (Share, error) { - res, err := s.c.baseRequest(http.MethodGet, routes.shares, nil, shareID) + res, err := s.c.baseOcsRequest(http.MethodGet, routes.shares, nil, shareID) if err != nil { return Share{}, err } @@ -77,7 +77,7 @@ func (s *shares) Create( "permissions": strconv.Itoa(int(permission)), }, } - res, err := s.c.baseRequest(http.MethodPost, routes.shares, ro) + res, err := s.c.baseOcsRequest(http.MethodPost, routes.shares, ro) if err != nil { return Share{}, err } @@ -88,7 +88,7 @@ func (s *shares) Create( //Delete Remove the given share. func (s *shares) Delete(shareID int) error { - _, err := s.c.baseRequest(http.MethodDelete, routes.shares, nil, strconv.Itoa(shareID)) + _, err := s.c.baseOcsRequest(http.MethodDelete, routes.shares, nil, strconv.Itoa(shareID)) return err } @@ -169,6 +169,6 @@ func (s *shares) baseShareUpdate(shareID string, key string, value string) error ro := &req.RequestOptions{ Data: map[string]string{key: value}, } - _, err := s.c.baseRequest(http.MethodPut, routes.shares, ro, shareID) + _, err := s.c.baseOcsRequest(http.MethodPut, routes.shares, ro, shareID) return err } diff --git a/users_impl.go b/users_impl.go index 87531fe..5e81100 100644 --- a/users_impl.go +++ b/users_impl.go @@ -20,7 +20,7 @@ type users struct { // List return the Nextcloud'user list func (u *users) List() ([]string, error) { - res, err := u.c.baseRequest(http.MethodGet, routes.users, nil) + res, err := u.c.baseOcsRequest(http.MethodGet, routes.users, nil) //res, err := c.session.Get(u.String(), nil) if err != nil { return nil, err @@ -32,7 +32,7 @@ func (u *users) List() ([]string, error) { //ListDetails return a map of user with details func (u *users) ListDetails() (map[string]UserDetails, error) { - res, err := u.c.baseRequest(http.MethodGet, routes.users, nil, "details") + res, err := u.c.baseOcsRequest(http.MethodGet, routes.users, nil, "details") //res, err := c.session.Get(u.String(), nil) if err != nil { return nil, err @@ -47,7 +47,7 @@ func (u *users) Get(name string) (*UserDetails, error) { if name == "" { return nil, &APIError{Message: "name cannot be empty"} } - res, err := u.c.baseRequest(http.MethodGet, routes.users, nil, name) + res, err := u.c.baseOcsRequest(http.MethodGet, routes.users, nil, name) if err != nil { return nil, err } @@ -66,7 +66,7 @@ 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) + res, err := u.c.baseOcsRequest(http.MethodGet, routes.users, ro) if err != nil { return nil, err } @@ -339,7 +339,7 @@ func (u *users) UpdateQuota(name string, quota int64) error { //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") + res, err := u.c.baseOcsRequest(http.MethodGet, routes.users, nil, name, "groups") if err != nil { return nil, err } @@ -415,7 +415,7 @@ func (u *users) updateAttribute(name string, key string, value string) error { } func (u *users) baseRequest(method string, ro *req.RequestOptions, subRoutes ...string) error { - _, err := u.c.baseRequest(method, routes.users, ro, subRoutes...) + _, err := u.c.baseOcsRequest(method, routes.users, ro, subRoutes...) return err } diff --git a/utils.go b/utils.go index dae14f9..7162771 100644 --- a/utils.go +++ b/utils.go @@ -10,6 +10,22 @@ import ( req "github.com/levigross/grequests" ) +func (c *client) baseOcsRequest(method string, route *url.URL, ro *req.RequestOptions, subRoutes ...string) (*req.Response, error) { + res, err := c.baseRequest(method, route, ro, subRoutes...) + if err != nil { + return nil, err + } + // As we cannot read the ReaderCloser twice, we use the string content + js := res.String() + var r baseResponse + json.Unmarshal([]byte(js), &r) + if r.Ocs.Meta.Statuscode == 200 || r.Ocs.Meta.Statuscode == 100 { + return res, nil + } + err = errorFromMeta(r.Ocs.Meta) + return nil, err +} + func (c *client) baseRequest(method string, route *url.URL, ro *req.RequestOptions, subRoutes ...string) (*req.Response, error) { if !c.loggedIn() { return nil, errUnauthorized @@ -38,15 +54,7 @@ func (c *client) baseRequest(method string, route *url.URL, ro *req.RequestOptio if err != nil { return nil, err } - // As we cannot read the ReaderCloser twice, we use the string content - js := res.String() - var r baseResponse - json.Unmarshal([]byte(js), &r) - if r.Ocs.Meta.Statuscode == 200 || r.Ocs.Meta.Statuscode == 100 { - return res, nil - } - err = errorFromMeta(r.Ocs.Meta) - return nil, err + return res, nil } func reformatJSON(json string) string {