From dab831b2ffe994fba4f07ae3b0f426080e8d00ae Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 12:47:01 +0200 Subject: [PATCH 01/19] [users] Introduce a (stub) Config object --- src/modules/users/CMakeLists.txt | 1 + src/modules/users/Config.cpp | 33 ++++++++++++++++++++++++++++ src/modules/users/Config.h | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/modules/users/Config.cpp create mode 100644 src/modules/users/Config.h diff --git a/src/modules/users/CMakeLists.txt b/src/modules/users/CMakeLists.txt index a449d39ac..bf93eb26a 100644 --- a/src/modules/users/CMakeLists.txt +++ b/src/modules/users/CMakeLists.txt @@ -28,6 +28,7 @@ calamares_add_plugin( users UsersPage.cpp SetHostNameJob.cpp CheckPWQuality.cpp + Config.cpp UI page_usersetup.ui RESOURCES diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp new file mode 100644 index 000000000..fb69298f6 --- /dev/null +++ b/src/modules/users/Config.cpp @@ -0,0 +1,33 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2020 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * License-Filename: LICENSE + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Config.h" + +Config::Config( QObject* parent ) + : QObject( parent ) +{ +} + +Config::~Config() {} + +void +Config::setConfigurationMap( const QVariantMap& ) +{ +} diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h new file mode 100644 index 000000000..95c8c33bd --- /dev/null +++ b/src/modules/users/Config.h @@ -0,0 +1,37 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2020 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * License-Filename: LICENSE + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef USERS_CONFIG_H +#define USERS_CONFIG_H + +#include +#include + +class Config : public QObject +{ +public: + Config( QObject* parent = nullptr ); + ~Config(); + + // Currently, config does nothing + void setConfigurationMap( const QVariantMap& ); +}; + +#endif From f9b114a67ad7d6b27bd90148493b82575c838460 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 15:21:04 +0200 Subject: [PATCH 02/19] [users] Pass the Config object to the Page - delay construction of the Page (widget) until it's needed - hand the Config object to the Page on construction This is prep-work for putting the configuration information into the Config object, rather than in the UI elements. --- src/modules/users/UsersPage.cpp | 7 ++++--- src/modules/users/UsersPage.h | 5 ++++- src/modules/users/UsersViewStep.cpp | 28 +++++++++++++++++++++------- src/modules/users/UsersViewStep.h | 3 +++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 5c649d622..62b8920e7 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -79,9 +79,10 @@ labelOk( QLabel* pix, QLabel* label ) pix->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, label->size() ) ); } -UsersPage::UsersPage( QWidget* parent ) +UsersPage::UsersPage( Config* config, QWidget* parent ) : QWidget( parent ) , ui( new Ui::Page_UserSetup ) + , m_config( config ) , m_readyFullName( false ) , m_readyUsername( false ) , m_readyHostname( false ) @@ -99,12 +100,12 @@ UsersPage::UsersPage( QWidget* parent ) connect( ui->textBoxUserVerifiedPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged ); connect( ui->textBoxRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged ); connect( ui->textBoxVerifiedRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged ); - connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [ this ]( int ) { + connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [this]( int ) { onPasswordTextChanged( ui->textBoxUserPassword->text() ); onRootPasswordTextChanged( ui->textBoxRootPassword->text() ); checkReady( isReady() ); } ); - connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [ this ]( int checked ) { + connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( int checked ) { /* When "reuse" is checked, hide the fields for explicitly * entering the root password. However, if we're going to * disable the root password anyway, hide them all regardless of diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 3382e9335..a13886de6 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -29,6 +29,8 @@ #include +class Config; + class QLabel; namespace Ui @@ -40,7 +42,7 @@ class UsersPage : public QWidget { Q_OBJECT public: - explicit UsersPage( QWidget* parent = nullptr ); + explicit UsersPage( Config* config, QWidget* parent = nullptr ); virtual ~UsersPage(); bool isReady(); @@ -95,6 +97,7 @@ private: void retranslate(); Ui::Page_UserSetup* ui; + Config* m_config; PasswordCheckList m_passwordChecks; bool m_passwordChecksChanged = false; diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index fe633b1c2..3fc86eb91 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -20,17 +20,17 @@ #include "UsersViewStep.h" +#include "Config.h" #include "SetHostNameJob.h" #include "SetPasswordJob.h" #include "UsersPage.h" +#include "GlobalStorage.h" +#include "JobQueue.h" #include "utils/Logger.h" #include "utils/NamedEnum.h" #include "utils/Variant.h" -#include "GlobalStorage.h" -#include "JobQueue.h" - CALAMARES_PLUGIN_FACTORY_DEFINITION( UsersViewStepFactory, registerPlugin< UsersViewStep >(); ) static const NamedEnumTable< SetHostNameJob::Action >& @@ -53,11 +53,11 @@ hostnameActions() UsersViewStep::UsersViewStep( QObject* parent ) : Calamares::ViewStep( parent ) - , m_widget( new UsersPage() ) + , m_widget( nullptr ) , m_actions( SetHostNameJob::Action::None ) + , m_config( new Config( this ) ) { emit nextStatusChanged( true ); - connect( m_widget, &UsersPage::checkReady, this, &UsersViewStep::nextStatusChanged ); } @@ -80,6 +80,11 @@ UsersViewStep::prettyName() const QWidget* UsersViewStep::widget() { + if ( !m_widget ) + { + m_widget = new UsersPage( m_config ); + connect( m_widget, &UsersPage::checkReady, this, &UsersViewStep::nextStatusChanged ); + } return m_widget; } @@ -87,7 +92,7 @@ UsersViewStep::widget() bool UsersViewStep::isNextEnabled() const { - return m_widget->isReady(); + return m_widget ? m_widget->isReady() : true; } @@ -122,7 +127,10 @@ UsersViewStep::jobs() const void UsersViewStep::onActivate() { - m_widget->onActivate(); + if ( m_widget ) + { + m_widget->onActivate(); + } } @@ -130,6 +138,10 @@ void UsersViewStep::onLeave() { m_jobs.clear(); + if ( !m_widget ) + { + return; + } m_jobs.append( m_widget->createJobs( m_defaultGroups ) ); Calamares::Job* j; @@ -222,4 +234,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) Action hostsfileAction = getBool( configurationMap, "writeHostsFile", true ) ? Action::WriteEtcHosts : Action::None; m_actions = hostsfileAction | hostnameAction; + + m_config->setConfigurationMap( configurationMap ); } diff --git a/src/modules/users/UsersViewStep.h b/src/modules/users/UsersViewStep.h index 03cc83819..aacf11f2a 100644 --- a/src/modules/users/UsersViewStep.h +++ b/src/modules/users/UsersViewStep.h @@ -29,6 +29,7 @@ #include #include +class Config; class UsersPage; class PLUGINDLLEXPORT UsersViewStep : public Calamares::ViewStep @@ -62,6 +63,8 @@ private: QStringList m_defaultGroups; SetHostNameJob::Actions m_actions; + + Config* m_config; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( UsersViewStepFactory ) From 8497aad7a173500d67fa3e28dbf609bf42caa9b6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 15:27:41 +0200 Subject: [PATCH 03/19] [users] Apply coding style --- src/modules/users/CheckPWQuality.cpp | 8 ++++---- src/modules/users/PasswordTests.cpp | 4 ++-- src/modules/users/Tests.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/users/CheckPWQuality.cpp b/src/modules/users/CheckPWQuality.cpp index ab3eed84f..f06137c9b 100644 --- a/src/modules/users/CheckPWQuality.cpp +++ b/src/modules/users/CheckPWQuality.cpp @@ -55,7 +55,7 @@ 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 ) ) ); } } @@ -71,7 +71,7 @@ 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 ) ) ); } } @@ -349,8 +349,8 @@ DEFINE_CHECK_FUNC( libpwquality ) /* Something actually added? */ if ( requirement_count ) { - checks.push_back( PasswordCheck( [ settings ]() { return settings->explanation(); }, - [ settings ]( const QString& s ) { + checks.push_back( PasswordCheck( [settings]() { return settings->explanation(); }, + [settings]( const QString& s ) { int r = settings->check( s ); if ( r < 0 ) { diff --git a/src/modules/users/PasswordTests.cpp b/src/modules/users/PasswordTests.cpp index b33526162..0c1b4bffc 100644 --- a/src/modules/users/PasswordTests.cpp +++ b/src/modules/users/PasswordTests.cpp @@ -24,9 +24,9 @@ QTEST_GUILESS_MAIN( PasswordTests ) -PasswordTests::PasswordTests() { } +PasswordTests::PasswordTests() {} -PasswordTests::~PasswordTests() { } +PasswordTests::~PasswordTests() {} void PasswordTests::initTestCase() diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 75c5e6d5f..196fd9d68 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -37,7 +37,7 @@ class UsersTests : public QObject Q_OBJECT public: UsersTests(); - virtual ~UsersTests() { } + virtual ~UsersTests() {} private Q_SLOTS: void initTestCase(); From 2f786079f30e75b4c999e8f109e4e650750777af Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 15:39:19 +0200 Subject: [PATCH 04/19] [users] Move shell settings to the Config object - this is a set-only property (as far as the current ViewStep is concerned) and is passed around in GS for non-obvious reasons. --- src/modules/users/Config.cpp | 27 ++++++++++++++++++++++++++- src/modules/users/Config.h | 29 ++++++++++++++++++++++++++++- src/modules/users/UsersViewStep.cpp | 9 --------- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index fb69298f6..58e2b76ac 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -20,6 +20,11 @@ #include "Config.h" +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + Config::Config( QObject* parent ) : QObject( parent ) { @@ -28,6 +33,26 @@ Config::Config( QObject* parent ) Config::~Config() {} void -Config::setConfigurationMap( const QVariantMap& ) +Config::setUserShell( const QString& shell ) +{ + if ( !shell.isEmpty() && !shell.startsWith( '/' ) ) + { + cWarning() << "User shell" << shell << "is not an absolute path."; + return; + } + // The shell is put into GS because the CreateUser job expects it there + Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell ); +} + + +void +Config::setConfigurationMap( const QVariantMap& configurationMap ) { + QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all + if ( configurationMap.contains( "userShell" ) ) + { + shell = CalamaresUtils::getString( configurationMap, "userShell" ); + } + // Now it might be explicitly set to empty, which is ok + setUserShell( shell ); } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 95c8c33bd..27416e88d 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -26,12 +26,39 @@ class Config : public QObject { + Q_OBJECT + + Q_PROPERTY( QString userShell READ userShell WRITE setUserShell NOTIFY userShellChanged ) + public: Config( QObject* parent = nullptr ); ~Config(); - // Currently, config does nothing void setConfigurationMap( const QVariantMap& ); + + /** @brief Full path to the user's shell executable + * + * Typically this will be /bin/bash, but it can be set from + * the config file with the *userShell* setting. + */ + QString userShell() const { return m_userShell; } + +public Q_SLOTS: + /** @brief Sets the user's shell if possible + * + * If the path is empty, that's ok: no shell will be explicitly set, + * so the user will get whatever shell is set to default in the target. + * + * The given non-empty @p path must be an absolute path (for use inside + * the target system!); if it is not, the shell is not changed. + */ + void setUserShell( const QString& path ); + +signals: + void userShellChanged( const QString& ); + +private: + QString m_userShell; }; #endif diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index 3fc86eb91..e037902ab 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -209,15 +209,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) m_widget->setPasswordCheckboxVisible( getBool( configurationMap, "allowWeakPasswords", false ) ); m_widget->setValidatePasswordDefault( !getBool( configurationMap, "allowWeakPasswordsDefault", false ) ); - QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all - if ( configurationMap.contains( "userShell" ) ) - { - shell = CalamaresUtils::getString( configurationMap, "userShell" ); - } - // Now it might be explicitly set to empty, which is ok - - Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell ); - using Action = SetHostNameJob::Action; QString hostnameActionString = CalamaresUtils::getString( configurationMap, "setHostname" ); From 35916eb20ff8ea351cba4047932ce52aeec62e15 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 16:39:13 +0200 Subject: [PATCH 05/19] [users] Move autologin and sudoers groups to Config --- src/modules/users/Config.cpp | 28 ++++++++++++++++++++++++++++ src/modules/users/Config.h | 17 +++++++++++++++++ src/modules/users/UsersViewStep.cpp | 14 -------------- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 58e2b76ac..a071da8fc 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -44,6 +44,31 @@ Config::setUserShell( const QString& shell ) Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell ); } +static inline void +setGS( const QString& key, const QString& group ) +{ + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( !gs || group.isEmpty() ) + { + return; + } + gs->insert( key, group ); +} + +void +Config::setAutologinGroup( const QString& group ) +{ + setGS( QStringLiteral( "autologinGroup" ), group ); + emit autologinGroupChanged( group ); +} + +void +Config::setSudoersGroup( const QString& group ) +{ + setGS( QStringLiteral( "sudoersGroup" ), group ); + emit sudoersGroupChanged( group ); +} + void Config::setConfigurationMap( const QVariantMap& configurationMap ) @@ -55,4 +80,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) } // Now it might be explicitly set to empty, which is ok setUserShell( shell ); + + setAutologinGroup( CalamaresUtils::getString( configurationMap, "autologinGroup" ) ); + setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) ); } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 27416e88d..b399c6141 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -30,6 +30,9 @@ class Config : public QObject Q_PROPERTY( QString userShell READ userShell WRITE setUserShell NOTIFY userShellChanged ) + Q_PROPERTY( QString autologinGroup READ autologinGroup WRITE setAutologinGroup NOTIFY autologinGroupChanged ) + Q_PROPERTY( QString sudoersGroup READ sudoersGroup WRITE setSudoersGroup NOTIFY sudoersGroupChanged ) + public: Config( QObject* parent = nullptr ); ~Config(); @@ -43,6 +46,11 @@ public: */ QString userShell() const { return m_userShell; } + /// The group of which auto-login users must be a member + QString autologinGroup() const { return m_autologinGroup; } + /// The group of which users who can "sudo" must be a member + QString sudoersGroup() const { return m_sudoersGroup; } + public Q_SLOTS: /** @brief Sets the user's shell if possible * @@ -54,11 +62,20 @@ public Q_SLOTS: */ void setUserShell( const QString& path ); + /// Sets the autologin group; empty is ignored + void setAutologinGroup( const QString& group ); + /// Sets the sudoer group; empty is ignored + void setSudoersGroup( const QString& group ); + signals: void userShellChanged( const QString& ); + void autologinGroupChanged( const QString& ); + void sudoersGroupChanged( const QString& ); private: QString m_userShell; + QString m_autologinGroup; + QString m_sudoersGroup; }; #endif diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index e037902ab..745163c2c 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -174,20 +174,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) m_defaultGroups = QStringList { "lp", "video", "network", "storage", "wheel", "audio" }; } - if ( configurationMap.contains( "autologinGroup" ) - && configurationMap.value( "autologinGroup" ).type() == QVariant::String ) - { - Calamares::JobQueue::instance()->globalStorage()->insert( - "autologinGroup", configurationMap.value( "autologinGroup" ).toString() ); - } - - if ( configurationMap.contains( "sudoersGroup" ) - && configurationMap.value( "sudoersGroup" ).type() == QVariant::String ) - { - Calamares::JobQueue::instance()->globalStorage()->insert( "sudoersGroup", - configurationMap.value( "sudoersGroup" ).toString() ); - } - bool setRootPassword = getBool( configurationMap, "setRootPassword", true ); Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", setRootPassword ); From 66ae1823a5ca7c99572d4f1f5cf27a143d050bce Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 25 Jul 2020 16:54:15 +0200 Subject: [PATCH 06/19] [users] Give Config object a user and login name - This is incomplete, because the business logic of guessing a login from the username is not here. --- src/modules/users/Config.cpp | 21 +++++++++++++++++++++ src/modules/users/Config.h | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index a071da8fc..856d27684 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -70,6 +70,27 @@ Config::setSudoersGroup( const QString& group ) } +void +Config::setLoginName( const QString& login ) +{ + if ( login != m_loginName ) + { + m_loginName = login; + emit loginNameChanged( login ); + } +} + +void +Config::setUserName( const QString& name ) +{ + if ( name != m_fullName ) + { + m_fullName = name; + emit userNameChanged( name ); + } +} + + void Config::setConfigurationMap( const QVariantMap& configurationMap ) { diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index b399c6141..32e51a309 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -33,6 +33,9 @@ class Config : public QObject Q_PROPERTY( QString autologinGroup READ autologinGroup WRITE setAutologinGroup NOTIFY autologinGroupChanged ) Q_PROPERTY( QString sudoersGroup READ sudoersGroup WRITE setSudoersGroup NOTIFY sudoersGroupChanged ) + Q_PROPERTY( QString userName READ userName WRITE setUserName NOTIFY userNameChanged ) + Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) + public: Config( QObject* parent = nullptr ); ~Config(); @@ -51,6 +54,11 @@ public: /// The group of which users who can "sudo" must be a member QString sudoersGroup() const { return m_sudoersGroup; } + /// The full (GECOS) name of the user + QString userName() const { return m_fullName; } + /// The login name of the user + QString loginName() const { return m_loginName; } + public Q_SLOTS: /** @brief Sets the user's shell if possible * @@ -67,15 +75,24 @@ public Q_SLOTS: /// Sets the sudoer group; empty is ignored void setSudoersGroup( const QString& group ); + /// Sets the full name, may guess a loginName + void setUserName( const QString& name ); + /// Sets the login name + void setLoginName( const QString& login ); + signals: void userShellChanged( const QString& ); void autologinGroupChanged( const QString& ); void sudoersGroupChanged( const QString& ); + void userNameChanged( const QString& ); + void loginNameChanged( const QString& ); private: QString m_userShell; QString m_autologinGroup; QString m_sudoersGroup; + QString m_fullName; + QString m_loginName; }; #endif From 411a202ba5be1546e83e56faaefcff9e59444a30 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 15:34:59 +0200 Subject: [PATCH 07/19] [users] Do some login-name guessing --- src/modules/users/Config.cpp | 41 ++++++++++++++++++++++++++++++++++++ src/modules/users/Config.h | 3 ++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 856d27684..ac01f9933 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -23,8 +23,11 @@ #include "GlobalStorage.h" #include "JobQueue.h" #include "utils/Logger.h" +#include "utils/String.h" #include "utils/Variant.h" +#include + Config::Config( QObject* parent ) : QObject( parent ) { @@ -75,11 +78,34 @@ Config::setLoginName( const QString& login ) { if ( login != m_loginName ) { + m_customLoginName = !login.isEmpty(); m_loginName = login; emit loginNameChanged( login ); } } +static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); + +static QString +makeLoginNameSuggestion( const QStringList& parts ) +{ + if ( parts.isEmpty() ) + { + return QString(); + } + + QString usernameSuggestion = parts.first(); + for ( int i = 1; i < parts.length(); ++i ) + { + if ( !parts.value( i ).isEmpty() ) + { + usernameSuggestion.append( parts.value( i ).at( 0 ) ); + } + } + + return USERNAME_RX.indexIn( usernameSuggestion ) != -1 ? usernameSuggestion : QString(); +} + void Config::setUserName( const QString& name ) { @@ -87,6 +113,21 @@ Config::setUserName( const QString& name ) { m_fullName = name; emit userNameChanged( name ); + + // Build login and hostname, if needed + QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive ); + QString cleanName = CalamaresUtils::removeDiacritics( name ).toLower().replace( rx, " " ).simplified(); + QStringList cleanParts = cleanName.split( ' ' ); + + if ( !m_customLoginName ) + { + QString login = makeLoginNameSuggestion( cleanParts ); + if ( !login.isEmpty() && login != m_loginName ) + { + m_loginName = login; + emit loginNameChanged( login ); + } + } } } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 32e51a309..6aed5840f 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -77,7 +77,7 @@ public Q_SLOTS: /// Sets the full name, may guess a loginName void setUserName( const QString& name ); - /// Sets the login name + /// Sets the login name (flags it as "custom") void setLoginName( const QString& login ); signals: @@ -93,6 +93,7 @@ private: QString m_sudoersGroup; QString m_fullName; QString m_loginName; + bool m_customLoginName = false; }; #endif From 5ffa09000a0b880d6ebcb808cc91733d0a21e8af Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 15:54:52 +0200 Subject: [PATCH 08/19] [users] Add hostname guessing to Config --- src/modules/users/Config.cpp | 73 ++++++++++++++++++++++++++++++++++-- src/modules/users/Config.h | 11 ++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index ac01f9933..9deaef81f 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -26,6 +26,7 @@ #include "utils/String.h" #include "utils/Variant.h" +#include #include Config::Config( QObject* parent ) @@ -84,12 +85,56 @@ Config::setLoginName( const QString& login ) } } -static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); +void +Config::setHostName( const QString& host ) +{ + if ( host != m_hostName ) + { + m_customHostName = !host.isEmpty(); + m_hostName = host; + emit hostNameChanged( host ); + } +} + + +/** @brief Guess the machine's name + * + * If there is DMI data, use that; otherwise, just call the machine "-pc". + * Reads the DMI data just once. + */ +static QString +guessProductName() +{ + static bool tried = false; + static QString dmiProduct; + + if ( !tried ) + { + // yes validateHostnameText() but these files can be a mess + QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive ); + QFile dmiFile( QStringLiteral( "/sys/devices/virtual/dmi/id/product_name" ) ); + + if ( dmiFile.exists() && dmiFile.open( QIODevice::ReadOnly ) ) + { + dmiProduct = QString::fromLocal8Bit( dmiFile.readAll().simplified().data() ) + .toLower() + .replace( dmirx, " " ) + .remove( ' ' ); + } + if ( dmiProduct.isEmpty() ) + { + dmiProduct = QStringLiteral( "-pc" ); + } + tried = true; + } + return dmiProduct; +} static QString makeLoginNameSuggestion( const QStringList& parts ) { - if ( parts.isEmpty() ) + static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); + if ( parts.isEmpty() || parts.first().isEmpty() ) { return QString(); } @@ -106,6 +151,20 @@ makeLoginNameSuggestion( const QStringList& parts ) return USERNAME_RX.indexIn( usernameSuggestion ) != -1 ? usernameSuggestion : QString(); } +static QString +makeHostnameSuggestion( const QStringList& parts ) +{ + static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); + if ( parts.isEmpty() || parts.first().isEmpty() ) + { + return QString(); + } + + QString productName = guessProductName(); + QString hostnameSuggestion = QStringLiteral( "%1-%2" ).arg( parts.first() ).arg( productName ); + return HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 ? hostnameSuggestion : QString(); +} + void Config::setUserName( const QString& name ) { @@ -128,10 +187,18 @@ Config::setUserName( const QString& name ) emit loginNameChanged( login ); } } + if ( !m_customHostName ) + { + QString hostname = makeHostnameSuggestion( cleanParts ); + if ( !hostname.isEmpty() && hostname != m_hostName ) + { + m_hostName = hostname; + emit hostNameChanged( hostname ); + } + } } } - void Config::setConfigurationMap( const QVariantMap& configurationMap ) { diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 6aed5840f..59f0e8d68 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -36,6 +36,8 @@ class Config : public QObject Q_PROPERTY( QString userName READ userName WRITE setUserName NOTIFY userNameChanged ) Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) + Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) + public: Config( QObject* parent = nullptr ); ~Config(); @@ -59,6 +61,9 @@ public: /// The login name of the user QString loginName() const { return m_loginName; } + /// The host name (name for the system) + QString hostName() const { return m_hostName; } + public Q_SLOTS: /** @brief Sets the user's shell if possible * @@ -80,12 +85,16 @@ public Q_SLOTS: /// Sets the login name (flags it as "custom") void setLoginName( const QString& login ); + /// Sets the host name (flags it as "custom") + void setHostName( const QString& host ); + signals: void userShellChanged( const QString& ); void autologinGroupChanged( const QString& ); void sudoersGroupChanged( const QString& ); void userNameChanged( const QString& ); void loginNameChanged( const QString& ); + void hostNameChanged( const QString& ); private: QString m_userShell; @@ -93,7 +102,9 @@ private: QString m_sudoersGroup; QString m_fullName; QString m_loginName; + QString m_hostName; bool m_customLoginName = false; + bool m_customHostName = false; }; #endif From 8a14cc7ffc1616dd254456190ab53e23465c6809 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 16:09:29 +0200 Subject: [PATCH 09/19] [users] Move some configuration from Page to Config object - make the HostName textbox just a view on the Config's HostName - make the username and login textboxes view onto Config - query the Config rather than the UI for job data --- src/modules/users/Config.cpp | 2 + src/modules/users/UsersPage.cpp | 135 ++++------------------------ src/modules/users/UsersPage.h | 7 -- src/modules/users/UsersViewStep.cpp | 2 +- src/modules/users/page_usersetup.ui | 4 +- 5 files changed, 24 insertions(+), 126 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 9deaef81f..24b14faf1 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -168,6 +168,8 @@ makeHostnameSuggestion( const QStringList& parts ) void Config::setUserName( const QString& name ) { + // TODO: handle "empty" case + // TODO: rename to "FullName" if ( name != m_fullName ) { m_fullName = name; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 62b8920e7..60afa1bb3 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -24,9 +24,9 @@ */ #include "UsersPage.h" - #include "ui_page_usersetup.h" +#include "Config.h" #include "CreateUserJob.h" #include "SetHostNameJob.h" #include "SetPasswordJob.h" @@ -34,7 +34,6 @@ #include "GlobalStorage.h" #include "JobQueue.h" #include "Settings.h" - #include "utils/CalamaresUtilsGui.h" #include "utils/Logger.h" #include "utils/Retranslator.h" @@ -94,8 +93,6 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) // Connect signals and slots connect( ui->textBoxFullName, &QLineEdit::textEdited, this, &UsersPage::onFullNameTextEdited ); - connect( ui->textBoxUsername, &QLineEdit::textEdited, this, &UsersPage::onUsernameTextEdited ); - connect( ui->textBoxHostname, &QLineEdit::textEdited, this, &UsersPage::onHostnameTextEdited ); connect( ui->textBoxUserPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged ); connect( ui->textBoxUserVerifiedPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged ); connect( ui->textBoxRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged ); @@ -124,8 +121,13 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) checkReady( isReady() ); } ); - m_customUsername = false; - m_customHostname = false; + connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName); + connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText ); + connect( config, &Config::hostNameChanged, this, &UsersPage::validateHostnameText ); + + connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName ); + connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText ); + connect( config, &Config::loginNameChanged, this, &UsersPage::validateUsernameText ); setWriteRootPassword( true ); ui->checkBoxReusePassword->setChecked( true ); @@ -147,13 +149,13 @@ UsersPage::retranslate() ui->retranslateUi( this ); if ( Calamares::Settings::instance()->isSetupMode() ) { - ui->textBoxUsername->setToolTip( tr( "If more than one person will " + ui->textBoxLoginName->setToolTip( tr( "If more than one person will " "use this computer, you can create multiple " "accounts after setup." ) ); } else { - ui->textBoxUsername->setToolTip( tr( "If more than one person will " + ui->textBoxLoginName->setToolTip( tr( "If more than one person will " "use this computer, you can create multiple " "accounts after installation." ) ); } @@ -177,12 +179,6 @@ UsersPage::isReady() return readyFields && m_readyRootPassword; } -QString -UsersPage::getHostname() const -{ - return ui->textBoxHostname->text(); -} - QString UsersPage::getRootPassword() const { @@ -206,7 +202,7 @@ UsersPage::getRootPassword() const QPair< QString, QString > UsersPage::getUserPassword() const { - return QPair< QString, QString >( ui->textBoxUsername->text(), ui->textBoxUserPassword->text() ); + return QPair< QString, QString >( m_config->loginName(), ui->textBoxUserPassword->text() ); } QList< Calamares::job_ptr > @@ -221,9 +217,9 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); Calamares::Job* j; - j = new CreateUserJob( ui->textBoxUsername->text(), - ui->textBoxFullName->text().isEmpty() ? ui->textBoxUsername->text() - : ui->textBoxFullName->text(), + j = new CreateUserJob( m_config->loginName(), + m_config->userName().isEmpty() ? m_config->loginName() + : m_config->userName(), ui->checkBoxAutoLogin->isChecked(), defaultGroupsList ); list.append( Calamares::job_ptr( j ) ); @@ -232,13 +228,13 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) { gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() ); } - gs->insert( "hostname", ui->textBoxHostname->text() ); + gs->insert( "hostname", m_config->hostName() ); if ( ui->checkBoxAutoLogin->isChecked() ) { - gs->insert( "autologinUser", ui->textBoxUsername->text() ); + gs->insert( "autologinUser", m_config->loginName() ); } - gs->insert( "username", ui->textBoxUsername->text() ); + gs->insert( "username", m_config->loginName() ); gs->insert( "password", CalamaresUtils::obscure( ui->textBoxUserPassword->text() ) ); return list; @@ -269,6 +265,7 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) { ui->labelFullNameError->clear(); ui->labelFullName->clear(); +#if 0 if ( !m_customUsername ) { ui->textBoxUsername->clear(); @@ -277,6 +274,7 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) { ui->textBoxHostname->clear(); } +#endif m_readyFullName = false; } else @@ -284,97 +282,10 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) ui->labelFullName->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, ui->labelFullName->size() ) ); m_readyFullName = true; - fillSuggestions(); } checkReady( isReady() ); } -/** @brief Guess the machine's name - * - * If there is DMI data, use that; otherwise, just call the machine "-pc". - * Reads the DMI data just once. - */ -static QString -guessProductName() -{ - static bool tried = false; - static QString dmiProduct; - - if ( !tried ) - { - // yes validateHostnameText() but these files can be a mess - QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive ); - QFile dmiFile( QStringLiteral( "/sys/devices/virtual/dmi/id/product_name" ) ); - - if ( dmiFile.exists() && dmiFile.open( QIODevice::ReadOnly ) ) - { - dmiProduct = QString::fromLocal8Bit( dmiFile.readAll().simplified().data() ) - .toLower() - .replace( dmirx, " " ) - .remove( ' ' ); - } - if ( dmiProduct.isEmpty() ) - { - dmiProduct = QStringLiteral( "-pc" ); - } - tried = true; - } - return dmiProduct; -} - -void -UsersPage::fillSuggestions() -{ - QString fullName = ui->textBoxFullName->text(); - QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive ); - QString cleanName = CalamaresUtils::removeDiacritics( fullName ).toLower().replace( rx, " " ).simplified(); - QStringList cleanParts = cleanName.split( ' ' ); - - if ( !m_customUsername ) - { - if ( !cleanParts.isEmpty() && !cleanParts.first().isEmpty() ) - { - QString usernameSuggestion = cleanParts.first(); - for ( int i = 1; i < cleanParts.length(); ++i ) - { - if ( !cleanParts.value( i ).isEmpty() ) - { - usernameSuggestion.append( cleanParts.value( i ).at( 0 ) ); - } - } - if ( USERNAME_RX.indexIn( usernameSuggestion ) != -1 ) - { - ui->textBoxUsername->setText( usernameSuggestion ); - validateUsernameText( usernameSuggestion ); - m_customUsername = false; - } - } - } - - if ( !m_customHostname ) - { - if ( !cleanParts.isEmpty() && !cleanParts.first().isEmpty() ) - { - QString hostnameSuggestion; - QString productName = guessProductName(); - hostnameSuggestion = QString( "%1-%2" ).arg( cleanParts.first() ).arg( productName ); - if ( HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 ) - { - ui->textBoxHostname->setText( hostnameSuggestion ); - validateHostnameText( hostnameSuggestion ); - m_customHostname = false; - } - } - } -} - - -void -UsersPage::onUsernameTextEdited( const QString& textRef ) -{ - m_customUsername = true; - validateUsernameText( textRef ); -} void @@ -427,14 +338,6 @@ UsersPage::validateUsernameText( const QString& textRef ) } -void -UsersPage::onHostnameTextEdited( const QString& textRef ) -{ - m_customHostname = true; - validateHostnameText( textRef ); -} - - void UsersPage::validateHostnameText( const QString& textRef ) { diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index a13886de6..ac5701b2d 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -65,8 +65,6 @@ public: */ void addPasswordCheck( const QString& key, const QVariant& value ); - ///@brief Hostname as entered / auto-filled - QString getHostname() const; ///@brief Root password, depends on settings, may be empty QString getRootPassword() const; ///@brief User name and password @@ -74,10 +72,7 @@ public: protected slots: void onFullNameTextEdited( const QString& ); - void fillSuggestions(); - void onUsernameTextEdited( const QString& ); void validateUsernameText( const QString& ); - void onHostnameTextEdited( const QString& ); void validateHostnameText( const QString& ); void onPasswordTextChanged( const QString& ); void onRootPasswordTextChanged( const QString& ); @@ -104,9 +99,7 @@ private: bool m_readyFullName; bool m_readyUsername; - bool m_customUsername; bool m_readyHostname; - bool m_customHostname; bool m_readyPassword; bool m_readyRootPassword; diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index 745163c2c..be6d61878 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -153,7 +153,7 @@ UsersViewStep::onLeave() j = new SetPasswordJob( "root", m_widget->getRootPassword() ); m_jobs.append( Calamares::job_ptr( j ) ); - j = new SetHostNameJob( m_widget->getHostname(), m_actions ); + j = new SetHostNameJob( m_config->hostName(), m_actions ); m_jobs.append( Calamares::job_ptr( j ) ); } diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui index b778647d8..4aefa9981 100644 --- a/src/modules/users/page_usersetup.ui +++ b/src/modules/users/page_usersetup.ui @@ -127,7 +127,7 @@ - + 0 @@ -226,7 +226,7 @@ - + 0 From 630a50804973d103f5ed70cfeb2d2a604bc0fa82 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 17:26:07 +0200 Subject: [PATCH 10/19] [users] Hack - create the widget anyway - since the configuration is in the UI parts, we need the widget still to load the whole configuration (until the config object is complete). Create the widget before doing configuration; this is wrong. But now we don't hit nullptr derefs all over. --- src/modules/users/UsersViewStep.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index be6d61878..03256d419 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -161,6 +161,8 @@ UsersViewStep::onLeave() void UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { + // Create the widget, after all .. as long as writing configuration to the UI is needed + (void)this->widget(); using CalamaresUtils::getBool; if ( configurationMap.contains( "defaultGroups" ) From d4a784f52186e5310440095a91506a9aae5ccf68 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 17:52:46 +0200 Subject: [PATCH 11/19] [users] Hook up full name to Config --- src/modules/users/Config.cpp | 20 +++++++++++++++---- src/modules/users/Config.h | 8 ++++---- src/modules/users/UsersPage.cpp | 34 +++++++++++---------------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 24b14faf1..d4fdb16c5 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -166,14 +166,26 @@ makeHostnameSuggestion( const QStringList& parts ) } void -Config::setUserName( const QString& name ) +Config::setFullName( const QString& name ) { - // TODO: handle "empty" case - // TODO: rename to "FullName" + if ( name.isEmpty() && !m_fullName.isEmpty() ) + { + if ( !m_customHostName ) + { + setHostName( name ); + } + if ( !m_customLoginName ) + { + setLoginName( name ); + } + m_fullName = name; + emit fullNameChanged( name ); + } + if ( name != m_fullName ) { m_fullName = name; - emit userNameChanged( name ); + emit fullNameChanged( name ); // Build login and hostname, if needed QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive ); diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 59f0e8d68..6a5ff0105 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -33,7 +33,7 @@ class Config : public QObject Q_PROPERTY( QString autologinGroup READ autologinGroup WRITE setAutologinGroup NOTIFY autologinGroupChanged ) Q_PROPERTY( QString sudoersGroup READ sudoersGroup WRITE setSudoersGroup NOTIFY sudoersGroupChanged ) - Q_PROPERTY( QString userName READ userName WRITE setUserName NOTIFY userNameChanged ) + Q_PROPERTY( QString fullName READ fullName WRITE setFullName NOTIFY fullNameChanged ) Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) @@ -57,7 +57,7 @@ public: QString sudoersGroup() const { return m_sudoersGroup; } /// The full (GECOS) name of the user - QString userName() const { return m_fullName; } + QString fullName() const { return m_fullName; } /// The login name of the user QString loginName() const { return m_loginName; } @@ -81,7 +81,7 @@ public Q_SLOTS: void setSudoersGroup( const QString& group ); /// Sets the full name, may guess a loginName - void setUserName( const QString& name ); + void setFullName( const QString& name ); /// Sets the login name (flags it as "custom") void setLoginName( const QString& login ); @@ -92,7 +92,7 @@ signals: void userShellChanged( const QString& ); void autologinGroupChanged( const QString& ); void sudoersGroupChanged( const QString& ); - void userNameChanged( const QString& ); + void fullNameChanged( const QString& ); void loginNameChanged( const QString& ); void hostNameChanged( const QString& ); diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 60afa1bb3..ee2719091 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -92,7 +92,6 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) ui->setupUi( this ); // Connect signals and slots - connect( ui->textBoxFullName, &QLineEdit::textEdited, this, &UsersPage::onFullNameTextEdited ); connect( ui->textBoxUserPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged ); connect( ui->textBoxUserVerifiedPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged ); connect( ui->textBoxRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged ); @@ -121,7 +120,10 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) checkReady( isReady() ); } ); - connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName); + connect( ui->textBoxFullName, &QLineEdit::textEdited, config, &Config::setFullName ); + connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited ); + + connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName ); connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText ); connect( config, &Config::hostNameChanged, this, &UsersPage::validateHostnameText ); @@ -150,14 +152,14 @@ UsersPage::retranslate() if ( Calamares::Settings::instance()->isSetupMode() ) { ui->textBoxLoginName->setToolTip( tr( "If more than one person will " - "use this computer, you can create multiple " - "accounts after setup." ) ); + "use this computer, you can create multiple " + "accounts after setup." ) ); } else { ui->textBoxLoginName->setToolTip( tr( "If more than one person will " - "use this computer, you can create multiple " - "accounts after installation." ) ); + "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, @@ -218,8 +220,7 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) Calamares::Job* j; j = new CreateUserJob( m_config->loginName(), - m_config->userName().isEmpty() ? m_config->loginName() - : m_config->userName(), + m_config->fullName().isEmpty() ? m_config->loginName() : m_config->fullName(), ui->checkBoxAutoLogin->isChecked(), defaultGroupsList ); list.append( Calamares::job_ptr( j ) ); @@ -265,16 +266,6 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) { ui->labelFullNameError->clear(); ui->labelFullName->clear(); -#if 0 - if ( !m_customUsername ) - { - ui->textBoxUsername->clear(); - } - if ( !m_customHostname ) - { - ui->textBoxHostname->clear(); - } -#endif m_readyFullName = false; } else @@ -287,7 +278,6 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) } - void UsersPage::validateUsernameText( const QString& textRef ) { @@ -321,11 +311,9 @@ UsersPage::validateUsernameText( const QString& textRef ) tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ) ); m_readyUsername = false; } - else if ( 0 == QString::compare("root", text, Qt::CaseSensitive ) ) + else if ( 0 == QString::compare( "root", text, Qt::CaseSensitive ) ) { - labelError( ui->labelUsername, - ui->labelUsernameError, - tr( "'root' is not allowed as user name." ) ); + labelError( ui->labelUsername, ui->labelUsernameError, tr( "'root' is not allowed as user name." ) ); m_readyUsername = false; } else From a564d7a753bafd95037cf5f75f0ca4326f3acc31 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 27 Jul 2020 17:14:06 +0200 Subject: [PATCH 12/19] [users] Fix build on Linux --- src/modules/users/CreateUserJob.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/users/CreateUserJob.cpp b/src/modules/users/CreateUserJob.cpp index beb454ac0..a6812dd53 100644 --- a/src/modules/users/CreateUserJob.cpp +++ b/src/modules/users/CreateUserJob.cpp @@ -121,10 +121,10 @@ createUser( const QString& loginName, const QString& fullName, const QString& sh << "-U"; if ( !shell.isEmpty() ) { - useradd << "-s" << shell; + useraddCommand << "-s" << shell; } - useradd << "-c" << fullName; - useradd << loginName; + useraddCommand << "-c" << fullName; + useraddCommand << loginName; #endif auto commandResult = CalamaresUtils::System::instance()->targetEnvCommand( useraddCommand ); From 40d7d1baac94703063d6ef57c4d515d17373fd68 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 10:21:23 +0200 Subject: [PATCH 13/19] [users] Move login validation to Config object - add a loginNameStatus which is a QString (empty if things are ok) stating what's wrong with the loginName, if anything. --- src/modules/users/Config.cpp | 58 ++++++++++++++++++++++++++++++--- src/modules/users/Config.h | 6 ++++ src/modules/users/UsersPage.cpp | 57 +++++++++----------------------- src/modules/users/UsersPage.h | 2 +- 4 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index d4fdb16c5..03ae60b2f 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -28,6 +28,10 @@ #include #include +#include + +static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); +static constexpr const int USERNAME_MAX_LENGTH = 31; Config::Config( QObject* parent ) : QObject( parent ) @@ -49,7 +53,7 @@ Config::setUserShell( const QString& shell ) } static inline void -setGS( const QString& key, const QString& group ) +insertInGlobalStorage( const QString& key, const QString& group ) { auto* gs = Calamares::JobQueue::instance()->globalStorage(); if ( !gs || group.isEmpty() ) @@ -62,14 +66,14 @@ setGS( const QString& key, const QString& group ) void Config::setAutologinGroup( const QString& group ) { - setGS( QStringLiteral( "autologinGroup" ), group ); + insertInGlobalStorage( QStringLiteral( "autologinGroup" ), group ); emit autologinGroupChanged( group ); } void Config::setSudoersGroup( const QString& group ) { - setGS( QStringLiteral( "sudoersGroup" ), group ); + insertInGlobalStorage( QStringLiteral( "sudoersGroup" ), group ); emit sudoersGroupChanged( group ); } @@ -82,7 +86,53 @@ Config::setLoginName( const QString& login ) m_customLoginName = !login.isEmpty(); m_loginName = login; emit loginNameChanged( login ); + emit loginNameStatusChanged( loginNameStatus() ); + } +} + +const QStringList& +Config::forbiddenLoginNames() +{ + static QStringList forbidden { "root" }; + return forbidden; +} + +QString +Config::loginNameStatus() const +{ + // An empty login is "ok", even if it isn't really + if ( m_loginName.isEmpty() ) + { + return QString(); + } + + QRegExpValidator validateEntireLoginName( USERNAME_RX ); + QRegExpValidator validateFirstLetter( QRegExp( "[a-z_].*" ) ); // anchors are implicit in QRegExpValidator + int pos = -1; + + if ( m_loginName.length() > USERNAME_MAX_LENGTH ) + { + return tr( "Your username is too long." ); + } + QString login( m_loginName ); // make a copy because validate() doesn't take const& + if ( validateFirstLetter.validate( login, pos ) == QValidator::Invalid ) + { + return tr( "Your username must start with a lowercase letter or underscore." ); + } + if ( validateEntireLoginName.validate( login, pos ) == QValidator::Invalid ) + { + return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ); } + + for ( const QString& badName : forbiddenLoginNames() ) + { + if ( 0 == QString::compare( badName, m_loginName, Qt::CaseSensitive ) ) + { + return tr( "'%1' is not allowed as user name." ).arg( badName ); + } + } + + return QString(); } void @@ -133,7 +183,6 @@ guessProductName() static QString makeLoginNameSuggestion( const QStringList& parts ) { - static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); if ( parts.isEmpty() || parts.first().isEmpty() ) { return QString(); @@ -199,6 +248,7 @@ Config::setFullName( const QString& name ) { m_loginName = login; emit loginNameChanged( login ); + emit loginNameStatusChanged( loginNameStatus() ); } } if ( !m_customHostName ) diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 6a5ff0105..91a494619 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -35,6 +35,7 @@ class Config : public QObject Q_PROPERTY( QString fullName READ fullName WRITE setFullName NOTIFY fullNameChanged ) Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) + Q_PROPERTY( QString loginNameStatus READ loginNameStatus NOTIFY loginNameStatusChanged ) Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) @@ -60,10 +61,14 @@ public: QString fullName() const { return m_fullName; } /// The login name of the user QString loginName() const { return m_loginName; } + /// Status message about login -- empty for "ok" + QString loginNameStatus() const; /// The host name (name for the system) QString hostName() const { return m_hostName; } + static const QStringList& forbiddenLoginNames(); + public Q_SLOTS: /** @brief Sets the user's shell if possible * @@ -94,6 +99,7 @@ signals: void sudoersGroupChanged( const QString& ); void fullNameChanged( const QString& ); void loginNameChanged( const QString& ); + void loginNameStatusChanged( const QString& ); void hostNameChanged( const QString& ); private: diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index ee2719091..cc940502e 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -46,7 +46,6 @@ #include #include -static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); static constexpr const int USERNAME_MAX_LENGTH = 31; static constexpr const int HOSTNAME_MIN_LENGTH = 2; @@ -129,7 +128,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName ); connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText ); - connect( config, &Config::loginNameChanged, this, &UsersPage::validateUsernameText ); + connect( config, &Config::loginNameStatusChanged, this, &UsersPage::reportLoginNameStatus ); setWriteRootPassword( true ); ui->checkBoxReusePassword->setChecked( true ); @@ -277,55 +276,31 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) checkReady( isReady() ); } - void -UsersPage::validateUsernameText( const QString& textRef ) +UsersPage::reportLoginNameStatus( const QString& status ) { - QString text( textRef ); - QRegExpValidator val_whole( USERNAME_RX ); - QRegExpValidator val_start( QRegExp( "[a-z_].*" ) ); // anchors are implicit in QRegExpValidator - int pos = -1; - - if ( text.isEmpty() ) - { - ui->labelUsernameError->clear(); - ui->labelUsername->clear(); - m_readyUsername = false; - } - else if ( text.length() > USERNAME_MAX_LENGTH ) - { - labelError( ui->labelUsername, ui->labelUsernameError, tr( "Your username is too long." ) ); - m_readyUsername = false; - } - else if ( val_start.validate( text, pos ) == QValidator::Invalid ) - { - labelError( ui->labelUsername, - ui->labelUsernameError, - tr( "Your username must start with a lowercase letter or underscore." ) ); - m_readyUsername = false; - } - else if ( val_whole.validate( text, pos ) == QValidator::Invalid ) + if ( status.isEmpty() ) { - labelError( ui->labelUsername, - ui->labelUsernameError, - tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ) ); - m_readyUsername = false; - } - else if ( 0 == QString::compare( "root", text, Qt::CaseSensitive ) ) - { - labelError( ui->labelUsername, ui->labelUsernameError, tr( "'root' is not allowed as user name." ) ); - m_readyUsername = false; + if ( m_config->loginName().isEmpty() ) + { + ui->labelUsernameError->clear(); + ui->labelUsername->clear(); + m_readyUsername = false; + } + else + { + labelOk( ui->labelUsername, ui->labelUsernameError ); + m_readyUsername = true; + } } else { - labelOk( ui->labelUsername, ui->labelUsernameError ); - m_readyUsername = true; + labelError( ui->labelUsername, ui->labelUsernameError, status ); + m_readyUsername = false; } - emit checkReady( isReady() ); } - void UsersPage::validateHostnameText( const QString& textRef ) { diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index ac5701b2d..7cf83100c 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -72,7 +72,7 @@ public: protected slots: void onFullNameTextEdited( const QString& ); - void validateUsernameText( const QString& ); + void reportLoginNameStatus( const QString& ); void validateHostnameText( const QString& ); void onPasswordTextChanged( const QString& ); void onRootPasswordTextChanged( const QString& ); From 9018913af53b97acb3bc6eb1a749ec78448b84e9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 10:45:38 +0200 Subject: [PATCH 14/19] [users] Move hostname validation to Config --- src/modules/users/Config.cpp | 73 ++++++++++++++++++++++----- src/modules/users/Config.h | 5 ++ src/modules/users/UsersPage.cpp | 89 +++++++++++---------------------- src/modules/users/UsersPage.h | 2 +- 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 03ae60b2f..bc1113672 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -33,6 +33,10 @@ static const QRegExp USERNAME_RX( "^[a-z_][a-z0-9_-]*[$]?$" ); static constexpr const int USERNAME_MAX_LENGTH = 31; +static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); +static constexpr const int HOSTNAME_MIN_LENGTH = 2; +static constexpr const int HOSTNAME_MAX_LENGTH = 63; + Config::Config( QObject* parent ) : QObject( parent ) { @@ -106,15 +110,22 @@ Config::loginNameStatus() const return QString(); } - QRegExpValidator validateEntireLoginName( USERNAME_RX ); - QRegExpValidator validateFirstLetter( QRegExp( "[a-z_].*" ) ); // anchors are implicit in QRegExpValidator - int pos = -1; - if ( m_loginName.length() > USERNAME_MAX_LENGTH ) { return tr( "Your username is too long." ); } + for ( const QString& badName : forbiddenLoginNames() ) + { + if ( 0 == QString::compare( badName, m_loginName, Qt::CaseSensitive ) ) + { + return tr( "'%1' is not allowed as username." ).arg( badName ); + } + } + QString login( m_loginName ); // make a copy because validate() doesn't take const& + QRegExpValidator validateEntireLoginName( USERNAME_RX ); + QRegExpValidator validateFirstLetter( QRegExp( "[a-z_].*" ) ); // anchors are implicit in QRegExpValidator + int pos = -1; if ( validateFirstLetter.validate( login, pos ) == QValidator::Invalid ) { return tr( "Your username must start with a lowercase letter or underscore." ); @@ -124,14 +135,6 @@ Config::loginNameStatus() const return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ); } - for ( const QString& badName : forbiddenLoginNames() ) - { - if ( 0 == QString::compare( badName, m_loginName, Qt::CaseSensitive ) ) - { - return tr( "'%1' is not allowed as user name." ).arg( badName ); - } - } - return QString(); } @@ -143,7 +146,52 @@ Config::setHostName( const QString& host ) m_customHostName = !host.isEmpty(); m_hostName = host; emit hostNameChanged( host ); + emit hostNameStatusChanged( hostNameStatus() ); + } +} + +const QStringList& +Config::forbiddenHostNames() +{ + static QStringList forbidden { "localhost" }; + return forbidden; +} + +QString +Config::hostNameStatus() const +{ + // An empty hostname is "ok", even if it isn't really + if ( m_hostName.isEmpty() ) + { + return QString(); + } + + if ( m_hostName.length() < HOSTNAME_MIN_LENGTH ) + { + return tr( "Your hostname is too short." ); } + if ( m_hostName.length() > HOSTNAME_MAX_LENGTH ) + { + return tr( "Your hostname is too long." ); + } + for ( const QString& badName : forbiddenHostNames() ) + { + if ( 0 == QString::compare( badName, m_hostName, Qt::CaseSensitive ) ) + { + return tr( "'%1' is not allowed as hostname." ).arg( badName ); + } + } + + QString text = m_hostName; + QRegExpValidator val( HOSTNAME_RX ); + int pos = -1; + + if ( val.validate( text, pos ) == QValidator::Invalid ) + { + return tr( "Only letters, numbers, underscore and hyphen are allowed." ); + } + + return QString(); } @@ -258,6 +306,7 @@ Config::setFullName( const QString& name ) { m_hostName = hostname; emit hostNameChanged( hostname ); + emit hostNameStatusChanged( hostNameStatus() ); } } } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 91a494619..824b70ba8 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -38,6 +38,7 @@ class Config : public QObject Q_PROPERTY( QString loginNameStatus READ loginNameStatus NOTIFY loginNameStatusChanged ) Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) + Q_PROPERTY( QString hostNameStatus READ hostNameStatus NOTIFY hostNameStatusChanged ) public: Config( QObject* parent = nullptr ); @@ -66,8 +67,11 @@ public: /// The host name (name for the system) QString hostName() const { return m_hostName; } + /// Status message about hostname -- empty for "ok" + QString hostNameStatus() const; static const QStringList& forbiddenLoginNames(); + static const QStringList& forbiddenHostNames(); public Q_SLOTS: /** @brief Sets the user's shell if possible @@ -101,6 +105,7 @@ signals: void loginNameChanged( const QString& ); void loginNameStatusChanged( const QString& ); void hostNameChanged( const QString& ); + void hostNameStatusChanged( const QString& ); private: QString m_userShell; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index cc940502e..7983e29ea 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -46,11 +46,6 @@ #include #include -static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); -static constexpr const int USERNAME_MAX_LENGTH = 31; -static constexpr const int HOSTNAME_MIN_LENGTH = 2; -static constexpr const int HOSTNAME_MAX_LENGTH = 63; - /** @brief How bad is the error for labelError() ? */ enum class Badness { @@ -77,6 +72,32 @@ labelOk( QLabel* pix, QLabel* label ) pix->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, label->size() ) ); } +/** Indicate error, update @p ok based on @p status */ +static inline void +labelStatus( QLabel* pix, QLabel* label, const QString& value, const QString& status, bool& ok ) +{ + if ( status.isEmpty() ) + { + if ( value.isEmpty() ) + { + // This is different from labelOK() because no checkmark is shown + label->clear(); + pix->clear(); + ok = false; + } + else + { + labelOk( pix, label ); + ok = true; + } + } + else + { + labelError( pix, label, status ); + ok = false; + } +} + UsersPage::UsersPage( Config* config, QWidget* parent ) : QWidget( parent ) , ui( new Ui::Page_UserSetup ) @@ -124,7 +145,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName ); connect( config, &Config::hostNameChanged, ui->textBoxHostName, &QLineEdit::setText ); - connect( config, &Config::hostNameChanged, this, &UsersPage::validateHostnameText ); + connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus ); connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName ); connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText ); @@ -279,64 +300,14 @@ UsersPage::onFullNameTextEdited( const QString& textRef ) void UsersPage::reportLoginNameStatus( const QString& status ) { - if ( status.isEmpty() ) - { - if ( m_config->loginName().isEmpty() ) - { - ui->labelUsernameError->clear(); - ui->labelUsername->clear(); - m_readyUsername = false; - } - else - { - labelOk( ui->labelUsername, ui->labelUsernameError ); - m_readyUsername = true; - } - } - else - { - labelError( ui->labelUsername, ui->labelUsernameError, status ); - m_readyUsername = false; - } + labelStatus( ui->labelUsername, ui->labelUsernameError, m_config->loginName(), status, m_readyUsername ); emit checkReady( isReady() ); } void -UsersPage::validateHostnameText( const QString& textRef ) +UsersPage::reportHostNameStatus( const QString& status ) { - QString text = textRef; - QRegExpValidator val( HOSTNAME_RX ); - int pos = -1; - - if ( text.isEmpty() ) - { - ui->labelHostnameError->clear(); - ui->labelHostname->clear(); - m_readyHostname = false; - } - else if ( text.length() < HOSTNAME_MIN_LENGTH ) - { - labelError( ui->labelHostname, ui->labelHostnameError, tr( "Your hostname is too short." ) ); - m_readyHostname = false; - } - else if ( text.length() > HOSTNAME_MAX_LENGTH ) - { - labelError( ui->labelHostname, ui->labelHostnameError, tr( "Your hostname is too long." ) ); - m_readyHostname = false; - } - else if ( val.validate( text, pos ) == QValidator::Invalid ) - { - labelError( ui->labelHostname, - ui->labelHostnameError, - tr( "Only letters, numbers, underscore and hyphen are allowed." ) ); - m_readyHostname = false; - } - else - { - labelOk( ui->labelHostname, ui->labelHostnameError ); - m_readyHostname = true; - } - + labelStatus( ui->labelHostname, ui->labelHostnameError, m_config->hostName(), status, m_readyHostname ); emit checkReady( isReady() ); } diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 7cf83100c..7e0830dc0 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -73,7 +73,7 @@ public: protected slots: void onFullNameTextEdited( const QString& ); void reportLoginNameStatus( const QString& ); - void validateHostnameText( const QString& ); + void reportHostNameStatus( const QString& ); void onPasswordTextChanged( const QString& ); void onRootPasswordTextChanged( const QString& ); From 0813ec33278381cf3bc4bcd0f3508a3514699001 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 10:49:12 +0200 Subject: [PATCH 15/19] [users] Misc cleanups - unused includes - avoid "my--pc" .. the dash is inserted by makeHostnameSuggestion() --- src/modules/users/Config.cpp | 2 +- src/modules/users/UsersPage.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index bc1113672..9a9ebe693 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -221,7 +221,7 @@ guessProductName() } if ( dmiProduct.isEmpty() ) { - dmiProduct = QStringLiteral( "-pc" ); + dmiProduct = QStringLiteral( "pc" ); } tried = true; } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 7983e29ea..af5a1391e 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -43,8 +43,6 @@ #include #include #include -#include -#include /** @brief How bad is the error for labelError() ? */ enum class Badness From 6c930af5cbe760305fb559874043d6da4f7af02e Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 11:18:07 +0200 Subject: [PATCH 16/19] [users] Use convenience method for labeling Full Name --- src/modules/users/UsersPage.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index af5a1391e..0877bf0e3 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -278,20 +278,9 @@ UsersPage::setWriteRootPassword( bool write ) void -UsersPage::onFullNameTextEdited( const QString& textRef ) +UsersPage::onFullNameTextEdited( const QString& fullName ) { - if ( textRef.isEmpty() ) - { - ui->labelFullNameError->clear(); - ui->labelFullName->clear(); - m_readyFullName = false; - } - else - { - ui->labelFullName->setPixmap( - CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, CalamaresUtils::Original, ui->labelFullName->size() ) ); - m_readyFullName = true; - } + labelStatus( ui->labelFullName, ui->labelFullNameError, fullName, QString(), m_readyFullName ); checkReady( isReady() ); } From 45b71c24e771d69d22699337bbd030590eb1ed4f Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 11:41:52 +0200 Subject: [PATCH 17/19] [users] Move autologin setting to Config --- src/modules/users/Config.cpp | 12 ++++++++++++ src/modules/users/Config.h | 11 +++++++++++ src/modules/users/UsersPage.cpp | 16 +++++++--------- src/modules/users/UsersPage.h | 1 - src/modules/users/UsersViewStep.cpp | 1 - src/modules/users/page_usersetup.ui | 2 +- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 9a9ebe693..f245aa866 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -312,6 +312,16 @@ Config::setFullName( const QString& name ) } } +void +Config::setAutoLogin( bool b ) +{ + if ( b != m_doAutoLogin ) + { + m_doAutoLogin = b; + emit autoLoginChanged( b ); + } +} + void Config::setConfigurationMap( const QVariantMap& configurationMap ) { @@ -325,4 +335,6 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) setAutologinGroup( CalamaresUtils::getString( configurationMap, "autologinGroup" ) ); setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) ); + + m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutologin", false ); } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 824b70ba8..e7ab8a736 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -33,6 +33,8 @@ class Config : public QObject Q_PROPERTY( QString autologinGroup READ autologinGroup WRITE setAutologinGroup NOTIFY autologinGroupChanged ) Q_PROPERTY( QString sudoersGroup READ sudoersGroup WRITE setSudoersGroup NOTIFY sudoersGroupChanged ) + Q_PROPERTY( bool doAutoLogin READ doAutoLogin WRITE setAutoLogin NOTIFY autoLoginChanged ) + Q_PROPERTY( QString fullName READ fullName WRITE setFullName NOTIFY fullNameChanged ) Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) Q_PROPERTY( QString loginNameStatus READ loginNameStatus NOTIFY loginNameStatusChanged ) @@ -70,6 +72,9 @@ public: /// Status message about hostname -- empty for "ok" QString hostNameStatus() const; + /// Should the user be automatically logged-in? + bool doAutoLogin() const { return m_doAutoLogin; } + static const QStringList& forbiddenLoginNames(); static const QStringList& forbiddenHostNames(); @@ -97,6 +102,9 @@ public Q_SLOTS: /// Sets the host name (flags it as "custom") void setHostName( const QString& host ); + /// Sets the autologin flag + void setAutoLogin( bool b ); + signals: void userShellChanged( const QString& ); void autologinGroupChanged( const QString& ); @@ -106,6 +114,7 @@ signals: void loginNameStatusChanged( const QString& ); void hostNameChanged( const QString& ); void hostNameStatusChanged( const QString& ); + void autoLoginChanged( bool ); private: QString m_userShell; @@ -114,6 +123,8 @@ private: QString m_fullName; QString m_loginName; QString m_hostName; + bool m_doAutoLogin = false; + bool m_customLoginName = false; bool m_customHostName = false; }; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 0877bf0e3..c9eb227e7 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -149,6 +149,11 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( config, &Config::loginNameChanged, ui->textBoxLoginName, &QLineEdit::setText ); connect( config, &Config::loginNameStatusChanged, this, &UsersPage::reportLoginNameStatus ); + connect( ui->checkBoxDoAutoLogin, &QCheckBox::stateChanged, this, [this]( int checked ) { + m_config->setAutoLogin( checked != Qt::Unchecked ); + } ); + connect( config, &Config::autoLoginChanged, ui->checkBoxDoAutoLogin, &QCheckBox::setChecked ); + setWriteRootPassword( true ); ui->checkBoxReusePassword->setChecked( true ); ui->checkBoxValidatePassword->setChecked( true ); @@ -239,7 +244,7 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) Calamares::Job* j; j = new CreateUserJob( m_config->loginName(), m_config->fullName().isEmpty() ? m_config->loginName() : m_config->fullName(), - ui->checkBoxAutoLogin->isChecked(), + m_config->doAutoLogin(), defaultGroupsList ); list.append( Calamares::job_ptr( j ) ); @@ -248,7 +253,7 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() ); } gs->insert( "hostname", m_config->hostName() ); - if ( ui->checkBoxAutoLogin->isChecked() ) + if ( m_config->doAutoLogin() ) { gs->insert( "autologinUser", m_config->loginName() ); } @@ -378,13 +383,6 @@ UsersPage::setValidatePasswordDefault( bool checked ) emit checkReady( isReady() ); } -void -UsersPage::setAutologinDefault( bool checked ) -{ - ui->checkBoxAutoLogin->setChecked( checked ); - emit checkReady( isReady() ); -} - void UsersPage::setReusePasswordDefault( bool checked ) { diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 7e0830dc0..7cd522498 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -54,7 +54,6 @@ public: void setWriteRootPassword( bool show ); void setPasswordCheckboxVisible( bool visible ); void setValidatePasswordDefault( bool checked ); - void setAutologinDefault( bool checked ); void setReusePasswordDefault( bool checked ); /** @brief Process entries in the passwordRequirements config entry diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index 03256d419..0826b8a28 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -180,7 +180,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", setRootPassword ); m_widget->setWriteRootPassword( setRootPassword ); - m_widget->setAutologinDefault( getBool( configurationMap, "doAutologin", false ) ); m_widget->setReusePasswordDefault( getBool( configurationMap, "doReusePassword", false ) ); if ( configurationMap.contains( "passwordRequirements" ) diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui index 4aefa9981..d880673b8 100644 --- a/src/modules/users/page_usersetup.ui +++ b/src/modules/users/page_usersetup.ui @@ -456,7 +456,7 @@ - + Log in automatically without asking for the password. From 6a03bcb25e19285b0aa8432857c8d6f11db1ab27 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 11:59:53 +0200 Subject: [PATCH 18/19] [users] Move setRootPassword to Config - this really controls whether a root password is written during installtion, so rename to writeRootPassword in the code. --- src/modules/users/Config.cpp | 3 +++ src/modules/users/Config.h | 3 +++ src/modules/users/UsersPage.cpp | 42 ++++++++++------------------- src/modules/users/UsersPage.h | 3 --- src/modules/users/UsersViewStep.cpp | 4 --- 5 files changed, 20 insertions(+), 35 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index f245aa866..1219c2a3c 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -337,4 +337,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) ); m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutologin", false ); + + m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true ); + Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword ); } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index e7ab8a736..d32ddc8f2 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -74,6 +74,8 @@ public: /// Should the user be automatically logged-in? bool doAutoLogin() const { return m_doAutoLogin; } + /// Should the root password be written (if false, no password is set and the root account is disabled for login) + bool writeRootPassword() const { return m_writeRootPassword; } static const QStringList& forbiddenLoginNames(); static const QStringList& forbiddenHostNames(); @@ -124,6 +126,7 @@ private: QString m_loginName; QString m_hostName; bool m_doAutoLogin = false; + bool m_writeRootPassword = true; bool m_customLoginName = false; bool m_customHostName = false; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index c9eb227e7..268e6099e 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -105,7 +105,6 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) , m_readyHostname( false ) , m_readyPassword( false ) , m_readyRootPassword( false ) - , m_writeRootPassword( true ) { ui->setupUi( this ); @@ -119,22 +118,19 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) onRootPasswordTextChanged( ui->textBoxRootPassword->text() ); checkReady( isReady() ); } ); - connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( int checked ) { + connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( const int checked ) { /* When "reuse" is checked, hide the fields for explicitly * entering the root password. However, if we're going to * disable the root password anyway, hide them all regardless of * the checkbox -- so when writeRoot is false, checked needs * to be true, to hide them all. */ - if ( !m_writeRootPassword ) - { - checked = true; - } - ui->labelChooseRootPassword->setVisible( !checked ); - ui->labelRootPassword->setVisible( !checked ); - ui->labelRootPasswordError->setVisible( !checked ); - ui->textBoxRootPassword->setVisible( !checked ); - ui->textBoxVerifiedRootPassword->setVisible( !checked ); + const bool visible = m_config->writeRootPassword() ? !checked : false; + ui->labelChooseRootPassword->setVisible( visible ); + ui->labelRootPassword->setVisible( visible ); + ui->labelRootPasswordError->setVisible( visible ); + ui->textBoxRootPassword->setVisible( visible ); + ui->textBoxVerifiedRootPassword->setVisible( visible ); checkReady( isReady() ); } ); @@ -154,7 +150,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) } ); connect( config, &Config::autoLoginChanged, ui->checkBoxDoAutoLogin, &QCheckBox::setChecked ); - setWriteRootPassword( true ); + ui->checkBoxReusePassword->setVisible( m_config->writeRootPassword() ); ui->checkBoxReusePassword->setChecked( true ); ui->checkBoxValidatePassword->setChecked( true ); @@ -196,18 +192,16 @@ bool UsersPage::isReady() { bool readyFields = m_readyFullName && m_readyHostname && m_readyPassword && m_readyUsername; - if ( !m_writeRootPassword || ui->checkBoxReusePassword->isChecked() ) - { - return readyFields; - } - - return readyFields && m_readyRootPassword; + // If we're going to write a root password, we need a valid one (or reuse the user's password) + readyFields + &= m_config->writeRootPassword() ? ( m_readyRootPassword || ui->checkBoxReusePassword->isChecked() ) : true; + return readyFields; } QString UsersPage::getRootPassword() const { - if ( m_writeRootPassword ) + if ( m_config->writeRootPassword() ) { if ( ui->checkBoxReusePassword->isChecked() ) { @@ -248,7 +242,7 @@ UsersPage::createJobs( const QStringList& defaultGroupsList ) defaultGroupsList ); list.append( Calamares::job_ptr( j ) ); - if ( m_writeRootPassword ) + if ( m_config->writeRootPassword() ) { gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() ); } @@ -274,14 +268,6 @@ UsersPage::onActivate() } -void -UsersPage::setWriteRootPassword( bool write ) -{ - m_writeRootPassword = write; - ui->checkBoxReusePassword->setVisible( write ); -} - - void UsersPage::onFullNameTextEdited( const QString& fullName ) { diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 7cd522498..35f7f0938 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -51,7 +51,6 @@ public: void onActivate(); - void setWriteRootPassword( bool show ); void setPasswordCheckboxVisible( bool visible ); void setValidatePasswordDefault( bool checked ); void setReusePasswordDefault( bool checked ); @@ -101,8 +100,6 @@ private: bool m_readyHostname; bool m_readyPassword; bool m_readyRootPassword; - - bool m_writeRootPassword; }; #endif // USERSPAGE_H diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index 0826b8a28..713811246 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -176,10 +176,6 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) m_defaultGroups = QStringList { "lp", "video", "network", "storage", "wheel", "audio" }; } - bool setRootPassword = getBool( configurationMap, "setRootPassword", true ); - Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", setRootPassword ); - - m_widget->setWriteRootPassword( setRootPassword ); m_widget->setReusePasswordDefault( getBool( configurationMap, "doReusePassword", false ) ); if ( configurationMap.contains( "passwordRequirements" ) From cc2e3f79ff045cd577ebc28e8f89c19350aadd58 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 28 Jul 2020 12:16:03 +0200 Subject: [PATCH 19/19] [users] Move job creation from widget to viewstep - This is a half-step: the ViewStep shouldn't do job creation either, eventually it needs to be the Config object, but this is better than asking the widget (UI) to create some jobs. - When updating login- or host-name, or the autologin setting, set it in GS as well. This is a minor improvement over doing it only when leaving the page. - Since the Config object isn't complete, there are leftovers in the widget, which has a fillGlobalStorage() for the not-jobs-related bits previously in createJobs(). --- src/modules/users/Config.cpp | 29 ++++++++++++++++++++++++++++ src/modules/users/UsersPage.cpp | 30 +++++------------------------ src/modules/users/UsersPage.h | 5 ++--- src/modules/users/UsersViewStep.cpp | 10 ++++++++-- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 1219c2a3c..bb739cbd1 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -87,6 +87,16 @@ Config::setLoginName( const QString& login ) { if ( login != m_loginName ) { + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( login.isEmpty() ) + { + gs->remove( "username" ); + } + else + { + gs->insert( "username", login ); + } + m_customLoginName = !login.isEmpty(); m_loginName = login; emit loginNameChanged( login ); @@ -143,6 +153,16 @@ Config::setHostName( const QString& host ) { if ( host != m_hostName ) { + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( host.isEmpty() ) + { + gs->remove( "hostname" ); + } + else + { + gs->insert( "hostname", host ); + } + m_customHostName = !host.isEmpty(); m_hostName = host; emit hostNameChanged( host ); @@ -317,6 +337,15 @@ Config::setAutoLogin( bool b ) { if ( b != m_doAutoLogin ) { + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( b ) + { + gs->insert( "autologinUser", loginName() ); + } + else + { + gs->remove( "autologinUser" ); + } m_doAutoLogin = b; emit autoLoginChanged( b ); } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 268e6099e..c4502256a 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -24,12 +24,9 @@ */ #include "UsersPage.h" -#include "ui_page_usersetup.h" #include "Config.h" -#include "CreateUserJob.h" -#include "SetHostNameJob.h" -#include "SetPasswordJob.h" +#include "ui_page_usersetup.h" #include "GlobalStorage.h" #include "JobQueue.h" @@ -189,7 +186,7 @@ UsersPage::retranslate() bool -UsersPage::isReady() +UsersPage::isReady() const { bool readyFields = m_readyFullName && m_readyHostname && m_readyPassword && m_readyUsername; // If we're going to write a root password, we need a valid one (or reuse the user's password) @@ -224,38 +221,21 @@ UsersPage::getUserPassword() const return QPair< QString, QString >( m_config->loginName(), ui->textBoxUserPassword->text() ); } -QList< Calamares::job_ptr > -UsersPage::createJobs( const QStringList& defaultGroupsList ) +void +UsersPage::fillGlobalStorage() const { - QList< Calamares::job_ptr > list; if ( !isReady() ) { - return list; + return; } Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - Calamares::Job* j; - j = new CreateUserJob( m_config->loginName(), - m_config->fullName().isEmpty() ? m_config->loginName() : m_config->fullName(), - m_config->doAutoLogin(), - defaultGroupsList ); - list.append( Calamares::job_ptr( j ) ); - if ( m_config->writeRootPassword() ) { gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() ); } - gs->insert( "hostname", m_config->hostName() ); - if ( m_config->doAutoLogin() ) - { - gs->insert( "autologinUser", m_config->loginName() ); - } - - gs->insert( "username", m_config->loginName() ); gs->insert( "password", CalamaresUtils::obscure( ui->textBoxUserPassword->text() ) ); - - return list; } diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 35f7f0938..b8cb0f06a 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -25,7 +25,6 @@ #define USERSPAGE_H #include "CheckPWQuality.h" -#include "Job.h" #include @@ -45,9 +44,9 @@ public: explicit UsersPage( Config* config, QWidget* parent = nullptr ); virtual ~UsersPage(); - bool isReady(); + bool isReady() const; - Calamares::JobList createJobs( const QStringList& defaultGroupsList ); + void fillGlobalStorage() const; void onActivate(); diff --git a/src/modules/users/UsersViewStep.cpp b/src/modules/users/UsersViewStep.cpp index 713811246..ddaf4837b 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -21,6 +21,7 @@ #include "UsersViewStep.h" #include "Config.h" +#include "CreateUserJob.h" #include "SetHostNameJob.h" #include "SetPasswordJob.h" #include "UsersPage.h" @@ -138,13 +139,16 @@ void UsersViewStep::onLeave() { m_jobs.clear(); - if ( !m_widget ) + if ( !m_widget || !m_widget->isReady() ) { return; } - m_jobs.append( m_widget->createJobs( m_defaultGroups ) ); Calamares::Job* j; + j = new CreateUserJob( m_config->loginName(), + m_config->fullName().isEmpty() ? m_config->loginName() : m_config->fullName(), + m_config->doAutoLogin(), + m_defaultGroups ); auto userPW = m_widget->getUserPassword(); j = new SetPasswordJob( userPW.first, userPW.second ); @@ -155,6 +159,8 @@ UsersViewStep::onLeave() j = new SetHostNameJob( m_config->hostName(), m_actions ); m_jobs.append( Calamares::job_ptr( j ) ); + + m_widget->fillGlobalStorage(); }