diff --git a/api/auth.go b/api/auth.go index 279e5cf..0155545 100644 --- a/api/auth.go +++ b/api/auth.go @@ -145,7 +145,6 @@ func (api *WebAPI) AccountDetails(r *Request) { logrus.WithFields(logrus.Fields{ "manager": manager, - "session": sess, }).Trace("Account details request") if manager == nil { diff --git a/api/main.go b/api/main.go index f53c15c..7adaf4d 100644 --- a/api/main.go +++ b/api/main.go @@ -90,6 +90,8 @@ 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.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 0f561ba..fa1a685 100644 --- a/api/project.go +++ b/api/project.go @@ -15,6 +15,7 @@ type CreateProjectRequest struct { Priority int64 `json:"priority"` Motd string `json:"motd"` Public bool `json:"public"` + Hidden bool `json:"hidden"` } type UpdateProjectRequest struct { @@ -24,6 +25,7 @@ type UpdateProjectRequest struct { Priority int64 `json:"priority"` Motd string `json:"motd"` Public bool `json:"public"` + Hidden bool `json:"hidden"` } type UpdateProjectResponse struct { @@ -55,8 +57,22 @@ type GetAssigneeStatsResponse struct { Assignees *[]storage.AssignedTasks `json:"assignees"` } +type WorkerRequestAccessResponse 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"` +} + func (api *WebAPI) ProjectCreate(r *Request) { + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + createReq := &CreateProjectRequest{} err := json.Unmarshal(r.Ctx.Request.Body(), createReq) if err != nil { @@ -74,26 +90,10 @@ func (api *WebAPI) ProjectCreate(r *Request) { Priority: createReq.Priority, Motd: createReq.Motd, Public: createReq.Public, + Hidden: createReq.Hidden, } - if isValidProject(project) { - id, err := api.Database.SaveProject(project) - - if err != nil { - r.Json(CreateProjectResponse{ - Ok: false, - Message: err.Error(), - }, 500) - } else { - r.OkJson(CreateProjectResponse{ - Ok: true, - Id: id, - }) - logrus.WithFields(logrus.Fields{ - "project": project, - }).Debug("Created project") - } - } else { + if !isValidProject(project) { logrus.WithFields(logrus.Fields{ "project": project, }).Warn("Invalid project") @@ -102,7 +102,36 @@ func (api *WebAPI) ProjectCreate(r *Request) { Ok: false, Message: "Invalid project", }, 400) + return } + + if !isProjectCreationAuthorized(project, manager) { + logrus.WithFields(logrus.Fields{ + "project": project, + }).Warn("Unauthorized project creation") + + r.Json(CreateProjectResponse{ + Ok: false, + Message: "You are not permitted to create a project with this configuration", + }, 400) + return + } + + id, err := api.Database.SaveProject(project) + if err != nil { + r.Json(CreateProjectResponse{ + Ok: false, + Message: err.Error(), + }, 500) + return + } + r.OkJson(CreateProjectResponse{ + Ok: true, + Id: id, + }) + logrus.WithFields(logrus.Fields{ + "project": project, + }).Debug("Created project") } func (api *WebAPI) ProjectUpdate(r *Request) { @@ -133,6 +162,7 @@ func (api *WebAPI) ProjectUpdate(r *Request) { Priority: updateReq.Priority, Motd: updateReq.Motd, Public: updateReq.Public, + Hidden: updateReq.Hidden, } if isValidProject(project) { @@ -180,6 +210,38 @@ func isValidProject(project *storage.Project) bool { return true } +func isProjectCreationAuthorized(project *storage.Project, manager interface{}) bool { + + return true + if manager == nil { + return false + } + + if project.Public && manager.(*storage.Manager).WebsiteAdmin { + return false + } + return true +} + +func isProjectUpdateAuthorized(project *storage.Project, manager interface{}, db *storage.Database) bool { + + var man storage.Manager + if manager != nil { + man = manager.(storage.Manager) + } + + if man.WebsiteAdmin { + return true + } + + role := db.ManagerHasRoleOn(&man, project.Id) + if role&storage.ROLE_EDIT == 1 { + return true + } + + return false +} + func (api *WebAPI) ProjectGet(r *Request) { id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) @@ -230,3 +292,64 @@ func (api *WebAPI) ProjectGetAssigneeStats(r *Request) { Assignees: stats, }) } + +func (api *WebAPI) ProjectGetAccessRequests(r *Request) { + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + 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.ManagerHasRoleOn(manager.(*storage.Manager), 1)& + storage.ROLE_MANAGE_ACCESS == 0 { + r.Json(ProjectGetAccessRequestsResponse{ + Ok: false, + Message: "Unauthorized", + }, 403) + return + } + requests := api.Database.GetAllAccessRequests(id) + + r.OkJson(ProjectGetAccessRequestsResponse{ + Ok: true, + Requests: requests, + }) +} + +func (api *WebAPI) WorkerRequestAccess(r *Request) { + + id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + worker, err := api.validateSignature(r) + if err != nil { + r.Json(WorkerRequestAccessResponse{ + Ok: false, + Message: err.Error(), + }, 401) + } + + res := api.Database.SaveAccessRequest(worker, id) + + if res { + r.OkJson(WorkerRequestAccessResponse{ + Ok: true, + }) + } else { + r.Json(WorkerRequestAccessResponse{ + Ok: false, + Message: "Project is public, you already have " + + "an active request or you already have access to this project", + }, 400) + } +} diff --git a/api/task.go b/api/task.go index d5bb5b0..8728559 100644 --- a/api/task.go +++ b/api/task.go @@ -177,6 +177,10 @@ func (api WebAPI) validateSignature(r *Request) (*storage.Worker, error) { widStr := string(r.Ctx.Request.Header.Peek("X-Worker-Id")) signature := r.Ctx.Request.Header.Peek("X-Signature") + if widStr == "" { + return nil, errors.New("worker id not specified") + } + wid, err := strconv.ParseInt(widStr, 10, 64) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ diff --git a/api/worker.go b/api/worker.go index 611dab3..83492d5 100644 --- a/api/worker.go +++ b/api/worker.go @@ -58,11 +58,9 @@ func (api *WebAPI) WorkerCreate(r *Request) { return } - identity := getIdentity(r) - if !canCreateWorker(r, workerReq, identity) { + if !canCreateWorker(r, workerReq) { logrus.WithFields(logrus.Fields{ - "identity": identity, "createWorkerRequest": workerReq, }).Warn("Failed CreateWorkerRequest") @@ -73,7 +71,7 @@ func (api *WebAPI) WorkerCreate(r *Request) { return } - worker, err := api.workerCreate(workerReq, getIdentity(r)) + worker, err := api.workerCreate(workerReq) if err != nil { handleErr(err, r) } else { @@ -185,7 +183,7 @@ func (api *WebAPI) WorkerUpdate(r *Request) { r.Json(GetTaskResponse{ Ok: false, Message: err.Error(), - }, 403) + }, 401) return } @@ -224,24 +222,23 @@ func (api *WebAPI) GetAllWorkerStats(r *Request) { }) } -func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (*storage.Worker, error) { +func (api *WebAPI) workerCreate(request *CreateWorkerRequest) (*storage.Worker, error) { if request.Alias == "" { request.Alias = "default_alias" } worker := storage.Worker{ - Created: time.Now().Unix(), - Identity: identity, - Secret: makeSecret(), - Alias: request.Alias, + Created: time.Now().Unix(), + Secret: makeSecret(), + Alias: request.Alias, } api.Database.SaveWorker(&worker) return &worker, nil } -func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool { +func canCreateWorker(r *Request, cwr *CreateWorkerRequest) bool { if cwr.Alias == "unassigned" { //Reserved alias @@ -260,13 +257,3 @@ func makeSecret() []byte { return secret } - -func getIdentity(r *Request) *storage.Identity { - - identity := storage.Identity{ - RemoteAddr: r.Ctx.RemoteAddr().String(), - UserAgent: string(r.Ctx.UserAgent()), - } - - return &identity -} diff --git a/schema.sql b/schema.sql index 85cf48b..4bbc6a0 100755 --- a/schema.sql +++ b/schema.sql @@ -1,39 +1,30 @@ -DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry, +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_verifies_task, worker_requests_access_to_project; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; -CREATE TABLE worker_identity -( - id SERIAL PRIMARY KEY, - remote_addr TEXT, - user_agent TEXT, - - UNIQUE (remote_addr) -); - CREATE TABLE worker ( - id SERIAL PRIMARY KEY, - alias TEXT, - created INTEGER, - identity INTEGER REFERENCES worker_identity (id), - secret BYTEA, - closed_task_count INTEGER DEFAULT 0 + id SERIAL PRIMARY KEY NOT NULL, + alias TEXT NOT NULL, + created INTEGER NOT NULL, + secret BYTEA NOT NULL, + closed_task_count INTEGER DEFAULT 0 NOT NULL ); CREATE TABLE project ( - id SERIAL PRIMARY KEY, - priority INTEGER DEFAULT 0, - name TEXT UNIQUE, - clone_url TEXT, - git_repo TEXT UNIQUE, - version TEXT, - motd TEXT, - public boolean, - closed_task_count INT DEFAULT 0 + id SERIAL PRIMARY KEY NOT NULL, + priority INTEGER DEFAULT 0 NOT NULL, + closed_task_count INT DEFAULT 0 NOT NULL, + public boolean NOT NULL, + hidden boolean NOT NULL, + name TEXT UNIQUE NOT NULL, + clone_url TEXT NOT NULL, + git_repo TEXT UNIQUE NOT NULL, + version TEXT NOT NULL, + motd TEXT NOT NULL ); CREATE TABLE worker_has_access_to_project @@ -61,43 +52,51 @@ CREATE TABLE task CREATE TABLE worker_verifies_task ( - verification_hash BIGINT, - task BIGINT REFERENCES task (id) ON DELETE CASCADE, - worker INT REFERENCES worker (id) + verification_hash BIGINT NOT NULL, + task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL, + worker INT REFERENCES worker (id) NOT NULL ); CREATE TABLE log_entry ( - level INTEGER, - message TEXT, - message_data TEXT, - timestamp INTEGER + level INTEGER NOT NULL, + message TEXT NOT NULL, + message_data TEXT NOT NULL, + timestamp INTEGER NOT NULL ); CREATE TABLE manager ( id SERIAL PRIMARY KEY, - username TEXT UNIQUE, - password BYTEA, - website_admin BOOLEAN + register_time INTEGER NOT NULL, + website_admin BOOLEAN NOT NULL, + username TEXT UNIQUE NOT NULL, + password BYTEA NOT NULL ); CREATE TABLE manager_has_role_on_project ( - manager INTEGER REFERENCES manager (id), - role SMALLINT, - project INTEGER REFERENCES project (id) + manager INTEGER REFERENCES manager (id) NOT NULL, + role SMALLINT NOT NULl, + project INTEGER REFERENCES project (id) NOT NULL ); CREATE TABLE project_monitoring_snapshot ( - project INT REFERENCES project (id), - new_task_count INT, - failed_task_count INT, - closed_task_count INT, - awaiting_verification_task_count INT, - worker_access_count INT, - timestamp INT + project INT REFERENCES project (id) NOT NULL, + new_task_count INT NOT NULL, + failed_task_count INT NOT NULL, + closed_task_count INT NOT NULL, + awaiting_verification_task_count INT NOT NULL, + worker_access_count INT NOT NULL, + timestamp INT NOT NULL +); + +CREATE TABLE worker_requests_access_to_project +( + worker INT REFERENCES worker (id), + project INT REFERENCES project (id), + PRIMARY KEY (worker, project) ); CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS @@ -114,6 +113,21 @@ CREATE TRIGGER on_task_delete FOR EACH ROW EXECUTE PROCEDURE on_task_delete_proc(); +CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS +$$ +BEGIN + IF NEW.id = 1 THEN + UPDATE manager SET website_admin= TRUE WHERE id = 1; + end if; + RETURN NEW; +END; +$$ LANGUAGE 'plpgsql'; +CREATE TRIGGER on_manager_insert + AFTER INSERT + ON manager + FOR EACH ROW +EXECUTE PROCEDURE on_manager_insert(); + CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver INT) RETURNS BOOLEAN AS $$ DECLARE diff --git a/storage/auth.go b/storage/auth.go index b8ebcdd..441848e 100644 --- a/storage/auth.go +++ b/storage/auth.go @@ -10,9 +10,10 @@ import ( type ManagerRole int const ( + ROLE_NONE ManagerRole = 0 ROLE_READ ManagerRole = 1 ROLE_EDIT ManagerRole = 2 - ROLE_MANAGE_ACCESS ManagerRole = 3 + ROLE_MANAGE_ACCESS ManagerRole = 4 ) type Manager struct { @@ -65,8 +66,8 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error { hash.Write([]byte(manager.Username)) hashedPassword := hash.Sum(nil) - row := db.QueryRow(`INSERT INTO manager (username, password, website_admin) - VALUES ($1,$2,$3) RETURNING ID`, + row := db.QueryRow(`INSERT INTO manager (username, password, website_admin, register_time) + VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID`, manager.Username, hashedPassword, manager.WebsiteAdmin) err := row.Scan(&manager.Id) @@ -78,6 +79,8 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error { return err } + manager.WebsiteAdmin = manager.Id == 1 + logrus.WithFields(logrus.Fields{ "manager": manager, }).Info("Database.SaveManager INSERT") @@ -121,3 +124,19 @@ func (database *Database) UpdateManagerPassword(manager *Manager, newPassword [] "id": manager.Id, }).Warning("Database.UpdateManagerPassword UPDATE") } + +func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) ManagerRole { + + db := database.getDB() + + row := db.QueryRow(`SELECT role FROM manager_has_role_on_project + WHERE project=$1 AND manager=$2`, projectId, manager.Id) + + var role ManagerRole + err := row.Scan(&role) + if err != nil { + return ROLE_NONE + } + + return role +} diff --git a/storage/monitoring.go b/storage/monitoring.go index eec4f84..978bdac 100644 --- a/storage/monitoring.go +++ b/storage/monitoring.go @@ -47,7 +47,7 @@ func (database *Database) MakeProjectSnapshots() { "took": time.Now().Sub(startTime), "add": inserted, "remove": deleted, - }).Trace("Took monitoring snapshot") + }).Trace("Took project monitoring snapshot") } func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to int) (ss *[]ProjectMonitoringSnapshot) { diff --git a/storage/project.go b/storage/project.go index caf1f25..05a21dd 100644 --- a/storage/project.go +++ b/storage/project.go @@ -15,6 +15,7 @@ type Project struct { Version string `json:"version"` Motd string `json:"motd"` Public bool `json:"public"` + Hidden bool `json:"hidden"` } type AssignedTasks struct { @@ -31,9 +32,10 @@ func (database *Database) SaveProject(project *Project) (int64, error) { func saveProject(project *Project, db *sql.DB) (int64, error) { - row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public) - VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING id`, - project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Public) + row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public, hidden) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`, + project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, + project.Public, project.Hidden) var id int64 err := row.Scan(&id) @@ -64,7 +66,7 @@ func (database *Database) GetProject(id int64) *Project { func getProject(id int64, db *sql.DB) *Project { - row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public + row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden FROM project WHERE id=$1`, id) project, err := scanProject(row) @@ -87,7 +89,7 @@ func scanProject(row *sql.Row) (*Project, error) { project := &Project{} err := row.Scan(&project.Id, &project.Priority, &project.Name, &project.CloneUrl, - &project.GitRepo, &project.Version, &project.Motd, &project.Public) + &project.GitRepo, &project.Version, &project.Motd, &project.Public, &project.Hidden) return project, err } @@ -95,7 +97,7 @@ func scanProject(row *sql.Row) (*Project, error) { func (database *Database) GetProjectWithRepoName(repoName string) *Project { db := database.getDB() - row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public + row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden FROM project WHERE LOWER(git_repo)=$1`, strings.ToLower(repoName)) @@ -115,8 +117,9 @@ func (database *Database) UpdateProject(project *Project) error { db := database.getDB() res, err := db.Exec(`UPDATE project - SET (priority, name, clone_url, git_repo, version, motd, public) = ($1,$2,$3,$4,$5,$6,$7) WHERE id=$8`, - project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Public, project.Id) + SET (priority, name, clone_url, git_repo, version, motd, public, hidden) = ($1,$2,$3,$4,$5,$6,$7,$8) WHERE id=$9`, + project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, + project.Public, project.Hidden, project.Id) if err != nil { return err } @@ -139,26 +142,24 @@ func (database Database) GetAllProjects(workerId int64) *[]Project { var err error if workerId == 0 { rows, err = db.Query(`SELECT - Id, priority, name, clone_url, git_repo, version, motd, public + Id, priority, name, clone_url, git_repo, version, motd, public, hidden FROM project - LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id - WHERE public + WHERE NOT hidden ORDER BY name`) } else { rows, err = db.Query(`SELECT - Id, priority, name, clone_url, git_repo, version, motd, public + Id, priority, name, clone_url, git_repo, version, motd, public, hidden FROM project LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id - WHERE public OR whatp.worker = $1 + WHERE NOT hidden OR whatp.worker = $1 ORDER BY name`, workerId) } handleErr(err) for rows.Next() { - p := Project{} err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, - &p.GitRepo, &p.Version, &p.Motd, &p.Public) + &p.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden) handleErr(err) projects = append(projects, p) } diff --git a/storage/worker.go b/storage/worker.go index 237a094..100c950 100644 --- a/storage/worker.go +++ b/storage/worker.go @@ -1,22 +1,14 @@ package storage import ( - "database/sql" - "errors" "github.com/Sirupsen/logrus" ) -type Identity struct { - RemoteAddr string `json:"remote_addr"` - UserAgent string `json:"user_agent"` -} - type Worker struct { - Id int64 `json:"id"` - Created int64 `json:"created"` - Identity *Identity `json:"identity"` - Alias string `json:"alias,omitempty"` - Secret []byte `json:"secret"` + Id int64 `json:"id"` + Created int64 `json:"created"` + Alias string `json:"alias,omitempty"` + Secret []byte `json:"secret"` } type WorkerStats struct { @@ -28,10 +20,8 @@ func (database *Database) SaveWorker(worker *Worker) { db := database.getDB() - identityId := getOrCreateIdentity(worker.Identity, db) - - row := db.QueryRow("INSERT INTO worker (created, identity, secret, alias) VALUES ($1,$2,$3,$4) RETURNING id", - worker.Created, identityId, worker.Secret, worker.Alias) + 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) handleErr(err) @@ -46,10 +36,9 @@ func (database *Database) GetWorker(id int64) *Worker { db := database.getDB() worker := &Worker{} - var identityId int64 - row := db.QueryRow("SELECT id, created, identity, secret, alias FROM worker WHERE id=$1", id) - err := row.Scan(&worker.Id, &worker.Created, &identityId, &worker.Secret, &worker.Alias) + row := db.QueryRow("SELECT id, created, secret, alias FROM worker WHERE id=$1", id) + err := row.Scan(&worker.Id, &worker.Created, &worker.Secret, &worker.Alias) if err != nil { logrus.WithFields(logrus.Fields{ "id": id, @@ -57,9 +46,6 @@ func (database *Database) GetWorker(id int64) *Worker { return nil } - worker.Identity, err = getIdentity(identityId, db) - handleErr(err) - logrus.WithFields(logrus.Fields{ "worker": worker, }).Trace("Database.getWorker SELECT worker") @@ -67,48 +53,6 @@ func (database *Database) GetWorker(id int64) *Worker { return worker } -func getIdentity(id int64, db *sql.DB) (*Identity, error) { - - identity := &Identity{} - - row := db.QueryRow("SELECT remote_addr, user_agent FROM worker_identity WHERE id=$1", id) - err := row.Scan(&identity.RemoteAddr, &identity.UserAgent) - - if err != nil { - return nil, errors.New("identity not found") - } - - logrus.WithFields(logrus.Fields{ - "identity": identity, - }).Trace("Database.getIdentity SELECT workerIdentity") - - return identity, nil -} - -func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 { - - res, err := db.Exec("INSERT INTO worker_identity (remote_addr, user_agent) VALUES ($1,$2) ON CONFLICT DO NOTHING", - identity.RemoteAddr, identity.UserAgent) - handleErr(err) - - rowsAffected, err := res.RowsAffected() - logrus.WithFields(logrus.Fields{ - "rowsAffected": rowsAffected, - }).Trace("Database.saveWorker INSERT workerIdentity") - - row := db.QueryRow("SELECT (id) FROM worker_identity WHERE remote_addr=$1", identity.RemoteAddr) - - var rowId int64 - err = row.Scan(&rowId) - handleErr(err) - - logrus.WithFields(logrus.Fields{ - "rowId": rowId, - }).Trace("Database.saveWorker SELECT workerIdentity") - - return rowId -} - func (database *Database) GrantAccess(workerId int64, projectId int64) bool { db := database.getDB() @@ -169,6 +113,88 @@ func (database *Database) UpdateWorker(worker *Worker) bool { return rowsAffected == 1 } +func (database *Database) SaveAccessRequest(worker *Worker, projectId int64) 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) + if err != nil { + return false + } + + rowsAffected, _ := res.RowsAffected() + + logrus.WithFields(logrus.Fields{ + "rowsAffected": rowsAffected, + }).Trace("Database.SaveAccessRequest INSERT") + + return rowsAffected == 1 +} + +func (database *Database) AcceptAccessRequest(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`) + 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) + handleErr(err) + + rowsAffected, _ := res.RowsAffected() + + logrus.WithFields(logrus.Fields{ + "rowsAffected": rowsAffected, + }).Trace("Database.AcceptAccessRequest") + + return rowsAffected == 1 +} + +func (database *Database) GetAllAccessRequests(projectId int64) *[]Worker { + + 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`, + projectId) + handleErr(err) + + requests := make([]Worker, 0) + + for rows.Next() { + w := Worker{} + _ = rows.Scan(&w.Id, &w.Alias, &w.Created) + requests = append(requests, w) + } + + return &requests +} + func (database *Database) GetAllWorkerStats() *[]WorkerStats { db := database.getDB() diff --git a/test/api_auth_test.go b/test/api_auth_test.go index 8129213..a2da173 100644 --- a/test/api_auth_test.go +++ b/test/api_auth_test.go @@ -163,3 +163,7 @@ func login(request *api.LoginRequest) (*api.LoginResponse, *http.Response) { return resp, r } + +func getSessionCtx(username string, password string, admin bool) { + +} diff --git a/test/api_project_test.go b/test/api_project_test.go index 9f62c39..cac4703 100644 --- a/test/api_project_test.go +++ b/test/api_project_test.go @@ -19,6 +19,7 @@ func TestCreateGetProject(t *testing.T) { Priority: 123, Motd: "motd", Public: true, + Hidden: true, }) id := resp.Id @@ -59,6 +60,9 @@ func TestCreateGetProject(t *testing.T) { if getResp.Project.Public != true { t.Error() } + if getResp.Project.Hidden != true { + t.Error() + } } func TestCreateProjectInvalid(t *testing.T) { @@ -141,6 +145,7 @@ func TestUpdateProjectValid(t *testing.T) { Name: "NameB", Motd: "MotdB", Public: false, + Hidden: true, }, pid) if updateResp.Ok != true { @@ -164,6 +169,9 @@ func TestUpdateProjectValid(t *testing.T) { if proj.Project.Priority != 2 { t.Error() } + if proj.Project.Hidden != true { + t.Error() + } } func TestUpdateProjectInvalid(t *testing.T) { diff --git a/test/api_worker_test.go b/test/api_worker_test.go index 3756525..4ba1fdc 100644 --- a/test/api_worker_test.go +++ b/test/api_worker_test.go @@ -33,12 +33,6 @@ func TestCreateGetWorker(t *testing.T) { t.Error() } - if len(getResp.Worker.Identity.RemoteAddr) <= 0 { - t.Error() - } - if len(getResp.Worker.Identity.UserAgent) <= 0 { - t.Error() - } if resp.Worker.Alias != "my_worker_alias" { t.Error() } diff --git a/test/common.go b/test/common.go index 4390bc5..d063835 100644 --- a/test/common.go +++ b/test/common.go @@ -15,6 +15,11 @@ import ( "strconv" ) +type SessionContext struct { + Manager *storage.Manager + SessionCookie *http.Cookie +} + func Post(path string, x interface{}, worker *storage.Worker) *http.Response { body, err := json.Marshal(x) diff --git a/test/schema.sql b/test/schema.sql index 85cf48b..af5a490 100755 --- a/test/schema.sql +++ b/test/schema.sql @@ -1,39 +1,30 @@ -DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry, +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_verifies_task, worker_requests_access_to_project; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; -CREATE TABLE worker_identity -( - id SERIAL PRIMARY KEY, - remote_addr TEXT, - user_agent TEXT, - - UNIQUE (remote_addr) -); - CREATE TABLE worker ( - id SERIAL PRIMARY KEY, - alias TEXT, - created INTEGER, - identity INTEGER REFERENCES worker_identity (id), - secret BYTEA, - closed_task_count INTEGER DEFAULT 0 + id SERIAL PRIMARY KEY NOT NULL, + alias TEXT NOT NULL, + created INTEGER NOT NULL, + secret BYTEA NOT NULL, + closed_task_count INTEGER DEFAULT 0 NOT NULL ); CREATE TABLE project ( - id SERIAL PRIMARY KEY, - priority INTEGER DEFAULT 0, - name TEXT UNIQUE, - clone_url TEXT, - git_repo TEXT UNIQUE, - version TEXT, - motd TEXT, - public boolean, - closed_task_count INT DEFAULT 0 + id SERIAL PRIMARY KEY NOT NULL, + priority INTEGER DEFAULT 0 NOT NULL, + closed_task_count INT DEFAULT 0 NOT NULL, + public boolean NOT NULL, + hidden boolean NOT NULL, + name TEXT UNIQUE NOT NULL, + clone_url TEXT NOT NULL, + git_repo TEXT UNIQUE NOT NULL, + version TEXT NOT NULL, + motd TEXT NOT NULL ); CREATE TABLE worker_has_access_to_project @@ -61,43 +52,50 @@ CREATE TABLE task CREATE TABLE worker_verifies_task ( - verification_hash BIGINT, - task BIGINT REFERENCES task (id) ON DELETE CASCADE, - worker INT REFERENCES worker (id) + verification_hash BIGINT NOT NULL, + task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL, + worker INT REFERENCES worker (id) NOT NULL ); CREATE TABLE log_entry ( - level INTEGER, - message TEXT, - message_data TEXT, - timestamp INTEGER + level INTEGER NOT NULL, + message TEXT NOT NULL, + message_data TEXT NOT NULL, + timestamp INTEGER NOT NULL ); CREATE TABLE manager ( id SERIAL PRIMARY KEY, - username TEXT UNIQUE, - password BYTEA, - website_admin BOOLEAN + register_time INTEGER NOT NULL, + website_admin BOOLEAN NOT NULL, + username TEXT UNIQUE NOT NULL, + password BYTEA NOT NULL ); CREATE TABLE manager_has_role_on_project ( - manager INTEGER REFERENCES manager (id), - role SMALLINT, - project INTEGER REFERENCES project (id) + manager INTEGER REFERENCES manager (id) NOT NULL, + role SMALLINT NOT NULl, + project INTEGER REFERENCES project (id) NOT NULL ); CREATE TABLE project_monitoring_snapshot ( - project INT REFERENCES project (id), - new_task_count INT, - failed_task_count INT, - closed_task_count INT, - awaiting_verification_task_count INT, - worker_access_count INT, - timestamp INT + project INT REFERENCES project (id) NOT NULL, + new_task_count INT NOT NULL, + failed_task_count INT NOT NULL, + closed_task_count INT NOT NULL, + awaiting_verification_task_count INT NOT NULL, + worker_access_count INT NOT NULL, + 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 @@ -114,6 +112,21 @@ CREATE TRIGGER on_task_delete FOR EACH ROW EXECUTE PROCEDURE on_task_delete_proc(); +CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS +$$ +BEGIN + IF NEW.id = 1 THEN + UPDATE manager SET website_admin= TRUE WHERE id = 1; + end if; + RETURN NEW; +END; +$$ LANGUAGE 'plpgsql'; +CREATE TRIGGER on_manager_insert + AFTER INSERT + ON manager + FOR EACH ROW +EXECUTE PROCEDURE on_manager_insert(); + CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver INT) RETURNS BOOLEAN AS $$ DECLARE diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index a631656..cc9429c 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -65,4 +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}`) + } + } diff --git a/web/angular/src/app/app-routing.module.ts b/web/angular/src/app/app-routing.module.ts index 1171089..cf5a1fe 100755 --- a/web/angular/src/app/app-routing.module.ts +++ b/web/angular/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ import {TranslateService} from "@ngx-translate/core"; import {LoginComponent} from "./login/login.component"; import {AccountDetailsComponent} from "./account-details/account-details.component"; import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component"; +import {ProjectPermsComponent} from "./project-perms/project-perms.component"; const routes: Routes = [ {path: "log", component: LogsComponent}, @@ -19,6 +20,7 @@ const routes: Routes = [ {path: "projects", component: ProjectListComponent}, {path: "project/:id", component: ProjectDashboardComponent}, {path: "project/:id/update", component: UpdateProjectComponent}, + {path: "project/:id/perms", component: ProjectPermsComponent}, {path: "new_project", component: CreateProjectComponent}, {path: "workers", component: WorkerDashboardComponent} ]; diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html index d6278ca..c33d0e3 100755 --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -6,10 +6,11 @@ [routerLink]="'log'">{{"nav.logs" | translate}} - +
{{project | json}}