Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/32064.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
sdk/ldaputil: Fix malformed error messages that embedded the literal `{{err}}` placeholder and a `%!(EXTRA ...)` marker, and wrap the underlying error so `errors.Is`/`errors.As` work.
```
12 changes: 6 additions & 6 deletions sdk/helper/ldaputil/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (c *Client) DialLDAP(cfg *ConfigEntry) (Connection, error) {
for _, uut := range urls {
u, err := url.Parse(uut)
if err != nil {
retErr = multierror.Append(retErr, fmt.Errorf(fmt.Sprintf("error parsing url %q: {{err}}", uut), err))
retErr = multierror.Append(retErr, fmt.Errorf("error parsing url %q: %w", uut, err))
continue
}
host, port, err := net.SplitHostPort(u.Host)
Expand Down Expand Up @@ -104,7 +104,7 @@ func (c *Client) DialLDAP(cfg *ConfigEntry) (Connection, error) {
retErr = nil
break
}
retErr = multierror.Append(retErr, fmt.Errorf(fmt.Sprintf("error connecting to host %q: {{err}}", uut), err))
retErr = multierror.Append(retErr, fmt.Errorf("error connecting to host %q: %w", uut, err))
}
if retErr != nil {
return nil, retErr
Expand Down Expand Up @@ -453,21 +453,21 @@ func sidBytesToString(b []byte) (string, error) {
var identifierAuthorityParts [3]uint16

if err := binary.Read(reader, binary.LittleEndian, &revision); err != nil {
return "", fmt.Errorf(fmt.Sprintf("SID %#v convert failed reading Revision: {{err}}", b), err)
return "", fmt.Errorf("SID %#v convert failed reading Revision: %w", b, err)
}

if err := binary.Read(reader, binary.LittleEndian, &subAuthorityCount); err != nil {
return "", fmt.Errorf(fmt.Sprintf("SID %#v convert failed reading SubAuthorityCount: {{err}}", b), err)
return "", fmt.Errorf("SID %#v convert failed reading SubAuthorityCount: %w", b, err)
}

if err := binary.Read(reader, binary.BigEndian, &identifierAuthorityParts); err != nil {
return "", fmt.Errorf(fmt.Sprintf("SID %#v convert failed reading IdentifierAuthority: {{err}}", b), err)
return "", fmt.Errorf("SID %#v convert failed reading IdentifierAuthority: %w", b, err)
}
identifierAuthority := (uint64(identifierAuthorityParts[0]) << 32) + (uint64(identifierAuthorityParts[1]) << 16) + uint64(identifierAuthorityParts[2])

subAuthority := make([]uint32, subAuthorityCount)
if err := binary.Read(reader, binary.LittleEndian, &subAuthority); err != nil {
return "", fmt.Errorf(fmt.Sprintf("SID %#v convert failed reading SubAuthority: {{err}}", b), err)
return "", fmt.Errorf("SID %#v convert failed reading SubAuthority: %w", b, err)
}

result := fmt.Sprintf("S-%d-%d", revision, identifierAuthority)
Expand Down
24 changes: 24 additions & 0 deletions sdk/helper/ldaputil/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package ldaputil

import (
"io"
"testing"

"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -96,6 +97,29 @@ func TestSIDBytesToString(t *testing.T) {
}
}

// TestSIDBytesToStringErrorFormatting confirms that a failure to convert a SID
// produces a usable error. These messages previously embedded the errwrap
// placeholder "{{err}}" in a format string passed to fmt.Errorf, which has no
// verb to consume the error argument, so the rendered message contained both
// the literal placeholder and Go's "%!(EXTRA ...)" marker and did not wrap the
// underlying error.
//
// go vet does not catch the original form, because fmt.Sprintf makes the format
// string non-constant and printf analysis is skipped.
func TestSIDBytesToStringErrorFormatting(t *testing.T) {
// Truncated: too short to read the Revision field.
_, err := sidBytesToString([]byte{})
require.Error(t, err)

assert.NotContains(t, err.Error(), "{{err}}",
"errwrap placeholder leaked into the rendered message")
assert.NotContains(t, err.Error(), "%!(EXTRA",
"error argument was not consumed by a format verb")

assert.ErrorIs(t, err, io.EOF,
"underlying error should be wrapped with %w so errors.Is can reach it")
}

func TestClient_renderUserSearchFilter(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
Loading