From cc3fb1b805ad72433da61ef6c95ffdb1fae935a5 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Thu, 4 May 2023 19:46:24 +0200 Subject: [PATCH 1/6] add unmarshalling of generalizedTimestamp this uses the github.com/go-asn1-ber/asn1-ber package to parse the timestamp --- search.go | 7 +++++++ search_test.go | 40 +++++++++++++++++++++++++++------------- v3/search.go | 7 +++++++ v3/search_test.go | 37 +++++++++++++++++++++++++------------ 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/search.go b/search.go index 66a76856..a59dc50c 100644 --- a/search.go +++ b/search.go @@ -7,6 +7,7 @@ import ( "sort" "strconv" "strings" + "time" ber "github.com/go-asn1-ber/asn1-ber" ) @@ -275,6 +276,12 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0]) } fv.SetInt(intVal) + case time.Time: + t, err := ber.ParseGeneralizedTime([]byte(values[0])) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) + } + fv.Set(reflect.ValueOf(t)) default: return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type) } diff --git a/search_test.go b/search_test.go index 6d034e4b..f7d80318 100644 --- a/search_test.go +++ b/search_test.go @@ -3,6 +3,7 @@ package ldap import ( "reflect" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -106,28 +107,41 @@ func TestEntry_Unmarshal(t *testing.T) { []byte("data"), }, }, + // Tests time.Time value. + { + Name: "createdTimestamp", + Values: []string{"202305041930Z"}, + ByteValues: nil, + }, }, } type User struct { - Dn string `ldap:"dn"` - Cn string `ldap:"cn"` - Mail string `ldap:"mail"` - ID int `ldap:"id"` - LongID int64 `ldap:"longId"` - Data []byte `ldap:"data"` + Dn string `ldap:"dn"` + Cn string `ldap:"cn"` + Mail string `ldap:"mail"` + ID int `ldap:"id"` + LongID int64 `ldap:"longId"` + Data []byte `ldap:"data"` + Created time.Time `ldap:"createdTimestamp"` + } + + created, err := time.Parse("200601021504Z", "202305041930Z") + if err != nil { + t.Errorf("failed to parse ref time: %s", err) } expect := &User{ - Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", - Cn: "mario", - Mail: "mario@go-ldap.com", - ID: 2147483647, - LongID: 9223372036854775807, - Data: []byte("data"), + Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", + Cn: "mario", + Mail: "mario@go-ldap.com", + ID: 2147483647, + LongID: 9223372036854775807, + Data: []byte("data"), + Created: created, } result := &User{} - err := entry.Unmarshal(result) + err = entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) diff --git a/v3/search.go b/v3/search.go index 66a76856..a59dc50c 100644 --- a/v3/search.go +++ b/v3/search.go @@ -7,6 +7,7 @@ import ( "sort" "strconv" "strings" + "time" ber "github.com/go-asn1-ber/asn1-ber" ) @@ -275,6 +276,12 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0]) } fv.SetInt(intVal) + case time.Time: + t, err := ber.ParseGeneralizedTime([]byte(values[0])) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) + } + fv.Set(reflect.ValueOf(t)) default: return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type) } diff --git a/v3/search_test.go b/v3/search_test.go index 6d034e4b..6f7d73b7 100644 --- a/v3/search_test.go +++ b/v3/search_test.go @@ -3,6 +3,7 @@ package ldap import ( "reflect" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -106,25 +107,37 @@ func TestEntry_Unmarshal(t *testing.T) { []byte("data"), }, }, + // Tests time.Time value. + { + Name: "createdTimestamp", + Values: []string{"202305041930Z"}, + ByteValues: nil, + }, }, } type User struct { - Dn string `ldap:"dn"` - Cn string `ldap:"cn"` - Mail string `ldap:"mail"` - ID int `ldap:"id"` - LongID int64 `ldap:"longId"` - Data []byte `ldap:"data"` + Dn string `ldap:"dn"` + Cn string `ldap:"cn"` + Mail string `ldap:"mail"` + ID int `ldap:"id"` + LongID int64 `ldap:"longId"` + Data []byte `ldap:"data"` + Created time.Time `ldap:"createdTimestamp"` } + t, err := time.Parse("200601021504Z", "202305041930Z") + if err != nil { + t.Errorf("failed to parse ref time: %s", err) + } expect := &User{ - Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", - Cn: "mario", - Mail: "mario@go-ldap.com", - ID: 2147483647, - LongID: 9223372036854775807, - Data: []byte("data"), + Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", + Cn: "mario", + Mail: "mario@go-ldap.com", + ID: 2147483647, + LongID: 9223372036854775807, + Data: []byte("data"), + Created: t, } result := &User{} err := entry.Unmarshal(result) From 98e4f4b1a9d9b8c9d533d68e93a76827f645f9de Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Thu, 4 May 2023 19:49:56 +0200 Subject: [PATCH 2/6] fix: do not overwrite t --- v3/search_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/search_test.go b/v3/search_test.go index 6f7d73b7..8df129a9 100644 --- a/v3/search_test.go +++ b/v3/search_test.go @@ -126,7 +126,7 @@ func TestEntry_Unmarshal(t *testing.T) { Created time.Time `ldap:"createdTimestamp"` } - t, err := time.Parse("200601021504Z", "202305041930Z") + created, err := time.Parse("200601021504Z", "202305041930Z") if err != nil { t.Errorf("failed to parse ref time: %s", err) } @@ -137,7 +137,7 @@ func TestEntry_Unmarshal(t *testing.T) { ID: 2147483647, LongID: 9223372036854775807, Data: []byte("data"), - Created: t, + Created: created, } result := &User{} err := entry.Unmarshal(result) From b8b015395be87c33f39a5104122d1dbaffe999ad Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Thu, 4 May 2023 19:51:15 +0200 Subject: [PATCH 3/6] fix: err already declared --- v3/search_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/search_test.go b/v3/search_test.go index 8df129a9..b4b2f334 100644 --- a/v3/search_test.go +++ b/v3/search_test.go @@ -140,7 +140,7 @@ func TestEntry_Unmarshal(t *testing.T) { Created: created, } result := &User{} - err := entry.Unmarshal(result) + err = entry.Unmarshal(result) assert.Nil(t, err) assert.Equal(t, expect, result) From ce9ec948ed4f208e94917f402726161132cc7ee6 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 6 May 2023 08:00:56 +0200 Subject: [PATCH 4/6] document time.Time is parsed --- search.go | 9 ++++++--- v3/search.go | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/search.go b/search.go index a59dc50c..65d20ab8 100644 --- a/search.go +++ b/search.go @@ -186,9 +186,9 @@ func readTag(f reflect.StructField) (string, bool) { // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type -// string, []string, int, int64 or []byte. Other field types will not be -// regarded. If the field type is a string or int but multiple attribute -// values are returned, the first value will be used to fill the field. +// string, []string, int, int64, []byte or time.Time. Other field types +// will not be regarded. If the field type is a string or int but multiple +// attribute values are returned, the first value will be used to fill the field. // // Example: // @@ -217,6 +217,9 @@ func readTag(f reflect.StructField) (string, bool) { // // values. // Data []byte `ldap:"data"` // +// // Time is parsed with the generalizedTime spec into a time.Time +// Created time.Time `ldap:"createdTimestamp"` +// // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string // // (or string array) and convert it to the desired type afterwards. diff --git a/v3/search.go b/v3/search.go index a59dc50c..65d20ab8 100644 --- a/v3/search.go +++ b/v3/search.go @@ -186,9 +186,9 @@ func readTag(f reflect.StructField) (string, bool) { // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type -// string, []string, int, int64 or []byte. Other field types will not be -// regarded. If the field type is a string or int but multiple attribute -// values are returned, the first value will be used to fill the field. +// string, []string, int, int64, []byte or time.Time. Other field types +// will not be regarded. If the field type is a string or int but multiple +// attribute values are returned, the first value will be used to fill the field. // // Example: // @@ -217,6 +217,9 @@ func readTag(f reflect.StructField) (string, bool) { // // values. // Data []byte `ldap:"data"` // +// // Time is parsed with the generalizedTime spec into a time.Time +// Created time.Time `ldap:"createdTimestamp"` +// // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string // // (or string array) and convert it to the desired type afterwards. From 4f224f2a55cc999029a6111d3f54faf9302645d2 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 6 May 2023 08:10:54 +0200 Subject: [PATCH 5/6] adapt error message also --- search.go | 2 +- v3/search.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/search.go b/search.go index 65d20ab8..310db64e 100644 --- a/search.go +++ b/search.go @@ -286,7 +286,7 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { } fv.Set(reflect.ValueOf(t)) default: - return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type) + return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte or time.Time, got %v", ft.Type) } } return diff --git a/v3/search.go b/v3/search.go index 65d20ab8..310db64e 100644 --- a/v3/search.go +++ b/v3/search.go @@ -286,7 +286,7 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { } fv.Set(reflect.ValueOf(t)) default: - return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type) + return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte or time.Time, got %v", ft.Type) } } return From 9d3242447ba9b2f7e46f5174c831944966d0abaa Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Sat, 6 May 2023 10:32:09 +0200 Subject: [PATCH 6/6] unmarshal into *DN also --- search.go | 23 +++++++++++++++++-- search_test.go | 56 ++++++++++++++++++++++++++++++++++------------ v3/search.go | 25 +++++++++++++++++++-- v3/search_test.go | 57 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 129 insertions(+), 32 deletions(-) diff --git a/search.go b/search.go index 310db64e..428a564d 100644 --- a/search.go +++ b/search.go @@ -186,7 +186,7 @@ func readTag(f reflect.StructField) (string, bool) { // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type -// string, []string, int, int64, []byte or time.Time. Other field types +// string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types // will not be regarded. If the field type is a string or int but multiple // attribute values are returned, the first value will be used to fill the field. // @@ -219,6 +219,11 @@ func readTag(f reflect.StructField) (string, bool) { // // // Time is parsed with the generalizedTime spec into a time.Time // Created time.Time `ldap:"createdTimestamp"` +// // *DN is parsed with the ParseDN +// Owner *ldap.DN `ldap:"owner"` +// +// // []*DN is parsed with the ParseDN +// Children []*ldap.DN `ldap:"children"` // // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string @@ -285,8 +290,22 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) } fv.Set(reflect.ValueOf(t)) + case *DN: + dn, err := ParseDN(values[0]) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0]) + } + fv.Set(reflect.ValueOf(dn)) + case []*DN: + for _, item := range values { + dn, err := ParseDN(item) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item) + } + fv.Set(reflect.Append(fv, reflect.ValueOf(dn))) + } default: - return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte or time.Time, got %v", ft.Type) + return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type) } } return diff --git a/search_test.go b/search_test.go index f7d80318..2386513d 100644 --- a/search_test.go +++ b/search_test.go @@ -113,32 +113,60 @@ func TestEntry_Unmarshal(t *testing.T) { Values: []string{"202305041930Z"}, ByteValues: nil, }, + // Tests *DN value + { + Name: "owner", + Values: []string{"uid=foo,dc=example,dc=org"}, + ByteValues: nil, + }, + // Tests []*DN value + { + Name: "children", + Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"}, + ByteValues: nil, + }, }, } type User struct { - Dn string `ldap:"dn"` - Cn string `ldap:"cn"` - Mail string `ldap:"mail"` - ID int `ldap:"id"` - LongID int64 `ldap:"longId"` - Data []byte `ldap:"data"` - Created time.Time `ldap:"createdTimestamp"` + Dn string `ldap:"dn"` + Cn string `ldap:"cn"` + Mail string `ldap:"mail"` + ID int `ldap:"id"` + LongID int64 `ldap:"longId"` + Data []byte `ldap:"data"` + Created time.Time `ldap:"createdTimestamp"` + Owner *DN `ldap:"owner"` + Children []*DN `ldap:"children"` } created, err := time.Parse("200601021504Z", "202305041930Z") if err != nil { t.Errorf("failed to parse ref time: %s", err) } + owner, err := ParseDN("uid=foo,dc=example,dc=org") + if err != nil { + t.Errorf("failed to parse ref DN: %s", err) + } + var children []*DN + for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} { + dn, err := ParseDN(child) + if err != nil { + t.Errorf("failed to parse child ref DN: %s", err) + } + children = append(children, dn) + } expect := &User{ - Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", - Cn: "mario", - Mail: "mario@go-ldap.com", - ID: 2147483647, - LongID: 9223372036854775807, - Data: []byte("data"), - Created: created, + Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", + Cn: "mario", + Mail: "mario@go-ldap.com", + ID: 2147483647, + LongID: 9223372036854775807, + Data: []byte("data"), + Created: created, + Owner: owner, + Children: children, } result := &User{} err = entry.Unmarshal(result) diff --git a/v3/search.go b/v3/search.go index 310db64e..2fae94f4 100644 --- a/v3/search.go +++ b/v3/search.go @@ -186,7 +186,7 @@ func readTag(f reflect.StructField) (string, bool) { // Unmarshal parses the Entry in the value pointed to by i // // Currently, this methods only supports struct fields of type -// string, []string, int, int64, []byte or time.Time. Other field types +// string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types // will not be regarded. If the field type is a string or int but multiple // attribute values are returned, the first value will be used to fill the field. // @@ -220,12 +220,19 @@ func readTag(f reflect.StructField) (string, bool) { // // Time is parsed with the generalizedTime spec into a time.Time // Created time.Time `ldap:"createdTimestamp"` // +// // *DN is parsed with the ParseDN +// Owner *ldap.DN `ldap:"owner"` +// +// // []*DN is parsed with the ParseDN +// Children []*ldap.DN `ldap:"children"` +// // // This won't work, as the field is not of type string. For this // // to work, you'll have to temporarily store the result in string // // (or string array) and convert it to the desired type afterwards. // UserAccountControl uint32 `ldap:"userPrincipalName"` // } // user := UserEntry{} +// // if err := result.Unmarshal(&user); err != nil { // // ... // } @@ -285,8 +292,22 @@ func (e *Entry) Unmarshal(i interface{}) (err error) { return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0]) } fv.Set(reflect.ValueOf(t)) + case *DN: + dn, err := ParseDN(values[0]) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0]) + } + fv.Set(reflect.ValueOf(dn)) + case []*DN: + for _, item := range values { + dn, err := ParseDN(item) + if err != nil { + return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item) + } + fv.Set(reflect.Append(fv, reflect.ValueOf(dn))) + } default: - return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte or time.Time, got %v", ft.Type) + return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type) } } return diff --git a/v3/search_test.go b/v3/search_test.go index b4b2f334..2386513d 100644 --- a/v3/search_test.go +++ b/v3/search_test.go @@ -113,31 +113,60 @@ func TestEntry_Unmarshal(t *testing.T) { Values: []string{"202305041930Z"}, ByteValues: nil, }, + // Tests *DN value + { + Name: "owner", + Values: []string{"uid=foo,dc=example,dc=org"}, + ByteValues: nil, + }, + // Tests []*DN value + { + Name: "children", + Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"}, + ByteValues: nil, + }, }, } type User struct { - Dn string `ldap:"dn"` - Cn string `ldap:"cn"` - Mail string `ldap:"mail"` - ID int `ldap:"id"` - LongID int64 `ldap:"longId"` - Data []byte `ldap:"data"` - Created time.Time `ldap:"createdTimestamp"` + Dn string `ldap:"dn"` + Cn string `ldap:"cn"` + Mail string `ldap:"mail"` + ID int `ldap:"id"` + LongID int64 `ldap:"longId"` + Data []byte `ldap:"data"` + Created time.Time `ldap:"createdTimestamp"` + Owner *DN `ldap:"owner"` + Children []*DN `ldap:"children"` } created, err := time.Parse("200601021504Z", "202305041930Z") if err != nil { t.Errorf("failed to parse ref time: %s", err) } + owner, err := ParseDN("uid=foo,dc=example,dc=org") + if err != nil { + t.Errorf("failed to parse ref DN: %s", err) + } + var children []*DN + for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} { + dn, err := ParseDN(child) + if err != nil { + t.Errorf("failed to parse child ref DN: %s", err) + } + children = append(children, dn) + } + expect := &User{ - Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", - Cn: "mario", - Mail: "mario@go-ldap.com", - ID: 2147483647, - LongID: 9223372036854775807, - Data: []byte("data"), - Created: created, + Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com", + Cn: "mario", + Mail: "mario@go-ldap.com", + ID: 2147483647, + LongID: 9223372036854775807, + Data: []byte("data"), + Created: created, + Owner: owner, + Children: children, } result := &User{} err = entry.Unmarshal(result)