mirror of
https://github.com/simon987/task_tracker.git
synced 2025-12-10 21:48:52 +00:00
added optional task unique field
This commit is contained in:
@@ -72,7 +72,8 @@ func (api *WebAPI) ReceiveGitWebHook(r *Request) {
|
||||
version := getVersion(payload)
|
||||
|
||||
project.Version = version
|
||||
api.Database.UpdateProject(project)
|
||||
err := api.Database.UpdateProject(project)
|
||||
handleErr(err, r)
|
||||
}
|
||||
|
||||
func signatureValid(r *Request) (matches bool) {
|
||||
|
||||
16
api/main.go
16
api/main.go
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/buaazp/fasthttprouter"
|
||||
"github.com/valyala/fasthttp"
|
||||
@@ -48,6 +49,7 @@ func New() *WebAPI {
|
||||
api.router.POST("/log/error", LogRequestMiddleware(LogError))
|
||||
|
||||
api.router.POST("/worker/create", LogRequestMiddleware(api.WorkerCreate))
|
||||
api.router.POST("/worker/update", LogRequestMiddleware(api.WorkerUpdate))
|
||||
api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet))
|
||||
|
||||
api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
|
||||
@@ -55,6 +57,7 @@ func New() *WebAPI {
|
||||
|
||||
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))
|
||||
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))
|
||||
api.router.GET("/project/stats", LogRequestMiddleware(api.ProjectGetAllStats))
|
||||
|
||||
@@ -67,6 +70,19 @@ func New() *WebAPI {
|
||||
|
||||
api.router.POST("/logs", LogRequestMiddleware(api.GetLog))
|
||||
|
||||
api.router.NotFound = func(ctx *fasthttp.RequestCtx) {
|
||||
|
||||
if ctx.Request.Header.IsOptions() {
|
||||
ctx.Response.Header.Add("Access-Control-Allow-Headers", "Content-Type")
|
||||
ctx.Response.Header.Add("Access-Control-Allow-Methods", "GET, POST, OPTION")
|
||||
ctx.Response.Header.Add("Access-Control-Allow-Origin", "*")
|
||||
} else {
|
||||
ctx.SetStatusCode(404)
|
||||
_, _ = fmt.Fprintf(ctx, "Not found")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,20 @@ type CreateProjectRequest struct {
|
||||
Public bool `json:"public"`
|
||||
}
|
||||
|
||||
type UpdateProjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Priority int64 `json:"priority"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
}
|
||||
|
||||
type UpdateProjectResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type CreateProjectResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Id int64 `json:"id,omitempty"`
|
||||
@@ -86,10 +100,66 @@ func (api *WebAPI) ProjectCreate(r *Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) ProjectUpdate(r *Request) {
|
||||
|
||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||
handleErr(err, r) //todo handle invalid id
|
||||
|
||||
updateReq := &UpdateProjectRequest{}
|
||||
if r.GetJson(updateReq) {
|
||||
|
||||
project := &storage.Project{
|
||||
Id: id,
|
||||
Name: updateReq.Name,
|
||||
CloneUrl: updateReq.CloneUrl,
|
||||
GitRepo: updateReq.GitRepo,
|
||||
Priority: updateReq.Priority,
|
||||
Motd: updateReq.Motd,
|
||||
Public: updateReq.Public,
|
||||
}
|
||||
|
||||
if isValidProject(project) {
|
||||
err := api.Database.UpdateProject(project)
|
||||
|
||||
if err != nil {
|
||||
r.Json(CreateProjectResponse{
|
||||
Ok: false,
|
||||
Message: err.Error(),
|
||||
}, 500)
|
||||
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"project": project,
|
||||
}).Warn("Error during project update")
|
||||
} else {
|
||||
r.OkJson(UpdateProjectResponse{
|
||||
Ok: true,
|
||||
})
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"project": project,
|
||||
}).Debug("Updated project")
|
||||
}
|
||||
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"project": project,
|
||||
}).Warn("Invalid project")
|
||||
|
||||
r.Json(CreateProjectResponse{
|
||||
Ok: false,
|
||||
Message: "Invalid project",
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidProject(project *storage.Project) bool {
|
||||
if len(project.Name) <= 0 {
|
||||
return false
|
||||
}
|
||||
if project.Priority < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -97,7 +167,7 @@ func isValidProject(project *storage.Project) bool {
|
||||
func (api *WebAPI) ProjectGet(r *Request) {
|
||||
|
||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||
handleErr(err, r)
|
||||
handleErr(err, r) //todo handle invalid id
|
||||
|
||||
project := api.Database.GetProject(id)
|
||||
|
||||
|
||||
77
api/task.go
77
api/task.go
@@ -1,8 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/dchest/siphash"
|
||||
"github.com/google/uuid"
|
||||
"src/task_tracker/storage"
|
||||
"strconv"
|
||||
@@ -14,12 +19,13 @@ type CreateTaskRequest struct {
|
||||
Recipe string `json:"recipe"`
|
||||
Priority int64 `json:"priority"`
|
||||
MaxAssignTime int64 `json:"max_assign_time"`
|
||||
Hash64 int64 `json:"hash_u64"`
|
||||
UniqueString string `json:"unique_string"`
|
||||
}
|
||||
|
||||
type ReleaseTaskRequest struct {
|
||||
TaskId int64 `json:"task_id"`
|
||||
Success bool `json:"success"`
|
||||
WorkerId *uuid.UUID `json:"worker_id"`
|
||||
TaskId int64 `json:"task_id"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type ReleaseTaskResponse struct {
|
||||
@@ -51,8 +57,14 @@ func (api *WebAPI) TaskCreate(r *Request) {
|
||||
MaxAssignTime: createReq.MaxAssignTime,
|
||||
}
|
||||
|
||||
if isTaskValid(task) {
|
||||
err := api.Database.SaveTask(task, createReq.Project)
|
||||
if createReq.IsValid() && isTaskValid(task) {
|
||||
|
||||
if createReq.UniqueString != "" {
|
||||
//TODO: Load key from config
|
||||
createReq.Hash64 = int64(siphash.Hash(1, 2, []byte(createReq.UniqueString)))
|
||||
}
|
||||
|
||||
err := api.Database.SaveTask(task, createReq.Project, createReq.Hash64)
|
||||
|
||||
if err != nil {
|
||||
r.Json(CreateTaskResponse{
|
||||
@@ -76,6 +88,10 @@ func (api *WebAPI) TaskCreate(r *Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req *CreateTaskRequest) IsValid() bool {
|
||||
return req.Hash64 == 0 || req.UniqueString == ""
|
||||
}
|
||||
|
||||
func isTaskValid(task *storage.Task) bool {
|
||||
if task.MaxRetries < 0 {
|
||||
return false
|
||||
@@ -89,7 +105,7 @@ func isTaskValid(task *storage.Task) bool {
|
||||
|
||||
func (api *WebAPI) TaskGetFromProject(r *Request) {
|
||||
|
||||
worker, err := api.workerFromQueryArgs(r)
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
Ok: false,
|
||||
@@ -121,7 +137,7 @@ func (api *WebAPI) TaskGetFromProject(r *Request) {
|
||||
|
||||
func (api *WebAPI) TaskGet(r *Request) {
|
||||
|
||||
worker, err := api.workerFromQueryArgs(r)
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
Ok: false,
|
||||
@@ -138,9 +154,11 @@ func (api *WebAPI) TaskGet(r *Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
|
||||
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")
|
||||
|
||||
widStr := string(r.Ctx.QueryArgs().Peek("wid"))
|
||||
wid, err := uuid.Parse(widStr)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
@@ -155,20 +173,53 @@ func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
|
||||
if worker == nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"wid": widStr,
|
||||
}).Warn("Can't parse wid")
|
||||
}).Warn("Worker id does not match any valid worker")
|
||||
|
||||
return nil, errors.New("worker id does not match any valid worker")
|
||||
}
|
||||
|
||||
var body []byte
|
||||
if r.Ctx.Request.Header.IsGet() {
|
||||
body = r.Ctx.Request.RequestURI()
|
||||
} else {
|
||||
body = r.Ctx.Request.Body()
|
||||
}
|
||||
|
||||
mac := hmac.New(crypto.SHA256.New, worker.Secret)
|
||||
mac.Write(body)
|
||||
|
||||
expectedMac := make([]byte, 64)
|
||||
hex.Encode(expectedMac, mac.Sum(nil))
|
||||
matches := bytes.Compare(expectedMac, signature) == 0
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"expected": string(expectedMac),
|
||||
"signature": string(signature),
|
||||
"matches": matches,
|
||||
}).Trace("Validating Worker signature")
|
||||
|
||||
if !matches {
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
return worker, nil
|
||||
}
|
||||
|
||||
func (api *WebAPI) TaskRelease(r *Request) {
|
||||
|
||||
req := ReleaseTaskRequest{}
|
||||
if r.GetJson(req) {
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
Ok: false,
|
||||
Message: err.Error(),
|
||||
}, 403)
|
||||
return
|
||||
}
|
||||
|
||||
res := api.Database.ReleaseTask(req.TaskId, req.WorkerId, req.Success)
|
||||
var req ReleaseTaskRequest
|
||||
if r.GetJson(&req) {
|
||||
|
||||
res := api.Database.ReleaseTask(req.TaskId, &worker.Id, req.Success)
|
||||
|
||||
response := ReleaseTaskResponse{
|
||||
Ok: res,
|
||||
|
||||
@@ -3,17 +3,28 @@ package api
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
"math/rand"
|
||||
"src/task_tracker/storage"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreateWorkerRequest struct {
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
type UpdateWorkerRequest struct {
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
type UpdateWorkerResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type CreateWorkerResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
WorkerId uuid.UUID `json:"id,omitempty"`
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Worker *storage.Worker `json:"worker,omitempty"`
|
||||
}
|
||||
|
||||
type GetWorkerResponse struct {
|
||||
@@ -55,13 +66,13 @@ func (api *WebAPI) WorkerCreate(r *Request) {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := api.workerCreate(workerReq, getIdentity(r))
|
||||
worker, err := api.workerCreate(workerReq, getIdentity(r))
|
||||
if err != nil {
|
||||
handleErr(err, r)
|
||||
} else {
|
||||
r.OkJson(CreateWorkerResponse{
|
||||
Ok: true,
|
||||
WorkerId: id,
|
||||
Ok: true,
|
||||
Worker: worker,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -84,6 +95,9 @@ func (api *WebAPI) WorkerGet(r *Request) {
|
||||
worker := api.Database.GetWorker(id)
|
||||
|
||||
if worker != nil {
|
||||
|
||||
worker.Secret = nil
|
||||
|
||||
r.OkJson(GetWorkerResponse{
|
||||
Ok: true,
|
||||
Worker: worker,
|
||||
@@ -136,22 +150,75 @@ func (api *WebAPI) WorkerRemoveAccess(r *Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (uuid.UUID, error) {
|
||||
func (api *WebAPI) WorkerUpdate(r *Request) {
|
||||
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
Ok: false,
|
||||
Message: err.Error(),
|
||||
}, 403)
|
||||
return
|
||||
}
|
||||
|
||||
req := &UpdateWorkerRequest{}
|
||||
if r.GetJson(req) {
|
||||
|
||||
worker.Alias = req.Alias
|
||||
|
||||
ok := api.Database.UpdateWorker(worker)
|
||||
|
||||
if ok {
|
||||
r.OkJson(UpdateWorkerResponse{
|
||||
Ok: true,
|
||||
})
|
||||
} else {
|
||||
r.OkJson(UpdateWorkerResponse{
|
||||
Ok: false,
|
||||
Message: "Could not update worker",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (*storage.Worker, error) {
|
||||
|
||||
if request.Alias == "" {
|
||||
request.Alias = "default_alias"
|
||||
}
|
||||
|
||||
worker := storage.Worker{
|
||||
Id: uuid.New(),
|
||||
Created: time.Now().Unix(),
|
||||
Identity: identity,
|
||||
Secret: makeSecret(),
|
||||
Alias: request.Alias,
|
||||
}
|
||||
|
||||
api.Database.SaveWorker(&worker)
|
||||
return worker.Id, nil
|
||||
return &worker, nil
|
||||
}
|
||||
|
||||
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
|
||||
|
||||
if cwr.Alias == "unassigned" {
|
||||
//Reserved alias
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func makeSecret() []byte {
|
||||
|
||||
secret := make([]byte, 32)
|
||||
for i := 0; i < 32; i++ {
|
||||
secret[i] = byte(rand.Int31())
|
||||
}
|
||||
|
||||
return secret
|
||||
}
|
||||
|
||||
func getIdentity(r *Request) *storage.Identity {
|
||||
|
||||
identity := storage.Identity{
|
||||
|
||||
Reference in New Issue
Block a user