From 58168472a8cef080281dd8b82d4c036dc5b59470 Mon Sep 17 00:00:00 2001 From: zipg Date: Fri, 10 Jul 2026 14:07:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(oracle):=20=E4=BF=9D=E7=95=99=E5=A0=A1?= =?UTF-8?q?=E5=9E=92=E6=9C=BA=E7=94=A8=E6=88=B7=E5=90=8D=E7=9A=84=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E8=AE=A4=E8=AF=81=E8=AF=AD=E4=B9=89=20(#3113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zipg <4047349+zipg@users.noreply.github.com> --- agents/drivers/oracle-go/main.go | 41 ++------------------------- agents/drivers/oracle-go/main_test.go | 38 ++++++++++++------------- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index f26ea20a7..f3412b282 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -620,7 +620,7 @@ func buildDSN(params connectParams) string { if strings.HasPrefix(strings.ToLower(connectionString), "oracle://") { return connectionString } - username := oracleAuthUsername(params.Username) + username := params.Username options := parseURLParams(params.URLParams) if params.SysDBA { options["AUTH TYPE"] = "SYSDBA" @@ -653,42 +653,6 @@ func buildDSN(params connectParams) string { return buildGoOraURL(params.Host, port, service, username, params.Password, options) } -func oracleAuthUsername(username string) string { - if username == "" || isQuotedOracleUsername(username) || !oracleUsernameRequiresQuoting(username) { - return username - } - // Oracle logon accepts quoted identifiers for users that cannot be - // represented as regular identifiers, such as bastion usernames with ':'. - return `"` + strings.ReplaceAll(username, `"`, `""`) + `"` -} - -func isQuotedOracleUsername(username string) bool { - return len(username) >= 2 && strings.HasPrefix(username, `"`) && strings.HasSuffix(username, `"`) -} - -func oracleUsernameRequiresQuoting(username string) bool { - for index, ch := range username { - if index == 0 { - if !isAsciiLetter(ch) { - return true - } - continue - } - if !isAsciiLetter(ch) && !isAsciiDigit(ch) && ch != '_' && ch != '$' && ch != '#' { - return true - } - } - return false -} - -func isAsciiLetter(ch rune) bool { - return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') -} - -func isAsciiDigit(ch rune) bool { - return ch >= '0' && ch <= '9' -} - func buildGoOraJDBC(user, password, connStr string, options map[string]string) string { if options == nil { options = make(map[string]string) @@ -699,7 +663,8 @@ func buildGoOraJDBC(user, password, connStr string, options map[string]string) s func buildGoOraURL(server string, port int, service, user, password string, options map[string]string) string { // go-ora v2.9.0 uses path escaping for user/password, leaving ':' unescaped. - // Userinfo escaping keeps usernames such as "9008888:reader" intact. + // Userinfo escaping keeps bastion usernames such as 9008888:reader intact + // without changing their authentication semantics. ret := fmt.Sprintf( "oracle://%s@%s/%s", url.UserPassword(user, password).String(), diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index 82f253679..6f9cdfcd1 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -299,7 +299,7 @@ func TestBuildDSNUsesConnectionStringWhenProvided(t *testing.T) { } } -func TestBuildDSNEncodesColonInCredentials(t *testing.T) { +func TestBuildDSNPreservesBastionUsernameAndEncodesCredentials(t *testing.T) { dsn := buildDSN(connectParams{ Host: "db.example.com", Port: 1521, @@ -313,11 +313,11 @@ func TestBuildDSNEncodesColonInCredentials(t *testing.T) { t.Fatal(err) } password, _ := parsed.User.Password() - if parsed.User.Username() != `"9008888:reader"` || password != "dbx:pass" { + if parsed.User.Username() != "9008888:reader" || password != "dbx:pass" { t.Fatalf("credentials should survive URL parsing, dsn=%s username=%q password=%q", dsn, parsed.User.Username(), password) } - if !strings.HasPrefix(parsed.User.String(), "%229008888%3Areader%22:") { - t.Fatalf("Oracle auth username should be quoted and escaped for non-regular identifiers, dsn=%s", dsn) + if !strings.HasPrefix(parsed.User.String(), "9008888%3Areader:") { + t.Fatalf("bastion username should be escaped without being quoted, dsn=%s", dsn) } } @@ -333,7 +333,7 @@ func TestBuildDSNEncodesColonInCredentialsFromJDBCServiceURL(t *testing.T) { t.Fatal(err) } password, _ := parsed.User.Password() - if parsed.User.Username() != `"9008888:reader"` || password != "dbx:pass" { + if parsed.User.Username() != "9008888:reader" || password != "dbx:pass" { t.Fatalf("credentials should survive JDBC URL conversion, dsn=%s username=%q password=%q", dsn, parsed.User.Username(), password) } if parsed.Host != "db.example.com:1521" || strings.TrimPrefix(parsed.Path, "/") != "XE" { @@ -341,21 +341,21 @@ func TestBuildDSNEncodesColonInCredentialsFromJDBCServiceURL(t *testing.T) { } } -func TestOracleAuthUsernameQuotesOnlyNonRegularIdentifiers(t *testing.T) { - tests := map[string]string{ - "scott": "scott", - "test": "test", - "SCOTT_1": "SCOTT_1", - "9008888:reader": `"9008888:reader"`, - "abc:def": `"abc:def"`, - `"abc:def"`: `"abc:def"`, - `abc"def`: `"abc""def"`, - } +func TestBuildDSNPreservesExplicitlyQuotedUsername(t *testing.T) { + dsn := buildDSN(connectParams{ + Host: "db.example.com", + Port: 1521, + Database: "XE", + Username: `"abc:def"`, + Password: "dbx:pass", + }) - for input, want := range tests { - if got := oracleAuthUsername(input); got != want { - t.Fatalf("oracleAuthUsername(%q) = %q, want %q", input, got, want) - } + parsed, err := url.Parse(dsn) + if err != nil { + t.Fatal(err) + } + if parsed.User.Username() != `"abc:def"` { + t.Fatalf("explicitly quoted username should remain unchanged, dsn=%s username=%q", dsn, parsed.User.Username()) } }