From c17b4c4160973e74f3ea9b386699a8277d2f6740 Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Thu, 27 Sep 2018 09:44:46 -0600 Subject: [PATCH 1/5] expression: Change current_user match authed user --- expression/builtin_info.go | 3 +-- expression/builtin_info_test.go | 2 +- expression/integration_test.go | 2 +- privilege/privilege.go | 4 ++++ privilege/privileges/privileges.go | 6 ++++++ session/session.go | 8 ++++++-- util/auth/auth.go | 13 ++++++++++--- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/expression/builtin_info.go b/expression/builtin_info.go index b498e96f289e1..24ef57ced9fa6 100644 --- a/expression/builtin_info.go +++ b/expression/builtin_info.go @@ -154,8 +154,7 @@ func (b *builtinCurrentUserSig) evalString(row chunk.Row) (string, bool, error) if data == nil || data.User == nil { return "", true, errors.Errorf("Missing session variable when eval builtin") } - - return data.User.String(), false, nil + return data.User.MatchedIdentityString(), false, nil } type userFunctionClass struct { diff --git a/expression/builtin_info_test.go b/expression/builtin_info_test.go index c71ce15c88bd5..2f255f3d98e15 100644 --- a/expression/builtin_info_test.go +++ b/expression/builtin_info_test.go @@ -84,7 +84,7 @@ func (s *testEvaluatorSuite) TestCurrentUser(c *C) { defer testleak.AfterTest(c)() ctx := mock.NewContext() sessionVars := ctx.GetSessionVars() - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "localhost"} fc := funcs[ast.CurrentUser] f, err := fc.getFunction(ctx, nil) diff --git a/expression/integration_test.go b/expression/integration_test.go index 5e520fe93ada6..c9efaeb1b71db 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -2347,7 +2347,7 @@ func (s *testIntegrationSuite) TestInfoBuiltin(c *C) { // for current_user sessionVars := tk.Se.GetSessionVars() originUser := sessionVars.User - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "localhost"} result = tk.MustQuery("select current_user()") result.Check(testkit.Rows("root@localhost")) sessionVars.User = originUser diff --git a/privilege/privilege.go b/privilege/privilege.go index 34c210086c671..3ccf266b42f91 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -40,6 +40,10 @@ type Manager interface { // ConnectionVerification verifies user privilege for connection. ConnectionVerification(user, host string, auth, salt []byte) bool + // Returns the actual user+host from the privileges table, + // including any wildcard characters. + ConnectionMatchIdentity(user, host string) (string, string) + // DBIsVisible returns true is the database is visible to current user. DBIsVisible(db string) bool diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 2230a44d7be52..1973dcd10c195 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -116,6 +116,12 @@ func (p *UserPrivileges) ConnectionVerification(user, host string, authenticatio return true } +func (p *UserPrivileges) ConnectionMatchIdentity(user, host string) (string, string) { + mysqlPriv := p.Handle.Get() + record := mysqlPriv.connectionVerification(user, host) + return record.User, record.Host +} + // DBIsVisible implements the Manager interface. func (p *UserPrivileges) DBIsVisible(db string) bool { if SkipWithGrant { diff --git a/session/session.go b/session/session.go index 04a0d0cc78f22..ed881ffc61af4 100644 --- a/session/session.go +++ b/session/session.go @@ -999,6 +999,7 @@ func (s *session) Auth(user *auth.UserIdentity, authentication []byte, salt []by // Check IP. if pm.ConnectionVerification(user.Username, user.Hostname, authentication, salt) { + user.MatchedUsername, user.MatchedHostname = pm.ConnectionMatchIdentity(user.Username, user.Hostname) s.sessionVars.User = user return true } @@ -1006,9 +1007,12 @@ func (s *session) Auth(user *auth.UserIdentity, authentication []byte, salt []by // Check Hostname. for _, addr := range getHostByIP(user.Hostname) { if pm.ConnectionVerification(user.Username, addr, authentication, salt) { + u, h := pm.ConnectionMatchIdentity(user.Username, addr) s.sessionVars.User = &auth.UserIdentity{ - Username: user.Username, - Hostname: addr, + Username: user.Username, + Hostname: addr, + MatchedUsername: u, + MatchedHostname: h, } return true } diff --git a/util/auth/auth.go b/util/auth/auth.go index 0c60b7d67d892..2511cfde51289 100644 --- a/util/auth/auth.go +++ b/util/auth/auth.go @@ -25,9 +25,11 @@ import ( // UserIdentity represents username and hostname. type UserIdentity struct { - Username string - Hostname string - CurrentUser bool + Username string + Hostname string + CurrentUser bool + MatchedUsername string + MatchedHostname string } // String converts UserIdentity to the format user@host. @@ -36,6 +38,11 @@ func (user *UserIdentity) String() string { return fmt.Sprintf("%s@%s", user.Username, user.Hostname) } +func (user *UserIdentity) MatchedIdentityString() string { + // TODO: Escape username and hostname. + return fmt.Sprintf("%s@%s", user.MatchedUsername, user.MatchedHostname) +} + // CheckScrambledPassword check scrambled password received from client. // The new authentication is performed in following manner: // SERVER: public_seed=create_random_string() From a49b1a6e73e78306af2d557de5938ef3bd54d7f1 Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Thu, 27 Sep 2018 10:16:16 -0600 Subject: [PATCH 2/5] expression: fixed tests, handled skip-grant-tables --- expression/integration_test.go | 6 +++--- privilege/privileges/privileges.go | 13 ++++++++++--- util/auth/auth.go | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/expression/integration_test.go b/expression/integration_test.go index fd09bf80472b5..9bd42e03963b8 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -2347,13 +2347,13 @@ func (s *testIntegrationSuite) TestInfoBuiltin(c *C) { // for current_user sessionVars := tk.Se.GetSessionVars() originUser := sessionVars.User - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "localhost"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "127.0.%%"} result = tk.MustQuery("select current_user()") - result.Check(testkit.Rows("root@localhost")) + result.Check(testkit.Rows("root@127.0.%%")) sessionVars.User = originUser // for user - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "127.0.%%"} result = tk.MustQuery("select user()") result.Check(testkit.Rows("root@localhost")) sessionVars.User = originUser diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 1973dcd10c195..0582451bb1fee 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -116,10 +116,17 @@ func (p *UserPrivileges) ConnectionVerification(user, host string, authenticatio return true } +// ConnectionMatchIdentity return the user+host that matched in the priv cache func (p *UserPrivileges) ConnectionMatchIdentity(user, host string) (string, string) { - mysqlPriv := p.Handle.Get() - record := mysqlPriv.connectionVerification(user, host) - return record.User, record.Host + if SkipWithGrant { + // This is MySQL 5.7 compatible behavior + return "skip-grants user", "skip-grants host" + } + + mysqlPriv := p.Handle.Get() + record := mysqlPriv.connectionVerification(user, host) + return record.User, record.Host + } // DBIsVisible implements the Manager interface. diff --git a/util/auth/auth.go b/util/auth/auth.go index 2511cfde51289..7fbe6ace81108 100644 --- a/util/auth/auth.go +++ b/util/auth/auth.go @@ -38,6 +38,7 @@ func (user *UserIdentity) String() string { return fmt.Sprintf("%s@%s", user.Username, user.Hostname) } +// MatchedIdentityString retyrbs matched identity in user@host format func (user *UserIdentity) MatchedIdentityString() string { // TODO: Escape username and hostname. return fmt.Sprintf("%s@%s", user.MatchedUsername, user.MatchedHostname) From 8ebd919b65b482a9bca746b7e0bb02d5cc96b6e5 Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Thu, 27 Sep 2018 10:18:26 -0600 Subject: [PATCH 3/5] Removed out of date TODO! --- expression/builtin_info.go | 1 - 1 file changed, 1 deletion(-) diff --git a/expression/builtin_info.go b/expression/builtin_info.go index 24ef57ced9fa6..807b3c006d862 100644 --- a/expression/builtin_info.go +++ b/expression/builtin_info.go @@ -148,7 +148,6 @@ func (b *builtinCurrentUserSig) Clone() builtinFunc { // evalString evals a builtinCurrentUserSig. // See https://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_current-user -// TODO: The value of CURRENT_USER() can differ from the value of USER(). We will finish this after we support grant tables. func (b *builtinCurrentUserSig) evalString(row chunk.Row) (string, bool, error) { data := b.ctx.GetSessionVars() if data == nil || data.User == nil { From 87875020f752630376e76613969a6ca2924fa802 Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Thu, 27 Sep 2018 11:04:19 -0600 Subject: [PATCH 4/5] Spelling --- util/auth/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/auth/auth.go b/util/auth/auth.go index 7fbe6ace81108..725073f2b8374 100644 --- a/util/auth/auth.go +++ b/util/auth/auth.go @@ -38,7 +38,7 @@ func (user *UserIdentity) String() string { return fmt.Sprintf("%s@%s", user.Username, user.Hostname) } -// MatchedIdentityString retyrbs matched identity in user@host format +// MatchedIdentityString returns matched identity in user@host format func (user *UserIdentity) MatchedIdentityString() string { // TODO: Escape username and hostname. return fmt.Sprintf("%s@%s", user.MatchedUsername, user.MatchedHostname) From 1c49b76d7d6f99cb43ebfdb57d28aa051ece9c05 Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Sun, 14 Oct 2018 15:01:18 +0000 Subject: [PATCH 5/5] Address PR feedback --- expression/builtin_info.go | 2 +- expression/builtin_info_test.go | 2 +- expression/integration_test.go | 4 ++-- privilege/privilege.go | 6 +---- privilege/privileges/privileges.go | 38 +++++++++++++----------------- session/session.go | 17 ++++++------- util/auth/auth.go | 16 ++++++------- 7 files changed, 38 insertions(+), 47 deletions(-) diff --git a/expression/builtin_info.go b/expression/builtin_info.go index 807b3c006d862..4bb90b8e9901b 100644 --- a/expression/builtin_info.go +++ b/expression/builtin_info.go @@ -153,7 +153,7 @@ func (b *builtinCurrentUserSig) evalString(row chunk.Row) (string, bool, error) if data == nil || data.User == nil { return "", true, errors.Errorf("Missing session variable when eval builtin") } - return data.User.MatchedIdentityString(), false, nil + return data.User.AuthIdentityString(), false, nil } type userFunctionClass struct { diff --git a/expression/builtin_info_test.go b/expression/builtin_info_test.go index 2f255f3d98e15..c4bae7f784ca0 100644 --- a/expression/builtin_info_test.go +++ b/expression/builtin_info_test.go @@ -84,7 +84,7 @@ func (s *testEvaluatorSuite) TestCurrentUser(c *C) { defer testleak.AfterTest(c)() ctx := mock.NewContext() sessionVars := ctx.GetSessionVars() - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "localhost"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", AuthUsername: "root", AuthHostname: "localhost"} fc := funcs[ast.CurrentUser] f, err := fc.getFunction(ctx, nil) diff --git a/expression/integration_test.go b/expression/integration_test.go index 9bd42e03963b8..1dd1d35b7ead4 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -2347,13 +2347,13 @@ func (s *testIntegrationSuite) TestInfoBuiltin(c *C) { // for current_user sessionVars := tk.Se.GetSessionVars() originUser := sessionVars.User - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "127.0.%%"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", AuthUsername: "root", AuthHostname: "127.0.%%"} result = tk.MustQuery("select current_user()") result.Check(testkit.Rows("root@127.0.%%")) sessionVars.User = originUser // for user - sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", MatchedUsername: "root", MatchedHostname: "127.0.%%"} + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", AuthUsername: "root", AuthHostname: "127.0.%%"} result = tk.MustQuery("select user()") result.Check(testkit.Rows("root@localhost")) sessionVars.User = originUser diff --git a/privilege/privilege.go b/privilege/privilege.go index 3ccf266b42f91..e14b95914af84 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -38,11 +38,7 @@ type Manager interface { // this means any privilege would be OK. RequestVerification(db, table, column string, priv mysql.PrivilegeType) bool // ConnectionVerification verifies user privilege for connection. - ConnectionVerification(user, host string, auth, salt []byte) bool - - // Returns the actual user+host from the privileges table, - // including any wildcard characters. - ConnectionMatchIdentity(user, host string) (string, string) + ConnectionVerification(user, host string, auth, salt []byte) (string, string, bool) // DBIsVisible returns true is the database is visible to current user. DBIsVisible(db string) bool diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 0582451bb1fee..6105875910077 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -70,63 +70,57 @@ func (p *UserPrivileges) RequestVerification(db, table, column string, priv mysq } // ConnectionVerification implements the Manager interface. -func (p *UserPrivileges) ConnectionVerification(user, host string, authentication, salt []byte) bool { +func (p *UserPrivileges) ConnectionVerification(user, host string, authentication, salt []byte) (u string, h string, success bool) { + if SkipWithGrant { p.user = user p.host = host - return true + success = true + return } mysqlPriv := p.Handle.Get() record := mysqlPriv.connectionVerification(user, host) if record == nil { log.Errorf("Get user privilege record fail: user %v, host %v", user, host) - return false + return } + u = record.User + h = record.Host + pwd := record.Password if len(pwd) != 0 && len(pwd) != mysql.PWDHashLen+1 { log.Errorf("User [%s] password from SystemDB not like a sha1sum", user) - return false + return } // empty password if len(pwd) == 0 && len(authentication) == 0 { p.user = user p.host = host - return true + success = true + return } if len(pwd) == 0 || len(authentication) == 0 { - return false + return } hpwd, err := auth.DecodePassword(pwd) if err != nil { log.Errorf("Decode password string error %v", err) - return false + return } if !auth.CheckScrambledPassword(salt, hpwd, authentication) { - return false + return } p.user = user p.host = host - return true -} - -// ConnectionMatchIdentity return the user+host that matched in the priv cache -func (p *UserPrivileges) ConnectionMatchIdentity(user, host string) (string, string) { - if SkipWithGrant { - // This is MySQL 5.7 compatible behavior - return "skip-grants user", "skip-grants host" - } - - mysqlPriv := p.Handle.Get() - record := mysqlPriv.connectionVerification(user, host) - return record.User, record.Host - + success = true + return } // DBIsVisible implements the Manager interface. diff --git a/session/session.go b/session/session.go index 07bdf10e96f73..5757d51293f6e 100644 --- a/session/session.go +++ b/session/session.go @@ -999,21 +999,22 @@ func (s *session) Auth(user *auth.UserIdentity, authentication []byte, salt []by pm := privilege.GetPrivilegeManager(s) // Check IP. - if pm.ConnectionVerification(user.Username, user.Hostname, authentication, salt) { - user.MatchedUsername, user.MatchedHostname = pm.ConnectionMatchIdentity(user.Username, user.Hostname) + var success bool + user.AuthUsername, user.AuthHostname, success = pm.ConnectionVerification(user.Username, user.Hostname, authentication, salt) + if success { s.sessionVars.User = user return true } // Check Hostname. for _, addr := range getHostByIP(user.Hostname) { - if pm.ConnectionVerification(user.Username, addr, authentication, salt) { - u, h := pm.ConnectionMatchIdentity(user.Username, addr) + u, h, success := pm.ConnectionVerification(user.Username, addr, authentication, salt) + if success { s.sessionVars.User = &auth.UserIdentity{ - Username: user.Username, - Hostname: addr, - MatchedUsername: u, - MatchedHostname: h, + Username: user.Username, + Hostname: addr, + AuthUsername: u, + AuthHostname: h, } return true } diff --git a/util/auth/auth.go b/util/auth/auth.go index 725073f2b8374..74187d6e0efdb 100644 --- a/util/auth/auth.go +++ b/util/auth/auth.go @@ -25,11 +25,11 @@ import ( // UserIdentity represents username and hostname. type UserIdentity struct { - Username string - Hostname string - CurrentUser bool - MatchedUsername string - MatchedHostname string + Username string + Hostname string + CurrentUser bool + AuthUsername string // Username matched in privileges system + AuthHostname string // Match in privs system (i.e. could be a wildcard) } // String converts UserIdentity to the format user@host. @@ -38,10 +38,10 @@ func (user *UserIdentity) String() string { return fmt.Sprintf("%s@%s", user.Username, user.Hostname) } -// MatchedIdentityString returns matched identity in user@host format -func (user *UserIdentity) MatchedIdentityString() string { +// AuthIdentityString returns matched identity in user@host format +func (user *UserIdentity) AuthIdentityString() string { // TODO: Escape username and hostname. - return fmt.Sprintf("%s@%s", user.MatchedUsername, user.MatchedHostname) + return fmt.Sprintf("%s@%s", user.AuthUsername, user.AuthHostname) } // CheckScrambledPassword check scrambled password received from client.