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

View as plain text