Repository: moby/moby
File: api/pkg/authconfig/authconfig.go (Go)

Code before:

// DecodeRequestBody decodes authentication information as sent as JSON in the
// body of a request. This function is to provide backward compatibility with old
// clients and API versions. Current clients and API versions expect authentication
// to be provided through the X-Registry-Auth header.
//
// Like [Decode], this function always returns an [AuthConfig], even if an
// error occurs. It is up to the caller to decide if authentication is required,
// and if the error can be ignored.
func DecodeRequestBody(r io.ReadCloser) (*registry.AuthConfig, error) {
	return decode(r)
}

func decode(r io.Reader) (*registry.AuthConfig, error) {
	authConfig := &registry.AuthConfig{}
	decoder := json.NewDecoder(r)
	if err := decoder.Decode(authConfig); err != nil {
		// always return an (empty) AuthConfig to increase compatibility with
		// the existing API.
		return &registry.AuthConfig{}, invalid(fmt.Errorf("invalid JSON: %w", err))
	}

	// Reject multiple JSON documents to prevent potential security issues
	// where additional data could be silently ignored.
	var extra json.RawMessage
	if err := decoder.Decode(&extra); err == nil {
		// Additional JSON document found - this should not be allowed
		return &registry.AuthConfig{}, invalid(errors.New("multiple JSON documents not allowed"))
	} else if !errors.Is(err, io.EOF) {
		// Unexpected trailing data (not valid JSON, not EOF)
		return &registry.AuthConfig{}, invalid(fmt.Errorf("unexpected trailing data: %w", err))
	}

	return authConfig, nil
}

func invalid(err error) error {
	return errInvalidParameter{fmt.Errorf("invalid X-Registry-Auth header: %w", err)}
}

type errInvalidParameter struct{ error }

func (errInvalidParameter) InvalidParameter() {}

func (e errInvalidParameter) Cause() error { return e.error }

func (e errInvalidParameter) Unwrap() error { return e.error }


Review comment (security):
This can be greatly simplified; decoder already has an option for this to check if more content is present;


```go
func decode(r io.Reader) (*registry.AuthConfig, error) {
	authConfig := &registry.AuthConfig{}
	dec := json.NewDecoder(r)
	if err := dec.Decode(authConfig); err != nil {
		// always return an (empty) AuthConfig to increase compatibility with
		// the existing API.
		return &registry.AuthConfig{}, invalid(fmt.Errorf("invalid JSON: %w", err))
	}
	if dec.More() {
		return &registry.AuthConfig{}, invalid(errors.New("multiple JSON documents not allowed"))
	}
	return authConfig, nil
}
```

Code after:

// DecodeRequestBody decodes authentication information as sent as JSON in the
// body of a request. This function is to provide backward compatibility with old
// clients and API versions. Current clients and API versions expect authentication
// to be provided through the X-Registry-Auth header.
//
// Like [Decode], this function always returns an [AuthConfig], even if an
// error occurs. It is up to the caller to decide if authentication is required,
// and if the error can be ignored.
func DecodeRequestBody(r io.ReadCloser) (*registry.AuthConfig, error) {
	return decode(r)
}

func decode(r io.Reader) (*registry.AuthConfig, error) {
	authConfig := &registry.AuthConfig{}
	dec := json.NewDecoder(r)
	if err := dec.Decode(authConfig); err != nil {
		// always return an (empty) AuthConfig to increase compatibility with
		// the existing API.
		return &registry.AuthConfig{}, invalid(fmt.Errorf("invalid JSON: %w", err))
	}
	if dec.More() {
		return &registry.AuthConfig{}, invalid(errors.New("multiple JSON documents not allowed"))
	}
	return authConfig, nil
}

func invalid(err error) error {
	return errInvalidParameter{fmt.Errorf("invalid X-Registry-Auth header: %w", err)}
}

type errInvalidParameter struct{ error }

func (errInvalidParameter) InvalidParameter() {}

func (e errInvalidParameter) Cause() error { return e.error }

func (e errInvalidParameter) Unwrap() error { return e.error }