mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Merge pull request #117 from vimbaer/passwordeval
New general config option: passwordeval
This commit is contained in:
commit
3ad598c7b4
3 changed files with 63 additions and 5 deletions
|
|
@ -10,6 +10,8 @@
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
@ -108,6 +110,35 @@ def test_get_password_from_system_keyring(monkeypatch):
|
||||||
assert _password == password
|
assert _password == password
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_password_from_command(tmpdir):
|
||||||
|
username = 'my_username'
|
||||||
|
resource = 'http://example.com'
|
||||||
|
password = 'testpassword'
|
||||||
|
filename = 'command.sh'
|
||||||
|
|
||||||
|
filepath = str(tmpdir) + '/' + filename
|
||||||
|
f = open(filepath, 'w')
|
||||||
|
f.write('#!/bin/sh\n'
|
||||||
|
'[ "$1" != "my_username" ] && exit 1\n'
|
||||||
|
'[ "$2" != "example.com" ] && exit 1\n'
|
||||||
|
'echo "{}"'.format(password))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
st = os.stat(filepath)
|
||||||
|
os.chmod(filepath, st.st_mode | stat.S_IEXEC)
|
||||||
|
|
||||||
|
@doubleclick.click.command()
|
||||||
|
@doubleclick.click.pass_context
|
||||||
|
def fake_app(ctx):
|
||||||
|
ctx.obj = {'config' : ({'passwordeval' : filepath},{},{})}
|
||||||
|
_password = utils.get_password(username, resource)
|
||||||
|
assert _password == password
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(fake_app)
|
||||||
|
assert not result.exception
|
||||||
|
|
||||||
|
|
||||||
def test_get_password_from_prompt():
|
def test_get_password_from_prompt():
|
||||||
getpass_calls = []
|
getpass_calls = []
|
||||||
|
|
||||||
|
|
@ -187,7 +218,6 @@ def test_get_password_from_cache(monkeypatch):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_class_init_args():
|
def test_get_class_init_args():
|
||||||
class Foobar(object):
|
class Foobar(object):
|
||||||
def __init__(self, foo, bar, baz=None):
|
def __init__(self, foo, bar, baz=None):
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ cli_logger = log.get(__name__)
|
||||||
PROJECT_HOME = 'https://github.com/untitaker/vdirsyncer'
|
PROJECT_HOME = 'https://github.com/untitaker/vdirsyncer'
|
||||||
DOCS_HOME = 'https://vdirsyncer.readthedocs.org/en/latest'
|
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'])
|
GENERAL_REQUIRED = set(['status_path'])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,9 @@ def get_password(username, resource, _lock=threading.Lock()):
|
||||||
1. read password from netrc (and only the password, username
|
1. read password from netrc (and only the password, username
|
||||||
in netrc will be ignored)
|
in netrc will be ignored)
|
||||||
2. read password from keyring (keyring needs to be installed)
|
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
|
b save in keyring if installed and user agrees
|
||||||
|
|
||||||
:param username: user's name on the server
|
:param username: user's name on the server
|
||||||
|
|
@ -117,8 +119,8 @@ def get_password(username, resource, _lock=threading.Lock()):
|
||||||
|
|
||||||
with _lock:
|
with _lock:
|
||||||
host = urlparse.urlsplit(resource).hostname
|
host = urlparse.urlsplit(resource).hostname
|
||||||
for func in (_password_from_cache, _password_from_netrc,
|
for func in (_password_from_command, _password_from_cache,
|
||||||
_password_from_keyring):
|
_password_from_netrc, _password_from_keyring):
|
||||||
password = func(username, host)
|
password = func(username, host)
|
||||||
if password is not None:
|
if password is not None:
|
||||||
logger.debug('Got password for {} from {}'
|
logger.debug('Got password for {} from {}'
|
||||||
|
|
@ -166,6 +168,32 @@ def _password_from_keyring(username, host):
|
||||||
return keyring.get_password(password_key_prefix + host, username)
|
return keyring.get_password(password_key_prefix + host, username)
|
||||||
|
|
||||||
|
|
||||||
|
def _password_from_command(username, host):
|
||||||
|
'''command'''
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
try:
|
||||||
|
general, _, _ = ctx.obj['config']
|
||||||
|
_command = general['passwordeval'].split()
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
command = [expand_path(_command[0])]
|
||||||
|
if len(_command) > 1:
|
||||||
|
command += _command[1:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(command + [username, host],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
password = proc.stdout.read().decode('utf-8').strip()
|
||||||
|
except OSError as e:
|
||||||
|
logger.debug('Failed to execute command: {}\n{}'.
|
||||||
|
format(" ".join(command), str(e)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
return password
|
||||||
|
|
||||||
|
|
||||||
class _FingerprintAdapter(requests.adapters.HTTPAdapter):
|
class _FingerprintAdapter(requests.adapters.HTTPAdapter):
|
||||||
def __init__(self, fingerprint=None, **kwargs):
|
def __init__(self, fingerprint=None, **kwargs):
|
||||||
self.fingerprint = str(fingerprint)
|
self.fingerprint = str(fingerprint)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue