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