Skip to content

Authentication plugin #1694

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
By default, if not explicitly set, user must be the current os user (…
…mandatory for some authentication plugin, like GSSAPI for example).
  • Loading branch information
rusher committed Apr 11, 2025
commit 5d80303f133d1c74bc2b616cfc4f79d4a0de3eac
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ db.SetMaxIdleConns(10)

The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
```
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
[[username][:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
```

A DSN in its fullest form:
Expand Down
43 changes: 43 additions & 0 deletions auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"encoding/pem"
"fmt"
"testing"

osuser "os/user"
)

var testPubKey = []byte("-----BEGIN PUBLIC KEY-----\n" +
Expand Down Expand Up @@ -79,6 +81,47 @@ func TestScrambleSHA256Pass(t *testing.T) {
}
}

func TestDefaultUser(t *testing.T) {
conn, mc := newRWMockConn(1)
mc.cfg.User = ""
mc.cfg.Passwd = "secret"

authData := []byte{90, 105, 74, 126, 30, 48, 37, 56, 3, 23, 115, 127, 69,
22, 41, 84, 32, 123, 43, 118}
plugin := "mysql_native_password"

// Send Client Authentication Packet
authPlugin, exists := globalPluginRegistry.GetPlugin(plugin)
if !exists {
t.Fatalf("plugin not registered")
}
var expectedUsername string
currentUser, err := osuser.Current()
if err != nil {
expectedUsername = ""
} else {
expectedUsername = currentUser.Username
}

authResp, err := authPlugin.InitAuth(authData, mc.cfg)
if err != nil {
t.Fatal(err)
}
err = mc.writeHandshakeResponsePacket(authResp, plugin)
if err != nil {
t.Fatal(err)
}

// check written auth response
authRespStart := 4 + 4 + 4 + 1 + 23
authRespEnd := authRespStart + len(expectedUsername)
writtenAuthResp := conn.written[authRespStart:authRespEnd]
expectedAuthResp := []byte(expectedUsername)
if !bytes.Equal(writtenAuthResp, expectedAuthResp) || conn.written[authRespEnd] != 0 {
t.Fatalf("unexpected written auth response: %v", writtenAuthResp)
}
}

func TestAuthFastCachingSHA256PasswordCached(t *testing.T) {
conn, mc := newRWMockConn(1)
mc.cfg.User = "root"
Expand Down
12 changes: 8 additions & 4 deletions dsn.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,18 @@ func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) {
func (cfg *Config) FormatDSN() string {
var buf bytes.Buffer

// [username[:password]@]
// [[username][:password]@]
if len(cfg.User) > 0 {
buf.WriteString(cfg.User)
if len(cfg.Passwd) > 0 {
buf.WriteByte(':')
buf.WriteString(cfg.Passwd)
}
buf.WriteByte('@')
} else if len(cfg.Passwd) > 0 {
buf.WriteByte(':')
buf.WriteString(cfg.Passwd)
buf.WriteByte('@')
}

// [protocol[(address)]]
Expand Down Expand Up @@ -408,7 +412,7 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
// New config with some default values
cfg = NewConfig()

// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// [[username][:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
Expand All @@ -418,11 +422,11 @@ func ParseDSN(dsn string) (cfg *Config, err error) {

// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// [[username][:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// [username][:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
Expand Down
3 changes: 3 additions & 0 deletions dsn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ var testDSNs = []struct {
}, {
"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
}, {
":p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
&Config{Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
}, {
"/dbname",
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
Expand Down
16 changes: 13 additions & 3 deletions packets.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"fmt"
"io"
"math"
osuser "os/user"
"strconv"
"time"
)
Expand Down Expand Up @@ -303,8 +304,17 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
// length encoded integer
clientFlags |= clientPluginAuthLenEncClientData
}
var userName string
if len(mc.cfg.User) > 0 {
userName = mc.cfg.User
} else {
// Get current user if username is empty
Copy link
Member

Choose a reason for hiding this comment

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

Please don't.

if currentUser, err := osuser.Current(); err == nil {
userName = currentUser.Username
}
}

pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
pktLen := 4 + 4 + 1 + 23 + len(userName) + 1 + len(authRespLEI) + len(authResp) + 21 + 1

// To specify a db name
if n := len(mc.cfg.DBName); n > 0 {
Expand Down Expand Up @@ -372,8 +382,8 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
}

// User [null terminated string]
if len(mc.cfg.User) > 0 {
pos += copy(data[pos:], mc.cfg.User)
if len(userName) > 0 {
pos += copy(data[pos:], userName)
}
data[pos] = 0x00
pos++
Expand Down