Skip to content

Commit

Permalink
add unmarshalling of generalizedTimestamp and DN (#434)
Browse files Browse the repository at this point in the history
* add unmarshalling of generalizedTimestamp

this uses the github.com/go-asn1-ber/asn1-ber package to parse the timestamp

* fix: do not overwrite t

* fix: err already declared

* document time.Time is parsed

* adapt error message also

* unmarshal into *DN also
  • Loading branch information
vetinari authored May 6, 2023
1 parent b50d289 commit 039466e
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 34 deletions.
37 changes: 33 additions & 4 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strconv"
"strings"
"time"

ber "github.com/go-asn1-ber/asn1-ber"
)
Expand Down Expand Up @@ -185,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, *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.
//
// Example:
//
Expand Down Expand Up @@ -216,6 +217,14 @@ 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"`
// // *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.
Expand Down Expand Up @@ -275,8 +284,28 @@ 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))
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 or []byte, 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
Expand Down
68 changes: 55 additions & 13 deletions search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ldap
import (
"reflect"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -106,28 +107,69 @@ func TestEntry_Unmarshal(t *testing.T) {
[]byte("data"),
},
},
// Tests time.Time value.
{
Name: "createdTimestamp",
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"`
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: "[email protected]",
ID: 2147483647,
LongID: 9223372036854775807,
Data: []byte("data"),
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
Cn: "mario",
Mail: "[email protected]",
ID: 2147483647,
LongID: 9223372036854775807,
Data: []byte("data"),
Created: created,
Owner: owner,
Children: children,
}
result := &User{}
err := entry.Unmarshal(result)
err = entry.Unmarshal(result)

assert.Nil(t, err)
assert.Equal(t, expect, result)
Expand Down
39 changes: 35 additions & 4 deletions v3/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strconv"
"strings"
"time"

ber "github.com/go-asn1-ber/asn1-ber"
)
Expand Down Expand Up @@ -185,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, *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.
//
// Example:
//
Expand Down Expand Up @@ -216,12 +217,22 @@ 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"`
//
// // *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 {
// // ...
// }
Expand Down Expand Up @@ -275,8 +286,28 @@ 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))
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 or []byte, 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
Expand Down
68 changes: 55 additions & 13 deletions v3/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ldap
import (
"reflect"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -106,28 +107,69 @@ func TestEntry_Unmarshal(t *testing.T) {
[]byte("data"),
},
},
// Tests time.Time value.
{
Name: "createdTimestamp",
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"`
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: "[email protected]",
ID: 2147483647,
LongID: 9223372036854775807,
Data: []byte("data"),
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
Cn: "mario",
Mail: "[email protected]",
ID: 2147483647,
LongID: 9223372036854775807,
Data: []byte("data"),
Created: created,
Owner: owner,
Children: children,
}
result := &User{}
err := entry.Unmarshal(result)
err = entry.Unmarshal(result)

assert.Nil(t, err)
assert.Equal(t, expect, result)
Expand Down

0 comments on commit 039466e

Please sign in to comment.