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