qubes-installer-qubes-os/anaconda/pyanaconda/ui/gui/spokes/password.py

340 lines
12 KiB
Python
Raw Normal View History

# 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.
#
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.gui.utils import set_password_visibility
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):
"""
.. inheritance-diagram:: PasswordSpoke
:parts: 3
"""
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. attach to the confirm field so that errors focus on confirm,
# and check changes to the password field in on_password_changed
self._confirm_check = self.add_check(self.confirm, 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)
self.pw_bar.add_offset_value("full", 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()
# set the visibility of the password entries
set_password_visibility(self.pw, False)
set_password_visibility(self.confirm, False)
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
2015-03-24 03:27:09 +00:00
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 = InputCheck.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
return result
def _updatePwQuality(self, empty, strength):
"""Update the password quality information.
"""
# If the password is empty, clear the strength bar
if empty:
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 on_password_changed(self, editable, data=None):
# Reset the counters used for the "press Done twice" logic
self._waiveStrengthClicks = 0
self._waiveASCIIClicks = 0
# Update the password/confirm match check on changes to the main password field
self._confirm_check.update_check_status()
def on_password_icon_clicked(self, entry, icon_pos, event):
"""Called by Gtk callback when the icon of a password entry is clicked."""
set_password_visibility(entry, not entry.get_visibility())
def _checkPasswordStrength(self, inputcheck):
"""Update the error message based on password strength.
Update the password strength bar and set an error message.
"""
pw = self.pw.get_text()
confirm = self.confirm.get_text()
# Skip the check if no password is required
if self._kickstarted:
return InputCheck.CHECK_OK
# If the password is empty, clear the strength bar and skip this check
if not pw and not confirm:
self._updatePwQuality(True, 0)
return InputCheck.CHECK_OK
# determine the password strength
valid, pwstrength, error = validatePassword(pw, "root", minlen=self.policy.minlen)
# set the strength bar
self._updatePwQuality(False, pwstrength)
# If the password failed the validity check, fail this check
if not valid and error:
return error
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 error:
return _(PASSWORD_WEAK_CONFIRM_WITH_ERROR) % 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 error:
return _(PASSWORD_WEAK_WITH_ERROR) % 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)