Repository: frispete/keyrings.cryptfile

Issue:
Pull keyring storage password from an environment variable.
I'm trying to use this backend with PIP but the fact that the storage the password is always prompted for, breaks when pip prompts keyring for the actual password.

Is there a way to provide the storage password without user input? The readme shows an environment variable, but I think that's just an example for how a script could use it. Pip is always a process/subprocess so I dont have control of setting it in that manner.


Discussion:

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 4191b8f..b595910 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,7 @@
+1.4.1 (unreleased)
+==================
+- Support for pulling the database from from the environment variable `KEYRING_CRYPTFILE_PASSWORD`
+
 1.4.0
 =====
 - Rename master branch to main
diff --git a/README.md b/README.md
index 124d446..ee74b54 100644
--- a/README.md
+++ b/README.md
@@ -157,14 +157,26 @@ Notes
 
 You can avoid the interactive getpass() request for the keyring password by
 supplying `kr.keyring_key = "your keyring password"` before calling any other
-methods on the keyring. The following example shows a simple way to retrieve the 
-password from an environment variable `KEYRING_CRYPTFILE_PASSWORD`, when present:
+methods on the keyring.
+
 ```python
 from getpass import getpass
 from os import getenv
 from keyrings.cryptfile.cryptfile import CryptFileKeyring
 kr = CryptFileKeyring()
-kr.keyring_key = getenv("KEYRING_CRYPTFILE_PASSWORD") or getpass()
+kr.keyring_key = getpass()
+keyring.set_keyring(kr)
+```
+
+or the keyring password can be provided via an environment variable
+
+```python
+os.environ['KEYRING_CRYPTFILE_PASSWORD']='password'
+
+from getpass import getpass
+from os import getenv
+from keyrings.cryptfile.cryptfile import CryptFileKeyring
+kr = CryptFileKeyring()
 keyring.set_keyring(kr)
 ```
 
diff --git a/keyrings/cryptfile/__init__.py b/keyrings/cryptfile/__init__.py
index 9e86e88..8e3c933 100644
--- a/keyrings/cryptfile/__init__.py
+++ b/keyrings/cryptfile/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '1.3.9'
+__version__ = '1.4.1'
diff --git a/keyrings/cryptfile/file.py b/keyrings/cryptfile/file.py
index 6811109..8aec39d 100644
--- a/keyrings/cryptfile/file.py
+++ b/keyrings/cryptfile/file.py
@@ -11,6 +11,7 @@ from .escape import escape as escape_for_ini
 
 from keyrings.cryptfile.file_base import Keyring, decodebytes, encodebytes
 
+KEYRING_CRYPTFILE_PASSWORD = "KEYRING_CRYPTFILE_PASSWORD"
 
 class PlaintextKeyring(Keyring):
     """Simple File Keyring with no encryption"""
@@ -195,9 +196,12 @@ class EncryptedKeyring(Encrypted, Keyring):
         user.
         """
         if self._keyring_key is None:
-            self._keyring_key = getpass.getpass(
-            'Please enter password for encrypted keyring: '
-        )
+            if KEYRING_CRYPTFILE_PASSWORD in os.environ:
+                self._keyring_key = os.environ[KEYRING_CRYPTFILE_PASSWORD]
+            else:
+                self._keyring_key = getpass.getpass(
+                    'Please enter password for encrypted keyring: '
+                )
         try:
             ref_pw = self.get_password('keyring-setting', 'password reference')
             assert ref_pw == 'password reference value'


Test patch:
diff --git a/keyrings/cryptfile/tests/test_cryptfile.py b/keyrings/cryptfile/tests/test_cryptfile.py
index ddcddb8..679d36c 100644
--- a/keyrings/cryptfile/tests/test_cryptfile.py
+++ b/keyrings/cryptfile/tests/test_cryptfile.py
@@ -135,6 +135,24 @@ def test_versions(version, activities, monkeypatch, tmp_path):
         else:
             raise Exception('unexpected activity selection')
 
+def test_password_via_env(monkeypatch, tmp_path):
+    fake_getpass = mock.Mock(return_value='passwd')
+    monkeypatch.setattr(getpass, 'getpass', fake_getpass)
+
+    kr = cryptfile.CryptFileKeyring()
+    kr.file_path = fspath(tmp_path.joinpath('cp_new.cfg'))
+    kr.set_password('test write', 'user', 'test password')
+
+    fake_getpass = mock.Mock(return_value='wrong passwd')
+    os.environ['KEYRING_CRYPTFILE_PASSWORD'] = "passwd"
+    monkeypatch.setattr(getpass, 'getpass', fake_getpass)
+
+    # now create a new one and get password here without prompt
+    kr = cryptfile.CryptFileKeyring()
+    kr.file_path = fspath(tmp_path.joinpath('cp_new.cfg'))
+
+    assert kr.get_password('test write', 'user') == 'test password'
+
 
 def test_new_file(monkeypatch, tmp_path):
     fake_getpass = mock.Mock(return_value='passwd')