1""" 2Core API functions and initialization routines. 3""" 4 5import os 6import sys 7import logging 8 9from .py27compat import configparser, filter 10from .py33compat import max 11 12from . import backend 13from .util import platform_ as platform 14from .backends import fail 15 16 17log = logging.getLogger(__name__) 18 19_keyring_backend = None 20 21 22def set_keyring(keyring): 23 """Set current keyring backend. 24 """ 25 global _keyring_backend 26 if not isinstance(keyring, backend.KeyringBackend): 27 raise TypeError("The keyring must be a subclass of KeyringBackend") 28 _keyring_backend = keyring 29 30 31def get_keyring(): 32 """Get current keyring backend. 33 """ 34 return _keyring_backend 35 36 37def disable(): 38 """ 39 Configure the null keyring as the default. 40 """ 41 root = platform.config_root() 42 try: 43 os.makedirs(root) 44 except OSError: 45 pass 46 filename = os.path.join(root, 'keyringrc.cfg') 47 if os.path.exists(filename): 48 msg = "Refusing to overwrite {filename}".format(**locals()) 49 raise RuntimeError(msg) 50 with open(filename, 'w') as file: 51 file.write('[backend]\ndefault-keyring=keyring.backends.null.Keyring') 52 53 54def get_password(service_name, username): 55 """Get password from the specified service. 56 """ 57 return _keyring_backend.get_password(service_name, username) 58 59 60def set_password(service_name, username, password): 61 """Set password for the user in the specified service. 62 """ 63 _keyring_backend.set_password(service_name, username, password) 64 65 66def delete_password(service_name, username): 67 """Delete the password for the user in the specified service. 68 """ 69 _keyring_backend.delete_password(service_name, username) 70 71 72def get_credential(service_name, username): 73 """Get a Credential for the specified service. 74 """ 75 return _keyring_backend.get_credential(service_name, username) 76 77 78def recommended(backend): 79 return backend.priority >= 1 80 81 82def init_backend(limit=None): 83 """ 84 Load a keyring specified in the config file or infer the best available. 85 86 Limit, if supplied, should be a callable taking a backend and returning 87 True if that backend should be included for consideration. 88 """ 89 # save the limit for the chainer to honor 90 backend._limit = limit 91 92 # get all keyrings passing the limit filter 93 keyrings = filter(limit, backend.get_all_keyring()) 94 95 set_keyring( 96 load_env() 97 or load_config() 98 or max(keyrings, default=fail.Keyring(), key=backend.by_priority) 99 ) 100 101 102def _load_keyring_class(keyring_name): 103 """ 104 Load the keyring class indicated by name. 105 106 These popular names are tested to ensure their presence. 107 108 >>> popular_names = [ 109 ... 'keyring.backends.Windows.WinVaultKeyring', 110 ... 'keyring.backends.OS_X.Keyring', 111 ... 'keyring.backends.kwallet.DBusKeyring', 112 ... 'keyring.backends.SecretService.Keyring', 113 ... ] 114 >>> list(map(_load_keyring_class, popular_names)) 115 [...] 116 117 These legacy names are retained for compatibility. 118 119 >>> legacy_names = [ 120 ... ] 121 >>> list(map(_load_keyring_class, legacy_names)) 122 [...] 123 """ 124 module_name, sep, class_name = keyring_name.rpartition('.') 125 __import__(module_name) 126 module = sys.modules[module_name] 127 return getattr(module, class_name) 128 129 130def load_keyring(keyring_name): 131 """ 132 Load the specified keyring by name (a fully-qualified name to the 133 keyring, such as 'keyring.backends.file.PlaintextKeyring') 134 """ 135 class_ = _load_keyring_class(keyring_name) 136 # invoke the priority to ensure it is viable, or raise a RuntimeError 137 class_.priority 138 return class_() 139 140 141def load_env(): 142 """Load a keyring configured in the environment variable.""" 143 try: 144 return load_keyring(os.environ['PYTHON_KEYRING_BACKEND']) 145 except KeyError: 146 pass 147 148 149def load_config(): 150 """Load a keyring using the config file in the config root.""" 151 152 filename = 'keyringrc.cfg' 153 154 keyring_cfg = os.path.join(platform.config_root(), filename) 155 156 if not os.path.exists(keyring_cfg): 157 return 158 159 config = configparser.RawConfigParser() 160 config.read(keyring_cfg) 161 _load_keyring_path(config) 162 163 # load the keyring class name, and then load this keyring 164 try: 165 if config.has_section("backend"): 166 keyring_name = config.get("backend", "default-keyring").strip() 167 else: 168 raise configparser.NoOptionError('backend', 'default-keyring') 169 170 except (configparser.NoOptionError, ImportError): 171 logger = logging.getLogger('keyring') 172 logger.warning("Keyring config file contains incorrect values.\n" 173 + "Config file: %s" % keyring_cfg) 174 return 175 176 return load_keyring(keyring_name) 177 178 179def _load_keyring_path(config): 180 "load the keyring-path option (if present)" 181 try: 182 path = config.get("backend", "keyring-path").strip() 183 sys.path.insert(0, path) 184 except (configparser.NoOptionError, configparser.NoSectionError): 185 pass 186 187 188# init the _keyring_backend 189init_backend() 190