diff --git a/api/main.go b/api/main.go index 0d0f813..28e8018 100644 --- a/api/main.go +++ b/api/main.go @@ -80,9 +80,6 @@ func New() *WebAPI { api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet)) api.router.GET("/worker/stats", LogRequestMiddleware(api.GetAllWorkerStats)) - api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess)) - api.router.POST("/access/remove", LogRequestMiddleware(api.WorkerRemoveAccess)) - api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate)) api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet)) api.router.POST("/project/update/:id", LogRequestMiddleware(api.ProjectUpdate)) @@ -90,8 +87,10 @@ func New() *WebAPI { api.router.GET("/project/monitoring-between/:id", LogRequestMiddleware(api.GetSnapshotsBetween)) api.router.GET("/project/monitoring/:id", LogRequestMiddleware(api.GetNSnapshots)) api.router.GET("/project/assignees/:id", LogRequestMiddleware(api.ProjectGetAssigneeStats)) - api.router.GET("/project/requests/:id", LogRequestMiddleware(api.ProjectGetAccessRequests)) - api.router.GET("/project/request_access/:id", LogRequestMiddleware(api.WorkerRequestAccess)) + api.router.GET("/project/accesses/:id", LogRequestMiddleware(api.ProjectGetWorkerAccesses)) + api.router.POST("/project/request_access", LogRequestMiddleware(api.WorkerRequestAccess)) + api.router.POST("/project/accept_request/:id/:wid", LogRequestMiddleware(api.AcceptAccessRequest)) + api.router.POST("/project/reject_request/:id/:wid", LogRequestMiddleware(api.RejectAccessRequest)) api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate)) api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject)) diff --git a/api/project.go b/api/project.go index 0947169..928dd2c 100644 --- a/api/project.go +++ b/api/project.go @@ -59,15 +59,15 @@ type GetAssigneeStatsResponse struct { Assignees *[]storage.AssignedTasks `json:"assignees"` } -type WorkerRequestAccessResponse struct { +type WorkerAccessRequestResponse struct { Ok bool `json:"ok"` Message string `json:"message,omitempty"` } type ProjectGetAccessRequestsResponse struct { - Ok bool `json:"ok"` - Message string `json:"message,omitempty"` - Requests *[]storage.Worker `json:"requests,omitempty"` + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` + Accesses *[]storage.WorkerAccess `json:"accesses,omitempty"` } func (api *WebAPI) ProjectCreate(r *Request) { @@ -187,7 +187,7 @@ func (api *WebAPI) ProjectUpdate(r *Request) { sess := api.Session.StartFasthttp(r.Ctx) manager := sess.Get("manager") - if !isProjectUpdateAuthorized(project, manager, api.Database) { + if !isActionAuthorized(project.Id, manager, storage.ROLE_EDIT, api.Database) { r.Json(CreateProjectResponse{ Ok: false, Message: "Unauthorized", @@ -245,7 +245,8 @@ func isProjectCreationAuthorized(project *storage.Project, manager interface{}) return true } -func isProjectUpdateAuthorized(project *storage.Project, manager interface{}, db *storage.Database) bool { +func isActionAuthorized(project int64, manager interface{}, + requiredRole storage.ManagerRole, db *storage.Database) bool { if manager == nil { return false @@ -255,8 +256,8 @@ func isProjectUpdateAuthorized(project *storage.Project, manager interface{}, db return true } - role := db.GetManagerRoleOn(manager.(*storage.Manager), project.Id) - if role&storage.ROLE_EDIT == 1 { + role := db.GetManagerRoleOn(manager.(*storage.Manager), project) + if role&requiredRole != 0 { return true } @@ -347,7 +348,7 @@ func (api *WebAPI) ProjectGetAssigneeStats(r *Request) { }) } -func (api *WebAPI) ProjectGetAccessRequests(r *Request) { +func (api *WebAPI) ProjectGetWorkerAccesses(r *Request) { sess := api.Session.StartFasthttp(r.Ctx) manager := sess.Get("manager") @@ -355,55 +356,120 @@ func (api *WebAPI) ProjectGetAccessRequests(r *Request) { id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) handleErr(err, r) //todo handle invalid id - if manager == nil { - r.Json(ProjectGetAccessRequestsResponse{ - Ok: false, - Message: "Unauthorized", - }, 401) - return - } - - if !manager.(*storage.Manager).WebsiteAdmin && - api.Database.GetManagerRoleOn(manager.(*storage.Manager), 1)& - storage.ROLE_MANAGE_ACCESS == 0 { + if !isActionAuthorized(id, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { r.Json(ProjectGetAccessRequestsResponse{ Ok: false, Message: "Unauthorized", }, 403) return } - requests := api.Database.GetAllAccessRequests(id) + accesses := api.Database.GetAllAccesses(id) r.OkJson(ProjectGetAccessRequestsResponse{ Ok: true, - Requests: requests, + Accesses: accesses, }) } func (api *WebAPI) WorkerRequestAccess(r *Request) { - id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) - handleErr(err, r) //todo handle invalid id + req := &WorkerAccessRequest{} + err := json.Unmarshal(r.Ctx.Request.Body(), req) + if err != nil { + r.Json(WorkerAccessRequestResponse{ + Ok: false, + Message: "Could not parse request", + }, 400) + return + } + + if !req.isValid() { + r.Json(WorkerAccessRequestResponse{ + Ok: false, + Message: "Invalid request", + }, 400) + return + } worker, err := api.validateSignature(r) if err != nil { - r.Json(WorkerRequestAccessResponse{ + r.Json(WorkerAccessRequestResponse{ Ok: false, Message: err.Error(), }, 401) } - res := api.Database.SaveAccessRequest(worker, id) + res := api.Database.SaveAccessRequest(&storage.WorkerAccess{ + Worker: *worker, + Submit: req.Submit, + Assign: req.Assign, + Project: req.Project, + }) if res { - r.OkJson(WorkerRequestAccessResponse{ + r.OkJson(WorkerAccessRequestResponse{ Ok: true, }) } else { - r.Json(WorkerRequestAccessResponse{ + r.Json(WorkerAccessRequestResponse{ Ok: false, Message: "Project is public, you already have " + "an active request or you already have access to this project", }, 400) } } + +func (api *WebAPI) AcceptAccessRequest(r *Request) { + + pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if !isActionAuthorized(pid, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { + r.Json(WorkerAccessRequestResponse{ + Message: "Unauthorized", + Ok: false, + }, 403) + return + } + + ok := api.Database.AcceptAccessRequest(wid, pid) + + if ok { + r.OkJson(WorkerAccessRequestResponse{ + Ok: true, + }) + } else { + r.OkJson(WorkerAccessRequestResponse{ + Ok: false, + Message: "Worker did not have access to this project", + }) + } +} + +func (api *WebAPI) RejectAccessRequest(r *Request) { + + pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + ok := api.Database.RejectAccessRequest(wid, pid) + + if ok { + r.OkJson(WorkerAccessRequestResponse{ + Ok: true, + }) + } else { + r.OkJson(WorkerAccessRequestResponse{ + Ok: false, + Message: "Worker did not have access to this project", + }) + } +} diff --git a/api/task.go b/api/task.go index 9208014..934d0af 100644 --- a/api/task.go +++ b/api/task.go @@ -49,8 +49,17 @@ type GetTaskResponse struct { func (api *WebAPI) TaskCreate(r *Request) { + worker, err := api.validateSignature(r) + if worker == nil { + r.Json(CreateProjectResponse{ + Ok: false, + Message: err.Error(), + }, 401) + return + } + createReq := &CreateTaskRequest{} - err := json.Unmarshal(r.Ctx.Request.Body(), createReq) + err = json.Unmarshal(r.Ctx.Request.Body(), createReq) if err != nil { r.Json(CreateProjectResponse{ Ok: false, @@ -74,7 +83,7 @@ func (api *WebAPI) TaskCreate(r *Request) { createReq.Hash64 = int64(siphash.Hash(1, 2, []byte(createReq.UniqueString))) } - err := api.Database.SaveTask(task, createReq.Project, createReq.Hash64) + err := api.Database.SaveTask(task, createReq.Project, createReq.Hash64, worker.Id) if err != nil { r.Json(CreateTaskResponse{ diff --git a/api/worker.go b/api/worker.go index 83492d5..8d986ab 100644 --- a/api/worker.go +++ b/api/worker.go @@ -9,10 +9,6 @@ import ( "time" ) -type CreateWorkerRequest struct { - Alias string `json:"alias"` -} - type UpdateWorkerRequest struct { Alias string `json:"alias"` } @@ -22,6 +18,10 @@ type UpdateWorkerResponse struct { Message string `json:"message,omitempty"` } +type CreateWorkerRequest struct { + Alias string `json:"alias"` +} + type CreateWorkerResponse struct { Ok bool `json:"ok"` Message string `json:"message,omitempty"` @@ -34,22 +34,25 @@ type GetWorkerResponse struct { Worker *storage.Worker `json:"worker,omitempty"` } -type WorkerAccessRequest struct { - WorkerId int64 `json:"worker_id"` - ProjectId int64 `json:"project_id"` -} - -type WorkerAccessResponse struct { - Ok bool `json:"ok"` - Message string `json:"message"` -} - type GetAllWorkerStatsResponse struct { Ok bool `json:"ok"` Message string `json:"message,omitempty"` Stats *[]storage.WorkerStats `json:"stats"` } +type WorkerAccessRequest struct { + Assign bool `json:"assign"` + Submit bool `json:"submit"` + Project int64 `json:"project"` +} + +func (w *WorkerAccessRequest) isValid() bool { + if !w.Assign && !w.Submit { + return false + } + return true +} + func (api *WebAPI) WorkerCreate(r *Request) { workerReq := &CreateWorkerRequest{} @@ -125,57 +128,6 @@ func (api *WebAPI) WorkerGet(r *Request) { } } -func (api *WebAPI) WorkerGrantAccess(r *Request) { - - req := &WorkerAccessRequest{} - err := json.Unmarshal(r.Ctx.Request.Body(), req) - if err != nil { - r.Json(GetTaskResponse{ - Ok: false, - Message: "Could not parse request", - }, 400) - return - } - - ok := api.Database.GrantAccess(req.WorkerId, req.ProjectId) - - if ok { - r.OkJson(WorkerAccessResponse{ - Ok: true, - }) - } else { - r.OkJson(WorkerAccessResponse{ - Ok: false, - Message: "Worker already has access to this project", - }) - } -} - -func (api *WebAPI) WorkerRemoveAccess(r *Request) { - - req := &WorkerAccessRequest{} - err := json.Unmarshal(r.Ctx.Request.Body(), req) - if err != nil { - r.Json(GetTaskResponse{ - Ok: false, - Message: "Could not parse request", - }, 400) - return - } - ok := api.Database.RemoveAccess(req.WorkerId, req.ProjectId) - - if ok { - r.OkJson(WorkerAccessResponse{ - Ok: true, - }) - } else { - r.OkJson(WorkerAccessResponse{ - Ok: false, - Message: "Worker did not have access to this project", - }) - } -} - func (api *WebAPI) WorkerUpdate(r *Request) { worker, err := api.validateSignature(r) diff --git a/schema.sql b/schema.sql index b7fbafe..669298c 100755 --- a/schema.sql +++ b/schema.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS worker, project, task, log_entry, - worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot, - worker_verifies_task, worker_requests_access_to_project; + worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, + worker_verifies_task; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; @@ -28,10 +28,13 @@ CREATE TABLE project motd TEXT NOT NULL ); -CREATE TABLE worker_has_access_to_project +CREATE TABLE worker_access ( - worker INTEGER REFERENCES worker (id), - project INTEGER REFERENCES project (id), + worker INTEGER REFERENCES worker (id), + project INTEGER REFERENCES project (id), + role_assign boolean, + role_submit boolean, + request boolean, primary key (worker, project) ); @@ -79,7 +82,7 @@ CREATE TABLE manager CREATE TABLE manager_has_role_on_project ( manager INTEGER REFERENCES manager (id) NOT NULL, - role SMALLINT NOT NULl, + role SMALLINT NOT NULL, project INTEGER REFERENCES project (id) NOT NULL ); @@ -94,12 +97,6 @@ CREATE TABLE project_monitoring_snapshot timestamp INT NOT NULL ); -CREATE TABLE worker_requests_access_to_project -( - worker INT REFERENCES worker (id) NOT NULL, - project INT REFERENCES project (id) NOT NULL -); - CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS $$ DECLARE diff --git a/storage/monitoring.go b/storage/monitoring.go index 978bdac..c3a3d30 100644 --- a/storage/monitoring.go +++ b/storage/monitoring.go @@ -30,7 +30,7 @@ func (database *Database) MakeProjectSnapshots() { WHERE task.project = project.id AND status = 1 AND wvt.task IS NULL), (SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 2), closed_task_count, - (SELECT COUNT(*) FROM worker_has_access_to_project wa WHERE wa.project = project.id), + (SELECT COUNT(*) FROM worker_access wa WHERE wa.project = project.id), (SELECT COUNT(*) FROM worker_verifies_task INNER JOIN task t on worker_verifies_task.task = t.id WHERE t.project = project.id), extract(epoch from now() at time zone 'utc') diff --git a/storage/task.go b/storage/task.go index 0ddbc4e..d932af2 100644 --- a/storage/task.go +++ b/storage/task.go @@ -2,6 +2,7 @@ package storage import ( "database/sql" + "errors" "fmt" "github.com/Sirupsen/logrus" ) @@ -35,15 +36,17 @@ const ( TR_SKIP TaskResult = 2 ) -func (database *Database) SaveTask(task *Task, project int64, hash64 int64) error { +func (database *Database) SaveTask(task *Task, project int64, hash64 int64, wid int64) error { db := database.getDB() //TODO: For some reason it refuses to insert the 64-bit value unless I do that... res, err := db.Exec(fmt.Sprintf(` INSERT INTO task (project, max_retries, recipe, priority, max_assign_time, hash64,verification_count) - VALUES ($1,$2,$3,$4,$5,NULLIF(%d, 0),$6)`, hash64), - project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime, task.VerificationCount) + SELECT $1,$2,$3,$4,$5,NULLIF(%d, 0),$6 FROM worker_access + WHERE role_submit AND worker=$7 AND project=$1`, hash64), + project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime, task.VerificationCount, + wid) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "task": task, @@ -59,6 +62,10 @@ func (database *Database) SaveTask(task *Task, project int64, hash64 int64) erro "task": task, }).Trace("Database.saveTask INSERT task") + if rowsAffected == 0 { + return errors.New("unauthorized task submit") + } + return nil } @@ -77,7 +84,7 @@ func (database *Database) GetTask(worker *Worker) *Task { LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 WHERE assignee IS NULL AND task.status=1 AND (project.public OR EXISTS ( - SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=project.id + SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=project.id )) AND wvt.task IS NULL ORDER BY project.priority DESC, task.priority DESC @@ -179,8 +186,8 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T INNER JOIN project project on task.project = project.id LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 WHERE assignee IS NULL AND project.id=$2 AND status=1 - AND (project.public OR EXISTS ( - SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=$2 + AND (project.public OR ( + SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=$2 )) AND wvt.task IS NULL ORDER BY task.priority DESC diff --git a/storage/worker.go b/storage/worker.go index 100c950..09a8c3c 100644 --- a/storage/worker.go +++ b/storage/worker.go @@ -16,11 +16,20 @@ type WorkerStats struct { ClosedTaskCount int64 `json:"closed_task_count"` } +type WorkerAccess struct { + Submit bool `json:"submit"` + Assign bool `json:"assign"` + Request bool `json:"request"` + Worker Worker `json:"worker"` + Project int64 `json:"project"` +} + func (database *Database) SaveWorker(worker *Worker) { db := database.getDB() - row := db.QueryRow("INSERT INTO worker (created, secret, alias) VALUES ($1,$2,$3) RETURNING id", + row := db.QueryRow(`INSERT INTO worker (created, secret, alias) + VALUES ($1,$2,$3) RETURNING id`, worker.Created, worker.Secret, worker.Alias) err := row.Scan(&worker.Id) @@ -56,14 +65,14 @@ func (database *Database) GetWorker(id int64) *Worker { func (database *Database) GrantAccess(workerId int64, projectId int64) bool { db := database.getDB() - res, err := db.Exec(`INSERT INTO worker_has_access_to_project (worker, project) VALUES ($1,$2) - ON CONFLICT DO NOTHING`, + res, err := db.Exec(`UPDATE worker_access SET + request=FALSE WHERE worker=$1 AND project=$2`, workerId, projectId) if err != nil { logrus.WithFields(logrus.Fields{ "workerId": workerId, "projectId": projectId, - }).WithError(err).Warn("Database.GrantAccess INSERT worker_hase_access_to_project") + }).WithError(err).Warn("Database.GrantAccess INSERT") return false } @@ -73,25 +82,7 @@ func (database *Database) GrantAccess(workerId int64, projectId int64) bool { "rowsAffected": rowsAffected, "workerId": workerId, "projectId": projectId, - }).Trace("Database.GrantAccess INSERT worker_has_access_to_project") - - return rowsAffected == 1 -} - -func (database *Database) RemoveAccess(workerId int64, projectId int64) bool { - - db := database.getDB() - res, err := db.Exec(`DELETE FROM worker_has_access_to_project WHERE worker=$1 AND project=$2`, - workerId, projectId) - handleErr(err) - - rowsAffected, _ := res.RowsAffected() - - logrus.WithFields(logrus.Fields{ - "rowsAffected": rowsAffected, - "workerId": workerId, - "projectId": projectId, - }).Trace("Database.RemoveAccess DELETE worker_has_access_to_project") + }).Trace("Database.GrantAccess INSERT") return rowsAffected == 1 } @@ -113,14 +104,14 @@ func (database *Database) UpdateWorker(worker *Worker) bool { return rowsAffected == 1 } -func (database *Database) SaveAccessRequest(worker *Worker, projectId int64) bool { +func (database *Database) SaveAccessRequest(wa *WorkerAccess) bool { db := database.getDB() - res, err := db.Exec(`INSERT INTO worker_requests_access_to_project - SELECT $1, id FROM project WHERE id=$2 AND NOT project.public - AND NOT EXISTS(SELECT * FROM worker_has_access_to_project WHERE worker=$1 AND project=$2)`, - worker.Id, projectId) + res, err := db.Exec(`INSERT INTO worker_access(worker, project, role_assign, + role_submit, request) + VALUES ($1,$2,$3,$4,TRUE)`, + wa.Worker.Id, wa.Project, wa.Assign, wa.Submit) if err != nil { return false } @@ -134,35 +125,12 @@ func (database *Database) SaveAccessRequest(worker *Worker, projectId int64) boo return rowsAffected == 1 } -func (database *Database) AcceptAccessRequest(worker *Worker, projectId int64) bool { +func (database *Database) AcceptAccessRequest(worker int64, projectId int64) bool { db := database.getDB() - res, err := db.Exec(`DELETE FROM worker_requests_access_to_project - WHERE worker=$1 AND project=$2`) - handleErr(err) - - rowsAffected, _ := res.RowsAffected() - if rowsAffected == 1 { - _, err := db.Exec(`INSERT INTO worker_has_access_to_project - (worker, project) VALUES ($1,$2)`, - worker.Id, projectId) - handleErr(err) - } - - logrus.WithFields(logrus.Fields{ - "rowsAffected": rowsAffected, - }).Trace("Database.AcceptAccessRequest") - - return rowsAffected == 1 -} - -func (database *Database) RejectAccessRequest(worker *Worker, projectId int64) bool { - - db := database.getDB() - - res, err := db.Exec(`DELETE FROM worker_requests_access_to_project - WHERE worker=$1 AND project=$2`, worker.Id, projectId) + res, err := db.Exec(`UPDATE worker_access SET request=FALSE + WHERE worker=$1 AND project=$2`, worker, projectId) handleErr(err) rowsAffected, _ := res.RowsAffected() @@ -174,22 +142,44 @@ func (database *Database) RejectAccessRequest(worker *Worker, projectId int64) b return rowsAffected == 1 } -func (database *Database) GetAllAccessRequests(projectId int64) *[]Worker { +func (database *Database) RejectAccessRequest(workerId int64, projectId int64) bool { + + db := database.getDB() + res, err := db.Exec(`DELETE FROM worker_access WHERE worker=$1 AND project=$2`, + workerId, projectId) + handleErr(err) + + rowsAffected, _ := res.RowsAffected() + + logrus.WithFields(logrus.Fields{ + "rowsAffected": rowsAffected, + "workerId": workerId, + "projectId": projectId, + }).Trace("Database.RejectAccessRequest DELETE") + + return rowsAffected == 1 +} + +func (database *Database) GetAllAccesses(projectId int64) *[]WorkerAccess { db := database.getDB() - rows, err := db.Query(`SELECT id, alias, created FROM worker_requests_access_to_project - INNER JOIN worker w on worker_requests_access_to_project.worker = w.id - WHERE project=$1`, + rows, err := db.Query(`SELECT id, alias, created, role_assign, role_submit, request + FROM worker_access + INNER JOIN worker w on worker_access.worker = w.id + WHERE project=$1 ORDER BY request, alias`, projectId) handleErr(err) - requests := make([]Worker, 0) + requests := make([]WorkerAccess, 0) for rows.Next() { - w := Worker{} - _ = rows.Scan(&w.Id, &w.Alias, &w.Created) - requests = append(requests, w) + wa := WorkerAccess{ + Project: projectId, + } + _ = rows.Scan(&wa.Worker.Id, &wa.Worker.Alias, &wa.Worker.Created, + &wa.Assign, &wa.Submit, &wa.Request) + requests = append(requests, wa) } return &requests diff --git a/test/api_auth_test.go b/test/api_auth_test.go index 1b64ba2..9b620da 100644 --- a/test/api_auth_test.go +++ b/test/api_auth_test.go @@ -143,6 +143,40 @@ func TestInvalidCredentialsLogin(t *testing.T) { } } +func TestRequireManageAccessRole(t *testing.T) { + + user := getSessionCtx("testreqmanrole", "testreqmanrole", false) + + pid := createProject(api.CreateProjectRequest{ + GitRepo: "testRequireManageAccessRole", + CloneUrl: "testRequireManageAccessRole", + Name: "testRequireManageAccessRole", + Version: "testRequireManageAccessRole", + }, user).Id + + w := genWid() + requestAccess(api.WorkerAccessRequest{ + Submit: true, + Assign: true, + Project: pid, + }, w) + + rGuest := acceptAccessRequest(pid, w.Id, nil) + rOtherUser := acceptAccessRequest(pid, w.Id, testUserCtx) + rUser := acceptAccessRequest(pid, w.Id, user) + + if rGuest.Ok != false { + t.Error() + } + if rOtherUser.Ok != false { + t.Error() + } + if rUser.Ok != true { + t.Error() + } + +} + func register(request *api.RegisterRequest) *api.RegisterResponse { r := Post("/register", request, nil, nil) diff --git a/test/api_task_bench_test.go b/test/api_task_bench_test.go index f13d509..7f07607 100644 --- a/test/api_task_bench_test.go +++ b/test/api_task_bench_test.go @@ -2,8 +2,6 @@ package test import ( "github.com/simon987/task_tracker/api" - "github.com/simon987/task_tracker/config" - "github.com/simon987/task_tracker/storage" "strconv" "testing" ) @@ -29,24 +27,3 @@ func BenchmarkCreateTaskRemote(b *testing.B) { }, worker) } } - -func BenchmarkCreateTask(b *testing.B) { - - config.SetupConfig() - db := storage.Database{} - - project, _ := db.SaveProject(&storage.Project{ - Priority: 1, - Id: 1, - Version: "bmcreatetask", - Public: true, - Motd: "bmcreatetask", - Name: "BenchmarkCreateTask" + strconv.Itoa(b.N), - GitRepo: "benchmark_test" + strconv.Itoa(b.N), - }) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = db.SaveTask(&storage.Task{}, project, 0) - } -} diff --git a/test/api_task_test.go b/test/api_task_test.go index 71d9147..74401ba 100644 --- a/test/api_task_test.go +++ b/test/api_task_test.go @@ -11,23 +11,29 @@ import ( func TestCreateTaskValid(t *testing.T) { - //Make sure there is always a project for id:1 - createProjectAsAdmin(api.CreateProjectRequest{ + pid := createProjectAsAdmin(api.CreateProjectRequest{ Name: "Some Test name", Version: "Test Version", CloneUrl: "http://github.com/test/test", - }) + GitRepo: "Some git repo", + }).Id worker := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Submit: true, + Assign: false, + }, worker) + acceptAccessRequest(pid, worker.Id, testAdminCtx) resp := createTask(api.CreateTaskRequest{ - Project: 1, + Project: pid, Recipe: "{}", MaxRetries: 3, }, worker) if resp.Ok != true { - t.Fail() + t.Error() } } @@ -143,6 +149,12 @@ func TestCreateGetTask(t *testing.T) { }) worker := genWid() + requestAccess(api.WorkerAccessRequest{ + Submit: true, + Assign: true, + Project: resp.Id, + }, worker) + acceptAccessRequest(resp.Id, worker.Id, testAdminCtx) createTask(api.CreateTaskRequest{ Project: resp.Id, @@ -211,6 +223,19 @@ func createTasks(prefix string) (int64, int64) { Public: true, }) worker := genWid() + requestAccess(api.WorkerAccessRequest{ + Submit: true, + Assign: false, + Project: highP.Id, + }, worker) + acceptAccessRequest(highP.Id, worker.Id, testAdminCtx) + requestAccess(api.WorkerAccessRequest{ + Submit: true, + Assign: false, + Project: lowP.Id, + }, worker) + acceptAccessRequest(lowP.Id, worker.Id, testAdminCtx) + createTask(api.CreateTaskRequest{ Project: lowP.Id, Recipe: "low1", @@ -303,6 +328,13 @@ func TestTaskNoAccess(t *testing.T) { Public: false, }).Id + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, worker) + acceptAccessRequest(worker.Id, pid, testAdminCtx) + createResp := createTask(api.CreateTaskRequest{ Project: pid, Priority: 1, @@ -315,8 +347,7 @@ func TestTaskNoAccess(t *testing.T) { t.Error() } - grantAccess(worker.Id, pid) - removeAccess(worker.Id, pid) + rejectAccessRequest(pid, worker.Id, testAdminCtx) tResp := getTaskFromProject(pid, worker) @@ -345,6 +376,13 @@ func TestTaskHasAccess(t *testing.T) { Public: false, }).Id + requestAccess(api.WorkerAccessRequest{ + Submit: true, + Assign: true, + Project: pid, + }, worker) + acceptAccessRequest(worker.Id, pid, testAdminCtx) + createResp := createTask(api.CreateTaskRequest{ Project: pid, Priority: 1, @@ -357,8 +395,6 @@ func TestTaskHasAccess(t *testing.T) { t.Error() } - grantAccess(worker.Id, pid) - tResp := getTaskFromProject(pid, worker) if tResp.Ok != true { @@ -392,6 +428,13 @@ func TestReleaseTaskSuccess(t *testing.T) { Public: true, }).Id + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, worker) + acceptAccessRequest(pid, worker.Id, testAdminCtx) + createTask(api.CreateTaskRequest{ Priority: 0, Project: pid, @@ -431,6 +474,12 @@ func TestCreateIntCollision(t *testing.T) { }).Id w := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w) + acceptAccessRequest(pid, w.Id, testAdminCtx) if createTask(api.CreateTaskRequest{ Project: pid, @@ -471,6 +520,12 @@ func TestCreateStringCollision(t *testing.T) { }).Id w := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w) + acceptAccessRequest(pid, w.Id, testAdminCtx) if createTask(api.CreateTaskRequest{ Project: pid, @@ -520,6 +575,12 @@ func TestCannotVerifySameTaskTwice(t *testing.T) { }).Id w := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w) + acceptAccessRequest(pid, w.Id, testAdminCtx) createTask(api.CreateTaskRequest{ VerificationCount: 2, @@ -560,6 +621,24 @@ func TestVerification2(t *testing.T) { w := genWid() w2 := genWid() w3 := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w) + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w2) + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w3) + acceptAccessRequest(pid, w.Id, testAdminCtx) + acceptAccessRequest(pid, w2.Id, testAdminCtx) + acceptAccessRequest(pid, w3.Id, testAdminCtx) createTask(api.CreateTaskRequest{ VerificationCount: 2, @@ -614,6 +693,12 @@ func TestReleaseTaskFail(t *testing.T) { }).Id w := genWid() + requestAccess(api.WorkerAccessRequest{ + Project: pid, + Assign: true, + Submit: true, + }, w) + acceptAccessRequest(pid, w.Id, testAdminCtx) createTask(api.CreateTaskRequest{ MaxRetries: 0, @@ -657,6 +742,18 @@ func TestTaskChain(t *testing.T) { CloneUrl: "testtaskchain2", Chain: p1, }).Id + requestAccess(api.WorkerAccessRequest{ + Project: p1, + Assign: true, + Submit: true, + }, w) + requestAccess(api.WorkerAccessRequest{ + Project: p2, + Assign: true, + Submit: true, + }, w) + acceptAccessRequest(p1, w.Id, testAdminCtx) + acceptAccessRequest(p2, w.Id, testAdminCtx) createTask(api.CreateTaskRequest{ Project: p2, diff --git a/test/api_worker_test.go b/test/api_worker_test.go index fd8e8c7..535fbe2 100644 --- a/test/api_worker_test.go +++ b/test/api_worker_test.go @@ -64,79 +64,6 @@ func TestGetWorkerInvalid(t *testing.T) { t.Error() } } - -func TestGrantAccessFailedProjectConstraint(t *testing.T) { - - wid := genWid() - - resp := grantAccess(wid.Id, 38274593) - - if resp.Ok != false { - t.Error() - } - if len(resp.Message) <= 0 { - t.Error() - } -} - -func TestRemoveAccessFailedProjectConstraint(t *testing.T) { - - worker := genWid() - - resp := removeAccess(worker.Id, 38274593) - - if resp.Ok != false { - t.Error() - } - if len(resp.Message) <= 0 { - t.Error() - } -} - -func TestRemoveAccessFailedWorkerConstraint(t *testing.T) { - - pid := createProjectAsAdmin(api.CreateProjectRequest{ - Priority: 1, - GitRepo: "dfffffffffff", - CloneUrl: "fffffffffff23r", - Version: "f83w9rw", - Motd: "ddddddddd", - Name: "removeaccessfailedworkerconstraint", - Public: true, - }).Id - - resp := removeAccess(0, pid) - - if resp.Ok != false { - t.Error() - } - if len(resp.Message) <= 0 { - t.Error() - } -} - -func TestGrantAccessFailedWorkerConstraint(t *testing.T) { - - pid := createProjectAsAdmin(api.CreateProjectRequest{ - Priority: 1, - GitRepo: "dfffffffffff1", - CloneUrl: "fffffffffff23r1", - Version: "f83w9rw1", - Motd: "ddddddddd1", - Name: "grantaccessfailedworkerconstraint", - Public: true, - }).Id - - resp := removeAccess(0, pid) - - if resp.Ok != false { - t.Error() - } - if len(resp.Message) <= 0 { - t.Error() - } -} - func TestUpdateAliasValid(t *testing.T) { wid := genWid() @@ -169,7 +96,30 @@ func TestCreateWorkerAliasInvalid(t *testing.T) { if len(resp.Message) <= 0 { t.Error() } +} +func TestInvalidAccessRequest(t *testing.T) { + + w := genWid() + pid := createProjectAsAdmin(api.CreateProjectRequest{ + Name: "testinvalidaccessreq", + CloneUrl: "testinvalidaccessreq", + GitRepo: "testinvalidaccessreq", + }).Id + + r := requestAccess(api.WorkerAccessRequest{ + Submit: false, + Assign: false, + Project: pid, + }, w) + + if r.Ok != false { + t.Error() + } + + if len(r.Message) <= 0 { + t.Error() + } } func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) { @@ -201,14 +151,11 @@ func genWid() *storage.Worker { return resp.Worker } -func grantAccess(wid int64, project int64) *api.WorkerAccessResponse { +func requestAccess(req api.WorkerAccessRequest, w *storage.Worker) *api.WorkerAccessRequestResponse { - r := Post("/access/grant", api.WorkerAccessRequest{ - WorkerId: wid, - ProjectId: project, - }, nil, nil) + r := Post(fmt.Sprintf("/project/request_access"), req, w, nil) - var resp *api.WorkerAccessResponse + var resp *api.WorkerAccessRequestResponse data, _ := ioutil.ReadAll(r.Body) err := json.Unmarshal(data, &resp) handleErr(err) @@ -216,14 +163,25 @@ func grantAccess(wid int64, project int64) *api.WorkerAccessResponse { return resp } -func removeAccess(wid int64, project int64) *api.WorkerAccessResponse { +func acceptAccessRequest(pid int64, wid int64, s *http.Client) *api.WorkerAccessRequestResponse { - r := Post("/access/remove", api.WorkerAccessRequest{ - WorkerId: wid, - ProjectId: project, - }, nil, nil) + r := Post(fmt.Sprintf("/project/accept_request/%d/%d", pid, wid), nil, + nil, s) - var resp *api.WorkerAccessResponse + var resp *api.WorkerAccessRequestResponse + data, _ := ioutil.ReadAll(r.Body) + err := json.Unmarshal(data, &resp) + handleErr(err) + + return resp +} + +func rejectAccessRequest(pid int64, wid int64, s *http.Client) *api.WorkerAccessRequestResponse { + + r := Post(fmt.Sprintf("/project/reject_request/%d/%d", pid, wid), nil, + nil, s) + + var resp *api.WorkerAccessRequestResponse data, _ := ioutil.ReadAll(r.Body) err := json.Unmarshal(data, &resp) handleErr(err) diff --git a/test/schema.sql b/test/schema.sql index b0344d4..650492b 100755 --- a/test/schema.sql +++ b/test/schema.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS worker, project, task, log_entry, - worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot, - worker_verifies_task, worker_requests_access_to_project; + worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, + worker_verifies_task; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; @@ -28,10 +28,13 @@ CREATE TABLE project motd TEXT NOT NULL ); -CREATE TABLE worker_has_access_to_project +CREATE TABLE worker_access ( - worker INTEGER REFERENCES worker (id), - project INTEGER REFERENCES project (id), + worker INTEGER REFERENCES worker (id), + project INTEGER REFERENCES project (id), + role_assign boolean, + role_submit boolean, + request boolean, primary key (worker, project) ); @@ -81,7 +84,7 @@ CREATE TABLE manager_has_role_on_project manager INTEGER REFERENCES manager (id) NOT NULL, role SMALLINT NOT NULL, project INTEGER REFERENCES project (id) NOT NULL, - primary key (manager, project) + PRIMARY KEY (manager, project) ); CREATE TABLE project_monitoring_snapshot @@ -95,12 +98,6 @@ CREATE TABLE project_monitoring_snapshot timestamp INT NOT NULL ); -CREATE TABLE worker_requests_access_to_project -( - worker INT REFERENCES worker (id) NOT NULL, - project INT REFERENCES project (id) NOT NULL -); - CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS $$ DECLARE diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index 06c27db..eed8374 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -65,8 +65,8 @@ export class ApiService { return this.http.get(this.url + `/worker/stats`, this.options) } - getProjectAccessRequests(project: number) { - return this.http.get(this.url + `/project/requests/${project}`) + getProjectAccess(project: number) { + return this.http.get(this.url + `/project/accesses/${project}`) } getAllManagers() { @@ -81,4 +81,12 @@ export class ApiService { return this.http.get(this.url + `/manager/demote/${managerId}`) } + acceptWorkerAccessRequest(wid: number, pid: number) { + return this.http.post(this.url + `/project/accept_request/${pid}/${wid}`, null) + } + + rejectWorkerAccessRequest(wid: number, pid: number) { + return this.http.post(this.url + `/project/reject_request/${pid}/${wid}`, null) + } + } diff --git a/web/angular/src/app/models/worker-access.ts b/web/angular/src/app/models/worker-access.ts new file mode 100644 index 0000000..474392e --- /dev/null +++ b/web/angular/src/app/models/worker-access.ts @@ -0,0 +1,9 @@ +import {Worker} from "./worker" + +export interface WorkerAccess { + submit: boolean + assign: boolean + request: boolean + worker: Worker + project: number +} diff --git a/web/angular/src/app/project-perms/project-perms.component.css b/web/angular/src/app/project-perms/project-perms.component.css index f94e490..eed824e 100644 --- a/web/angular/src/app/project-perms/project-perms.component.css +++ b/web/angular/src/app/project-perms/project-perms.component.css @@ -2,3 +2,7 @@ button { margin-left: 15px; } + +.request { + color: #757575; +} diff --git a/web/angular/src/app/project-perms/project-perms.component.html b/web/angular/src/app/project-perms/project-perms.component.html index 64543a0..338fde8 100644 --- a/web/angular/src/app/project-perms/project-perms.component.html +++ b/web/angular/src/app/project-perms/project-perms.component.html @@ -11,19 +11,23 @@ - - person_add -

{{w.alias}}

+ + library_add + get_app +

{{wa.worker.alias}} {{wa.request ? ('perms.pending' | translate) : ''}}

- Id={{w.id}}, {{"perms.created" | translate}} + Id={{wa.worker.id}}, {{"perms.created" | translate}} {{moment.unix(w.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}} + class="text-mono">{{moment.unix(wa.worker.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}
- -
diff --git a/web/angular/src/app/project-perms/project-perms.component.ts b/web/angular/src/app/project-perms/project-perms.component.ts index 48fd1c9..f52c567 100644 --- a/web/angular/src/app/project-perms/project-perms.component.ts +++ b/web/angular/src/app/project-perms/project-perms.component.ts @@ -1,4 +1,3 @@ -import {Worker} from "../models/worker" import {Component, OnInit} from '@angular/core'; import {ApiService} from "../api.service"; import {Project} from "../models/project"; @@ -7,6 +6,7 @@ import {MessengerService} from "../messenger.service"; import {TranslateService} from "@ngx-translate/core"; import * as moment from "moment" +import {WorkerAccess} from "../models/worker-access"; @Component({ selector: 'app-project-perms', @@ -24,7 +24,7 @@ export class ProjectPermsComponent implements OnInit { project: Project; private projectId: number; - requests: Worker[]; + accesses: WorkerAccess[]; unauthorized: boolean = false; moment = moment; @@ -32,20 +32,30 @@ export class ProjectPermsComponent implements OnInit { this.route.params.subscribe(params => { this.projectId = params["id"]; this.getProject(); - this.getProjectRequests(); + this.getProjectAccesses(); }) } + public acceptRequest(wa: WorkerAccess) { + this.apiService.acceptWorkerAccessRequest(wa.worker.id, this.projectId) + .subscribe(() => this.getProjectAccesses()) + } + + public rejectRequest(wa: WorkerAccess) { + this.apiService.rejectWorkerAccessRequest(wa.worker.id, this.projectId) + .subscribe(() => this.getProjectAccesses()) + } + private getProject() { this.apiService.getProject(this.projectId).subscribe(data => { this.project = data["project"] }) } - private getProjectRequests() { - this.apiService.getProjectAccessRequests(this.projectId).subscribe( + private getProjectAccesses() { + this.apiService.getProjectAccess(this.projectId).subscribe( data => { - this.requests = data["requests"] + this.accesses = data["accesses"] }, error => { if (error && (error.status == 401 || error.status == 403)) { @@ -55,6 +65,6 @@ export class ProjectPermsComponent implements OnInit { } public refresh() { - this.getProjectRequests() + this.getProjectAccesses() } } diff --git a/web/angular/src/assets/i18n/en.json b/web/angular/src/assets/i18n/en.json index 3b245af..b963c6c 100644 --- a/web/angular/src/assets/i18n/en.json +++ b/web/angular/src/assets/i18n/en.json @@ -103,7 +103,11 @@ "created": "Created on", "grant": "Accept request", "reject": "Deny request", - "refresh": "Refresh" + "remove": "Remove access", + "refresh": "Refresh", + "pending": "(Pending)", + "assign": "Assign", + "submit": "Submit" }, "messenger": { "close": "Close", diff --git a/web/angular/src/assets/i18n/fr.json b/web/angular/src/assets/i18n/fr.json index ca6cd99..578611b 100644 --- a/web/angular/src/assets/i18n/fr.json +++ b/web/angular/src/assets/i18n/fr.json @@ -105,7 +105,11 @@ "created": "Créé le", "grant": "Accepter la requête", "reject": "Rejeter la requête", - "refresh": "Refraichir" + "remove": "Enlever l'accès", + "refresh": "Refraichir", + "pending": "(En attente)", + "assign": "Assigner", + "submit": "Soumettre" }, "messenger": { "close": "Fermer",