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
This commit is contained in:
parent
decf83d403
commit
ab67b7d2f1
@ -37,7 +37,12 @@
|
|||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QRegExpValidator>
|
#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 )
|
UsersPage::UsersPage( QWidget* parent )
|
||||||
: QWidget( parent )
|
: QWidget( parent )
|
||||||
@ -268,9 +273,7 @@ UsersPage::validateUsernameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else if ( text.length() > USERNAME_MAX_LENGTH )
|
else if ( text.length() > USERNAME_MAX_LENGTH )
|
||||||
{
|
{
|
||||||
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelUsername, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelUsername->size() ) );
|
|
||||||
ui->labelUsernameError->setText(
|
ui->labelUsernameError->setText(
|
||||||
tr( "Your username is too long." ) );
|
tr( "Your username is too long." ) );
|
||||||
|
|
||||||
@ -278,18 +281,14 @@ UsersPage::validateUsernameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else if ( val.validate( text, pos ) == QValidator::Invalid )
|
else if ( val.validate( text, pos ) == QValidator::Invalid )
|
||||||
{
|
{
|
||||||
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelUsername, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelUsername->size() ) );
|
|
||||||
ui->labelUsernameError->setText(
|
ui->labelUsernameError->setText(
|
||||||
tr( "Your username contains invalid characters. Only lowercase letters and numbers are allowed." ) );
|
tr( "Your username contains invalid characters. Only lowercase letters and numbers are allowed." ) );
|
||||||
|
|
||||||
m_readyUsername = false;
|
m_readyUsername = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
|
markLabel( ui->labelUsername, CalamaresUtils::Yes );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelUsername->size() ) );
|
|
||||||
ui->labelUsernameError->clear();
|
ui->labelUsernameError->clear();
|
||||||
m_readyUsername = true;
|
m_readyUsername = true;
|
||||||
}
|
}
|
||||||
@ -322,9 +321,7 @@ UsersPage::validateHostnameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else if ( text.length() < HOSTNAME_MIN_LENGTH )
|
else if ( text.length() < HOSTNAME_MIN_LENGTH )
|
||||||
{
|
{
|
||||||
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelHostname, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelHostname->size() ) );
|
|
||||||
ui->labelHostnameError->setText(
|
ui->labelHostnameError->setText(
|
||||||
tr( "Your hostname is too short." ) );
|
tr( "Your hostname is too short." ) );
|
||||||
|
|
||||||
@ -333,9 +330,7 @@ UsersPage::validateHostnameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else if ( text.length() > HOSTNAME_MAX_LENGTH )
|
else if ( text.length() > HOSTNAME_MAX_LENGTH )
|
||||||
{
|
{
|
||||||
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelHostname, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelHostname->size() ) );
|
|
||||||
ui->labelHostnameError->setText(
|
ui->labelHostnameError->setText(
|
||||||
tr( "Your hostname is too long." ) );
|
tr( "Your hostname is too long." ) );
|
||||||
|
|
||||||
@ -344,9 +339,7 @@ UsersPage::validateHostnameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else if ( val.validate( text, pos ) == QValidator::Invalid )
|
else if ( val.validate( text, pos ) == QValidator::Invalid )
|
||||||
{
|
{
|
||||||
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelHostname, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelHostname->size() ) );
|
|
||||||
ui->labelHostnameError->setText(
|
ui->labelHostnameError->setText(
|
||||||
tr( "Your hostname contains invalid characters. Only letters, numbers and dashes are allowed." ) );
|
tr( "Your hostname contains invalid characters. Only letters, numbers and dashes are allowed." ) );
|
||||||
|
|
||||||
@ -354,9 +347,7 @@ UsersPage::validateHostnameText( const QString& textRef )
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
|
markLabel( ui->labelHostname, CalamaresUtils::Yes );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelHostname->size() ) );
|
|
||||||
ui->labelHostnameError->clear();
|
ui->labelHostnameError->clear();
|
||||||
m_readyHostname = true;
|
m_readyHostname = true;
|
||||||
}
|
}
|
||||||
@ -364,7 +355,6 @@ UsersPage::validateHostnameText( const QString& textRef )
|
|||||||
emit checkReady( isReady() );
|
emit checkReady( isReady() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
UsersPage::onPasswordTextChanged( const QString& )
|
UsersPage::onPasswordTextChanged( const QString& )
|
||||||
{
|
{
|
||||||
@ -380,24 +370,35 @@ UsersPage::onPasswordTextChanged( const QString& )
|
|||||||
else if ( pw1 != pw2 )
|
else if ( pw1 != pw2 )
|
||||||
{
|
{
|
||||||
ui->labelUserPasswordError->setText( tr( "Your passwords do not match!" ) );
|
ui->labelUserPasswordError->setText( tr( "Your passwords do not match!" ) );
|
||||||
ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelUserPassword, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelUserPassword->size() ) );
|
|
||||||
m_readyPassword = false;
|
m_readyPassword = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->labelUserPasswordError->clear();
|
bool ok = true;
|
||||||
ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
|
for ( auto pc : m_passwordChecks )
|
||||||
CalamaresUtils::Original,
|
{
|
||||||
ui->labelUserPassword->size() ) );
|
QString s = pc.filter( pw1 );
|
||||||
m_readyPassword = true;
|
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() );
|
emit checkReady( isReady() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
UsersPage::onRootPasswordTextChanged( const QString& )
|
UsersPage::onRootPasswordTextChanged( const QString& )
|
||||||
{
|
{
|
||||||
@ -413,18 +414,30 @@ UsersPage::onRootPasswordTextChanged( const QString& )
|
|||||||
else if ( pw1 != pw2 )
|
else if ( pw1 != pw2 )
|
||||||
{
|
{
|
||||||
ui->labelRootPasswordError->setText( tr( "Your passwords do not match!" ) );
|
ui->labelRootPasswordError->setText( tr( "Your passwords do not match!" ) );
|
||||||
ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No,
|
markLabel( ui->labelRootPassword, CalamaresUtils::No );
|
||||||
CalamaresUtils::Original,
|
|
||||||
ui->labelRootPassword->size() ) );
|
|
||||||
m_readyRootPassword = false;
|
m_readyRootPassword = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->labelRootPasswordError->clear();
|
bool ok = true;
|
||||||
ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes,
|
for ( auto pc : m_passwordChecks )
|
||||||
CalamaresUtils::Original,
|
{
|
||||||
ui->labelRootPassword->size() ) );
|
QString s = pc.filter( pw1 );
|
||||||
m_readyRootPassword = true;
|
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() );
|
emit checkReady( isReady() );
|
||||||
@ -444,3 +457,69 @@ UsersPage::setReusePasswordDefault( bool checked )
|
|||||||
ui->checkBoxReusePassword->setChecked( checked );
|
ui->checkBoxReusePassword->setChecked( checked );
|
||||||
emit checkReady( isReady() );
|
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>
|
#include <QWidget>
|
||||||
|
|
||||||
namespace Ui {
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
class Page_UserSetup;
|
class Page_UserSetup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +52,8 @@ public:
|
|||||||
void setAutologinDefault( bool checked );
|
void setAutologinDefault( bool checked );
|
||||||
void setReusePasswordDefault( bool checked );
|
void setReusePasswordDefault( bool checked );
|
||||||
|
|
||||||
|
void addPasswordCheck( const QString& key, const QVariant& value );
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onFullNameTextEdited( const QString& );
|
void onFullNameTextEdited( const QString& );
|
||||||
void fillSuggestions();
|
void fillSuggestions();
|
||||||
@ -65,6 +70,42 @@ signals:
|
|||||||
private:
|
private:
|
||||||
Ui::Page_UserSetup* ui;
|
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 USERNAME_RX = QRegExp( "^[a-z_][a-z0-9_-]*[$]?$" );
|
||||||
const QRegExp HOSTNAME_RX = QRegExp( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
|
const QRegExp HOSTNAME_RX = QRegExp( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" );
|
||||||
const int USERNAME_MAX_LENGTH = 31;
|
const int USERNAME_MAX_LENGTH = 31;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "UsersPage.h"
|
#include "UsersPage.h"
|
||||||
|
|
||||||
|
#include "utils/Logger.h"
|
||||||
#include "JobQueue.h"
|
#include "JobQueue.h"
|
||||||
#include "GlobalStorage.h"
|
#include "GlobalStorage.h"
|
||||||
|
|
||||||
@ -165,5 +166,17 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
|||||||
{
|
{
|
||||||
m_widget->setReusePasswordDefault( configurationMap.value( "doReusePassword" ).toBool() );
|
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
|
setRootPassword: true
|
||||||
doReusePassword: 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…
Reference in New Issue
Block a user