qubes-installer-qubes-os/anaconda/pyanaconda/ui/gui/spokes/password.py
Marek Marczykowski-Górecki 701ced5ddb anaconda: update to 22.20.13-1
Apply diff anaconda-21.48.21-1..anaconda-22.20.13-1
2016-03-22 02:27:17 +13:00

337 lines
13 KiB
Python

# root password spoke class
#
# Copyright (C) 2012-2014 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@redhat.com>
# Chris Lumens <clumens@redhat.com>
#
from pyanaconda.flags import flags
from pyanaconda.i18n import _, CN_
from pyanaconda.users import cryptPassword, validatePassword
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.categories.user_settings import UserSettingsCategory
from pyanaconda.ui.gui.helpers import GUISpokeInputCheckHandler
from pyanaconda.ui.common import FirstbootSpokeMixIn
from pyanaconda.ui.helpers import InputCheck
from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI,\
PASSWORD_STRENGTH_DESC, PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR,\
PASSWORD_WEAK_CONFIRM, PASSWORD_WEAK_CONFIRM_WITH_ERROR, PASSWORD_DONE_TWICE,\
PW_ASCII_CHARS, PASSWORD_ASCII
__all__ = ["PasswordSpoke"]
class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
builderObjects = ["passwordWindow"]
mainWidgetName = "passwordWindow"
focusWidgetName = "pw"
uiFile = "spokes/password.glade"
helpFile = "PasswordSpoke.xml"
category = UserSettingsCategory
icon = "dialog-password-symbolic"
title = CN_("GUI|Spoke", "_ROOT PASSWORD")
def __init__(self, *args):
NormalSpoke.__init__(self, *args)
GUISpokeInputCheckHandler.__init__(self)
self._lock = self.data.rootpw.lock
self._kickstarted = False
def initialize(self):
NormalSpoke.initialize(self)
# place holders for the text boxes
self.pw = self.builder.get_object("pw")
self.confirm = self.builder.get_object("confirmPW")
self.lock = self.builder.get_object("lock")
# Install the password checks:
# - Has a password been specified?
# - If a password has been specified and there is data in the confirm box, do they match?
# - How strong is the password?
# - Does the password contain non-ASCII characters?
# - Is there any data in the confirm box?
self.add_check(self.pw, self._checkPasswordEmpty)
# The password confirmation needs to be checked whenever either of the password
# fields change. Separate checks are created for each field so that edits on either
# will trigger a new check and so that the last edited field will get focus when
# Done is clicked. The checks are saved here so that either check can trigger the
# other check in order to reset the status on both when either field is changed.
# The check_data field is used as a flag to prevent infinite recursion.
self._confirm_check = self.add_check(self.confirm, self._checkPasswordConfirm)
self._password_check = self.add_check(self.pw, self._checkPasswordConfirm)
# Keep a reference for these checks, since they have to be manually run for the
# click Done twice check.
self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength)
self._pwASCIICheck = self.add_check(self.pw, self._checkPasswordASCII)
self.add_check(self.confirm, self._checkPasswordEmpty)
# Counters for checks that ask the user to click Done to confirm
self._waiveStrengthClicks = 0
self._waiveASCIIClicks = 0
# Password validation data
self._pwq_error = None
self._pwq_valid = True
self._kickstarted = self.data.rootpw.seen
if self._kickstarted:
self.pw.set_placeholder_text(_("The password is set."))
self.confirm.set_placeholder_text(_("The password is set."))
self.pw_bar = self.builder.get_object("password_bar")
self.pw_label = self.builder.get_object("password_label")
# Configure levels for the password bar
self.pw_bar.add_offset_value("low", 2)
self.pw_bar.add_offset_value("medium", 3)
self.pw_bar.add_offset_value("high", 4)
# Configure the password policy, if available. Otherwise use defaults.
self.policy = self.data.anaconda.pwpolicy.get_policy("root")
if not self.policy:
self.policy = self.data.anaconda.PwPolicyData()
def refresh(self):
# Enable the input checks in case they were disabled on the last exit
for check in self.checks:
check.enabled = True
self.lock.set_active(self._lock)
self.on_lock_clicked(self.lock)
self.pw.emit("changed")
self.confirm.emit("changed")
def on_lock_clicked(self, lock):
self.pw.set_sensitive(not lock.get_active())
self.confirm.set_sensitive(not lock.get_active())
if not lock.get_active():
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.data.rootpw.lock:
return _("Root account is disabled")
elif self.data.rootpw.password:
return _("Root password is set")
else:
return _("Root password is not set")
@property
def mandatory(self):
return not any(user for user in self.data.user.userList
if "wheel" in user.groups)
def apply(self):
pw = self.pw.get_text()
# value from the kickstart changed
self.data.rootpw.seen = False
self._kickstarted = False
if pw:
self.data.rootpw.password = cryptPassword(pw)
self.data.rootpw.isCrypted = True
self.data.rootpw.lock = self._lock
self.pw.set_placeholder_text("")
self.confirm.set_placeholder_text("")
@property
def completed(self):
return bool(self.data.rootpw.password or self.data.rootpw.lock)
@property
def sensitive(self):
return not (self.completed and flags.automatedInstall
and self.data.rootpw.seen)
def _checkPasswordEmpty(self, inputcheck):
"""Check whether a password has been specified at all."""
# If the password was set by kickstart, skip this check
if self._kickstarted and not self.policy.changesok:
return InputCheck.CHECK_OK
if self.lock.get_active():
return InputCheck.CHECK_OK
if not self.get_input(inputcheck.input_obj):
if inputcheck.input_obj == self.pw:
return _(PASSWORD_EMPTY_ERROR)
else:
return _(PASSWORD_CONFIRM_ERROR_GUI)
else:
return InputCheck.CHECK_OK
def _checkPasswordConfirm(self, inputcheck):
"""Check whether the password matches the confirmation data."""
pw = self.pw.get_text()
confirm = self.confirm.get_text()
lock = self.lock.get_active()
if lock:
self._lock = True
self._password = None
self.clear_info()
self._error = False
result = GUICheck.CHECK_OK
# Skip the check if no password is required
elif (not pw and not confirm) and self._kickstarted:
result = InputCheck.CHECK_OK
elif confirm and (pw != confirm):
result = _(PASSWORD_CONFIRM_ERROR_GUI)
else:
result = InputCheck.CHECK_OK
# If the check succeeded, reset the status of the other check object
# Disable the current check to prevent a cycle
inputcheck.enabled = False
if result == InputCheck.CHECK_OK:
if inputcheck == self._confirm_check:
self._password_check.update_check_status()
else:
self._confirm_check.update_check_status()
inputcheck.enabled = True
return result
def _updatePwQuality(self, editable=None, data=None):
"""Update the password quality information.
This function is called by the ::changed signal handler on the
password field.
"""
pwtext = self.pw.get_text()
# Reset the counters used for the "press Done twice" logic
self._waiveStrengthClicks = 0
self._waiveASCIIClicks = 0
self._pwq_valid, strength, self._pwq_error = validatePassword(pwtext, "root", minlen=self.policy.minlen)
if not pwtext:
val = 0
elif strength < 50:
val = 1
elif strength < 75:
val = 2
elif strength < 90:
val = 3
else:
val = 4
text = _(PASSWORD_STRENGTH_DESC[val])
self.pw_bar.set_value(val)
self.pw_label.set_text(text)
def _checkPasswordStrength(self, inputcheck):
"""Update the error message based on password strength.
Convert the strength set by _updatePwQuality into an error message.
"""
pw = self.pw.get_text()
confirm = self.confirm.get_text()
# Skip the check if no password is required
if (not pw and not confirm) and self._kickstarted:
return InputCheck.CHECK_OK
# Check for validity errors
if (not self._pwq_valid) and (self._pwq_error):
return self._pwq_error
# use strength from policy, not bars
_valid, pwstrength, _error = validatePassword(pw, "root", minlen=self.policy.minlen)
if pwstrength < self.policy.minquality:
# If Done has been clicked twice, waive the check
if self._waiveStrengthClicks > 1:
return InputCheck.CHECK_OK
elif self._waiveStrengthClicks == 1:
if self._pwq_error:
return _(PASSWORD_WEAK_CONFIRM_WITH_ERROR) % self._pwq_error
else:
return _(PASSWORD_WEAK_CONFIRM)
else:
# non-strict allows done to be clicked twice
if self.policy.strict:
done_msg = ""
else:
done_msg = _(PASSWORD_DONE_TWICE)
if self._pwq_error:
return _(PASSWORD_WEAK_WITH_ERROR) % (self._pwq_error, done_msg)
else:
return _(PASSWORD_WEAK) % done_msg
else:
return InputCheck.CHECK_OK
def _checkPasswordASCII(self, inputcheck):
"""Set an error message if the password contains non-ASCII characters.
Like the password strength check, this check can be bypassed by
pressing Done twice.
"""
# If Done has been clicked, waive the check
if self._waiveASCIIClicks > 0:
return InputCheck.CHECK_OK
password = self.get_input(inputcheck.input_obj)
if password and any(char not in PW_ASCII_CHARS for char in password):
return _(PASSWORD_ASCII)
return InputCheck.CHECK_OK
def on_back_clicked(self, button):
# If the failed check is for password strength or non-ASCII
# characters, add a click to the counter and check again
failed_check = next(self.failed_checks_with_message, None)
if not self.policy.strict and failed_check == self._pwStrengthCheck:
self._waiveStrengthClicks += 1
self._pwStrengthCheck.update_check_status()
elif failed_check == self._pwASCIICheck:
self._waiveASCIIClicks += 1
self._pwASCIICheck.update_check_status()
# If neither the password nor the confirm field are set, skip the checks
if (not self.pw.get_text()) and (not self.confirm.get_text()):
for check in self.checks:
check.enabled = False
if GUISpokeInputCheckHandler.on_back_clicked(self, button):
NormalSpoke.on_back_clicked(self, button)