Skip to content

Commit c4bbd62

Browse files
authored
refactor(cachetest): build fixtures via ldap.NewObject instead of unsafe (#633)
simple-ldap-go v1.14.0 adds `NewObject(cn, dn) Object`. Until then `Object.cn` / `Object.dn` were written only by the package-internal `objectFromEntry`, so a consumer that wanted a fixture with a known DN had no public way to build one — `internal/ldap_cache/cachetest` reached the fields through `reflect` + `unsafe.Pointer(...UnsafeAddr())`, which gosec flags as G103. See netresearch/simple-ldap-go#191. The three fixture constructors (`NewUserWithDN`, `NewGroupWithDN`, `NewComputerWithDN`) now set the embedded `Object` via `ldap.NewObject(cn, dn)` in a plain composite literal. That removes `setObjectFields`, the `reflect` and `unsafe` imports, and the two `#nosec G103` annotations together with the comment block that justified them — a suppression that no longer suppresses anything. `cachetest` stays an ordinary (non-`_test.go`) package: it is imported from another package's tests, which `_test.go` files cannot be. Dependency bumped to v1.14.0; `go mod tidy` also dropped stale `go.sum` lines for `gofiber/storage/bbolt` v2.1.8, `gofiber/utils` v2.1.0 and `shamaton/msgpack` v3.1.2, none of which were the selected versions. Not covered here: `internal/ldap_cache/test_helpers_test.go` carries a second copy of the same reflect+unsafe writer for that package's own tests. It is a `_test.go` file, outside gosec's default scan set, and out of scope for this change. Verification: `go build ./...`, `go vet ./...`, `go test ./... -count=1` and `golangci-lint run ./...` on the branch; `gosec ./...` reports `Issues: 0`. The fixture tests assert on DN/CN — swapping the `NewObject` arguments locally made them fail, so they do pin the values the unsafe writer used to produce.
2 parents f68381b + d0e5db6 commit c4bbd62

4 files changed

Lines changed: 48 additions & 78 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/gofiber/storage/memory/v2 v2.1.2
1313
github.com/joho/godotenv v1.5.1
1414
github.com/mxschmitt/playwright-go v0.6100.0
15-
github.com/netresearch/simple-ldap-go v1.13.0
15+
github.com/netresearch/simple-ldap-go v1.14.0
1616
github.com/rs/zerolog v1.35.1
1717
github.com/stretchr/testify v1.11.1
1818
github.com/testcontainers/testcontainers-go v0.43.0

go.sum

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,10 @@ github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5W
235235
github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo=
236236
github.com/gofiber/fiber/v2 v2.52.14 h1:Of3L+9qVFaQNwPlcmEdl5IIodHz8BSE0j37R7rWu4pE=
237237
github.com/gofiber/fiber/v2 v2.52.14/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
238-
github.com/gofiber/storage/bbolt/v2 v2.1.8 h1:GBxG2lEtsbBN2zhwwlL5x8YB8jqX4994q3HMC7GCok4=
239-
github.com/gofiber/storage/bbolt/v2 v2.1.8/go.mod h1:afyeCkYB+HTFMzq0TMqYTbNG4fJi+TIW22B93/oEadc=
240238
github.com/gofiber/storage/bbolt/v2 v2.1.9 h1:lO9ZeF8qAF6p/Chf3wc8eiTzkWCFly1XdYIldSk2zS0=
241239
github.com/gofiber/storage/bbolt/v2 v2.1.9/go.mod h1:fJ7Xm6mQbfxxCYliaVxCU+EUzr+17YdODoIhbtQF33w=
242240
github.com/gofiber/storage/memory/v2 v2.1.2 h1:IGOMSgnlCv/Dh1rkXA9G69kHIAywUqPjaGFUR5XATE0=
243241
github.com/gofiber/storage/memory/v2 v2.1.2/go.mod h1:R3dBncEPAGt83cQleHFXdT/t7Pep6+5ED0rBfJSqjJg=
244-
github.com/gofiber/utils/v2 v2.1.0 h1:WSu4COJhJw9moNfJu2nQvaM9AFvAQ/nZbigjhHqKgOQ=
245-
github.com/gofiber/utils/v2 v2.1.0/go.mod h1:DdOgEVwQTi8cou/AKWPqhXOR4fHGRVhA/rEWL3IXG7Q=
246242
github.com/gofiber/utils/v2 v2.2.0 h1:YSSmCzQponq/f9uSOg2HtXC5qK1Dmor0o6DqaQVz8GE=
247243
github.com/gofiber/utils/v2 v2.2.0/go.mod h1:Ieopk6sQh7rbhQ12aBNCJtJuG0gxAg0nz63sFCrrOmE=
248244
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
@@ -420,8 +416,8 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81
420416
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
421417
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
422418
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
423-
github.com/netresearch/simple-ldap-go v1.13.0 h1:hP3zlKCsB+0me6TqV41RoPTanRaqDp4u2hcpDPuGoiY=
424-
github.com/netresearch/simple-ldap-go v1.13.0/go.mod h1:PHBAklheFIQUQRfoarVSMwlfcB9YhRhbaL8CuQq9awU=
419+
github.com/netresearch/simple-ldap-go v1.14.0 h1:H9ERR1FcEWOzzxj5tI2LjnTlo3FCNyeynCnQ9MS3X14=
420+
github.com/netresearch/simple-ldap-go v1.14.0/go.mod h1:+Wz+HjRgA49ClNG9CB3poT82N7FxDFP4szggt3n720I=
425421
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
426422
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
427423
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
@@ -498,8 +494,8 @@ github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8Qgc
498494
github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE=
499495
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
500496
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
501-
github.com/shamaton/msgpack/v3 v3.1.2 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw=
502-
github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
497+
github.com/shamaton/msgpack/v3 v3.2.0 h1:1q2Ms+MWmuRju+PuDMSFDB7p7621npeX4zprJN5Zck8=
498+
github.com/shamaton/msgpack/v3 v3.2.0/go.mod h1:sgBYvEiyz8JR1NC3yGRoPVME9xXovpnh3l/plW1nfRo=
503499
github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM=
504500
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
505501
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
11
// Package cachetest provides exported test helpers for seeding the
2-
// ldap_cache.Manager from external test packages. It uses reflection +
3-
// unsafe to set the unexported dn/cn fields on simple-ldap-go's Object
4-
// type. DO NOT import from production code — this exists for tests only.
2+
// ldap_cache.Manager from external test packages. DO NOT import from
3+
// production code — this exists for tests only.
54
package cachetest
65

76
import (
8-
"reflect"
9-
"unsafe"
10-
117
ldap "github.com/netresearch/simple-ldap-go"
128

139
"github.com/netresearch/ldap-manager/internal/ldap_cache"
1410
)
1511

16-
// NewUserWithDN builds a ldap.User with the unexported dn/cn fields set
17-
// via reflection. Use only in tests.
12+
// NewUserWithDN builds a ldap.User with dn/cn populated. Use only in tests.
1813
func NewUserWithDN(dn, cn, sam string, enabled bool, groups []string) ldap.User {
19-
u := ldap.User{SAMAccountName: sam, Enabled: enabled, Groups: groups}
20-
setObjectFields(reflect.ValueOf(&u).Elem().FieldByName("Object"), dn, cn)
21-
22-
return u
14+
return ldap.User{
15+
Object: ldap.NewObject(cn, dn),
16+
SAMAccountName: sam,
17+
Enabled: enabled,
18+
Groups: groups,
19+
}
2320
}
2421

25-
// NewGroupWithDN builds a ldap.Group with unexported dn/cn populated.
22+
// NewGroupWithDN builds a ldap.Group with dn/cn populated.
2623
func NewGroupWithDN(dn, cn string, members []string) ldap.Group {
27-
g := ldap.Group{Members: members}
28-
setObjectFields(reflect.ValueOf(&g).Elem().FieldByName("Object"), dn, cn)
29-
30-
return g
24+
return ldap.Group{
25+
Object: ldap.NewObject(cn, dn),
26+
Members: members,
27+
}
3128
}
3229

33-
// NewComputerWithDN builds a ldap.Computer with unexported dn/cn populated.
30+
// NewComputerWithDN builds a ldap.Computer with dn/cn populated.
3431
func NewComputerWithDN(dn, cn, sam string, enabled bool, groups []string) ldap.Computer {
35-
c := ldap.Computer{SAMAccountName: sam, Enabled: enabled, Groups: groups}
36-
setObjectFields(reflect.ValueOf(&c).Elem().FieldByName("Object"), dn, cn)
37-
38-
return c
32+
return ldap.Computer{
33+
Object: ldap.NewObject(cn, dn),
34+
SAMAccountName: sam,
35+
Enabled: enabled,
36+
Groups: groups,
37+
}
3938
}
4039

4140
// Seed replaces all three caches in one call. Pass nil for any kind you
@@ -51,20 +50,3 @@ func Seed(m *ldap_cache.Manager, users []ldap.User, groups []ldap.Group, compute
5150
m.SetComputersForTesting(computers)
5251
}
5352
}
54-
55-
func setObjectFields(obj reflect.Value, dn, cn string) {
56-
dnField := obj.FieldByName("dn")
57-
cnField := obj.FieldByName("cn")
58-
// Test-only writers for unexported simple-ldap-go fields. This package is
59-
// imported by the tests of two other packages (internal/web and
60-
// internal/ldap_cache), and Go cannot share helpers declared in _test.go
61-
// files across package boundaries — so it has to be an ordinary package and
62-
// is compiled into the binary, unreachable from any production call path.
63-
// The unsafe write exists because simple-ldap-go sets Object.dn/cn only in
64-
// objectFromEntry when decoding a directory response and exposes no
65-
// constructor taking them; see netresearch/simple-ldap-go#191.
66-
dnPtr := unsafe.Pointer(dnField.UnsafeAddr()) // #nosec G103 -- test fixture writer, see comment above
67-
cnPtr := unsafe.Pointer(cnField.UnsafeAddr()) // #nosec G103 -- test fixture writer, see comment above
68-
reflect.NewAt(dnField.Type(), dnPtr).Elem().SetString(dn)
69-
reflect.NewAt(cnField.Type(), cnPtr).Elem().SetString(cn)
70-
}

internal/ldap_cache/test_helpers_test.go

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ package ldap_cache
44

55
import (
66
"errors"
7-
"reflect"
87
"sync"
9-
"unsafe"
108

119
ldap "github.com/netresearch/simple-ldap-go"
1210
)
@@ -104,40 +102,34 @@ func NewMockComputer(_, samAccountName string, enabled bool, groups []string) ld
104102
}
105103
}
106104

107-
// Test-only helpers: seed ldap.User / Group / Computer with a real DN by
108-
// poking simple-ldap-go's unexported Object fields. Production code builds
109-
// these via objectFromEntry internal to that package.
105+
// Test-only helpers: seed ldap.User / Group / Computer with a real DN.
106+
// simple-ldap-go's Object carries dn and cn unexported; ldap.NewObject is the
107+
// constructor it exposes for building fixtures.
110108

111-
// newUserWithDN creates a ldap.User with the DN and CN fields populated via reflection.
109+
// newUserWithDN creates a ldap.User with the DN and CN fields populated.
112110
func newUserWithDN(dn, cn, sam string, enabled bool, groups []string) ldap.User {
113-
u := ldap.User{SAMAccountName: sam, Enabled: enabled, Groups: groups}
114-
setObjectFields(reflect.ValueOf(&u).Elem().FieldByName("Object"), dn, cn)
115-
116-
return u
111+
return ldap.User{
112+
Object: ldap.NewObject(cn, dn),
113+
SAMAccountName: sam,
114+
Enabled: enabled,
115+
Groups: groups,
116+
}
117117
}
118118

119-
// newGroupWithDN creates a ldap.Group with the DN and CN fields populated via reflection.
119+
// newGroupWithDN creates a ldap.Group with the DN and CN fields populated.
120120
func newGroupWithDN(dn, cn string, members []string) ldap.Group {
121-
g := ldap.Group{Members: members}
122-
setObjectFields(reflect.ValueOf(&g).Elem().FieldByName("Object"), dn, cn)
123-
124-
return g
121+
return ldap.Group{
122+
Object: ldap.NewObject(cn, dn),
123+
Members: members,
124+
}
125125
}
126126

127-
// newComputerWithDN creates a ldap.Computer with the DN and CN fields populated via reflection.
127+
// newComputerWithDN creates a ldap.Computer with the DN and CN fields populated.
128128
func newComputerWithDN(dn, cn, sam string, enabled bool, groups []string) ldap.Computer { //nolint:unused
129-
c := ldap.Computer{SAMAccountName: sam, Enabled: enabled, Groups: groups}
130-
setObjectFields(reflect.ValueOf(&c).Elem().FieldByName("Object"), dn, cn)
131-
132-
return c
133-
}
134-
135-
// setObjectFields uses unsafe pointer arithmetic to write the unexported dn and cn
136-
// fields of simple-ldap-go's Object struct. This is only valid in test code where
137-
// we need to seed deterministic DNs without going through LDAP entry parsing.
138-
func setObjectFields(obj reflect.Value, dn, cn string) {
139-
dnField := obj.FieldByName("dn")
140-
cnField := obj.FieldByName("cn")
141-
reflect.NewAt(dnField.Type(), unsafe.Pointer(dnField.UnsafeAddr())).Elem().SetString(dn) //nolint:gosec
142-
reflect.NewAt(cnField.Type(), unsafe.Pointer(cnField.UnsafeAddr())).Elem().SetString(cn) //nolint:gosec
129+
return ldap.Computer{
130+
Object: ldap.NewObject(cn, dn),
131+
SAMAccountName: sam,
132+
Enabled: enabled,
133+
Groups: groups,
134+
}
143135
}

0 commit comments

Comments
 (0)