Repository: FirefighterBlu3/python-pam

Issue:
Documentation does not match API
Hello there, with the new 2.0.0 release, the API documentation does not appear to match the module's new API.

```
Python 3.9.7 (default, Sep 10 2021, 14:59:43)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pam
>>> p = pam.authenticate()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/var/lib/virtualenvs/system-config/lib/python3.9/site-packages/pam/__init__.py", line 125, in authenticate
    return __PA.authenticate(*args, **kvargs)
TypeError: authenticate() missing 2 required positional arguments: 'username' and 'password'
``` 

This can be overcome by calling as such:

`result = pam.authenticate(username='foo', password='bar')`

However, `pam.authenticate` no longer returns a boolean, it returns the internal PAM_* constants

Discussion:

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index eccb36b..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Python package
-
-on: [push]
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: ["3.10"]
-
-    steps:
-      - uses: actions/checkout@v2
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.python-version }}
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip
-          pip install tox tox-gh-actions
-      - name: Tox test
-        run: |
-          tox -vv
diff --git a/pyproject.toml b/pyproject.toml
index 83d8bee..d8ef361 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,13 +18,6 @@ envlist = py310
 isolated_build = true
 #skipsdist = true
 
-[gh-actions]
-python =
-    3.7: py37
-    3.8: py38
-    3.9: py39
-    3.10: py310
-
 [testenv]
 basepython = python3.10
 passenv = *
diff --git a/src/pam/__init__.py b/src/pam/__init__.py
index 374ef9d..0c6f9e0 100644
--- a/src/pam/__init__.py
+++ b/src/pam/__init__.py
@@ -116,14 +116,22 @@ __all__ = [
 __PA = None
 
 
-def authenticate(*args, **kvargs):
+def authenticate(username,
+                 password,
+                 service='login',
+                 env=None,
+                 call_end=True,
+                 encoding='utf-8',
+                 resetcreds=True,
+                 print_failure_messages=False):
     global __PA
 
     if __PA is None:  # pragma: no branch
         __PA = PamAuthenticator()
 
-    return __PA.authenticate(*args, **kvargs)
+    return __PA.authenticate(username, password, service, env, call_end, encoding, resetcreds, print_failure_messages)
 
 
 # legacy implementations used pam.pam()
-pam = authenticate
+pam = PamAuthenticator
+authenticate.__doc__ = PamAuthenticator.authenticate.__doc__
diff --git a/src/pam/__internals.py b/src/pam/__internals.py
index 9f3adb0..bc02e82 100644
--- a/src/pam/__internals.py
+++ b/src/pam/__internals.py
@@ -17,6 +17,7 @@ from ctypes import c_size_t
 from ctypes import c_void_p
 from ctypes import memmove
 from ctypes.util import find_library
+from typing import Union
 
 PAM_ABORT = 26
 PAM_ACCT_EXPIRED = 13
@@ -173,7 +174,7 @@ class PamConv(Structure):
 
 class PamAuthenticator:
     code = 0
-    reason = None
+    reason = None  # type: Union[str, bytes, None]
 
     def __init__(self):
         # use a trick of dlopen(), this effectively becomes
@@ -252,23 +253,15 @@ class PamAuthenticator:
 
     def authenticate(
                 self,
-                username,
-                password,
-                service='login',
-                env=None,
-                call_end=True,
-                encoding='utf-8',
-                resetcreds=True,
-                print_failure_messages=False):
-        self.pam_authenticate.__annotations = {'username': str,
-                                               'password': str,
-                                               'service': str,
-                                               'env': dict,
-                                               'call_end': bool,
-                                               'encoding': str,
-                                               'resetcreds': bool,
-                                               'return': bool,
-                                               'print_failure_messages': bool}
+                username,                       # type: Union[str, bytes]
+                password,                       # type: Union[str, bytes]
+                service='login',                # type: Union[str, bytes]
+                env=None,                       # type: dict
+                call_end=True,                  # type: bool
+                encoding='utf-8',               # type: str
+                resetcreds=True,                # type: bool
+                print_failure_messages=False    # type: bool
+                ):                              # type: (...) -> bool
         """username and password authentication for the given service.
 
         Returns True for success, or False for failure.
@@ -281,15 +274,15 @@ class PamAuthenticator:
         necessary conversions using the supplied encoding.
 
         Args:
-          username: username to authenticate
-          password: password in plain text
-          service:  PAM service to authenticate against, defaults to 'login'
-          env:      Pam environment variables
-          call_end: call the pam_end() function after (default true)
-          print_failure_messages: Print messages on failure
+          username (str): username to authenticate
+          password (str): password in plain text
+          service (str):  PAM service to authenticate against, defaults to 'login'
+          env (dict):      Pam environment variables
+          call_end (bool): call the pam_end() function after (default true)
+          print_failure_messages (bool): Print messages on failure
 
         Returns:
-          success:  True
+          success:  PAM_SUCCESS
           failure:  False
         """
 
@@ -331,7 +324,7 @@ class PamAuthenticator:
             self.code = retval
             self.reason = ("pam_start() failed: %s" %
                            self.pam_strerror(self.handle, retval))
-            return retval
+            return False
 
         # set the TTY, required when pam_securetty is used and the username
         # root is used note: this is only needed WHEN the pam_securetty.so
@@ -353,10 +346,10 @@ class PamAuthenticator:
 
         # ctty can be invalid if no tty is being used
         if ctty:  # pragma: no branch
-            ctty = c_char_p(ctty.encode(encoding))
+            ctty_p = c_char_p(ctty.encode(encoding))
 
-            retval = self.pam_set_item(self.handle, PAM_TTY, ctty)
-            retval = self.pam_set_item(self.handle, PAM_XDISPLAY, ctty)
+            retval = self.pam_set_item(self.handle, PAM_TTY, ctty_p)
+            retval = self.pam_set_item(self.handle, PAM_XDISPLAY, ctty_p)
 
         # Set the environment variables if they were supplied
         if env:
@@ -387,7 +380,7 @@ class PamAuthenticator:
         self.reason = self.pam_strerror(self.handle, auth_success)
 
         if sys.version_info >= (3,):  # pragma: no branch
-            self.reason = self.reason.decode(encoding)
+            self.reason = self.reason.decode(encoding)  # type: ignore
 
         if call_end and hasattr(self, 'pam_end'):  # pragma: no branch
             self.pam_end(self.handle, auth_success)
@@ -396,7 +389,7 @@ class PamAuthenticator:
         if print_failure_messages and self.code != PAM_SUCCESS:  # pragma: no cover
             print(f"Failure: {self.reason}")
 
-        return auth_success
+        return auth_success == PAM_SUCCESS
 
     def end(self):
         """A direct call to pam_end()


Test patch:
diff --git a/tests/test_internals.py b/tests/test_internals.py
index 4c92b0d..de3e131 100644
--- a/tests/test_internals.py
+++ b/tests/test_internals.py
@@ -85,7 +85,7 @@ def test_PamAuthenticator__normal_success(pam_obj):
         pytest.skip("test requires valid TEST_USERNAME and TEST_PASSWORD set in environment")
 
     rv = pam_obj.authenticate(TEST_USERNAME, TEST_PASSWORD)
-    assert PAM_SUCCESS == rv
+    assert True is rv
 
 
 def test_PamAuthenticator__normal_password_failure(pam_obj):
@@ -93,12 +93,14 @@ def test_PamAuthenticator__normal_password_failure(pam_obj):
         pytest.skip("test requires valid TEST_USERNAME and TEST_PASSWORD set in environment")
 
     rv = pam_obj.authenticate(TEST_USERNAME, 'not-valid')
-    assert PAM_AUTH_ERR == rv
+    assert False is rv
+    assert PAM_AUTH_ERR == pam_obj.code
 
 
 def test_PamAuthenticator__normal_unknown_username(pam_obj):
     rv = pam_obj.authenticate('bad_user_name', '')
-    assert rv in (PAM_AUTH_ERR, PAM_USER_UNKNOWN)
+    assert False is rv
+    assert pam_obj.code in (PAM_AUTH_ERR, PAM_USER_UNKNOWN)
 
 
 def test_PamAuthenticator__unset_DISPLAY(pam_obj):
@@ -111,7 +113,7 @@ def test_PamAuthenticator__unset_DISPLAY(pam_obj):
     if not (TEST_USERNAME and TEST_PASSWORD):
         pytest.skip("test requires valid TEST_USERNAME and TEST_PASSWORD set in environment")
 
-    assert PAM_SUCCESS == rv
+    assert True is rv
 
 
 def test_PamAuthenticator__env_requires_dict(pam_obj):
@@ -137,7 +139,7 @@ def test_PamAuthenticator__env_set(pam_obj):
     if not (TEST_USERNAME and TEST_PASSWORD):
         pytest.skip("test requires valid TEST_USERNAME and TEST_PASSWORD set in environment")
 
-    assert PAM_SUCCESS == rv
+    assert True == rv
 
 
 def test_PamAuthenticator__putenv_incomplete_setup(pam_obj):