Added initial support for saving VNC password in the GNOME keyring

This commit is contained in:
Daniel P. Berrange 2006-07-19 14:33:23 -04:00
parent 5e10581061
commit 05d9d83af6
5 changed files with 372 additions and 10 deletions

View File

@ -20,6 +20,8 @@ import gconf
import gtk.gdk
from virtManager.keyring import *
class vmmConfig:
def __init__(self, appname, gconf_dir, glade_dir, icon_dir):
self.appname = appname
@ -30,6 +32,10 @@ class vmmConfig:
self.glade_dir = glade_dir
self.icon_dir = icon_dir
# We don;t create it straight away, since we don't want
# to block the app pending user authorizaation to access
# the keyring
self.keyring = None
self.status_icons = {
"blocked": gtk.gdk.pixbuf_new_from_file_at_size(self.get_icon_dir() + "/state_blocked.png", 18, 18),
@ -153,3 +159,48 @@ class vmmConfig:
self.conf.notify_add(self.conf_dir + "/stats/history-length", callback)
def get_secret_name(self, vm):
return "vm-console-" + vm.get_uuid()
def clear_console_password(self, vm):
id = self.conf.get_int(self.conf_dir + "/console/passwords/" + vm.get_uuid())
if id != None:
if self.keyring == None:
self.keyring = vmmKeyring()
self.keyring.clear_secret(id)
self.conf.unset(self.conf_dir + "/console/passwords/" + vm.get_uuid())
def get_console_password(self, vm):
id = self.conf.get_int(self.conf_dir + "/console/passwords/" + vm.get_uuid())
if id != None:
if self.keyring == None:
try:
self.keyring = vmmKeyring()
except:
print "Unable to access keyring"
return ""
secret = self.keyring.get_secret(id)
if secret != None and secret.get_name() == self.get_secret_name(vm):
# XXX validate attributes
return secret.get_secret()
return ""
def set_console_password(self, vm, password):
if self.keyring == None:
try:
self.keyring = vmmKeyring()
except:
print "Unable to access keyring"
return
# Nb, we don't bother to check if there is an existing
# secret, because gnome-keyring auto-replaces an existing
# one if the attributes match - which they will since UUID
# is our unique key
secret = vmmSecret(self.get_secret_name(vm), password, { "uuid" : vm.get_uuid(), "hvuri": vm.get_connection().get_uri() })
id = self.keyring.add_secret(secret)
self.conf.set_int(self.conf_dir + "/console/passwords/" + vm.get_uuid(), id)

View File

@ -119,8 +119,7 @@ class vmmConsole(gobject.GObject):
return 0
def _vnc_disconnected(self, src):
self.window.get_widget("console-auth-password").set_text("")
self.window.get_widget("console-pages").set_current_page(2)
self.activate_auth_page()
def try_login(self, src=None):
password = self.window.get_widget("console-auth-password").get_text()
@ -132,22 +131,43 @@ class vmmConsole(gobject.GObject):
#print protocol + "://" + host + ":" + str(port)
if protocol != "vnc":
print "Activate inactive"
self.window.get_widget("console-pages").set_curent_page(0)
self.activate_unavailable_page()
return
if not(self.vncViewer.is_connected()):
self.vncViewer.connect_to_host(host, port)
if self.vncViewer.is_authenticated():
self.window.get_widget("console-pages").set_current_page(3)
self.activate_viewer_page()
elif password and (self.vncViewer.authenticate(password) == 1):
self.window.get_widget("console-pages").set_current_page(3)
if self.window.get_widget("console-auth-remember").get_active():
self.config.set_console_password(self.vm, password)
else:
self.config.clear_console_password(self.vm)
self.activate_viewer_page()
self.vncViewer.activate()
else:
self.window.get_widget("console-auth-password").set_text("")
self.window.get_widget("console-pages").set_current_page(2)
self.activate_auth_page()
def activate_unavailable_page(self):
self.window.get_widget("console-pages").set_current_page(0)
def activate_screenshot_page(self):
self.window.get_widget("console-pages").set_current_page(1)
def activate_auth_page(self):
pw = self.config.get_console_password(self.vm)
self.window.get_widget("console-auth-password").set_text(pw)
if pw != None and pw != "":
self.window.get_widget("console-auth-remember").set_active(True)
else:
self.window.get_widget("console-auth-remember").set_active(False)
self.window.get_widget("console-pages").set_current_page(2)
def activate_viewer_page(self):
self.window.get_widget("console-pages").set_current_page(3)
def control_vm_shutdown(self, src):
status = self.vm.status()
if not(status in [ libvirt.VIR_DOMAIN_SHUTDOWN, libvirt.VIR_DOMAIN_SHUTOFF, libvirt.VIR_DOMAIN_CRASHED ]):
@ -246,9 +266,9 @@ class vmmConsole(gobject.GObject):
cr.show_text(overlay)
self.window.get_widget("console-screenshot").set_from_pixmap(screenshot, None)
self.window.get_widget("console-pages").set_current_page(1)
self.activate_screenshot_page()
else:
self.window.get_widget("console-pages").set_current_page(0)
self.activate_unavailable_page()
else:
self.try_login()
except:

234
src/virtManager/keyring.py Normal file
View File

@ -0,0 +1,234 @@
#
# Copyright (C) 2006 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# There is no python binding for keyring in FC5 so we use
# ctypes to do the magic. This code is scary, but works
# pretty well...so far
#
# XXX audit this code for memory leaks. The gnome-keyring API
# docs are non-existant so i've no clue what bits are the callers
# responsibility to free() :-(
from ctypes import *
import gobject
from virtManager.secret import *
class vmmKeyring:
# Map to GnomeKeyringAttribute struct
# XXX lame that we have 32 & 64 bit variant
# need to get the Union stuff working for the
# 'value' field which should solve the padding
# problems automagically
class Attribute64(Structure):
_fields_ =[('name', c_char_p),
('type', c_int),
('pad', c_int),
('value', c_char_p)]
class Attribute32(Structure):
_fields_ =[('name', c_char_p),
('type', c_int),
('value', c_char_p)]
# Hack to map to GArray struct in glib
class GArray(Structure):
_fields_ = [('data', c_char_p),
('len', c_uint)]
def __init__(self):
self.loop = gobject.MainLoop()
# Load the two libs we need to play with
self.glib = cdll.LoadLibrary("libglib-2.0.so")
self.krlib = cdll.LoadLibrary("libgnome-keyring.so")
# Declare the callback type
cbtype = CFUNCTYPE(c_void_p, c_int, c_char_p, c_void_p)
self.cb = cbtype(self._get_default_keyring_complete)
# This gets filled out by callback with keyring name
self.keyring = None
# Get the keyring name
f = self.krlib.gnome_keyring_get_default_keyring(self.cb, None, None)
# Block until complete
# XXX lame - blocks whole UI
self.loop.run()
# User might have denied access
if self.keyring == None:
raise "Cannot access default keyring"
s = vmmSecret("Hello! World", "hus!h!hush", { "fdosdo": "bar1", "wi1zz": 3, "wow": 222 })
sid = self.add_secret(s)
self.secrets = {}
sout = self.get_secret(sid)
def add_secret(self, secret):
# We need to store the attributes in an array
g_array_new = self.glib.g_array_new
g_array_new.restype = c_void_p
# XXX remove this lame 32/64 bit fork
if sizeof(c_void_p) == 4:
attrs = g_array_new(c_int(0), c_int(0), sizeof(c_char_p) + sizeof(c_int) + sizeof(c_char_p))
else:
attrs = g_array_new(c_int(0), c_int(0), sizeof(c_char_p) + sizeof(c_int) + sizeof(c_int) + sizeof(c_char_p))
# Key a hold of them so they not immediately garbage collected
saveAttrs = {}
for key in secret.list_attributes():
# Add all attributes to array
a = None
# XXX remove this lame 32/64 bit fork
if sizeof(c_void_p) == 4:
a = vmmKeyring.Attribute32(name= c_char_p(key),
type= c_int(0),
value= c_char_p(str(secret.get_attribute(key))))
else:
a = vmmKeyring.Attribute64(name= c_char_p(key),
type= c_int(0),
pad= c_int(0),
value= c_char_p(str(secret.get_attribute(key))))
saveAttrs[key] = a
self.glib.g_array_append_vals(attrs, byref(a), 1)
# Declare callback type
cbaddtype = CFUNCTYPE(c_void_p, c_int, c_int, POINTER(c_int))
self.cbadd = cbaddtype(self._add_secret_complete)
# Fetch handle to our function
creator = self.krlib.gnome_keyring_item_create
creator.restype = c_void_p
# Callback will populate id of the secret in this
id = c_int(-1)
# Now add the secret
creator(None, c_int(0), c_char_p(secret.get_name()), attrs, c_char_p(secret.get_secret()), c_int(1), self.cbadd, pointer(id), None)
# Block until compelte
self.loop.run()
# Release attributes no longer neede
self.glib.g_array_free(attrs)
return id.value
def get_secret(self, id):
# Declare the callback type
cbgetinfotype = CFUNCTYPE(c_void_p, c_int, c_void_p, POINTER(c_int))
self.cbgetinfo = cbgetinfotype(self._get_item_info_complete)
# Fetch the method we want to call
getinfo = self.krlib.gnome_keyring_item_get_info
getinfo.restype = c_void_p
# We need this in callback
i = c_int(id)
# Fetch the basic info
p = getinfo(c_char_p(self.keyring), c_int(id), self.cbgetinfo, pointer(i), None)
# Block until done
self.loop.run()
if self.secrets.has_key(id):
# Declare callback type
cbgetattrstype = CFUNCTYPE(c_void_p, c_int, POINTER(vmmKeyring.GArray), POINTER(c_int))
self.cbgetattrs = cbgetattrstype(self._get_item_attrs_complete)
# Declare function we wnt to call to get attributes
getattrs = self.krlib.gnome_keyring_item_get_attributes
getattrs.restype = c_void_p
# Fetch the attrs
getattrs(c_char_p(self.keyring), c_int(id), self.cbgetattrs, pointer(i), None)
# Block until done
self.loop.run()
secret = self.secrets[id]
del self.secrets[id]
return secret
else:
return None
def clear_secret(self, id):
# Declare the callback type
cbdeletetype = CFUNCTYPE(c_void_p, c_int, c_void_p)
self.cbdelete = cbdeletetype(self._delete_item_complete)
# Fetch the method we want to call
getinfo = self.krlib.gnome_keyring_item_delete
getinfo.restype = c_void_p
# Fetch the basic info
p = getinfo(c_char_p(self.keyring), c_int(id), self.cbdelete, None, None)
# Block until done
self.loop.run()
def _get_default_keyring_complete(self, status, name, data):
if status != 0:
self.keyring = None
self.loop.quit()
return
# Save name of default keyring somewhere safe
if name == None:
name = ""
self.keyring = name
self.loop.quit()
def _add_secret_complete(self, status, id, data):
if status != 0:
data.contents.value = -1
self.loop.quit()
return
data.contents.value = id
self.loop.quit()
def _delete_item_complete(self, status, data):
self.loop.quit()
def _get_item_info_complete(self, status, info, data=None):
if status != 0:
self.loop.quit()
return
getname = self.krlib.gnome_keyring_item_info_get_display_name
getname.restype = c_char_p
getsecret = self.krlib.gnome_keyring_item_info_get_secret
getsecret.restype = c_char_p
name = getname(info)
secret = getsecret(info)
self.secrets[data.contents.value] = vmmSecret(name, secret)
self.loop.quit()
def _get_item_attrs_complete(self, status, attrs, data=None):
if status != 0:
self.loop.quit()
return
# XXX @#%$&(#%@ glib has a macro for accessing
# elements in array which can obviously can't use
# from python. Figure out nasty pointer magic here...
self.loop.quit()

48
src/virtManager/secret.py Normal file
View File

@ -0,0 +1,48 @@
#
# Copyright (C) 2006 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
class vmmSecret:
def __init__(self, name, secret=None, attributes={}):
self.name = name
self.secret = secret
self.attributes = attributes
def set_secret(self, data):
self.secret = data
def get_secret(self):
return self.secret
def get_name(self):
return self.name
def add_attribute(self, key, value):
if type(value) != str:
value = str(value)
self.attributes[key] = value
def list_attributes(self):
return self.attributes.keys()
def get_attribute(self, key):
return self.attributes[key]

View File

@ -13,11 +13,20 @@ URL: http://people.redhat.com/berrange/virt-manager/
Source0: %{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
# Any modern python should do
Requires: python
# These two are just the oldest version tested
Requires: pygtk2 >= 1.99.12-6
Requires: gnome-python2-gconf >= 1.99.11-7
# Absolutely require this version or newer
Requires: libvirt-python >= 0.1.1
# Definitely does not work with earlier due to python API changes
Requires: dbus-python >= 0.61
# Might work with earlier, but this is what we've tested
# We use 'ctypes' so don't need the 'gnome-keyring-python' bits
Requires: gnome-keyring >= 0.4.9
# Minimum we've tested with
Requires: python-ctypes >= 0.9.9.6
# src/vncViewer/image.py needs either this
Requires: python-imaging