Source file
src/os/user/user_windows_test.go
1
2
3
4
5 package user
6
7 import (
8 "crypto/rand"
9 "encoding/base64"
10 "encoding/binary"
11 "errors"
12 "fmt"
13 "internal/syscall/windows"
14 "internal/testenv"
15 "os"
16 "os/exec"
17 "runtime"
18 "slices"
19 "strconv"
20 "strings"
21 "sync"
22 "syscall"
23 "testing"
24 "unicode"
25 "unicode/utf8"
26 "unsafe"
27 )
28
29
30
31
32 func addUserAccount(t *testing.T) (name, password string) {
33 t.TempDir()
34 pattern := t.Name()
35
36
37 const maxNameLen, suffixLen = 20, 4
38 pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
39
40 mapper := func(r rune) rune {
41 if r < utf8.RuneSelf {
42 if '0' <= r && r <= '9' ||
43 'a' <= r && r <= 'z' ||
44 'A' <= r && r <= 'Z' {
45 return r
46 }
47 } else if unicode.IsLetter(r) || unicode.IsNumber(r) {
48 return r
49 }
50 return -1
51 }
52 pattern = strings.Map(mapper, pattern)
53
54
55 var pwd [33]byte
56 rand.Read(pwd[:])
57
58 password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
59 password16, err := syscall.UTF16PtrFromString(password)
60 if err != nil {
61 t.Fatal(err)
62 }
63
64 try := 0
65 for {
66
67 var suffix [2]byte
68 rand.Read(suffix[:])
69 suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
70 name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
71 name16, err := syscall.UTF16PtrFromString(name)
72 if err != nil {
73 t.Fatal(err)
74 }
75
76 userInfo := windows.UserInfo1{
77 Name: name16,
78 Password: password16,
79 Priv: windows.USER_PRIV_USER,
80 }
81 err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
82 if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
83 t.Skip("skipping test; don't have permission to create user")
84 }
85
86 if errors.Is(err, windows.NERR_UserExists) {
87 if try++; try < 1000 {
88 t.Log("user already exists, trying again with a different name")
89 continue
90 }
91 }
92 if err != nil {
93 t.Fatalf("NetUserAdd failed: %v", err)
94 }
95
96 t.Cleanup(func() {
97 if err := windows.NetUserDel(nil, name16); err != nil {
98 if !errors.Is(err, windows.NERR_UserNotFound) {
99 t.Fatal(err)
100 }
101 }
102 })
103 return name, password
104 }
105 }
106
107
108
109
110 func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
111 if testenv.Builder() == "" {
112
113
114
115
116 t.Skip("skipping non-hermetic test outside of Go builders")
117 }
118 name, password := addUserAccount(t)
119 name16, err := syscall.UTF16PtrFromString(name)
120 if err != nil {
121 t.Fatal(err)
122 }
123 pwd16, err := syscall.UTF16PtrFromString(password)
124 if err != nil {
125 t.Fatal(err)
126 }
127 domain, err := syscall.UTF16PtrFromString(".")
128 if err != nil {
129 t.Fatal(err)
130 }
131 const LOGON32_PROVIDER_DEFAULT = 0
132 const LOGON32_LOGON_INTERACTIVE = 2
133 var token syscall.Token
134 if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
135 t.Fatal(err)
136 }
137 t.Cleanup(func() {
138 token.Close()
139 })
140 usr, err := Lookup(name)
141 if err != nil {
142 t.Fatal(err)
143 }
144 return token, usr
145 }
146
147 func TestImpersonatedSelf(t *testing.T) {
148 runtime.LockOSThread()
149 defer runtime.UnlockOSThread()
150
151 want, err := current()
152 if err != nil {
153 t.Fatal(err)
154 }
155
156 levels := []uint32{
157 windows.SecurityAnonymous,
158 windows.SecurityIdentification,
159 windows.SecurityImpersonation,
160 windows.SecurityDelegation,
161 }
162 for _, level := range levels {
163 t.Run(strconv.Itoa(int(level)), func(t *testing.T) {
164 if err = windows.ImpersonateSelf(level); err != nil {
165 t.Fatal(err)
166 }
167 defer windows.RevertToSelf()
168
169 got, err := current()
170 if level == windows.SecurityAnonymous {
171
172
173 if err == nil {
174 t.Fatal("expected error")
175 }
176 return
177 }
178 if err != nil {
179 t.Fatal(err)
180 }
181 compare(t, want, got)
182 })
183 }
184 }
185
186 func TestImpersonated(t *testing.T) {
187 runtime.LockOSThread()
188 defer runtime.UnlockOSThread()
189
190 want, err := current()
191 if err != nil {
192 t.Fatal(err)
193 }
194
195
196 token, _ := windowsTestAccount(t)
197
198
199 if err = windows.ImpersonateLoggedOnUser(token); err != nil {
200 t.Fatal(err)
201 }
202 defer func() {
203 err = windows.RevertToSelf()
204 if err != nil {
205
206 panic(err)
207 }
208 }()
209
210 got, err := current()
211 if err != nil {
212 t.Fatal(err)
213 }
214 compare(t, want, got)
215 }
216
217 func TestCurrentNetapi32(t *testing.T) {
218 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
219
220
221 Current()
222
223
224 netapi32, err := syscall.UTF16PtrFromString("netapi32.dll")
225 if err != nil {
226 fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
227 os.Exit(9)
228 return
229 }
230 mod, _ := windows.GetModuleHandle(netapi32)
231 if mod != 0 {
232 fmt.Fprintf(os.Stderr, "netapi32.dll is loaded\n")
233 os.Exit(9)
234 return
235 }
236 os.Exit(0)
237 return
238 }
239 exe := testenv.Executable(t)
240 cmd := testenv.CleanCmdEnv(exec.Command(exe, "-test.run=^TestCurrentNetapi32$"))
241 cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
242 out, err := cmd.CombinedOutput()
243 if err != nil {
244 t.Fatalf("%v\n%s", err, out)
245 }
246 }
247
248 func TestGroupIdsTestUser(t *testing.T) {
249
250 _, user := windowsTestAccount(t)
251
252 gids, err := user.GroupIds()
253 if err != nil {
254 t.Fatal(err)
255 }
256
257 if err != nil {
258 t.Fatalf("%+v.GroupIds(): %v", user, err)
259 }
260 if !slices.Contains(gids, user.Gid) {
261 t.Errorf("%+v.GroupIds() = %v; does not contain user GID %s", user, gids, user.Gid)
262 }
263 }
264
265 var isSystemDefaultLCIDEnglish = sync.OnceValue(func() bool {
266
267
268 r, _, _ := syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetSystemDefaultLCID").Call()
269 lcid := uint32(r)
270
271 lcidLow := lcid & 0xFF
272
273
274 return lcidLow == 0x00 || lcidLow == 0x09
275 })
276
277 var serviceAccounts = []struct {
278 sid string
279 name string
280 }{
281 {"S-1-5-18", "NT AUTHORITY\\SYSTEM"},
282 {"S-1-5-19", "NT AUTHORITY\\LOCAL SERVICE"},
283 {"S-1-5-20", "NT AUTHORITY\\NETWORK SERVICE"},
284 }
285
286 func TestLookupServiceAccount(t *testing.T) {
287 t.Parallel()
288 for _, tt := range serviceAccounts {
289 t.Run(tt.name, func(t *testing.T) {
290 u, err := Lookup(tt.name)
291 if err != nil {
292 t.Logf("Lookup(%q): %v", tt.name, err)
293 if !isSystemDefaultLCIDEnglish() {
294 t.Skipf("test not supported on non-English Windows")
295 }
296 t.Fail()
297 return
298 }
299 if u.Uid != tt.sid {
300 t.Errorf("unexpected uid for %q; got %q, want %q", u.Name, u.Uid, tt.sid)
301 }
302 t.Logf("Lookup(%q): %q", tt.name, u.Username)
303 })
304 }
305 }
306
307 func TestLookupIdServiceAccount(t *testing.T) {
308 t.Parallel()
309 for _, tt := range serviceAccounts {
310 u, err := LookupId(tt.sid)
311 if err != nil {
312 t.Errorf("LookupId(%q): %v", tt.sid, err)
313 continue
314 }
315 if u.Gid != tt.sid {
316 t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
317 }
318 if u.Username != tt.name {
319 if isSystemDefaultLCIDEnglish() {
320 t.Errorf("unexpected user name for %q; got %q, want %q", u.Gid, u.Username, tt.name)
321 } else {
322 t.Logf("user name for %q: %q", u.Gid, u.Username)
323 }
324 }
325 }
326 }
327
328 func TestLookupGroupServiceAccount(t *testing.T) {
329 t.Parallel()
330 for _, tt := range serviceAccounts {
331 t.Run(tt.name, func(t *testing.T) {
332 g, err := LookupGroup(tt.name)
333 if err != nil {
334 t.Logf("LookupGroup(%q): %v", tt.name, err)
335 if !isSystemDefaultLCIDEnglish() {
336 t.Skipf("test not supported on non-English Windows")
337 }
338 t.Fail()
339 return
340 }
341 if g.Gid != tt.sid {
342 t.Errorf("unexpected gid for %q; got %q, want %q", g.Name, g.Gid, tt.sid)
343 }
344 })
345 }
346 }
347
348 func TestLookupGroupIdServiceAccount(t *testing.T) {
349 t.Parallel()
350 for _, tt := range serviceAccounts {
351 u, err := LookupGroupId(tt.sid)
352 if err != nil {
353 t.Errorf("LookupGroupId(%q): %v", tt.sid, err)
354 continue
355 }
356 if u.Gid != tt.sid {
357 t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
358 }
359 }
360 }
361
View as plain text