Skip to content

Commit

Permalink
Fix normalized DN attribute escaping
Browse files Browse the repository at this point in the history
Apply the DN escaping rules on the normalized DN parts when
turning a DN into a normalized string form

Fixes: #70
  • Loading branch information
rhafer committed Jul 13, 2022
1 parent 45c5c6b commit 419a21f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 1 deletion.
56 changes: 55 additions & 1 deletion pkg/ldapdn/ldapdn.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ldapdn

import (
"bytes"
"encoding/hex"

"github.com/go-ldap/ldap/v3"
"golang.org/x/text/cases"
)
Expand All @@ -22,12 +25,63 @@ func Normalize(dn *ldap.DN) string {
} else if r > 0 {
nDN += ","
}
nDN = nDN + caseFold.String(ava.Type) + "=" + caseFold.String(ava.Value)
nDN = nDN + caseFold.String(ava.Type) + "=" + encodeRDNValue(caseFold.String(ava.Value))
}
}
return nDN
}

// encodeRDNValue applies the DN escaping rules (RFC4514) to the supplied
// string (the value part of an RDN). Returns the escaped string.
// Note: This function is taken from https://github.com/go-ldap/ldap/pull/104
func encodeRDNValue(rDNValue string) string {
encodedBuf := bytes.Buffer{}

escapeChar := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteByte(c)
}

escapeHex := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteString(hex.EncodeToString([]byte{c}))
}

for i := 0; i < len(rDNValue); i++ {
char := rDNValue[i]
if i == 0 && char == ' ' || char == '#' {
// Special case leading space or number sign.
escapeChar(char)
continue
}
if i == len(rDNValue)-1 && char == ' ' {
// Special case trailing space.
escapeChar(char)
continue
}

switch char {
case '"', '+', ',', ';', '<', '>', '\\':
// Each of these special characters must be escaped.
escapeChar(char)
continue
}

if char < ' ' || char > '~' {
// All special character escapes are handled first
// above. All bytes less than ASCII SPACE and all bytes
// greater than ASCII TILDE must be hex-escaped.
escapeHex(char)
continue
}

// Any other character does not require escaping.
encodedBuf.WriteByte(char)
}

return encodedBuf.String()
}

// ParseNormalize normalizes the passed LDAP DN string by first parsing it (using ldap.ParseDN)
// and then casefolding all RDN using ldapdn.Normalize(). ParseNormalize will return an error
// when parsing the DN fails.
Expand Down
24 changes: 24 additions & 0 deletions pkg/ldapdn/ldapdn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ldapdn

import (
"testing"
)

func TestParseNormalize(t *testing.T) {
tests := map[string]string{
"uid=Test,ou=test": "uid=test,ou=test",
"uid=rDN1+cn=rDN2,ou=test": "uid=rdn1+cn=rdn2,ou=test",
"uid=Test\\+withplus,ou=test": "uid=test\\+withplus,ou=test",
"uid=Test\\2bTest,ou=test": "uid=test\\+test,ou=test",
"uid=Test\\00test,ou=teSt": "uid=test\\00test,ou=test",
}

for in, out := range tests {
res, err := ParseNormalize(in)
if err != nil {
t.Errorf("Unexpected err: %s", err)
} else if res != out {
t.Errorf("Expected %s got %s", out, res)
}
}
}

0 comments on commit 419a21f

Please sign in to comment.