| package authconfig | |
| import ( | |
| "encoding/base64" | |
| "strings" | |
| "testing" | |
| "github.com/moby/moby/api/types/registry" | |
| "gotest.tools/v3/assert" | |
| is "gotest.tools/v3/assert/cmp" | |
| ) | |
| func TestDecodeAuthConfig(t *testing.T) { | |
| tests := []struct { | |
| doc string | |
| input string | |
| inputBase64 string | |
| expected registry.AuthConfig | |
| expectedErr string | |
| }{ | |
| { | |
| doc: "empty", | |
| input: ``, | |
| inputBase64: ``, | |
| expected: registry.AuthConfig{}, | |
| }, | |
| { | |
| doc: "empty JSON", | |
| input: `{}`, | |
| inputBase64: `e30=`, | |
| expected: registry.AuthConfig{}, | |
| }, | |
| { | |
| doc: "malformed JSON", | |
| input: `{`, | |
| inputBase64: `ew==`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: invalid JSON: unexpected EOF`, | |
| }, | |
| { | |
| doc: "test authConfig", | |
| input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`, | |
| inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ==`, | |
| expected: registry.AuthConfig{ | |
| Username: "testuser", | |
| Password: "testpassword", | |
| ServerAddress: "example.com", | |
| }, | |
| }, | |
| { | |
| doc: "multiple authConfig (should be rejected)", | |
| input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}{"username":"testuser2","password":"testpassword2","serveraddress":"example.org"}`, | |
| inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifXsidXNlcm5hbWUiOiJ0ZXN0dXNlcjIiLCJwYXNzd29yZCI6InRlc3RwYXNzd29yZDIiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5vcmcifQ==`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: multiple JSON documents not allowed`, | |
| }, | |
| // We currently only support base64url encoding with padding, so | |
| // un-padded should produce an error. | |
| // | |
| // RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5 | |
| // RFC4648, section 3.2: https://tools.ietf.org/html/rfc4648#section-3.2 | |
| { | |
| doc: "empty JSON no padding", | |
| input: `{}`, | |
| inputBase64: `e30`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: must be a valid base64url-encoded string`, | |
| }, | |
| { | |
| doc: "test authConfig", | |
| input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`, | |
| inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: must be a valid base64url-encoded string`, | |
| }, | |
| { | |
| doc: "JSON with trailing whitespace (should accept)", | |
| input: `{"username":"testuser","password":"testpassword"} `, | |
| inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQifSAgIA==`, | |
| expected: registry.AuthConfig{ | |
| Username: "testuser", | |
| Password: "testpassword", | |
| }, | |
| }, | |
| { | |
| doc: "JSON with trailing invalid data", | |
| input: `{"username":"testuser"}invalid`, | |
| inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIn1pbnZhbGlk`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: multiple JSON documents not allowed`, | |
| }, | |
| { | |
| doc: "multiple empty JSON documents", | |
| input: `{}{}`, | |
| inputBase64: `e317fQ==`, | |
| expected: registry.AuthConfig{}, | |
| expectedErr: `invalid X-Registry-Auth header: multiple JSON documents not allowed`, | |
| }, | |
| } | |
| for _, tc := range tests { | |
| t.Run(tc.doc, func(t *testing.T) { | |
| if tc.inputBase64 != "" { | |
| // Sanity check to make sure our fixtures are correct. | |
| b64 := base64.URLEncoding.EncodeToString([]byte(tc.input)) | |
| if !strings.HasSuffix(tc.inputBase64, "=") { | |
| b64 = strings.TrimRight(b64, "=") | |
| } | |
| assert.Check(t, is.Equal(b64, tc.inputBase64)) | |
| } | |
| out, err := Decode(tc.inputBase64) | |
| if tc.expectedErr != "" { | |
| assert.Check(t, is.ErrorType(err, errInvalidParameter{})) | |
| assert.Check(t, is.Error(err, tc.expectedErr)) | |
| } else { | |
| assert.NilError(t, err) | |
| assert.Equal(t, *out, tc.expected) | |
| } | |
| }) | |
| } | |
| } | |
| func TestEncodeAuthConfig(t *testing.T) { | |
| tests := []struct { | |
| doc string | |
| input registry.AuthConfig | |
| outBase64 string | |
| outPlain string | |
| }{ | |
| { | |
| // Older daemons (or registries) may not handle an empty string, | |
| // which resulted in an "io.EOF" when unmarshaling or decoding. | |
| // | |
| // FIXME(thaJeztah): find exactly what code-paths are impacted by this. | |
| doc: "empty", | |
| input: registry.AuthConfig{}, | |
| outBase64: `e30=`, | |
| outPlain: `{}`, | |
| }, | |
| { | |
| doc: "test authConfig", | |
| input: registry.AuthConfig{ | |
| Username: "testuser", | |
| Password: "testpassword", | |
| ServerAddress: "example.com", | |
| }, | |
| outBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ==`, | |
| outPlain: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`, | |
| }, | |
| } | |
| for _, tc := range tests { | |
| // Sanity check to make sure our fixtures are correct. | |
| b64 := base64.URLEncoding.EncodeToString([]byte(tc.outPlain)) | |
| assert.Check(t, is.Equal(b64, tc.outBase64)) | |
| t.Run(tc.doc, func(t *testing.T) { | |
| out, err := Encode(tc.input) | |
| assert.NilError(t, err) | |
| assert.Equal(t, out, tc.outBase64) | |
| authJSON, err := base64.URLEncoding.DecodeString(out) | |
| assert.NilError(t, err) | |
| assert.Equal(t, string(authJSON), tc.outPlain) | |
| }) | |
| } | |
| } | |
| func BenchmarkDecodeAuthConfig(b *testing.B) { | |
| cases := []struct { | |
| doc string | |
| inputBase64 string | |
| invalid bool | |
| }{ | |
| { | |
| doc: "empty", | |
| inputBase64: ``, | |
| }, | |
| { | |
| doc: "empty JSON", | |
| inputBase64: `e30=`, | |
| }, | |
| { | |
| doc: "valid", | |
| inputBase64: base64.URLEncoding.EncodeToString([]byte(`{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`)), | |
| }, | |
| { | |
| doc: "invalid base64", | |
| inputBase64: "not-base64", | |
| invalid: true, | |
| }, | |
| { | |
| doc: "malformed JSON", | |
| inputBase64: `ew==`, | |
| invalid: true, | |
| }, | |
| } | |
| for _, tc := range cases { | |
| b.Run(tc.doc, func(b *testing.B) { | |
| b.ReportAllocs() | |
| for i := 0; i < b.N; i++ { | |
| _, err := Decode(tc.inputBase64) | |
| if !tc.invalid && err != nil { | |
| b.Fatal(err) | |
| } | |
| } | |
| }) | |
| } | |
| } |