@@ -7,6 +7,7 @@ package user
7
7
import (
8
8
"crypto/rand"
9
9
"encoding/base64"
10
+ "encoding/binary"
10
11
"errors"
11
12
"fmt"
12
13
"internal/syscall/windows"
@@ -16,11 +17,92 @@ import (
16
17
"runtime"
17
18
"slices"
18
19
"strconv"
20
+ "strings"
19
21
"syscall"
20
22
"testing"
23
+ "unicode"
24
+ "unicode/utf8"
21
25
"unsafe"
22
26
)
23
27
28
+ // addUserAccount creates a local user account.
29
+ // It returns the name and password of the new account.
30
+ // Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
31
+ func addUserAccount (t * testing.T ) (name , password string ) {
32
+ t .TempDir ()
33
+ pattern := t .Name ()
34
+ // Windows limits the user name to 20 characters,
35
+ // leave space for a 4 digits random suffix.
36
+ const maxNameLen , suffixLen = 20 , 4
37
+ pattern = pattern [:min (len (pattern ), maxNameLen - suffixLen )]
38
+ // Drop unusual characters from the account name.
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
+ // Generate a long random password.
54
+ var pwd [33 ]byte
55
+ rand .Read (pwd [:])
56
+ // Add special chars to ensure it satisfies password requirements.
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
+ // Calculate a random suffix to append to the user name.
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
+ // Create user.
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
+ // If the user already exists, try again with a different name.
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
+ // Delete the user when the test is done.
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
+
24
106
// windowsTestAccount creates a test user and returns a token for that user.
25
107
// If the user already exists, it will be deleted and recreated.
26
108
// The caller is responsible for closing the token.
@@ -32,61 +114,29 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
32
114
// See https://dev.go/issue/70396.
33
115
t .Skip ("skipping non-hermetic test outside of Go builders" )
34
116
}
35
- const testUserName = "GoStdTestUser01"
36
- var password [33 ]byte
37
- rand .Read (password [:])
38
- // Add special chars to ensure it satisfies password requirements.
39
- pwd := base64 .StdEncoding .EncodeToString (password [:]) + "_-As@!%*(1)4#2"
40
- name , err := syscall .UTF16PtrFromString (testUserName )
117
+ name , password := addUserAccount (t )
118
+ name16 , err := syscall .UTF16PtrFromString (name )
41
119
if err != nil {
42
120
t .Fatal (err )
43
121
}
44
- pwd16 , err := syscall .UTF16PtrFromString (pwd )
122
+ pwd16 , err := syscall .UTF16PtrFromString (password )
45
123
if err != nil {
46
124
t .Fatal (err )
47
125
}
48
- userInfo := windows.UserInfo1 {
49
- Name : name ,
50
- Password : pwd16 ,
51
- Priv : windows .USER_PRIV_USER ,
52
- }
53
- // Create user.
54
- err = windows .NetUserAdd (nil , 1 , (* byte )(unsafe .Pointer (& userInfo )), nil )
55
- if errors .Is (err , syscall .ERROR_ACCESS_DENIED ) {
56
- t .Skip ("skipping test; don't have permission to create user" )
57
- }
58
- if errors .Is (err , windows .NERR_UserExists ) {
59
- // User already exists, delete and recreate.
60
- if err = windows .NetUserDel (nil , name ); err != nil {
61
- t .Fatal (err )
62
- }
63
- if err = windows .NetUserAdd (nil , 1 , (* byte )(unsafe .Pointer (& userInfo )), nil ); err != nil {
64
- t .Fatal (err )
65
- }
66
- } else if err != nil {
67
- t .Fatal (err )
68
- }
69
- t .Cleanup (func () {
70
- if err = windows .NetUserDel (nil , name ); err != nil {
71
- if ! errors .Is (err , windows .NERR_UserNotFound ) {
72
- t .Fatal (err )
73
- }
74
- }
75
- })
76
126
domain , err := syscall .UTF16PtrFromString ("." )
77
127
if err != nil {
78
128
t .Fatal (err )
79
129
}
80
130
const LOGON32_PROVIDER_DEFAULT = 0
81
131
const LOGON32_LOGON_INTERACTIVE = 2
82
132
var token syscall.Token
83
- if err = windows .LogonUser (name , domain , pwd16 , LOGON32_LOGON_INTERACTIVE , LOGON32_PROVIDER_DEFAULT , & token ); err != nil {
133
+ if err = windows .LogonUser (name16 , domain , pwd16 , LOGON32_LOGON_INTERACTIVE , LOGON32_PROVIDER_DEFAULT , & token ); err != nil {
84
134
t .Fatal (err )
85
135
}
86
136
t .Cleanup (func () {
87
137
token .Close ()
88
138
})
89
- usr , err := Lookup (testUserName )
139
+ usr , err := Lookup (name )
90
140
if err != nil {
91
141
t .Fatal (err )
92
142
}
0 commit comments