Skip to content

Commit

Permalink
Merge pull request #143 from ralphbean/feature/vault
Browse files Browse the repository at this point in the history
Add a bugwarrior-vault command.
  • Loading branch information
ralphbean committed Sep 19, 2014
2 parents 2bbc92f + 7f1c317 commit c97e512
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 24 deletions.
3 changes: 2 additions & 1 deletion bugwarrior/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#
from bugwarrior.command import pull
from bugwarrior.command import pull, vault

__all__ = [
'pull',
'vault',
]
75 changes: 74 additions & 1 deletion bugwarrior/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@
from lockfile import LockTimeout
from lockfile.pidlockfile import PIDLockFile

import getpass
import keyring
import click

from taskw.warrior import TaskWarriorBase

from bugwarrior.config import get_taskrc_path, load_config
from bugwarrior.services import aggregate_issues
from bugwarrior.services import aggregate_issues, SERVICES
from bugwarrior.db import synchronize


# We overwrite 'list' further down.
lst = list


@click.command()
def pull():
""" Pull down tasks from forges and add them to your taskwarrior tasks.
Relies on configuration in ~/.bugwarriorrc
"""
try:
# Load our config file
config = load_config()
Expand Down Expand Up @@ -44,3 +57,63 @@ def pull():
)
except:
log.name('command').trace('error').critical('oh noes')


@click.group()
def vault():
""" Password/keyring management for bugwarrior.
If you use the keyring password oracle in your bugwarrior config, this tool
can be used to manage your keyring.
"""
pass


def targets():
config = load_config()
for section in config.sections():
if section in ['general']:
continue
service_name = config.get(section, 'service')
service_class = SERVICES[service_name]
for option in config.options(section):
value = config.get(section, option)
if not value:
continue
if '@oracle:use_keyring' in value:
yield service_class.get_keyring_service(config, section)


@vault.command()
def list():
pws = lst(targets())
print "%i @oracle:use_keyring passwords in .bugwarriorrc" % len(pws)
for section in pws:
print "-", section


@vault.command()
@click.argument('target')
@click.argument('username')
def clear(target, username):
target_list = lst(targets())
if target not in target_list:
raise ValueError("%s must be one of %r" % (target, target_list))

if keyring.get_password(target, username):
keyring.delete_password(target, username)
print "Password cleared for %s, %s" % (target, username)
else:
print "No password found for %s, %s" % (target, username)


@vault.command()
@click.argument('target')
@click.argument('username')
def set(target, username):
target_list = lst(targets())
if target not in target_list:
raise ValueError("%s must be one of %r" % (target, target_list))

keyring.set_password(target, username, getpass.getpass())
print "Password set for %s, %s" % (target, username)
13 changes: 8 additions & 5 deletions bugwarrior/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,9 @@ def config_get(self, key=None, to_type=None):
return to_type(value)
return value

def _get_key(self, key):
return '%s.%s' % (
self.CONFIG_PREFIX,
key
)
@classmethod
def _get_key(cls, key):
return '%s.%s' % (cls.CONFIG_PREFIX, key)

def get_service_metadata(self):
return {}
Expand Down Expand Up @@ -222,6 +220,11 @@ def issues(self):
"""
raise NotImplementedError()

@classmethod
def get_keyring_service(cls, config, section):
""" Given the keyring service name for this service. """
raise NotImplementedError


class Issue(object):
# Set to a dictionary mapping UDA short names with type and long name.
Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/bitbucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,19 @@ def __init__(self, *args, **kw):
password = self.config_get_default('password')
if not password or password.startswith('@oracle:'):
username = self.config_get('username')
service = "bitbucket://%[email protected]/%s" % (login, username)
password = get_service_password(
service, login, oracle=password,
self.get_keyring_service(self.config, self.target),
login, oracle=password,
interactive=self.config.interactive)

self.auth = (login, password)

@classmethod
def get_keyring_service(cls, config, section):
login = config.get(section, cls._get_key('login'))
username = config.get(section, cls._get_key('username'))
return "bitbucket://%[email protected]/%s" % (login, username)

def get_data(self, url):
response = requests.get(self.BASE_API + url, auth=self.auth)

Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/bz.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,22 @@ def __init__(self, *args, **kw):
self.advanced = asbool(self.config_get_default('advanced', 'no'))

if not self.password or self.password.startswith("@oracle:"):
service = "bugzilla://%s@%s" % (self.username, self.base_uri)
self.password = get_service_password(
service, self.username, oracle=self.password,
self.get_keyring_service(self.config, self.target),
self.username, oracle=self.password,
interactive=self.config.interactive
)

url = 'https://%s/xmlrpc.cgi' % self.base_uri
self.bz = bugzilla.Bugzilla(url=url)
self.bz.login(self.username, self.password)

@classmethod
def get_keyring_service(cls, config, section):
username = config.get(section, cls._get_key('username'))
base_uri = config.get(section, cls._get_key('base_uri'))
return "bugzilla://%s@%s" % (username, base_uri)

@classmethod
def validate_config(cls, config, target):
req = ['bugzilla.username', 'bugzilla.password', 'bugzilla.base_uri']
Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ def __init__(self, *args, **kw):
password = self.config_get_default('password')
if not password or password.startswith('@oracle:'):
username = self.config_get('username')
service = "github://%[email protected]/%s" % (login, username)
password = get_service_password(
service, login, oracle=password,
self.get_keyring_service(self.config, self.target),
login, oracle=password,
interactive=self.config.interactive
)
self.auth = (login, password)
Expand Down Expand Up @@ -156,6 +156,12 @@ def __init__(self, *args, **kw):
'filter_pull_requests', default=False, to_type=asbool
)

@classmethod
def get_keyring_service(cls, config, section):
login = config.get(section, cls._get_key('login'))
username = config.get(section, cls._get_key('username'))
return "github://%[email protected]/%s" % (login, username)

def get_service_metadata(self):
return {
'import_labels_as_tags': self.import_labels_as_tags,
Expand Down
11 changes: 8 additions & 3 deletions bugwarrior/services/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,9 @@ def __init__(self, *args, **kw):
self.url = self.config_get('base_uri')
password = self.config_get('password')
if not password or password.startswith("@oracle:"):
service = "jira://%s@%s" % (self.username, self.url)
password = get_service_password(
service, self.username,
oracle=password,
self.get_keyring_service(self.config, self.target),
self.username, oracle=password,
interactive=self.config.interactive
)

Expand All @@ -143,6 +142,12 @@ def __init__(self, *args, **kw):
'label_template', default='{{label}}', to_type=six.text_type
)

@classmethod
def get_keyring_service(cls, config, section):
username = config.get(section, cls._get_key('username'))
base_uri = config.get(section, cls._get_key('base_uri'))
return "jira://%s@%s" % (username, base_uri)

def get_service_metadata(self):
return {
'url': self.url,
Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/mplan.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ def __init__(self, *args, **kw):
_login = self.config_get('login')
_password = self.config_get('password')
if not _password or _password.startswith("@oracle:"):
service = "megaplan://%s@%s" % (_login, self.hostname)
_password = get_service_password(
service, _login, oracle=_password,
self.get_keyring_service(self.config, self.target),
_login, oracle=_password,
interactive=self.config.interactive
)

Expand All @@ -88,6 +88,12 @@ def __init__(self, *args, **kw):
'project_name', self.hostname
)

@classmethod
def get_keyring_service(cls, config, section):
login = config.get(section, cls._get_key('login'))
hostname = config.get(section, cls._get_key('hostname'))
return "megaplan://%s@%s" % (login, hostname)

def get_service_metadata(self):
return {
'project_name': self.project_name,
Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/teamlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ def __init__(self, *args, **kw):
_login = self.config_get('login')
_password = self.config_get('password')
if not _password or _password.startswith("@oracle:"):
service = "teamlab://%s@%s" % (_login, self.hostname)
_password = get_service_password(
service, _login, oracle=_password,
self.get_keyring_service(self.config, self.target),
_login, oracle=_password,
interactive=self.config.interactive
)

Expand All @@ -137,6 +137,12 @@ def __init__(self, *args, **kw):
'project_name', self.hostname
)

@classmethod
def get_keyring_service(cls, config, section):
login = config.get(section, cls._get_key('login'))
hostname = config.get(section, cls._get_key('hostname'))
return "teamlab://%s@%s" % (login, hostname)

def get_service_metadata(self):
return {
'hostname': self.hostname,
Expand Down
10 changes: 8 additions & 2 deletions bugwarrior/services/trac.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ def __init__(self, *args, **kw):
username = self.config_get('username')
password = self.config_get('password')
if not password or password.startswith('@oracle:'):
service = "https://%s@%s/" % (username, base_uri)
password = get_service_password(
service, username, oracle=password,
self.get_keyring_service(self.config, self.target),
username, oracle=password,
interactive=self.config.interactive
)

Expand All @@ -83,6 +83,12 @@ def __init__(self, *args, **kw):
)
self.trac = offtrac.TracServer(uri)

@classmethod
def get_keyring_service(cls, config, section):
username = config.get(section, cls._get_key('username'))
base_uri = config.get(section, cls._get_key('base_uri'))
return "https://%s@%s/" % (username, base_uri)

@classmethod
def validate_config(cls, config, target):
for option in ['trac.username', 'trac.password', 'trac.base_uri']:
Expand Down
7 changes: 6 additions & 1 deletion docs/source/common_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ from:

* ``password = @oracle:use_keyring``: Retrieve a password from a keyring.
* ``password = @oracle:ask_password``: Ask for a password at runtime.
* ``password = @oracle:eval:<command>`` Use the output of <command> as the password.
* ``password = @oracle:eval:<command>`` Use the output of <command> as the
password.

If you use the ``use_keyring`` oracle, you may want to also check out the
``bugwarrior-vault`` command line tool. It can be used to manage your
passwords as stored in your local keyring (say to reset them or clear them).

Hooks
-----
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"jinja2>=2.7.2",
"pycurl",
"dogpile.cache>=0.5.3",
"lockfile>=0.9.1"
"lockfile>=0.9.1",
"click",
],
tests_require=[
"Mock",
Expand All @@ -53,5 +54,6 @@
entry_points="""
[console_scripts]
bugwarrior-pull = bugwarrior:pull
bugwarrior-vault = bugwarrior:vault
""",
)

0 comments on commit c97e512

Please sign in to comment.