From 0367f61705f09561620749019ed336145ab83d7a Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Thu, 1 Dec 2016 15:05:13 -0500 Subject: [PATCH] Add comparison methods for DN/RDN/AttributeTypeAndValue --- dn.go | 67 +++++++++++++++++++++++++++++++ dn_test.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/dn.go b/dn.go index 5e7ab6da..a8ece314 100644 --- a/dn.go +++ b/dn.go @@ -175,3 +175,70 @@ func ParseDN(str string) (*DN, error) { } return dn, nil } + +// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). +// Returns true if they have the same number of relative distinguished names +// and corresponding relative distinguished names (by position) are the same. +func (d *DN) Equal(other *DN) bool { + if len(d.RDNs) != len(other.RDNs) { + return false + } + for i := range d.RDNs { + if !d.RDNs[i].Equal(other.RDNs[i]) { + return false + } + } + return true +} + +// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. +// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" +// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" +// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com" +func (d *DN) AncestorOf(other *DN) bool { + if len(d.RDNs) >= len(other.RDNs) { + return false + } + // Take the last `len(d.RDNs)` RDNs from the other DN to compare against + otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] + for i := range d.RDNs { + if !d.RDNs[i].Equal(otherRDNs[i]) { + return false + } + } + return true +} + +// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). +// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues +// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type. +// The order of attributes is not significant. +// Case of attribute types is not significant. +func (r *RelativeDN) Equal(other *RelativeDN) bool { + if len(r.Attributes) != len(other.Attributes) { + return false + } + return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes) +} + +func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { + for _, attr := range attrs { + found := false + for _, myattr := range r.Attributes { + if myattr.Equal(attr) { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue +// Case of the attribute type is not significant +func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool { + return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value +} diff --git a/dn_test.go b/dn_test.go index c78180a3..5055cc15 100644 --- a/dn_test.go +++ b/dn_test.go @@ -91,3 +91,119 @@ func TestErrorDNParsing(t *testing.T) { } } } + +func TestDNEqual(t *testing.T) { + testcases := []struct { + A string + B string + Equal bool + }{ + // Exact match + {"", "", true}, + {"o=A", "o=A", true}, + {"o=A", "o=B", false}, + + {"o=A,o=B", "o=A,o=B", true}, + {"o=A,o=B", "o=A,o=C", false}, + + {"o=A+o=B", "o=A+o=B", true}, + {"o=A+o=B", "o=A+o=C", false}, + + // Case mismatch in type is ignored + {"o=A", "O=A", true}, + {"o=A,o=B", "o=A,O=B", true}, + {"o=A+o=B", "o=A+O=B", true}, + + // Case mismatch in value is significant + {"o=a", "O=A", false}, + {"o=a,o=B", "o=A,O=B", false}, + {"o=a+o=B", "o=A+O=B", false}, + + // Multi-valued RDN order mismatch is ignored + {"o=A+o=B", "O=B+o=A", true}, + // Number of RDN attributes is significant + {"o=A+o=B", "O=B+o=A+O=B", false}, + + // Missing values are significant + {"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter + {"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter + + // Whitespace tests + // Matching + { + "cn=John Doe, ou=People, dc=sun.com", + "cn=John Doe, ou=People, dc=sun.com", + true, + }, + // Difference in leading/trailing chars is ignored + { + "cn=John Doe, ou=People, dc=sun.com", + "cn=John Doe,ou=People,dc=sun.com", + true, + }, + // Difference in values is significant + { + "cn=John Doe, ou=People, dc=sun.com", + "cn=John Doe, ou=People, dc=sun.com", + false, + }, + } + + for i, tc := range testcases { + a, err := ldap.ParseDN(tc.A) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + b, err := ldap.ParseDN(tc.B) + if err != nil { + t.Errorf("%d: %v", i, err) + 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) + 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) + continue + } + } +} + +func TestDNAncestor(t *testing.T) { + testcases := []struct { + A string + B string + Ancestor bool + }{ + // Exact match returns false + {"", "", false}, + {"o=A", "o=A", false}, + {"o=A,o=B", "o=A,o=B", false}, + {"o=A+o=B", "o=A+o=B", false}, + + // Mismatch + {"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false}, + + // Descendant + {"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true}, + } + + for i, tc := range testcases { + a, err := ldap.ParseDN(tc.A) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + b, err := ldap.ParseDN(tc.B) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual { + t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual) + continue + } + } +}