[PATCH] Add a gui password spoke

Jesse Keating jkeating at redhat.com
Thu Aug 23 05:05:17 UTC 2012


This spoke sets the root password, or if left blank disables the root
login.  Some level of validation is done, however the validation happens
at the apply() stage.  This sets the spoke to not complete if there is
some error in validation.  Next time the user clicks the spoke they'll
see what kind of validate error they got.  I couldn't figure out a way
to prevent leaving the spoke if the password didn't validate while using
the update_info() function.

capslock monitoring is not enabled currently, but there is some stub
code commented out that deals with it.
---
 pyanaconda/ui/gui/spokes/password.glade | 164 ++++++++++++++++++++++++++++++++
 pyanaconda/ui/gui/spokes/password.py    | 155 ++++++++++++++++++++++++++++++
 2 files changed, 319 insertions(+)
 create mode 100644 pyanaconda/ui/gui/spokes/password.glade
 create mode 100644 pyanaconda/ui/gui/spokes/password.py

diff --git a/pyanaconda/ui/gui/spokes/password.glade b/pyanaconda/ui/gui/spokes/password.glade
new file mode 100644
index 0000000..70661de
--- /dev/null
+++ b/pyanaconda/ui/gui/spokes/password.glade
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <!-- interface-requires AnacondaWidgets 1.0 -->
+  <object class="AnacondaSpokeWindow" id="passwordWindow">
+    <property name="startup_id">filler</property>
+    <property name="can_focus">False</property>
+    <property name="startup_id">filler</property>
+    <property name="window_name">ROOT PASSWORD</property>
+    <signal name="back-clicked" handler="on_back_clicked" swapped="no"/>
+    <child internal-child="main_box">
+      <object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child internal-child="nav_area">
+          <object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
+            <property name="can_focus">False</property>
+            <property name="margin_left">6</property>
+            <property name="margin_right">6</property>
+            <property name="margin_top">6</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child internal-child="alignment">
+          <object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
+            <property name="can_focus">False</property>
+            <property name="yalign">0</property>
+            <child internal-child="action_area">
+              <object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkBox" id="box1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">The root account is used for administering the system.  Enter a password for the root user.</property>
+                        <property name="justify">right</property>
+                        <property name="wrap">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="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkGrid" id="pwgrid">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="row_spacing">6</property>
+                    <property name="column_spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="pwlabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Root Password:</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="confirmlabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Confirm:</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="pw">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="visibility">False</property>
+                        <property name="invisible_char">●</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="confirm">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="visibility">False</property>
+                        <property name="invisible_char">●</property>
+                        <property name="activates_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="capslock">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
new file mode 100644
index 0000000..5f61ec1
--- /dev/null
+++ b/pyanaconda/ui/gui/spokes/password.py
@@ -0,0 +1,155 @@
+# root password spoke class
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Jesse Keating <jkeating at redhat.com>
+#
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+N_ = lambda x: x
+
+from gi.repository import Gtk
+
+from pyanaconda.users import cryptPassword
+import pwquality
+import string
+
+from pyanaconda.ui.gui.spokes import NormalSpoke
+# Need a new category I guess
+from pyanaconda.ui.gui.categories.localization import LocalizationCategory
+#from _isys import isCapsLockEnabled
+
+__all__ = ["PasswordSpoke"]
+
+
+class PasswordSpoke(NormalSpoke):
+    builderObjects = ["passwordWindow"]
+
+    mainWidgetName = "passwordWindow"
+    uiFile = "spokes/password.glade"
+
+    # update this category to something... new?
+    category = LocalizationCategory
+
+    icon = "dialog-password-symbolic"
+    title = N_("ROOT PASSWORD")
+
+    def __init__(self, *args):
+        NormalSpoke.__init__(self, *args)
+        self._password = None
+        self._error = False
+
+    def initialize(self):
+        NormalSpoke.initialize(self)
+        # Set the rootpw to locked by default, setting a password is optional
+        self.data.rootpw.lock = True
+        # place holders for the text boxes
+        self.pw = self.builder.get_object("pw")
+        self.confirm = self.builder.get_object("confirm")
+
+#    def refresh(self):
+#        self.setCapsLockLabel()
+
+# Caps lock detection isn't hooked up right now
+#    def setCapsLockLabel(self):
+#        if isCapsLockEnabled():
+#            self.capslock.set_text("<b>" + _("Caps Lock is on.") + "</b>")
+#            self.capslock.set_use_markup(True)
+#        else:
+#            self.capslock..set_text("")
+
+    @property
+    def status(self):
+        if self._error:
+            return _("Error setting root password")
+        elif self.data.rootpw.lock:
+            return _("Root account is disabled")
+        else:
+            return _("Root password is set")
+
+    def apply(self):
+        if self._validatePassword():
+            if self._password:
+                self.data.rootpw.password = cryptPassword(self._password)
+                self.data.rootpw.isCrypted = True
+                self.data.rootpw.lock = False
+            else:
+                # Blank password case, disable the account
+                self.data.rootpw.lock = True
+                self.data.rootpw.password = ''
+                self.data.rootpw.isCrypted = False
+        else:
+            self.window.clear_info()
+            self.window.set_info(Gtk.MessageType.WARNING, self._error)
+
+    @property
+    def completed(self):
+        # FUTURE -- update completed to false if package payload doesn't
+        # include firstboot and some environment to run it in
+        # We are by default complete, but locked.  If a user attempts to set
+        # a password but fails, then we are no longer complete.
+        return not self._error
+
+    def _validatePassword(self):
+        # Do various steps to validate the password
+        # sets self._error to an error string
+        # Return True if valid, False otherwise
+        pw = self.pw.get_text()
+        confirm = self.confirm.get_text()
+
+        # if both pw and confirm are blank, password is disabled.
+        if (pw and not confirm) or (confirm and not pw):
+            self._error = _("You must enter your root password "
+                           "and confirm it by typing it a second "
+                           "time to continue.")
+            return False
+
+        if pw != confirm:
+            self._error = _("The passwords you entered were "
+                            "different.  Please try again.")
+            return False
+
+        if pw and len(pw) < 6:
+            self._error = _("The root password must be at least "
+                            "six characters long.")
+            return False
+
+        if pw:
+            try:
+                settings = pwquality.PWQSettings()
+                settings.read_config()
+                settings.check(pw, None, "root")
+            except pwquality.PWQError as (e, msg):
+                self._error = _("You have provided a weak password: %s" % msg)
+                # Figure out if this is the first time this message has displayed
+                # if not the first, let it go through.
+                return False
+
+        legal = string.digits + string.ascii_letters + string.punctuation + " "
+        for letter in pw:
+            if letter not in legal:
+                self._error = _("Requested password contains "
+                                "non-ASCII characters, which are "
+                                "not allowed.")
+                return False
+
+        # if no errors, clear the info for next time we go into the spoke
+        self._password = pw
+        self.window.clear_info()
+        self._error = False
+        return True
-- 
1.7.11.2



More information about the anaconda-patches mailing list