Repository: mhammond/pywin32 (language: python)

Issue:
`CredGetSessionTypes` is not implemented in `win32credmodule.cpp`
Hi, I was looking to write up a function to sanity check that credentials could be saved before attempting to write credentials. To accomplish this, I wanted to call  [`CredGetSessionTypes`](https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credgetsessiontypes) to ensure the following:

1. `CRED_PERSIST_NONE` was not returned for the particular credential type.
2. `ERROR_NO_SUCH_LOGON_SESSION` was not produced as an error.

Both the error and constant are implemented in the current pywin32 source.

Looking at the API details for the function, it appears to not be a low-level "clean-up" function like [`CredFree`](https://learn.microsoft.com/en-us/windows/desktop/api/WinCred/nf-wincred-credfree), and therefore `CredGetSessionTypes` not being a part of the currently implemented source is either a bug or feature omission depending on perspective.



Discussion / hints:

Fix patch:
diff --git a/win32/src/win32credmodule.cpp b/win32/src/win32credmodule.cpp
index 4856010e1..efcddfe99 100644
--- a/win32/src/win32credmodule.cpp
+++ b/win32/src/win32credmodule.cpp
@@ -533,6 +533,33 @@ PyObject *PyCredEnumerate(PyObject *self, PyObject *args, PyObject *kwargs)
     return ret;
 }
 
+// @pymethod dict|win32cred|CredGetSessionTypes|Returns maximum persistence supported by the current logon session
+// @rdesc Returns an integer list
+PyObject* PyCredGetSessionTypes(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    static char *keywords[] = {"MaximumPersistCount", NULL};
+    DWORD mpc = CRED_TYPE_MAXIMUM;
+    if (!PyArg_ParseTupleAndKeywords(
+            args, kwargs, "|k:CredGetSessionTypes", keywords,
+            &mpc))  // @pyparm int|MaximumPersistCount|CRED_TYPE_MAXIMUM|Maximum array entries
+        return NULL;
+    if ((mpc == 0) || (mpc > CRED_TYPE_MAXIMUM)) {
+        PyErr_SetString(PyExc_ValueError, "Argument must be between 1 and CRED_TYPE_MAXIMUM");
+        return NULL;
+    }
+    BOOL res = TRUE;
+    DWORD arr[CRED_TYPE_MAXIMUM];
+    Py_BEGIN_ALLOW_THREADS;
+    res = CredGetSessionTypes(mpc, arr);
+    Py_END_ALLOW_THREADS;
+    if (!res)
+        return PyWin_SetAPIError("CredGetSessionTypes");
+    PyObject *ret = PyList_New(mpc);
+    for (DWORD i = 0; i < mpc; ++i)
+        PyList_SET_ITEM(ret, i, PyLong_FromUnsignedLong(arr[i]));
+    return ret;
+}
+
 // @pymethod dict|win32cred|CredGetTargetInfo|Determines type and location of credential target
 // @rdesc Returns a <o PyCREDENTIAL_TARGET_INFORMATION> dict
 // @comm The target information will not be available until an attempt is made to authenticate against it
@@ -1098,6 +1125,8 @@ static struct PyMethodDef win32cred_functions[] = {
     // @pymeth CredEnumerate|Lists stored credentials for current logon session
     {"CredEnumerate", (PyCFunction)PyCredEnumerate, METH_VARARGS | METH_KEYWORDS,
      "Lists stored credentials for current logon session"},
+    {"CredGetSessionTypes", (PyCFunction)PyCredGetSessionTypes, METH_VARARGS | METH_KEYWORDS,
+     "Returns maximum persistence supported by the current logon session"},
     // @pymeth CredGetTargetInfo|Determines type and location of credential target
     {"CredGetTargetInfo", (PyCFunction)PyCredGetTargetInfo, METH_VARARGS | METH_KEYWORDS,
      "Determines type and location of credential target"},
@@ -1148,6 +1177,10 @@ PYWIN_MODULE_INIT_FUNC(win32cred)
     PyModule_AddIntConstant(module, "CRED_TYPE_DOMAIN_PASSWORD", CRED_TYPE_DOMAIN_PASSWORD);
     PyModule_AddIntConstant(module, "CRED_TYPE_DOMAIN_CERTIFICATE", CRED_TYPE_DOMAIN_CERTIFICATE);
     PyModule_AddIntConstant(module, "CRED_TYPE_DOMAIN_VISIBLE_PASSWORD", CRED_TYPE_DOMAIN_VISIBLE_PASSWORD);
+    PyModule_AddIntConstant(module, "CRED_TYPE_GENERIC_CERTIFICATE", CRED_TYPE_GENERIC_CERTIFICATE);
+    PyModule_AddIntConstant(module, "CRED_TYPE_DOMAIN_EXTENDED", CRED_TYPE_DOMAIN_EXTENDED);
+    PyModule_AddIntConstant(module, "CRED_TYPE_MAXIMUM", CRED_TYPE_MAXIMUM);
+    PyModule_AddIntConstant(module, "CRED_TYPE_MAXIMUM_EX", CRED_TYPE_MAXIMUM + 1000);
     // credential flags
     PyModule_AddIntConstant(module, "CRED_FLAGS_PROMPT_NOW", CRED_FLAGS_PROMPT_NOW);
     PyModule_AddIntConstant(module, "CRED_FLAGS_USERNAME_TARGET", CRED_FLAGS_USERNAME_TARGET);


Test patch:
diff --git a/win32/test/test_win32cred.py b/win32/test/test_win32cred.py
index e4af18135..c44fe18b8 100644
--- a/win32/test/test_win32cred.py
+++ b/win32/test/test_win32cred.py
@@ -71,3 +71,22 @@ def test_creddelete(self):
         )
         self.assertIsNone(win32cred.CredDelete(self.dummy_cred))
         self.assertFalse(self.is_dummy_cred())
+
+    def test_credgetsessiontypes(self):
+        res = win32cred.CredGetSessionTypes()
+        self.assertEqual(len(res), win32cred.CRED_TYPE_MAXIMUM)
+        for i in range(1, len(res)):
+            self.assertEqual(res[:i], win32cred.CredGetSessionTypes(i))
+            self.assertEqual(
+                res[:i], win32cred.CredGetSessionTypes(MaximumPersistCount=i)
+            )
+        self.assertRaises(
+            ValueError,
+            win32cred.CredGetSessionTypes,
+            0,
+        )
+        self.assertRaises(
+            ValueError,
+            win32cred.CredGetSessionTypes,
+            MaximumPersistCount=win32cred.CRED_TYPE_MAXIMUM + 1,
+        )