Discussion:
[firstboot 1/2] Add the pwcheck module for getting the password strength
Martin Gracik
2010-11-23 15:08:19 UTC
Permalink
pwcheck returns the strength of a password in an interval
of 0 to 7, 0 meaning weak, and 7 strong. It also has a
mapping of string representations of these strength values,
so we can show this information to the user.
---
firstboot/pwcheck.py | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 123 insertions(+), 0 deletions(-)
create mode 100644 firstboot/pwcheck.py

diff --git a/firstboot/pwcheck.py b/firstboot/pwcheck.py
new file mode 100644
index 0000000..6aae573
--- /dev/null
+++ b/firstboot/pwcheck.py
@@ -0,0 +1,123 @@
+import re
+import cracklib
+
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger("pwcheck")
+
+import gettext
+_ = lambda x: gettext.ldgettext("firstboot", x)
+
+
+class PwError(Exception):
+ pass
+
+
+class PwRule(object):
+
+ def __init__(self, rule, weight=1, required=False, desc=""):
+ if callable(rule):
+ # is a func
+ self.rule = rule
+ else:
+ # is a regex
+ pattern = re.compile(rule)
+ self.rule = lambda x: bool(pattern.search(x))
+
+ if not weight:
+ raise PwError("weight must be a non-zero value")
+
+ self.weight = weight
+ self.required = required
+
+ self.desc = desc
+
+ @property
+ def include(self):
+ return self.weight > 0
+
+ @property
+ def exclude(self):
+ return self.weight < 0
+
+ def check(self, password):
+ passed = self.rule(password)
+
+ if ((self.include and self.required and not passed) or
+ (self.exclude and self.required and passed)):
+ logger.debug("%s: %d", self.desc, 0)
+ raise PwError("password does not meet required criteria")
+
+ if ((self.include and passed) or
+ (self.exclude and not passed)):
+ logger.debug("%s: %d", self.desc, self.weight)
+ return self.weight
+ else:
+ logger.debug("%s: %d", self.desc, 0)
+ return 0
+
+
+class Password(object):
+
+ def cracklib_check(password):
+ try:
+ cracklib.FascistCheck(password)
+ except ValueError:
+ return False
+ else:
+ return True
+
+ RULES = [ PwRule(rule=lambda x: len(x) >= 4, weight=1, required=True,
+ desc="4 characters or more"),
+ PwRule(rule=lambda x: len(x) >= 8, weight=1, required=False,
+ desc="8 characters or more"),
+ PwRule(rule=lambda x: len(x) >= 12, weight=1, required=False,
+ desc="12 characters or more"),
+ PwRule(rule=r"[a-z]+", weight=1, required=False,
+ desc="at least one lowercase character"),
+ PwRule(rule=r"[A-Z]+", weight=1, required=False,
+ desc="at least one uppercase character"),
+ PwRule(rule=r"[0-9]+", weight=1, required=False,
+ desc="at least one digit"),
+ PwRule(rule=r"[^a-zA-Z0-9]+", weight=1, required=False,
+ desc="at least one special character"),
+ PwRule(rule=cracklib_check, weight=-1, required=False,
+ desc="cracklib") ]
+
+ STRENGTH_STRINGS = [ _("Very weak"),
+ _("Very weak"),
+ _("Weak"),
+ _("Weak"),
+ _("Fairly strong"),
+ _("Strong"),
+ _("Very strong"),
+ _("Very strong") ]
+
+ def __init__(self, password):
+ self.password = password
+
+ @property
+ def strength(self):
+ strength = 0
+ for rule in self.RULES:
+ try:
+ strength += rule.check(self.password)
+ except PwError:
+ return 0
+
+ return strength
+
+ @property
+ def strength_string(self):
+ strength = self.strength
+
+ if strength < 0:
+ return self.STRENGTH_STRINGS[0]
+
+ try:
+ return self.STRENGTH_STRINGS[strength]
+ except IndexError:
+ return _("Undefined")
+
+ def __str__(self):
+ return "%s" % self.password
--
1.7.1.1
Martin Gracik
2010-11-23 15:08:20 UTC
Permalink
Do not show a warning message dialog after
clicking Forward. Instead show a label with
the password strenght, immediately after the
user writes in his password.
Also show a check icon if the confirm password
matches the password.
---
modules/create_user.py | 62 ++++++++++++++++++++++++++++++-----------------
1 files changed, 39 insertions(+), 23 deletions(-)

diff --git a/modules/create_user.py b/modules/create_user.py
index c472cfc..4b6d81f 100644
--- a/modules/create_user.py
+++ b/modules/create_user.py
@@ -27,6 +27,7 @@ from firstboot.config import *
from firstboot.constants import *
from firstboot.functions import *
from firstboot.module import *
+from firstboot.pwcheck import Password

import gettext
_ = lambda x: gettext.ldgettext("firstboot", x)
@@ -105,8 +106,8 @@ class moduleClass(Module):
self.confirmEntry.set_text("")
self.passwordEntry.grab_focus()
return RESULT_FAILURE
- elif not userGroupCheck.isPasswordOk(password, self.passwordEntry):
- return RESULT_FAILURE
+ #elif not userGroupCheck.isPasswordOk(password, self.passwordEntry):
+ # return RESULT_FAILURE

user = self.admin.lookupUserByName(username)

@@ -236,15 +237,22 @@ class moduleClass(Module):

self.passwordEntry = gtk.Entry()
self.passwordEntry.set_visibility(False)
- self.passwordEntry.set_property("primary-icon-stock",
- gtk.STOCK_DIALOG_WARNING)
- self.passwordEntry.set_property("primary-icon-tooltip-text",
- _("Password empty"))
- self.passwordEntry.set_property("primary-icon-activatable", False)
- self.passwordEntry.connect("changed", self.passwordEntry_changed)
-
+ self.strengthLabel = gtk.Label()
+ self.strengthLabel.set_alignment(0.0, 0.5)
self.confirmEntry = gtk.Entry()
self.confirmEntry.set_visibility(False)
+ self.confirmIcon = gtk.Image()
+ self.confirmIcon.set_alignment(0.0, 0.5)
+ self.confirmIcon.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
+ # hide by default
+ self.confirmIcon.set_no_show_all(True)
+
+ self.passwordEntry.connect("changed", self.passwordEntry_changed,
+ self.strengthLabel,
+ self.confirmEntry, self.confirmIcon)
+
+ self.confirmEntry.connect("changed", self.confirmEntry_changed,
+ self.passwordEntry, self.confirmIcon)

self.vbox.pack_start(label, False, True)

@@ -280,6 +288,9 @@ class moduleClass(Module):
table.attach(label, 0, 1, 3, 4, gtk.FILL)
table.attach(self.confirmEntry, 1, 2, 3, 4, gtk.SHRINK, gtk.FILL, 5)

+ table.attach(self.strengthLabel, 2, 3, 2, 3, gtk.FILL)
+ table.attach(self.confirmIcon, 2, 3, 3, 4, gtk.FILL)
+
self.is_admin = gtk.CheckButton(_("Add to Administrators group"))
self.is_admin.set_alignment(0.0, 0.5)
table.attach(self.is_admin, 2, 3, 1, 2, gtk.FILL)
@@ -393,26 +404,31 @@ class moduleClass(Module):
def usernameEntry_changed(self, un_entry):
self.guessUserName = not bool(un_entry.get_text())

- def passwordEntry_changed(self, entry):
+ def passwordEntry_changed(self, entry, strengthLabel,
+ confirmEntry, confirmIcon):
+
+ self.confirmEntry_changed(confirmEntry, entry, confirmIcon)
+
pw = entry.get_text()
if not pw:
- entry.set_property("primary-icon-stock", gtk.STOCK_DIALOG_WARNING)
- entry.set_property("primary-icon-tooltip-text",
- _("Password empty"))
+ strengthLabel.set_text("")
return

- try:
- cracklib.FascistCheck(pw)
- except ValueError as e:
- msg = gettext.ldgettext("cracklib", e)
+ pw = Password(pw)
+ strengthLabel.set_markup('<b>%s</b>' % pw.strength_string)
+
+ def confirmEntry_changed(self, entry, passwordEntry, confirmIcon):
+ pw = passwordEntry.get_text()
+ if not pw:
+ # blank icon
+ confirmIcon.hide()
+ return

- entry.set_property("primary-icon-stock", gtk.STOCK_DIALOG_WARNING)
- entry.set_property("primary-icon-tooltip-text",
- _("Weak password: %s") % msg)
+ if pw == entry.get_text():
+ confirmIcon.show()
else:
- entry.set_property("primary-icon-stock", gtk.STOCK_APPLY)
- entry.set_property("primary-icon-tooltip-text",
- _("Password OK"))
+ # blank icon
+ confirmIcon.hide()

def _runSCU(self, *args):
i = gtk.Invisible()
--
1.7.1.1
Loading...