Skip to content

Commit bcbb47a

Browse files
committed
*: support username prefix for starter mode
1 parent e96b621 commit bcbb47a

17 files changed

Lines changed: 492 additions & 29 deletions

File tree

errors.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,11 @@ error = '''
976976
Cannot add or update a child row: a foreign key constraint fails (%.192s)
977977
'''
978978

979+
["ddl:1468"]
980+
error = '''
981+
User name must start with `%s.` (use `%s.%s` instead)
982+
'''
983+
979984
["ddl:1470"]
980985
error = '''
981986
String '%-.70s' is too long for %s (should be no longer than %d)
@@ -3101,6 +3106,11 @@ error = '''
31013106
You must reset your password using ALTER USER statement before executing this statement
31023107
'''
31033108

3109+
["server:20003"]
3110+
error = '''
3111+
User name prefix does not match the assigned keyspace.
3112+
'''
3113+
31043114
["server:3159"]
31053115
error = '''
31063116
Connections using insecure transport are prohibited while --require_secure_transport=ON.

pkg/errno/errcode.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ const (
861861
ErrBadUser = 3162
862862
ErrUserAlreadyExists = 3163
863863
ErrInvalidJSONPathArrayCell = 3165
864+
ErrUserPrefixMismatch = 20003
864865
ErrInvalidEncryptionOption = 3184
865866
ErrTooLongValueForType = 3505
866867
ErrPKIndexCantBeInvisible = 3522

pkg/errno/errname.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
864864
ErrBadUser: mysql.Message("User %s does not exist.", nil),
865865
ErrUserAlreadyExists: mysql.Message("User %s already exists.", nil),
866866
ErrInvalidJSONPathArrayCell: mysql.Message("A path expression is not a path to a cell in an array.", nil),
867+
ErrUserPrefixMismatch: mysql.Message("User name prefix does not match the assigned keyspace.", nil),
867868
ErrInvalidEncryptionOption: mysql.Message("Invalid encryption option.", nil),
868869
ErrTooLongValueForType: mysql.Message("Too long enumeration/set value for column %s.", nil),
869870
ErrPKIndexCantBeInvisible: mysql.Message("A primary key index cannot be invisible", nil),

pkg/executor/grant.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (e *GrantExec) Next(ctx context.Context, _ *chunk.Chunk) error {
174174
user.User.Username = e.Ctx().GetSessionVars().User.AuthUsername
175175
user.User.Hostname = e.Ctx().GetSessionVars().User.AuthHostname
176176
}
177-
exists, err := userExists(ctx, e.Ctx(), user.User.Username, user.User.Hostname)
177+
exists, err := userExistsWithRetryVariants(ctx, e.Ctx(), &user.User.Username, user.User.Hostname)
178178
if err != nil {
179179
return err
180180
}

pkg/executor/revoke.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func (e *RevokeExec) Next(ctx context.Context, _ *chunk.Chunk) error {
103103
}
104104

105105
// Check if user exists.
106-
exists, err := userExists(ctx, e.Ctx(), user.User.Username, user.User.Hostname)
106+
exists, err := userExistsWithRetryVariants(ctx, e.Ctx(), &user.User.Username, user.User.Hostname)
107107
if err != nil {
108108
return err
109109
}

pkg/executor/simple.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/pingcap/tidb/pkg/expression"
4141
"github.com/pingcap/tidb/pkg/extension"
4242
"github.com/pingcap/tidb/pkg/infoschema"
43+
"github.com/pingcap/tidb/pkg/keyspace"
4344
"github.com/pingcap/tidb/pkg/kv"
4445
"github.com/pingcap/tidb/pkg/meta"
4546
"github.com/pingcap/tidb/pkg/meta/model"
@@ -692,7 +693,7 @@ func (e *SimpleExec) executeRevokeRole(ctx context.Context, s *ast.RevokeRoleStm
692693
e.setCurrentUser(s.Users)
693694

694695
for _, role := range s.Roles {
695-
exists, err := userExists(ctx, e.Ctx(), role.Username, role.Hostname)
696+
exists, err := userExistsWithRetryVariants(ctx, e.Ctx(), &role.Username, role.Hostname)
696697
if err != nil {
697698
return errors.Trace(err)
698699
}
@@ -1157,6 +1158,9 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm
11571158

11581159
users := make([]*auth.UserIdentity, 0, len(s.Specs))
11591160
for _, spec := range s.Specs {
1161+
if err := keyspace.GetUsernamePolicy().ValidateUsername(spec.User.Username); err != nil {
1162+
return err
1163+
}
11601164
if len(spec.User.Username) > auth.UserNameMaxLength {
11611165
return exeerrors.ErrWrongStringLength.GenWithStackByArgs(spec.User.Username, "user name", auth.UserNameMaxLength)
11621166
}
@@ -1785,7 +1789,7 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
17851789
}
17861790
}
17871791

1788-
exists, currentAuthPlugin, err := userExistsInternal(ctx, sqlExecutor, spec.User.Username, spec.User.Hostname)
1792+
exists, currentAuthPlugin, err := userExistsInternalWithRetryVariants(ctx, sqlExecutor, &spec.User.Username, spec.User.Hostname)
17891793
if err != nil {
17901794
return err
17911795
}
@@ -2075,7 +2079,7 @@ func (e *SimpleExec) executeGrantRole(ctx context.Context, s *ast.GrantRoleStmt)
20752079
e.setCurrentUser(s.Users)
20762080

20772081
for _, role := range s.Roles {
2078-
exists, err := userExists(ctx, e.Ctx(), role.Username, role.Hostname)
2082+
exists, err := userExistsWithRetryVariants(ctx, e.Ctx(), &role.Username, role.Hostname)
20792083
if err != nil {
20802084
return err
20812085
}
@@ -2084,7 +2088,7 @@ func (e *SimpleExec) executeGrantRole(ctx context.Context, s *ast.GrantRoleStmt)
20842088
}
20852089
}
20862090
for _, user := range s.Users {
2087-
exists, err := userExists(ctx, e.Ctx(), user.Username, user.Hostname)
2091+
exists, err := userExistsWithRetryVariants(ctx, e.Ctx(), &user.Username, user.Hostname)
20882092
if err != nil {
20892093
return err
20902094
}
@@ -2142,6 +2146,9 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error {
21422146
}
21432147
for _, userToUser := range s.UserToUsers {
21442148
oldUser, newUser := userToUser.OldUser, userToUser.NewUser
2149+
if err := keyspace.GetUsernamePolicy().ValidateUsername(newUser.Username); err != nil {
2150+
return err
2151+
}
21452152
if len(newUser.Username) > auth.UserNameMaxLength {
21462153
return exeerrors.ErrWrongStringLength.GenWithStackByArgs(newUser.Username, "user name", auth.UserNameMaxLength)
21472154
}
@@ -2455,6 +2462,42 @@ func userExists(ctx context.Context, sctx sessionctx.Context, name string, host
24552462
return len(rows) > 0, nil
24562463
}
24572464

2465+
func userExistsWithRetryVariants(ctx context.Context, sctx sessionctx.Context, name *string, host string) (bool, error) {
2466+
exists, err := userExists(ctx, sctx, *name, host)
2467+
if err != nil || exists {
2468+
return exists, err
2469+
}
2470+
for _, variant := range keyspace.GetUsernamePolicy().GetUsernameVariants(*name) {
2471+
exists, err = userExists(ctx, sctx, variant, host)
2472+
if err != nil {
2473+
return false, err
2474+
}
2475+
if exists {
2476+
*name = variant
2477+
return true, nil
2478+
}
2479+
}
2480+
return false, nil
2481+
}
2482+
2483+
func userExistsInternalWithRetryVariants(ctx context.Context, sqlExecutor sqlexec.SQLExecutor, name *string, host string) (bool, string, error) {
2484+
exists, authPlugin, err := userExistsInternal(ctx, sqlExecutor, *name, host)
2485+
if err != nil || exists {
2486+
return exists, authPlugin, err
2487+
}
2488+
for _, variant := range keyspace.GetUsernamePolicy().GetUsernameVariants(*name) {
2489+
exists, authPlugin, err = userExistsInternal(ctx, sqlExecutor, variant, host)
2490+
if err != nil {
2491+
return false, "", err
2492+
}
2493+
if exists {
2494+
*name = variant
2495+
return true, authPlugin, nil
2496+
}
2497+
}
2498+
return false, "", nil
2499+
}
2500+
24582501
// use the same internal executor to read within the same transaction, otherwise same as userExists
24592502
func userExistsInternal(ctx context.Context, sqlExecutor sqlexec.SQLExecutor, name string, host string) (bool, string, error) {
24602503
sql := new(strings.Builder)

pkg/executor/test/simpletest/BUILD.bazel

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ go_test(
99
],
1010
flaky = True,
1111
race = "on",
12-
shard_count = 12,
12+
shard_count = 13,
1313
deps = [
1414
"//pkg/config",
15+
"//pkg/config/deploymode",
16+
"//pkg/config/kerneltype",
1517
"//pkg/parser/ast",
1618
"//pkg/parser/auth",
1719
"//pkg/parser/mysql",

pkg/executor/test/simpletest/simple_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222

2323
"github.com/pingcap/errors"
2424
"github.com/pingcap/tidb/pkg/config"
25+
"github.com/pingcap/tidb/pkg/config/deploymode"
26+
"github.com/pingcap/tidb/pkg/config/kerneltype"
2527
"github.com/pingcap/tidb/pkg/parser/ast"
2628
"github.com/pingcap/tidb/pkg/parser/auth"
2729
"github.com/pingcap/tidb/pkg/parser/mysql"
@@ -38,6 +40,44 @@ import (
3840
"go.opencensus.io/stats/view"
3941
)
4042

43+
func TestStarterUsernamePolicyInSimpleExec(t *testing.T) {
44+
if !kerneltype.IsNextGen() {
45+
t.Skip("starter deploy mode is nextgen-only")
46+
}
47+
48+
restoreConfig := config.RestoreFunc()
49+
originalMode := deploymode.Get()
50+
t.Cleanup(func() {
51+
restoreConfig()
52+
require.NoError(t, deploymode.Set(originalMode))
53+
})
54+
55+
config.UpdateGlobal(func(conf *config.Config) {
56+
conf.KeyspaceName = "SYSTEM"
57+
})
58+
require.NoError(t, deploymode.Set(deploymode.Starter))
59+
60+
store := testkit.CreateMockStore(t)
61+
tk := testkit.NewTestKit(t, store)
62+
tk.MustExec("create user if not exists `SYSTEM.r1`@`%`")
63+
tk.MustExec("create user if not exists `SYSTEM.u1`@`%`")
64+
65+
tk.MustContainErrMsg("create user u2@'%' identified by 'pwd'", "User name must start with `SYSTEM.`")
66+
tk.MustContainErrMsg("create role r2", "User name must start with `SYSTEM.`")
67+
tk.MustExec("create role `SYSTEM.r2`")
68+
tk.MustQuery("select User from mysql.user where User='SYSTEM.r2' and Host='%'").Check(testkit.Rows("SYSTEM.r2"))
69+
tk.MustContainErrMsg("rename user `SYSTEM.u1`@`%` to u2@'%'", "User name must start with `SYSTEM.`")
70+
71+
tk.MustExec("grant r1 to u1")
72+
tk.MustQuery("select TO_USER from mysql.role_edges where FROM_USER='SYSTEM.r1' and TO_USER='SYSTEM.u1' and TO_HOST='%'").Check(testkit.Rows("SYSTEM.u1"))
73+
74+
tk.MustExec("revoke `SYSTEM.r1` from `SYSTEM.u1`")
75+
tk.MustQuery("select TO_USER from mysql.role_edges where FROM_USER='SYSTEM.r1' and TO_USER='SYSTEM.u1' and TO_HOST='%'").Check(testkit.Rows())
76+
77+
tk.MustExec("alter user u1 identified by 'pwd2'")
78+
tk.MustQuery("select authentication_string from mysql.user where user='SYSTEM.u1' and host='%'").Check(testkit.Rows(auth.EncodePassword("pwd2")))
79+
}
80+
4181
func TestUserWithSetNames(t *testing.T) {
4282
store := testkit.CreateMockStore(t)
4383
tk := testkit.NewTestKit(t, store)

pkg/keyspace/BUILD.bazel

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ go_library(
55
srcs = [
66
"doc.go",
77
"keyspace.go",
8+
"username_policy.go",
89
],
910
importpath = "github.com/pingcap/tidb/pkg/keyspace",
1011
visibility = ["//visibility:public"],
1112
deps = [
1213
"//pkg/config",
14+
"//pkg/config/deploymode",
1315
"//pkg/config/kerneltype",
16+
"//pkg/util/dbterror/exeerrors",
1417
"@com_github_pingcap_kvproto//pkg/kvrpcpb",
1518
"@com_github_tikv_client_go_v2//tikv",
1619
"@org_uber_go_zap//:zap",
@@ -24,10 +27,13 @@ go_test(
2427
srcs = ["keyspace_test.go"],
2528
embed = [":keyspace"],
2629
flaky = True,
27-
shard_count = 2,
30+
shard_count = 3,
2831
deps = [
2932
"//pkg/config",
33+
"//pkg/config/deploymode",
3034
"//pkg/config/kerneltype",
35+
"//pkg/parser/terror",
36+
"//pkg/util/dbterror/exeerrors",
3137
"@com_github_stretchr_testify//require",
3238
],
3339
)

pkg/keyspace/keyspace_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import (
1919
"testing"
2020

2121
"github.com/pingcap/tidb/pkg/config"
22+
"github.com/pingcap/tidb/pkg/config/deploymode"
2223
"github.com/pingcap/tidb/pkg/config/kerneltype"
24+
"github.com/pingcap/tidb/pkg/parser/terror"
25+
"github.com/pingcap/tidb/pkg/util/dbterror/exeerrors"
2326
"github.com/stretchr/testify/require"
2427
)
2528

@@ -71,6 +74,42 @@ func TestNoKeyspaceNameSet(t *testing.T) {
7174
}
7275
}
7376

77+
func TestUsernamePolicy(t *testing.T) {
78+
restoreConfig := config.RestoreFunc()
79+
originalMode := deploymode.Get()
80+
t.Cleanup(func() {
81+
restoreConfig()
82+
if kerneltype.IsNextGen() {
83+
require.NoError(t, deploymode.Set(originalMode))
84+
}
85+
})
86+
87+
config.UpdateGlobal(func(conf *config.Config) {
88+
conf.KeyspaceName = "ks"
89+
})
90+
91+
policy := GetUsernamePolicy()
92+
require.NoError(t, policy.ValidateUsername("user"))
93+
require.Empty(t, policy.GetUsernameVariants("user"))
94+
require.Empty(t, policy.GetOriginalUsername("ks.user"))
95+
96+
if !kerneltype.IsNextGen() {
97+
return
98+
}
99+
100+
require.NoError(t, deploymode.Set(deploymode.Starter))
101+
policy = GetUsernamePolicy()
102+
require.NoError(t, policy.ValidateUsername("ks.user"))
103+
require.True(t, policy.ValidateUsernameFormat("other.user"))
104+
require.True(t, policy.ValidateUsernameFormat("other.user.extra"))
105+
require.True(t, terror.ErrorEqual(policy.ValidateUsername("user"), exeerrors.ErrUserNameNeedPrefix))
106+
require.Equal(t, []string{"ks.user"}, policy.GetUsernameVariants("user"))
107+
require.Empty(t, policy.GetUsernameVariants("ks.user"))
108+
require.Empty(t, policy.GetUsernameVariants("other.user"))
109+
require.Empty(t, policy.GetUsernameVariants("other.user.extra"))
110+
require.Equal(t, "user", policy.GetOriginalUsername("ks.user"))
111+
}
112+
74113
func BenchmarkGetKeyspaceNameBytesBySettings(b *testing.B) {
75114
if !kerneltype.IsNextGen() {
76115
b.Skip("NextGen is not enabled, skipping benchmark")

0 commit comments

Comments
 (0)