rework worker permissions

This commit is contained in:
simon987 2019-02-16 16:18:28 -05:00
parent e079fc8497
commit 8784b536d3
20 changed files with 454 additions and 328 deletions

View File

@ -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))

View File

@ -59,7 +59,7 @@ type GetAssigneeStatsResponse struct {
Assignees *[]storage.AssignedTasks `json:"assignees"`
}
type WorkerRequestAccessResponse struct {
type WorkerAccessRequestResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
}
@ -67,7 +67,7 @@ type WorkerRequestAccessResponse struct {
type ProjectGetAccessRequestsResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
Requests *[]storage.Worker `json:"requests,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",
})
}
}

View File

@ -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{

View File

@ -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)

View File

@ -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),
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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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),
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

View File

@ -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)
}
}

View File

@ -0,0 +1,9 @@
import {Worker} from "./worker"
export interface WorkerAccess {
submit: boolean
assign: boolean
request: boolean
worker: Worker
project: number
}

View File

@ -2,3 +2,7 @@
button {
margin-left: 15px;
}
.request {
color: #757575;
}

View File

@ -11,19 +11,23 @@
<mat-card-content>
<mat-list *ngIf="!unauthorized">
<mat-list-item *ngFor="let w of requests">
<mat-icon mat-list-icon>person_add</mat-icon>
<h4 mat-line>{{w.alias}}</h4>
<mat-list-item *ngFor="let wa of accesses" [class.request]="wa.request">
<mat-icon mat-list-icon *ngIf="wa.submit" [title]="'perms.assign'|translate">library_add</mat-icon>
<mat-icon mat-list-icon *ngIf="wa.assign" [title]="'perms.submit'|translate">get_app</mat-icon>
<h4 mat-line>{{wa.worker.alias}} {{wa.request ? ('perms.pending' | translate) : ''}}</h4>
<div mat-line>
Id=<span class="text-mono">{{w.id}}</span>, {{"perms.created" | translate}}
Id=<span class="text-mono">{{wa.worker.id}}</span>, {{"perms.created" | translate}}
<span
class="text-mono">{{moment.unix(w.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}</span>
class="text-mono">{{moment.unix(wa.worker.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}</span>
</div>
<span style="flex: 1 1 auto;"></span>
<button mat-raised-button color="primary" [title]="'perms.grant' | translate">
<button mat-raised-button color="primary" [title]="'perms.grant' | translate"
*ngIf="wa.request" (click)="acceptRequest(wa)">
<mat-icon>check</mat-icon>
</button>
<button mat-raised-button color="warn" [title]="'perms.reject' | translate">
<button mat-raised-button color="warn"
[title]="wa.request ? ('perms.reject'|translate) : ('perms.remove'|translate)"
(click)="rejectRequest(wa)">
<mat-icon>close</mat-icon>
</button>
</mat-list-item>

View File

@ -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()
}
}

View File

@ -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",

View File

@ -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",