This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Implement a "strict" mode for getpass.getuser()
Type: enhancement Stage: needs patch
Components: Library (Lib), Windows Versions: Python 3.11
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: eryksun, paul.moore, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2022-02-04 05:55 by eryksun, last changed 2022-04-11 14:59 by admin.

Messages (2)
msg412495 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2022-02-04 05:55
getpass.getuser() checks the environment variables LOGNAME (login name), USER, LNAME, and USERNAME, in that order. In Windows, LOGNAME, USER, and LNAME have no conventional usage. I think there should be a strict mode that restricts getuser() to check only USERNAME in Windows and only LOGNAME in POSIX [1]. If the login variable isn't defined, it should fall back on using the system API, based on the user ID in POSIX and the logon ID in Windows.

For the fallback in Windows, the _winapi module could implement GetCurrentProcessToken(), GetTokenInformation(), and LsaGetLogonSessionData(). For TokenStatistics, return a dict with just "AuthenticationId". For LsaGetLogonSessionData(), return a dict with just "UserName". GetCurrentProcessToken() returns a pseudohandle (-4), which should not be closed.

For example, assuming _winapi wraps the required functions:

    def getuser(strict=False):
        """Get the username from the environment or password database.

        First try various environment variables. If strict, check only LOGNAME
        in POSIX and only USERNAME in Windows. As a fallback, in POSIX get the
        user name from the password database, and in Windows get the user name
        from the logon-session data of the current process.
        """
        posix = sys.platform != 'win32'

        if strict:
            names = ('LOGNAME',) if posix else ('USERNAME',)
        else:
            names = ('LOGNAME', 'USER', 'LNAME', 'USERNAME')

        for name in names:
            if user := os.environ.get(name):
                return user

        if posix:
            import pwd
            return pwd.getpwuid(os.getuid())[0]

        import _winapi
        logon_id = _winapi.GetTokenInformation(
                        _winapi.GetCurrentProcessToken(),
                        _winapi.TokenStatistics)['AuthenticationId']
        return _winapi.LsaGetLogonSessionData(logon_id)['UserName']

Like WinAPI GetUserNameW(), the above fallback returns the logon user name instead of the account name of the token user. As far as I know, the user name and the account name only differ for the builtin service account logons "SYSTEM" (999) and "NETWORK SERVICE" (996), for which the user name is the machine security principal (i.e. the machine's NETBIOS name plus "$"). The user name of the builtin "LOCAL SERVICE" logon (997), on the other hand, is just the "LOCAL SERVICE" account name, since this account lacks network access.

Unlike GetUserNameW(), the above code uses the process token instead of the effective token. This is like POSIX getuid(), whereas what GetUserNameW() does is like geteuid(). getuser() could implement an `effective` option to return the effective user name. In Windows this would switch to calling GetCurrentThreadEffectiveToken() instead of GetCurrentProcessToken().

---

[1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
msg412556 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2022-02-05 03:39
Here's an example for the suggested changes to _winapi.

Include these headers:

    #include <ntsecapi.h> // LsaGetLogonSessionData
    #include <subauth.h> // STATUS_SUCCESS

Add these argument-clinic macros to _winapi_functions:

    _WINAPI_GETCURRENTPROCESSTOKEN_METHODDEF
    _WINAPI_GETTOKENINFORMATION_METHODDEF
    _WINAPI_LSAGETLOGONSESSIONDATA_METHODDEF

Add TokenStatistics in winapi_exec():

    WINAPI_CONSTANT(F_DWORD, TokenStatistics);

Add minimal implementations that wrap the WinAPI functions:

    /*[clinic input]
    _winapi.GetCurrentProcessToken -> HANDLE

    Return a handle for the access token of the current process.
    [clinic start generated code]*/

    static HANDLE
    _winapi_GetCurrentProcessToken_impl(PyObject *module)
    /*[clinic end generated code: output=cf8e8e20dd41dd6e input=73a282cf3718af9e]*/

    {
        return GetCurrentProcessToken();
    }


    /*[clinic input]
    _winapi.GetTokenInformation

        handle: HANDLE
        information_class: unsigned_long
        /

    Get information from an access token.
    [clinic start generated code]*/

    static PyObject *
    _winapi_GetTokenInformation_impl(PyObject *module, HANDLE handle,
                                     unsigned long information_class)
    /*[clinic end generated code: output=caecec0a25658348 input=b277ad2414f1b03e]*/

    {
        if (information_class != TokenStatistics) {
            return PyErr_Format(
                        PyExc_NotImplementedError,
                        "Unsupported information class: %d",
                        information_class);
        }

        DWORD returned_size;
        TOKEN_STATISTICS info;

        if (!GetTokenInformation(handle, information_class, &info, sizeof(info),
                &returned_size)) {
            return PyErr_SetFromWindowsErr(0);
        }

        PyObject *result = PyDict_New();
        if (!result) {
            return NULL;
        }

        PyObject *value = PyLong_FromUnsignedLongLong(
                            (((uint64_t)info.AuthenticationId.HighPart) << 32) +
                            info.AuthenticationId.LowPart);
        if (!value) {
            goto error;
        }
        if (PyDict_SetItemString(result, "AuthenticationId", value) < 0) {
            Py_DECREF(value);
            goto error;
        }
        Py_DECREF(value);

        return result;

    error:
        Py_CLEAR(result);
        return NULL;
    }


    /*[clinic input]
    _winapi.LsaGetLogonSessionData

        logon_id: unsigned_long_long
        /
    Get data for the logon session identified by logon_id.
    [clinic start generated code]*/

    static PyObject *
    _winapi_LsaGetLogonSessionData_impl(PyObject *module,
                                        unsigned long long logon_id)
    /*[clinic end generated code: output=680ac7725ef34527 input=01ff4216b89d01ef]*/

    {
        SECURITY_LOGON_SESSION_DATA *pdata;
        LUID logon_luid;
        logon_luid.HighPart = logon_id >> 32;
        logon_luid.LowPart = logon_id & 0xFFFFFFFF;

        NTSTATUS status = LsaGetLogonSessionData(&logon_luid, &pdata);
        if (status != STATUS_SUCCESS) {
            return PyErr_SetFromWindowsErr(LsaNtStatusToWinError(status));
        }

        PyObject *result = PyDict_New();
        if (!result) {
            goto error;
        }

        PyObject *value = PyUnicode_FromWideChar(pdata->UserName.Buffer,
                                pdata->UserName.Length / sizeof(WCHAR));
        if (!value) {
            goto error;
        }
        if (PyDict_SetItemString(result, "UserName", value) < 0) {
            Py_DECREF(value);
            goto error;
        }
        Py_DECREF(value);

        LsaFreeReturnBuffer(pdata);
        return result;

    error:
        LsaFreeReturnBuffer(pdata);
        Py_CLEAR(result);
        return NULL;
    }
History
Date User Action Args
2022-04-11 14:59:55adminsetgithub: 90789
2022-02-05 03:39:16eryksunsetmessages: + msg412556
2022-02-04 05:55:34eryksuncreate