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
80 changes: 80 additions & 0 deletions dn.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
enchex "encoding/hex"
"errors"
"fmt"
"sort"
"strings"

ber "gopkg.in/asn1-ber.v1"
Expand All @@ -63,16 +64,95 @@ type AttributeTypeAndValue struct {
Value string
}

// String returns a normalized string representation of this attribute type and
// value pair which is the a lowercased join of the Type and Value with a "=".
func (a *AttributeTypeAndValue) String() string {
return strings.ToLower(a.Type) + "=" + a.encodeValue()
}

func (a *AttributeTypeAndValue) encodeValue() string {
// Normalize the value first.
// value := strings.ToLower(a.Value)
value := a.Value

encodedBuf := bytes.Buffer{}

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

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

for i := 0; i < len(value); i++ {
char := value[i]
if i == 0 && char == ' ' || char == '#' {
// Special case leading space or number sign.
escapeChar(char)
continue
}
if i == len(value)-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()
}

// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
type RelativeDN struct {
Attributes []*AttributeTypeAndValue
}

// String returns a normalized string representation of this relative DN which
// is the a join of all attributes (sorted in increasing order) with a "+".
func (r *RelativeDN) String() string {
attrs := make([]string, len(r.Attributes))
for i := range r.Attributes {
attrs[i] = r.Attributes[i].String()
}
sort.Strings(attrs)
return strings.Join(attrs, "+")
}

// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
type DN struct {
RDNs []*RelativeDN
}

// String returns a normalized string representation of this DN which is the
// join of all relative DNs with a ",".
func (d *DN) String() string {
rdns := make([]string, len(d.RDNs))
for i := range d.RDNs {
rdns[i] = d.RDNs[i].String()
}
return strings.Join(rdns, ",")
}

// ParseDN returns a distinguishedName or an error
func ParseDN(str string) (*DN, error) {
dn := new(DN)
Expand Down
89 changes: 47 additions & 42 deletions dn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,44 @@ import (

func TestSuccessfulDNParsing(t *testing.T) {
testcases := map[string]ldap.DN{
"": ldap.DN{[]*ldap.RelativeDN{}},
"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}},
"UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&ldap.AttributeTypeAndValue{"OU", "Sales"},
&ldap.AttributeTypeAndValue{"CN", "J. Smith"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
"1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
"CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
" CN = Lu\\C4\\8Di\\C4\\87 ": ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
` A = 1 , B = 2 `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"A", "1"}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"B", "2"}}}}},
` A = 1 + B = 2 `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&ldap.AttributeTypeAndValue{"A", "1"},
&ldap.AttributeTypeAndValue{"B", "2"}}}}},
` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{" A ", " 1 "}}},
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{" B ", " 2 "}}}}},
` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: ldap.DN{[]*ldap.RelativeDN{
&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
&ldap.AttributeTypeAndValue{" A ", " 1 "},
&ldap.AttributeTypeAndValue{" B ", " 2 "}}}}},
"": {[]*ldap.RelativeDN{}},
"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
{[]*ldap.AttributeTypeAndValue{{"dc", "dummy"}}},
{[]*ldap.AttributeTypeAndValue{{"dc", "com"}}}}},
"UID=jsmith,DC=example,DC=net": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"UID", "jsmith"}}},
{[]*ldap.AttributeTypeAndValue{{"DC", "example"}}},
{[]*ldap.AttributeTypeAndValue{{"DC", "net"}}}}},
"OU=Sales+CN=J. Smith,DC=example,DC=net": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{
{"OU", "Sales"},
{"CN", "J. Smith"}}},
{[]*ldap.AttributeTypeAndValue{{"DC", "example"}}},
{[]*ldap.AttributeTypeAndValue{{"DC", "net"}}}}},
"1.3.6.1.4.1.1466.0=#04024869": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
"1.3.6.1.4.1.1466.0=#04024869,DC=net": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}},
{[]*ldap.AttributeTypeAndValue{{"DC", "net"}}}}},
"CN=Lu\\C4\\8Di\\C4\\87": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"CN", "Lučić"}}}}},
" CN = Lu\\C4\\8Di\\C4\\87 ": {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"CN", "Lučić"}}}}},
` A = 1 , B = 2 `: {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{"A", "1"}}},
{[]*ldap.AttributeTypeAndValue{{"B", "2"}}}}},
` A = 1 + B = 2 `: {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{
{"A", "1"},
{"B", "2"}}}}},
` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{{" A ", " 1 "}}},
{[]*ldap.AttributeTypeAndValue{{" B ", " 2 "}}}}},
` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: {[]*ldap.RelativeDN{
{[]*ldap.AttributeTypeAndValue{
{" A ", " 1 "},
{" B ", " 2 "}}}}},
}

for test, answer := range testcases {
Expand Down Expand Up @@ -137,8 +137,8 @@ func TestDNEqual(t *testing.T) {
},
// Difference in leading/trailing chars is ignored
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe,ou=People,dc=sun.com",
"cn=\\ John\\20Doe, ou=People, dc=sun.com",
"cn= \\ John Doe,ou=People,dc=sun.com",
true,
},
// Difference in values is significant
Expand All @@ -161,11 +161,16 @@ func TestDNEqual(t *testing.T) {
continue
}
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}

if expected, actual := a.Equal(b), a.String() == b.String(); expected != actual {
t.Errorf("%d: when asserting string comparison of %q and %q expected equal %v, got %v", i, a, b, expected, actual)
continue
}
}
Expand Down