From b522169832dd5eee2bc2a30047db1edc02d116a0 Mon Sep 17 00:00:00 2001 From: Denis Gukov Date: Sat, 26 Aug 2023 18:48:16 +0200 Subject: [PATCH 1/7] test: check role permissions --- api/projects/project.go | 34 +++++++++++++--------------------- db/ProjectUser.go | 12 ++++++++++-- db/ProjectUser_test.go | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 db/ProjectUser_test.go diff --git a/api/projects/project.go b/api/projects/project.go index 9a407550..866a14af 100644 --- a/api/projects/project.go +++ b/api/projects/project.go @@ -24,7 +24,7 @@ func ProjectMiddleware(next http.Handler) http.Handler { } // check if user in project's team - _, err = helpers.Store(r).GetProjectUser(projectID, user.ID) + projectUser, err := helpers.Store(r).GetProjectUser(projectID, user.ID) if err != nil { helpers.WriteError(w, err) @@ -38,6 +38,7 @@ func ProjectMiddleware(next http.Handler) http.Handler { return } + context.Set(r, "projectUserRole", projectUser.Role) context.Set(r, "project", project) next.ServeHTTP(w, r) }) @@ -47,27 +48,12 @@ func ProjectMiddleware(next http.Handler) http.Handler { func GetMustCanMiddlewareFor(permissions db.ProjectUserPermission) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - project := context.Get(r, "project").(db.Project) user := context.Get(r, "user").(*db.User) + projectUserRole := context.Get(r, "projectUserRole").(db.ProjectUserRole) - if !user.Admin { - // check if user in project's team - projectUser, err := helpers.Store(r).GetProjectUser(project.ID, user.ID) - - if err == db.ErrNotFound { - w.WriteHeader(http.StatusForbidden) - return - } - - if err != nil { - helpers.WriteError(w, err) - return - } - - if r.Method != "GET" && r.Method != "HEAD" && !projectUser.Can(permissions) { - w.WriteHeader(http.StatusForbidden) - return - } + if !user.Admin && r.Method != "GET" && r.Method != "HEAD" && !projectUserRole.Can(permissions) { + w.WriteHeader(http.StatusForbidden) + return } next.ServeHTTP(w, r) @@ -77,7 +63,13 @@ func GetMustCanMiddlewareFor(permissions db.ProjectUserPermission) mux.Middlewar // GetProject returns a project details func GetProject(w http.ResponseWriter, r *http.Request) { - helpers.WriteJSON(w, http.StatusOK, context.Get(r, "project")) + var project struct { + db.Project + UserPermissions db.ProjectUserPermission `json:"userPermissions"` + } + project.Project = context.Get(r, "project").(db.Project) + project.UserPermissions = context.Get(r, "projectUserRole").(db.ProjectUserRole).GetPermissions() + helpers.WriteJSON(w, http.StatusOK, project) } // UpdateProject saves updated project details to the database diff --git a/db/ProjectUser.go b/db/ProjectUser.go index 6b561abe..ce768170 100644 --- a/db/ProjectUser.go +++ b/db/ProjectUser.go @@ -19,7 +19,7 @@ const ( ) var rolePermissions = map[ProjectUserRole]ProjectUserPermission{ - ProjectOwner: CanRunProjectTasks | CanUpdateProject | CanManageProjectResources, + ProjectOwner: CanRunProjectTasks | CanManageProjectResources | CanUpdateProject, ProjectManager: CanRunProjectTasks | CanManageProjectResources, ProjectTaskRunner: CanRunProjectTasks, ProjectGuest: 0, @@ -39,5 +39,13 @@ type ProjectUser struct { func (u *ProjectUser) Can(permissions ProjectUserPermission) bool { userPermissions := rolePermissions[u.Role] - return (userPermissions & userPermissions) == permissions + return (userPermissions & permissions) == permissions +} + +func (r ProjectUserRole) Can(permissions ProjectUserPermission) bool { + return (rolePermissions[r] & permissions) == permissions +} + +func (r ProjectUserRole) GetPermissions() ProjectUserPermission { + return rolePermissions[r] } diff --git a/db/ProjectUser_test.go b/db/ProjectUser_test.go new file mode 100644 index 00000000..08be5e95 --- /dev/null +++ b/db/ProjectUser_test.go @@ -0,0 +1,15 @@ +package db + +import ( + "testing" +) + +func TestProjectUsers_RoleCan(t *testing.T) { + if !ProjectManager.Can(CanManageProjectResources) { + t.Fatal() + } + + if ProjectManager.Can(CanUpdateProject) { + t.Fatal() + } +} From 4398544e91d59c8daed3023279ef86f2b2ea9266 Mon Sep 17 00:00:00 2001 From: Denis Gukov Date: Sat, 26 Aug 2023 20:43:42 +0200 Subject: [PATCH 2/7] feat(fe): handle permissions on UI --- api-docs.yml | 20 +++ api/projects/project.go | 20 ++- api/router.go | 12 +- api/user.go | 11 +- web/src/App.vue | 218 +++++++++++++------------ web/src/components/ItemListPageBase.js | 9 + web/src/components/TeamMemberForm.vue | 16 +- web/src/lib/constants.js | 21 +++ web/src/views/project/Activity.vue | 15 +- web/src/views/project/Environment.vue | 1 + web/src/views/project/History.vue | 7 +- web/src/views/project/Inventory.vue | 1 + web/src/views/project/Keys.vue | 1 + web/src/views/project/Repositories.vue | 1 + web/src/views/project/Team.vue | 23 +-- web/src/views/project/Templates.vue | 10 +- 16 files changed, 246 insertions(+), 140 deletions(-) diff --git a/api-docs.yml b/api-docs.yml index b4c8b1fb..1c182f24 100644 --- a/api-docs.yml +++ b/api-docs.yml @@ -928,6 +928,26 @@ paths: 204: description: Project deleted + + /project/{project_id}/permissions: + parameters: + - $ref: "#/parameters/project_id" + get: + tags: + - project + summary: Fetch permissions of the current user for project + responses: + 200: + description: Permissions + schema: + type: object + properties: + role: + type: string + permissions: + type: number + + /project/{project_id}/events: parameters: - $ref: '#/parameters/project_id' diff --git a/api/projects/project.go b/api/projects/project.go index 866a14af..6dcadede 100644 --- a/api/projects/project.go +++ b/api/projects/project.go @@ -44,8 +44,8 @@ func ProjectMiddleware(next http.Handler) http.Handler { }) } -// GetMustCanMiddlewareFor ensures that the user has administrator rights -func GetMustCanMiddlewareFor(permissions db.ProjectUserPermission) mux.MiddlewareFunc { +// GetMustCanMiddleware ensures that the user has administrator rights +func GetMustCanMiddleware(permissions db.ProjectUserPermission) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := context.Get(r, "user").(*db.User) @@ -63,13 +63,17 @@ func GetMustCanMiddlewareFor(permissions db.ProjectUserPermission) mux.Middlewar // GetProject returns a project details func GetProject(w http.ResponseWriter, r *http.Request) { - var project struct { - db.Project - UserPermissions db.ProjectUserPermission `json:"userPermissions"` + helpers.WriteJSON(w, http.StatusOK, context.Get(r, "project")) +} + +func GetUserRole(w http.ResponseWriter, r *http.Request) { + var permissions struct { + Role db.ProjectUserRole `json:"role"` + Permissions db.ProjectUserPermission `json:"permissions"` } - project.Project = context.Get(r, "project").(db.Project) - project.UserPermissions = context.Get(r, "projectUserRole").(db.ProjectUserRole).GetPermissions() - helpers.WriteJSON(w, http.StatusOK, project) + permissions.Role = context.Get(r, "projectUserRole").(db.ProjectUserRole) + permissions.Permissions = permissions.Role.GetPermissions() + helpers.WriteJSON(w, http.StatusOK, permissions) } // UpdateProject saves updated project details to the database diff --git a/api/router.go b/api/router.go index 5c63a86f..3a055882 100644 --- a/api/router.go +++ b/api/router.go @@ -128,17 +128,19 @@ func Route() *mux.Router { // // Start and Stop tasks projectTaskStart := authenticatedAPI.PathPrefix("/project/{project_id}").Subrouter() - projectTaskStart.Use(projects.ProjectMiddleware, projects.GetMustCanMiddlewareFor(db.CanRunProjectTasks)) + projectTaskStart.Use(projects.ProjectMiddleware, projects.GetMustCanMiddleware(db.CanRunProjectTasks)) projectTaskStart.Path("/tasks").HandlerFunc(projects.AddTask).Methods("POST") projectTaskStop := authenticatedAPI.PathPrefix("/tasks").Subrouter() - projectTaskStop.Use(projects.ProjectMiddleware, projects.GetTaskMiddleware, projects.GetMustCanMiddlewareFor(db.CanRunProjectTasks)) + projectTaskStop.Use(projects.ProjectMiddleware, projects.GetTaskMiddleware, projects.GetMustCanMiddleware(db.CanRunProjectTasks)) projectTaskStop.HandleFunc("/{task_id}/stop", projects.StopTask).Methods("POST") // // Project resources CRUD projectUserAPI := authenticatedAPI.PathPrefix("/project/{project_id}").Subrouter() - projectUserAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddlewareFor(db.CanManageProjectResources)) + projectUserAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddleware(db.CanManageProjectResources)) + + projectUserAPI.Path("/role").HandlerFunc(projects.GetUserRole).Methods("GET", "HEAD") projectUserAPI.Path("/events").HandlerFunc(getAllEvents).Methods("GET", "HEAD") projectUserAPI.HandleFunc("/events/last", getLastEvents).Methods("GET", "HEAD") @@ -173,14 +175,14 @@ func Route() *mux.Router { // // Updating and deleting project projectAdminAPI := authenticatedAPI.Path("/project/{project_id}").Subrouter() - projectAdminAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddlewareFor(db.CanUpdateProject)) + projectAdminAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddleware(db.CanUpdateProject)) projectAdminAPI.Methods("PUT").HandlerFunc(projects.UpdateProject) projectAdminAPI.Methods("DELETE").HandlerFunc(projects.DeleteProject) // // Manage project users projectAdminUsersAPI := authenticatedAPI.PathPrefix("/project/{project_id}").Subrouter() - projectAdminUsersAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddlewareFor(db.CanManageProjectUsers)) + projectAdminUsersAPI.Use(projects.ProjectMiddleware, projects.GetMustCanMiddleware(db.CanManageProjectUsers)) projectAdminUsersAPI.Path("/users").HandlerFunc(projects.AddUser).Methods("POST") projectUserManagement := projectAdminUsersAPI.PathPrefix("/users").Subrouter() diff --git a/api/user.go b/api/user.go index 41e8fa4d..dc9e7222 100644 --- a/api/user.go +++ b/api/user.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "github.com/ansible-semaphore/semaphore/api/helpers" "github.com/ansible-semaphore/semaphore/db" + "github.com/ansible-semaphore/semaphore/util" "github.com/gorilla/context" "github.com/gorilla/mux" "io" @@ -18,7 +19,15 @@ func getUser(w http.ResponseWriter, r *http.Request) { return } - helpers.WriteJSON(w, http.StatusOK, context.Get(r, "user")) + var user struct { + db.User + CanCreateProject bool `json:"can_create_project"` + } + + user.User = *context.Get(r, "user").(*db.User) + user.CanCreateProject = user.Admin || util.Config.NonAdminCanCreateProject + + helpers.WriteJSON(w, http.StatusOK, user) } func getAPITokens(w http.ResponseWriter, r *http.Request) { diff --git a/web/src/App.vue b/web/src/App.vue index 98a5b4b4..a4b23904 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,56 +1,56 @@