1package ldap_test
2
3import (
4	"reflect"
5	"testing"
6
7	"gopkg.in/ldap.v2"
8)
9
10func TestSuccessfulDNParsing(t *testing.T) {
11	testcases := map[string]ldap.DN{
12		"": ldap.DN{[]*ldap.RelativeDN{}},
13		"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{
14			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
15			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}},
16			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}},
17		"UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
18			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}},
19			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
20			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
21		"OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
22			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
23				&ldap.AttributeTypeAndValue{"OU", "Sales"},
24				&ldap.AttributeTypeAndValue{"CN", "J. Smith"}}},
25			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
26			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
27		"1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{
28			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
29		"1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{
30			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}},
31			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
32		"CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
33			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
34		"  CN  =  Lu\\C4\\8Di\\C4\\87  ": ldap.DN{[]*ldap.RelativeDN{
35			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
36		`   A   =   1   ,   B   =   2   `: ldap.DN{[]*ldap.RelativeDN{
37			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"A", "1"}}},
38			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"B", "2"}}}}},
39		`   A   =   1   +   B   =   2   `: ldap.DN{[]*ldap.RelativeDN{
40			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
41				&ldap.AttributeTypeAndValue{"A", "1"},
42				&ldap.AttributeTypeAndValue{"B", "2"}}}}},
43		`   \ \ A\ \    =   \ \ 1\ \    ,   \ \ B\ \    =   \ \ 2\ \    `: ldap.DN{[]*ldap.RelativeDN{
44			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"  A  ", "  1  "}}},
45			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"  B  ", "  2  "}}}}},
46		`   \ \ A\ \    =   \ \ 1\ \    +   \ \ B\ \    =   \ \ 2\ \    `: ldap.DN{[]*ldap.RelativeDN{
47			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
48				&ldap.AttributeTypeAndValue{"  A  ", "  1  "},
49				&ldap.AttributeTypeAndValue{"  B  ", "  2  "}}}}},
50	}
51
52	for test, answer := range testcases {
53		dn, err := ldap.ParseDN(test)
54		if err != nil {
55			t.Errorf(err.Error())
56			continue
57		}
58		if !reflect.DeepEqual(dn, &answer) {
59			t.Errorf("Parsed DN %s is not equal to the expected structure", test)
60			t.Logf("Expected:")
61			for _, rdn := range answer.RDNs {
62				for _, attribs := range rdn.Attributes {
63					t.Logf("#%v\n", attribs)
64				}
65			}
66			t.Logf("Actual:")
67			for _, rdn := range dn.RDNs {
68				for _, attribs := range rdn.Attributes {
69					t.Logf("#%v\n", attribs)
70				}
71			}
72		}
73	}
74}
75
76func TestErrorDNParsing(t *testing.T) {
77	testcases := map[string]string{
78		"*":                       "DN ended with incomplete type, value pair",
79		"cn=Jim\\0Test":           "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'",
80		"cn=Jim\\0":               "Got corrupted escaped character",
81		"DC=example,=net":         "DN ended with incomplete type, value pair",
82		"1=#0402486":              "Failed to decode BER encoding: encoding/hex: odd length hex string",
83		"test,DC=example,DC=com":  "incomplete type, value pair",
84		"=test,DC=example,DC=com": "incomplete type, value pair",
85	}
86
87	for test, answer := range testcases {
88		_, err := ldap.ParseDN(test)
89		if err == nil {
90			t.Errorf("Expected %s to fail parsing but succeeded\n", test)
91		} else if err.Error() != answer {
92			t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error())
93		}
94	}
95}
96
97func TestDNEqual(t *testing.T) {
98	testcases := []struct {
99		A     string
100		B     string
101		Equal bool
102	}{
103		// Exact match
104		{"", "", true},
105		{"o=A", "o=A", true},
106		{"o=A", "o=B", false},
107
108		{"o=A,o=B", "o=A,o=B", true},
109		{"o=A,o=B", "o=A,o=C", false},
110
111		{"o=A+o=B", "o=A+o=B", true},
112		{"o=A+o=B", "o=A+o=C", false},
113
114		// Case mismatch in type is ignored
115		{"o=A", "O=A", true},
116		{"o=A,o=B", "o=A,O=B", true},
117		{"o=A+o=B", "o=A+O=B", true},
118
119		// Case mismatch in value is significant
120		{"o=a", "O=A", false},
121		{"o=a,o=B", "o=A,O=B", false},
122		{"o=a+o=B", "o=A+O=B", false},
123
124		// Multi-valued RDN order mismatch is ignored
125		{"o=A+o=B", "O=B+o=A", true},
126		// Number of RDN attributes is significant
127		{"o=A+o=B", "O=B+o=A+O=B", false},
128
129		// Missing values are significant
130		{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
131		{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter
132
133		// Whitespace tests
134		// Matching
135		{
136			"cn=John Doe, ou=People, dc=sun.com",
137			"cn=John Doe, ou=People, dc=sun.com",
138			true,
139		},
140		// Difference in leading/trailing chars is ignored
141		{
142			"cn=John Doe, ou=People, dc=sun.com",
143			"cn=John Doe,ou=People,dc=sun.com",
144			true,
145		},
146		// Difference in values is significant
147		{
148			"cn=John Doe, ou=People, dc=sun.com",
149			"cn=John  Doe, ou=People, dc=sun.com",
150			false,
151		},
152	}
153
154	for i, tc := range testcases {
155		a, err := ldap.ParseDN(tc.A)
156		if err != nil {
157			t.Errorf("%d: %v", i, err)
158			continue
159		}
160		b, err := ldap.ParseDN(tc.B)
161		if err != nil {
162			t.Errorf("%d: %v", i, err)
163			continue
164		}
165		if expected, actual := tc.Equal, a.Equal(b); expected != actual {
166			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
167			continue
168		}
169		if expected, actual := tc.Equal, b.Equal(a); expected != actual {
170			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
171			continue
172		}
173	}
174}
175
176func TestDNAncestor(t *testing.T) {
177	testcases := []struct {
178		A        string
179		B        string
180		Ancestor bool
181	}{
182		// Exact match returns false
183		{"", "", false},
184		{"o=A", "o=A", false},
185		{"o=A,o=B", "o=A,o=B", false},
186		{"o=A+o=B", "o=A+o=B", false},
187
188		// Mismatch
189		{"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false},
190
191		// Descendant
192		{"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true},
193	}
194
195	for i, tc := range testcases {
196		a, err := ldap.ParseDN(tc.A)
197		if err != nil {
198			t.Errorf("%d: %v", i, err)
199			continue
200		}
201		b, err := ldap.ParseDN(tc.B)
202		if err != nil {
203			t.Errorf("%d: %v", i, err)
204			continue
205		}
206		if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual {
207			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
208			continue
209		}
210	}
211}
212