Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roles #1440

Merged
merged 8 commits into from
Aug 26, 2023
Merged

Roles #1440

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .dredd/hooks/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ var pathSubPatterns = []func() string{
func() string { return strconv.Itoa(userProject.ID) },
func() string { return strconv.Itoa(userPathTestUser.ID) },
func() string { return strconv.Itoa(userKey.ID) },
func() string { return strconv.Itoa(int(repoID)) },
func() string { return strconv.Itoa(int(inventoryID)) },
func() string { return strconv.Itoa(int(environmentID)) },
func() string { return strconv.Itoa(int(templateID)) },
func() string { return strconv.Itoa(repoID) },
func() string { return strconv.Itoa(inventoryID) },
func() string { return strconv.Itoa(environmentID) },
func() string { return strconv.Itoa(templateID) },
func() string { return strconv.Itoa(task.ID) },
func() string { return strconv.Itoa(schedule.ID) },
func() string { return strconv.Itoa(view.ID) },
Expand Down
1 change: 1 addition & 0 deletions .dredd/hooks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func main() {
h.Before("project > /api/project/{project_id}/tasks/{task_id} > Get a single task > 200 > application/json", capabilityWrapper("task"))
h.Before("project > /api/project/{project_id}/tasks/{task_id} > Deletes task (including output) > 204 > application/json", capabilityWrapper("task"))
h.Before("project > /api/project/{project_id}/tasks/{task_id}/output > Get task output > 200 > application/json", capabilityWrapper("task"))
h.Before("project > /api/project/{project_id}/tasks/{task_id}/stop > Stop a job > 204 > application/json", capabilityWrapper("task"))

h.Before("schedule > /api/project/{project_id}/schedules/{schedule_id} > Get schedule > 200 > application/json", capabilityWrapper("schedule"))
h.Before("schedule > /api/project/{project_id}/schedules/{schedule_id} > Updates schedule > 204 > application/json", capabilityWrapper("schedule"))
Expand Down
44 changes: 43 additions & 1 deletion api-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,28 @@ paths:
204:
description: Project deleted


/project/{project_id}/role:
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
example: owner
permissions:
type: number
example: 0


/project/{project_id}/events:
parameters:
- $ref: '#/parameters/project_id'
Expand Down Expand Up @@ -1487,7 +1509,6 @@ paths:
description: view removed



# tasks
/project/{project_id}/tasks:
parameters:
Expand Down Expand Up @@ -1533,6 +1554,8 @@ paths:
description: Task queued
schema:
$ref: "#/definitions/Task"


/project/{project_id}/tasks/last:
parameters:
- $ref: "#/parameters/project_id"
Expand All @@ -1547,6 +1570,22 @@ paths:
type: array
items:
$ref: '#/definitions/Task'


/project/{project_id}/tasks/{task_id}/stop:
parameters:
- $ref: "#/parameters/project_id"
- $ref: '#/parameters/task_id'
post:
tags:
- project
summary: Stop a job
responses:
204:
description: Task queued



/project/{project_id}/tasks/{task_id}:
parameters:
- $ref: "#/parameters/project_id"
Expand All @@ -1567,6 +1606,9 @@ paths:
responses:
204:
description: task deleted



/project/{project_id}/tasks/{task_id}/output:
parameters:
- $ref: '#/parameters/project_id'
Expand Down
40 changes: 18 additions & 22 deletions api/projects/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -38,36 +38,22 @@ func ProjectMiddleware(next http.Handler) http.Handler {
return
}

context.Set(r, "projectUserRole", projectUser.Role)
context.Set(r, "project", project)
next.ServeHTTP(w, r)
})
}

// 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) {
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)
Expand All @@ -80,6 +66,16 @@ func GetProject(w http.ResponseWriter, r *http.Request) {
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"`
}
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
func UpdateProject(w http.ResponseWriter, r *http.Request) {
project := context.Get(r, "project").(db.Project)
Expand Down
12 changes: 7 additions & 5 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("/project/{project_id}").Subrouter()
projectTaskStop.Use(projects.ProjectMiddleware, projects.GetTaskMiddleware, projects.GetMustCanMiddlewareFor(db.CanRunProjectTasks))
projectTaskStop.Use(projects.ProjectMiddleware, projects.GetTaskMiddleware, projects.GetMustCanMiddleware(db.CanRunProjectTasks))
projectTaskStop.HandleFunc("/tasks/{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")
Expand Down Expand Up @@ -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()
Expand Down
11 changes: 10 additions & 1 deletion api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand Down
14 changes: 11 additions & 3 deletions db/ProjectUser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const (
)

var rolePermissions = map[ProjectUserRole]ProjectUserPermission{
ProjectOwner: CanRunProjectTasks | CanUpdateProject | CanManageProjectResources,
ProjectManager: CanRunProjectTasks | CanManageProjectResources,
ProjectOwner: CanRunProjectTasks | CanManageProjectResources | CanUpdateProject | CanManageProjectUsers,
ProjectManager: CanRunProjectTasks | CanManageProjectResources | CanManageProjectUsers,
ProjectTaskRunner: CanRunProjectTasks,
ProjectGuest: 0,
}
Expand All @@ -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]
}
15 changes: 15 additions & 0 deletions db/ProjectUser_test.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading