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