diff --git a/server/datastore/datastore_queries.go b/server/datastore/datastore_queries.go index ac7fc53583..9f022fa8a0 100644 --- a/server/datastore/datastore_queries.go +++ b/server/datastore/datastore_queries.go @@ -17,7 +17,7 @@ func testApplyQueries(t *testing.T, ds kolide.Datastore) { zwass := test.NewUser(t, ds, "Zach", "zwass", "zwass@kolide.co", true) groob := test.NewUser(t, ds, "Victor", "groob", "victor@kolide.co", true) expectedQueries := []*kolide.Query{ - {Name: "foo", Description: "get the foos", Query: "select * from foo"}, + {Name: "foo", Description: "get the foos", Query: "select * from foo", ObserverCanRun: true}, {Name: "bar", Description: "do some bars", Query: "select baz from bar"}, } @@ -34,6 +34,7 @@ func testApplyQueries(t *testing.T, ds kolide.Datastore) { assert.Equal(t, comp.Description, q.Description) assert.Equal(t, comp.Query, q.Query) assert.Equal(t, &zwass.ID, q.AuthorID) + assert.Equal(t, comp.ObserverCanRun, q.ObserverCanRun) } // Victor modifies a query (but also pushes the same version of the @@ -160,6 +161,7 @@ func testSaveQuery(t *testing.T, ds kolide.Datastore) { assert.NotEqual(t, 0, query.ID) query.Query = "baz" + query.ObserverCanRun = true err = ds.SaveQuery(query) require.Nil(t, err) @@ -169,6 +171,7 @@ func testSaveQuery(t *testing.T, ds kolide.Datastore) { require.NotNil(t, queryVerify) assert.Equal(t, "baz", queryVerify.Query) assert.Equal(t, "Zach", queryVerify.AuthorName) + assert.True(t, queryVerify.ObserverCanRun) } func testListQuery(t *testing.T, ds kolide.Datastore) { diff --git a/server/datastore/mysql/migrations/tables/20210517112751_TeamsObserverQueries.go b/server/datastore/mysql/migrations/tables/20210517112751_TeamsObserverQueries.go new file mode 100644 index 0000000000..444358985a --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20210517112751_TeamsObserverQueries.go @@ -0,0 +1,26 @@ +package tables + +import ( + "database/sql" + + "github.com/pkg/errors" +) + +func init() { + MigrationClient.AddMigration(Up_20210517112751, Down_20210517112751) +} + +func Up_20210517112751(tx *sql.Tx) error { + sql := ` + ALTER TABLE queries + ADD COLUMN observer_can_run TINYINT(1) NOT NULL DEFAULT FALSE + ` + if _, err := tx.Exec(sql); err != nil { + return errors.Wrap(err, "add column observer_run") + } + return nil +} + +func Down_20210517112751(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/queries.go b/server/datastore/mysql/queries.go index fe7e709e1c..33b524b0fd 100644 --- a/server/datastore/mysql/queries.go +++ b/server/datastore/mysql/queries.go @@ -34,14 +34,16 @@ func (d *Datastore) ApplyQueries(authorID uint, queries []*kolide.Query) (err er description, query, author_id, - saved - ) VALUES ( ?, ?, ?, ?, true ) + saved, + observer_can_run + ) VALUES ( ?, ?, ?, ?, true, ? ) ON DUPLICATE KEY UPDATE name = VALUES(name), description = VALUES(description), query = VALUES(query), author_id = VALUES(author_id), - saved = VALUES(saved) + saved = VALUES(saved), + observer_can_run = VALUES(observer_can_run) ` stmt, err := tx.Prepare(sql) if err != nil { @@ -52,7 +54,7 @@ func (d *Datastore) ApplyQueries(authorID uint, queries []*kolide.Query) (err er if q.Name == "" { return errors.New("query name must not be empty") } - _, err := stmt.Exec(q.Name, q.Description, q.Query, authorID) + _, err := stmt.Exec(q.Name, q.Description, q.Query, authorID, q.ObserverCanRun) if err != nil { return errors.Wrap(err, "exec ApplyQueries insert") } @@ -95,10 +97,11 @@ func (d *Datastore) NewQuery(query *kolide.Query, opts ...kolide.OptionalArg) (* description, query, saved, - author_id - ) VALUES ( ?, ?, ?, ?, ? ) + author_id, + observer_can_run + ) VALUES ( ?, ?, ?, ?, ?, ? ) ` - result, err := db.Exec(sqlStatement, query.Name, query.Description, query.Query, query.Saved, query.AuthorID) + result, err := db.Exec(sqlStatement, query.Name, query.Description, query.Query, query.Saved, query.AuthorID, query.ObserverCanRun) if err != nil && isDuplicate(err) { return nil, alreadyExists("Query", 0) @@ -116,10 +119,10 @@ func (d *Datastore) NewQuery(query *kolide.Query, opts ...kolide.OptionalArg) (* func (d *Datastore) SaveQuery(q *kolide.Query) error { sql := ` UPDATE queries - SET name = ?, description = ?, query = ?, author_id = ?, saved = ? + SET name = ?, description = ?, query = ?, author_id = ?, saved = ?, observer_can_run = ? WHERE id = ? ` - result, err := d.db.Exec(sql, q.Name, q.Description, q.Query, q.AuthorID, q.Saved, q.ID) + result, err := d.db.Exec(sql, q.Name, q.Description, q.Query, q.AuthorID, q.Saved, q.ObserverCanRun, q.ID) if err != nil { return errors.Wrap(err, "updating query") } diff --git a/server/kolide/queries.go b/server/kolide/queries.go index 63cbc8c1fb..dff3b3b1c1 100644 --- a/server/kolide/queries.go +++ b/server/kolide/queries.go @@ -73,7 +73,10 @@ type Query struct { Description string `json:"description"` Query string `json:"query"` Saved bool `json:"saved"` - AuthorID *uint `json:"author_id" db:"author_id"` + // ObserverCanRun indicates whether users with Observer role can run this as + // a live query. + ObserverCanRun bool `json:"observer_can_run" db:"observer_can_run"` + AuthorID *uint `json:"author_id" db:"author_id"` // AuthorName is retrieved with a join to the users table in the MySQL // backend (using AuthorID) AuthorName string `json:"author_name" db:"author_name"`