Skip to content

Commit 039466e

Browse files
authored
add unmarshalling of generalizedTimestamp and DN (#434)
* 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
1 parent b50d289 commit 039466e

File tree

4 files changed

+178
-34
lines changed

4 files changed

+178
-34
lines changed

search.go

+33-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"sort"
88
"strconv"
99
"strings"
10+
"time"
1011

1112
ber "github.com/go-asn1-ber/asn1-ber"
1213
)
@@ -185,9 +186,9 @@ func readTag(f reflect.StructField) (string, bool) {
185186
// Unmarshal parses the Entry in the value pointed to by i
186187
//
187188
// Currently, this methods only supports struct fields of type
188-
// string, []string, int, int64 or []byte. Other field types will not be
189-
// regarded. If the field type is a string or int but multiple attribute
190-
// values are returned, the first value will be used to fill the field.
189+
// string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types
190+
// will not be regarded. If the field type is a string or int but multiple
191+
// attribute values are returned, the first value will be used to fill the field.
191192
//
192193
// Example:
193194
//
@@ -216,6 +217,14 @@ func readTag(f reflect.StructField) (string, bool) {
216217
// // values.
217218
// Data []byte `ldap:"data"`
218219
//
220+
// // Time is parsed with the generalizedTime spec into a time.Time
221+
// Created time.Time `ldap:"createdTimestamp"`
222+
// // *DN is parsed with the ParseDN
223+
// Owner *ldap.DN `ldap:"owner"`
224+
//
225+
// // []*DN is parsed with the ParseDN
226+
// Children []*ldap.DN `ldap:"children"`
227+
//
219228
// // This won't work, as the field is not of type string. For this
220229
// // to work, you'll have to temporarily store the result in string
221230
// // (or string array) and convert it to the desired type afterwards.
@@ -275,8 +284,28 @@ func (e *Entry) Unmarshal(i interface{}) (err error) {
275284
return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0])
276285
}
277286
fv.SetInt(intVal)
287+
case time.Time:
288+
t, err := ber.ParseGeneralizedTime([]byte(values[0]))
289+
if err != nil {
290+
return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0])
291+
}
292+
fv.Set(reflect.ValueOf(t))
293+
case *DN:
294+
dn, err := ParseDN(values[0])
295+
if err != nil {
296+
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0])
297+
}
298+
fv.Set(reflect.ValueOf(dn))
299+
case []*DN:
300+
for _, item := range values {
301+
dn, err := ParseDN(item)
302+
if err != nil {
303+
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item)
304+
}
305+
fv.Set(reflect.Append(fv, reflect.ValueOf(dn)))
306+
}
278307
default:
279-
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type)
308+
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type)
280309
}
281310
}
282311
return

search_test.go

+55-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ldap
33
import (
44
"reflect"
55
"testing"
6+
"time"
67

78
"github.com/stretchr/testify/assert"
89
)
@@ -106,28 +107,69 @@ func TestEntry_Unmarshal(t *testing.T) {
106107
[]byte("data"),
107108
},
108109
},
110+
// Tests time.Time value.
111+
{
112+
Name: "createdTimestamp",
113+
Values: []string{"202305041930Z"},
114+
ByteValues: nil,
115+
},
116+
// Tests *DN value
117+
{
118+
Name: "owner",
119+
Values: []string{"uid=foo,dc=example,dc=org"},
120+
ByteValues: nil,
121+
},
122+
// Tests []*DN value
123+
{
124+
Name: "children",
125+
Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"},
126+
ByteValues: nil,
127+
},
109128
},
110129
}
111130

112131
type User struct {
113-
Dn string `ldap:"dn"`
114-
Cn string `ldap:"cn"`
115-
Mail string `ldap:"mail"`
116-
ID int `ldap:"id"`
117-
LongID int64 `ldap:"longId"`
118-
Data []byte `ldap:"data"`
132+
Dn string `ldap:"dn"`
133+
Cn string `ldap:"cn"`
134+
Mail string `ldap:"mail"`
135+
ID int `ldap:"id"`
136+
LongID int64 `ldap:"longId"`
137+
Data []byte `ldap:"data"`
138+
Created time.Time `ldap:"createdTimestamp"`
139+
Owner *DN `ldap:"owner"`
140+
Children []*DN `ldap:"children"`
141+
}
142+
143+
created, err := time.Parse("200601021504Z", "202305041930Z")
144+
if err != nil {
145+
t.Errorf("failed to parse ref time: %s", err)
146+
}
147+
owner, err := ParseDN("uid=foo,dc=example,dc=org")
148+
if err != nil {
149+
t.Errorf("failed to parse ref DN: %s", err)
150+
}
151+
var children []*DN
152+
for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} {
153+
dn, err := ParseDN(child)
154+
if err != nil {
155+
t.Errorf("failed to parse child ref DN: %s", err)
156+
}
157+
children = append(children, dn)
119158
}
120159

121160
expect := &User{
122-
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
123-
Cn: "mario",
124-
125-
ID: 2147483647,
126-
LongID: 9223372036854775807,
127-
Data: []byte("data"),
161+
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
162+
Cn: "mario",
163+
164+
ID: 2147483647,
165+
LongID: 9223372036854775807,
166+
Data: []byte("data"),
167+
Created: created,
168+
Owner: owner,
169+
Children: children,
128170
}
129171
result := &User{}
130-
err := entry.Unmarshal(result)
172+
err = entry.Unmarshal(result)
131173

132174
assert.Nil(t, err)
133175
assert.Equal(t, expect, result)

v3/search.go

+35-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"sort"
88
"strconv"
99
"strings"
10+
"time"
1011

1112
ber "github.com/go-asn1-ber/asn1-ber"
1213
)
@@ -185,9 +186,9 @@ func readTag(f reflect.StructField) (string, bool) {
185186
// Unmarshal parses the Entry in the value pointed to by i
186187
//
187188
// Currently, this methods only supports struct fields of type
188-
// string, []string, int, int64 or []byte. Other field types will not be
189-
// regarded. If the field type is a string or int but multiple attribute
190-
// values are returned, the first value will be used to fill the field.
189+
// string, []string, int, int64, []byte, *DN, []*DN or time.Time. Other field types
190+
// will not be regarded. If the field type is a string or int but multiple
191+
// attribute values are returned, the first value will be used to fill the field.
191192
//
192193
// Example:
193194
//
@@ -216,12 +217,22 @@ func readTag(f reflect.StructField) (string, bool) {
216217
// // values.
217218
// Data []byte `ldap:"data"`
218219
//
220+
// // Time is parsed with the generalizedTime spec into a time.Time
221+
// Created time.Time `ldap:"createdTimestamp"`
222+
//
223+
// // *DN is parsed with the ParseDN
224+
// Owner *ldap.DN `ldap:"owner"`
225+
//
226+
// // []*DN is parsed with the ParseDN
227+
// Children []*ldap.DN `ldap:"children"`
228+
//
219229
// // This won't work, as the field is not of type string. For this
220230
// // to work, you'll have to temporarily store the result in string
221231
// // (or string array) and convert it to the desired type afterwards.
222232
// UserAccountControl uint32 `ldap:"userPrincipalName"`
223233
// }
224234
// user := UserEntry{}
235+
//
225236
// if err := result.Unmarshal(&user); err != nil {
226237
// // ...
227238
// }
@@ -275,8 +286,28 @@ func (e *Entry) Unmarshal(i interface{}) (err error) {
275286
return fmt.Errorf("ldap: could not parse value '%s' into int field", values[0])
276287
}
277288
fv.SetInt(intVal)
289+
case time.Time:
290+
t, err := ber.ParseGeneralizedTime([]byte(values[0]))
291+
if err != nil {
292+
return fmt.Errorf("ldap: could not parse value '%s' into time.Time field", values[0])
293+
}
294+
fv.Set(reflect.ValueOf(t))
295+
case *DN:
296+
dn, err := ParseDN(values[0])
297+
if err != nil {
298+
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", values[0])
299+
}
300+
fv.Set(reflect.ValueOf(dn))
301+
case []*DN:
302+
for _, item := range values {
303+
dn, err := ParseDN(item)
304+
if err != nil {
305+
return fmt.Errorf("ldap: could not parse value '%s' into *ldap.DN field", item)
306+
}
307+
fv.Set(reflect.Append(fv, reflect.ValueOf(dn)))
308+
}
278309
default:
279-
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64 or []byte, got %v", ft.Type)
310+
return fmt.Errorf("ldap: expected field to be of type string, []string, int, int64, []byte, *DN, []*DN or time.Time, got %v", ft.Type)
280311
}
281312
}
282313
return

v3/search_test.go

+55-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ldap
33
import (
44
"reflect"
55
"testing"
6+
"time"
67

78
"github.com/stretchr/testify/assert"
89
)
@@ -106,28 +107,69 @@ func TestEntry_Unmarshal(t *testing.T) {
106107
[]byte("data"),
107108
},
108109
},
110+
// Tests time.Time value.
111+
{
112+
Name: "createdTimestamp",
113+
Values: []string{"202305041930Z"},
114+
ByteValues: nil,
115+
},
116+
// Tests *DN value
117+
{
118+
Name: "owner",
119+
Values: []string{"uid=foo,dc=example,dc=org"},
120+
ByteValues: nil,
121+
},
122+
// Tests []*DN value
123+
{
124+
Name: "children",
125+
Values: []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"},
126+
ByteValues: nil,
127+
},
109128
},
110129
}
111130

112131
type User struct {
113-
Dn string `ldap:"dn"`
114-
Cn string `ldap:"cn"`
115-
Mail string `ldap:"mail"`
116-
ID int `ldap:"id"`
117-
LongID int64 `ldap:"longId"`
118-
Data []byte `ldap:"data"`
132+
Dn string `ldap:"dn"`
133+
Cn string `ldap:"cn"`
134+
Mail string `ldap:"mail"`
135+
ID int `ldap:"id"`
136+
LongID int64 `ldap:"longId"`
137+
Data []byte `ldap:"data"`
138+
Created time.Time `ldap:"createdTimestamp"`
139+
Owner *DN `ldap:"owner"`
140+
Children []*DN `ldap:"children"`
141+
}
142+
143+
created, err := time.Parse("200601021504Z", "202305041930Z")
144+
if err != nil {
145+
t.Errorf("failed to parse ref time: %s", err)
146+
}
147+
owner, err := ParseDN("uid=foo,dc=example,dc=org")
148+
if err != nil {
149+
t.Errorf("failed to parse ref DN: %s", err)
150+
}
151+
var children []*DN
152+
for _, child := range []string{"uid=bar,dc=example,dc=org", "uid=baz,dc=example,dc=org"} {
153+
dn, err := ParseDN(child)
154+
if err != nil {
155+
t.Errorf("failed to parse child ref DN: %s", err)
156+
}
157+
children = append(children, dn)
119158
}
120159

121160
expect := &User{
122-
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
123-
Cn: "mario",
124-
125-
ID: 2147483647,
126-
LongID: 9223372036854775807,
127-
Data: []byte("data"),
161+
Dn: "cn=mario,ou=Users,dc=go-ldap,dc=github,dc=com",
162+
Cn: "mario",
163+
164+
ID: 2147483647,
165+
LongID: 9223372036854775807,
166+
Data: []byte("data"),
167+
Created: created,
168+
Owner: owner,
169+
Children: children,
128170
}
129171
result := &User{}
130-
err := entry.Unmarshal(result)
172+
err = entry.Unmarshal(result)
131173

132174
assert.Nil(t, err)
133175
assert.Equal(t, expect, result)

0 commit comments

Comments
 (0)