From 2d817318f21fdec2b091ac0acfdc46b746e204cb Mon Sep 17 00:00:00 2001 From: Jonas Plum Date: Sun, 23 Jan 2022 04:27:31 +0100 Subject: [PATCH] Better file api (#30) * Better file api --- backup.go | 2 +- database/busdb/busdb.go | 1 + database/ticket_field.go | 6 +-- definition/tickets.yaml | 33 -------------- file.go | 92 +++++++++++++++++++++++++++++++++++-- generated/api/api.go | 22 --------- generated/api/test_api.go | 9 ---- generated/catalyst.json | 95 --------------------------------------- generated/catalyst.yml | 71 ----------------------------- generated/community.json | 95 --------------------------------------- generated/community.yml | 71 ----------------------------- restore.go | 2 +- server.go | 15 ++++--- service/ticket.go | 5 --- test/server_test.go | 2 +- ui/src/client/api.ts | 76 ------------------------------- ui/src/views/Ticket.vue | 29 +----------- 17 files changed, 105 insertions(+), 521 deletions(-) diff --git a/backup.go b/backup.go index 0c5128f..abc563e 100644 --- a/backup.go +++ b/backup.go @@ -18,7 +18,7 @@ import ( "github.com/SecurityBrewery/catalyst/storage" ) -func BackupHandler(catalystStorage *storage.Storage, c *database.Config) http.HandlerFunc { +func backupHandler(catalystStorage *storage.Storage, c *database.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Disposition", "attachment; filename=backup.zip") w.Header().Set("Content-Type", "application/zip") diff --git a/database/busdb/busdb.go b/database/busdb/busdb.go index 9b384f0..bf5d1f0 100644 --- a/database/busdb/busdb.go +++ b/database/busdb/busdb.go @@ -190,6 +190,7 @@ func toHTTPErr(err error) error { if errors.As(err, &ae) { return &api.HTTPError{Status: ae.Code, Internal: err} } + return err } return nil } diff --git a/database/ticket_field.go b/database/ticket_field.go index c0f46cb..c641028 100644 --- a/database/ticket_field.go +++ b/database/ticket_field.go @@ -166,7 +166,7 @@ func (db *Database) SetReferences(ctx context.Context, id int64, references []*m }) } -func (db *Database) LinkFiles(ctx context.Context, id int64, files []*model.File) (*model.TicketWithTickets, error) { +func (db *Database) AddFile(ctx context.Context, id int64, file *model.File) (*model.TicketWithTickets, error) { ticketFilterQuery, ticketFilterVars, err := db.Hooks.TicketWriteFilter(ctx) if err != nil { return nil, err @@ -174,9 +174,9 @@ func (db *Database) LinkFiles(ctx context.Context, id int64, files []*model.File query := `LET d = DOCUMENT(@@collection, @ID) ` + ticketFilterQuery + ` - UPDATE d WITH { "modified": @now, "files": @files } IN @@collection + UPDATE d WITH { "modified": @now, "files": APPEND(NOT_NULL(d.files, []), [@file]) } IN @@collection RETURN NEW` - return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"files": files, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ + return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"file": file, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ Type: bus.DatabaseEntryUpdated, Ids: []driver.DocumentID{ driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), diff --git a/definition/tickets.yaml b/definition/tickets.yaml index d79d937..26ea3ad 100644 --- a/definition/tickets.yaml +++ b/definition/tickets.yaml @@ -386,39 +386,6 @@ paths: - { id: 8126, created: "2021-10-02T16:04:59.078186Z", modified: "2021-10-02T16:04:59.078186Z", name: "Surfaceintroduce virus detected", owner: "demo", references: [ { href: "http://www.centralworld-class.io/synthesize", name: "university" },{ href: "https://www.futurevirtual.org/supply-chains/markets/sticky/iterate", name: "goal" },{ href: "http://www.chiefsyndicate.io/action-items", name: "unemployment" } ],"schema": "{}", status: "closed", type: "alert" } security: [ { roles: [ "ticket:write" ] } ] - /tickets/{id}/files: - put: - tags: [ "tickets" ] - summary: "Link files to an ticket" - description: "Link files to an ticket. The files themself will be stored in object storage." - operationId: "linkFiles" - parameters: - - { name: "id", in: "path", description: "Ticket ID", required: true, type: integer, format: "int64", x-example: 8125 } - - { name: "files", in: "body", description: "Added files", required: true, schema: { type: array, items: { $ref: "#/definitions/File" } }, x-example: [ { key: myfile, name: "document.doc" } ] } - responses: - "200": - description: "successful operation" - schema: { $ref: "#/definitions/TicketWithTickets" } - examples: - test: - id: 8125 - created: "2021-10-02T16:04:59.078186Z" - modified: "2021-12-12T12:12:12.000000012Z" - name: "phishing from selenafadel@von.com detected" - owner: "demo" - references: - - { href: "https://www.seniorleading-edge.name/users/efficient", name: "recovery" } - - { href: "http://www.dynamicseamless.com/clicks-and-mortar", name: "force" } - - { href: "http://www.leadscalable.biz/envisioneer", name: "fund" } - "schema": "{}" - status: "closed" - type: "alert" - files: [ { key: myfile, name: "document.doc" } ] - tickets: - - { id: 8126, created: "2021-10-02T16:04:59.078186Z", modified: "2021-10-02T16:04:59.078186Z", name: "Surfaceintroduce virus detected", owner: "demo", references: [ { href: "http://www.centralworld-class.io/synthesize", name: "university" },{ href: "https://www.futurevirtual.org/supply-chains/markets/sticky/iterate", name: "goal" },{ href: "http://www.chiefsyndicate.io/action-items", name: "unemployment" } ],"schema": "{}", status: "closed", type: "alert" } - - security: [ { roles: [ "ticket:write" ] } ] - /tickets/{id}/playbooks: post: tags: [ "tickets" ] diff --git a/file.go b/file.go index ad7d5cf..eed29d3 100644 --- a/file.go +++ b/file.go @@ -1,11 +1,15 @@ package catalyst import ( + "context" "errors" "fmt" "io" + "log" "net/http" + "strconv" + "github.com/arangodb/go-driver" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" @@ -13,11 +17,15 @@ import ( tusd "github.com/tus/tusd/pkg/handler" "github.com/tus/tusd/pkg/s3store" + "github.com/SecurityBrewery/catalyst/bus" + "github.com/SecurityBrewery/catalyst/database" + "github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/generated/api" + "github.com/SecurityBrewery/catalyst/generated/model" "github.com/SecurityBrewery/catalyst/storage" ) -func upload(client *s3.S3, external string) http.HandlerFunc { +func tusdUpload(db *database.Database, bus *bus.Bus, client *s3.S3, external string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ticketID := chi.URLParam(r, "ticketID") if ticketID == "" { @@ -36,14 +44,44 @@ func upload(client *s3.S3, external string) http.HandlerFunc { store.UseIn(composer) handler, err := tusd.NewUnroutedHandler(tusd.Config{ - BasePath: external + "/api/files/" + ticketID + "/upload/", - StoreComposer: composer, + BasePath: external + "/api/files/" + ticketID + "/tusd/", + StoreComposer: composer, + NotifyCompleteUploads: true, }) if err != nil { api.JSONErrorStatus(w, http.StatusBadRequest, fmt.Errorf("could not create tusd handler: %w", err)) return } + userID := "unknown" + user, ok := busdb.UserFromContext(r.Context()) + if ok { + userID = user.ID + } + + go func() { + event := <-handler.CompleteUploads + + id, err := strconv.ParseInt(ticketID, 10, 64) + if err != nil { + return + } + + file := &model.File{Key: event.Upload.Storage["Key"], Name: event.Upload.MetaData["filename"]} + + ctx := context.Background() + doc, err := db.AddFile(ctx, id, file) + if err != nil { + log.Println(err) + return + } + + err = bus.PublishRequest(userID, "LinkFiles", []driver.DocumentID{driver.DocumentID(fmt.Sprintf("tickets/%d", doc.ID))}) + if err != nil { + log.Println(err) + } + }() + switch r.Method { case http.MethodHead: handler.HeadFile(w, r) @@ -54,6 +92,54 @@ func upload(client *s3.S3, external string) http.HandlerFunc { default: api.JSONErrorStatus(w, http.StatusInternalServerError, errors.New("unknown method")) } + + } +} + +func upload(db *database.Database, client *s3.S3, uploader *s3manager.Uploader) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ticketID := chi.URLParam(r, "ticketID") + if ticketID == "" { + api.JSONErrorStatus(w, http.StatusBadRequest, errors.New("ticketID not given")) + return + } + + file, header, err := r.FormFile("file") + if err != nil { + api.JSONErrorStatus(w, http.StatusBadRequest, err) + return + } + defer file.Close() + + if err := storage.CreateBucket(client, ticketID); err != nil { + api.JSONErrorStatus(w, http.StatusBadRequest, fmt.Errorf("could not create bucket: %w", err)) + return + } + + _, err = uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String("catalyst-" + ticketID), + Key: aws.String(header.Filename), + Body: file, + }) + if err != nil { + api.JSONErrorStatus(w, http.StatusBadRequest, err) + return + } + + id, err := strconv.ParseInt(ticketID, 10, 64) + if err != nil { + api.JSONErrorStatus(w, http.StatusBadRequest, err) + return + } + + _, err = db.AddFile(r.Context(), id, &model.File{ + Key: header.Filename, + Name: header.Filename, + }) + if err != nil { + api.JSONErrorStatus(w, http.StatusBadRequest, err) + return + } } } diff --git a/generated/api/api.go b/generated/api/api.go index 36f502a..98f17e0 100755 --- a/generated/api/api.go +++ b/generated/api/api.go @@ -68,7 +68,6 @@ type Service interface { RunArtifact(context.Context, int64, string, string) error AddComment(context.Context, int64, *model.CommentForm) (*model.TicketWithTickets, error) RemoveComment(context.Context, int64, int) (*model.TicketWithTickets, error) - LinkFiles(context.Context, int64, []*model.File) (*model.TicketWithTickets, error) AddTicketPlaybook(context.Context, int64, *model.PlaybookTemplateForm) (*model.TicketWithTickets, error) RemoveTicketPlaybook(context.Context, int64, string) (*model.TicketWithTickets, error) SetTask(context.Context, int64, string, string, *model.Task) (*model.TicketWithTickets, error) @@ -139,7 +138,6 @@ func NewServer(service Service, roleAuth func([]string) func(http.Handler) http. r.With(roleAuth([]string{"ticket:write"})).Post("/tickets/{id}/artifacts/{name}/run/{automation}", s.runArtifactHandler) r.With(roleAuth([]string{"ticket:write"})).Post("/tickets/{id}/comments", s.addCommentHandler) r.With(roleAuth([]string{"ticket:write"})).Delete("/tickets/{id}/comments/{commentID}", s.removeCommentHandler) - r.With(roleAuth([]string{"ticket:write"})).Put("/tickets/{id}/files", s.linkFilesHandler) r.With(roleAuth([]string{"ticket:write"})).Post("/tickets/{id}/playbooks", s.addTicketPlaybookHandler) r.With(roleAuth([]string{"ticket:write"})).Delete("/tickets/{id}/playbooks/{playbookID}", s.removeTicketPlaybookHandler) r.With(roleAuth([]string{"ticket:write"})).Put("/tickets/{id}/playbooks/{playbookID}/task/{taskID}", s.setTaskHandler) @@ -642,26 +640,6 @@ func (s *server) removeCommentHandler(w http.ResponseWriter, r *http.Request) { response(w, result, err) } -func (s *server) linkFilesHandler(w http.ResponseWriter, r *http.Request) { - idP, err := parseURLInt64(r, "id") - if err != nil { - JSONError(w, err) - return - } - - // jl, _ := gojsonschema.NewReaderLoader(r.Body) - // []*model.FileSchema.Validate(jl) - - var filesP []*model.File - if err := parseBody(r, &filesP); err != nil { - JSONError(w, err) - return - } - - result, err := s.service.LinkFiles(r.Context(), idP, filesP) - response(w, result, err) -} - func (s *server) addTicketPlaybookHandler(w http.ResponseWriter, r *http.Request) { idP, err := parseURLInt64(r, "id") if err != nil { diff --git a/generated/api/test_api.go b/generated/api/test_api.go index f650bed..64fb1cc 100755 --- a/generated/api/test_api.go +++ b/generated/api/test_api.go @@ -378,15 +378,6 @@ var Tests = []struct { }, }, - { - Name: "LinkFiles", - Args: Args{Method: "Put", URL: "/tickets/8125/files", Data: []interface{}{map[string]interface{}{"key": "myfile", "name": "document.doc"}}}, - Want: Want{ - Status: 200, - Body: map[string]interface{}{"created": time.Date(2021, time.October, 2, 16, 4, 59, 78186000, time.UTC), "files": []interface{}{map[string]interface{}{"key": "myfile", "name": "document.doc"}}, "id": 8125, "modified": time.Date(2021, time.December, 12, 12, 12, 12, 12, time.UTC), "name": "phishing from selenafadel@von.com detected", "owner": "demo", "references": []interface{}{map[string]interface{}{"href": "https://www.seniorleading-edge.name/users/efficient", "name": "recovery"}, map[string]interface{}{"href": "http://www.dynamicseamless.com/clicks-and-mortar", "name": "force"}, map[string]interface{}{"href": "http://www.leadscalable.biz/envisioneer", "name": "fund"}}, "schema": "{}", "status": "closed", "tickets": []interface{}{map[string]interface{}{"created": time.Date(2021, time.October, 2, 16, 4, 59, 78186000, time.UTC), "id": 8126, "modified": time.Date(2021, time.October, 2, 16, 4, 59, 78186000, time.UTC), "name": "Surfaceintroduce virus detected", "owner": "demo", "references": []interface{}{map[string]interface{}{"href": "http://www.centralworld-class.io/synthesize", "name": "university"}, map[string]interface{}{"href": "https://www.futurevirtual.org/supply-chains/markets/sticky/iterate", "name": "goal"}, map[string]interface{}{"href": "http://www.chiefsyndicate.io/action-items", "name": "unemployment"}}, "schema": "{}", "status": "closed", "type": "alert"}}, "type": "alert"}, - }, - }, - { Name: "AddTicketPlaybook", Args: Args{Method: "Post", URL: "/tickets/8125/playbooks", Data: map[string]interface{}{"yaml": "name: Simple\ntasks:\n input:\n name: Upload malware if possible\n type: input\n schema:\n title: Malware\n type: object\n properties:\n malware:\n type: string\n title: Select malware\n default: \"\"\n next:\n hash: \"malware != ''\"\n\n hash:\n name: Hash the malware\n type: automation\n automation: hash.sha1\n payload:\n default: \"playbook.tasks['input'].data['malware']\"\n next:\n escalate:\n\n escalate:\n name: Escalate to malware team\n type: task\n"}}, diff --git a/generated/catalyst.json b/generated/catalyst.json index 6f94a40..8a92c8c 100644 --- a/generated/catalyst.json +++ b/generated/catalyst.json @@ -3189,101 +3189,6 @@ "tags" : [ "tickets" ] } }, - "/tickets/{id}/files" : { - "put" : { - "description" : "Link files to an ticket. The files themself will be stored in object storage.", - "operationId" : "linkFiles", - "parameters" : [ { - "description" : "Ticket ID", - "example" : 8125, - "in" : "path", - "name" : "id", - "required" : true, - "schema" : { - "format" : "int64", - "type" : "integer" - } - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "items" : { - "$ref" : "#/components/schemas/File" - }, - "type" : "array" - } - } - }, - "description" : "Added files", - "required" : true - }, - "responses" : { - "200" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/TicketWithTickets" - } - }, - "test" : { - "example" : { - "created" : "2021-10-02T16:04:59.078+0000", - "files" : [ { - "key" : "myfile", - "name" : "document.doc" - } ], - "id" : 8125, - "modified" : "2021-12-12T12:12:12.000+0000", - "name" : "phishing from selenafadel@von.com detected", - "owner" : "demo", - "references" : [ { - "href" : "https://www.seniorleading-edge.name/users/efficient", - "name" : "recovery" - }, { - "href" : "http://www.dynamicseamless.com/clicks-and-mortar", - "name" : "force" - }, { - "href" : "http://www.leadscalable.biz/envisioneer", - "name" : "fund" - } ], - "schema" : "{}", - "status" : "closed", - "tickets" : [ { - "created" : "2021-10-02T16:04:59.078+0000", - "id" : 8126, - "modified" : "2021-10-02T16:04:59.078+0000", - "name" : "Surfaceintroduce virus detected", - "owner" : "demo", - "references" : [ { - "href" : "http://www.centralworld-class.io/synthesize", - "name" : "university" - }, { - "href" : "https://www.futurevirtual.org/supply-chains/markets/sticky/iterate", - "name" : "goal" - }, { - "href" : "http://www.chiefsyndicate.io/action-items", - "name" : "unemployment" - } ], - "schema" : "{}", - "status" : "closed", - "type" : "alert" - } ], - "type" : "alert" - } - } - }, - "description" : "successful operation" - } - }, - "security" : [ { - "roles" : [ "ticket:write" ] - } ], - "summary" : "Link files to an ticket", - "tags" : [ "tickets" ], - "x-codegen-request-body-name" : "files" - } - }, "/tickets/{id}/playbooks" : { "post" : { "operationId" : "addTicketPlaybook", diff --git a/generated/catalyst.yml b/generated/catalyst.yml index 9b07934..cff8bb6 100644 --- a/generated/catalyst.yml +++ b/generated/catalyst.yml @@ -5190,77 +5190,6 @@ paths: summary: Remove an comment from an ticket tags: - tickets - /tickets/{id}/files: - put: - description: Link files to an ticket. The files themself will be stored in object - storage. - operationId: linkFiles - parameters: - - description: Ticket ID - format: int64 - in: path - name: id - required: true - type: integer - x-example: 8125 - - description: Added files - in: body - name: files - required: true - schema: - items: - $ref: '#/definitions/File' - type: array - x-example: - - key: myfile - name: document.doc - responses: - "200": - description: successful operation - examples: - test: - created: 2021-10-02T16:04:59.078186Z - files: - - key: myfile - name: document.doc - id: 8125 - modified: 2021-12-12T12:12:12.000000012Z - name: phishing from selenafadel@von.com detected - owner: demo - references: - - href: https://www.seniorleading-edge.name/users/efficient - name: recovery - - href: http://www.dynamicseamless.com/clicks-and-mortar - name: force - - href: http://www.leadscalable.biz/envisioneer - name: fund - schema: '{}' - status: closed - tickets: - - created: 2021-10-02T16:04:59.078186Z - id: 8126 - modified: 2021-10-02T16:04:59.078186Z - name: Surfaceintroduce virus detected - owner: demo - references: - - href: http://www.centralworld-class.io/synthesize - name: university - - href: https://www.futurevirtual.org/supply-chains/markets/sticky/iterate - name: goal - - href: http://www.chiefsyndicate.io/action-items - name: unemployment - schema: '{}' - status: closed - type: alert - type: alert - schema: - $ref: '#/definitions/TicketWithTickets' - security: - - roles: - - ticket:write - summary: Link files to an ticket - tags: - - tickets /tickets/{id}/playbooks: post: operationId: addTicketPlaybook diff --git a/generated/community.json b/generated/community.json index f39ce70..7929dc3 100644 --- a/generated/community.json +++ b/generated/community.json @@ -2759,101 +2759,6 @@ "tags" : [ "tickets" ] } }, - "/tickets/{id}/files" : { - "put" : { - "description" : "Link files to an ticket. The files themself will be stored in object storage.", - "operationId" : "linkFiles", - "parameters" : [ { - "description" : "Ticket ID", - "example" : 8125, - "in" : "path", - "name" : "id", - "required" : true, - "schema" : { - "format" : "int64", - "type" : "integer" - } - } ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "items" : { - "$ref" : "#/components/schemas/File" - }, - "type" : "array" - } - } - }, - "description" : "Added files", - "required" : true - }, - "responses" : { - "200" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/TicketWithTickets" - } - }, - "test" : { - "example" : { - "created" : "2021-10-02T16:04:59.078+0000", - "files" : [ { - "key" : "myfile", - "name" : "document.doc" - } ], - "id" : 8125, - "modified" : "2021-12-12T12:12:12.000+0000", - "name" : "phishing from selenafadel@von.com detected", - "owner" : "demo", - "references" : [ { - "href" : "https://www.seniorleading-edge.name/users/efficient", - "name" : "recovery" - }, { - "href" : "http://www.dynamicseamless.com/clicks-and-mortar", - "name" : "force" - }, { - "href" : "http://www.leadscalable.biz/envisioneer", - "name" : "fund" - } ], - "schema" : "{}", - "status" : "closed", - "tickets" : [ { - "created" : "2021-10-02T16:04:59.078+0000", - "id" : 8126, - "modified" : "2021-10-02T16:04:59.078+0000", - "name" : "Surfaceintroduce virus detected", - "owner" : "demo", - "references" : [ { - "href" : "http://www.centralworld-class.io/synthesize", - "name" : "university" - }, { - "href" : "https://www.futurevirtual.org/supply-chains/markets/sticky/iterate", - "name" : "goal" - }, { - "href" : "http://www.chiefsyndicate.io/action-items", - "name" : "unemployment" - } ], - "schema" : "{}", - "status" : "closed", - "type" : "alert" - } ], - "type" : "alert" - } - } - }, - "description" : "successful operation" - } - }, - "security" : [ { - "roles" : [ "ticket:write" ] - } ], - "summary" : "Link files to an ticket", - "tags" : [ "tickets" ], - "x-codegen-request-body-name" : "files" - } - }, "/tickets/{id}/playbooks" : { "post" : { "operationId" : "addTicketPlaybook", diff --git a/generated/community.yml b/generated/community.yml index 1835681..b90e6a2 100644 --- a/generated/community.yml +++ b/generated/community.yml @@ -4778,77 +4778,6 @@ paths: summary: Remove an comment from an ticket tags: - tickets - /tickets/{id}/files: - put: - description: Link files to an ticket. The files themself will be stored in object - storage. - operationId: linkFiles - parameters: - - description: Ticket ID - format: int64 - in: path - name: id - required: true - type: integer - x-example: 8125 - - description: Added files - in: body - name: files - required: true - schema: - items: - $ref: '#/definitions/File' - type: array - x-example: - - key: myfile - name: document.doc - responses: - "200": - description: successful operation - examples: - test: - created: 2021-10-02T16:04:59.078186Z - files: - - key: myfile - name: document.doc - id: 8125 - modified: 2021-12-12T12:12:12.000000012Z - name: phishing from selenafadel@von.com detected - owner: demo - references: - - href: https://www.seniorleading-edge.name/users/efficient - name: recovery - - href: http://www.dynamicseamless.com/clicks-and-mortar - name: force - - href: http://www.leadscalable.biz/envisioneer - name: fund - schema: '{}' - status: closed - tickets: - - created: 2021-10-02T16:04:59.078186Z - id: 8126 - modified: 2021-10-02T16:04:59.078186Z - name: Surfaceintroduce virus detected - owner: demo - references: - - href: http://www.centralworld-class.io/synthesize - name: university - - href: https://www.futurevirtual.org/supply-chains/markets/sticky/iterate - name: goal - - href: http://www.chiefsyndicate.io/action-items - name: unemployment - schema: '{}' - status: closed - type: alert - type: alert - schema: - $ref: '#/definitions/TicketWithTickets' - security: - - roles: - - ticket:write - summary: Link files to an ticket - tags: - - tickets /tickets/{id}/playbooks: post: operationId: addTicketPlaybook diff --git a/restore.go b/restore.go index 3535ae2..179d0f3 100644 --- a/restore.go +++ b/restore.go @@ -24,7 +24,7 @@ import ( "github.com/SecurityBrewery/catalyst/storage" ) -func RestoreHandler(catalystStorage *storage.Storage, db *database.Database, c *database.Config) http.HandlerFunc { +func restoreHandler(catalystStorage *storage.Storage, db *database.Database, c *database.Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { uf, header, err := r.FormFile("backup") if err != nil { diff --git a/server.go b/server.go index d96d6ef..559019b 100644 --- a/server.go +++ b/server.go @@ -122,13 +122,14 @@ func setupAPI(catalystService *service.Service, catalystStorage *storage.Storage allowAll, Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser(), ) - apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Head("/files/:ticketID/upload/:id", upload(catalystStorage.S3(), config.ExternalAddress)) - apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Patch("/files/:ticketID/upload/:id", upload(catalystStorage.S3(), config.ExternalAddress)) - apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Post("/files/:ticketID/upload", upload(catalystStorage.S3(), config.ExternalAddress)) - apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Get("/files/:ticketID/download/:key", download(catalystStorage.Downloader())) + apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Head("/files/{ticketID}/tusd/{id}", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress)) + apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Patch("/files/{ticketID}/tusd/{id}", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress)) + apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Post("/files/{ticketID}/tusd", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress)) + apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Post("/files/{ticketID}/upload", upload(catalystDatabase, catalystStorage.S3(), catalystStorage.Uploader())) + apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Get("/files/{ticketID}/download/{key}", download(catalystStorage.Downloader())) - apiServer.With(AuthorizeRole([]string{role.BackupRead.String()})).Get("/backup/create", BackupHandler(catalystStorage, dbConfig)) - apiServer.With(AuthorizeRole([]string{role.BackupRestore.String()})).Post("/backup/restore", RestoreHandler(catalystStorage, catalystDatabase, dbConfig)) + apiServer.With(AuthorizeRole([]string{role.BackupRead.String()})).Get("/backup/create", backupHandler(catalystStorage, dbConfig)) + apiServer.With(AuthorizeRole([]string{role.BackupRestore.String()})).Post("/backup/restore", restoreHandler(catalystStorage, catalystDatabase, dbConfig)) server := chi.NewRouter() server.Use(middleware.RequestID, middleware.RealIP, middleware.Logger, middleware.Recoverer, allowAll) @@ -138,7 +139,7 @@ func setupAPI(catalystService *service.Service, catalystStorage *storage.Storage server.With(Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser()).Handle("/wss", handleWebSocket(bus)) // server.With(Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser()).NotFound(static) server.NotFound(func(w http.ResponseWriter, r *http.Request) { - log.Println("not found") + log.Println("not found", r.URL.RawPath) Authenticate(catalystDatabase, config.Auth)(AuthorizeBlockedUser()(http.HandlerFunc(static))).ServeHTTP(w, r) }) diff --git a/service/ticket.go b/service/ticket.go index 1bc06c5..12152d2 100644 --- a/service/ticket.go +++ b/service/ticket.go @@ -130,11 +130,6 @@ func (s *Service) RemoveComment(ctx context.Context, i int64, i2 int) (doc *mode return s.database.RemoveComment(ctx, i, int64(i2)) } -func (s *Service) LinkFiles(ctx context.Context, i int64, files []*model.File) (doc *model.TicketWithTickets, err error) { - defer s.publishRequest(ctx, err, "LinkFiles", ticketWithTicketsID(doc)) - return s.database.LinkFiles(ctx, i, files) -} - func (s *Service) AddTicketPlaybook(ctx context.Context, i int64, form *model.PlaybookTemplateForm) (doc *model.TicketWithTickets, err error) { defer s.publishRequest(ctx, err, "AddTicketPlaybook", ticketWithTicketsID(doc)) return s.database.AddTicketPlaybook(ctx, i, form) diff --git a/test/server_test.go b/test/server_test.go index c1f05ec..bdbee81 100644 --- a/test/server_test.go +++ b/test/server_test.go @@ -287,7 +287,7 @@ func createFile(ctx context.Context, server *catalyst.Server) { log.Fatal(err) } - if _, err := server.DB.LinkFiles(ctx, 8125, []*model.File{{Key: "test.txt", Name: "test.txt"}}); err != nil { + if _, err := server.DB.AddFile(ctx, 8125, &model.File{Key: "test.txt", Name: "test.txt"}); err != nil { log.Fatal(err) } } diff --git a/ui/src/client/api.ts b/ui/src/client/api.ts index 2bcb1f7..095375d 100644 --- a/ui/src/client/api.ts +++ b/ui/src/client/api.ts @@ -5422,46 +5422,6 @@ export const TicketsApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, - /** - * Link files to an ticket. The files themself will be stored in object storage. - * @summary Link files to an ticket - * @param {number} id Ticket ID - * @param {Array} files Added files - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - linkFiles: async (id: number, files: Array, options: any = {}): Promise => { - // verify required parameter 'id' is not null or undefined - assertParamExists('linkFiles', 'id', id) - // verify required parameter 'files' is not null or undefined - assertParamExists('linkFiles', 'files', files) - const localVarPath = `/tickets/{id}/files` - .replace(`{${"id"}}`, encodeURIComponent(String(id))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(files, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @summary Link an ticket to an ticket @@ -6141,18 +6101,6 @@ export const TicketsApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getTicket(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * Link files to an ticket. The files themself will be stored in object storage. - * @summary Link files to an ticket - * @param {number} id Ticket ID - * @param {Array} files Added files - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async linkFiles(id: number, files: Array, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.linkFiles(id, files, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @summary Link an ticket to an ticket @@ -6437,17 +6385,6 @@ export const TicketsApiFactory = function (configuration?: Configuration, basePa getTicket(id: number, options?: any): AxiosPromise { return localVarFp.getTicket(id, options).then((request) => request(axios, basePath)); }, - /** - * Link files to an ticket. The files themself will be stored in object storage. - * @summary Link files to an ticket - * @param {number} id Ticket ID - * @param {Array} files Added files - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - linkFiles(id: number, files: Array, options?: any): AxiosPromise { - return localVarFp.linkFiles(id, files, options).then((request) => request(axios, basePath)); - }, /** * * @summary Link an ticket to an ticket @@ -6739,19 +6676,6 @@ export class TicketsApi extends BaseAPI { return TicketsApiFp(this.configuration).getTicket(id, options).then((request) => request(this.axios, this.basePath)); } - /** - * Link files to an ticket. The files themself will be stored in object storage. - * @summary Link files to an ticket - * @param {number} id Ticket ID - * @param {Array} files Added files - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TicketsApi - */ - public linkFiles(id: number, files: Array, options?: any) { - return TicketsApiFp(this.configuration).linkFiles(id, files, options).then((request) => request(this.axios, this.basePath)); - } - /** * * @summary Link an ticket to an ticket diff --git a/ui/src/views/Ticket.vue b/ui/src/views/Ticket.vue index c50dcad..3b571d8 100644 --- a/ui/src/views/Ticket.vue +++ b/ui/src/views/Ticket.vue @@ -1425,34 +1425,7 @@ export default Vue.extend({ setupUppy: function(id: number) { let uppy = new Uppy(); uppy.use(Tus, { - endpoint: location.protocol + '//' + location.hostname + ':'+ location.port + "/api/files/" + id.toString() + "/upload" - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - uppy.on("upload-success", (file: UppyFile, response: any) => { - if (this.ticket !== undefined) { - let files: Array = []; - if (this.ticket.files) { - files = this.ticket.files; - } - - let regex = /\/([^/]*)\+[^/]*$/; - let matches = response.uploadURL.match(regex); - if (matches.length != 2) { - return; - } - - console.log(matches[1]); - files.push({ name: file.name, key: matches[1] }); - API.linkFiles(id, files) - .then(response => { - this.$store.dispatch("alertSuccess", { name: "File added" }); - this.setTicket(response.data); - }) - .catch(error => { - this.$store.dispatch("alertError", { name: "File not added", detail: error }); - }); - } + endpoint: location.protocol + '//' + location.hostname + ':'+ location.port + "/api/files/" + id.toString() + "/tusd" }); return uppy; },