diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 87e64deb1..7c4bb6a7d 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -37,7 +37,12 @@ #include #include - +/** 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 << '"'; +} diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 0f328f46c..5a72e11de 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -28,7 +28,10 @@ #include -namespace Ui { +#include + +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; + using MessageFunc = std::function; + + /** 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 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; diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index d601014ae..25b4dee84 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -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() ); + } + } } diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index dea0ba999..d5466c62f 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -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