Improve bus (#3)

* Improve bus
* Add ticket log
This commit is contained in:
Jonas Plum
2021-12-27 19:08:07 +01:00
committed by GitHub
parent 1fade14ba5
commit b5dd0cfacd
50 changed files with 756 additions and 456 deletions

View File

@@ -1,26 +0,0 @@
package automation
import (
"context"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/role"
)
func New(apiurl, apikey string, bus *bus.Bus, db *database.Database) error {
if err := jobAutomation(jobContext(), apiurl, apikey, bus, db); err != nil {
log.Fatal(err)
}
return resultAutomation(bus, db)
}
func jobContext() context.Context {
// TODO: change roles?
bot := &models.UserResponse{ID: "bot", Roles: []string{role.Admin}}
return busdb.UserContext(context.Background(), bot)
}

View File

@@ -1,116 +0,0 @@
package automation
import (
"encoding/json"
"fmt"
"log"
"golang.org/x/net/context"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func jobAutomation(ctx context.Context, apiurl, apikey string, catalystBus *bus.Bus, db *database.Database) error {
return catalystBus.SubscribeJob(func(automationMsg *bus.JobMsg) {
job, err := db.JobCreate(ctx, automationMsg.ID, &models.JobForm{
Automation: automationMsg.Automation,
Payload: automationMsg.Message.Payload,
Origin: automationMsg.Origin,
})
if err != nil {
log.Println(err)
return
}
automation, err := db.AutomationGet(ctx, automationMsg.Automation)
if err != nil {
log.Println(err)
return
}
if automation.Script == "" {
log.Println("automation is empty")
return
}
if automationMsg.Message.Secrets == nil {
automationMsg.Message.Secrets = map[string]string{}
}
automationMsg.Message.Secrets["catalyst_apikey"] = apikey
automationMsg.Message.Secrets["catalyst_apiurl"] = apiurl
scriptMessage, _ := json.Marshal(automationMsg.Message)
containerID, logs, err := createContainer(ctx, automation.Image, automation.Script, string(scriptMessage))
if err != nil {
log.Println(err)
return
}
if _, err := db.JobUpdate(ctx, automationMsg.ID, &models.Job{
Automation: job.Automation,
Container: &containerID,
Origin: job.Origin,
Output: job.Output,
Log: &logs,
Payload: job.Payload,
Status: job.Status,
}); err != nil {
log.Println(err)
return
}
var result map[string]interface{}
stdout, _, err := runDocker(ctx, automationMsg.ID, containerID, db)
if err != nil {
result = map[string]interface{}{"error": fmt.Sprintf("error running script %s %s", err, string(stdout))}
} else {
var data map[string]interface{}
if err := json.Unmarshal(stdout, &data); err != nil {
result = map[string]interface{}{"error": string(stdout)}
} else {
result = data
}
}
if err := catalystBus.PublishResult(automationMsg.Automation, result, automationMsg.Origin); err != nil {
log.Println(err)
}
if err := db.JobComplete(ctx, automationMsg.ID, result); err != nil {
log.Println(err)
return
}
})
}
/*
func getAutomation(automationID string, config *Config) (*models.AutomationResponse, error) {
req, err := http.NewRequest(http.MethodGet, config.CatalystAPIUrl+"/automations/"+automationID, nil)
if err != nil {
return nil, err
}
req.Header.Set("PRIVATE-TOKEN", config.CatalystAPIKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var automation models.AutomationResponse
if err := json.Unmarshal(b, &automation); err != nil {
return nil, err
}
return &automation, nil
}
*/

View File

@@ -1,38 +0,0 @@
package automation
import (
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func resultAutomation(catalystBus *bus.Bus, db *database.Database) error {
return catalystBus.SubscribeResult(func(resultMsg *bus.ResultMsg) {
if resultMsg.Target != nil {
ctx := jobContext()
switch {
case resultMsg.Target.TaskOrigin != nil:
if _, err := db.TaskComplete(
ctx,
resultMsg.Target.TaskOrigin.TicketId,
resultMsg.Target.TaskOrigin.PlaybookId,
resultMsg.Target.TaskOrigin.TaskId,
resultMsg.Data,
); err != nil {
log.Println(err)
}
case resultMsg.Target.ArtifactOrigin != nil:
enrichment := &models.EnrichmentForm{
Data: resultMsg.Data,
Name: resultMsg.Automation,
}
_, err := db.EnrichArtifact(ctx, resultMsg.Target.ArtifactOrigin.TicketId, resultMsg.Target.ArtifactOrigin.Artifact, enrichment)
if err != nil {
log.Println(err)
}
}
}
})
}

View File

@@ -4,16 +4,7 @@ import (
"encoding/json"
"log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const (
channelUpdate = "data"
channelJob = "job"
channelResult = "result"
)
type Bus struct {
@@ -22,25 +13,13 @@ type Bus struct {
}
type Config struct {
Host string
Key string
resultBusKey string
jobBusKey string
dataBusKey string
APIUrl string
}
type JobMsg struct {
ID string `json:"id"`
Automation string `json:"automation"`
Origin *models.Origin `json:"origin"`
Message *models.Message `json:"message"`
}
type ResultMsg struct {
Automation string `json:"automation"`
Data map[string]interface{} `json:"data,omitempty"`
Target *models.Origin `json:"target"`
Host string
Key string
databaseUpdateBusKey string
jobBusKey string
resultBusKey string
requestKey string
APIUrl string
}
func New(c *Config) (*Bus, error) {
@@ -51,7 +30,7 @@ func New(c *Config) (*Bus, error) {
return nil, err
}
c.dataBusKey, err = client.GenerateKey(c.Key, channelUpdate+"/", "rwls", 0)
c.databaseUpdateBusKey, err = client.GenerateKey(c.Key, channelDatabaseUpdate+"/", "rwls", 0)
if err != nil {
return nil, err
}
@@ -63,30 +42,14 @@ func New(c *Config) (*Bus, error) {
if err != nil {
return nil, err
}
c.requestKey, err = client.GenerateKey(c.Key, ChannelRequest+"/", "rwls", 0)
if err != nil {
return nil, err
}
return &Bus{config: c, client: client}, err
}
func (b *Bus) PublishUpdate(ids []driver.DocumentID) error {
return b.jsonPublish(ids, channelUpdate, b.config.dataBusKey)
}
func (b *Bus) PublishJob(id, automation string, payload interface{}, context *models.Context, origin *models.Origin) error {
return b.jsonPublish(&JobMsg{
ID: id,
Automation: automation,
Origin: origin,
Message: &models.Message{
Context: context,
Payload: payload,
},
}, channelJob, b.config.jobBusKey)
}
func (b *Bus) PublishResult(automation string, data map[string]interface{}, target *models.Origin) error {
return b.jsonPublish(&ResultMsg{Automation: automation, Data: data, Target: target}, channelResult, b.config.resultBusKey)
}
func (b *Bus) jsonPublish(msg interface{}, channel, key string) error {
payload, err := json.Marshal(msg)
if err != nil {
@@ -96,39 +59,6 @@ func (b *Bus) jsonPublish(msg interface{}, channel, key string) error {
return b.client.Publish(key, channel, payload)
}
func (b *Bus) SubscribeUpdate(f func(ids []driver.DocumentID)) error {
return b.safeSubscribe(b.config.dataBusKey, channelUpdate, func(c *emitter.Client, m emitter.Message) {
var msg []driver.DocumentID
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(msg)
})
}
func (b *Bus) SubscribeJob(f func(msg *JobMsg)) error {
return b.safeSubscribe(b.config.jobBusKey, channelJob, func(c *emitter.Client, m emitter.Message) {
var msg JobMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
func (b *Bus) SubscribeResult(f func(msg *ResultMsg)) error {
return b.safeSubscribe(b.config.resultBusKey, channelResult, func(c *emitter.Client, m emitter.Message) {
var msg ResultMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
func (b *Bus) safeSubscribe(key, channel string, handler func(c *emitter.Client, m emitter.Message)) error {
defer func() {
if r := recover(); r != nil {

42
bus/databaseupdate.go Normal file
View File

@@ -0,0 +1,42 @@
package bus
import (
"encoding/json"
"log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2"
)
const channelDatabaseUpdate = "databaseupdate"
type DatabaseUpdateType string
const (
DatabaseEntryRead DatabaseUpdateType = "read"
DatabaseEntryCreated DatabaseUpdateType = "created"
DatabaseEntryUpdated DatabaseUpdateType = "updated"
)
type DatabaseUpdateMsg struct {
IDs []driver.DocumentID `json:"ids"`
Type DatabaseUpdateType `json:"type"`
}
func (b *Bus) PublishDatabaseUpdate(ids []driver.DocumentID, databaseUpdateType DatabaseUpdateType) error {
return b.jsonPublish(&DatabaseUpdateMsg{
IDs: ids,
Type: databaseUpdateType,
}, channelDatabaseUpdate, b.config.databaseUpdateBusKey)
}
func (b *Bus) SubscribeDatabaseUpdate(f func(msg *DatabaseUpdateMsg)) error {
return b.safeSubscribe(b.config.databaseUpdateBusKey, channelDatabaseUpdate, func(c *emitter.Client, m emitter.Message) {
var msg DatabaseUpdateMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}

42
bus/job.go Normal file
View File

@@ -0,0 +1,42 @@
package bus
import (
"encoding/json"
"log"
emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const channelJob = "job"
type JobMsg struct {
ID string `json:"id"`
Automation string `json:"automation"`
Origin *models.Origin `json:"origin"`
Message *models.Message `json:"message"`
}
func (b *Bus) PublishJob(id, automation string, payload interface{}, context *models.Context, origin *models.Origin) error {
return b.jsonPublish(&JobMsg{
ID: id,
Automation: automation,
Origin: origin,
Message: &models.Message{
Context: context,
Payload: payload,
},
}, channelJob, b.config.jobBusKey)
}
func (b *Bus) SubscribeJob(f func(msg *JobMsg)) error {
return b.safeSubscribe(b.config.jobBusKey, channelJob, func(c *emitter.Client, m emitter.Message) {
var msg JobMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}

36
bus/request.go Normal file
View File

@@ -0,0 +1,36 @@
package bus
import (
"encoding/json"
"log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2"
)
const ChannelRequest = "request"
type RequestMsg struct {
IDs []driver.DocumentID `json:"ids"`
Function string `json:"function"`
User string `json:"user"`
}
func (b *Bus) PublishRequest(user, f string, ids []driver.DocumentID) error {
return b.jsonPublish(&RequestMsg{
User: user,
Function: f,
IDs: ids,
}, ChannelRequest, b.config.requestKey)
}
func (b *Bus) SubscribeRequest(f func(msg *RequestMsg)) error {
return b.safeSubscribe(b.config.requestKey, ChannelRequest, func(c *emitter.Client, m emitter.Message) {
var msg RequestMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}

33
bus/result.go Normal file
View File

@@ -0,0 +1,33 @@
package bus
import (
"encoding/json"
"log"
emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const channelResult = "result"
type ResultMsg struct {
Automation string `json:"automation"`
Data map[string]interface{} `json:"data,omitempty"`
Target *models.Origin `json:"target"`
}
func (b *Bus) PublishResult(automation string, data map[string]interface{}, target *models.Origin) error {
return b.jsonPublish(&ResultMsg{Automation: automation, Data: data, Target: target}, channelResult, b.config.resultBusKey)
}
func (b *Bus) SubscribeResult(f func(msg *ResultMsg)) error {
return b.safeSubscribe(b.config.resultBusKey, channelResult, func(c *emitter.Client, m emitter.Message) {
var msg ResultMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}

60
busservice/busservice.go Normal file
View File

@@ -0,0 +1,60 @@
package busservice
import (
"context"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/role"
"github.com/SecurityBrewery/catalyst/time"
)
type busService struct {
db *database.Database
apiURL string
apiKey string
catalystBus *bus.Bus
}
func New(apiurl, apikey string, catalystBus *bus.Bus, db *database.Database) error {
h := &busService{db: db, apiURL: apiurl, apiKey: apikey, catalystBus: catalystBus}
if err := catalystBus.SubscribeRequest(h.logRequest); err != nil {
return err
}
if err := catalystBus.SubscribeResult(h.handleResult); err != nil {
return err
}
if err := catalystBus.SubscribeJob(h.handleJob); err != nil {
return err
}
return nil
}
func busContext() context.Context {
// TODO: change roles?
bot := &models.UserResponse{ID: "bot", Roles: []string{role.Admin}}
return busdb.UserContext(context.Background(), bot)
}
func (h *busService) logRequest(msg *bus.RequestMsg) {
var logEntries []*models.LogEntry
for _, i := range msg.IDs {
logEntries = append(logEntries, &models.LogEntry{
Type: bus.ChannelRequest,
Reference: i.String(),
Creator: msg.User,
Message: msg.Function,
Created: time.Now().UTC(),
})
}
if err := h.db.LogBatchCreate(busContext(), logEntries); err != nil {
log.Println(err)
}
}

View File

@@ -1,4 +1,4 @@
package automation
package busservice
import (
"archive/tar"

113
busservice/job.go Normal file
View File

@@ -0,0 +1,113 @@
package busservice
import (
"encoding/json"
"fmt"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func (h *busService) handleJob(automationMsg *bus.JobMsg) {
ctx := busContext()
job, err := h.db.JobCreate(ctx, automationMsg.ID, &models.JobForm{
Automation: automationMsg.Automation,
Payload: automationMsg.Message.Payload,
Origin: automationMsg.Origin,
})
if err != nil {
log.Println(err)
return
}
automation, err := h.db.AutomationGet(ctx, automationMsg.Automation)
if err != nil {
log.Println(err)
return
}
if automation.Script == "" {
log.Println("automation is empty")
return
}
if automationMsg.Message.Secrets == nil {
automationMsg.Message.Secrets = map[string]string{}
}
automationMsg.Message.Secrets["catalyst_apikey"] = h.apiKey
automationMsg.Message.Secrets["catalyst_apiurl"] = h.apiURL
scriptMessage, _ := json.Marshal(automationMsg.Message)
containerID, logs, err := createContainer(ctx, automation.Image, automation.Script, string(scriptMessage))
if err != nil {
log.Println(err)
return
}
if _, err := h.db.JobUpdate(ctx, automationMsg.ID, &models.Job{
Automation: job.Automation,
Container: &containerID,
Origin: job.Origin,
Output: job.Output,
Log: &logs,
Payload: job.Payload,
Status: job.Status,
}); err != nil {
log.Println(err)
return
}
var result map[string]interface{}
stdout, _, err := runDocker(ctx, automationMsg.ID, containerID, h.db)
if err != nil {
result = map[string]interface{}{"error": fmt.Sprintf("error running script %s %s", err, string(stdout))}
} else {
var data map[string]interface{}
if err := json.Unmarshal(stdout, &data); err != nil {
result = map[string]interface{}{"error": string(stdout)}
} else {
result = data
}
}
if err := h.catalystBus.PublishResult(automationMsg.Automation, result, automationMsg.Origin); err != nil {
log.Println(err)
}
if err := h.db.JobComplete(ctx, automationMsg.ID, result); err != nil {
log.Println(err)
return
}
}
/*
func getAutomation(automationID string, config *Config) (*models.AutomationResponse, error) {
req, err := http.NewRequest(http.MethodGet, config.CatalystAPIUrl+"/automations/"+automationID, nil)
if err != nil {
return nil, err
}
req.Header.Set("PRIVATE-TOKEN", config.CatalystAPIKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var automation models.AutomationResponse
if err := json.Unmarshal(b, &automation); err != nil {
return nil, err
}
return &automation, nil
}
*/

35
busservice/result.go Normal file
View File

@@ -0,0 +1,35 @@
package busservice
import (
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func (h *busService) handleResult(resultMsg *bus.ResultMsg) {
if resultMsg.Target != nil {
ctx := busContext()
switch {
case resultMsg.Target.TaskOrigin != nil:
if _, err := h.db.TaskComplete(
ctx,
resultMsg.Target.TaskOrigin.TicketId,
resultMsg.Target.TaskOrigin.PlaybookId,
resultMsg.Target.TaskOrigin.TaskId,
resultMsg.Data,
); err != nil {
log.Println(err)
}
case resultMsg.Target.ArtifactOrigin != nil:
enrichment := &models.EnrichmentForm{
Data: resultMsg.Data,
Name: resultMsg.Automation,
}
_, err := h.db.EnrichArtifact(ctx, resultMsg.Target.ArtifactOrigin.TicketId, resultMsg.Target.ArtifactOrigin.Artifact, enrichment)
if err != nil {
log.Println(err)
}
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/time"
@@ -60,11 +61,10 @@ func (db *Database) ArtifactUpdate(ctx context.Context, id int64, name string, a
"name": name,
"artifact": artifact,
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: fmt.Sprintf("Update artifact %s", name),
})
}
@@ -92,10 +92,9 @@ func (db *Database) EnrichArtifact(ctx context.Context, id int64, name string, e
"enrichmentname": enrichment.Name,
"enrichment": enrichment,
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: fmt.Sprintf("Run %s on artifact", enrichment.Name),
})
}

View File

@@ -33,22 +33,13 @@ func NewDatabase(ctx context.Context, internal driver.Database, b *bus.Bus) (*Bu
}, nil
}
type OperationType int
const (
Create OperationType = iota
Read = iota
Update = iota
)
type Operation struct {
OperationType OperationType
Ids []driver.DocumentID
Msg string
Type bus.DatabaseUpdateType
Ids []driver.DocumentID
}
var CreateOperation = &Operation{OperationType: Create}
var ReadOperation = &Operation{OperationType: Read}
var CreateOperation = &Operation{Type: bus.DatabaseEntryCreated}
var ReadOperation = &Operation{Type: bus.DatabaseEntryRead}
func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]interface{}, operation *Operation) (driver.Cursor, *models.LogEntry, error) {
cur, err := db.internal.Query(ctx, query, vars)
@@ -59,8 +50,8 @@ func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]i
var logs *models.LogEntry
switch {
case operation.OperationType == Update:
if err := db.LogAndNotify(ctx, operation.Ids, operation.Msg); err != nil {
case operation.Type == bus.DatabaseEntryCreated, operation.Type == bus.DatabaseEntryUpdated:
if err := db.bus.PublishDatabaseUpdate(operation.Ids, operation.Type); err != nil {
return nil, nil, err
}
}
@@ -68,19 +59,6 @@ func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]i
return cur, logs, err
}
func (db BusDatabase) LogAndNotify(ctx context.Context, ids []driver.DocumentID, msg string) error {
var logEntries []*models.LogEntry
for _, i := range ids {
logEntries = append(logEntries, &models.LogEntry{Reference: i.String(), Message: msg})
}
if err := db.LogBatchCreate(ctx, logEntries); err != nil {
return err
}
return db.bus.PublishUpdate(ids)
}
func (db BusDatabase) Remove(ctx context.Context) error {
return db.internal.Remove(ctx)
}
@@ -104,7 +82,7 @@ func (c Collection) CreateDocument(ctx, newctx context.Context, key string, docu
return meta, err
}
err = c.db.LogAndNotify(ctx, []driver.DocumentID{meta.ID}, "Document created")
err = c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryCreated)
if err != nil {
return meta, err
}
@@ -117,7 +95,7 @@ func (c Collection) CreateEdge(ctx, newctx context.Context, edge *driver.EdgeDoc
return meta, err
}
err = c.db.LogAndNotify(ctx, []driver.DocumentID{meta.ID}, "Document created")
err = c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryCreated)
if err != nil {
return meta, err
}
@@ -138,7 +116,7 @@ func (c Collection) CreateEdges(ctx context.Context, edges []*driver.EdgeDocumen
ids = append(ids, meta.ID)
}
err = c.db.LogAndNotify(ctx, ids, "Document created")
err = c.db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryCreated)
if err != nil {
return metas, err
}
@@ -160,7 +138,7 @@ func (c Collection) UpdateDocument(ctx context.Context, key string, update inter
return meta, err
}
return meta, c.db.bus.PublishUpdate([]driver.DocumentID{meta.ID})
return meta, c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryUpdated)
}
func (c Collection) ReplaceDocument(ctx context.Context, key string, document interface{}) (driver.DocumentMeta, error) {
@@ -169,7 +147,7 @@ func (c Collection) ReplaceDocument(ctx context.Context, key string, document in
return meta, err
}
return meta, c.db.bus.PublishUpdate([]driver.DocumentID{meta.ID})
return meta, c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryUpdated)
}
func (c Collection) RemoveDocument(ctx context.Context, formatInt string) (driver.DocumentMeta, error) {

View File

@@ -3,6 +3,8 @@ package busdb
import (
"context"
"errors"
"github.com/SecurityBrewery/catalyst/bus"
"strings"
"github.com/arangodb/go-driver"
@@ -12,15 +14,16 @@ import (
const LogCollectionName = "logs"
func (db *BusDatabase) LogCreate(ctx context.Context, reference, message string) (*models.LogEntry, error) {
func (db *BusDatabase) LogCreate(ctx context.Context, logType, reference, message string) (*models.LogEntry, error) {
user, ok := UserFromContext(ctx)
if !ok {
return nil, errors.New("no user in context")
}
logentry := &models.LogEntry{
Type: logType,
Reference: reference,
Created: time.Now(),
Created: time.Now().UTC(),
Creator: user.ID,
Message: message,
}
@@ -31,27 +34,18 @@ func (db *BusDatabase) LogCreate(ctx context.Context, reference, message string)
return nil, err
}
return &doc, db.bus.PublishUpdate([]driver.DocumentID{driver.DocumentID(logentry.Reference)})
return &doc, nil
}
func (db *BusDatabase) LogBatchCreate(ctx context.Context, logEntryForms []*models.LogEntry) error {
user, ok := UserFromContext(ctx)
if !ok {
return errors.New("no user in context")
}
func (db *BusDatabase) LogBatchCreate(ctx context.Context, logentries []*models.LogEntry) error {
var ids []driver.DocumentID
var logentries []*models.LogEntry
for _, logEntryForm := range logEntryForms {
logentry := &models.LogEntry{
Reference: logEntryForm.Reference,
Created: time.Now(),
Creator: user.ID,
Message: logEntryForm.Message,
for _, entry := range logentries {
if strings.HasPrefix(entry.Reference, "tickets/") {
ids = append(ids, driver.DocumentID(entry.Reference))
}
logentries = append(logentries, logentry)
ids = append(ids, driver.DocumentID(logentry.Reference))
}
if ids != nil {
go db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryCreated)
}
_, errs, err := db.logCollection.CreateDocuments(ctx, logentries)
@@ -63,7 +57,7 @@ func (db *BusDatabase) LogBatchCreate(ctx context.Context, logEntryForms []*mode
return err
}
return db.bus.PublishUpdate(ids)
return nil
}
func (db *BusDatabase) LogList(ctx context.Context, reference string) ([]*models.LogEntry, error) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/client"
"github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/caql"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
@@ -144,11 +145,10 @@ func (db *Database) JobLogAppend(ctx context.Context, id string, logLine string)
"ID": id,
"logline": logLine,
}, &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)),
},
Msg: fmt.Sprintf("Append logline"),
})
if err != nil {
return err
@@ -166,11 +166,10 @@ func (db *Database) JobComplete(ctx context.Context, id string, out interface{})
"ID": id,
"out": out,
}, &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)),
},
Msg: fmt.Sprintf("Set output"),
})
if err != nil {
return err

View File

@@ -7,6 +7,7 @@ import (
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb"
)
@@ -37,12 +38,11 @@ func (db *Database) RelatedRemove(ctx context.Context, id, id2 int64) error {
"id": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))),
"id2": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))),
}, &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))),
driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))),
},
Msg: "Removed ticket/artifact relation",
})
return err
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/arangodb/go-driver"
"github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/caql"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
@@ -133,7 +134,7 @@ func toTicketSimpleResponse(key string, ticket *models.Ticket) (*models.TicketSi
}, nil
}
func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*models.TicketSimpleResponse) *models.TicketWithTickets {
func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*models.TicketSimpleResponse, logs []*models.LogEntry) *models.TicketWithTickets {
return &models.TicketWithTickets{
Artifacts: ticketResponse.Artifacts,
Comments: ticketResponse.Comments,
@@ -152,6 +153,7 @@ func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*model
Type: ticketResponse.Type,
Write: ticketResponse.Write,
Logs: logs,
Tickets: tickets,
}
}
@@ -244,9 +246,8 @@ func (db *Database) TicketBatchCreate(ctx context.Context, ticketForms []*models
for _, apiTicket := range apiTickets {
ids = append(ids, driver.NewDocumentID(TicketCollectionName, fmt.Sprint(apiTicket.ID)))
}
if err := db.BusDatabase.LogAndNotify(ctx, ids, "Ticket created"); err != nil {
return nil, err
}
go db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryUpdated)
ticketResponses, err := toTicketResponses(apiTickets)
if err != nil {
@@ -405,7 +406,12 @@ func (db *Database) ticketGetQuery(ctx context.Context, ticketID int64, query st
return nil, err
}
return toTicketWithTickets(ticketResponse, tickets), nil
logs, err := db.LogList(ctx, fmt.Sprintf("%s/%d", TicketCollectionName, ticketID))
if err != nil {
return nil, err
}
return toTicketWithTickets(ticketResponse, tickets, logs), nil
}
func (db *Database) TicketUpdate(ctx context.Context, ticketID int64, ticket *models.Ticket) (*models.TicketWithTickets, error) {
@@ -420,10 +426,9 @@ func (db *Database) TicketUpdate(ctx context.Context, ticketID int64, ticket *mo
RETURN NEW`
ticket.Modified = time.Now().UTC() // TODO make setable?
return db.ticketGetQuery(ctx, ticketID, query, mergeMaps(map[string]interface{}{"ticket": ticket}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Ids: []driver.DocumentID{
Type: bus.DatabaseEntryUpdated, Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, strconv.FormatInt(ticketID, 10)),
},
Msg: "Ticket updated",
})
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/iancoleman/strcase"
"github.com/mingrammer/commonregex"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/pointer"
@@ -34,11 +35,10 @@ func (db *Database) AddArtifact(ctx context.Context, id int64, artifact *models.
UPDATE d WITH { "modified": @now, "artifacts": PUSH(NOT_NULL(d.artifacts, []), @artifact) } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"artifact": artifact, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Add artifact",
})
}
@@ -74,11 +74,10 @@ func (db *Database) RemoveArtifact(ctx context.Context, id int64, name string) (
UPDATE d WITH { "modified": @now, "artifacts": newartifacts } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"name": name, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Remove artifact",
})
}
@@ -93,11 +92,10 @@ func (db *Database) SetTemplate(ctx context.Context, id int64, schema string) (*
UPDATE d WITH { "schema": @schema } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"schema": schema}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Set Template",
})
}
@@ -125,11 +123,10 @@ func (db *Database) AddComment(ctx context.Context, id int64, comment *models.Co
UPDATE d WITH { "modified": @now, "comments": PUSH(NOT_NULL(d.comments, []), @comment) } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"comment": comment, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Add comment",
})
}
@@ -144,11 +141,10 @@ func (db *Database) RemoveComment(ctx context.Context, id int64, commentID int64
UPDATE d WITH { "modified": @now, "comments": REMOVE_NTH(d.comments, @commentID) } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"commentID": commentID, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Remove comment",
})
}
@@ -163,11 +159,10 @@ func (db *Database) SetReferences(ctx context.Context, id int64, references []*m
UPDATE d WITH { "modified": @now, "references": @references } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"references": references, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Changed references",
})
}
@@ -182,11 +177,10 @@ func (db *Database) LinkFiles(ctx context.Context, id int64, files []*models.Fil
UPDATE d WITH { "modified": @now, "files": @files } IN @@collection
RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"files": files, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
},
Msg: "Linked files",
})
}
@@ -224,11 +218,10 @@ func (db *Database) AddTicketPlaybook(ctx context.Context, id int64, playbookTem
"playbookID": findName(parentTicket.Playbooks, playbookID),
"now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
},
Msg: "Added playbook",
})
if err != nil {
return nil, err
@@ -284,10 +277,9 @@ func (db *Database) RemoveTicketPlaybook(ctx context.Context, id int64, playbook
"playbookID": playbookID,
"now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
},
Msg: fmt.Sprintf("Removed playbook %s", playbookID),
})
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/arangodb/go-driver"
"github.com/google/uuid"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/time"
@@ -74,11 +75,10 @@ func (db *Database) TaskComplete(ctx context.Context, id int64, playbookID strin
"closed": time.Now().UTC(),
"now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
},
Msg: fmt.Sprintf("Completed task %s in playbook %s", taskID, playbookID),
})
if err != nil {
return nil, err
@@ -136,11 +136,10 @@ func (db *Database) TaskUpdate(ctx context.Context, id int64, playbookID string,
"task": task,
"now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update,
Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
},
Msg: fmt.Sprintf("Saved task %s in playbook %s", taskID, playbookID),
})
if err != nil {
return nil, err

View File

@@ -16,14 +16,15 @@ paths:
schema: { type: array, items: { $ref: "#/definitions/LogEntry" } }
examples:
test:
- { "created": "2021-12-12T12:12:12.000000012Z","creator": "bob","reference": "tickets/294511","message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim." }
- { type: "manual", "created": "2021-12-12T12:12:12.000000012Z","creator": "bob","reference": "tickets/294511","message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim." }
security: [ { roles: [ "log:read" ] } ]
definitions:
LogEntry:
type: object
required: [ reference, creator, created, message ]
required: [ type, reference, creator, created, message ]
properties:
type: { type: string }
reference: { type: string }
creator: { type: string }
created: { type: string, format: "date-time" }

View File

@@ -1037,6 +1037,8 @@ definitions:
comments: { type: array, items: { $ref: '#/definitions/Comment' } }
artifacts: { type: array, items: { $ref: "#/definitions/Artifact" } }
logs: { type: array, items: { $ref: '#/definitions/LogEntry' } }
created: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" }
modified: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" }

View File

@@ -723,7 +723,8 @@
"created" : "2021-12-12T12:12:12.000+0000",
"creator" : "bob",
"message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference" : "tickets/294511"
"reference" : "tickets/294511",
"type" : "manual"
} ]
}
},
@@ -5418,9 +5419,12 @@
},
"reference" : {
"type" : "string"
},
"type" : {
"type" : "string"
}
},
"required" : [ "created", "creator", "message", "reference" ],
"required" : [ "created", "creator", "message", "reference", "type" ],
"type" : "object"
},
"Message" : {
@@ -6445,6 +6449,12 @@
"format" : "int64",
"type" : "integer"
},
"logs" : {
"items" : {
"$ref" : "#/components/schemas/LogEntry"
},
"type" : "array"
},
"modified" : {
"format" : "date-time",
"type" : "string"

View File

@@ -312,7 +312,10 @@ definitions:
type: string
reference:
type: string
type:
type: string
required:
- type
- reference
- creator
- created
@@ -1152,6 +1155,10 @@ definitions:
example: 123
format: int64
type: integer
logs:
items:
$ref: '#/definitions/LogEntry'
type: array
modified:
example: 1985-04-12T23:20:50.52Z
format: date-time
@@ -1945,6 +1952,7 @@ paths:
combine wish influence income guess run stand. Charge limit crime
ignore statement foundation study issue stop claim.
reference: tickets/294511
type: manual
schema:
items:
$ref: '#/definitions/LogEntry'

View File

@@ -491,7 +491,8 @@
"created" : "2021-12-12T12:12:12.000+0000",
"creator" : "bob",
"message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference" : "tickets/294511"
"reference" : "tickets/294511",
"type" : "manual"
} ]
}
},
@@ -4905,9 +4906,12 @@
},
"reference" : {
"type" : "string"
},
"type" : {
"type" : "string"
}
},
"required" : [ "created", "creator", "message", "reference" ],
"required" : [ "created", "creator", "message", "reference", "type" ],
"type" : "object"
},
"Message" : {
@@ -5866,6 +5870,12 @@
"format" : "int64",
"type" : "integer"
},
"logs" : {
"items" : {
"$ref" : "#/components/schemas/LogEntry"
},
"type" : "array"
},
"modified" : {
"format" : "date-time",
"type" : "string"

View File

@@ -247,7 +247,10 @@ definitions:
type: string
reference:
type: string
type:
type: string
required:
- type
- reference
- creator
- created
@@ -1033,6 +1036,10 @@ definitions:
example: 123
format: int64
type: integer
logs:
items:
$ref: '#/definitions/LogEntry'
type: array
modified:
example: 1985-04-12T23:20:50.52Z
format: date-time
@@ -1674,6 +1681,7 @@ paths:
combine wish influence income guess run stand. Charge limit crime
ignore statement foundation study issue stop claim.
reference: tickets/294511
type: manual
schema:
items:
$ref: '#/definitions/LogEntry'

View File

@@ -77,7 +77,7 @@ func init() {
gojsonschema.NewStringLoader(`{"type":"object","required":["automation","running","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"running":{"type":"boolean"},"status":{"type":"string"}},"$id":"#/definitions/Job"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["automation"],"x-embed":"","properties":{"automation":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"payload":{}},"$id":"#/definitions/JobForm"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","automation","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"id":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"status":{"type":"string"}},"$id":"#/definitions/JobResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["reference","creator","created","message"],"x-embed":"","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"},"reference":{"type":"string"}},"$id":"#/definitions/LogEntry"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["type","reference","creator","created","message"],"x-embed":"","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"},"reference":{"type":"string"},"type":{"type":"string"}},"$id":"#/definitions/LogEntry"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"context":{"$ref":"#/definitions/Context"},"payload":{"type":"object"},"secrets":{"type":"object","additionalProperties":{"type":"string"}}},"$id":"#/definitions/Message"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","blocked","roles"],"x-embed":"","properties":{"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"},"secret":{"type":"string"}},"$id":"#/definitions/NewUserResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"artifact_origin":{"$ref":"#/definitions/ArtifactOrigin"},"task_origin":{"$ref":"#/definitions/TaskOrigin"}},"$id":"#/definitions/Origin"}`),
@@ -105,7 +105,7 @@ func init() {
gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketType"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeForm"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","type","status","created","modified","schema"],"x-embed":"","properties":{"artifacts":{"items":{"$ref":"#/definitions/Artifact"},"type":"array"},"comments":{"items":{"$ref":"#/definitions/Comment"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"type":"object"},"files":{"items":{"$ref":"#/definitions/File"},"type":"array"},"id":{"format":"int64","type":"integer"},"modified":{"format":"date-time","type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"playbooks":{"type":"object","additionalProperties":{"$ref":"#/definitions/PlaybookResponse"}},"read":{"items":{"type":"string"},"type":"array"},"references":{"items":{"$ref":"#/definitions/Reference"},"type":"array"},"schema":{"type":"string"},"status":{"type":"string"},"tickets":{"items":{"$ref":"#/definitions/TicketSimpleResponse"},"type":"array"},"type":{"type":"string"},"write":{"items":{"type":"string"},"type":"array"}},"$id":"#/definitions/TicketWithTickets"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","type","status","created","modified","schema"],"x-embed":"","properties":{"artifacts":{"items":{"$ref":"#/definitions/Artifact"},"type":"array"},"comments":{"items":{"$ref":"#/definitions/Comment"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"type":"object"},"files":{"items":{"$ref":"#/definitions/File"},"type":"array"},"id":{"format":"int64","type":"integer"},"logs":{"items":{"$ref":"#/definitions/LogEntry"},"type":"array"},"modified":{"format":"date-time","type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"playbooks":{"type":"object","additionalProperties":{"$ref":"#/definitions/PlaybookResponse"}},"read":{"items":{"type":"string"},"type":"array"},"references":{"items":{"$ref":"#/definitions/Reference"},"type":"array"},"schema":{"type":"string"},"status":{"type":"string"},"tickets":{"items":{"$ref":"#/definitions/TicketSimpleResponse"},"type":"array"},"type":{"type":"string"},"write":{"items":{"type":"string"},"type":"array"}},"$id":"#/definitions/TicketWithTickets"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon"],"x-embed":"","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"$id":"#/definitions/Type"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["blocked","apikey","roles"],"x-embed":"","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"sha256":{"type":"string"}},"$id":"#/definitions/User"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"email":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"$id":"#/definitions/UserData"}`),
@@ -271,6 +271,7 @@ type LogEntry struct {
Creator string `json:"creator"`
Message string `json:"message"`
Reference string `json:"reference"`
Type string `json:"type"`
}
type Message struct {
@@ -530,6 +531,7 @@ type TicketWithTickets struct {
Details interface{} `json:"details,omitempty"`
Files []*File `json:"files,omitempty"`
ID int64 `json:"id"`
Logs []*LogEntry `json:"logs,omitempty"`
Modified time.Time `json:"modified"`
Name string `json:"name"`
Owner *string `json:"owner,omitempty"`

View File

@@ -640,7 +640,8 @@ func init() {
"created": "2021-12-12T12:12:12.000000012Z",
"creator": "bob",
"message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference": "tickets/294511"
"reference": "tickets/294511",
"type": "manual"
}
]
}
@@ -5680,6 +5681,7 @@ func init() {
"LogEntry": {
"type": "object",
"required": [
"type",
"reference",
"creator",
"created",
@@ -5698,6 +5700,9 @@ func init() {
},
"reference": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -6816,6 +6821,12 @@ func init() {
"format": "int64",
"example": 123
},
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/LogEntry"
}
},
"modified": {
"type": "string",
"format": "date-time",
@@ -7668,7 +7679,8 @@ func init() {
"created": "2021-12-12T12:12:12.000000012Z",
"creator": "bob",
"message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference": "tickets/294511"
"reference": "tickets/294511",
"type": "manual"
}
]
}
@@ -12708,6 +12720,7 @@ func init() {
"LogEntry": {
"type": "object",
"required": [
"type",
"reference",
"creator",
"created",
@@ -12726,6 +12739,9 @@ func init() {
},
"reference": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -13844,6 +13860,12 @@ func init() {
"format": "int64",
"example": 123
},
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/LogEntry"
}
},
"modified": {
"type": "string",
"format": "date-time",

View File

@@ -233,7 +233,7 @@ func TestService(t *testing.T) {
args: args{method: "GET", url: "/api/logs/tickets%252F294511"},
want: want{
status: 200,
body: []interface{}{map[string]interface{}{"created": "2021-12-12T12:12:12.000000012Z", "creator": "bob", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "reference": "tickets/294511"}},
body: []interface{}{map[string]interface{}{"created": "2021-12-12T12:12:12.000000012Z", "creator": "bob", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "reference": "tickets/294511", "type": "manual"}},
},
},
{

2
go.mod
View File

@@ -55,7 +55,7 @@ require (
go.etcd.io/bbolt v1.3.6 // indirect
go.mongodb.org/mongo-driver v1.7.4 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211105192438-b53810dc28af
golang.org/x/net v0.0.0-20211105192438-b53810dc28af // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect

View File

@@ -8,8 +8,8 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/SecurityBrewery/catalyst/automation"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/busservice"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
@@ -72,7 +72,7 @@ func New(hooks *hooks.Hooks, config *Config) (*Server, error) {
return nil, err
}
err = automation.New(config.Bus.APIUrl, config.InitialAPIKey, catalystBus, catalystDatabase)
err = busservice.New(config.Bus.APIUrl, config.InitialAPIKey, catalystBus, catalystDatabase)
if err != nil {
return nil, err
}

View File

@@ -10,25 +10,29 @@ import (
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets"
)
func (s *Service) RunArtifact(ctx context.Context, params *tickets.RunArtifactParams) *api.Response {
func (s *Service) RunArtifact(ctx context.Context, params *tickets.RunArtifactParams) (r *api.Response) {
artifact, err := s.database.ArtifactGet(ctx, params.ID, params.Name)
if err != nil {
return response(nil, err)
return s.response(ctx, "RunArtifact", ticketID(params.ID), nil, err)
}
jobID := uuid.NewString()
origin := &models.Origin{ArtifactOrigin: &models.ArtifactOrigin{TicketId: params.ID, Artifact: params.Name}}
return response(nil, s.bus.PublishJob(jobID, params.Automation, params.Name, &models.Context{Artifact: artifact}, origin))
err = s.bus.PublishJob(jobID, params.Automation, params.Name, &models.Context{Artifact: artifact}, origin)
return s.response(ctx, "RunArtifact", ticketID(params.ID), nil, err)
}
func (s *Service) EnrichArtifact(ctx context.Context, params *tickets.EnrichArtifactParams) *api.Response {
return response(s.database.EnrichArtifact(ctx, params.ID, params.Name, params.Data))
i, err := s.database.EnrichArtifact(ctx, params.ID, params.Name, params.Data)
return s.response(ctx, "EnrichArtifact", ticketID(params.ID), i, err)
}
func (s *Service) SetArtifact(ctx context.Context, params *tickets.SetArtifactParams) *api.Response {
return response(s.database.ArtifactUpdate(ctx, params.ID, params.Name, params.Artifact))
i, err := s.database.ArtifactUpdate(ctx, params.ID, params.Name, params.Artifact)
return s.response(ctx, "SetArtifact", ticketID(params.ID), i, err)
}
func (s *Service) GetArtifact(ctx context.Context, params *tickets.GetArtifactParams) *api.Response {
return response(s.database.ArtifactGet(ctx, params.ID, params.Name))
i, err := s.database.ArtifactGet(ctx, params.ID, params.Name)
return s.response(ctx, "GetArtifact", nil, i, err)
}

View File

@@ -2,27 +2,40 @@ package service
import (
"context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/automations"
)
func automationID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.AutomationCollectionName, id))}
}
func (s *Service) CreateAutomation(ctx context.Context, params *automations.CreateAutomationParams) *api.Response {
return response(s.database.AutomationCreate(ctx, params.Automation))
i, err := s.database.AutomationCreate(ctx, params.Automation)
return s.response(ctx, "CreateAutomation", automationID(i.ID), i, err)
}
func (s *Service) GetAutomation(ctx context.Context, params *automations.GetAutomationParams) *api.Response {
return response(s.database.AutomationGet(ctx, params.ID))
i, err := s.database.AutomationGet(ctx, params.ID)
return s.response(ctx, "GetAutomation", nil, i, err)
}
func (s *Service) UpdateAutomation(ctx context.Context, params *automations.UpdateAutomationParams) *api.Response {
return response(s.database.AutomationUpdate(ctx, params.ID, params.Automation))
i, err := s.database.AutomationUpdate(ctx, params.ID, params.Automation)
return s.response(ctx, "UpdateAutomation", automationID(i.ID), i, err)
}
func (s *Service) DeleteAutomation(ctx context.Context, params *automations.DeleteAutomationParams) *api.Response {
return response(nil, s.database.AutomationDelete(ctx, params.ID))
err := s.database.AutomationDelete(ctx, params.ID)
return s.response(ctx, "DeleteAutomation", automationID(params.ID), nil, err)
}
func (s *Service) ListAutomations(ctx context.Context) *api.Response {
return response(s.database.AutomationList(ctx))
i, err := s.database.AutomationList(ctx)
return s.response(ctx, "ListAutomations", nil, i, err)
}

View File

@@ -2,28 +2,38 @@ package service
import (
"context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/google/uuid"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/jobs"
)
func (s *Service) RunJob(_ context.Context, params *jobs.RunJobParams) *api.Response {
func jobID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.JobCollectionName, id))}
}
func (s *Service) RunJob(ctx context.Context, params *jobs.RunJobParams) *api.Response {
msgContext := &models.Context{}
jobID := uuid.NewString()
return response(nil, s.bus.PublishJob(jobID, params.Job.Automation, params.Job.Payload, msgContext, params.Job.Origin))
newJobID := uuid.NewString()
return s.response(ctx, "RunJob", jobID(newJobID), nil, s.bus.PublishJob(newJobID, params.Job.Automation, params.Job.Payload, msgContext, params.Job.Origin))
}
func (s *Service) GetJob(ctx context.Context, params *jobs.GetJobParams) *api.Response {
return response(s.database.JobGet(ctx, params.ID))
i, err := s.database.JobGet(ctx, params.ID)
return s.response(ctx, "GetJob", nil, i, err)
}
func (s *Service) ListJobs(ctx context.Context) *api.Response {
return response(s.database.JobList(ctx))
i, err := s.database.JobList(ctx)
return s.response(ctx, "ListJobs", nil, i, err)
}
func (s *Service) UpdateJob(ctx context.Context, params *jobs.UpdateJobParams) *api.Response {
return response(s.database.JobUpdate(ctx, params.ID, params.Job))
i, err := s.database.JobUpdate(ctx, params.ID, params.Job)
return s.response(ctx, "UpdateJob", jobID(i.ID), i, err)
}

View File

@@ -10,5 +10,6 @@ import (
func (s *Service) GetLogs(ctx context.Context, params *logs.GetLogsParams) *api.Response {
id, _ := url.QueryUnescape(params.Reference)
return response(s.database.LogList(ctx, id))
i, err := s.database.LogList(ctx, id)
return s.response(ctx, "GetLogs", nil, i, err)
}

View File

@@ -5,35 +5,46 @@ import (
"fmt"
"strings"
"github.com/arangodb/go-driver"
"github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/playbooks"
)
func playbookID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.PlaybookCollectionName, id))}
}
func (s *Service) CreatePlaybook(ctx context.Context, params *playbooks.CreatePlaybookParams) *api.Response {
return response(s.database.PlaybookCreate(ctx, params.Playbook))
i, err := s.database.PlaybookCreate(ctx, params.Playbook)
return s.response(ctx, "CreatePlaybook", playbookID(i.ID), i, err)
}
func (s *Service) GetPlaybook(ctx context.Context, params *playbooks.GetPlaybookParams) *api.Response {
return response(s.database.PlaybookGet(ctx, params.ID))
i, err := s.database.PlaybookGet(ctx, params.ID)
return s.response(ctx, "GetPlaybook", nil, i, err)
}
func (s *Service) UpdatePlaybook(ctx context.Context, params *playbooks.UpdatePlaybookParams) *api.Response {
if err := validate(params.Playbook, models.PlaybookTemplateFormSchema); err != nil {
return response(nil, err)
return s.response(ctx, "UpdatePlaybook", nil, nil, err)
}
return response(s.database.PlaybookUpdate(ctx, params.ID, params.Playbook))
i, err := s.database.PlaybookUpdate(ctx, params.ID, params.Playbook)
return s.response(ctx, "UpdatePlaybook", playbookID(i.ID), i, err)
}
func (s *Service) DeletePlaybook(ctx context.Context, params *playbooks.DeletePlaybookParams) *api.Response {
return response(nil, s.database.PlaybookDelete(ctx, params.ID))
err := s.database.PlaybookDelete(ctx, params.ID)
return s.response(ctx, "DeletePlaybook", playbookID(params.ID), nil, err)
}
func (s *Service) ListPlaybooks(ctx context.Context) *api.Response {
return response(s.database.PlaybookList(ctx))
i, err := s.database.PlaybookList(ctx)
return s.response(ctx, "ListPlaybooks", nil, i, err)
}
func validate(e interface{}, schema *gojsonschema.Schema) error {

View File

@@ -1,6 +1,7 @@
package service
import (
"context"
"errors"
"log"
"net/http"
@@ -10,6 +11,7 @@ import (
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/storage"
@@ -30,11 +32,22 @@ func (s *Service) Healthy() bool {
return true
}
func response(v interface{}, err error) *api.Response {
func (s *Service) response(ctx context.Context, function string, ids []driver.DocumentID, v interface{}, err error) *api.Response {
if err != nil {
log.Println(err)
return &api.Response{Code: httpStatus(err), Body: gin.H{"error": err.Error()}}
}
if ids != nil {
userID := "unknown"
user, ok := busdb.UserFromContext(ctx)
if ok {
userID = user.ID
}
go s.bus.PublishRequest(userID, function, ids)
}
if v == nil {
return &api.Response{Code: http.StatusNoContent, Body: v}
}

View File

@@ -7,5 +7,6 @@ import (
)
func (s *Service) GetStatistics(ctx context.Context) *api.Response {
return response(s.database.Statistics(ctx))
i, err := s.database.Statistics(ctx)
return s.response(ctx, "GetStatistics", nil, i, err)
}

View File

@@ -7,5 +7,6 @@ import (
)
func (s *Service) ListTasks(ctx context.Context) *api.Response {
return response(s.database.TaskList(ctx))
i, err := s.database.TaskList(ctx)
return s.response(ctx, "ListTasks", nil, i, err)
}

View File

@@ -2,27 +2,40 @@ package service
import (
"context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/templates"
)
func templateID(s string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.TemplateCollectionName, s))}
}
func (s *Service) CreateTemplate(ctx context.Context, params *templates.CreateTemplateParams) *api.Response {
return response(s.database.TemplateCreate(ctx, params.Template))
i, err := s.database.TemplateCreate(ctx, params.Template)
return s.response(ctx, "CreateTemplate", templateID(i.ID), i, err)
}
func (s *Service) GetTemplate(ctx context.Context, params *templates.GetTemplateParams) *api.Response {
return response(s.database.TemplateGet(ctx, params.ID))
i, err := s.database.TemplateGet(ctx, params.ID)
return s.response(ctx, "GetTemplate", nil, i, err)
}
func (s *Service) UpdateTemplate(ctx context.Context, params *templates.UpdateTemplateParams) *api.Response {
return response(s.database.TemplateUpdate(ctx, params.ID, params.Template))
i, err := s.database.TemplateUpdate(ctx, params.ID, params.Template)
return s.response(ctx, "UpdateTemplate", templateID(i.ID), i, err)
}
func (s *Service) DeleteTemplate(ctx context.Context, params *templates.DeleteTemplateParams) *api.Response {
return response(nil, s.database.TemplateDelete(ctx, params.ID))
err := s.database.TemplateDelete(ctx, params.ID)
return s.response(ctx, "DeleteTemplate", templateID(params.ID), nil, err)
}
func (s *Service) ListTemplates(ctx context.Context) *api.Response {
return response(s.database.TemplateList(ctx))
i, err := s.database.TemplateList(ctx)
return s.response(ctx, "ListTemplates", nil, i, err)
}

View File

@@ -4,39 +4,56 @@ import (
"context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets"
)
func ticketID(id int64) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%d", database.TicketCollectionName, id))}
}
func ticketIDs(ticketResponses []*models.TicketResponse) []driver.DocumentID {
var ids []driver.DocumentID
for _, ticketResponse := range ticketResponses {
ids = append(ids, ticketID(ticketResponse.ID)...)
}
return ids
}
func (s *Service) CreateTicket(ctx context.Context, params *tickets.CreateTicketParams) *api.Response {
createdTickets, err := s.database.TicketBatchCreate(ctx, []*models.TicketForm{params.Ticket})
if len(createdTickets) > 0 {
return response(createdTickets[0], err)
return s.response(ctx, "CreateTicket", ticketIDs(createdTickets), createdTickets[0], err)
}
return response(nil, err)
return s.response(ctx, "CreateTicket", ticketIDs(createdTickets), nil, err)
}
func (s *Service) CreateTicketBatch(ctx context.Context, params *tickets.CreateTicketBatchParams) *api.Response {
_, err := s.database.TicketBatchCreate(ctx, params.Ticket)
return response(nil, err)
ticketBatch, err := s.database.TicketBatchCreate(ctx, params.Ticket)
return s.response(ctx, "CreateTicketBatch", ticketIDs(ticketBatch), nil, err)
}
func (s *Service) GetTicket(ctx context.Context, params *tickets.GetTicketParams) *api.Response {
return response(s.database.TicketGet(ctx, params.ID))
ticket, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "GetTicket", nil, ticket, err)
}
func (s *Service) UpdateTicket(ctx context.Context, params *tickets.UpdateTicketParams) *api.Response {
return response(s.database.TicketUpdate(ctx, params.ID, params.Ticket))
ticket, err := s.database.TicketUpdate(ctx, params.ID, params.Ticket)
return s.response(ctx, "UpdateTicket", ticketID(ticket.ID), ticket, err)
}
func (s *Service) DeleteTicket(ctx context.Context, params *tickets.DeleteTicketParams) *api.Response {
if err := s.database.TicketDelete(ctx, params.ID); err != nil {
return response(nil, err)
return s.response(ctx, "DeleteTicket", ticketID(params.ID), nil, err)
}
_ = s.storage.DeleteBucket(fmt.Sprint(params.ID))
return response(nil, nil)
return s.response(ctx, "DeleteTicket", ticketID(params.ID), nil, nil)
}
func (s *Service) ListTickets(ctx context.Context, params *tickets.ListTicketsParams) *api.Response {
@@ -48,5 +65,7 @@ func (s *Service) ListTickets(ctx context.Context, params *tickets.ListTicketsPa
if params.Type != nil && *params.Type != "" {
t = *params.Type
}
return response(s.database.TicketList(ctx, t, q, params.Sort, params.Desc, *params.Offset, *params.Count))
ticketList, err := s.database.TicketList(ctx, t, q, params.Sort, params.Desc, *params.Offset, *params.Count)
return s.response(ctx, "ListTickets", nil, ticketList, err)
}

View File

@@ -8,67 +8,81 @@ import (
)
func (s *Service) AddArtifact(ctx context.Context, params *tickets.AddArtifactParams) *api.Response {
return response(s.database.AddArtifact(ctx, params.ID, params.Artifact))
i, err := s.database.AddArtifact(ctx, params.ID, params.Artifact)
return s.response(ctx, "AddArtifact", ticketID(params.ID), i, err)
}
func (s *Service) RemoveArtifact(ctx context.Context, params *tickets.RemoveArtifactParams) *api.Response {
return response(s.database.RemoveArtifact(ctx, params.ID, params.Name))
i, err := s.database.RemoveArtifact(ctx, params.ID, params.Name)
return s.response(ctx, "RemoveArtifact", ticketID(params.ID), i, err)
}
func (s *Service) SetSchema(ctx context.Context, params *tickets.SetSchemaParams) *api.Response {
return response(s.database.SetTemplate(ctx, params.ID, params.Schema))
i, err := s.database.SetTemplate(ctx, params.ID, params.Schema)
return s.response(ctx, "SetSchema", ticketID(params.ID), i, err)
}
func (s *Service) AddComment(ctx context.Context, params *tickets.AddCommentParams) *api.Response {
return response(s.database.AddComment(ctx, params.ID, params.Comment))
i, err := s.database.AddComment(ctx, params.ID, params.Comment)
return s.response(ctx, "AddComment", ticketID(params.ID), i, err)
}
func (s *Service) RemoveComment(ctx context.Context, params *tickets.RemoveCommentParams) *api.Response {
return response(s.database.RemoveComment(ctx, params.ID, params.CommentID))
i, err := s.database.RemoveComment(ctx, params.ID, params.CommentID)
return s.response(ctx, "RemoveComment", ticketID(params.ID), i, err)
}
func (s *Service) LinkTicket(ctx context.Context, params *tickets.LinkTicketParams) *api.Response {
err := s.database.RelatedCreate(ctx, params.ID, params.LinkedID)
if err != nil {
return response(nil, err)
return s.response(ctx, "LinkTicket", ticketID(params.ID), nil, err)
}
return s.GetTicket(ctx, &tickets.GetTicketParams{ID: params.ID})
i, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "LinkTicket", ticketID(params.ID), i, err)
}
func (s *Service) UnlinkTicket(ctx context.Context, params *tickets.UnlinkTicketParams) *api.Response {
err := s.database.RelatedRemove(ctx, params.ID, params.LinkedID)
if err != nil {
return response(nil, err)
return s.response(ctx, "UnlinkTicket", ticketID(params.ID), nil, err)
}
return s.GetTicket(ctx, &tickets.GetTicketParams{ID: params.ID})
i, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "UnlinkTicket", ticketID(params.ID), i, err)
}
func (s Service) SetReferences(ctx context.Context, params *tickets.SetReferencesParams) *api.Response {
return response(s.database.SetReferences(ctx, params.ID, params.References))
i, err := s.database.SetReferences(ctx, params.ID, params.References)
return s.response(ctx, "SetReferences", ticketID(params.ID), i, err)
}
func (s Service) LinkFiles(ctx context.Context, params *tickets.LinkFilesParams) *api.Response {
return response(s.database.LinkFiles(ctx, params.ID, params.Files))
i, err := s.database.LinkFiles(ctx, params.ID, params.Files)
return s.response(ctx, "LinkFiles", ticketID(params.ID), i, err)
}
func (s Service) AddTicketPlaybook(ctx context.Context, params *tickets.AddTicketPlaybookParams) *api.Response {
return response(s.database.AddTicketPlaybook(ctx, params.ID, params.Playbook))
i, err := s.database.AddTicketPlaybook(ctx, params.ID, params.Playbook)
return s.response(ctx, "AddTicketPlaybook", ticketID(params.ID), i, err)
}
func (s Service) RemoveTicketPlaybook(ctx context.Context, params *tickets.RemoveTicketPlaybookParams) *api.Response {
return response(s.database.RemoveTicketPlaybook(ctx, params.ID, params.PlaybookID))
i, err := s.database.RemoveTicketPlaybook(ctx, params.ID, params.PlaybookID)
return s.response(ctx, "RemoveTicketPlaybook", ticketID(params.ID), i, err)
}
func (s Service) CompleteTask(ctx context.Context, params *tickets.CompleteTaskParams) *api.Response {
return response(s.database.TaskComplete(ctx, params.ID, params.PlaybookID, params.TaskID, params.Data))
i, err := s.database.TaskComplete(ctx, params.ID, params.PlaybookID, params.TaskID, params.Data)
return s.response(ctx, "CompleteTask", ticketID(params.ID), i, err)
}
func (s Service) SetTask(ctx context.Context, params *tickets.SetTaskParams) *api.Response {
return response(s.database.TaskUpdate(ctx, params.ID, params.PlaybookID, params.TaskID, params.Task))
i, err := s.database.TaskUpdate(ctx, params.ID, params.PlaybookID, params.TaskID, params.Task)
return s.response(ctx, "SetTask", ticketID(params.ID), i, err)
}
func (s *Service) RunTask(ctx context.Context, params *tickets.RunTaskParams) *api.Response {
return response(nil, s.database.TaskRun(ctx, params.ID, params.PlaybookID, params.TaskID))
err := s.database.TaskRun(ctx, params.ID, params.PlaybookID, params.TaskID)
return s.response(ctx, "RunTask", ticketID(params.ID), nil, err)
}

View File

@@ -2,27 +2,40 @@ package service
import (
"context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickettypes"
)
func ticketTypeID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.TicketTypeCollectionName, id))}
}
func (s *Service) CreateTicketType(ctx context.Context, params *tickettypes.CreateTicketTypeParams) *api.Response {
return response(s.database.TicketTypeCreate(ctx, params.Tickettype))
ticketType, err := s.database.TicketTypeCreate(ctx, params.Tickettype)
return s.response(ctx, "CreateTicketType", ticketTypeID(ticketType.ID), ticketType, err)
}
func (s *Service) GetTicketType(ctx context.Context, params *tickettypes.GetTicketTypeParams) *api.Response {
return response(s.database.TicketTypeGet(ctx, params.ID))
ticketType, err := s.database.TicketTypeGet(ctx, params.ID)
return s.response(ctx, "GetTicketType", nil, ticketType, err)
}
func (s *Service) UpdateTicketType(ctx context.Context, params *tickettypes.UpdateTicketTypeParams) *api.Response {
return response(s.database.TicketTypeUpdate(ctx, params.ID, params.Tickettype))
ticketType, err := s.database.TicketTypeUpdate(ctx, params.ID, params.Tickettype)
return s.response(ctx, "UpdateTicketType", ticketTypeID(ticketType.ID), ticketType, err)
}
func (s *Service) DeleteTicketType(ctx context.Context, params *tickettypes.DeleteTicketTypeParams) *api.Response {
return response(nil, s.database.TicketTypeDelete(ctx, params.ID))
err := s.database.TicketTypeDelete(ctx, params.ID)
return s.response(ctx, "DeleteTicketType", ticketTypeID(params.ID), nil, err)
}
func (s *Service) ListTicketTypes(ctx context.Context) *api.Response {
return response(s.database.TicketTypeList(ctx))
ticketTypes, err := s.database.TicketTypeList(ctx)
return s.response(ctx, "ListTicketTypes", nil, ticketTypes, err)
}

View File

@@ -14,24 +14,24 @@ import (
func (s *Service) GetSettings(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx)
if !ok {
return response(nil, errors.New("no user in context"))
return s.response(ctx, "GetSettings", nil, nil, errors.New("no user in context"))
}
setting, err := s.database.UserDataGet(ctx, user.ID)
if err != nil {
return response(nil, err)
return s.response(ctx, "GetSettings", nil, nil, err)
}
settings := mergeSettings(s.settings, setting)
ticketTypeList, err := s.database.TicketTypeList(ctx)
if err != nil {
return response(nil, err)
return s.response(ctx, "GetSettings", nil, nil, err)
}
settings.TicketTypes = ticketTypeList
return response(settings, nil)
return s.response(ctx, "GetSettings", nil, settings, nil)
}
func mergeSettings(globalSettings *models.Settings, user *models.UserDataResponse) *models.Settings {

View File

@@ -3,36 +3,49 @@ package service
import (
"context"
"errors"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/users"
)
func userID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.UserCollectionName, id))}
}
func (s *Service) GetUser(ctx context.Context, params *users.GetUserParams) *api.Response {
return response(s.database.UserGet(ctx, params.ID))
i, err := s.database.UserGet(ctx, params.ID)
return s.response(ctx, "GetUser", nil, i, err)
}
func (s *Service) ListUsers(ctx context.Context) *api.Response {
return response(s.database.UserList(ctx))
i, err := s.database.UserList(ctx)
return s.response(ctx, "ListUsers", nil, i, err)
}
func (s *Service) CreateUser(ctx context.Context, params *users.CreateUserParams) *api.Response {
return response(s.database.UserCreate(ctx, params.User))
i, err := s.database.UserCreate(ctx, params.User)
return s.response(ctx, "CreateUser", userID(i.ID), i, err)
}
func (s *Service) DeleteUser(ctx context.Context, params *users.DeleteUserParams) *api.Response {
return response(nil, s.database.UserDelete(ctx, params.ID))
err := s.database.UserDelete(ctx, params.ID)
return s.response(ctx, "DeleteUser", userID(params.ID), nil, err)
}
func (s *Service) CurrentUser(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx)
if !ok {
return response(nil, errors.New("no user in context"))
return s.response(ctx, "CurrentUser", nil, nil, errors.New("no user in context"))
}
return response(user, nil)
return s.response(ctx, "CurrentUser", nil, user, nil)
}
func (s *Service) UpdateUser(ctx context.Context, params *users.UpdateUserParams) *api.Response {
return response(s.database.UserUpdate(ctx, params.ID, params.User))
i, err := s.database.UserUpdate(ctx, params.ID, params.User)
return s.response(ctx, "UpdateUser", userID(i.ID), i, err)
}

View File

@@ -3,37 +3,50 @@ package service
import (
"context"
"errors"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/userdata"
)
func userdataID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.UserDataCollectionName, id))}
}
func (s *Service) GetUserData(ctx context.Context, params *userdata.GetUserDataParams) *api.Response {
return response(s.database.UserDataGet(ctx, params.ID))
userData, err := s.database.UserDataGet(ctx, params.ID)
return s.response(ctx, "GetUserData", nil, userData, err)
}
func (s *Service) ListUserData(ctx context.Context) *api.Response {
return response(s.database.UserDataList(ctx))
userData, err := s.database.UserDataList(ctx)
return s.response(ctx, "ListUserData", nil, userData, err)
}
func (s *Service) UpdateUserData(ctx context.Context, params *userdata.UpdateUserDataParams) *api.Response {
return response(s.database.UserDataUpdate(ctx, params.ID, params.Userdata))
userData, err := s.database.UserDataUpdate(ctx, params.ID, params.Userdata)
return s.response(ctx, "UpdateUserData", userdataID(userData.ID), userData, err)
}
func (s *Service) CurrentUserData(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx)
if !ok {
return response(nil, errors.New("no user in context"))
return s.response(ctx, "CurrentUserData", userdataID(user.ID), nil, errors.New("no user in context"))
}
return s.GetUserData(ctx, &userdata.GetUserDataParams{ID: user.ID})
userData, err := s.database.UserDataGet(ctx, user.ID)
return s.response(ctx, "GetUserData", nil, userData, err)
}
func (s *Service) UpdateCurrentUserData(ctx context.Context, params *userdata.UpdateCurrentUserDataParams) *api.Response {
user, ok := busdb.UserFromContext(ctx)
if !ok {
return response(nil, errors.New("no user in context"))
return s.response(ctx, "UpdateCurrentUserData", userdataID(user.ID), nil, errors.New("no user in context"))
}
return response(s.database.UserDataUpdate(ctx, user.ID, params.Userdata))
userData, err := s.database.UserDataUpdate(ctx, user.ID, params.Userdata)
return s.response(ctx, "UpdateCurrentUserData", userdataID(user.ID), userData, err)
}

View File

@@ -77,7 +77,7 @@ func SetupTestData(ctx context.Context, db *database.Database) error {
return err
}
if _, err := db.LogCreate(ctx, "tickets/294511", "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim."); err != nil {
if _, err := db.LogCreate(ctx, "manual", "tickets/294511", "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim."); err != nil {
return err
}

View File

@@ -612,6 +612,12 @@ export interface LogEntry {
* @memberof LogEntry
*/
reference: string;
/**
*
* @type {string}
* @memberof LogEntry
*/
type: string;
}
/**
*
@@ -2026,6 +2032,12 @@ export interface TicketWithTickets {
* @memberof TicketWithTickets
*/
id: number;
/**
*
* @type {Array<LogEntry>}
* @memberof TicketWithTickets
*/
logs?: Array<LogEntry>;
/**
*
* @type {string}

View File

@@ -200,8 +200,15 @@
</v-btn>
</template>
</v-textarea>
<div v-for="(comment, id) in ticket.comments" :key="id" class="pb-2">
<v-card elevation="0" color="cards">
<div v-for="(comment, id) in logs(ticket)" :key="id" class="pb-2">
<div v-if="'type' in comment && comment.message !== 'AddComment'" style="text-align: center">
<span class="text--disabled" :title="comment.created">
{{ comment.message }} &middot;
<strong> {{ comment.creator }}</strong> &middot;
{{ relDate(comment.created) }}
</span>
</div>
<v-card v-else-if="!('type' in comment)" elevation="0" color="cards">
<v-card-subtitle class="pb-0">
<strong> {{ comment.creator }}</strong>
<span class="text--disabled ml-3" :title="comment.created">
@@ -791,7 +798,7 @@ import {
Task,
Type,
TypeColorEnum,
TaskResponse, PlaybookResponse, UserResponse, TaskTypeEnum
TaskResponse, PlaybookResponse, UserResponse, TaskTypeEnum, TicketWithTickets,
} from "../client";
import {API} from "@/services/api";
@@ -1520,6 +1527,9 @@ export default Vue.extend({
});
}
return relDate;
},
logs: function(ticket: TicketWithTickets) {
return this.lodash.reverse(this.lodash.sortBy(this.lodash.union(ticket.comments, ticket.logs), ['created']))
}
},
mounted() {

View File

@@ -6,7 +6,6 @@ import (
"net/http"
"sync"
"github.com/arangodb/go-driver"
"github.com/gin-gonic/gin"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
@@ -48,10 +47,10 @@ func handleWebSocket(catalystBus *bus.Bus) func(ctx *gin.Context) {
broker := websocketBroker{clients: map[string]chan []byte{}}
// send all messages from bus to websocket
err := catalystBus.SubscribeUpdate(func(ids []driver.DocumentID) {
err := catalystBus.SubscribeDatabaseUpdate(func(msg *bus.DatabaseUpdateMsg) {
b, err := json.Marshal(map[string]interface{}{
"action": "update",
"ids": ids,
"ids": msg.IDs,
})
if err != nil {
return