Skip to content

Commit

Permalink
Fixes jaraco#350 Add get_credential() API
Browse files Browse the repository at this point in the history
Adds a new API to enable backends to return a username along with the password.
The entry points include fallback behavior for backends that do not implement the API.
  • Loading branch information
zooba committed Oct 27, 2018
1 parent 869b2a3 commit f32f39c
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 3 deletions.
4 changes: 2 additions & 2 deletions keyring/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import absolute_import

from .core import (set_keyring, get_keyring, set_password, get_password,
delete_password)
delete_password, get_credential)

__all__ = (
'set_keyring', 'get_keyring', 'set_password', 'get_password',
'delete_password',
'delete_password', 'get_credential',
)
23 changes: 22 additions & 1 deletion keyring/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import entrypoints

from . import errors, util
from . import credentials, errors, util
from .util import properties
from .py27compat import add_metaclass, filter

Expand Down Expand Up @@ -108,6 +108,27 @@ def delete_password(self, service, username):
"""
raise errors.PasswordDeleteError("reason")

# for backward-compatibility, don't require a backend to implement
# get_credential
# @abc.abstractmethod
def get_credential(self, service, username):
"""Gets the username and password for the service.
Returns a Credential instance.
The *username* argument is optional and may be omitted by
the caller or ignored by the backend. Callers must use the
returned username.
"""
# The default implementation requires a username here.
if username is not None:
password = self.get_password(service, username)
if password is not None:
return credentials.SimpleCredential(
username,
password,
)
return None


class Crypter:
"""Base class providing encryption and decryption
Expand Down
16 changes: 16 additions & 0 deletions keyring/backends/Windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ..py27compat import text_type
from ..util import properties
from ..backend import KeyringBackend
from ..credentials import SimpleCredential
from ..errors import PasswordDeleteError, ExceptionRaisedContext

try:
Expand Down Expand Up @@ -127,6 +128,21 @@ def _delete_password(self, target):
TargetName=target,
)

def get_credential(self, service, username):
res = None
# get the credentials associated with the provided username
if username:
res = self._get_password(self._compound_name(username, service))
# get any first password under the service name
if not res:
res = self._get_password(service)
if not res:
return None
return SimpleCredential(
res['UserName'],
res['CredentialBlob'].decode('utf-16'),
)


class OldPywinError:
"""
Expand Down
6 changes: 6 additions & 0 deletions keyring/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def delete_password(service_name, username):
_keyring_backend.delete_password(service_name, username)


def get_credential(service_name, username):
"""Get a Credential for the specified service.
"""
return _keyring_backend.get_credential(service_name, username)


def recommended(backend):
return backend.priority >= 1

Expand Down
24 changes: 24 additions & 0 deletions keyring/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,27 @@ def test_different_user(self):
assert keyring.get_password('service1', 'user2') == 'password2'
self.set_password('service2', 'user3', 'password3')
assert keyring.get_password('service1', 'user1') == 'password1'

def test_credential(self):
keyring = self.keyring

cred = keyring.get_credential('service', None)
assert cred is None

self.set_password('service1', 'user1', 'password1')
self.set_password('service1', 'user2', 'password2')

cred = keyring.get_credential('service1', None)
assert cred is not None
assert (cred.username, cred.password) in (
('user1', 'password1'),
('user2', 'password2'),
(None, None),
)

cred = keyring.get_credential('service1', 'user2')
assert cred is not None
assert (cred.username, cred.password) in (
('user1', 'password1'),
('user2', 'password2'),
)

0 comments on commit f32f39c

Please sign in to comment.