diff --git a/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles.go b/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles.go new file mode 100644 index 00000000000..e96ebe6f3cd --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles.go @@ -0,0 +1,88 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20240314101544, Down_20240314101544) +} + +func Up_20240314101544(tx *sql.Tx) error { + // create a new table to store user information that's persisted after + // users are deleted. + _, err := tx.Exec(` + CREATE TABLE IF NOT EXISTS user_persistent_info ( + -- id is an unique identifier for the row, independent from whatever is stored in 'users' + id int(10) unsigned NOT NULL AUTO_INCREMENT, + + -- user_id is a nullable FK reference to the users table + user_id int(10) unsigned DEFAULT NULL, + + -- user_name mirrors the users.name value + user_name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + + -- user_email mirrors the users.email value + user_email varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + + -- timestamps + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY (id), + UNIQUE INDEX idx_unique_user_id (user_id), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL + ) + `) + if err != nil { + return fmt.Errorf("failed to add user_persistent_info table: %w", err) + } + + // migrate existing data. + _, err = tx.Exec(` + INSERT INTO user_persistent_info (user_id, user_name, user_email) + SELECT id, name, email + FROM users + `) + if err != nil { + return fmt.Errorf("failed to add user information into the user_persistent_info table: %w", err) + } + + tables := []string{ + "nano_commands", "windows_mdm_commands", + "mdm_apple_configuration_profiles", "mdm_windows_configuration_profiles", + } + + for _, t := range tables { + _, err := tx.Exec(fmt.Sprintf(` + ALTER TABLE`+" `%s` "+` + -- user_persistent_info_id references the user that created the entity. + -- it's NULL for rows created prior to this migration, + -- and also for entities that don't have an user + -- associated with it (eg: Fleet initiated actions) + ADD COLUMN user_persistent_info_id int(10) unsigned DEFAULT NULL, + + -- fleet_owned indicates if the entity is managed by Fleet. + ADD COLUMN fleet_owned tinyint(1) DEFAULT NULL + `, t)) + if err != nil { + return fmt.Errorf("failed to add user_persistent_info_id and fleet_owned to %s: %w", t, err) + } + + _, err = tx.Exec(fmt.Sprintf(` + ALTER TABLE`+" `%s` "+` + ADD CONSTRAINT`+" `fk_%s_user_info` "+` + FOREIGN KEY (user_persistent_info_id) REFERENCES user_persistent_info(id) + ON DELETE RESTRICT`, t, t)) + if err != nil { + return fmt.Errorf("failed to add user_persistent_info_id foreign key to %s: %w", t, err) + } + } + + return nil +} + +func Down_20240314101544(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles_test.go b/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles_test.go new file mode 100644 index 00000000000..dc43e520e1c --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20240314101544_TrackUserReferencesForCommandsAndProfiles_test.go @@ -0,0 +1,155 @@ +package tables + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/fleetdm/fleet/v4/server/ptr" + "github.com/stretchr/testify/require" +) + +func TestUp_20240314101544(t *testing.T) { + db := applyUpToPrev(t) + + dataStmts := ` +INSERT INTO users VALUES + (1,'2023-07-21','2023-07-21',_binary '$2a$12$n6hwsD7OU2bAXX94551DQOBcNNhfsEPS3Y6JEuLDjsLNvry3lgJjy','0fF81xRQIriYzm5fdXouk3V3tRwsZJhV','admin','admin@email.com',0,'','',0,'admin',0), + (2,'2023-07-21','2023-07-21',_binary '$2a$12$YxPPOd5TOmYhDlH5CfGIfuxBe4GJ78gbwvtxoBHTTw.symxpVcEZS','JPDLcBcv4j1QwIU+rHoRWBt3HVJC8hnf','User 1','user1@email.com',0,'','',0,NULL,0), + (3,'2023-07-21','2023-07-21',_binary '$2a$12$u3kuHl44jMojsols1NayLu0pPBwZvnWH6j6ZuDk6HsN4r0jgg7BRu','MoWlTEHH9zR7blcJ0l7/1c4EMnkh/dxq','User 2','user2@email.com',0,'','',0,NULL,0); + +INSERT INTO nano_commands + (command_uuid, request_type, command, created_at, updated_at) +VALUES + ('nano-command-uuid-1', 'nano', '