Added initial support for saving VNC password in the GNOME keyring
This commit is contained in:
parent
5e10581061
commit
05d9d83af6
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
|
@ -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]
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue