Skip to content

Commit 9e0be4f

Browse files
Feature/176 trigger api trigger playbook by UUID (#201)
1 parent e7905a0 commit 9e0be4f

File tree

11 files changed

+180
-50
lines changed

11 files changed

+180
-50
lines changed

database/memory/memory.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ type InMemoryDatabase struct {
1111
playbooks map[string]cacao.Playbook
1212
}
1313

14-
func New() InMemoryDatabase {
15-
return InMemoryDatabase{playbooks: make(map[string]cacao.Playbook)}
14+
func New() *InMemoryDatabase {
15+
return &InMemoryDatabase{playbooks: make(map[string]cacao.Playbook)}
1616
}
1717

1818
func (memory *InMemoryDatabase) GetPlaybooks() ([]cacao.Playbook, error) {
@@ -25,7 +25,7 @@ func (memory *InMemoryDatabase) GetPlaybooks() ([]cacao.Playbook, error) {
2525
return playbookList, nil
2626
}
2727

28-
func (memory *InMemoryDatabase) GetPlaybooksMetas() ([]api.PlaybookMeta, error) {
28+
func (memory *InMemoryDatabase) GetPlaybookMetas() ([]api.PlaybookMeta, error) {
2929
size := len(memory.playbooks)
3030
playbookList := make([]api.PlaybookMeta, 0, size)
3131
for _, playbook := range memory.playbooks {
@@ -54,7 +54,7 @@ func (memory *InMemoryDatabase) Create(json *[]byte) (cacao.Playbook, error) {
5454
return cacao.Playbook{}, errors.New("playbook already exists")
5555
}
5656
memory.playbooks[result.ID] = *result
57-
return *result, nil
57+
return memory.playbooks[result.ID], nil
5858
}
5959

6060
func (memory *InMemoryDatabase) Read(id string) (cacao.Playbook, error) {

internal/controller/controller.go

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131

3232
"github.com/gin-gonic/gin"
3333

34+
"soarca/database/memory"
3435
mongo "soarca/database/mongodb"
3536
playbookrepository "soarca/database/playbook"
3637
"soarca/routes"
@@ -106,22 +107,31 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer {
106107
}
107108

108109
func (controller *Controller) setupDatabase() error {
109-
mongo.LoadComponent()
110+
initMongoDatabase, _ := strconv.ParseBool(utils.GetEnv("DATABASE", "false"))
110111

111-
log.Info("SOARCA API Trying to start")
112-
mongo_uri := os.Getenv("MONGODB_URI")
113-
db_username := os.Getenv("DB_USERNAME")
114-
db_password := os.Getenv("DB_PASSWORD")
112+
if initMongoDatabase {
113+
114+
mongo.LoadComponent()
115+
116+
log.Info("SOARCA API Trying to start")
117+
uri := os.Getenv("MONGODB_URI")
118+
username := os.Getenv("DB_USERNAME")
119+
password := os.Getenv("DB_PASSWORD")
120+
121+
if uri == "" || username == "" || password == "" {
122+
log.Error("you must set 'MONGODB_URI' or 'DB_USERNAME' or 'DB_PASSWORD' in the environment variable")
123+
return errors.New("could not obtain required environment settings")
124+
}
125+
err := mongo.SetupMongodb(uri, username, password)
126+
if err != nil {
127+
return err
128+
}
129+
controller.playbookRepo = playbookrepository.SetupPlaybookRepository(mongo.GetCacaoRepo(), mongo.DefaultLimitOpts())
130+
} else {
131+
// Use in memory database
132+
controller.playbookRepo = memory.New()
115133

116-
if mongo_uri == "" || db_username == "" || db_password == "" {
117-
log.Error("you must set 'MONGODB_URI' or 'DB_USERNAME' or 'DB_PASSWORD' in the environment variable")
118-
return errors.New("could not obtain required environment settings")
119-
}
120-
err := mongo.SetupMongodb(mongo_uri, db_username, db_password)
121-
if err != nil {
122-
return err
123134
}
124-
controller.playbookRepo = playbookrepository.SetupPlaybookRepository(mongo.GetCacaoRepo(), mongo.DefaultLimitOpts())
125135

126136
return nil
127137
}
@@ -168,24 +178,23 @@ func initializeCore(app *gin.Engine) error {
168178
origins := strings.Split(strings.ReplaceAll(utils.GetEnv("SOARCA_ALLOWED_ORIGINS", "*"), " ", ""), ",")
169179

170180
routes.Cors(app, origins)
171-
err := routes.Api(app, &mainController)
181+
182+
err := mainController.setupDatabase()
172183
if err != nil {
173184
log.Error(err)
174185
return err
175186
}
176187

177-
initDatabase := utils.GetEnv("DATABASE", "false")
178-
if initDatabase == "true" {
179-
err = mainController.setupDatabase()
180-
if err != nil {
181-
log.Error(err)
182-
return err
183-
}
184-
err = routes.Database(app, &mainController)
185-
if err != nil {
186-
log.Error(err)
187-
return err
188-
}
188+
err = routes.Api(app, &mainController, &mainController)
189+
if err != nil {
190+
log.Error(err)
191+
return err
192+
}
193+
194+
err = routes.Database(app, &mainController)
195+
if err != nil {
196+
log.Error(err)
197+
return err
189198
}
190199

191200
// NOTE: Assuming that the cache is the main information mediator for

routes/router.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ func Reporter(app *gin.Engine, informer informer.IExecutionInformer) error {
4040

4141
func Api(app *gin.Engine,
4242
controller decomposer_controller.IController,
43+
database database.IController,
4344
) error {
4445
log.Trace("Trying to setup all Routes")
4546
// gin.SetMode(gin.ReleaseMode)
4647

47-
trigger_api := trigger.New(controller)
48+
trigger_api := trigger.New(controller, database)
4849
coa_routes.Routes(app)
4950
status.Routes(app)
5051
operator.Routes(app)

routes/trigger/trigger_api.go

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77
"time"
88

9+
"soarca/internal/controller/database"
910
"soarca/internal/controller/decomposer_controller"
1011
"soarca/internal/decomposer"
1112
"soarca/logger"
@@ -29,19 +30,76 @@ func init() {
2930

3031
type TriggerApi struct {
3132
controller decomposer_controller.IController
33+
database database.IController
3234
Executionsch chan decomposer.ExecutionDetails
3335
}
3436

35-
func New(controller decomposer_controller.IController) *TriggerApi {
37+
func New(controller decomposer_controller.IController, database database.IController) *TriggerApi {
3638
instance := TriggerApi{}
3739
instance.controller = controller
40+
instance.database = database
3841
// Channel to get back execution details
3942
instance.Executionsch = make(chan decomposer.ExecutionDetails)
4043
return &instance
4144
}
4245

43-
func (trigger *TriggerApi) Execute(context *gin.Context) {
46+
// trigger
47+
//
48+
// @Summary trigger a playbook by id that is stored in SOARCA
49+
// @Schemes
50+
// @Description trigger playbook by id
51+
// @Tags trigger
52+
// @Accept json
53+
// @Produce json
54+
// @Param id path string true "playbook ID"
55+
// @Success 200 {object} api.Execution
56+
// @failure 400 {object} api.Error
57+
// @Router /trigger/playbook/{id} [POST]
58+
func (trigger *TriggerApi) ExecuteById(context *gin.Context) {
59+
60+
id := context.Param("id")
61+
62+
db := trigger.database.GetDatabaseInstance()
63+
playbook, err := db.Read(id)
64+
if err != nil {
65+
log.Error("failed to load playbook")
66+
error.SendErrorResponse(context, http.StatusBadRequest,
67+
"Failed to load playbook",
68+
"POST /trigger/playbook/"+id, err.Error())
69+
return
70+
}
71+
4472
// create new decomposer when execute is called
73+
decomposer := trigger.controller.NewDecomposer()
74+
executionDetail, errDecomposer := decomposer.Execute(playbook)
75+
if errDecomposer != nil {
76+
error.SendErrorResponse(context, http.StatusBadRequest,
77+
"Failed to decode playbook",
78+
"POST /trigger/playbook/"+id,
79+
executionDetail.ExecutionId.String())
80+
} else {
81+
msg := gin.H{
82+
"execution_id": executionDetail.ExecutionId.String(),
83+
"payload": executionDetail.PlaybookId,
84+
}
85+
context.JSON(http.StatusOK, msg)
86+
}
87+
88+
}
89+
90+
// trigger
91+
//
92+
// @Summary trigger a playbook by supplying a cacao playbook payload
93+
// @Schemes
94+
// @Description trigger playbook
95+
// @Tags trigger
96+
// @Accept json
97+
// @Produce json
98+
// @Param playbook body cacao.Playbook true "execute playbook by payload"
99+
// @Success 200 {object} api.Execution
100+
// @failure 400 {object} api.Error
101+
// @Router /trigger/playbook [POST]
102+
func (trigger *TriggerApi) Execute(context *gin.Context) {
45103
decomposer := trigger.controller.NewDecomposer()
46104
jsonData, errIo := io.ReadAll(context.Request.Body)
47105
if errIo != nil {

routes/trigger/trigger_endpoints.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,10 @@ import (
44
"github.com/gin-gonic/gin"
55
)
66

7-
// trigger
8-
//
9-
// @Summary trigger a playbook by supplying a cacao playbook payload
10-
// @Schemes
11-
// @Description trigger playbook
12-
// @Tags trigger
13-
// @Accept json
14-
// @Produce json
15-
// @Param playbook body cacao.Playbook true "execute playbook by payload"
16-
// @Success 200 {object} api.Execution
17-
// @failure 400 {object} api.Error
18-
// @Router /trigger/playbook [POST]
197
func Routes(route *gin.Engine, trigger *TriggerApi) {
208
group := route.Group("/trigger")
219
{
2210
group.POST("/playbook", trigger.Execute)
11+
group.POST("/playbook/:id", trigger.ExecuteById)
2312
}
2413
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package api_test
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestTest(t *testing.T) {
13+
// Start SOARCA in separate threat
14+
t.Setenv("PORT", "8085")
15+
go initializeSoarca(t)
16+
17+
// Wait for the server to be online
18+
time.Sleep(400 * time.Millisecond)
19+
20+
client := http.Client{}
21+
buffer := bytes.NewBufferString("")
22+
request, err := http.NewRequest("POST", "http://localhost:8085/trigger/", buffer)
23+
if err != nil {
24+
t.Fail()
25+
}
26+
27+
request.Header.Add("Origin", "http://example.com")
28+
response, err := client.Do(request)
29+
if err != nil {
30+
t.Log(err)
31+
t.Fail()
32+
}
33+
origins := response.Header.Get("Access-Control-Allow-Origin")
34+
assert.Equal(t, "*", origins)
35+
36+
}

test/unittest/database/memory/memory_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func TestGetAllPlaybookMetas(t *testing.T) {
239239
// assert.Equal(t, playbook, workflow)
240240
}
241241

242-
playbooks, err := mem.GetPlaybooksMetas()
242+
playbooks, err := mem.GetPlaybookMetas()
243243
assert.Equal(t, err, nil)
244244
assert.Equal(t, len(playbooks), 10)
245245

test/unittest/executor/playbook_action/playbook_action_executor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
mock_database_controller "soarca/test/unittest/mocks/mock_controller/database"
99
mock_decomposer_controller "soarca/test/unittest/mocks/mock_controller/decomposer"
1010
"soarca/test/unittest/mocks/mock_decomposer"
11+
mocks_playbook_test "soarca/test/unittest/mocks/mock_playbook_database"
1112
"soarca/test/unittest/mocks/mock_reporter"
12-
mocks_playbook_test "soarca/test/unittest/mocks/playbook"
1313

1414
"soarca/models/cacao"
1515
"soarca/models/execution"

test/unittest/mocks/playbook/mock_playbook.go renamed to test/unittest/mocks/mock_playbook_database/mock_playbook_database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package mocks_playbook_test
1+
package mock_playbook_database
22

33
import (
44
"soarca/models/api"

test/unittest/routes/playbook_api/playbook_api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"soarca/models/decoder"
1616
playbookRouter "soarca/routes/playbook"
1717
mock_database_controller "soarca/test/unittest/mocks/mock_controller/database"
18-
mock_playbook "soarca/test/unittest/mocks/playbook"
18+
mock_playbook "soarca/test/unittest/mocks/mock_playbook_database"
1919

2020
"github.com/gin-gonic/gin"
2121
"github.com/stretchr/testify/assert"

0 commit comments

Comments
 (0)