Skip to content

Commit 8168ee0

Browse files
authored
Merge pull request #99 from liggitt/dn-comparison
Add comparison methods for DN/RDN/AttributeTypeAndValue
2 parents 056dc13 + 0367f61 commit 8168ee0

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

dn.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,70 @@ func ParseDN(str string) (*DN, error) {
175175
}
176176
return dn, nil
177177
}
178+
179+
// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
180+
// Returns true if they have the same number of relative distinguished names
181+
// and corresponding relative distinguished names (by position) are the same.
182+
func (d *DN) Equal(other *DN) bool {
183+
if len(d.RDNs) != len(other.RDNs) {
184+
return false
185+
}
186+
for i := range d.RDNs {
187+
if !d.RDNs[i].Equal(other.RDNs[i]) {
188+
return false
189+
}
190+
}
191+
return true
192+
}
193+
194+
// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
195+
// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
196+
// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
197+
// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
198+
func (d *DN) AncestorOf(other *DN) bool {
199+
if len(d.RDNs) >= len(other.RDNs) {
200+
return false
201+
}
202+
// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
203+
otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
204+
for i := range d.RDNs {
205+
if !d.RDNs[i].Equal(otherRDNs[i]) {
206+
return false
207+
}
208+
}
209+
return true
210+
}
211+
212+
// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
213+
// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
214+
// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
215+
// The order of attributes is not significant.
216+
// Case of attribute types is not significant.
217+
func (r *RelativeDN) Equal(other *RelativeDN) bool {
218+
if len(r.Attributes) != len(other.Attributes) {
219+
return false
220+
}
221+
return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
222+
}
223+
224+
func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
225+
for _, attr := range attrs {
226+
found := false
227+
for _, myattr := range r.Attributes {
228+
if myattr.Equal(attr) {
229+
found = true
230+
break
231+
}
232+
}
233+
if !found {
234+
return false
235+
}
236+
}
237+
return true
238+
}
239+
240+
// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
241+
// Case of the attribute type is not significant
242+
func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
243+
return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
244+
}

dn_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,119 @@ func TestErrorDNParsing(t *testing.T) {
9191
}
9292
}
9393
}
94+
95+
func TestDNEqual(t *testing.T) {
96+
testcases := []struct {
97+
A string
98+
B string
99+
Equal bool
100+
}{
101+
// Exact match
102+
{"", "", true},
103+
{"o=A", "o=A", true},
104+
{"o=A", "o=B", false},
105+
106+
{"o=A,o=B", "o=A,o=B", true},
107+
{"o=A,o=B", "o=A,o=C", false},
108+
109+
{"o=A+o=B", "o=A+o=B", true},
110+
{"o=A+o=B", "o=A+o=C", false},
111+
112+
// Case mismatch in type is ignored
113+
{"o=A", "O=A", true},
114+
{"o=A,o=B", "o=A,O=B", true},
115+
{"o=A+o=B", "o=A+O=B", true},
116+
117+
// Case mismatch in value is significant
118+
{"o=a", "O=A", false},
119+
{"o=a,o=B", "o=A,O=B", false},
120+
{"o=a+o=B", "o=A+O=B", false},
121+
122+
// Multi-valued RDN order mismatch is ignored
123+
{"o=A+o=B", "O=B+o=A", true},
124+
// Number of RDN attributes is significant
125+
{"o=A+o=B", "O=B+o=A+O=B", false},
126+
127+
// Missing values are significant
128+
{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
129+
{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter
130+
131+
// Whitespace tests
132+
// Matching
133+
{
134+
"cn=John Doe, ou=People, dc=sun.com",
135+
"cn=John Doe, ou=People, dc=sun.com",
136+
true,
137+
},
138+
// Difference in leading/trailing chars is ignored
139+
{
140+
"cn=John Doe, ou=People, dc=sun.com",
141+
"cn=John Doe,ou=People,dc=sun.com",
142+
true,
143+
},
144+
// Difference in values is significant
145+
{
146+
"cn=John Doe, ou=People, dc=sun.com",
147+
"cn=John Doe, ou=People, dc=sun.com",
148+
false,
149+
},
150+
}
151+
152+
for i, tc := range testcases {
153+
a, err := ldap.ParseDN(tc.A)
154+
if err != nil {
155+
t.Errorf("%d: %v", i, err)
156+
continue
157+
}
158+
b, err := ldap.ParseDN(tc.B)
159+
if err != nil {
160+
t.Errorf("%d: %v", i, err)
161+
continue
162+
}
163+
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
164+
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
165+
continue
166+
}
167+
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
168+
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
169+
continue
170+
}
171+
}
172+
}
173+
174+
func TestDNAncestor(t *testing.T) {
175+
testcases := []struct {
176+
A string
177+
B string
178+
Ancestor bool
179+
}{
180+
// Exact match returns false
181+
{"", "", false},
182+
{"o=A", "o=A", false},
183+
{"o=A,o=B", "o=A,o=B", false},
184+
{"o=A+o=B", "o=A+o=B", false},
185+
186+
// Mismatch
187+
{"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false},
188+
189+
// Descendant
190+
{"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true},
191+
}
192+
193+
for i, tc := range testcases {
194+
a, err := ldap.ParseDN(tc.A)
195+
if err != nil {
196+
t.Errorf("%d: %v", i, err)
197+
continue
198+
}
199+
b, err := ldap.ParseDN(tc.B)
200+
if err != nil {
201+
t.Errorf("%d: %v", i, err)
202+
continue
203+
}
204+
if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual {
205+
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
206+
continue
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)