Skip to content

Commit

Permalink
Drafting suggested edits for #351.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Oct 27, 2018
1 parent 5c84315 commit 7886086
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 46 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, get_username_and_password)
delete_password, get_credential)

__all__ = (
'set_keyring', 'get_keyring', 'set_password', 'get_password',
'delete_password', 'get_username_and_password',
'delete_password', 'get_credential',
)
10 changes: 5 additions & 5 deletions keyring/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import entrypoints

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

Expand Down Expand Up @@ -109,11 +110,11 @@ def delete_password(self, service, username):
raise errors.PasswordDeleteError("reason")

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


class Crypter:
Expand Down
7 changes: 4 additions & 3 deletions keyring/backends/Windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..util import properties
from ..backend import KeyringBackend
from ..errors import PasswordDeleteError, ExceptionRaisedContext
from .. import credentials

try:
# prefer pywin32-ctypes
Expand Down Expand Up @@ -127,7 +128,7 @@ def _delete_password(self, target):
TargetName=target,
)

def get_username_and_password(self, service, username):
def get_credential(self, service, username):
res = None
# get the credentials associated with the provided username
if username:
Expand All @@ -136,10 +137,10 @@ def get_username_and_password(self, service, username):
if not res:
res = self._get_password(service)
if not res:
return None, None
return None
username = res['UserName']
blob = res['CredentialBlob']
return username, blob.decode('utf-16')
return credentials.SimpleCredential(username, blob.decode('utf-16'))


class OldPywinError:
Expand Down
16 changes: 2 additions & 14 deletions keyring/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,10 @@ def delete_password(service_name, username):
_keyring_backend.delete_password(service_name, username)


def get_username_and_password(service_name, username):
def get_credential(service_name, username=None):
"""Get username and password from the specified service.
"""
try:
call = _keyring_backend.get_username_and_password
except AttributeError:
pass
else:
return call(service_name, username)

# The fallback behavior requires a username.
if username is not None:
password = _keyring_backend.get_password(service_name, username)
if password is not None:
return username, password
return None, None
return _keyring_backend.get_credential(service_name, username)


def recommended(backend):
Expand Down
17 changes: 4 additions & 13 deletions keyring/credentials.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import abc
import collections

from .py27compat import add_metaclass

Expand All @@ -20,22 +21,12 @@ def password(self):
return None


class SimpleCredential(Credential):
class SimpleCredential(
collections.namedtuple('Credential', 'username password'),
Credential):
"""Simple credentials implementation
"""

def __init__(self, username, password):
self._username = username
self._password = password

@property
def username(self):
return self._username

@property
def password(self):
return self._password


class EnvironCredential(Credential):
"""Source credentials from environment variables.
Expand Down
19 changes: 10 additions & 9 deletions keyring/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .util import random_string
from keyring import errors
from keyring import credentials

__metaclass__ = type

Expand Down Expand Up @@ -135,21 +136,21 @@ def test_different_user(self):
self.set_password('service2', 'user3', 'password3')
assert keyring.get_password('service1', 'user1') == 'password1'

def test_username_and_password(self):
def test_get_credential(self):
keyring = self.keyring
get = keyring.get_username_and_password
get = keyring.get_credential

assert get('service1', None) == (None, None)
assert get('service1', None) is None
self.set_password('service1', 'user1', 'password1')
self.set_password('service1', 'user2', 'password2')

# Using get_username_and_password may produce any of these results
valid = set((
('user1', 'password1'),
('user2', 'password2'),
))
# Using get_credential may produce any of these results
valid = {
credentials.SimpleCredential('user1', 'password1'),
credentials.SimpleCredential('user2', 'password2'),
}
# Passing None for username may result in no password
valid_or_none = valid | set(((None, None),))
valid_or_none = valid | {None}

assert get('service1', None) in valid_or_none
assert get('service1', 'user2') in valid

0 comments on commit 7886086

Please sign in to comment.