@@ -103,39 +103,6 @@ func TestHtpasswdAuthMultipleUsers(t *testing.T) {
103103 }
104104}
105105
106- func TestHtpasswdAuthSupportedHashTypes (t * testing.T ) {
107- logger := zerolog .New (zerolog .NewConsoleWriter ())
108-
109- cfg := & config.HtpasswdConfig {
110- Contents : "testuser:" + testBcryptHash ,
111- }
112-
113- auth , err := NewHtpasswdAuth (cfg , & logger )
114- if err != nil {
115- t .Fatalf ("Failed to create htpasswd auth: %v" , err )
116- }
117-
118- types := auth .SupportedHashTypes ()
119- if len (types ) == 0 {
120- t .Error ("Expected at least one supported hash type" )
121- }
122-
123- // The library DefaultSystems includes: bcrypt, md5, sha, sha256, sha512, crypt, plain
124- expected := []string {"bcrypt" , "md5" , "sha" , "sha256" , "sha512" , "crypt" , "plain" }
125- for _ , exp := range expected {
126- found := false
127- for _ , typ := range types {
128- if typ == exp {
129- found = true
130- break
131- }
132- }
133- if ! found {
134- t .Errorf ("Expected hash type %s to be in supported types" , exp )
135- }
136- }
137- }
138-
139106func TestBasicAuthMiddleware (t * testing.T ) {
140107 logger := zerolog .New (zerolog .NewConsoleWriter ())
141108 cfg := & config.HtpasswdConfig {
@@ -224,7 +191,7 @@ func TestShouldSkipAuth(t *testing.T) {
224191 }
225192}
226193
227- func TestGetClientIP (t * testing.T ) {
194+ func TestInvalidCredentialsAgainstAuthSpec (t * testing.T ) {
228195 logger := zerolog .New (zerolog .NewConsoleWriter ())
229196 cfg := & config.HtpasswdConfig {
230197 Contents : "testuser:" + testBcryptHash ,
@@ -235,28 +202,86 @@ func TestGetClientIP(t *testing.T) {
235202 t .Fatalf ("Failed to create auth: %v" , err )
236203 }
237204
238- // Test X-Forwarded-For
239- req := httptest .NewRequest ("GET" , "/v2/" , nil )
240- req .RemoteAddr = "192.0.2.1:12345"
241- req .Header .Set ("X-Forwarded-For" , "10.0.0.1" )
242- ip := auth .getClientIP (req )
243- if ip != "10.0.0.1" {
244- t .Errorf ("Expected IP %s, got %s" , "10.0.0.1" , ip )
245- }
205+ middleware := auth .DistributionMiddleware ()
206+ handler := middleware (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
207+ w .WriteHeader (http .StatusOK )
208+ }))
246209
247- // Test X-Real-IP
248- req .Header .Del ("X-Forwarded-For" )
249- req .Header .Set ("X-Real-IP" , "10.0.0.2" )
250- ip = auth .getClientIP (req )
251- if ip != "10.0.0.2" {
252- t .Errorf ("Expected IP %s, got %s" , "10.0.0.2" , ip )
253- }
210+ testCases := []struct {
211+ name string
212+ username string
213+ password string
214+ expectedStatus int
215+ }{
216+ {
217+ name : "wrong password for existing user" ,
218+ username : "testuser" ,
219+ password : "wrongpassword" ,
220+ expectedStatus : http .StatusUnauthorized ,
221+ },
222+ {
223+ name : "non-existent user with password" ,
224+ username : "nonexistent" ,
225+ password : "password" ,
226+ expectedStatus : http .StatusUnauthorized ,
227+ },
228+ {
229+ name : "non-existent user empty password" ,
230+ username : "nonexistent" ,
231+ password : "" ,
232+ expectedStatus : http .StatusUnauthorized ,
233+ },
234+ {
235+ name : "existing user empty password" ,
236+ username : "testuser" ,
237+ password : "" ,
238+ expectedStatus : http .StatusUnauthorized ,
239+ },
240+ {
241+ name : "non-existent user with very long password" ,
242+ username : "hacker" ,
243+ password : strings .Repeat ("a" , 1000 ),
244+ expectedStatus : http .StatusUnauthorized ,
245+ },
246+ }
247+
248+ for _ , tc := range testCases {
249+ t .Run (tc .name , func (t * testing.T ) {
250+ req := httptest .NewRequest ("GET" , "/v2/some/repo/manifests/latest" , nil )
251+ req .SetBasicAuth (tc .username , tc .password )
252+ w := httptest .NewRecorder ()
253+
254+ handler .ServeHTTP (w , req )
255+
256+ // Verify 401 status
257+ if w .Code != tc .expectedStatus {
258+ t .Errorf ("Expected status %d, got %d" , tc .expectedStatus , w .Code )
259+ }
260+
261+ // Verify WWW-Authenticate header is set (per auth spec)
262+ authHeader := w .Header ().Get ("WWW-Authenticate" )
263+ if authHeader == "" {
264+ t .Error ("Expected WWW-Authenticate header to be set" )
265+ }
266+ if ! strings .Contains (authHeader , "Sorcerer OCI Registry" ) {
267+ t .Errorf ("Expected WWW-Authenticate to contain realm, got: %s" , authHeader )
268+ }
269+
270+ // Verify that response body doesn't reveal user existence
271+ // (should be the same for wrong password as non-existent user)
272+ body1 := w .Body .String ()
254273
255- // Test RemoteAddr fallback
256- req .Header .Del ("X-Real-IP" )
257- ip = auth .getClientIP (req )
258- if ip != "192.0.2.1" {
259- t .Errorf ("Expected IP %s, got %s" , "192.0.2.1" , ip )
274+ // Test with a different invalid credential
275+ req2 := httptest .NewRequest ("GET" , "/v2/some/repo/manifests/latest" , nil )
276+ req2 .SetBasicAuth ("otheruser" , "otherpass" )
277+ w2 := httptest .NewRecorder ()
278+ handler .ServeHTTP (w2 , req2 )
279+
280+ body2 := w2 .Body .String ()
281+ if body1 != body2 {
282+ t .Error ("Response should not reveal whether user exists by differing" )
283+ }
284+ })
260285 }
261286}
262287
0 commit comments