Skip to content

Commit

Permalink
Merge pull request #1440 from ansible-semaphore/roles
Browse files Browse the repository at this point in the history
Roles
  • Loading branch information
fiftin committed Aug 26, 2023
2 parents 3452272 + 5c8e518 commit 36570ae
Show file tree
Hide file tree
Showing 20 changed files with 320 additions and 163 deletions.
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()
}
}

0 comments on commit 36570ae

Please sign in to comment.