fix(oracle): 保留堡垒机用户名的原始认证语义 (#3113)

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipg 2026-07-10 14:07:52 +08:00 committed by GitHub
parent 9903747381
commit 58168472a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 57 deletions

View File

@ -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(),

View File

@ -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())
}
}