Added passwordeval as an option for the general config section. If no password is provided the command provided as passwordeval will be called with username and hostname as arguments.

This commit is contained in:
vimbaer 2014-09-10 22:03:05 +02:00
parent 9dbb359569
commit 4e895b8635
3 changed files with 64 additions and 4 deletions

View file

@ -10,6 +10,9 @@
import click
from click.testing import CliRunner
from tempfile import mkstemp
import os
import stat
import pytest
import requests
@ -108,6 +111,35 @@ def test_get_password_from_system_keyring(monkeypatch):
assert _password == password
def test_get_password_from_evalcmd():
username = 'my_username'
resource = 'http://example.com'
password = 'testpassword'
fd, temp_path = mkstemp()
os.close(fd)
fp = open(temp_path, 'w')
fp.write('#!/bin/sh\n'
'[ "$1" != "my_username" ] && exit 1\n'
'[ "$2" != "example.com" ] && exit 1\n'
'echo "{}"'.format(password))
fp.close()
st = os.stat(temp_path)
os.chmod(temp_path, st.st_mode | stat.S_IEXEC)
@doubleclick.click.command()
@doubleclick.click.pass_context
def fake_app(ctx):
ctx.obj = {'config' : ({'passwordeval' : temp_path},{},{})}
_password = utils.get_password(username, resource)
assert _password == password
runner = CliRunner()
result = runner.invoke(fake_app)
assert not result.exception
os.remove(temp_path)
def test_get_password_from_prompt():
getpass_calls = []
@ -187,7 +219,6 @@ def test_get_password_from_cache(monkeypatch):
]
def test_get_class_init_args():
class Foobar(object):
def __init__(self, foo, bar, baz=None):

View file

@ -30,7 +30,7 @@ cli_logger = log.get(__name__)
PROJECT_HOME = 'https://github.com/untitaker/vdirsyncer'
DOCS_HOME = 'https://vdirsyncer.readthedocs.org/en/latest'
GENERAL_ALL = set(['processes', 'status_path'])
GENERAL_ALL = set(['processes', 'status_path', 'passwordeval'])
GENERAL_REQUIRED = set(['status_path'])

View file

@ -97,7 +97,9 @@ def get_password(username, resource, _lock=threading.Lock()):
1. read password from netrc (and only the password, username
in netrc will be ignored)
2. read password from keyring (keyring needs to be installed)
3a ask user for the password
3. read password from the command passed as passwordeval in the
general config section with username and host as parameters
4a ask user for the password
b save in keyring if installed and user agrees
:param username: user's name on the server
@ -118,7 +120,7 @@ def get_password(username, resource, _lock=threading.Lock()):
with _lock:
host = urlparse.urlsplit(resource).hostname
for func in (_password_from_cache, _password_from_netrc,
_password_from_keyring):
_password_from_keyring, _password_from_evalcmd):
password = func(username, host)
if password is not None:
logger.debug('Got password for {} from {}'
@ -166,6 +168,33 @@ def _password_from_keyring(username, host):
return keyring.get_password(password_key_prefix + host, username)
def _password_from_evalcmd(username, host):
'''evalcmd'''
import subprocess
try:
general, _, _ = ctx.obj['config']
_evalcmd = general.get('passwordeval', '')
except (KeyError, IndexError):
return None
if _evalcmd == '':
return None
evalcmd = expand_path(_evalcmd)
try:
proc = subprocess.Popen([evalcmd, username, host],
stdout=subprocess.PIPE)
password = proc.stdout.read().strip()
except OSError, e:
logger.debug('Failed to execute evalcmd: {}\n{}'.
format(evalcmd, str(e)))
return None
return password
class _FingerprintAdapter(requests.adapters.HTTPAdapter):
def __init__(self, fingerprint=None, **kwargs):
self.fingerprint = str(fingerprint)