Upgrade keyring to 4.0

This commit is contained in:
Matthew Jones
2014-08-06 15:31:20 -04:00
parent 6a7cb5a4e7
commit cbe26c4619
6 changed files with 93 additions and 107 deletions

View File

@@ -30,7 +30,7 @@ gevent-websocket==0.9.3 (geventwebsocket/*)
httplib2==0.9 (httplib2/*) httplib2==0.9 (httplib2/*)
importlib==1.0.3 (importlib/*, needed for Python 2.6 support) importlib==1.0.3 (importlib/*, needed for Python 2.6 support)
iso8601==0.1.10 (iso8601/*) iso8601==0.1.10 (iso8601/*)
keyring==3.7 (keyring/*, excluded bin/keyring) keyring==4.0 (keyring/*, excluded bin/keyring)
kombu==3.0.14 (kombu/*) kombu==3.0.14 (kombu/*)
Markdown==2.4 (markdown/*, excluded bin/markdown_py) Markdown==2.4 (markdown/*, excluded bin/markdown_py)
mock==1.0.1 (mock.py) mock==1.0.1 (mock.py)

View File

@@ -10,10 +10,18 @@ from ..errors import PasswordDeleteError, ExceptionRaisedContext
from . import file from . import file
try: try:
import pywintypes # prefer pywin32-ctypes
import win32cred from win32ctypes import pywintypes
from win32ctypes import win32cred
# force demand import to raise ImportError
win32cred.__name__
except ImportError: except ImportError:
pass # fallback to pywin32
try:
import pywintypes
import win32cred
except ImportError:
pass
try: try:
import winreg import winreg
@@ -229,9 +237,31 @@ class RegistryKeyring(KeyringBackend):
hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_name, 0, hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_name, 0,
winreg.KEY_ALL_ACCESS) winreg.KEY_ALL_ACCESS)
winreg.DeleteValue(hkey, username) winreg.DeleteValue(hkey, username)
winreg.CloseKey(hkey)
except WindowsError: except WindowsError:
e = sys.exc_info()[1] e = sys.exc_info()[1]
raise PasswordDeleteError(e) raise PasswordDeleteError(e)
self._delete_key_if_empty(service)
def _delete_key_if_empty(self, service):
key_name = r'Software\%s\Keyring' % service
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_name, 0,
winreg.KEY_ALL_ACCESS)
try:
winreg.EnumValue(key, 0)
return
except WindowsError:
pass
winreg.CloseKey(key)
# it's empty; delete everything
while key_name != 'Software':
parent, sep, base = key_name.rpartition('\\')
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, parent, 0,
winreg.KEY_ALL_ACCESS)
winreg.DeleteKey(key, base)
winreg.CloseKey(key)
key_name = parent
class OldPywinError(object): class OldPywinError(object):

View File

@@ -62,6 +62,8 @@ class Keyring(KeyringBackend):
KWallet.__name__ KWallet.__name__
if exc: if exc:
raise RuntimeError("KDE libraries not available") raise RuntimeError("KDE libraries not available")
if "DISPLAY" not in os.environ:
raise RuntimeError("cannot connect to X server")
# Infer if KDE environment is active based on environment vars. # Infer if KDE environment is active based on environment vars.
# TODO: Does PyKDE provide a better indicator? # TODO: Does PyKDE provide a better indicator?
kde_session_keys = ( kde_session_keys = (

View File

@@ -37,8 +37,9 @@ class CommandLineTool(object):
if opts.keyring_backend is not None: if opts.keyring_backend is not None:
try: try:
backend = core.load_keyring(opts.keyring_path, if opts.keyring_path:
opts.keyring_backend) sys.path.insert(0, opts.keyring_path)
backend = core.load_keyring(opts.keyring_backend)
set_keyring(backend) set_keyring(backend)
except (Exception,): except (Exception,):
# Tons of things can go wrong here: # Tons of things can go wrong here:

View File

@@ -5,7 +5,6 @@ Created by Kang Zhang on 2009-07-09
""" """
import os import os
import sys import sys
import warnings
import logging import logging
from .py27compat import configparser from .py27compat import configparser
@@ -71,19 +70,12 @@ def _get_best_keyring():
return keyrings[0] return keyrings[0]
def load_keyring(keyring_path, keyring_name): def load_keyring(keyring_name):
""" """
Load the specified keyring by name (a fully-qualified name to the Load the specified keyring by name (a fully-qualified name to the
keyring, such as 'keyring.backends.file.PlaintextKeyring') keyring, such as 'keyring.backends.file.PlaintextKeyring')
`keyring_path` is an additional, optional search path and may be None.
**deprecated** In the future, keyring_path must be None.
""" """
module_name, sep, class_name = keyring_name.rpartition('.') module_name, sep, class_name = keyring_name.rpartition('.')
if keyring_path is not None and keyring_path not in sys.path:
warnings.warn("keyring_path is deprecated and should always be None",
DeprecationWarning)
sys.path.insert(0, keyring_path)
__import__(module_name) __import__(module_name)
module = sys.modules[module_name] module = sys.modules[module_name]
class_ = getattr(module, class_name) class_ = getattr(module, class_name)
@@ -93,46 +85,32 @@ def load_keyring(keyring_path, keyring_name):
def load_config(): def load_config():
"""Load a keyring using the config file. """Load a keyring using the config file in the config root."""
The config file can be in the current working directory, or in the user's
home directory.
"""
keyring = None
filename = 'keyringrc.cfg' filename = 'keyringrc.cfg'
local_path = os.path.join(os.getcwd(), filename) keyring_cfg = os.path.join(platform.config_root(), filename)
config_path = os.path.join(platform.config_root(), filename)
# search from current working directory and the data root if not os.path.exists(keyring_cfg):
keyring_cfg_candidates = [local_path, config_path] return
# initialize the keyring_config with the first detected config file config = configparser.RawConfigParser()
keyring_cfg = None config.read(keyring_cfg)
for path in keyring_cfg_candidates: _load_keyring_path(config)
keyring_cfg = path
if os.path.exists(path):
break
if os.path.exists(keyring_cfg): # load the keyring class name, and then load this keyring
config = configparser.RawConfigParser() try:
config.read(keyring_cfg) if config.has_section("backend"):
_load_keyring_path(config) keyring_name = config.get("backend", "default-keyring").strip()
else:
raise configparser.NoOptionError('backend', 'default-keyring')
# load the keyring class name, and then load this keyring except (configparser.NoOptionError, ImportError):
try: logger.warning("Keyring config file contains incorrect values.\n" +
if config.has_section("backend"): "Config file: %s" % keyring_cfg)
keyring_name = config.get("backend", "default-keyring").strip() return
else:
raise configparser.NoOptionError('backend', 'default-keyring')
keyring = load_keyring(None, keyring_name) return load_keyring(keyring_name)
except (configparser.NoOptionError, ImportError):
logger.warning("Keyring config file contains incorrect values.\n" +
"Config file: %s" % keyring_cfg)
return keyring
def _load_keyring_path(config): def _load_keyring_path(config):
"load the keyring-path option (if present)" "load the keyring-path option (if present)"

View File

@@ -10,17 +10,23 @@ import os
import tempfile import tempfile
import shutil import shutil
from keyring.tests.py30compat import unittest
import mock import mock
import pytest
import keyring.backend import keyring.backend
import keyring.core import keyring.core
import keyring.util.platform_
from keyring import errors from keyring import errors
PASSWORD_TEXT = "This is password" PASSWORD_TEXT = "This is password"
PASSWORD_TEXT_2 = "This is password2" PASSWORD_TEXT_2 = "This is password2"
KEYRINGRC = "keyringrc.cfg"
@pytest.yield_fixture()
def config_filename(tmpdir):
filename = tmpdir / 'keyringrc.cfg'
with mock.patch('keyring.util.platform_.config_root', lambda: str(tmpdir)):
yield str(filename)
class TestKeyring(keyring.backend.KeyringBackend): class TestKeyring(keyring.backend.KeyringBackend):
@@ -53,7 +59,7 @@ class TestKeyring2(TestKeyring):
return PASSWORD_TEXT_2 return PASSWORD_TEXT_2
class CoreTestCase(unittest.TestCase): class TestCore:
mock_global_backend = mock.patch('keyring.core._keyring_backend') mock_global_backend = mock.patch('keyring.core._keyring_backend')
@mock_global_backend @mock_global_backend
@@ -72,7 +78,7 @@ class CoreTestCase(unittest.TestCase):
""" """
result = keyring.core.get_password("test", "user") result = keyring.core.get_password("test", "user")
backend.get_password.assert_called_once_with('test', 'user') backend.get_password.assert_called_once_with('test', 'user')
self.assertIsNotNone(result) assert result is not None
@mock_global_backend @mock_global_backend
def test_delete_password(self, backend): def test_delete_password(self, backend):
@@ -85,71 +91,40 @@ class CoreTestCase(unittest.TestCase):
keyring.core.set_keyring(TestKeyring()) keyring.core.set_keyring(TestKeyring())
keyring.core.set_password("test", "user", "password") keyring.core.set_password("test", "user", "password")
self.assertEqual(keyring.core.get_password("test", "user"), assert keyring.core.get_password("test", "user") == PASSWORD_TEXT
PASSWORD_TEXT)
def test_set_keyring_in_config(self): def test_set_keyring_in_config(self, config_filename):
"""Test setting the keyring by config file. """Test setting the keyring by config file.
""" """
# create the config file # create the config file
config_file = open(KEYRINGRC, 'w') with open(config_filename, 'w') as config_file:
config_file.writelines([ config_file.writelines([
"[backend]\n", "[backend]\n",
# the path for the user created keyring # the path for the user created keyring
"keyring-path= %s\n" % os.path.dirname(os.path.abspath(__file__)), "keyring-path= %s\n" % os.path.dirname(os.path.abspath(__file__)),
# the name of the keyring class # the name of the keyring class
"default-keyring=test_core.TestKeyring2\n", "default-keyring=test_core.TestKeyring2\n",
]) ])
config_file.close()
# init the keyring lib, the lib will automaticlly load the # init the keyring lib, the lib will automaticlly load the
# config file and load the user defined module # config file and load the user defined module
keyring.core.init_backend() keyring.core.init_backend()
keyring.core.set_password("test", "user", "password") keyring.core.set_password("test", "user", "password")
self.assertEqual(keyring.core.get_password("test", "user"), assert keyring.core.get_password("test", "user") == PASSWORD_TEXT_2
PASSWORD_TEXT_2)
os.remove(KEYRINGRC) def test_load_config_empty(self, config_filename):
"A non-existent or empty config should load"
assert keyring.core.load_config() is None
def test_load_config(self): def test_load_config_degenerate(self, config_filename):
tempdir = tempfile.mkdtemp() "load_config should succeed in the absence of a backend section"
old_location = os.getcwd() with open(config_filename, 'w') as config_file:
os.chdir(tempdir) config_file.write('[keyring]')
personal_cfg = os.path.join(os.path.expanduser("~"), "keyringrc.cfg") assert keyring.core.load_config() is None
if os.path.exists(personal_cfg):
os.rename(personal_cfg, personal_cfg + '.old')
personal_renamed = True
else:
personal_renamed = False
# loading with an empty environment def test_load_config_blank_backend(self, config_filename):
keyring.core.load_config() "load_config should succeed with an empty [backend] section"
with open(config_filename, 'w') as config_file:
# loading with a file that doesn't have a backend section config_file.write('[backend]')
cfg = os.path.join(tempdir, "keyringrc.cfg") assert keyring.core.load_config() is None
f = open(cfg, 'w')
f.write('[keyring]')
f.close()
keyring.core.load_config()
# loading with a file that doesn't have a default-keyring value
cfg = os.path.join(tempdir, "keyringrc.cfg")
f = open(cfg, 'w')
f.write('[backend]')
f.close()
keyring.core.load_config()
os.chdir(old_location)
shutil.rmtree(tempdir)
if personal_renamed:
os.rename(personal_cfg + '.old', personal_cfg)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(CoreTestCase))
return suite
if __name__ == "__main__":
unittest.main(defaultTest="test_suite")