Source file src/os/user/user_windows_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package user
     6  
     7  import (
     8  	"crypto/rand"
     9  	"encoding/base64"
    10  	"errors"
    11  	"fmt"
    12  	"internal/syscall/windows"
    13  	"internal/testenv"
    14  	"os"
    15  	"os/exec"
    16  	"runtime"
    17  	"slices"
    18  	"strconv"
    19  	"syscall"
    20  	"testing"
    21  	"unsafe"
    22  )
    23  
    24  // windowsTestAccount creates a test user and returns a token for that user.
    25  // If the user already exists, it will be deleted and recreated.
    26  // The caller is responsible for closing the token.
    27  func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
    28  	if testenv.Builder() == "" {
    29  		// Adding and deleting users requires special permissions.
    30  		// Even if we have them, we don't want to create users on
    31  		// on dev machines, as they may not be cleaned up.
    32  		// See https://dev.go/issue/70396.
    33  		t.Skip("skipping non-hermetic test outside of Go builders")
    34  	}
    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)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	pwd16, err := syscall.UTF16PtrFromString(pwd)
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    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  	domain, err := syscall.UTF16PtrFromString(".")
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	const LOGON32_PROVIDER_DEFAULT = 0
    81  	const LOGON32_LOGON_INTERACTIVE = 2
    82  	var token syscall.Token
    83  	if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	t.Cleanup(func() {
    87  		token.Close()
    88  	})
    89  	usr, err := Lookup(testUserName)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	return token, usr
    94  }
    95  
    96  func TestImpersonatedSelf(t *testing.T) {
    97  	runtime.LockOSThread()
    98  	defer runtime.UnlockOSThread()
    99  
   100  	want, err := current()
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  
   105  	levels := []uint32{
   106  		windows.SecurityAnonymous,
   107  		windows.SecurityIdentification,
   108  		windows.SecurityImpersonation,
   109  		windows.SecurityDelegation,
   110  	}
   111  	for _, level := range levels {
   112  		t.Run(strconv.Itoa(int(level)), func(t *testing.T) {
   113  			if err = windows.ImpersonateSelf(level); err != nil {
   114  				t.Fatal(err)
   115  			}
   116  			defer windows.RevertToSelf()
   117  
   118  			got, err := current()
   119  			if level == windows.SecurityAnonymous {
   120  				// We can't get the process token when using an anonymous token,
   121  				// so we expect an error here.
   122  				if err == nil {
   123  					t.Fatal("expected error")
   124  				}
   125  				return
   126  			}
   127  			if err != nil {
   128  				t.Fatal(err)
   129  			}
   130  			compare(t, want, got)
   131  		})
   132  	}
   133  }
   134  
   135  func TestImpersonated(t *testing.T) {
   136  	runtime.LockOSThread()
   137  	defer runtime.UnlockOSThread()
   138  
   139  	want, err := current()
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  
   144  	// Create a test user and log in as that user.
   145  	token, _ := windowsTestAccount(t)
   146  
   147  	// Impersonate the test user.
   148  	if err = windows.ImpersonateLoggedOnUser(token); err != nil {
   149  		t.Fatal(err)
   150  	}
   151  	defer func() {
   152  		err = windows.RevertToSelf()
   153  		if err != nil {
   154  			// If we can't revert to self, we can't continue testing.
   155  			panic(err)
   156  		}
   157  	}()
   158  
   159  	got, err := current()
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	compare(t, want, got)
   164  }
   165  
   166  func TestCurrentNetapi32(t *testing.T) {
   167  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   168  		// Test that Current does not load netapi32.dll.
   169  		// First call Current.
   170  		Current()
   171  
   172  		// Then check if netapi32.dll is loaded.
   173  		netapi32, err := syscall.UTF16PtrFromString("netapi32.dll")
   174  		if err != nil {
   175  			fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
   176  			os.Exit(9)
   177  			return
   178  		}
   179  		mod, _ := windows.GetModuleHandle(netapi32)
   180  		if mod != 0 {
   181  			fmt.Fprintf(os.Stderr, "netapi32.dll is loaded\n")
   182  			os.Exit(9)
   183  			return
   184  		}
   185  		os.Exit(0)
   186  		return
   187  	}
   188  	exe := testenv.Executable(t)
   189  	cmd := testenv.CleanCmdEnv(exec.Command(exe, "-test.run=^TestCurrentNetapi32$"))
   190  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
   191  	out, err := cmd.CombinedOutput()
   192  	if err != nil {
   193  		t.Fatalf("%v\n%s", err, out)
   194  	}
   195  }
   196  
   197  func TestGroupIdsTestUser(t *testing.T) {
   198  	// Create a test user and log in as that user.
   199  	_, user := windowsTestAccount(t)
   200  
   201  	gids, err := user.GroupIds()
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  
   206  	if err != nil {
   207  		t.Fatalf("%+v.GroupIds(): %v", user, err)
   208  	}
   209  	if !slices.Contains(gids, user.Gid) {
   210  		t.Errorf("%+v.GroupIds() = %v; does not contain user GID %s", user, gids, user.Gid)
   211  	}
   212  }
   213  
   214  var serviceAccounts = []struct {
   215  	sid  string
   216  	name string
   217  }{
   218  	{"S-1-5-18", "NT AUTHORITY\\SYSTEM"},
   219  	{"S-1-5-19", "NT AUTHORITY\\LOCAL SERVICE"},
   220  	{"S-1-5-20", "NT AUTHORITY\\NETWORK SERVICE"},
   221  }
   222  
   223  func TestLookupServiceAccount(t *testing.T) {
   224  	t.Parallel()
   225  	for _, tt := range serviceAccounts {
   226  		u, err := Lookup(tt.name)
   227  		if err != nil {
   228  			t.Errorf("Lookup(%q): %v", tt.name, err)
   229  			continue
   230  		}
   231  		if u.Uid != tt.sid {
   232  			t.Errorf("unexpected uid for %q; got %q, want %q", u.Name, u.Uid, tt.sid)
   233  		}
   234  	}
   235  }
   236  
   237  func TestLookupIdServiceAccount(t *testing.T) {
   238  	t.Parallel()
   239  	for _, tt := range serviceAccounts {
   240  		u, err := LookupId(tt.sid)
   241  		if err != nil {
   242  			t.Errorf("LookupId(%q): %v", tt.sid, err)
   243  			continue
   244  		}
   245  		if u.Gid != tt.sid {
   246  			t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
   247  		}
   248  		if u.Username != tt.name {
   249  			t.Errorf("unexpected user name for %q; got %q, want %q", u.Gid, u.Username, tt.name)
   250  		}
   251  	}
   252  }
   253  
   254  func TestLookupGroupServiceAccount(t *testing.T) {
   255  	t.Parallel()
   256  	for _, tt := range serviceAccounts {
   257  		u, err := LookupGroup(tt.name)
   258  		if err != nil {
   259  			t.Errorf("LookupGroup(%q): %v", tt.name, err)
   260  			continue
   261  		}
   262  		if u.Gid != tt.sid {
   263  			t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
   264  		}
   265  	}
   266  }
   267  
   268  func TestLookupGroupIdServiceAccount(t *testing.T) {
   269  	t.Parallel()
   270  	for _, tt := range serviceAccounts {
   271  		u, err := LookupGroupId(tt.sid)
   272  		if err != nil {
   273  			t.Errorf("LookupGroupId(%q): %v", tt.sid, err)
   274  			continue
   275  		}
   276  		if u.Gid != tt.sid {
   277  			t.Errorf("unexpected gid for %q; got %q, want %q", u.Name, u.Gid, tt.sid)
   278  		}
   279  	}
   280  }
   281  

View as plain text