diff --git a/src/modules/users/CheckPWQuality.cpp b/src/modules/users/CheckPWQuality.cpp index 3728c5a92..39c1f79de 100644 --- a/src/modules/users/CheckPWQuality.cpp +++ b/src/modules/users/CheckPWQuality.cpp @@ -31,19 +31,15 @@ #include PasswordCheck::PasswordCheck() - : m_message() + : m_weight( 0 ) + , m_message() , m_accept( []( const QString& ) { return true; } ) { } -PasswordCheck::PasswordCheck( const QString& m, AcceptFunc a ) - : m_message( [m]() { return m; } ) - , m_accept( a ) -{ -} - -PasswordCheck::PasswordCheck( MessageFunc m, AcceptFunc a ) - : m_message( m ) +PasswordCheck::PasswordCheck( MessageFunc m, AcceptFunc a, Weight weight ) + : m_weight( weight ) + , m_message( m ) , m_accept( a ) { } @@ -59,7 +55,8 @@ DEFINE_CHECK_FUNC( minLength ) { cDebug() << Logger::SubEntry << "minLength set to" << minLength; checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too short" ); }, - [minLength]( const QString& s ) { return s.length() >= minLength; } ) ); + [minLength]( const QString& s ) { return s.length() >= minLength; }, + PasswordCheck::Weight( 10 ) ) ); } } @@ -74,7 +71,8 @@ DEFINE_CHECK_FUNC( maxLength ) { cDebug() << Logger::SubEntry << "maxLength set to" << maxLength; checks.push_back( PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is too long" ); }, - [maxLength]( const QString& s ) { return s.length() <= maxLength; } ) ); + [maxLength]( const QString& s ) { return s.length() <= maxLength; }, + PasswordCheck::Weight( 10 ) ) ); } } @@ -363,7 +361,8 @@ DEFINE_CHECK_FUNC( libpwquality ) cDebug() << "Password strength" << r << "too low"; } return r >= settings->arbitrary_minimum_strength; - } ) ); + }, + PasswordCheck::Weight( 100 ) ) ); } } #endif diff --git a/src/modules/users/CheckPWQuality.h b/src/modules/users/CheckPWQuality.h index 046f31496..1aeb34ba8 100644 --- a/src/modules/users/CheckPWQuality.h +++ b/src/modules/users/CheckPWQuality.h @@ -38,11 +38,18 @@ public: 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 */ + using Weight = size_t; + + /** @brief Generate a @p message if @p filter returns true + * + * When @p filter returns true on the proposed password, the + * password is accepted (by this check). If false, then the + * @p message will be shown to the user. + * + * @p weight is used to order the checks (low-weight goes first). + */ + PasswordCheck( MessageFunc message, AcceptFunc filter, Weight weight = 1000 ); + /** @brief Null check, always accepts, no message */ PasswordCheck(); /** Applies this check to the given password string @p s @@ -52,7 +59,11 @@ public: */ QString filter( const QString& s ) const { return m_accept( s ) ? QString() : m_message(); } + Weight weight() const { return m_weight; } + bool operator<( const PasswordCheck& other ) const { return weight() < other.weight(); } + private: + Weight m_weight; MessageFunc m_message; AcceptFunc m_accept; }; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index c0965a7ed..05095da70 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -144,6 +144,11 @@ UsersPage::retranslate() "use this computer, you can create multiple " "accounts after installation." ) ); } + // Re-do password checks (with output messages) as well. + // .. the password-checking methods get their values from the text boxes, + // not from their parameters. + onPasswordTextChanged( QString() ); + onRootPasswordTextChanged( QString() ); } @@ -222,6 +227,8 @@ void UsersPage::onActivate() { ui->textBoxFullName->setFocus(); + onPasswordTextChanged( QString() ); + onRootPasswordTextChanged( QString() ); } @@ -407,14 +414,7 @@ UsersPage::validateHostnameText( const QString& textRef ) bool UsersPage::checkPasswordAcceptance( const QString& pw1, const QString& pw2, QLabel* badge, QLabel* message ) { - if ( pw1.isEmpty() && pw2.isEmpty() ) - { - // Not exactly labelOk() because we also don't want a checkmark OK - badge->clear(); - message->clear(); - return false; - } - else if ( pw1 != pw2 ) + if ( pw1 != pw2 ) { labelError( badge, message, tr( "Your passwords do not match!" ) ); return false; @@ -424,6 +424,12 @@ UsersPage::checkPasswordAcceptance( const QString& pw1, const QString& pw2, QLab bool failureIsFatal = ui->checkBoxValidatePassword->isChecked(); bool failureFound = false; + if ( m_passwordChecksChanged ) + { + std::sort( m_passwordChecks.begin(), m_passwordChecks.end() ); + m_passwordChecksChanged = false; + } + for ( auto pc : m_passwordChecks ) { QString s = pc.filter( pw1 ); @@ -502,6 +508,8 @@ UsersPage::setReusePasswordDefault( bool checked ) void UsersPage::addPasswordCheck( const QString& key, const QVariant& value ) { + m_passwordChecksChanged = true; + if ( key == "minLength" ) { add_check_minLength( m_passwordChecks, value ); @@ -510,6 +518,16 @@ UsersPage::addPasswordCheck( const QString& key, const QVariant& value ) { add_check_maxLength( m_passwordChecks, value ); } + else if ( key == "nonempty" ) + { + if ( value.toBool() ) + { + m_passwordChecks.push_back( PasswordCheck( + []() { return QCoreApplication::translate( "PWQ", "Password is empty" ); }, + []( const QString& s ) { return !s.isEmpty(); }, + PasswordCheck::Weight( 1 ) ) ); + } + } #ifdef CHECK_PWQUALITY else if ( key == "libpwquality" ) { diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index a2befbd26..c6bf87ecf 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -90,6 +90,7 @@ private: Ui::Page_UserSetup* ui; PasswordCheckList m_passwordChecks; + bool m_passwordChecksChanged = false; bool m_readyFullName; bool m_readyUsername; diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index cae9bef0d..00747195c 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -58,8 +58,14 @@ 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. +# on the user. The values given in this sample file set only very weak +# validation settings. +# +# - nonempty rejects empty passwords +# - there are no length validations +# - libpwquality (if it is enabled at all) has no length of class +# restrictions, although it will still reject palindromes and +# dictionary words with these settings. # # Checks may be listed multiple times; each is checked separately, # and no effort is done to ensure that the checks are consistent @@ -84,6 +90,7 @@ doReusePassword: true # (That will show the box *Allow weak passwords* in the user- # interface, and check it by default). passwordRequirements: + nonempty: true minLength: -1 # Password at least this many characters maxLength: -1 # Password at most this many characters libpwquality: