summaryrefslogtreecommitdiffstats
path: root/pyanaconda/ui/gui/spokes/user.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyanaconda/ui/gui/spokes/user.py')
-rw-r--r--pyanaconda/ui/gui/spokes/user.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
new file mode 100644
index 000000000..011d71d4f
--- /dev/null
+++ b/pyanaconda/ui/gui/spokes/user.py
@@ -0,0 +1,420 @@
+# User creation spoke
+#
+# Copyright (C) 2013 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): Martin Sivak <msivak@redhat.com>
+#
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+N_ = lambda x: x
+
+from gi.repository import Gtk
+
+from pyanaconda.users import cryptPassword, validatePassword
+from pwquality import PWQError
+
+from pyanaconda.ui.gui.spokes import NormalSpoke
+from pyanaconda.ui.gui import GUIObject
+from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory
+from pyanaconda.ui.common import FirstbootSpokeMixIn
+from pyanaconda.ui.gui.utils import enlightbox
+
+import unicodedata
+import pwquality
+
+__all__ = ["UserSpoke", "AdvancedUserDialog"]
+
+def strip_accents(s):
+ """This function takes arbitrary unicode string
+ and returns it with all the diacritics removed.
+
+ :param s: arbitrary string
+ :type s: unicode
+
+ :return: s with diacritics removed
+ :rtype: unicode
+
+ """
+ return ''.join((c for c in unicodedata.normalize('NFD', s)
+ if unicodedata.category(c) != 'Mn'))
+
+class AdvancedUserDialog(GUIObject):
+ builderObjects = ["advancedUserDialog", "uid", "gid"]
+ mainWidgetName = "advancedUserDialog"
+ uiFile = "advanced_user.glade"
+
+ def __init__(self, user, groupDict, data):
+ GUIObject.__init__(self, data)
+ self._user = user
+ self._groupDict = groupDict
+
+ def initialize(self):
+ GUIObject.initialize(self)
+
+ def _apply_checkboxes(self, _editable, data = None):
+ """Update the state of this screen according to the
+ checkbox states on the screen. It is called from
+ the toggled Gtk event.
+ """
+ c_home = self.builder.get_object("c_home").get_active()
+ c_uid = self.builder.get_object("c_uid").get_active()
+ c_gid = self.builder.get_object("c_gid").get_active()
+
+ self.builder.get_object("t_home").set_sensitive(c_home)
+ self.builder.get_object("l_home").set_sensitive(c_home)
+ self.builder.get_object("spin_uid").set_sensitive(c_uid)
+ self.builder.get_object("spin_gid").set_sensitive(c_gid)
+
+ def refresh(self):
+ t_home = self.builder.get_object("t_home")
+ if self._user.homedir:
+ t_home.set_text(self._user.homedir)
+ elif self._user.name:
+ t_home.set_text("/home/%s" % self._user.name)
+
+ groups = []
+ for group_name in self._user.groups:
+ group = self._groupDict[group_name]
+
+ if group.name and group.gid is not None:
+ groups.append("%s (%d)" % (group.name, group.gid))
+ elif group.name:
+ groups.append(group.name)
+ elif group.gid is not None:
+ groups.append("(%d)" % (group.gid,))
+
+ self.builder.get_object("t_groups").set_text(", ".join(groups))
+
+ def run(self):
+ self.window.show()
+ rc = self.window.run()
+ self.window.hide()
+
+ #OK clicked
+ if rc == 1:
+ if self.builder.get_object("c_home").get_active():
+ self._user.homedir = self.builder.get_object("t_home").get_text()
+ else:
+ self._user.homedir = None
+
+ if self.builder.get_object("c_uid").get_active():
+ self._user.uid = int(self.builder.get_object("uid").get_value())
+ else:
+ self._user.uid = None
+
+ if self.builder.get_object("c_gid").get_active():
+ pass
+ #self._user.gid = int(self.builder.get_widget("gid").get_value())
+ else:
+ #self._user.gid = None
+ pass
+
+ groups = self.builder.get_object("t_groups").get_text().split(",")
+ self._user.groups = []
+ for group in groups:
+ group = group.strip()
+ if group not in self._groupDict:
+ self._groupDict[group] = self.data.GroupData(name = group)
+ self._user.groups.append(group)
+
+ #Cancel clicked, window destroyed...
+ else:
+ pass
+
+ return rc
+
+
+
+class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ builderObjects = ["userCreationWindow"]
+
+ mainWidgetName = "userCreationWindow"
+ uiFile = "spokes/user.glade"
+
+ category = UserSettingsCategory
+
+ icon = "avatar-default-symbolic"
+ title = N_("_USER CREATION")
+
+ def __init__(self, *args):
+ NormalSpoke.__init__(self, *args)
+ self._oldweak = None
+ self._error = False
+
+ def initialize(self):
+ NormalSpoke.initialize(self)
+
+ if self.data.user.userList:
+ self._user = self.data.user.userList[0]
+ else:
+ self._user = self.data.UserData()
+ self._wheel = self.data.GroupData(name = "wheel")
+ self._groupDict = {"wheel": self._wheel}
+
+ # placeholders for the text boxes
+ self.fullname = self.builder.get_object("t_fullname")
+ self.username = self.builder.get_object("t_username")
+ self.pw = self.builder.get_object("t_password")
+ self.confirm = self.builder.get_object("t_verifypassword")
+ self.admin = self.builder.get_object("c_admin")
+ self.usepassword = self.builder.get_object("c_usepassword")
+
+ self.guesser = {
+ self.username: True
+ }
+
+ # set up passphrase quality checker
+ self._pwq = pwquality.PWQSettings()
+ self._pwq.read_config()
+
+ self.pw_bar = self.builder.get_object("password_bar")
+ self.pw_label = self.builder.get_object("password_label")
+
+ self._advanced = AdvancedUserDialog(self._user, self._groupDict,
+ self.data)
+ self._advanced.initialize()
+
+ def refresh(self):
+ self.username.set_text(self._user.name)
+ self.fullname.set_text(self._user.gecos)
+ self.admin.set_active(self._wheel.name in self._user.groups)
+
+ if self.usepassword.get_active():
+ self._checkPassword()
+
+ if self.username.get_text() and self.usepassword.get_active():
+ self.pw.grab_focus()
+ elif self.fullname.get_text():
+ self.username.grab_focus()
+ else:
+ self.fullname.grab_focus()
+
+ @property
+ def status(self):
+ if self._error:
+ return _("Error creating user account: %s") % self._error
+ elif len(self.data.user.userList) == 0:
+ return _("No user will be created")
+ elif self._wheel.name in self.data.user.userList[0].groups:
+ return _("Administrator %s will be created") % self.data.user.userList[0].name
+ else:
+ return _("User %s will be created") % self.data.user.userList[0].name
+
+ @property
+ def mandatory(self):
+ # mandatory only if root account is disabled
+ return (not self.data.rootpw.password) or self.data.rootpw.lock
+
+ def apply(self):
+ if self.username.get_text():
+ self._user.name = self.username.get_text()
+ self._user.gecos = self.fullname.get_text()
+ self._user.password = cryptPassword(self.pw.get_text())
+ self._user.isCrypted = True
+
+ if self.admin.get_active() and \
+ self._wheel.name not in self._user.groups:
+ self._user.groups.append(self._wheel.name)
+ elif not self.admin.get_active() and \
+ self._wheel.name in self._user.groups:
+ self._user.groups.remove(self._wheel.name)
+
+ self.data.group.groupList += [self._groupDict[g] for g in self._user.groups
+ if g != self._wheel.name]
+
+ if self._user not in self.data.user.userList:
+ self.data.user.userList.append(self._user)
+
+ elif self._user in self.data.user.userList:
+ self.data.user.userList.remove(self._user)
+
+ @property
+ def completed(self):
+ return self._user in self.data.user.userList
+
+ def _passwordDisabler(self, editable = None, data = None):
+ """Called by Gtk callback when the "Use password" check
+ button is toggled. It will make password entries in/sensitive."""
+
+ self.pw.set_sensitive(self.usepassword.get_active())
+ self.confirm.set_sensitive(self.usepassword.get_active())
+ if not self.usepassword.get_active():
+ self.clear_info()
+ else:
+ self._checkPassword()
+
+ def _guessNameDisabler(self, editable = None, data = None):
+ """Called by Gtk callback when the username or hostname
+ entry changes. It disables the guess algorithm if the
+ user added his own text there and reenable it when the
+ user deletes the whole text."""
+
+ if editable.get_text() == "":
+ self.guesser[editable] = True
+ self._guessNames()
+ else:
+ self.guesser[editable] = False
+
+ def _guessNames(self, editable = None, data = None):
+ """Called by Gtk callback when the full name field changes.
+ It guesses the username and hostname, strips diacritics
+ and make those lowercase.
+ """
+
+ fullname = self.fullname.get_text().split()
+ username = fullname[-1].decode("utf-8").lower()
+ if len(fullname) > 1:
+ username = fullname[0][0].decode("utf-8").lower() + username
+ username = strip_accents(username).encode("utf-8")
+
+ # after the text is updated in guesser, the guess has to be reenabled
+ if self.guesser[self.username]:
+ self.username.set_text(username)
+ self.guesser[self.username] = True
+
+ def _checkPassword(self, editable = None, data = None):
+ """This method updates the password indicators according
+ to the passwords entered by the user. It is called by
+ the changed Gtk event handler.
+ """
+ if self.pw.get_text() == "":
+ strength = -2
+ elif self.pw.get_text() != self.confirm.get_text():
+ strength = -1
+ else:
+ try:
+ strength = self._pwq.check(self.pw.get_text(), None, None)
+ _pwq_error = None
+ except pwquality.PWQError as (e, msg):
+ _pwq_error = msg
+ strength = 0
+
+ if strength == -1:
+ val = 0
+ text = _("Mismatch!")
+ self._error = _("The passwords do not match!")
+ elif strength == -2:
+ val = 0
+ text = _("Empty!")
+ self._error = _("The password is empty!")
+ elif strength < 50:
+ val = 1
+ text = _("Weak")
+ self._error = _("The password you have provided is weak")
+ if _pwq_error:
+ self._error += ": %s. " % _pwq_error
+ else:
+ self._error += ". "
+ self._error += _("You will have to press Done twice to confirm it.")
+ elif strength < 75:
+ val = 2
+ text = _("Fair")
+ self._error = False
+ elif strength < 90:
+ val = 3
+ text = _("Good")
+ self._error = False
+ else:
+ val = 4
+ text = _("Strong")
+ self._error = False
+
+ self.pw_bar.set_value(val)
+ self.pw_label.set_text(text)
+
+ self.clear_info()
+ if self._error:
+ self.set_warning(self._error)
+ self.window.show_all()
+
+ def _validatePassword(self):
+ """This method checks the password weakness and
+ implements the Press Done twice logic. It is used from
+ the on_back_clicked handler.
+
+ It also sets the self._error of the password is not
+ sufficient or does not pass the pwquality checks.
+
+ :return: True if the password should be accepted, False otherwise
+ :rtype: bool
+
+ """
+
+ # Do various steps to validate the password
+ # sets self._error to an error string
+ # Return True if valid, False otherwise
+ self._error = False
+ pw = self.pw.get_text()
+ confirm = self.confirm.get_text()
+
+ if not pw and not confirm:
+ self._error = _("You must provide and confirm a password.")
+ return False
+
+ try:
+ self._error = validatePassword(pw, confirm)
+ except 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 Done again to use anyway.") % msg
+ self._oldweak = pw
+ return False
+
+ if self._error:
+ return False
+
+ # if no errors, clear the info for next time we go into the spoke
+ self._password = pw
+ self.clear_info()
+ self._error = False
+ return True
+
+ def on_advanced_clicked(self, _button):
+ """Handler for the Advanced.. button. It starts the Advanced dialog
+ for setting homedit, uid, gid and groups.
+ """
+
+ self._user.name = self.username.get_text()
+
+ if self.admin.get_active() and \
+ self._wheel.name not in self._user.groups:
+ self._user.groups.append(self._wheel.name)
+ elif not self.admin.get_active() and \
+ self._wheel.name in self._user.groups:
+ self._user.groups.remove(self._wheel.name)
+
+ self._advanced.refresh()
+ with enlightbox(self.window, self._advanced.window):
+ response = self._advanced.run()
+
+ self.admin.set_active(self._wheel.name in self._user.groups)
+
+ def on_back_clicked(self, button):
+ if not self.usepassword.get_active() or self._validatePassword():
+ self._error = False
+ self.clear_info()
+ NormalSpoke.on_back_clicked(self, button)
+ else:
+ self.clear_info()
+ self.set_warning(self._error)
+ self.pw.grab_focus()
+ self.window.show_all()
+