From db7e21b8baefa11b4d7fed7cb1864132ad2adfd4 Mon Sep 17 00:00:00 2001 From: Ashwin Ammbekar Date: Thu, 22 Mar 2018 22:43:11 -0700 Subject: [PATCH 1/7] Support DN to string conversion #154 --- dn.go | 21 ++++++++++++++++++++ dn_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/dn.go b/dn.go index 1ee9a1b9..8cd473c2 100644 --- a/dn.go +++ b/dn.go @@ -208,6 +208,27 @@ func (d *DN) AncestorOf(other *DN) bool { return true } +// String returns string representation of the DN Object with required +// escaping of spaces and formatting applied +func (d DN) String() string { + var buffer bytes.Buffer + + for i := range d.RDNs { + if i > 0 { + buffer.WriteString(",") + } + for j := range d.RDNs[i].Attributes { + if j > 0 { + buffer.WriteString("+") + } + buffer.WriteString(d.RDNs[i].Attributes[j].Type) + buffer.WriteString("=") + buffer.WriteString(d.RDNs[i].Attributes[j].Value) + } + } + return buffer.String() +} + // 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. diff --git a/dn_test.go b/dn_test.go index af5fc146..6472afb7 100644 --- a/dn_test.go +++ b/dn_test.go @@ -2,9 +2,10 @@ package ldap_test import ( "reflect" + "strings" "testing" - "gopkg.in/ldap.v2" + "github.com/ldap" ) func TestSuccessfulDNParsing(t *testing.T) { @@ -173,6 +174,61 @@ func TestDNEqual(t *testing.T) { } } +func TestDNString(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=B", true}, + {"o= A +o= B", "o=A+o=B", 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 + + // Real examples + // 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 + } + dnstr := a.String() + + if tc.Equal == (strings.Compare(tc.B, dnstr) != 0) { + t.Errorf("%d: when converting to string '%s', got '%s' expected and '%s'", i, tc.A, dnstr, tc.B) + continue + } + } +} + func TestDNAncestor(t *testing.T) { testcases := []struct { A string From bbc9d2101dbc09e8a9a8750dece625509076be13 Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Thu, 22 Mar 2018 22:50:02 -0700 Subject: [PATCH 2/7] Support DN to string conversion #154 --- dn_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dn_test.go b/dn_test.go index 6472afb7..de3881f8 100644 --- a/dn_test.go +++ b/dn_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/ldap" + "gopkg.in/ldap.v2" ) func TestSuccessfulDNParsing(t *testing.T) { From d8872bce5a4de6f68e5fd60de8214a3df26d513f Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Fri, 23 Mar 2018 18:14:23 -0700 Subject: [PATCH 3/7] Support DN to string conversion #154 --- dn.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++- dn_test.go | 29 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/dn.go b/dn.go index 8cd473c2..49caf309 100644 --- a/dn.go +++ b/dn.go @@ -46,6 +46,7 @@ import ( enchex "encoding/hex" "errors" "fmt" + "regexp" "strings" "gopkg.in/asn1-ber.v1" @@ -223,12 +224,66 @@ func (d DN) String() string { } buffer.WriteString(d.RDNs[i].Attributes[j].Type) buffer.WriteString("=") - buffer.WriteString(d.RDNs[i].Attributes[j].Value) + //Escape the value before building DN string + val := EscapeAttrValue(d.RDNs[i].Attributes[j].Value) + buffer.WriteString(val) } } return buffer.String() } +//EscapeAttrValue function escapes the LDAP special characters from the value part +//of the RDN +/** +distinguishedName = [ relativeDistinguishedName + *( COMMA relativeDistinguishedName ) ] +relativeDistinguishedName = attributeTypeAndValue + *( PLUS attributeTypeAndValue ) +attributeTypeAndValue = attributeType EQUALS attributeValue +attributeType = descr / numericoid +attributeValue = string / hexstring + +; The following characters are to be escaped when they appear +; in the value to be encoded: ESC, one of , leading +; SHARP or SPACE, trailing SPACE, and NULL. +string = [ ( leadchar / pair ) [ *( stringchar / pair ) + ( trailchar / pair ) ] ] + +leadchar = LUTF1 / UTFMB +LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A / + %x3D / %x3F-5B / %x5D-7F + +trailchar = TUTF1 / UTFMB +TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A / +%x3D / %x3F-5B / %x5D-7F + +stringchar = SUTF1 / UTFMB +SUTF1 = %x01-21 / %x23-2A / %x2D-3A / +%x3D / %x3F-5B / %x5D-7F + +pair = ESC ( ESC / special / hexpair ) +special = escaped / SPACE / SHARP / EQUALS +escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE +hexstring = SHARP 1*hexpair +hexpair = HEX HEX + +where the productions , , , , +, , , , , , , , +, , and are defined in [RFC4512]. +*/ +func EscapeAttrValue(in string) string { + + escape := regexp.MustCompile("[,\\#+<>;\"=]") + out := escape.ReplaceAllString(in, "\\$0") + if strings.HasPrefix(out, " ") { + out = "\\" + out + } + if strings.HasSuffix(out, " ") { + out = out[:len(out)-1] + "\\ " + } + return out +} + // 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. diff --git a/dn_test.go b/dn_test.go index de3881f8..7c493800 100644 --- a/dn_test.go +++ b/dn_test.go @@ -174,6 +174,35 @@ func TestDNEqual(t *testing.T) { } } +func TestEscapeAttrValue(t *testing.T) { + testcases := []struct { + A string + B string + Equal bool + }{ + // Exact match + {"", "", true}, + {"o=A", "o\\=A", true}, + {"o,A", "o\\,A", true}, + {"o#A", "o\\#A", true}, + {"oA", "o\\>A", true}, + {"o;A", "o\\;A", true}, + {"o\"A", "o\\\"A", true}, + {"o+A", "o\\+A", true}, + {" o=A", "\\ o\\=A", true}, + {"o=A ", "o\\=A\\ ", true}, + {" o=A ", "\\ o\\=A\\ ", true}, + } + for i, tc := range testcases { + a := ldap.EscapeAttrValue(tc.A) + if strings.Compare(tc.B, a) != 0 { + t.Errorf("%d: when escaping string '%s', got '%s' expected and '%s'", i, tc.A, a, tc.B) + continue + } + } +} + func TestDNString(t *testing.T) { testcases := []struct { A string From a4bd60965535007eee78f9d32ff1cb5912b4002f Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Fri, 23 Mar 2018 18:30:35 -0700 Subject: [PATCH 4/7] Fix build issues with older go versions due to string.Compare --- dn_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dn_test.go b/dn_test.go index 7c493800..3f6584ee 100644 --- a/dn_test.go +++ b/dn_test.go @@ -2,7 +2,6 @@ package ldap_test import ( "reflect" - "strings" "testing" "gopkg.in/ldap.v2" @@ -196,7 +195,7 @@ func TestEscapeAttrValue(t *testing.T) { } for i, tc := range testcases { a := ldap.EscapeAttrValue(tc.A) - if strings.Compare(tc.B, a) != 0 { + if tc.B != a { t.Errorf("%d: when escaping string '%s', got '%s' expected and '%s'", i, tc.A, a, tc.B) continue } @@ -251,7 +250,7 @@ func TestDNString(t *testing.T) { } dnstr := a.String() - if tc.Equal == (strings.Compare(tc.B, dnstr) != 0) { + if tc.Equal == (tc.B != dnstr) { t.Errorf("%d: when converting to string '%s', got '%s' expected and '%s'", i, tc.A, dnstr, tc.B) continue } From 0c0d0b4234debe19de6e5ebc7fac95562a36675e Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Tue, 7 Aug 2018 00:02:58 -0700 Subject: [PATCH 5/7] fix unit test pointing to external gopkg.in/ldap and escape chars --- dn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dn_test.go b/dn_test.go index 62d4f832..2c90eb88 100644 --- a/dn_test.go +++ b/dn_test.go @@ -192,7 +192,7 @@ func TestEscapeAttrValue(t *testing.T) { {" o=A ", "\\ o\\=A\\ ", true}, } for i, tc := range testcases { - a := ldap.EscapeAttrValue(tc.A) + a := EscapeAttrValue(tc.A) if tc.B != a { t.Errorf("%d: when escaping string '%s', got '%s' expected and '%s'", i, tc.A, a, tc.B) continue @@ -241,7 +241,7 @@ func TestDNString(t *testing.T) { } for i, tc := range testcases { - a, err := ldap.ParseDN(tc.A) + a, err := ParseDN(tc.A) if err != nil { t.Errorf("%d: %v", i, err) continue From 7c25f64ecf64b4cad9482e0779e16b433ad10eed Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Wed, 24 Oct 2018 15:24:13 -0700 Subject: [PATCH 6/7] RND String() support --- dn.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/dn.go b/dn.go index 841d05a9..ac237e44 100644 --- a/dn.go +++ b/dn.go @@ -213,6 +213,23 @@ func (d *DN) AncestorOf(other *DN) bool { return true } +// String returns string representation of the RDN Object with required +// escaping of spaces and formatting applied +func (rdn RelativeDN) String() string { + var buffer bytes.Buffer + for j := range rdn.Attributes { + if j > 0 { + buffer.WriteString("+") + } + buffer.WriteString(rdn.Attributes[j].Type) + buffer.WriteString("=") + //Escape the value before building DN string + val := EscapeAttrValue(rdn.Attributes[j].Value) + buffer.WriteString(val) + } + return buffer.String() +} + // String returns string representation of the DN Object with required // escaping of spaces and formatting applied func (d DN) String() string { @@ -222,16 +239,7 @@ func (d DN) String() string { if i > 0 { buffer.WriteString(",") } - for j := range d.RDNs[i].Attributes { - if j > 0 { - buffer.WriteString("+") - } - buffer.WriteString(d.RDNs[i].Attributes[j].Type) - buffer.WriteString("=") - //Escape the value before building DN string - val := EscapeAttrValue(d.RDNs[i].Attributes[j].Value) - buffer.WriteString(val) - } + buffer.WriteString(d.RDNs[i].String()) } return buffer.String() } From fe8bf1a81fb366b380e645f98c77628df5cd89d6 Mon Sep 17 00:00:00 2001 From: Ashwin Ambekar Date: Wed, 24 Oct 2018 15:33:13 -0700 Subject: [PATCH 7/7] Lint fixes of RND String() support --- dn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dn.go b/dn.go index ac237e44..e80ce6cd 100644 --- a/dn.go +++ b/dn.go @@ -215,16 +215,16 @@ func (d *DN) AncestorOf(other *DN) bool { // String returns string representation of the RDN Object with required // escaping of spaces and formatting applied -func (rdn RelativeDN) String() string { +func (r RelativeDN) String() string { var buffer bytes.Buffer - for j := range rdn.Attributes { + for j := range r.Attributes { if j > 0 { buffer.WriteString("+") } - buffer.WriteString(rdn.Attributes[j].Type) + buffer.WriteString(r.Attributes[j].Type) buffer.WriteString("=") //Escape the value before building DN string - val := EscapeAttrValue(rdn.Attributes[j].Value) + val := EscapeAttrValue(r.Attributes[j].Value) buffer.WriteString(val) } return buffer.String()