diff --git a/go/Godeps/LICENSES b/go/Godeps/LICENSES index 04293825252..3abfa6bba09 100644 --- a/go/Godeps/LICENSES +++ b/go/Godeps/LICENSES @@ -9582,6 +9582,40 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. = LICENSE ed6066ae50f153e2965216c6d4b9335900f1f8b2b526527f49a619d7 = ================================================================================ +================================================================================ += golang.org/x/term licensed under: = + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + += LICENSE ed6066ae50f153e2965216c6d4b9335900f1f8b2b526527f49a619d7 = +================================================================================ + ================================================================================ = golang.org/x/text licensed under: = diff --git a/go/Godeps/update.sh b/go/Godeps/update.sh index 6532142b6a8..e3c5ece99ac 100755 --- a/go/Godeps/update.sh +++ b/go/Godeps/update.sh @@ -5,6 +5,5 @@ set -eo pipefail script_dir=$(dirname "$0") cd $script_dir/.. -( go list -deps -json ./cmd/dolt/. && \ - GOOS=windows go list -deps -json ./cmd/dolt/. ) \ +( go list -deps -json -tags +amd64,+arm64,+linux,+windows,+darwin,+386 ./cmd/dolt/. ) \ | go run ./utils/3pdeps/. > ./Godeps/LICENSES diff --git a/go/Godeps/verify.sh b/go/Godeps/verify.sh index 4e862fa2950..5f7638ae5a8 100755 --- a/go/Godeps/verify.sh +++ b/go/Godeps/verify.sh @@ -5,6 +5,5 @@ set -eo pipefail script_dir=$(dirname "$0") cd $script_dir/.. -( go list -deps -json ./cmd/dolt/. && \ - GOOS=windows go list -deps -json ./cmd/dolt/. ) \ +( go list -deps -json -tags +arm64,+amd64,+windows,+linux,+darwin,+386 ./cmd/dolt/. ) \ | go run ./utils/3pdeps/. -verify ./Godeps/LICENSES diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index b4b7648e0fe..b443d0d5afb 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -75,52 +75,6 @@ func ParseAuthor(authorStr string) (string, string, error) { return name, email, nil } -const ( - AllowEmptyFlag = "allow-empty" - SkipEmptyFlag = "skip-empty" - DateParam = "date" - MessageArg = "message" - AuthorParam = "author" - ForceFlag = "force" - DryRunFlag = "dry-run" - SetUpstreamFlag = "set-upstream" - AllFlag = "all" - UpperCaseAllFlag = "ALL" - HardResetParam = "hard" - SoftResetParam = "soft" - CheckoutCoBranch = "b" - NoFFParam = "no-ff" - SquashParam = "squash" - AbortParam = "abort" - CopyFlag = "copy" - MoveFlag = "move" - DeleteFlag = "delete" - DeleteForceFlag = "D" - OutputOnlyFlag = "output-only" - RemoteParam = "remote" - BranchParam = "branch" - TrackFlag = "track" - AmendFlag = "amend" - CommitFlag = "commit" - NoCommitFlag = "no-commit" - NoEditFlag = "no-edit" - OursFlag = "ours" - TheirsFlag = "theirs" - NumberFlag = "number" - NotFlag = "not" - MergesFlag = "merges" - ParentsFlag = "parents" - MinParentsFlag = "min-parents" - DecorateFlag = "decorate" - OneLineFlag = "oneline" - ShallowFlag = "shallow" - CachedFlag = "cached" - ListFlag = "list" - UserParam = "user" - NoPrettyFlag = "no-pretty" - ShowIgnoredFlag = "ignored" -) - const ( SyncBackupId = "sync" SyncBackupUrlId = "sync-url" @@ -202,7 +156,7 @@ func CreateCloneArgParser() *argparser.ArgParser { ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use.") ap.SupportsString(dbfactory.OSSCredsFileParam, "", "file", "OSS credentials file.") ap.SupportsString(dbfactory.OSSCredsProfile, "", "profile", "OSS profile to use.") - ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") + ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") return ap } @@ -247,7 +201,7 @@ func CreateCherryPickArgParser() *argparser.ArgParser { func CreateFetchArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithVariableArgs("fetch") - ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") + ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") return ap } @@ -270,7 +224,7 @@ func CreatePullArgParser() *argparser.ArgParser { ap.SupportsFlag(CommitFlag, "", "Perform the merge and commit the result. This is the default option, but can be overridden with the --no-commit flag. Note that this option does not affect fast-forward merges, which don't create a new merge commit, and if any merge conflicts or constraint violations are detected, no commit will be attempted.") ap.SupportsFlag(NoCommitFlag, "", "Perform the merge and stop just before creating a merge commit. Note this will not prevent a fast-forward merge; use the --no-ff arg together with the --no-commit arg to prevent both fast-forwards and merge commits.") ap.SupportsFlag(NoEditFlag, "", "Use an auto-generated commit message when creating a merge commit. The default for interactive CLI sessions is to open an editor.") - ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") + ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.") return ap } diff --git a/go/cmd/dolt/cli/credentials.go b/go/cmd/dolt/cli/credentials.go new file mode 100644 index 00000000000..c40e234dc20 --- /dev/null +++ b/go/cmd/dolt/cli/credentials.go @@ -0,0 +1,86 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "fmt" + "os" + + "golang.org/x/crypto/ssh/terminal" + + "github.com/dolthub/dolt/go/libraries/utils/argparser" +) + +type UserPassword struct { + Username string + Password string + Specified bool // If true, the user and password were provided by the user. +} + +const DOLT_ENV_PWD = "DOLT_CLI_PASSWORD" +const DOLT_SILENCE_USER_REQ_FOR_TESTING = "DOLT_SILENCE_USER_REQ_FOR_TESTING" + +// BuildUserPasswordPrompt builds a UserPassword struct from the parsed args. The user is prompted for a password if one +// is not provided. If a username is not provided, the default is "root" (which will not be allowed is a password is +// provided). A new instances of ArgParseResults is returned which does not contain the user or password flags. +func BuildUserPasswordPrompt(parsedArgs *argparser.ArgParseResults) (newParsedArgs *argparser.ArgParseResults, credentials *UserPassword, err error) { + userId, hasUserId := parsedArgs.GetValue(UserFlag) + password, hasPassword := parsedArgs.GetValue(PasswordFlag) + + if !hasPassword { + envPassword, hasEnvPassword := os.LookupEnv(DOLT_ENV_PWD) + if hasEnvPassword { + password = envPassword + hasPassword = true + } + } + + newParsedArgs = parsedArgs.DropValue(UserFlag) + newParsedArgs = newParsedArgs.DropValue(PasswordFlag) + + if !hasUserId && !hasPassword { + // Common "out of box" behavior. + return newParsedArgs, &UserPassword{Username: "root", Password: "", Specified: false}, nil + } + + if hasUserId && hasPassword { + return newParsedArgs, &UserPassword{Username: userId, Password: password, Specified: true}, nil + } + + if hasUserId && !hasPassword { + password = "" + val, hasVal := os.LookupEnv(DOLT_ENV_PWD) + if hasVal { + password = val + } else { + Printf("Enter password: ") + passwordBytes, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return nil, nil, err + } + password = string(passwordBytes) // Assuming UTF-8 for time being. This may not work forever. + } + return newParsedArgs, &UserPassword{Username: userId, Password: password, Specified: true}, nil + } + + testOverride, hasTestOverride := os.LookupEnv(DOLT_SILENCE_USER_REQ_FOR_TESTING) + if hasTestOverride && testOverride == "Y" { + // Used for BATS testing only. Typical usage will not hit this path, but we have many legacy tests which + // do not provide a user, and the DOLT_ENV_PWD is set to avoid the prompt. + return newParsedArgs, &UserPassword{Specified: false}, nil + } + + return nil, nil, fmt.Errorf("When a password is provided, a user must also be provided. Use the --user flag to provide a username") +} diff --git a/go/cmd/dolt/cli/flags.go b/go/cmd/dolt/cli/flags.go new file mode 100644 index 00000000000..8492a3c0f54 --- /dev/null +++ b/go/cmd/dolt/cli/flags.go @@ -0,0 +1,64 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +// Constants for command line flags names. These tend to be used in multiple places, so defining +// them low in the package dependency tree makes sense. +const ( + AbortParam = "abort" + AllFlag = "all" + AllowEmptyFlag = "allow-empty" + AmendFlag = "amend" + AuthorParam = "author" + BranchParam = "branch" + CachedFlag = "cached" + CheckoutCoBranch = "b" + CommitFlag = "commit" + CopyFlag = "copy" + DateParam = "date" + DecorateFlag = "decorate" + DeleteFlag = "delete" + DeleteForceFlag = "D" + DryRunFlag = "dry-run" + ForceFlag = "force" + HardResetParam = "hard" + ListFlag = "list" + MergesFlag = "merges" + MessageArg = "message" + MinParentsFlag = "min-parents" + MoveFlag = "move" + NoCommitFlag = "no-commit" + NoEditFlag = "no-edit" + NoFFParam = "no-ff" + NoPrettyFlag = "no-pretty" + NotFlag = "not" + NumberFlag = "number" + OneLineFlag = "oneline" + OursFlag = "ours" + OutputOnlyFlag = "output-only" + ParentsFlag = "parents" + PasswordFlag = "password" + RemoteParam = "remote" + SetUpstreamFlag = "set-upstream" + ShallowFlag = "shallow" + ShowIgnoredFlag = "ignored" + SkipEmptyFlag = "skip-empty" + SoftResetParam = "soft" + SquashParam = "squash" + TheirsFlag = "theirs" + TrackFlag = "track" + UpperCaseAllFlag = "ALL" + UserFlag = "user" +) diff --git a/go/cmd/dolt/commands/clone.go b/go/cmd/dolt/commands/clone.go index 6854d13654d..2eb95554318 100644 --- a/go/cmd/dolt/commands/clone.go +++ b/go/cmd/dolt/commands/clone.go @@ -237,7 +237,7 @@ func validateAndParseDolthubUrl(urlStr string) (string, bool) { } func getRemoteUserAndPassConfig(apr *argparser.ArgParseResults) (*creds.DoltCredsForPass, errhand.VerboseError) { - if !apr.Contains(cli.UserParam) { + if !apr.Contains(cli.UserFlag) { return nil, nil } pass, found := os.LookupEnv("DOLT_REMOTE_PASSWORD") @@ -245,7 +245,7 @@ func getRemoteUserAndPassConfig(apr *argparser.ArgParseResults) (*creds.DoltCred return nil, errhand.BuildDError("error: must set DOLT_REMOTE_PASSWORD environment variable to use --user param").Build() } return &creds.DoltCredsForPass{ - Username: apr.GetValueOrDefault(cli.UserParam, ""), + Username: apr.GetValueOrDefault(cli.UserFlag, ""), Password: pass, }, nil } diff --git a/go/cmd/dolt/commands/sqlserver/server.go b/go/cmd/dolt/commands/sqlserver/server.go index fe8ba6155da..2c7acdc10d0 100644 --- a/go/cmd/dolt/commands/sqlserver/server.go +++ b/go/cmd/dolt/commands/sqlserver/server.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "net/http" + "os" "runtime" "strconv" "time" @@ -45,6 +46,10 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/sqlserver" ) +const ( + LocalConnectionUser = "__dolt_local_user__" +) + // Serve starts a MySQL-compatible server. Returns any errors that were encountered. func Serve( ctx context.Context, @@ -219,6 +224,12 @@ func Serve( lck := env.NewDBLock(serverConfig.Port()) sqlserver.SetRunningServer(mySQLServer, &lck) + localConnectionHost := "localhost" + if isDoltTestEnvSet() { + localConnectionHost = "%" + } + sqlEngine.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb.AddSuperUser(LocalConnectionUser, localConnectionHost, lck.Secret) + var metSrv *http.Server if serverConfig.MetricsHost() != "" && serverConfig.MetricsPort() > 0 { mux := http.NewServeMux() @@ -508,3 +519,8 @@ func checkForUnixSocket(config ServerConfig) (string, bool, error) { return "", false, nil } + +// isDoltTestEnvSet Temporary function to enable __dolt_local_user__ to be used until https://github.com/dolthub/dolt/issues/6239 is resolved +func isDoltTestEnvSet() bool { + return os.Getenv("DOLT_ENABLE_LOCAL_USER_FOR_ALL_HOSTS") != "" +} diff --git a/go/cmd/dolt/commands/sqlserver/sqlclient.go b/go/cmd/dolt/commands/sqlserver/sqlclient.go index 6a48c1b6b65..3a24286c67b 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlclient.go +++ b/go/cmd/dolt/commands/sqlserver/sqlclient.go @@ -475,13 +475,13 @@ func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, s // BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this // module isn't ideal, but it's the only way to get the server config into the queryist. -func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, apr *argparser.ArgParseResults, port int, database string) (cli.LateBindQueryist, error) { - serverConfig, err := GetServerConfig(cwdFS, apr) +func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, port int, database string) (cli.LateBindQueryist, error) { + clientConfig, err := GetClientConfig(cwdFS, creds, apr) if err != nil { return nil, err } - parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(serverConfig, database)) + parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(clientConfig, database)) if err != nil { return nil, err } diff --git a/go/cmd/dolt/commands/sqlserver/sqlserver.go b/go/cmd/dolt/commands/sqlserver/sqlserver.go index a8f613136ed..ff450939fa3 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlserver.go +++ b/go/cmd/dolt/commands/sqlserver/sqlserver.go @@ -265,7 +265,7 @@ func GetServerConfig(cwdFS filesys.Filesys, apr *argparser.ArgParseResults) (Ser } yamlCfg = cfg.(YAMLConfig) } else { - return getCommandLineServerConfig(apr) + return getCommandLineConfig(nil, apr) } // if command line user argument was given, replace yaml's user and password @@ -283,6 +283,38 @@ func GetServerConfig(cwdFS filesys.Filesys, apr *argparser.ArgParseResults) (Ser return yamlCfg, nil } +// GetClientConfig returns configuration which is sutable for a client to use. The fact that it returns a ServerConfig +// is a little confusing, but it is because the client and server use the same configuration struct. The main difference +// between this method and GetServerConfig is that this method required a cli.UserPassword argument. It is created by +// prompting the user, and we don't want the server to follow that code path. +func GetClientConfig(cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults) (ServerConfig, error) { + cfgFile, hasCfgFile := apr.GetValue(configFileFlag) + + if !hasCfgFile { + return getCommandLineConfig(creds, apr) + } + + var yamlCfg YAMLConfig + cfg, err := getYAMLServerConfig(cwdFS, cfgFile) + if err != nil { + return nil, err + } + yamlCfg = cfg.(YAMLConfig) + + // if command line user argument was given, replace yaml's user and password + if creds.Specified { + yamlCfg.UserConfig.Name = &creds.Username + yamlCfg.UserConfig.Password = &creds.Password + } + + if connStr, ok := apr.GetValue(goldenMysqlConn); ok { + cli.Println(connStr) + yamlCfg.GoldenMysqlConn = &connStr + } + + return yamlCfg, nil +} + // SetupDoltConfig updates the given server config with where to create .doltcfg directory func SetupDoltConfig(dEnv *env.DoltEnv, apr *argparser.ArgParseResults, config ServerConfig) error { if _, ok := apr.GetValue(configFileFlag); ok { @@ -358,41 +390,47 @@ func SetupDoltConfig(dEnv *env.DoltEnv, apr *argparser.ArgParseResults, config S return nil } -// getCommandLineServerConfig sets server config variables and persisted global variables with values defined on command line. -// If not defined, it sets variables to default values. -func getCommandLineServerConfig(apr *argparser.ArgParseResults) (ServerConfig, error) { - serverConfig := DefaultServerConfig() +// getCommandLineConfig sets server config variables and persisted global variables with values defined on command line. +// If not defined, it sets variables to default values. The creds option is available when building a client config, which +// is used for most commands. `dolt sql-server` is special, and its user/pwd is pa`ssed in via apr, so creds must be nil +// to indicate that the user/pwd should be taken from the apr. +func getCommandLineConfig(creds *cli.UserPassword, apr *argparser.ArgParseResults) (ServerConfig, error) { + config := DefaultServerConfig() if sock, ok := apr.GetValue(socketFlag); ok { // defined without value gets default if sock == "" { sock = defaultUnixSocketFilePath } - serverConfig.WithSocket(sock) + config.WithSocket(sock) } if host, ok := apr.GetValue(hostFlag); ok { - serverConfig.WithHost(host) + config.WithHost(host) } if port, ok := apr.GetInt(portFlag); ok { - serverConfig.WithPort(port) - } - - if user, ok := apr.GetValue(commands.UserFlag); ok { - serverConfig.withUser(user) + config.WithPort(port) } - if password, ok := apr.GetValue(passwordFlag); ok { - serverConfig.withPassword(password) + if creds == nil { + if user, ok := apr.GetValue(cli.UserFlag); ok { + config.withUser(user) + } + if password, ok := apr.GetValue(cli.PasswordFlag); ok { + config.withPassword(password) + } + } else { + config.withUser(creds.Username) + config.withPassword(creds.Password) } if port, ok := apr.GetInt(remotesapiPortFlag); ok { - serverConfig.WithRemotesapiPort(&port) + config.WithRemotesapiPort(&port) } if persistenceBehavior, ok := apr.GetValue(persistenceBehaviorFlag); ok { - serverConfig.withPersistenceBehavior(persistenceBehavior) + config.withPersistenceBehavior(persistenceBehavior) } if timeoutStr, ok := apr.GetValue(timeoutFlag); ok { @@ -402,7 +440,7 @@ func getCommandLineServerConfig(apr *argparser.ArgParseResults) (ServerConfig, e return nil, fmt.Errorf("invalid value for --timeout '%s'", timeoutStr) } - serverConfig.withTimeout(timeout * 1000) + config.withTimeout(timeout * 1000) err = sql.SystemVariables.SetGlobal("net_read_timeout", timeout*1000) if err != nil { @@ -415,42 +453,42 @@ func getCommandLineServerConfig(apr *argparser.ArgParseResults) (ServerConfig, e } if _, ok := apr.GetValue(readonlyFlag); ok { - serverConfig.withReadOnly(true) + config.withReadOnly(true) } if logLevel, ok := apr.GetValue(logLevelFlag); ok { - serverConfig.withLogLevel(LogLevel(strings.ToLower(logLevel))) + config.withLogLevel(LogLevel(strings.ToLower(logLevel))) } if dataDir, ok := apr.GetValue(commands.MultiDBDirFlag); ok { - serverConfig.withDataDir(dataDir) + config.withDataDir(dataDir) } if dataDir, ok := apr.GetValue(commands.DataDirFlag); ok { - serverConfig.withDataDir(dataDir) + config.withDataDir(dataDir) } if queryParallelism, ok := apr.GetInt(queryParallelismFlag); ok { - serverConfig.withQueryParallelism(queryParallelism) + config.withQueryParallelism(queryParallelism) } if maxConnections, ok := apr.GetInt(maxConnectionsFlag); ok { - serverConfig.withMaxConnections(uint64(maxConnections)) + config.withMaxConnections(uint64(maxConnections)) err := sql.SystemVariables.SetGlobal("max_connections", uint64(maxConnections)) if err != nil { return nil, fmt.Errorf("failed to set max_connections. Error: %s", err.Error()) } } - serverConfig.autoCommit = !apr.Contains(noAutoCommitFlag) - serverConfig.allowCleartextPasswords = apr.Contains(allowCleartextPasswordsFlag) + config.autoCommit = !apr.Contains(noAutoCommitFlag) + config.allowCleartextPasswords = apr.Contains(allowCleartextPasswordsFlag) if connStr, ok := apr.GetValue(goldenMysqlConn); ok { cli.Println(connStr) - serverConfig.withGoldenMysqlConnectionString(connStr) + config.withGoldenMysqlConnectionString(connStr) } - return serverConfig, nil + return config, nil } // getYAMLServerConfig returns server config variables with values defined in yaml file. diff --git a/go/cmd/dolt/commands/utils.go b/go/cmd/dolt/commands/utils.go index 0b5c72dab5e..6be511f476d 100644 --- a/go/cmd/dolt/commands/utils.go +++ b/go/cmd/dolt/commands/utils.go @@ -16,10 +16,13 @@ package commands import ( "context" + "crypto/sha1" "fmt" + "net" "path/filepath" "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/mysql_db" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands/engine" @@ -84,7 +87,10 @@ func NewArgFreeCliContext(ctx context.Context, dEnv *env.DoltEnv) (cli.CliContex return nil, errhand.VerboseErrorFromError(err) } - lateBind, verr := BuildSqlEngineQueryist(ctx, dEnv.FS, mrEnv, argparser.NewEmptyResults()) + emptyArgs := argparser.NewEmptyResults() + emptyArgs, creds, _ := cli.BuildUserPasswordPrompt(emptyArgs) + lateBind, verr := BuildSqlEngineQueryist(ctx, dEnv.FS, mrEnv, creds, emptyArgs) + if err != nil { return nil, verr } @@ -93,17 +99,11 @@ func NewArgFreeCliContext(ctx context.Context, dEnv *env.DoltEnv) (cli.CliContex // BuildSqlEngineQueryist Utility function to build a local SQLEngine for use interacting with data on disk using // SQL queries. ctx, cwdFS, mrEnv, and apr must all be non-nil. -func BuildSqlEngineQueryist(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.MultiRepoEnv, apr *argparser.ArgParseResults) (cli.LateBindQueryist, errhand.VerboseError) { - if ctx == nil || cwdFS == nil || mrEnv == nil || apr == nil { +func BuildSqlEngineQueryist(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.MultiRepoEnv, creds *cli.UserPassword, apr *argparser.ArgParseResults) (cli.LateBindQueryist, errhand.VerboseError) { + if ctx == nil || cwdFS == nil || mrEnv == nil || creds == nil || apr == nil { errhand.VerboseErrorFromError(fmt.Errorf("Invariant violated. Nil argument provided to BuildSqlEngineQueryist")) } - // Retrieve username and password from command line, if provided - username := DefaultUser - if user, ok := apr.GetValue(UserFlag); ok { - username = user - } - // We want to know if the user provided us the data-dir flag, but we want to use the abs value used to // create the DoltEnv. This is a little messy. dataDir, dataDirGiven := apr.GetValue(DataDirFlag) @@ -189,7 +189,7 @@ func BuildSqlEngineQueryist(ctx context.Context, cwdFS filesys.Filesys, mrEnv *e database = mrEnv.GetFirstDatabase() } - binder, err := newLateBindingEngine(cfgDirPath, privsFp, branchControlFilePath, username, database, mrEnv) + binder, err := newLateBindingEngine(cfgDirPath, privsFp, branchControlFilePath, creds, database, mrEnv) if err != nil { return nil, errhand.VerboseErrorFromError(err) } @@ -201,7 +201,7 @@ func newLateBindingEngine( cfgDirPath string, privsFp string, branchControlFilePath string, - username string, + creds *cli.UserPassword, database string, mrEnv *env.MultiRepoEnv, ) (cli.LateBindQueryist, error) { @@ -210,7 +210,8 @@ func newLateBindingEngine( DoltCfgDirPath: cfgDirPath, PrivFilePath: privsFp, BranchCtrlFilePath: branchControlFilePath, - ServerUser: username, + ServerUser: creds.Username, + ServerPass: creds.Password, ServerHost: "localhost", Autocommit: true, } @@ -224,6 +225,7 @@ func newLateBindingEngine( if err != nil { return nil, nil, nil, err } + sqlCtx, err := se.NewDefaultContext(ctx2) if err != nil { return nil, nil, nil, err @@ -233,15 +235,45 @@ func newLateBindingEngine( // database set when you begin using them. sqlCtx.SetCurrentDatabase(database) - // Add specified user as new superuser, if it doesn't already exist - if user := se.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb.GetUser(config.ServerUser, config.ServerHost, false); user == nil { - se.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb.AddSuperUser(config.ServerUser, config.ServerHost, config.ServerPass) + rawDb := se.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb + salt, err := rawDb.Salt() + if err != nil { + return nil, nil, nil, err + } + + var dbUser string + if creds.Specified { + dbUser = creds.Username + + // When running in local mode, we want to attempt respect the user/pwd they provided. If they didn't provide + // one, we'll give then super user privs. Respecting the user/pwd is not a security stance - it's there + // to enable testing of application settings. + + authResponse := buildAuthResponse(salt, config.ServerPass) + + err := passwordValidate(rawDb, salt, dbUser, authResponse) + if err != nil { + return nil, nil, nil, err + } + + } else { + dbUser = DefaultUser + user := rawDb.GetUser(dbUser, config.ServerHost, false) + if user != nil { + // Want to ensure that the user has an empty password. If it has a password, we'll error + err := passwordValidate(rawDb, salt, dbUser, nil) + if err != nil { + return nil, nil, nil, err + } + } + + // If the user doesn't exist, we'll create it with superuser privs. + rawDb.AddSuperUser(dbUser, config.ServerHost, "") } // Set client to specified user - sqlCtx.Session.SetClient(sql.Client{User: config.ServerUser, Address: config.ServerHost, Capabilities: 0}) + sqlCtx.Session.SetClient(sql.Client{User: dbUser, Address: config.ServerHost, Capabilities: 0}) return se, sqlCtx, func() { se.Close() }, nil - } return lateBinder, nil @@ -280,3 +312,54 @@ func getActiveBranchName(sqlCtx *sql.Context, queryEngine cli.Queryist) (string, } return branchName, nil } + +// passwordValidate validates the password for the given user. This is a helper function around ValidateHash. Returns +// nil if the user is authenticated, an error otherwise. +func passwordValidate(rawDb *mysql_db.MySQLDb, salt []byte, user string, authResponse []byte) error { + // The port is meaningless here. It's going to be stripped in the ValidateHash function + addr, _ := net.ResolveTCPAddr("tcp", "localhost:3306") + + authenticated, err := rawDb.ValidateHash(salt, user, authResponse, addr) + if err != nil { + return err + } + if authenticated == nil { + // Shouldn't happen - err above should happen instead. But just in case... + return fmt.Errorf("unable to authenticate user %s", user) + } + return nil +} + +// buildAuthResponse takes the user password and server salt to construct an authResponse. This is the client +// side logic of the mysql_native_password authentication protocol. +func buildAuthResponse(salt []byte, password string) []byte { + if len(password) == 0 { + return nil + } + + // Final goal is to get to this: + // XOR(SHA(password), SHA(salt, SHA(SHA(password)))) + + crypt := sha1.New() + crypt.Write([]byte(password)) + shaPwd := crypt.Sum(nil) + + crypt.Reset() + crypt.Write(shaPwd) + // This is the value stored in the Database in the mysql.user table in the authentication_string column when + // the plugin is set to mysql_native_password. + shaShaPwd := crypt.Sum(nil) + + // Using salt and shaShaPwd (both of which the server knows) we execute an XOR with shaPwd. This means the server + // can XOR the result of this with shaShaPwd to get shaPwd. Then the server takes the sha of that value and validates + // it's what it has stored in the database. + crypt.Reset() + crypt.Write(salt) + crypt.Write(shaShaPwd) + scramble := crypt.Sum(nil) + for i := range shaPwd { + shaPwd[i] ^= scramble[i] + } + + return shaPwd +} diff --git a/go/cmd/dolt/dolt.go b/go/cmd/dolt/dolt.go index cc45cf99acc..2f4d4852225 100644 --- a/go/cmd/dolt/dolt.go +++ b/go/cmd/dolt/dolt.go @@ -521,7 +521,16 @@ The sql subcommand is currently the only command that uses these flags. All othe var cliCtx cli.CliContext = nil if initCliContext(subcommandName) { - lateBind, err := buildLateBinder(ctx, dEnv.FS, mrEnv, apr, subcommandName, verboseEngineSetup) + // validate that --user and --password are set appropriately. + aprAlt, creds, err := cli.BuildUserPasswordPrompt(apr) + apr = aprAlt + if err != nil { + cli.PrintErrln(color.RedString("Failed to parse credentials: %v", err)) + return 1 + } + + lateBind, err := buildLateBinder(ctx, dEnv.FS, mrEnv, creds, apr, subcommandName, verboseEngineSetup) + if err != nil { cli.PrintErrln(color.RedString("Failure to Load SQL Engine: %v", err)) return 1 @@ -554,7 +563,7 @@ The sql subcommand is currently the only command that uses these flags. All othe return res } -func buildLateBinder(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.MultiRepoEnv, apr *argparser.ArgParseResults, subcommandName string, verbose bool) (cli.LateBindQueryist, error) { +func buildLateBinder(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.MultiRepoEnv, creds *cli.UserPassword, apr *argparser.ArgParseResults, subcommandName string, verbose bool) (cli.LateBindQueryist, error) { var targetEnv *env.DoltEnv = nil @@ -572,7 +581,7 @@ func buildLateBinder(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.Mult targetEnv = mrEnv.GetEnv(useDb) } - // There is no target environment detected. This is allowed for a small number of command. + // There is no target environment detected. This is allowed for a small number of commands. // We don't expect that number to grow, so we list them here. // It's also allowed when --help is passed. // So we defer the error until the caller tries to use the cli.LateBindQueryist @@ -593,14 +602,20 @@ func buildLateBinder(ctx context.Context, cwdFS filesys.Filesys, mrEnv *env.Mult if verbose { cli.Println("verbose: starting remote mode") } - return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, apr, lock.Port, useDb) + + if !creds.Specified { + creds = &cli.UserPassword{Username: sqlserver.LocalConnectionUser, Password: lock.Secret, Specified: false} + } + + return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, lock.Port, useDb) } } if verbose { cli.Println("verbose: starting local mode") } - return commands.BuildSqlEngineQueryist(ctx, cwdFS, mrEnv, apr) + + return commands.BuildSqlEngineQueryist(ctx, cwdFS, mrEnv, creds, apr) } // doc is currently used only when a `initCliContext` command is specified. This will include all commands in time, @@ -662,7 +677,8 @@ func interceptSendMetrics(ctx context.Context, args []string) (bool, int) { func buildGlobalArgs() *argparser.ArgParser { ap := argparser.NewArgParserWithVariableArgs("dolt") - ap.SupportsString(commands.UserFlag, "u", "user", fmt.Sprintf("Defines the local superuser (defaults to `%v`). If the specified user exists, will take on permissions of that user.", commands.DefaultUser)) + ap.SupportsString(cli.UserFlag, "u", "user", fmt.Sprintf("Defines the local superuser (defaults to `%v`). If the specified user exists, will take on permissions of that user.", commands.DefaultUser)) + ap.SupportsString(cli.PasswordFlag, "p", "password", "Defines the password for the user. Defaults to empty string.") ap.SupportsString(commands.DataDirFlag, "", "directory", "Defines a directory whose subdirectories should all be dolt data repositories accessible as independent databases within. Defaults to the current directory.") ap.SupportsString(commands.CfgDirFlag, "", "directory", "Defines a directory that contains configuration files for dolt. Defaults to `$data-dir/.doltcfg`. Will only be created if there is a change to configuration settings.") ap.SupportsString(commands.PrivsFilePathFlag, "", "privilege file", "Path to a file to load and store users and grants. Defaults to `$doltcfg-dir/privileges.db`. Will only be created if there is a change to privileges.") diff --git a/go/libraries/utils/argparser/args_test.go b/go/libraries/utils/argparser/args_test.go index 588c27012dc..bbbb86a0bde 100644 --- a/go/libraries/utils/argparser/args_test.go +++ b/go/libraries/utils/argparser/args_test.go @@ -280,3 +280,46 @@ func TestValidation(t *testing.T) { t.Error("Arg list issues") } } + +func TestDropValue(t *testing.T) { + ap := NewArgParserWithVariableArgs("test") + + ap.SupportsString("string", "", "string_value", "A string") + ap.SupportsFlag("flag", "", "A flag") + + apr, err := ap.Parse([]string{"--string", "str", "--flag", "1234"}) + if err != nil { + t.Fatal(err.Error()) + } + + newApr1 := apr.DropValue("string") + require.NotEqualf(t, apr, newApr1, "Original value and new value are equal") + + _, hasVal := newApr1.GetValue("string") + if hasVal { + t.Error("DropValue failed to drop string") + } + _, hasVal = newApr1.GetValue("flag") + if !hasVal { + t.Error("DropValue dropped the wrong value") + } + if newApr1.NArg() != 1 || newApr1.Arg(0) != "1234" { + t.Error("DropValue didn't preserve args") + } + + newApr2 := apr.DropValue("flag") + require.NotEqualf(t, apr, newApr2, "DropValue failes to drop flag") + + _, hasVal = newApr2.GetValue("string") + if !hasVal { + t.Error("DropValue dropped the wrong value") + } + _, hasVal = newApr2.GetValue("flag") + if hasVal { + t.Error("DropValue failed to drop flag") + } + if newApr2.NArg() != 1 || newApr2.Arg(0) != "1234" { + t.Error("DropValue didn't preserve args") + } + +} diff --git a/go/libraries/utils/argparser/results.go b/go/libraries/utils/argparser/results.go index 9ec12c9f4a4..d914c965ed7 100644 --- a/go/libraries/utils/argparser/results.go +++ b/go/libraries/utils/argparser/results.go @@ -121,6 +121,23 @@ func (res *ArgParseResults) GetValues(names ...string) map[string]string { return vals } +// DropValue removes the value for the given name from the results. A new ArgParseResults object is returned without the +// names value. If the value is not present in the results then the original results object is returned. +func (res *ArgParseResults) DropValue(name string) *ArgParseResults { + if _, ok := res.options[name]; !ok { + return res + } + + newNamedArgs := make(map[string]string, len(res.options)-1) + for flag, val := range res.options { + if flag != name { + newNamedArgs[flag] = val + } + } + + return &ArgParseResults{newNamedArgs, res.Args, res.parser} +} + func (res *ArgParseResults) MustGetValue(name string) string { val, ok := res.options[name] diff --git a/integration-tests/bats/helper/common.bash b/integration-tests/bats/helper/common.bash index c252f2ecceb..af4e850f17e 100644 --- a/integration-tests/bats/helper/common.bash +++ b/integration-tests/bats/helper/common.bash @@ -59,6 +59,19 @@ setup_no_dolt_init() { if [ -z "$DOLT_TEST_RETRIES" ]; then export BATS_TEST_RETRIES="$DOLT_TEST_RETRIES" fi + + # Our tests use a mix of root and dolt users, and the CLI + # commands which authenticate will block for user input if we don't + # set DOLT_CLI_PASSWORD - so we set it to the empty string by default. + # The DOLT_SILENCE_USER_REQ_FOR_TESTING environment variable is to skip + # a check which errors out if a password is presented but no user is specified. + # The combination of these two flags allows us to avoid altering hundreds + # of existing tests which predate the authentication restrictions added + # during the cli -> sql migration. + export DOLT_CLI_PASSWORD="" + export DOLT_SILENCE_USER_REQ_FOR_TESTING="Y" + # Temporary Flag: https://github.com/dolthub/dolt/issues/6239 + export DOLT_ENABLE_LOCAL_USER_FOR_ALL_HOSTS="Y" } assert_feature_version() { diff --git a/integration-tests/bats/import-mysqldump.bats b/integration-tests/bats/import-mysqldump.bats index 683ddc47798..6154efdcde2 100644 --- a/integration-tests/bats/import-mysqldump.bats +++ b/integration-tests/bats/import-mysqldump.bats @@ -2,11 +2,7 @@ load $BATS_TEST_DIRNAME/helper/common.bash setup() { - REPO_NAME="dolt_repo_$$" - mkdir $REPO_NAME - cd $REPO_NAME - - dolt init + setup_common } teardown() { diff --git a/integration-tests/bats/sql-local-remote.bats b/integration-tests/bats/sql-local-remote.bats index f8371902877..a8c3a350cad 100644 --- a/integration-tests/bats/sql-local-remote.bats +++ b/integration-tests/bats/sql-local-remote.bats @@ -28,6 +28,8 @@ setup() { skip "This test tests remote connections directly, SQL_ENGINE is not needed." fi setup_no_dolt_init + unset DOLT_CLI_PASSWORD + unset DOLT_SILENCE_USER_REQ_FOR_TESTING make_repo defaultDB make_repo altDB } @@ -48,7 +50,7 @@ get_staged_tables() { @test "sql-local-remote: test switch between server/no server" { start_sql_server defaultDB - run dolt --verbose-engine-setup --user dolt sql -q "show databases" + run dolt --verbose-engine-setup --user dolt --password "" sql -q "show databases" [ "$status" -eq 0 ] || false [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "defaultDB" ]] || false @@ -56,7 +58,7 @@ get_staged_tables() { stop_sql_server 1 - run dolt --verbose-engine-setup sql -q "show databases" + run dolt --verbose-engine-setup sql -q "show databases" [ "$status" -eq 0 ] || false [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "defaultDB" ]] || false @@ -70,34 +72,34 @@ get_staged_tables() { mkdir someplace_else cd someplace_else - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --use-db altDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" --use-db altDB sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --use-db defaultDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" --use-db defaultDB sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "defaultDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false stop_sql_server 1 - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --use-db altDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" --use-db altDB sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --use-db defaultDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" --use-db defaultDB sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "defaultDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR" --user dolt --password "" sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false @@ -111,23 +113,23 @@ get_staged_tables() { mkdir -p someplace_new/fun cd someplace_new/fun - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --password "" sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --use-db defaultDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --password "" --use-db defaultDB sql -q "show tables" [ "$status" -eq 1 ] [[ "$output" =~ "defaultDB does not exist" ]] || false stop_sql_server 1 - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --password "" sql -q "show tables" [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "altDB_tbl" ]] || false - run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --use-db defaultDB sql -q "show tables" + run dolt --verbose-engine-setup --data-dir="$ROOT_DIR/altDB" --user dolt --password "" --use-db defaultDB sql -q "show tables" [ "$status" -eq 1 ] [[ "$output" =~ "defaultDB does not exist" ]] || false } @@ -144,7 +146,7 @@ get_staged_tables() { cd .. start_sql_server altDB - run dolt --user dolt blame test + run dolt --user dolt --password "" blame test [ "$status" -eq 0 ] export out="$output" stop_sql_server 1 @@ -153,15 +155,16 @@ get_staged_tables() { [ "$status" -eq 0 ] [[ "$output" = $out ]] || false } + @test "sql-local-remote: verify simple dolt add behavior." { start_sql_server altDB cd altDB - run dolt --verbose-engine-setup --user dolt sql -q "create table testtable (pk int PRIMARY KEY)" + run dolt --verbose-engine-setup --user dolt --password "" sql -q "create table testtable (pk int PRIMARY KEY)" [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false - run dolt --verbose-engine-setup --user dolt add . + run dolt --verbose-engine-setup --user dolt --password "" add . [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false @@ -175,7 +178,7 @@ get_staged_tables() { @test "sql-local-remote: test 'status' and switch between server/no server" { start_sql_server defaultDB - run dolt --user dolt status + run dolt --user dolt --password "" status [ "$status" -eq 0 ] || false [[ "$output" =~ "On branch main" ]] || false [[ "$output" =~ "Changes to be committed:" ]] || false @@ -191,7 +194,7 @@ get_staged_tables() { ! [[ "$output" =~ " new table: generated_foo" ]] || false remoteOutput=$output - run dolt --user dolt status --ignored + run dolt --user dolt --password "" status --ignored [ "$status" -eq 0 ] || false [[ "$output" =~ "On branch main" ]] || false [[ "$output" =~ "Changes to be committed:" ]] || false @@ -215,7 +218,7 @@ get_staged_tables() { [ "$status" -eq 0 ] || false localOutput=$output - run dolt --user dolt status --ignored + run dolt --user dolt --password "" status --ignored [ "$status" -eq 0 ] || false localIgnoredOutput=$output @@ -234,7 +237,7 @@ get_staged_tables() { start_sql_server altDB - run dolt --verbose-engine-setup --user dolt commit -m "committing remotely" + run dolt --verbose-engine-setup commit -m "committing remotely" [ "$status" -eq 0 ] [[ "$output" =~ "committing remotely" ]] || false @@ -259,16 +262,15 @@ get_staged_tables() { [[ "$output" =~ "committing locally" ]] || false } - @test "sql-local-remote: verify simple dolt branch behavior." { start_sql_server altDB cd altDB - run dolt --verbose-engine-setup --user dolt branch b1 + run dolt --verbose-engine-setup --user dolt --password "" branch b1 [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false - run dolt --verbose-engine-setup --user dolt branch + run dolt --verbose-engine-setup --user dolt --password "" branch [ "$status" -eq 0 ] [[ "$output" =~ "starting remote mode" ]] || false [[ "$output" =~ "main" ]] || false @@ -276,13 +278,164 @@ get_staged_tables() { stop_sql_server 1 - run dolt --verbose-engine-setup --user dolt branch b2 + run dolt --verbose-engine-setup --user dolt --password "" branch b2 [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false - run dolt --verbose-engine-setup --user dolt branch + run dolt --verbose-engine-setup --user dolt --password "" branch [ "$status" -eq 0 ] [[ "$output" =~ "starting local mode" ]] || false [[ "$output" =~ "main" ]] || false [[ "$output" =~ "b2" ]] || false } + +@test "sql-local-remote: check that the --password argument is used when talking to a server and ignored with local" { + start_sql_server altDb + + dolt --user dolt --password "" sql -q "CREATE USER 'joe'@'%' IDENTIFIED BY 'joe123'; GRANT ALL PRIVILEGES ON defaultDb.* TO 'joe'@'%' WITH GRANT OPTION;"; + + run dolt --verbose-engine-setup --user joe --password "badpwd" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "starting remote mode" ]] || false + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + run dolt --user joe --password "joe123" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'joe'@'%' to database 'altDB'" ]] || false + + run dolt --verbose-engine-setup --user joe --password "joe123" --use-db defaultDB sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "defaultDB_tbl" ]] || false + + # Empty Password should work since we started the server with the 'dolt' user with no pwd. + run dolt --verbose-engine-setup --user dolt --password "" sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "starting remote mode" ]] || false + [[ "$output" =~ "altDB_tbl" ]] || false + + stop_sql_server 1 + + run dolt --verbose-engine-setup --user joe --password failnow sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "starting local mode" ]] || false + + # altDB is not accessable to joe + run dolt --verbose-engine-setup --user joe --password "joe123" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + run dolt --verbose-engine-setup --user joe --password "joe123" --use-db defaultDB sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "defaultDB_tbl" ]] || false + + # Get access denied for a failed login (bad pwd) + run dolt --verbose-engine-setup --user joe --password failalways sql -q "SELECT user, host FROM mysql.user" + [ "$status" -eq 1 ] + [[ "$output" =~ "starting local mode" ]] || false + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + # Get an permission error when attempting to access forbidden info as an authenticated user. + run dolt --verbose-engine-setup --user joe --password "joe123" sql -q "SELECT user, host FROM mysql.user" + [ "$status" -eq 1 ] + [[ "$output" =~ "command denied to user 'joe'@'%'" ]] || false + + # Similar test to above, but will get different results because the dolt user doesn't exist (it was + # used to start sql-server + run dolt --user dolt --password "" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'dolt'" ]] || false +} + +@test "sql-local-remote: check that the DOLT_CLI_PASSWORD argument is used when talking to a server and ignored with local" { + start_sql_server altDb + + dolt --user dolt --password "" sql -q "CREATE USER 'joe'@'%' IDENTIFIED BY 'joe123'; GRANT ALL PRIVILEGES ON defaultDb.* TO 'joe'@'%' WITH GRANT OPTION;"; + + export DOLT_CLI_PASSWORD="badpwd" + run dolt --verbose-engine-setup --user joe sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "starting remote mode" ]] || false + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + export DOLT_CLI_PASSWORD="joe123" + run dolt --user joe sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'joe'@'%' to database 'altDB'" ]] || false + + export DOLT_CLI_PASSWORD="joe123" + run dolt --verbose-engine-setup --user joe --use-db defaultDB sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "defaultDB_tbl" ]] || false + + export DOLT_CLI_PASSWORD="" + run dolt --verbose-engine-setup --user dolt sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "starting remote mode" ]] || false + [[ "$output" =~ "altDB_tbl" ]] || false + + stop_sql_server 1 + + export DOLT_CLI_PASSWORD="badpwd" + run dolt --verbose-engine-setup --user joe sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "starting local mode" ]] || false + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + export DOLT_CLI_PASSWORD="joe123" + run dolt --user joe --password "joe123" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + run dolt --user joe --password "joe123" --use-db defaultDB sql -q "show tables" + [ "$status" -eq 0 ] + [[ "$output" =~ "defaultDB_tbl" ]] || false + + # Get access denied for a failed login (bad pwd) + export DOLT_CLI_PASSWORD="badpwd" + run dolt --user joe sql -q "SELECT user, host FROM mysql.user" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'joe'" ]] || false + + export DOLT_CLI_PASSWORD="joe123" + # Get an permission error when attempting to access forbidden info as an authenticated user. + run dolt --user joe sql -q "SELECT user, host FROM mysql.user" + [ "$status" -eq 1 ] + [[ "$output" =~ "command denied to user 'joe'@'%'" ]] || false + + export DOLT_CLI_PASSWORD="badpwd" + run dolt --user rambo --use-db defaultDB sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'rambo'" ]] || false + + export DOLT_CLI_PASSWORD="" + run dolt --user dolt sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "Access denied for user 'dolt'" ]] || false + + unset DOLT_CLI_PASSWORD +} + +@test "sql-local-remote: ensure passing only a password results in an error" { + export SQL_USER="root" + start_sql_server altDb + + run dolt --password "anything" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false + + export DOLT_CLI_PASSWORD="anything" + run dolt sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false + + stop_sql_server 1 + + run dolt --password "anything" sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false + + export DOLT_CLI_PASSWORD="anything" + run dolt sql -q "show tables" + [ "$status" -eq 1 ] + [[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false +} diff --git a/integration-tests/bats/sql-server.bats b/integration-tests/bats/sql-server.bats index e3ec1d28417..021fc04e1e6 100644 --- a/integration-tests/bats/sql-server.bats +++ b/integration-tests/bats/sql-server.bats @@ -1518,21 +1518,6 @@ data_dir: $DATA_DIR [ "$status" -eq 1 ] } -@test "sql-server: sql-server locks database to writes" { - cd repo2 - dolt sql -q "create table a (x int primary key)" - start_sql_server - - run dolt --verbose-engine-setup sql -q "create table b (x int primary key)" - [ "$status" -eq 1 ] - [[ "$output" =~ "Error connecting to remote database" ]] || false - [[ "$output" =~ "User not found 'root'" ]] || false - - run dolt --verbose-engine-setup --user dolt sql -q "create table b (x int primary key)" - [ "$status" -eq 0 ] - [[ "$output" =~ "starting remote mode" ]] || false -} - @test "sql-server: start server without socket flag should set default socket path" { skiponwindows "unix socket is not available on Windows" cd repo2 @@ -1935,4 +1920,4 @@ behavior: [[ "$output" =~ "newOther" ]] || false [[ "$output" =~ "main" ]] || false [[ ! "$output" =~ "other" ]] || false -} \ No newline at end of file +} diff --git a/integration-tests/bats/sql-shell.bats b/integration-tests/bats/sql-shell.bats index 8c1de665541..8be2eccef56 100644 --- a/integration-tests/bats/sql-shell.bats +++ b/integration-tests/bats/sql-shell.bats @@ -21,28 +21,6 @@ teardown() { teardown_common } -@test "sql-shell: --user option changes superuser" { - # remove config - rm -rf .doltcfg - - # default is root@localhost - run dolt sql <<< "select user, host from mysql.user" - [ "$status" -eq 0 ] - [[ "$output" =~ "root" ]] || false - ! [[ "$output" =~ "dolt" ]] || false - [[ "$output" =~ "localhost" ]] || false - - # make it dolt@localhost - run dolt --user=dolt sql <<< "select user, host from mysql.user" - [ "$status" -eq 0 ] - ! [[ "$output" =~ "root" ]] || false - [[ "$output" =~ "dolt" ]] || false - [[ "$output" =~ "localhost" ]] || false - - # remove config - rm -rf .doltcfg -} - @test "sql-shell: use user without privileges, and no superuser created" { rm -rf .doltcfg diff --git a/integration-tests/bats/sql.bats b/integration-tests/bats/sql.bats index 41f7f8134ba..44644fa390f 100755 --- a/integration-tests/bats/sql.bats +++ b/integration-tests/bats/sql.bats @@ -39,28 +39,6 @@ teardown() { teardown_common } -@test "sql: --user option changes superuser" { - # remove config - rm -rf .doltcfg - - # default is root@localhost - run dolt sql -q "select user, host from mysql.user" - [ "$status" -eq 0 ] - [[ "$output" =~ "root" ]] || false - ! [[ "$output" =~ "dolt" ]] || false - [[ "$output" =~ "localhost" ]] || false - - # make it dolt@localhost - run dolt --user=dolt sql -q "select user, host from mysql.user" - [ "$status" -eq 0 ] - ! [[ "$output" =~ "root" ]] || false - [[ "$output" =~ "dolt" ]] || false - [[ "$output" =~ "localhost" ]] || false - - # remove config - rm -rf .doltcfg -} - @test "sql: --user don't create superuser if using an existing user" { rm -rf .doltcfg diff --git a/integration-tests/data-dump-loading-tests/sakila-data-dump-load.bats b/integration-tests/data-dump-loading-tests/sakila-data-dump-load.bats index 184b1862b24..9b9adf8985c 100644 --- a/integration-tests/data-dump-loading-tests/sakila-data-dump-load.bats +++ b/integration-tests/data-dump-loading-tests/sakila-data-dump-load.bats @@ -23,8 +23,11 @@ teardown() { run dolt ls [ "${#lines[@]}" -eq 17 ] + # triggers run dolt sql -q "select trigger_name from information_schema.triggers;" -r csv + [ "$status" -eq 0 ] + [ "$output" = "TRIGGER_NAME customer_create_date payment_date