From f2758e239ccacbb64fb7e745181f923486b0cde6 Mon Sep 17 00:00:00 2001 From: konrad Date: Tue, 4 Sep 2018 20:15:24 +0200 Subject: [PATCH] Added namespace user rights --- REST-Tests/namespaces.http | 25 ++++++++++++++++++- models/error.go | 16 +++++++++++++ models/list_users.go | 2 +- models/models.go | 1 + models/namespace_users.go | 20 ++++++++++++++++ models/namespace_users_create.go | 40 +++++++++++++++++++++++++++++++ models/namespace_users_delete.go | 25 +++++++++++++++++++ models/namespace_users_readall.go | 23 ++++++++++++++++++ models/namespace_users_rights.go | 15 ++++++++++++ routes/crud/create.go | 3 +++ routes/crud/delete.go | 6 +++-- routes/routes.go | 7 ++++++ 12 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 models/namespace_users.go create mode 100644 models/namespace_users_create.go create mode 100644 models/namespace_users_delete.go create mode 100644 models/namespace_users_readall.go create mode 100644 models/namespace_users_rights.go diff --git a/REST-Tests/namespaces.http b/REST-Tests/namespaces.http index 0b4e86f71e..169239d2a2 100644 --- a/REST-Tests/namespaces.http +++ b/REST-Tests/namespaces.http @@ -1,3 +1,26 @@ # Get all namespaces GET http://localhost:8080/api/v1/namespaces -Authorization: Bearer {{auth_token}} \ No newline at end of file +Authorization: Bearer {{auth_token}} + +### + +# Get all users who have access to that namespace +GET http://localhost:8080/api/v1/namespaces/1/users +Authorization: Bearer {{auth_token}} + +### + +# Give a user access to that namespace +PUT http://localhost:8080/api/v1/namespaces/1/users +Authorization: Bearer {{auth_token}} +Content-Type: application/json + +{"user_id":2, "right": 0} + +### + +# Delete a user from a namespace +DELETE http://localhost:8080/api/v1/namespaces/1/users/2 +Authorization: Bearer {{auth_token}} + +### \ No newline at end of file diff --git a/models/error.go b/models/error.go index de48c22e1b..481ef5abdf 100644 --- a/models/error.go +++ b/models/error.go @@ -415,6 +415,22 @@ func (err ErrTeamDoesNotHaveAccessToNamespace) Error() string { return fmt.Sprintf("You need to have access to this namespace to do that [NamespaceID: %d, TeamID: %d]", err.NamespaceID, err.TeamID) } +// ErrUserAlreadyHasNamespaceAccess represents an error where a user already has access to a namespace +type ErrUserAlreadyHasNamespaceAccess struct { + UserID int64 + NamespaceID int64 +} + +// IsErrUserAlreadyHasNamespaceAccess checks if an error is ErrUserAlreadyHasNamespaceAccess. +func IsErrUserAlreadyHasNamespaceAccess(err error) bool { + _, ok := err.(ErrUserAlreadyHasNamespaceAccess) + return ok +} + +func (err ErrUserAlreadyHasNamespaceAccess) Error() string { + return fmt.Sprintf("This user already has access to that namespace. [User ID: %d, Namespace ID: %d]", err.UserID, err.NamespaceID) +} + // ============ // Team errors // ============ diff --git a/models/list_users.go b/models/list_users.go index 0b8971d509..f3e528713f 100644 --- a/models/list_users.go +++ b/models/list_users.go @@ -5,7 +5,7 @@ type ListUser struct { ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"` UserID int64 `xorm:"int(11) not null" json:"user_id" param:"user"` ListID int64 `xorm:"int(11) not null" json:"list_id" param:"list"` - Right TeamRight `xorm:"int(11)" json:"right"` + Right UserRight `xorm:"int(11)" json:"right"` Created int64 `xorm:"created" json:"created"` Updated int64 `xorm:"updated" json:"updated"` diff --git a/models/models.go b/models/models.go index a2fb97cfea..31ec930157 100644 --- a/models/models.go +++ b/models/models.go @@ -41,6 +41,7 @@ func init() { new(TeamNamespace), new(Namespace), new(ListUser), + new(NamespaceUser), ) } diff --git a/models/namespace_users.go b/models/namespace_users.go new file mode 100644 index 0000000000..0c4f6baffd --- /dev/null +++ b/models/namespace_users.go @@ -0,0 +1,20 @@ +package models + +// NamespaceUser represents a namespace <-> user relation +type NamespaceUser struct { + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"` + UserID int64 `xorm:"int(11) not null" json:"user_id" param:"user"` + NamespaceID int64 `xorm:"int(11) not null" json:"namespace_id" param:"namespace"` + Right UserRight `xorm:"int(11)" json:"right"` + + Created int64 `xorm:"created" json:"created"` + Updated int64 `xorm:"updated" json:"updated"` + + CRUDable `xorm:"-" json:"-"` + Rights `xorm:"-" json:"-"` +} + +// TableName is the table name for NamespaceUser +func (NamespaceUser) TableName() string { + return "users_namespace" +} diff --git a/models/namespace_users_create.go b/models/namespace_users_create.go new file mode 100644 index 0000000000..76e6766969 --- /dev/null +++ b/models/namespace_users_create.go @@ -0,0 +1,40 @@ +package models + +// Create creates a new namespace <-> user relation +func (un *NamespaceUser) Create(user *User) (err error) { + + // Check if the right is valid + if err := un.Right.isValid(); err != nil { + return err + } + + // Check if the namespace exists + l, err := GetNamespaceByID(un.NamespaceID) + if err != nil { + return + } + + // Check if the user exists + if _, err = GetUserByID(un.UserID); err != nil { + return err + } + + // Check if the user already has access or is owner of that namespace + // We explicitly DO NOT check for teams here + if l.OwnerID == un.UserID { + return ErrUserAlreadyHasNamespaceAccess{UserID: un.UserID, NamespaceID: un.NamespaceID} + } + + exist, err := x.Where("namespace_id = ? AND user_id = ?", un.NamespaceID, un.UserID).Get(&NamespaceUser{}) + if err != nil { + return + } + if exist { + return ErrUserAlreadyHasNamespaceAccess{UserID: un.UserID, NamespaceID: un.NamespaceID} + } + + // Insert user <-> namespace relation + _, err = x.Insert(un) + + return +} diff --git a/models/namespace_users_delete.go b/models/namespace_users_delete.go new file mode 100644 index 0000000000..1d64ca4af1 --- /dev/null +++ b/models/namespace_users_delete.go @@ -0,0 +1,25 @@ +package models + +// Delete deletes a namespace <-> user relation +func (nu *NamespaceUser) Delete() (err error) { + + // Check if the user exists + _, err = GetUserByID(nu.UserID) + if err != nil { + return + } + + // Check if the user has access to the namespace + has, err := x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). + Get(&NamespaceUser{}) + if err != nil { + return + } + if !has { + return ErrUserDoesNotHaveAccessToNamespace{NamespaceID: nu.NamespaceID, UserID: nu.UserID} + } + + _, err = x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). + Delete(&NamespaceUser{}) + return +} diff --git a/models/namespace_users_readall.go b/models/namespace_users_readall.go new file mode 100644 index 0000000000..3b5d498e9a --- /dev/null +++ b/models/namespace_users_readall.go @@ -0,0 +1,23 @@ +package models + +// ReadAll gets all users who have access to a namespace +func (un *NamespaceUser) ReadAll(user *User) (interface{}, error) { + // Check if the user has access to the namespace + l, err := GetNamespaceByID(un.NamespaceID) + if err != nil { + return nil, err + } + if !l.CanRead(user) { + return nil, ErrNeedToHaveNamespaceReadAccess{} + } + + // Get all users + all := []*User{} + err = x. + Select("users.*"). + Join("INNER", "users_namespace", "user_id = users.id"). + Where("users_namespace.namespace_id = ?", un.NamespaceID). + Find(&all) + + return all, err +} diff --git a/models/namespace_users_rights.go b/models/namespace_users_rights.go new file mode 100644 index 0000000000..e9f0df18b6 --- /dev/null +++ b/models/namespace_users_rights.go @@ -0,0 +1,15 @@ +package models + +// CanCreate checks if the user can create a new user <-> namespace relation +func (nu *NamespaceUser) CanCreate(doer *User) bool { + // Get the namespace and check if the user has write access on it + n, _ := GetNamespaceByID(nu.NamespaceID) + return n.CanWrite(doer) +} + +// CanDelete checks if the user can delete a user <-> namespace relation +func (nu *NamespaceUser) CanDelete(doer *User) bool { + // Get the namespace and check if the user has write access on it + n, _ := GetNamespaceByID(nu.NamespaceID) + return n.CanWrite(doer) +} diff --git a/routes/crud/create.go b/routes/crud/create.go index e56dd7537d..c163370200 100644 --- a/routes/crud/create.go +++ b/routes/crud/create.go @@ -66,6 +66,9 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error { if models.IsErrUserAlreadyHasAccess(err) { return echo.NewHTTPError(http.StatusBadRequest, "This user already has access to this list.") } + if models.IsErrUserAlreadyHasNamespaceAccess(err) { + return echo.NewHTTPError(http.StatusBadRequest, "This user already has access to this namespace.") + } if models.IsErrInvalidUserRight(err) { return echo.NewHTTPError(http.StatusBadRequest, "The right is invalid.") } diff --git a/routes/crud/delete.go b/routes/crud/delete.go index 1cb91606f2..72e75da63e 100644 --- a/routes/crud/delete.go +++ b/routes/crud/delete.go @@ -2,7 +2,6 @@ package crud import ( "code.vikunja.io/api/models" - "fmt" "github.com/labstack/echo" "net/http" ) @@ -25,7 +24,6 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error { err = c.CObject.Delete() if err != nil { - fmt.Println(err) if models.IsErrNeedToBeListAdmin(err) { return echo.NewHTTPError(http.StatusForbidden, "You need to be the list admin to delete a list.") } @@ -49,6 +47,10 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "This user does not have access to the list.") } + if models.IsErrUserDoesNotHaveAccessToNamespace(err) { + return echo.NewHTTPError(http.StatusBadRequest, "This user does not have access to the namespace.") + } + return echo.NewHTTPError(http.StatusInternalServerError) } diff --git a/routes/routes.go b/routes/routes.go index 557d108794..509c98fa9c 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -134,6 +134,13 @@ func RegisterRoutes(e *echo.Echo) { a.PUT("/namespaces/:namespace/teams", namespaceTeamHandler.CreateWeb) a.DELETE("/namespaces/:namespace/teams/:team", namespaceTeamHandler.DeleteWeb) + namespaceUserHandler := &crud.WebHandler{ + CObject: &models.NamespaceUser{}, + } + a.GET("/namespaces/:namespace/users", namespaceUserHandler.ReadAllWeb) + a.PUT("/namespaces/:namespace/users", namespaceUserHandler.CreateWeb) + a.DELETE("/namespaces/:namespace/users/:user", namespaceUserHandler.DeleteWeb) + teamHandler := &crud.WebHandler{ CObject: &models.Team{}, }