Passwords: introduce password-checking

- Introduce a map 'passwordRequirements' in users.conf,
   which is a list of named requirements. There are only
   two settings right now, min and max length, but
   additional checks can easily be added in UsersPage.cpp
   by defining additional lambda's to check the given
   password string.
 - Add PasswordCheck instances as needed, with functions
   to check acceptability and to produce messages on rejection.
 - Documentation in the users.conf file itself.

 - In passing, refactor setting of pixmaps on labels.

FIXES #790
main
Adriaan de Groot 7 years ago
parent c2a69ea943
commit d839f8e0b3

@ -37,7 +37,12 @@
#include <QRegExp>
#include <QRegExpValidator>
/** Add a standard pixmap to a label. */
static void
markLabel( QLabel* label, CalamaresUtils::ImageType i )
{
label->setPixmap( CalamaresUtils::defaultPixmap( i, CalamaresUtils::Original, label->size() ) );
}
UsersPage::UsersPage( QWidget* parent )
: QWidget( parent )
@ -268,9 +273,7 @@ UsersPage::validateUsernameText( const QString& textRef )
}
else if ( text.length() > USERNAME_MAX_LENGTH )
{
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelUsername->size() ) );
markLabel( ui->labelUsername, CalamaresUtils::No );
ui->labelUsernameError->setText(
tr( "Your username is too long." ) );
@ -278,18 +281,14 @@ UsersPage::validateUsernameText( const QString& textRef )
}
else if ( val.validate( text, pos ) == QValidator::Invalid )
{
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelUsername->size() ) );
markLabel( ui->labelUsername, CalamaresUtils::No );
ui->labelUsernameError->setText(
tr( "Your username contains invalid characters. Only lowercase letters and numbers are allowed." ) );
m_readyUsername = false;
}
else {
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
CalamaresUtils::Original,
ui->labelUsername->size() ) );
markLabel( ui->labelUsername, CalamaresUtils::Yes );
ui->labelUsernameError->clear();
m_readyUsername = true;
}
@ -322,9 +321,7 @@ UsersPage::validateHostnameText( const QString& textRef )
}
else if ( text.length() < HOSTNAME_MIN_LENGTH )
{
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelHostname->size() ) );
markLabel( ui->labelHostname, CalamaresUtils::No );
ui->labelHostnameError->setText(
tr( "Your hostname is too short." ) );
@ -333,9 +330,7 @@ UsersPage::validateHostnameText( const QString& textRef )
}
else if ( text.length() > HOSTNAME_MAX_LENGTH )
{
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelHostname->size() ) );
markLabel( ui->labelHostname, CalamaresUtils::No );
ui->labelHostnameError->setText(
tr( "Your hostname is too long." ) );
@ -344,9 +339,7 @@ UsersPage::validateHostnameText( const QString& textRef )
}
else if ( val.validate( text, pos ) == QValidator::Invalid )
{
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelHostname->size() ) );
markLabel( ui->labelHostname, CalamaresUtils::No );
ui->labelHostnameError->setText(
tr( "Your hostname contains invalid characters. Only letters, numbers and dashes are allowed." ) );
@ -354,9 +347,7 @@ UsersPage::validateHostnameText( const QString& textRef )
}
else
{
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
CalamaresUtils::Original,
ui->labelHostname->size() ) );
markLabel( ui->labelHostname, CalamaresUtils::Yes );
ui->labelHostnameError->clear();
m_readyHostname = true;
}
@ -364,7 +355,6 @@ UsersPage::validateHostnameText( const QString& textRef )
emit checkReady( isReady() );
}
void
UsersPage::onPasswordTextChanged( const QString& )
{
@ -380,24 +370,35 @@ UsersPage::onPasswordTextChanged( const QString& )
else if ( pw1 != pw2 )
{
ui->labelUserPasswordError->setText( tr( "Your passwords do not match!" ) );
ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelUserPassword->size() ) );
markLabel( ui->labelUserPassword, CalamaresUtils::No );
m_readyPassword = false;
}
else
{
ui->labelUserPasswordError->clear();
ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
CalamaresUtils::Original,
ui->labelUserPassword->size() ) );
m_readyPassword = true;
bool ok = true;
for ( auto pc : m_passwordChecks )
{
QString s = pc.filter( pw1 );
if ( !s.isEmpty() )
{
ui->labelUserPasswordError->setText( s );
markLabel( ui->labelUserPassword, CalamaresUtils::No );
ok = false;
m_readyPassword = false;
}
}
if ( ok )
{
ui->labelUserPasswordError->clear();
markLabel( ui->labelUserPassword, CalamaresUtils::Yes );
m_readyPassword = true;
}
}
emit checkReady( isReady() );
}
void
UsersPage::onRootPasswordTextChanged( const QString& )
{
@ -413,18 +414,30 @@ UsersPage::onRootPasswordTextChanged( const QString& )
else if ( pw1 != pw2 )
{
ui->labelRootPasswordError->setText( tr( "Your passwords do not match!" ) );
ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
CalamaresUtils::Original,
ui->labelRootPassword->size() ) );
markLabel( ui->labelRootPassword, CalamaresUtils::No );
m_readyRootPassword = false;
}
else
{
ui->labelRootPasswordError->clear();
ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
CalamaresUtils::Original,
ui->labelRootPassword->size() ) );
m_readyRootPassword = true;
bool ok = true;
for ( auto pc : m_passwordChecks )
{
QString s = pc.filter( pw1 );
if ( !s.isEmpty() )
{
ui->labelRootPasswordError->setText( s );
markLabel( ui->labelRootPassword, CalamaresUtils::No );
ok = false;
m_readyRootPassword = false;
}
}
if ( ok )
{
ui->labelRootPasswordError->clear();
markLabel( ui->labelRootPassword, CalamaresUtils::Yes );
m_readyRootPassword = true;
}
}
emit checkReady( isReady() );
@ -444,3 +457,69 @@ UsersPage::setReusePasswordDefault( bool checked )
ui->checkBoxReusePassword->setChecked( checked );
emit checkReady( isReady() );
}
UsersPage::PasswordCheck::PasswordCheck()
: m_message()
, m_accept( []( const QString& s )
{
return true;
} )
{
}
UsersPage::PasswordCheck::PasswordCheck( const QString& m, AcceptFunc a )
: m_message( [m](){ return m; } )
, m_accept( a )
{
}
UsersPage::PasswordCheck::PasswordCheck( MessageFunc m, AcceptFunc a )
: m_message( m )
, m_accept( a )
{
}
void
UsersPage::addPasswordCheck( const QString& key, const QVariant& value )
{
if ( key == "minLength" )
{
int minLength = -1;
if ( value.canConvert( QVariant::Int ) )
minLength = value.toInt();
if ( minLength > 0 )
{
cDebug() << key << " .. set to" << minLength;
m_passwordChecks.push_back(
PasswordCheck(
[]()
{
return tr( "Password is too short" );
},
[minLength]( const QString& s )
{
return s.length() >= minLength;
} ) );
}
}
else if ( key == "maxLength" )
{
int maxLength = -1;
if ( value.canConvert( QVariant::Int ) )
maxLength = value.toInt();
if ( maxLength > 0 )
{
cDebug() << key << " .. set to" << maxLength;
m_passwordChecks.push_back(
PasswordCheck( []()
{
return tr( "Password is too long" );
}, [maxLength]( const QString& s )
{
return s.length() <= maxLength;
} ) );
}
}
else
cDebug() << "WARNING: Unknown password-check key" << '"' << key << '"';
}

@ -28,7 +28,10 @@
#include <QWidget>
namespace Ui {
#include <functional>
namespace Ui
{
class Page_UserSetup;
}
@ -49,6 +52,8 @@ public:
void setAutologinDefault( bool checked );
void setReusePasswordDefault( bool checked );
void addPasswordCheck( const QString& key, const QVariant& value );
protected slots:
void onFullNameTextEdited( const QString& );
void fillSuggestions();
@ -65,6 +70,42 @@ signals:
private:
Ui::Page_UserSetup* ui;
/**
* Support for (dynamic) checks on the password's validity.
* This can be used to implement password requirements like
* "at least 6 characters". Function addPasswordCheck()
* instantiates these and adds them to the list of checks.
*/
class PasswordCheck
{
public:
/** Return true if the string is acceptable. */
using AcceptFunc = std::function<bool( const QString& )>;
using MessageFunc = std::function<QString()>;
/** Generate a @p message if @p filter returns true */
PasswordCheck( MessageFunc message, AcceptFunc filter );
/** Yields @p message if @p filter returns true */
PasswordCheck( const QString& message, AcceptFunc filter );
/** Null check, always returns empty */
PasswordCheck();
/** Applies this check to the given password string @p s
* and returns an empty string if the password is ok
* according to this filter. Returns a message describing
* what is wrong if not.
*/
QString filter( const QString& s ) const
{
return m_accept( s ) ? QString() : m_message();
}
private:
MessageFunc m_message;
AcceptFunc m_accept;
} ;
QVector<PasswordCheck> m_passwordChecks;
const QRegExp USERNAME_RX = QRegExp( "^[a-z_][a-z0-9_-]*[$]?$" );
const QRegExp HOSTNAME_RX = QRegExp( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
const int USERNAME_MAX_LENGTH = 31;

@ -20,6 +20,7 @@
#include "UsersPage.h"
#include "utils/Logger.h"
#include "JobQueue.h"
#include "GlobalStorage.h"
@ -159,11 +160,23 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_widget->setAutologinDefault( configurationMap.value( "doAutologin" ).toBool() );
}
if ( configurationMap.contains( "doReusePassword" ) &&
configurationMap.value( "doReusePassword" ).type() == QVariant::Bool )
{
m_widget->setReusePasswordDefault( configurationMap.value( "doReusePassword" ).toBool() );
}
if ( configurationMap.contains( "passwordRequirements" ) &&
configurationMap.value( "passwordRequirements" ).type() == QVariant::Map )
{
auto pr_checks( configurationMap.value( "passwordRequirements" ).toMap() );
for (decltype(pr_checks)::const_iterator i = pr_checks.constBegin();
i != pr_checks.constEnd(); ++i)
{
m_widget->addPasswordCheck( i.key(), i.value() );
}
}
}

@ -27,3 +27,17 @@ sudoersGroup: wheel
setRootPassword: true
doReusePassword: true
# These are optional password-requirements that a distro can enforce
# on the user. The values given in this sample file disable each check,
# as if the check was not listed at all.
#
# Checks may be listed multiple times; each is checked separately,
# and no effort is done to ensure that the checks are consistent
# (e.g. specifying a maximum length less than the minimum length
# will annoy users).
#
# (additional checks may be implemented in UsersPage.cpp)
passwordRequirements:
minLength: -1 # Password at least this many characters
maxLength: -1 # Password at most this many characters

Loading…
Cancel
Save