[PATCH] Add a gui password spoke

Jesse Keating jkeating at redhat.com
Thu Aug 23 20:12:08 UTC 2012


This spoke sets the root password, or if left blank disables the root
login.  Some level of validation is done and the user is trapped until
they get it right or press back twice on a weak password.

capslock monitoring is not enabled currently, but there is some stub
code commented out that deals with it.
---
 po/POTFILES.in                          |   2 +
 pyanaconda/ui/gui/spokes/password.glade | 151 +++++++++++++++++++++++++++++
 pyanaconda/ui/gui/spokes/password.py    | 167 ++++++++++++++++++++++++++++++++
 3 files changed, 320 insertions(+)
 create mode 100644 pyanaconda/ui/gui/spokes/password.glade
 create mode 100644 pyanaconda/ui/gui/spokes/password.py

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 977d425..5fee5ce 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -84,6 +84,7 @@ pyanaconda/ui/gui/spokes/custom.py
 pyanaconda/ui/gui/spokes/datetime_spoke.py
 pyanaconda/ui/gui/spokes/keyboard.py
 pyanaconda/ui/gui/spokes/network.py
+pyanaconda/ui/gui/spokes/password.py
 pyanaconda/ui/gui/spokes/software.py
 pyanaconda/ui/gui/spokes/source.py
 pyanaconda/ui/gui/spokes/storage.py
@@ -97,6 +98,7 @@ pyanaconda/ui/gui/spokes/network.glade
 pyanaconda/ui/gui/spokes/software.glade
 pyanaconda/ui/gui/spokes/storage.glade
 pyanaconda/ui/gui/spokes/keyboard.glade
+pyanaconda/ui/gui/spokes/password.glade
 pyanaconda/ui/gui/spokes/source.glade
 pyanaconda/ui/gui/spokes/welcome.glade
 pyanaconda/ui/gui/spokes/custom.glade
diff --git a/pyanaconda/ui/gui/spokes/password.glade b/pyanaconda/ui/gui/spokes/password.glade
new file mode 100644
index 0000000..38c21a8
--- /dev/null
+++ b/pyanaconda/ui/gui/spokes/password.glade
@@ -0,0 +1,151 @@
+<?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>
+            <property name="xscale">0</property>
+            <property name="yscale">0.5</property>
+            <child internal-child="action_area">
+              <object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkGrid" id="pwgrid">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</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">1</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">2</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">1</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">2</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">3</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="details">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">The root account is used for administering the system.  Enter a password for the root user.  Leave blank to disable (lock) the account.</property>
+                        <property name="wrap">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">2</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">0</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..0e8d5e8
--- /dev/null
+++ b/pyanaconda/ui/gui/spokes/password.py
@@ -0,0 +1,167 @@
+# 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
+        self._oldweak = None
+
+    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()
+        self.pw.grab_focus()
+
+# 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._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
+
+    @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):
+                if pw == self._oldweak:
+                    # We got a second attempt with the same weak password
+                    pass
+                else:
+                    self._error = _("You have provided a weak password: %s. "
+                                    " Press Back again to use anyway." % msg)
+                    self._oldweak = pw
+                    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
+
+    def on_back_clicked(self, button):
+        if self._validatePassword():
+            self.window.clear_info()
+            NormalSpoke.on_back_clicked(self, button)
+        else:
+            self.window.clear_info()
+            self.window.set_info(Gtk.MessageType.WARNING, self._error)
+            self.pw.grab_focus()
+            self.window.show_all()
\ No newline at end of file
-- 
1.7.11.2



More information about the anaconda-patches mailing list