Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

privilege/privileges: sort user records in privilege cache #7211

Merged
merged 8 commits into from
Aug 2, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enable-streaming = false
lower-case-table-names = 2

# Make "kill query" behavior compatible with MySQL. It's not recommend to
# turn on this option when TiDB server is behand a proxy.
# turn on this option when TiDB server is behind a proxy.
compatible-kill-query = false

[log]
Expand Down
98 changes: 90 additions & 8 deletions privilege/privileges/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package privileges

import (
"fmt"
"sort"
"strings"
"sync/atomic"
"time"
Expand Down Expand Up @@ -47,7 +48,8 @@ func computePrivMask(privs []mysql.PrivilegeType) mysql.PrivilegeType {
return mask
}

type userRecord struct {
// UserRecord is used to represent a user record in privilege cache.
type UserRecord struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe no need to make this public if want to test it.

maybe try this trick...https://github.com/lysu/tidb/blob/87ce884b2e0dc679554cb666b05f3347b3c72957/types/export_test.go#L17

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache_test.go use a different package, so this trick can't work.

Host string // max length 60, primary key
User string // max length 16, primary key
Password string // max length 41
Expand Down Expand Up @@ -103,7 +105,7 @@ type columnsPrivRecord struct {

// MySQLPrivilege is the in-memory cache of mysql privilege tables.
type MySQLPrivilege struct {
User []userRecord
User []UserRecord
DB []dbRecord
TablesPriv []tablesPrivRecord
ColumnsPriv []columnsPrivRecord
Expand Down Expand Up @@ -154,7 +156,87 @@ func noSuchTable(err error) bool {

// LoadUserTable loads the mysql.user table from database.
func (p *MySQLPrivilege) LoadUserTable(ctx sessionctx.Context) error {
return p.loadTable(ctx, "select Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Process_priv,Grant_priv,References_priv,Alter_priv,Show_db_priv,Super_priv,Execute_priv,Index_priv,Create_user_priv,Trigger_priv from mysql.user order by host, user;", p.decodeUserTableRow)
err := p.loadTable(ctx, "select Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Process_priv,Grant_priv,References_priv,Alter_priv,Show_db_priv,Super_priv,Execute_priv,Index_priv,Create_user_priv,Trigger_priv from mysql.user;", p.decodeUserTableRow)
if err != nil {
return errors.Trace(err)
}
// See https://dev.mysql.com/doc/refman/8.0/en/connection-access.html
// When multiple matches are possible, the server must determine which of them to use. It resolves this issue as follows:
// 1. Whenever the server reads the user table into memory, it sorts the rows.
// 2. When a client attempts to connect, the server looks through the rows in sorted order.
// 3. The server uses the first row that matches the client host name and user name.
// The server uses sorting rules that order rows with the most-specific Host values first.
p.SortUserTable()
return nil
}

type sortedUserRecord []UserRecord

func (s sortedUserRecord) Len() int {
return len([]UserRecord(s))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return len(s) works

}

func (s sortedUserRecord) Less(i, j int) bool {
x := ([]UserRecord(s))[i]
y := ([]UserRecord(s))[j]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

	x := s[i]
	y := s[j]

works too~


// Compare two item by user's host first.
c1 := compareHost(x.Host, y.Host)
if c1 < 0 {
return true
}
if c1 > 0 {
return false
}

// Then, compare item by user's name value.
return x.User < y.User
}

// compareHost compares two host string using some special rules, return value 1, 0, -1 means > = <.
func compareHost(x, y string) int {
// The more-specific, the smaller it is.
// The pattern '%' means “any host” and is least specific.
if y == `%` {
if x == `%` {
return 0
}
return -1
}

// The empty string '' also means “any host” but sorts after '%'.
if y == "" {
if x == "" {
return 0
}
return -1
}

if strings.HasSuffix(y, `%`) {
if !strings.HasSuffix(x, `%`) {
return -1
}
return 0
}

// For other case, the order is nondeterministic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

192.168.% is larger than 192.168.199.%.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a check by length.

switch x < y {
case true:
return -1
case false:
return 1
}
return 0
}

func (s sortedUserRecord) Swap(i, j int) {
s1 := []UserRecord(s)
s1[i], s1[j] = s1[j], s1[i]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto~

}

// SortUserTable sorts p.User in the MySQLPrivilege struct.
func (p MySQLPrivilege) SortUserTable() {
sort.Sort(sortedUserRecord(p.User))
}

// LoadDBTable loads the mysql.db table from database.
Expand Down Expand Up @@ -206,7 +288,7 @@ func (p *MySQLPrivilege) loadTable(sctx sessionctx.Context, sql string,
}

func (p *MySQLPrivilege) decodeUserTableRow(row chunk.Row, fs []*ast.ResultField) error {
var value userRecord
var value UserRecord
for i, f := range fs {
switch {
case f.ColumnAsName.L == "user":
Expand Down Expand Up @@ -326,7 +408,7 @@ func decodeSetToPrivilege(s types.Set) mysql.PrivilegeType {
return ret
}

func (record *userRecord) match(user, host string) bool {
func (record *UserRecord) match(user, host string) bool {
return record.User == user && patternMatch(host, record.patChars, record.patTypes)
}

Expand Down Expand Up @@ -355,7 +437,7 @@ func patternMatch(str string, patChars, patTypes []byte) bool {
}

// connectionVerification verifies the connection have access to TiDB server.
func (p *MySQLPrivilege) connectionVerification(user, host string) *userRecord {
func (p *MySQLPrivilege) connectionVerification(user, host string) *UserRecord {
for i := 0; i < len(p.User); i++ {
record := &p.User[i]
if record.match(user, host) {
Expand All @@ -365,7 +447,7 @@ func (p *MySQLPrivilege) connectionVerification(user, host string) *userRecord {
return nil
}

func (p *MySQLPrivilege) matchUser(user, host string) *userRecord {
func (p *MySQLPrivilege) matchUser(user, host string) *UserRecord {
for i := 0; i < len(p.User); i++ {
record := &p.User[i]
if record.match(user, host) {
Expand Down Expand Up @@ -557,7 +639,7 @@ func (p *MySQLPrivilege) UserPrivilegesTable() [][]types.Datum {
return rows
}

func appendUserPrivilegesTableRow(rows [][]types.Datum, user userRecord) [][]types.Datum {
func appendUserPrivilegesTableRow(rows [][]types.Datum, user UserRecord) [][]types.Datum {
var isGrantable string
if user.Privileges&mysql.GrantPriv > 0 {
isGrantable = "YES"
Expand Down
37 changes: 37 additions & 0 deletions privilege/privileges/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,40 @@ func (s *testCacheSuite) TestAbnormalMySQLTable(c *C) {
err = p.LoadAll(se)
c.Assert(err, IsNil)
}

func (s *testCacheSuite) TestSortUserTable(c *C) {
var p privileges.MySQLPrivilege
p.User = []privileges.UserRecord{
{Host: `%`, User: "root"},
{Host: `%`, User: "jeffrey"},
{Host: "localhost", User: "root"},
{Host: "localhost", User: ""},
}
p.SortUserTable()
result := []privileges.UserRecord{
{Host: "localhost", User: "root"},
{Host: "localhost", User: ""},
{Host: `%`, User: "jeffrey"},
{Host: `%`, User: "root"},
}
checkUserRecord(p.User, result, c)

p.User = []privileges.UserRecord{
{Host: `%`, User: "jeffrey"},
{Host: "h1.example.net", User: ""},
}
p.SortUserTable()
result = []privileges.UserRecord{
{Host: "h1.example.net", User: ""},
{Host: `%`, User: "jeffrey"},
}
checkUserRecord(p.User, result, c)
}

func checkUserRecord(x, y []privileges.UserRecord, c *C) {
c.Assert(len(x), Equals, len(y))
for i := 0; i < len(x); i++ {
c.Assert(x[i].User, Equals, y[i].User)
c.Assert(x[i].Host, Equals, y[i].Host)
}
}