@@ -86,3 +86,125 @@ func TestMySQLDbOverwriteUsersAndGrantsData(t *testing.T) {
86
86
87
87
rd .Close ()
88
88
}
89
+
90
+ func TestMatchesHostPattern (t * testing.T ) {
91
+ tests := []struct {
92
+ name string
93
+ host string
94
+ pattern string
95
+ expected bool
96
+ }{
97
+ // Basic wildcard patterns
98
+ {"IP wildcard - exact match" , "127.0.0.1" , "127.0.0.%" , true },
99
+ {"IP wildcard - different last octet" , "127.0.0.255" , "127.0.0.%" , true },
100
+ {"IP wildcard - no match" , "192.168.1.1" , "127.0.0.%" , false },
101
+ {"IP wildcard - partial match" , "127.0.1.1" , "127.0.0.%" , false },
102
+
103
+ // Multiple wildcards
104
+ {"Multiple wildcards" , "192.168.1.100" , "192.168.%.%" , true },
105
+ {"Multiple wildcards - no match" , "10.0.1.100" , "192.168.%.%" , false },
106
+
107
+ // Single wildcard at different positions
108
+ {"Wildcard first octet" , "10.0.0.1" , "%.0.0.1" , true },
109
+ {"Wildcard middle octet" , "192.168.50.1" , "192.%.50.1" , true },
110
+ {"Wildcard last octet" , "192.168.1.255" , "192.168.1.%" , true },
111
+
112
+ // Non-IP patterns
113
+ {"Hostname wildcard" , "server1.example.com" , "server%.example.com" , true },
114
+ {"Hostname wildcard - no match" , "db1.example.com" , "server%.example.com" , false },
115
+ {"Domain wildcard" , "host.subdomain.example.com" , "%.example.com" , true },
116
+
117
+ // Edge cases
118
+ {"Empty pattern" , "127.0.0.1" , "" , false },
119
+ {"Pattern without wildcard" , "127.0.0.1" , "127.0.0.1" , false }, // Should return false as it's not a wildcard pattern
120
+ {"Just wildcard" , "anything" , "%" , true },
121
+ {"Multiple wildcards together" , "test" , "%%" , true },
122
+
123
+ // Special characters in patterns (should be escaped)
124
+ {"Pattern with dots" , "test.host" , "test.%" , true },
125
+ {"Pattern with regex chars" , "test[1]" , "test[%]" , true },
126
+ }
127
+
128
+ for _ , tt := range tests {
129
+ t .Run (tt .name , func (t * testing.T ) {
130
+ result := matchesHostPattern (tt .host , tt .pattern )
131
+ require .Equal (t , tt .expected , result , "matchesHostPattern(%q, %q) = %v, want %v" , tt .host , tt .pattern , result , tt .expected )
132
+ })
133
+ }
134
+ }
135
+
136
+ func TestGetUserWithWildcardAuthentication (t * testing.T ) {
137
+ ctx := sql .NewEmptyContext ()
138
+ db := CreateEmptyMySQLDb ()
139
+ p := & capturingPersistence {}
140
+ db .SetPersister (p )
141
+
142
+ // Add test users with various host patterns
143
+ ed := db .Editor ()
144
+ db .AddSuperUser (ed , "testuser" , "127.0.0.1" , "password" )
145
+ db .AddSuperUser (ed , "localhost_user" , "localhost" , "password" )
146
+ db .AddSuperUser (ed , "wildcard_user" , "127.0.0.%" , "password" )
147
+ db .AddSuperUser (ed , "subnet_user" , "192.168.1.%" , "password" )
148
+ db .AddSuperUser (ed , "hostname_user" , "%.example.com" , "password" )
149
+ db .AddSuperUser (ed , "any_user" , "%" , "password" )
150
+ db .Persist (ctx , ed )
151
+ ed .Close ()
152
+
153
+ rd := db .Reader ()
154
+ defer rd .Close ()
155
+
156
+ tests := []struct {
157
+ name string
158
+ username string
159
+ host string
160
+ expectedUser string
161
+ shouldFind bool
162
+ }{
163
+ // Specific IP tests
164
+ {"Specific IP - exact match" , "testuser" , "127.0.0.1" , "testuser" , true },
165
+ {"Localhost user - normalized" , "localhost_user" , "127.0.0.1" , "localhost_user" , true },
166
+ {"Localhost user - ::1" , "localhost_user" , "::1" , "localhost_user" , true },
167
+ {"Non-existent user" , "nonexistent" , "127.0.0.1" , "" , false },
168
+
169
+ // IP wildcard tests
170
+ {"Wildcard IP - 127.0.0.% matches 127.0.0.1" , "wildcard_user" , "127.0.0.1" , "wildcard_user" , true },
171
+ {"Wildcard IP - 127.0.0.% matches 127.0.0.100" , "wildcard_user" , "127.0.0.100" , "wildcard_user" , true },
172
+ {"Wildcard IP - 127.0.0.% matches 127.0.0.255" , "wildcard_user" , "127.0.0.255" , "wildcard_user" , true },
173
+ {"Wildcard IP - 127.0.0.% does not match 127.0.1.1" , "wildcard_user" , "127.0.1.1" , "" , false },
174
+ {"Wildcard IP - 127.0.0.% does not match 192.168.1.1" , "wildcard_user" , "192.168.1.1" , "" , false },
175
+
176
+ // Subnet wildcard tests
177
+ {"Subnet wildcard - 192.168.1.% matches 192.168.1.1" , "subnet_user" , "192.168.1.1" , "subnet_user" , true },
178
+ {"Subnet wildcard - 192.168.1.% matches 192.168.1.100" , "subnet_user" , "192.168.1.100" , "subnet_user" , true },
179
+ {"Subnet wildcard - 192.168.1.% does not match 192.168.2.1" , "subnet_user" , "192.168.2.1" , "" , false },
180
+ {"Subnet wildcard - 192.168.1.% does not match 10.0.0.1" , "subnet_user" , "10.0.0.1" , "" , false },
181
+
182
+ // Hostname wildcard tests
183
+ {"Hostname wildcard - %.example.com matches host.example.com" , "hostname_user" , "host.example.com" , "hostname_user" , true },
184
+ {"Hostname wildcard - %.example.com matches www.example.com" , "hostname_user" , "www.example.com" , "hostname_user" , true },
185
+ {"Hostname wildcard - %.example.com does not match example.com" , "hostname_user" , "example.com" , "" , false },
186
+ {"Hostname wildcard - %.example.com does not match host.other.com" , "hostname_user" , "host.other.com" , "" , false },
187
+
188
+ // Global wildcard tests
189
+ {"Global wildcard - % matches any IP" , "any_user" , "10.0.0.1" , "any_user" , true },
190
+ {"Global wildcard - % matches any hostname" , "any_user" , "any.hostname.com" , "any_user" , true },
191
+ {"Global wildcard - % matches localhost" , "any_user" , "localhost" , "any_user" , true },
192
+
193
+ // Issue #9624 scenario
194
+ {"Customer scenario - matches connecting IP in range" , "wildcard_user" , "127.0.0.50" , "wildcard_user" , true },
195
+ }
196
+
197
+ for _ , tt := range tests {
198
+ t .Run (tt .name , func (t * testing.T ) {
199
+ user := db .GetUser (rd , tt .username , tt .host , false )
200
+
201
+ if ! tt .shouldFind {
202
+ require .Nil (t , user , "Expected no user to be found for %s@%s" , tt .username , tt .host )
203
+ return
204
+ }
205
+
206
+ require .NotNil (t , user , "Expected user to be found for %s@%s" , tt .username , tt .host )
207
+ require .Equal (t , tt .expectedUser , user .User , "Expected username %s, got %s" , tt .expectedUser , user .User )
208
+ })
209
+ }
210
+ }
0 commit comments