[PATCH f19-branch] Network spoke: add secret agent (static wep and wpa)

Radek Vykydal rvykydal at redhat.com
Tue Jun 4 09:47:33 UTC 2013


Static WEP and WPA works, WPA Enterprise will come later.

I'd like to have it in F19.

http://rvykydal.fedorapeople.org/secret_agent_wpa.png

---
 pyanaconda/ui/gui/spokes/network.glade | 135 +++++++++++++++++++++
 pyanaconda/ui/gui/spokes/network.py    | 208 ++++++++++++++++++++++++++++++---
 2 files changed, 326 insertions(+), 17 deletions(-)

diff --git a/pyanaconda/ui/gui/spokes/network.glade b/pyanaconda/ui/gui/spokes/network.glade
index fec6a6a..9d1b742 100644
--- a/pyanaconda/ui/gui/spokes/network.glade
+++ b/pyanaconda/ui/gui/spokes/network.glade
@@ -2493,4 +2493,139 @@ updates available for you.</property>
       </object>
     </child>
   </object>
+  <object class="GtkDialog" id="secret_agent_dialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Authentication</property>
+    <property name="type_hint">dialog</property>
+    <property name="decorated">False</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog_box">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">5</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area2">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <property name="image_position">right</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="connect_button">
+                <property name="label">gtk-connect</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">6</property>
+            <property name="margin_right">6</property>
+            <property name="margin_top">6</property>
+            <property name="margin_bottom">6</property>
+            <child>
+              <object class="GtkImage" id="image_password_dialog">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">start</property>
+                <property name="hexpand">True</property>
+                <property name="xpad">15</property>
+                <property name="stock">gtk-missing-image</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="password_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="label_title">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="margin_bottom">6</property>
+                    <property name="hexpand">True</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Authentication required by wireless network&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">2</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_message">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <property name="label" translatable="yes">Passwords or encryption keys are required to access
+the wireless network</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="padding">3</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">button1</action-widget>
+      <action-widget response="1">connect_button</action-widget>
+    </action-widgets>
+  </object>
 </interface>
diff --git a/pyanaconda/ui/gui/spokes/network.py b/pyanaconda/ui/gui/spokes/network.py
index 432fc09..8e78823 100644
--- a/pyanaconda/ui/gui/spokes/network.py
+++ b/pyanaconda/ui/gui/spokes/network.py
@@ -24,8 +24,6 @@
 # - move callback connection to initialize?
 # - Automatically reconnecting wifi after failure
 #   https://bugzilla.redhat.com/show_bug.cgi?id=712778#c1
-# - secrets agent - use nm_applet?
-#   see we_dont_have_nm_applet_as_secrets_agent
 # - callback on NM_CLIENT_ACTIVE_CONNECTIONS
 # - support connection to hidden network (ap-other)
 # - NMClient.CLIENT_WIRELESS_ENABLED callback (hw switch?) - test
@@ -41,17 +39,19 @@ from pyanaconda.ui.gui import GUIObject
 from pyanaconda.ui.gui.spokes import NormalSpoke, StandaloneSpoke
 from pyanaconda.ui.gui.categories.software import SoftwareCategory
 from pyanaconda.ui.gui.hubs.summary import SummaryHub
-from pyanaconda.ui.gui.utils import gtk_call_once
+from pyanaconda.ui.gui.utils import gtk_call_once, enlightbox
 
 from pyanaconda import network
 from pyanaconda.nm import nm_activated_devices, nm_device_setting_value
 
 from gi.repository import GLib, GObject, Pango, Gio, NetworkManager, NMClient
 import dbus
+import dbus.service
 import socket
 import subprocess
 import struct
 import time
+import string
 from dbus.mainloop.glib import DBusGMainLoop
 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
@@ -69,6 +69,11 @@ NM_802_11_AP_FLAGS_PRIVACY = 0x1
 NM_802_11_AP_SEC_NONE = 0x0
 NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x200
 DBUS_PROPS_IFACE = "org.freedesktop.DBus.Properties"
+SECRET_AGENT_IFACE = 'org.freedesktop.NetworkManager.SecretAgent'
+AGENT_MANAGER_IFACE = 'org.freedesktop.NetworkManager.AgentManager'
+AGENT_MANAGER_PATH = "/org/freedesktop/NetworkManager/AgentManager"
+
+
 
 def getNMObjProperty(object, nm_iface_suffix, property):
     props_iface = dbus.Interface(object, DBUS_PROPS_IFACE)
@@ -118,10 +123,6 @@ configuration_of_disconnected_devices_allowed = True
 # for installer
 # https://bugzilla.redhat.com/show_bug.cgi?id=704119
 
-we_dont_have_nm_applet_as_secrets_agent = True
-# so we have to disconnect from former ap before trying
-# to connect to new one bound to fail due to no secrets
-
 __all__ = ["NetworkSpoke", "NetworkStandaloneSpoke"]
 
 class CellRendererSignal(Gtk.CellRendererPixbuf):
@@ -404,12 +405,6 @@ class NetworkControlBox(object):
 
         log.info("network: access point changed: %s" % ssid_target)
 
-        if we_dont_have_nm_applet_as_secrets_agent:
-            if self.find_active_connection_for_device(device):
-                # TODO we should pass callback and block until really disconnected?
-                # or is wireless reconnection stuff solved in NM? TEST!
-                device.disconnect(None, None)
-
         con = self.find_connection_for_device(device, ssid_target)
         if con:
             self.client.activate_connection(con, device,
@@ -962,13 +957,10 @@ class NetworkControlBox(object):
         for ap in access_points:
             ssid = ap.get_ssid()
             if ssid in strongest_aps:
-                #print "DBG: found %s duplicate" % ssid
                 if ap.get_strength() > strongest_aps[ssid].get_strength():
                     strongest_aps[ssid] = ap
-                    #print "DBG: ...stronger"
             else:
                 strongest_aps[ssid] = ap
-                #print "DBG: adding %s ap" % ssid
 
         return strongest_aps.values()
 
@@ -1077,6 +1069,186 @@ class NetworkControlBox(object):
     def hostname(self, value):
         self.entry_hostname.set_text(value)
 
+
+class SecretAgentDialog(GUIObject):
+    builderObjects = ["secret_agent_dialog"]
+    mainWidgetName = "secret_agent_dialog"
+    uiFile = "spokes/network.glade"
+
+    def __init__(self, *args, **kwargs):
+        self._content = kwargs.pop('content', {})
+        GUIObject.__init__(self, *args, **kwargs)
+        img = self.builder.get_object("image_password_dialog")
+        img.set_from_icon_name("dialog-password-symbolic", Gtk.IconSize.DIALOG)
+        self.builder.get_object("label_message").set_text(self._content['message'])
+        self.builder.get_object("label_title").set_use_markup(True)
+        self.builder.get_object("label_title").set_markup("<b>%s</b>" % self._content['title'])
+        self._connect_button = self.builder.get_object("connect_button")
+
+    def initialize(self):
+        self._entries = {}
+        grid = Gtk.Grid()
+        grid.set_row_spacing(6)
+        grid.set_column_spacing(6)
+
+        for row, secret in enumerate(self._content['secrets']):
+            label = Gtk.Label(secret['label'])
+            label.set_halign(Gtk.Align.START)
+            entry = Gtk.Entry()
+            entry.set_visibility(False)
+            entry.set_hexpand(True)
+            self._validate(entry, secret)
+            entry.connect("changed", self._validate, secret)
+            entry.connect("activate", self._password_entered_cb)
+            self._entries[secret['key']] = entry
+            label.set_use_underline(True)
+            label.set_mnemonic_widget(entry)
+            grid.attach(label, 0, row, 1, 1)
+            grid.attach(entry, 1, row, 1, 1)
+
+        self.builder.get_object("password_box").add(grid)
+
+    def run(self):
+        self.initialize()
+        self.window.show_all()
+        rc = self.window.run()
+        for secret in self._content['secrets']:
+            secret['value'] = self._entries[secret['key']].get_text()
+        self.window.destroy()
+        return rc
+
+    @property
+    def valid(self):
+        return all(secret['valid'] for secret in self._content['secrets'])
+
+    def _validate(self, entry, secret):
+        secret['value'] = entry.get_text()
+        if secret['validate']:
+            secret['valid'] = secret['validate'](secret)
+        else:
+            secret['valid'] = len(secret['value']) > 0
+        self._update_connect_button()
+
+    def _password_entered_cb(self, entry):
+        if self._connect_button.get_sensitive() and self.valid:
+            self.window.response(1)
+
+    def _update_connect_button(self):
+        self._connect_button.set_sensitive(self.valid)
+
+secret_agent = None
+
+class NotAuthorizedException(dbus.DBusException):
+    _dbus_error_name = SECRET_AGENT_IFACE + '.NotAuthorized'
+
+class SecretAgent(dbus.service.Object):
+    def __init__(self, spoke):
+        self._bus = dbus.SystemBus()
+        self.spoke = spoke
+        dbus.service.Object.__init__(self, self._bus, "/org/freedesktop/NetworkManager/SecretAgent")
+
+    @dbus.service.method(SECRET_AGENT_IFACE,
+                         in_signature='a{sa{sv}}osasb',
+                         out_signature='a{sa{sv}}',
+                         sender_keyword='sender')
+    def GetSecrets(self, connection_hash, connection_path, setting_name, hints, request_new, sender=None):
+        if not sender:
+            raise NotAuthorizedException("Internal error: couldn't get sender")
+        uid = self._bus.get_unix_user(sender)
+        if uid != 0:
+            raise NotAuthorizedException("UID %d not authorized" % uid)
+
+        log.debug("Secrets requested path '%s' setting '%s' hints '%s' new %d" % (connection_path, setting_name, str(hints), request_new))
+
+        content = self._get_content(setting_name, connection_hash)
+        dialog = SecretAgentDialog(self.spoke.data, content=content)
+        with enlightbox(self.spoke.window, dialog.window):
+            rc = dialog.run()
+
+        secrets = dbus.Dictionary()
+        if rc == 1:
+            for secret in content['secrets']:
+                secrets[secret['key']] = secret['value']
+
+        settings = dbus.Dictionary({setting_name: secrets})
+
+        return settings
+
+    def _get_content(self, setting_name, connection_hash):
+        content = {}
+        connection_type = connection_hash['connection']['type']
+        if connection_type == "802-11-wireless":
+            content['title'] = _("Authentication required by wireless network")
+            content['message'] = _("Passwords or encryption keys are required to access\nthe wireless network '%(network_id)s'.") \
+                                 % {'network_id':str(connection_hash['connection']['id'])}
+            content['secrets'] = self._get_wireless_secrets(connection_hash[setting_name])
+        else:
+            log.info("Connection type %s not supported by secret agent" % connection_type)
+
+        return content
+
+    def _get_wireless_secrets(self, original_secrets):
+        secrets = []
+        key_mgmt = original_secrets['key-mgmt']
+        if key_mgmt in ['wpa-none', 'wpa-psk']:
+            secrets.append({'label'     : _('_Password:'),
+                            'key'      : 'psk',
+                            'value'    : original_secrets.get('psk', ''),
+                            'validate' : self._validate_wpapsk,
+                            'password' : True})
+        # static WEP
+        elif key_mgmt == 'none':
+            key_idx = str(original_secrets.get('wep_tx_keyidx', '0'))
+            secrets.append({'label'     : _('_Key:'),
+                            'key'      : 'wep-key%s' % key_idx,
+                            'value'    : original_secrets.get('wep-key%s' % key_idx, ''),
+                            'wep_key_type': original_secrets.get('wep-key-type', ''),
+                            'validate' : self._validate_staticwep,
+                            'password' : True})
+        else:
+            log.info("Unsupported wireless key management: %s" % key_mgmt)
+
+        return secrets
+
+    def _validate_wpapsk(self, secret):
+        value = secret['value']
+        if len(value) == 64:
+            # must be composed of hexadecimal digits only
+            return all(c in string.hexdigits for c in value)
+        else:
+            return 8 <= len(value) <= 63
+
+    def _validate_staticwep(self, secret):
+        value = secret['value']
+        if secret['wep_key_type'] == NetworkManager.WepKeyType.KEY:
+            if len(value) in (10, 26):
+                return all(c in string.hexdigits for c in value)
+            elif len(value) in (5, 13):
+                return all(c in string.letters for c in value)
+            else:
+                return False
+        elif secret['wep_key_type'] == NetworkManager.WepKeyType.PASSPHRASE:
+            return 0 <= len(value) <= 64
+        else:
+            return True
+
+def register_secret_agent(spoke):
+
+    if not flags.can_touch_runtime_system("register anaconda secret agent"):
+        return False
+
+    global secret_agent
+    if not secret_agent:
+        secret_agent = SecretAgent(spoke)
+        bus = dbus.SystemBus()
+        proxy = bus.get_object(NM_SERVICE, AGENT_MANAGER_PATH)
+        proxy.Register("anaconda", dbus_interface=AGENT_MANAGER_IFACE)
+    else:
+        secret_agent.spoke = spoke
+
+    return True
+
+
 class NetworkSpoke(NormalSpoke):
     builderObjects = ["networkWindow", "liststore_wireless_network", "liststore_devices", "add_device_dialog", "liststore_add_device"]
     mainWidgetName = "networkWindow"
@@ -1109,7 +1281,7 @@ class NetworkSpoke(NormalSpoke):
     @property
     def mandatory(self):
         return self.data.method.method in ("url", "nfs")
-    
+
     @property
     def status(self):
         """ A short string describing which devices are connected. """
@@ -1173,6 +1345,7 @@ class NetworkSpoke(NormalSpoke):
         return flags.can_touch_runtime_system("allow network configuration")
 
     def initialize(self):
+        register_secret_agent(self)
         NormalSpoke.initialize(self)
         self.network_control_box.initialize()
         if not self.data.network.seen:
@@ -1261,6 +1434,7 @@ class NetworkStandaloneSpoke(StandaloneSpoke):
                 or len(self.network_control_box.activated_connections()) > 0)
 
     def initialize(self):
+        register_secret_agent(self)
         StandaloneSpoke.initialize(self)
         self.network_control_box.initialize()
 
-- 
1.7.11.7



More information about the anaconda-patches mailing list