From 2cf81ea56f230830e635cde26b79cfde42b396e8 Mon Sep 17 00:00:00 2001 From: "Ricardo L. Dani" Date: Wed, 10 Sep 2014 14:45:17 -0300 Subject: [PATCH] Added support to redefine users of a repository Thanks, @scorphus :) --- api/handler.go | 19 ++++++++++ api/handler_test.go | 65 +++++++++++++++++++++++++++++++++++ docs/source/api.rst | 21 +++++++++++ repository/repository.go | 16 +++++++++ repository/repository_test.go | 62 +++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+) diff --git a/api/handler.go b/api/handler.go index 4c36f9c..37cf0e1 100644 --- a/api/handler.go +++ b/api/handler.go @@ -64,6 +64,7 @@ func SetupRouter() *pat.Router { router.Post("/user", http.HandlerFunc(newUser)) router.Delete("/user/{name}", http.HandlerFunc(removeUser)) router.Delete("/repository/revoke", http.HandlerFunc(revokeAccess)) + router.Put("/repository/set", http.HandlerFunc(setAccess)) router.Get("/repository/{name:[^/]*/?[^/]+}/archive", http.HandlerFunc(getArchive)) router.Get("/repository/{name:[^/]*/?[^/]+}/contents", http.HandlerFunc(getFileContents)) router.Get("/repository/{name:[^/]*/?[^/]+}/tree", http.HandlerFunc(getTree)) @@ -82,6 +83,24 @@ func SetupRouter() *pat.Router { return router } +func setAccess(w http.ResponseWriter, r *http.Request) { + repositories, users, err := accessParameters(r.Body) + readOnly := r.URL.Query().Get("readonly") == "yes" + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err := repository.SetAccess(repositories, users, readOnly); err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + if readOnly { + fmt.Fprintf(w, "Successfully set read-only access to users \"%s\" into repository \"%s\"", users, repositories) + } else { + fmt.Fprintf(w, "Successfully set full access to users \"%s\" into repository \"%s\"", users, repositories) + } +} + func grantAccess(w http.ResponseWriter, r *http.Request) { repositories, users, err := accessParameters(r.Body) readOnly := r.URL.Query().Get("readonly") == "yes" diff --git a/api/handler_test.go b/api/handler_test.go index e3beec9..85d15c7 100644 --- a/api/handler_test.go +++ b/api/handler_test.go @@ -41,6 +41,10 @@ func post(url string, b io.Reader, c *gocheck.C) (*httptest.ResponseRecorder, *h return request("POST", url, b, c) } +func put(url string, b io.Reader, c *gocheck.C) (*httptest.ResponseRecorder, *http.Request) { + return request("PUT", url, b, c) +} + func del(url string, b io.Reader, c *gocheck.C) (*httptest.ResponseRecorder, *http.Request) { return request("DELETE", url, b, c) } @@ -338,6 +342,67 @@ func (s *S) TestNewRepositoryShouldReturnErrorWhenBodyIsEmpty(c *gocheck.C) { c.Assert(recorder.Code, gocheck.Equals, 400) } +func (s *S) TestSetAccessShouldReturnErrorWhenBodyIsEmpty(c *gocheck.C) { + b := strings.NewReader("") + recorder, request := put("/repository/set", b, c) + s.router.ServeHTTP(recorder, request) + c.Assert(recorder.Code, gocheck.Equals, 400) +} + +func (s *S) TestSetAccessUpdatesReposDocument(c *gocheck.C) { + u, err := user.New("pippin", map[string]string{}) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.User().Remove(bson.M{"_id": "pippin"}) + c.Assert(err, gocheck.IsNil) + r := repository.Repository{Name: "onerepo", Users: []string{"oneuser"}} + err = conn.Repository().Insert(&r) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": r.Name}) + r2 := repository.Repository{Name: "otherepo", Users: []string{"otheruser"}} + err = conn.Repository().Insert(&r2) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": r2.Name}) + b := bytes.NewBufferString(fmt.Sprintf(`{"repositories": ["%s", "%s"], "users": ["%s"]}`, r.Name, r2.Name, u.Name)) + rec, req := put("/repository/set", b, c) + s.router.ServeHTTP(rec, req) + var repos []repository.Repository + err = conn.Repository().Find(bson.M{"_id": bson.M{"$in": []string{r.Name, r2.Name}}}).All(&repos) + c.Assert(err, gocheck.IsNil) + c.Assert(rec.Code, gocheck.Equals, 200) + for _, repo := range repos { + c.Assert(repo.Users, gocheck.DeepEquals, []string{u.Name}) + } +} + +func (s *S) TestSetReadonlyAccessUpdatesReposDocument(c *gocheck.C) { + u, err := user.New("pippin", map[string]string{}) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.User().Remove(bson.M{"_id": "pippin"}) + c.Assert(err, gocheck.IsNil) + r := repository.Repository{Name: "onerepo", ReadOnlyUsers: []string{"oneuser"}} + err = conn.Repository().Insert(&r) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": r.Name}) + r2 := repository.Repository{Name: "otherepo", ReadOnlyUsers: []string{"otheruser"}} + err = conn.Repository().Insert(&r2) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().Remove(bson.M{"_id": r2.Name}) + b := bytes.NewBufferString(fmt.Sprintf(`{"repositories": ["%s", "%s"], "users": ["%s"]}`, r.Name, r2.Name, u.Name)) + rec, req := put("/repository/set?readonly=yes", b, c) + s.router.ServeHTTP(rec, req) + var repos []repository.Repository + err = conn.Repository().Find(bson.M{"_id": bson.M{"$in": []string{r.Name, r2.Name}}}).All(&repos) + c.Assert(err, gocheck.IsNil) + c.Assert(rec.Code, gocheck.Equals, 200) + for _, repo := range repos { + c.Assert(repo.ReadOnlyUsers, gocheck.DeepEquals, []string{u.Name}) + } +} + func (s *S) TestGrantAccessUpdatesReposDocument(c *gocheck.C) { u, err := user.New("pippin", map[string]string{}) conn, err := db.Conn() diff --git a/docs/source/api.rst b/docs/source/api.rst index ed68fae..66a30fc 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -51,6 +51,27 @@ Repository retrieval Retrieves information about a repository. +Access set in repository +-------------------------- + +Redefines collections of users with read and write access into a repository. Specify ``readonly=yes`` if you'd like to set read-only access. + +* Method: PUT +* URI: /repository/set +* Format: JSON + +Example URL for **read/write** access (http://gandalf-server omitted for clarity):: + + $ curl -XPUT /repository/set \ # PUT to /repository/set + -d '{"repositories": ["myrepo"], \ # Collection of repositories + "users": ["john", "james"]}' # Users with read/write access + +Example URL for **read-only** access (http://gandalf-server omitted for clarity):: + + $ curl -XPUT /repository/set?readonly=yes \ # PUT to /repository/set + -d '{"repositories": ["myrepo"], \ # Collection of repositories + "users": ["bob", "alice"]}' # Users with read-only access + Access grant in repository -------------------------- diff --git a/repository/repository.go b/repository/repository.go index e5c0c8f..e4a980b 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -285,6 +285,22 @@ func (r *Repository) isValid() (bool, error) { return true, nil } +// SetAccess gives full or read-only permission for users in all specified repositories. +// It redefines all users permissions, replacing the respective user collection +func SetAccess(rNames, uNames []string, readOnly bool) error { + conn, err := db.Conn() + if err != nil { + return err + } + defer conn.Close() + if readOnly { + _, err = conn.Repository().UpdateAll(bson.M{"_id": bson.M{"$in": rNames}}, bson.M{"$set": bson.M{"readonlyusers": uNames}}) + } else { + _, err = conn.Repository().UpdateAll(bson.M{"_id": bson.M{"$in": rNames}}, bson.M{"$set": bson.M{"users": uNames}}) + } + return err +} + // GrantAccess gives full or read-only permission for users in all specified repositories. // If any of the repositories/users does not exist, GrantAccess just skips it. func GrantAccess(rNames, uNames []string, readOnly bool) error { diff --git a/repository/repository_test.go b/repository/repository_test.go index 0b9ac32..6bad5fa 100644 --- a/repository/repository_test.go +++ b/repository/repository_test.go @@ -512,6 +512,68 @@ func (s *S) TestReadWriteURLUseUidFromConfigFile(c *gocheck.C) { c.Assert(remote, gocheck.Equals, fmt.Sprintf("test@%s:f#.git", host)) } +func (s *S) TestSetAccessShouldAddUserToListOfRepositories(c *gocheck.C) { + tmpdir, err := commandmocker.Add("git", "$*") + c.Assert(err, gocheck.IsNil) + defer commandmocker.Remove(tmpdir) + r, err := New("proj1", []string{"someuser"}, []string{"otheruser"}, true) + c.Assert(err, gocheck.IsNil) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.Repository().RemoveId(r.Name) + r2, err := New("proj2", []string{"otheruser"}, []string{"someuser"}, true) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().RemoveId(r2.Name) + u := struct { + Name string `bson:"_id"` + }{Name: "lolcat"} + err = conn.User().Insert(&u) + c.Assert(err, gocheck.IsNil) + defer conn.User().RemoveId(u.Name) + err = SetAccess([]string{r.Name, r2.Name}, []string{u.Name}, false) + c.Assert(err, gocheck.IsNil) + err = conn.Repository().FindId(r.Name).One(&r) + c.Assert(err, gocheck.IsNil) + err = conn.Repository().FindId(r2.Name).One(&r2) + c.Assert(err, gocheck.IsNil) + c.Assert(r.Users, gocheck.DeepEquals, []string{u.Name}) + c.Assert(r2.Users, gocheck.DeepEquals, []string{u.Name}) + c.Assert(r.ReadOnlyUsers, gocheck.DeepEquals, []string{"otheruser"}) + c.Assert(r2.ReadOnlyUsers, gocheck.DeepEquals, []string{"someuser"}) +} + +func (s *S) TestSetReadonlyAccessShouldAddUserToListOfRepositories(c *gocheck.C) { + tmpdir, err := commandmocker.Add("git", "$*") + c.Assert(err, gocheck.IsNil) + defer commandmocker.Remove(tmpdir) + r, err := New("proj1", []string{"someuser"}, []string{"otheruser"}, true) + c.Assert(err, gocheck.IsNil) + conn, err := db.Conn() + c.Assert(err, gocheck.IsNil) + defer conn.Close() + defer conn.Repository().RemoveId(r.Name) + r2, err := New("proj2", []string{"otheruser"}, []string{"someuser"}, true) + c.Assert(err, gocheck.IsNil) + defer conn.Repository().RemoveId(r2.Name) + u := struct { + Name string `bson:"_id"` + }{Name: "lolcat"} + err = conn.User().Insert(&u) + c.Assert(err, gocheck.IsNil) + defer conn.User().RemoveId(u.Name) + err = SetAccess([]string{r.Name, r2.Name}, []string{u.Name}, true) + c.Assert(err, gocheck.IsNil) + err = conn.Repository().FindId(r.Name).One(&r) + c.Assert(err, gocheck.IsNil) + err = conn.Repository().FindId(r2.Name).One(&r2) + c.Assert(err, gocheck.IsNil) + c.Assert(r.Users, gocheck.DeepEquals, []string{"someuser"}) + c.Assert(r2.Users, gocheck.DeepEquals, []string{"otheruser"}) + c.Assert(r.ReadOnlyUsers, gocheck.DeepEquals, []string{u.Name}) + c.Assert(r2.ReadOnlyUsers, gocheck.DeepEquals, []string{u.Name}) +} + func (s *S) TestGrantAccessShouldAddUserToListOfRepositories(c *gocheck.C) { tmpdir, err := commandmocker.Add("git", "$*") c.Assert(err, gocheck.IsNil)