From c1f720c150e9d30e10435561a70795b319424d3e Mon Sep 17 00:00:00 2001
From: Jordan Borean <jborean93@gmail.com>
Date: Wed, 12 Jun 2024 08:58:25 +1000
Subject: [PATCH] Tidy up NTLM error messages and update changelog

---
 CHANGELOG.md           |  4 +++-
 src/spnego/_ntlm.py    |  8 +++++---
 src/spnego/_version.py |  2 +-
 tests/test_ntlm.py     | 16 ++++++++++++----
 4 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fee354d..fed943d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,12 @@
 # Changelog
 
-## 0.10.3 - TBD
+## 0.11.0 - TBD
 
 * Support input password string encoded with the `surrogatepass` error option
   * This allows the caller to provide a password for a gMSA or machine account that could contain invalid surrogate pairs for both NTLM and Kerberos auth.
 * Stop using deprecated `datetime.dateime.utcnow()` for CredSSP acceptor context
+* Treat an empty string as a valid password, `None` is kept as use the cached credential
+* Improve the exception shown when no password was provided and no cached credential was available
 
 ## 0.10.2 - 2023-10-04
 
diff --git a/src/spnego/_ntlm.py b/src/spnego/_ntlm.py
index 99d5ed3..0d85231 100644
--- a/src/spnego/_ntlm.py
+++ b/src/spnego/_ntlm.py
@@ -135,7 +135,9 @@ def _get_credential(
         https://asecuritysite.com/encryption/lmhash
     """
     if not store:
-        raise OperationNotAvailableError(context_msg="Retrieving NTLM store without NTLM_USER_FILE set to a filepath")
+        raise OperationNotAvailableError(
+            context_msg="No username or password was specified and the credential cache did not exist or contained no credentials"
+        )
 
     domain = domain or ""
 
@@ -178,7 +180,7 @@ def store_lines(
         else:
             raise SpnegoError(
                 ErrorCode.failure,
-                context_msg="Failed to find any matching credential in " "NTLM_USER_FILE credential store.",
+                context_msg="Failed to find any matching credential in NTLM_USER_FILE credential store.",
             )
 
 
@@ -306,7 +308,7 @@ def __init__(
             # Make sure that the credential file is set and exists
             if not _get_credential_file():
                 raise OperationNotAvailableError(
-                    context_msg="Retrieving NTLM store without NTLM_USER_FILE set to a " "filepath"
+                    context_msg="NTLM acceptor requires NTLM credential cache to be provided through the env var NTLM_USER_FILE set to a filepath"
                 )
 
         self._temp_negotiate: typing.Optional[Negotiate] = None
diff --git a/src/spnego/_version.py b/src/spnego/_version.py
index 300b5f3..a7962ed 100644
--- a/src/spnego/_version.py
+++ b/src/spnego/_version.py
@@ -1,4 +1,4 @@
 # Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com>
 # MIT License (see LICENSE or https://opensource.org/licenses/MIT)
 
-__version__ = "0.10.3"
+__version__ = "0.11.0"
diff --git a/tests/test_ntlm.py b/tests/test_ntlm.py
index 88fb2f4..0061614 100644
--- a/tests/test_ntlm.py
+++ b/tests/test_ntlm.py
@@ -168,12 +168,20 @@ def test_invalid_lm_compat_level(level, monkeypatch):
         ntlm.NTLMProxy("user", "pass")
 
 
-@pytest.mark.parametrize("usage", ["initiate", "accept"])
-def test_context_no_store(usage):
+def test_context_no_store_initiate():
     with pytest.raises(
-        OperationNotAvailableError, match="Retrieving NTLM store without NTLM_USER_FILE set to a " "filepath"
+        OperationNotAvailableError,
+        match="No username or password was specified and the credential cache did not exist or contained no credentials",
     ):
-        ntlm.NTLMProxy(CredentialCache(), usage=usage)
+        ntlm.NTLMProxy(CredentialCache(), usage="initiate")
+
+
+def test_context_no_store_accept():
+    with pytest.raises(
+        OperationNotAvailableError,
+        match="NTLM acceptor requires NTLM credential cache to be provided through the env var NTLM_USER_FILE set to a filepath",
+    ):
+        ntlm.NTLMProxy(CredentialCache(), usage="accept")
 
 
 def test_iov_available():