package sql import ( "context" "errors" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/pkg/errorx" "github.com/FAU-CDI/wisski-distillery/pkg/sqle" ) var errProvisionInvalidDatabaseParams = errors.New("Provision: Invalid parameters") var errProvisionInvalidGrant = errors.New("Provision: Grant failed") // Provision provisions sql-specific resource for the given instance func (sql *SQL) Provision(ctx context.Context, instance models.Instance, domain string) error { return sql.CreateDatabase(ctx, instance.SqlDatabase, instance.SqlUsername, instance.SqlPassword) } // Purge purges sql-specific resources for the given instance func (sql *SQL) Purge(ctx context.Context, instance models.Instance, domain string) error { return errorx.First( sql.PurgeDatabase(instance.SqlDatabase), sql.PurgeUser(ctx, instance.SqlUsername), ) } // CreateDatabase creates a new database with the given name. // It then generates a new user, with the name 'user' and the password 'password', that is then granted access to this database. // // Provision internally waits for the database to become available. func (sql *SQL) CreateDatabase(ctx context.Context, name, user, password string) error { // NOTE(twiesing): We shouldn't use string concat to build sql queries. // But the driver doesn't support using query params for this particular query. // Apparently it's a "feature", see https://github.com/go-sql-driver/mysql/issues/398#issuecomment-169951763. // quick and dirty check to make sure that all the names won't sql inject. if !sqle.IsSafeDatabaseLiteral(name) || !sqle.IsSafeDatabaseSingleQuote(user) || !sqle.IsSafeDatabaseSingleQuote(password) { return errProvisionInvalidDatabaseParams } // We use the sql shell here, because not only can we not use query params, but the driver outright rejects queries. // Queries of the form "CREATE USER 'test'@'%' IDENTIFIED BY 'test'; FLUSH PRIVILEGES;" return error 1064 when using driver, but are fine with the shell. // This should be fixed eventually, but I have no idea how. if err := sql.unsafeWaitShell(ctx); err != nil { return err } query := "CREATE DATABASE `" + name + "`;" + "CREATE USER '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + "GRANT ALL PRIVILEGES ON `" + name + "`.* TO `" + user + "`@`%`; FLUSH PRIVILEGES;" if !sql.unsafeQueryShell(ctx, query) { return errProvisionInvalidGrant } return nil } var errCreateSuperuserGrant = errors.New("CreateSuperUser: Grant failed") // CreateSuperuser createsa new user, with the name 'user' and the password 'password'. // It then grants this user superuser status in the database. // // CreateSuperuser internally waits for the database to become available. func (sql *SQL) CreateSuperuser(ctx context.Context, user, password string, allowExisting bool) error { // NOTE(twiesing): This function unsafely uses the shell directly to create a superuser. // This is for two reasons: // (1) this is used during bootstraping // (2) The underlying driver doesn't support "GRANT ALL PRIVILEGES" // See also [sql.Provision]. if !sqle.IsSafeDatabaseSingleQuote(user) || !sqle.IsSafeDatabaseSingleQuote(password) { return errProvisionInvalidDatabaseParams } if err := sql.unsafeWaitShell(ctx); err != nil { return err } var IfNotExists string if allowExisting { IfNotExists = "IF NOT EXISTS" } query := "CREATE USER " + IfNotExists + " '" + user + "'@'%' IDENTIFIED BY '" + password + "';" + "GRANT ALL PRIVILEGES ON *.* TO '" + user + "'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" if !sql.unsafeQueryShell(ctx, query) { return errCreateSuperuserGrant } return nil } var errPurgeUser = errors.New("PurgeUser: Failed to drop user") // SQLPurgeUser deletes the specified user from the database func (sql *SQL) PurgeUser(ctx context.Context, user string) error { if !sqle.IsSafeDatabaseSingleQuote(user) { return errPurgeUser } query := "DROP USER IF EXISTS '" + user + "'@'%';" + "FLUSH PRIVILEGES;" if !sql.unsafeQueryShell(ctx, query) { return errPurgeUser } return nil } var errSQLPurgeDB = errors.New("unable to drop database: unsafe database name") // SQLPurgeDatabase deletes the specified db from the database func (sql *SQL) PurgeDatabase(db string) error { if !sqle.IsSafeDatabaseLiteral(db) { return errSQLPurgeDB } return sql.Exec("DROP DATABASE IF EXISTS `" + db + "`") }