Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #350 Add get_username_and_password API #351

Merged
merged 2 commits into from
Oct 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
22 changes: 22 additions & 0 deletions keyring/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,25 @@ 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 None or (cred.username, cred.password) in (
('user1', 'password1'),
('user2', 'password2'),
)

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