diff --git a/NEWS b/NEWS index 0be5f85e..7ae2cb0a 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ - osc rlog now works with srcmd5 also - plugins now should be placed in /usr/lib/osc-plugins to match FHS (the /var path is still supported though) - osc now includes automatically generated man page +- osc can now store credentials in Gnome keyring if it is available 0.117: - support checkout of single package via "osc co PACKAGE" when local dir is project diff --git a/README b/README index 70a77e21..5ce626a4 100644 --- a/README +++ b/README @@ -115,6 +115,15 @@ described above. So basically just adjust all apiurl sections (it might be the case that some sections already have the correct format). +KEYRING USAGE + +Osc now can store passwords in Gnome keyring instead of ~/.oscrc. To use it, +you need python-gnomekeyring and keyring daemon running. + +If you want to switch to using Gnome keyring you need to delete apiurl section +from ~/.oscrc and you will be asked for credentials again, which will be then +stored in Gnome keyring. + USAGE EXAMPLES: (online at http://en.opensuse.org/Build_Service/CLI ) diff --git a/osc/commandline.py b/osc/commandline.py index ae5d4acd..3802efa9 100755 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -88,6 +88,8 @@ class Osc(cmdln.Cmdln): optparser.add_option('-c', '--config', dest='conffile', metavar='FILE', help='specify alternate configuration file') + optparser.add_option('--no-gnome-keyring', action='store_true', + help='disable usage of GNOME Keyring') return optparser @@ -99,7 +101,8 @@ class Osc(cmdln.Cmdln): override_debug = self.options.debug, override_http_debug = self.options.http_debug, override_traceback = self.options.traceback, - override_post_mortem = self.options.post_mortem) + override_post_mortem = self.options.post_mortem, + override_no_gnome_keyring = self.options.no_gnome_keyring) except oscerr.NoConfigfile, e: print >>sys.stderr, e.msg print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file @@ -118,13 +121,7 @@ class Osc(cmdln.Cmdln): import getpass user = raw_input('Username: ') passwd = getpass.getpass() - cp = conf.get_configParser() - cp.add_section(e.url) - cp.set(e.url, 'user', user) - cp.set(e.url, 'pass', passwd) - file = open(e.file, 'w') - cp.write(file, True) - if file: file.close() + conf.add_section(e.file, e.url, user, passwd) if try_again: self.postoptparse(try_again = False) self.conf = conf diff --git a/osc/conf.py b/osc/conf.py index 69c36d3b..74704645 100644 --- a/osc/conf.py +++ b/osc/conf.py @@ -32,6 +32,14 @@ The configuration dictionary could look like this: import OscConfigParser from osc import oscerr +try: + import gobject + gobject.set_application_name('osc') + import gnomekeyring + GNOME_KEYRING = gnomekeyring.is_available() +except: + GNOME_KEYRING = False + # being global to this module, this dict can be accessed from outside # it will hold the parsed configuration config = { } @@ -54,6 +62,7 @@ DEFAULTS = { 'apiurl': 'https://api.opensuse.org', 'http_debug': '0', 'traceback': '0', 'post_mortem': '0', + 'gnome_keyring': '1', 'cookiejar': '~/.osc_cookiejar', # enable project tracking by default 'do_package_tracking': '1', @@ -104,6 +113,9 @@ apiurl = %(apiurl)s # print call traces in case of errors #traceback = 1 +# use GNOME keyring for credentials if available +#gnome_keyring = 0 + [%(apiurl)s] user = %(user)s pass = %(pass)s @@ -114,6 +126,8 @@ pass = %(pass)s # additional headers to pass to a request, e.g. for special authentication #http_headers = Host: foofoobar, # User: mumblegack +# Force using of keyring for this API +#keyring = 1 """ @@ -261,6 +275,16 @@ def write_initial_config(conffile, entries, custom_template = ''): conf_template = custom_template or new_conf_template config = DEFAULTS.copy() config.update(entries) + if config['gnome_keyring'] and GNOME_KEYRING: + protocol, host = \ + parse_apisrv_url(None, config['apisrv']) + gnomekeyring.set_network_password_sync( + user = config['user'], + password = config['pass'], + protocol = protocol, + server = host) + config['user'] = '' + config['pass'] = '' sio = StringIO.StringIO(conf_template.strip() % config) cp = OscConfigParser.OscConfigParser(DEFAULTS) cp.readfp(sio) @@ -279,13 +303,41 @@ def write_initial_config(conffile, entries, custom_template = ''): finally: if file: file.close() +def add_section(filename, url, user, passwd): + """ + Add a section to config file for new api url. + """ + global config + cp = get_configParser(filename) + try: + cp.add_section(url) + except OscConfigParser.ConfigParser.DuplicateSectionError: + # Section might have existed, but was empty + pass + if config['gnome_keyring'] and GNOME_KEYRING: + protocol, host = \ + parse_apisrv_url(None, url) + gnomekeyring.set_network_password_sync( + user = user, + password = passwd, + protocol = protocol, + server = host) + cp.set(url, 'keyring', '1') + else: + cp.set(url, 'user', user) + cp.set(url, 'pass', passwd) + file = open(filename, 'w') + cp.write(file, True) + if file: file.close() + def get_config(override_conffile = None, override_apiurl = None, override_debug = None, override_http_debug = None, override_traceback = None, - override_post_mortem = None): + override_post_mortem = None, + override_no_gnome_keyring = None): """do the actual work (see module documentation)""" import os import sys @@ -346,16 +398,38 @@ def get_config(override_conffile = None, # the following regexp does _not_ support quoted commas within the value. http_header_regexp = re.compile(r"\s*(.*?)\s*:\s*(.*?)\s*(?:,\s*|\Z)") + # override values which we were called with + # This needs to be done before processing API sections as it might be already used there + if override_no_gnome_keyring: + config['gnome_keyring'] = False + aliases = {} for url in [ x for x in cp.sections() if x != 'general' ]: # backward compatiblity scheme, host = \ parse_apisrv_url(config.get('scheme', 'https'), url) apiurl = urljoin(scheme, host) - #FIXME: this could actually be the ideal spot to take defaults - #from the general section. - user = cp.get(url, 'user') - password = cp.get(url, 'pass') + user = None + # Read from gnome keyring if available + if config['gnome_keyring'] and GNOME_KEYRING: + try: + gk_data = gnomekeyring.find_network_password_sync( + protocol = scheme, + server = host) + password = gk_data[0]['password'] + user = gk_data[0]['user'] + except gnomekeyring.NoMatchError: + # Fallback to file based auth. + pass + # Read credentials from config + if user is None: + #FIXME: this could actually be the ideal spot to take defaults + #from the general section. + user = cp.get(url, 'user') + password = cp.get(url, 'pass') + if cp.has_option(url, 'keyring') and cp.get(url, 'keyring'): + # This APIURL was configured to use keyring by + continue email = '' if cp.has_option(url, 'email'): email = cp.get(url, 'email')