diff --git a/src/modules/users/CMakeLists.txt b/src/modules/users/CMakeLists.txt index fc1f26d0f..367f6161e 100644 --- a/src/modules/users/CMakeLists.txt +++ b/src/modules/users/CMakeLists.txt @@ -7,13 +7,16 @@ calamares_add_plugin( users TYPE viewmodule EXPORT_MACRO PLUGINDLLEXPORT_PRO SOURCES - CreateUserJob.cpp - SetPasswordJob.cpp + jobs/CreateUserJob.cpp + jobs/SetPasswordJob.cpp UsersViewStep.cpp - UsersPage.cpp - SetHostNameJob.cpp + gui/UsersPage.cpp + jobs/SetHostNameJob.cpp + jobs/SetAvatarJob.cpp + gui/AddUserDialog.cpp UI - page_usersetup.ui + gui/page_usersetup.ui + gui/adduserdialog.ui RESOURCES users.qrc LINK_LIBRARIES diff --git a/src/modules/users/README.md b/src/modules/users/README.md new file mode 100644 index 000000000..caae37275 --- /dev/null +++ b/src/modules/users/README.md @@ -0,0 +1,16 @@ +# Users module + +The users module take care of managing the users that will be created on the installed system. + +## Configuration of the module + +The following settings are available in **users.conf**: + + - ```defaultGroups```: list of groups every user will be added to. + - ```autologinGroup```: group to add the user with autologin to, if any. + - ```doAutologin```: allow for users with autologin (at most one per system). Defaults to false. + - ```sudoersGroup```: group for sudoers usage. + - ```setRootPassword```: allow to set the root password in the installed system. Defaults to false. + - ```availableShells```: comma-separated list of available shells for new users. If not present, new users will not have any explicit shell in /etc/passwd, therefore the system default (usually, /bin/bash) will be used. + - ```avatarFilePath```: path where to copy user avatars to; ~ can be used to represent the user's home directory. If not present or empty, we won't allow users to set avatars. + diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp deleted file mode 100644 index 9766843fd..000000000 --- a/src/modules/users/UsersPage.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/* === This file is part of Calamares - === - * - * Copyright 2014-2015, Teo Mrnjavac - * - * Portions from the Manjaro Installation Framework - * by Roland Singer - * Copyright (C) 2007 Free Software Foundation, Inc. - * - * 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 "UsersPage.h" -#include "ui_page_usersetup.h" -#include "CreateUserJob.h" -#include "SetPasswordJob.h" -#include "SetHostNameJob.h" -#include "JobQueue.h" -#include "GlobalStorage.h" -#include "utils/Logger.h" -#include "utils/CalamaresUtilsGui.h" -#include "utils/Retranslator.h" - -#include -#include -#include -#include -#include - - - -UsersPage::UsersPage( QWidget* parent ) - : QWidget( parent ) - , ui( new Ui::Page_UserSetup ) - , m_readyFullName( false ) - , m_readyUsername( false ) - , m_readyHostname( false ) - , m_readyPassword( false ) - , m_readyRootPassword( false ) - , m_writeRootPassword( true ) -{ - ui->setupUi( this ); - - // 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 ); - connect( ui->textBoxVerifiedRootPassword, &QLineEdit::textChanged, - this, &UsersPage::onRootPasswordTextChanged ); - connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, - this, [this]( int checked ) - { - ui->labelChooseRootPassword->setVisible( !checked ); - ui->labelExtraRootPassword->setVisible( !checked ); - ui->labelRootPassword->setVisible( !checked ); - ui->labelRootPasswordError->setVisible( !checked ); - ui->textBoxRootPassword->setVisible( !checked ); - ui->textBoxVerifiedRootPassword->setVisible( !checked ); - checkReady( isReady() ); - } ); - - m_customUsername = false; - m_customHostname = false; - - setWriteRootPassword( true ); - ui->checkBoxReusePassword->setChecked( true ); - - CALAMARES_RETRANSLATE( ui->retranslateUi( this ); ) -} - - -UsersPage::~UsersPage() -{ - delete ui; -} - - -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; -} - - -QList< Calamares::job_ptr > -UsersPage::createJobs( const QStringList& defaultGroupsList ) -{ - QList< Calamares::job_ptr > list; - if ( !isReady() ) - return list; - - Calamares::Job* j; - j = new CreateUserJob( ui->textBoxUsername->text(), - ui->textBoxFullName->text().isEmpty() ? - ui->textBoxUsername->text() : - ui->textBoxFullName->text(), - ui->checkBoxAutoLogin->isChecked(), - defaultGroupsList ); - list.append( Calamares::job_ptr( j ) ); - - j = new SetPasswordJob( ui->textBoxUsername->text(), - ui->textBoxUserPassword->text() ); - list.append( Calamares::job_ptr( j ) ); - - if ( m_writeRootPassword ) - { - if ( ui->checkBoxReusePassword->isChecked() ) - j = new SetPasswordJob( "root", - ui->textBoxUserPassword->text() ); - else - j = new SetPasswordJob( "root", - ui->textBoxRootPassword->text() ); - list.append( Calamares::job_ptr( j ) ); - } - - j = new SetHostNameJob( ui->textBoxHostname->text() ); - list.append( Calamares::job_ptr( j ) ); - - Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - gs->insert( "hostname", ui->textBoxHostname->text() ); - if ( ui->checkBoxAutoLogin->isChecked() ) - gs->insert( "autologinUser", ui->textBoxUsername->text() ); - - gs->insert( "username", ui->textBoxUsername->text() ); - gs->insert( "password", CalamaresUtils::obscure( ui->textBoxUserPassword->text() ) ); - - return list; -} - - -void -UsersPage::onActivate() -{ - ui->textBoxFullName->setFocus(); -} - - -void -UsersPage::setWriteRootPassword( bool write ) -{ - ui->checkBoxReusePassword->setVisible( write ); - m_writeRootPassword = write; -} - - -void -UsersPage::onFullNameTextEdited( const QString& textRef ) -{ - if ( textRef.isEmpty() ) - { - ui->labelFullNameError->clear(); - ui->labelFullName->clear(); - if ( !m_customUsername ) - ui->textBoxUsername->clear(); - if ( !m_customHostname ) - ui->textBoxHostname->clear(); - m_readyFullName = false; - } - else - { - ui->labelFullName->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, - CalamaresUtils::Original, - ui->labelFullName->size() ) ); - m_readyFullName = true; - fillSuggestions(); - } - checkReady( isReady() ); -} - - -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( "%1-pc" ).arg( cleanParts.first() ); - 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 -UsersPage::validateUsernameText( const QString& textRef ) -{ - QString text( textRef ); - QRegExp rx( USERNAME_RX ); - QRegExpValidator val( rx ); - int pos = -1; - - if ( text.isEmpty() ) - { - ui->labelUsernameError->clear(); - ui->labelUsername->clear(); - m_readyUsername = false; - } - else if ( text.length() > USERNAME_MAX_LENGTH ) - { - ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelUsername->size() ) ); - ui->labelUsernameError->setText( - tr( "Your username is too long." ) ); - - m_readyUsername = false; - } - else if ( val.validate( text, pos ) == QValidator::Invalid ) - { - ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelUsername->size() ) ); - ui->labelUsernameError->setText( - tr( "Your username contains invalid characters. Only lowercase letters and numbers are allowed." ) ); - - m_readyUsername = false; - } - else { - ui->labelUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, - CalamaresUtils::Original, - ui->labelUsername->size() ) ); - ui->labelUsernameError->clear(); - m_readyUsername = true; - } - - emit checkReady( isReady() ); -} - - -void -UsersPage::onHostnameTextEdited( const QString& textRef ) -{ - m_customHostname = true; - validateHostnameText( textRef ); -} - - -void -UsersPage::validateHostnameText( const QString& textRef ) -{ - QString text = textRef; - QRegExp rx( HOSTNAME_RX ); - QRegExpValidator val( rx ); - int pos = -1; - - if ( text.isEmpty() ) - { - ui->labelHostnameError->clear(); - ui->labelHostname->clear(); - m_readyHostname= false; - } - else if ( text.length() < HOSTNAME_MIN_LENGTH ) - { - ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelHostname->size() ) ); - ui->labelHostnameError->setText( - tr( "Your hostname is too short." ) ); - - m_readyHostname = false; - - } - else if ( text.length() > HOSTNAME_MAX_LENGTH ) - { - ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelHostname->size() ) ); - ui->labelHostnameError->setText( - tr( "Your hostname is too long." ) ); - - m_readyHostname = false; - - } - else if ( val.validate( text, pos ) == QValidator::Invalid ) - { - ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelHostname->size() ) ); - ui->labelHostnameError->setText( - tr( "Your hostname contains invalid characters. Only letters, numbers and dashes are allowed." ) ); - - m_readyHostname = false; - } - else - { - ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, - CalamaresUtils::Original, - ui->labelHostname->size() ) ); - ui->labelHostnameError->clear(); - m_readyHostname = true; - } - - emit checkReady( isReady() ); -} - - -void -UsersPage::onPasswordTextChanged( const QString& ) -{ - QString pw1 = ui->textBoxUserPassword->text(); - QString pw2 = ui->textBoxUserVerifiedPassword->text(); - - if ( pw1.isEmpty() && pw2.isEmpty() ) - { - ui->labelUserPasswordError->clear(); - ui->labelUserPassword->clear(); - m_readyPassword = false; - } - else if ( pw1 != pw2 ) - { - ui->labelUserPasswordError->setText( tr( "Your passwords do not match!" ) ); - ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelUserPassword->size() ) ); - m_readyPassword = false; - } - else - { - ui->labelUserPasswordError->clear(); - ui->labelUserPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, - CalamaresUtils::Original, - ui->labelUserPassword->size() ) ); - m_readyPassword = true; - } - - emit checkReady( isReady() ); -} - - -void -UsersPage::onRootPasswordTextChanged( const QString& ) -{ - QString pw1 = ui->textBoxRootPassword->text(); - QString pw2 = ui->textBoxVerifiedRootPassword->text(); - - if ( pw1.isEmpty() && pw2.isEmpty() ) - { - ui->labelRootPasswordError->clear(); - ui->labelRootPassword->clear(); - m_readyRootPassword = false; - } - else if ( pw1 != pw2 ) - { - ui->labelRootPasswordError->setText( tr( "Your passwords do not match!" ) ); - ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, - CalamaresUtils::Original, - ui->labelRootPassword->size() ) ); - m_readyRootPassword = false; - } - else - { - ui->labelRootPasswordError->clear(); - ui->labelRootPassword->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, - CalamaresUtils::Original, - ui->labelRootPassword->size() ) ); - m_readyRootPassword = true; - } - - emit checkReady( isReady() ); -} - - -void -UsersPage::setAutologinDefault( bool checked ) -{ - ui->checkBoxAutoLogin->setChecked( checked ); - emit checkReady( isReady() ); -} - -void -UsersPage::setReusePasswordDefault( bool checked ) -{ - ui->checkBoxReusePassword->setChecked( checked ); - emit checkReady( isReady() ); -} diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h deleted file mode 100644 index 632d49471..000000000 --- a/src/modules/users/UsersPage.h +++ /dev/null @@ -1,84 +0,0 @@ -/* === This file is part of Calamares - === - * - * Copyright 2014-2015, Teo Mrnjavac - * - * Portions from the Manjaro Installation Framework - * by Roland Singer - * Copyright (C) 2007 Free Software Foundation, Inc. - * - * 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 USERSPAGE_H -#define USERSPAGE_H - -#include "Typedefs.h" - -#include - -namespace Ui { -class Page_UserSetup; -} - -class UsersPage : public QWidget -{ - Q_OBJECT -public: - explicit UsersPage( QWidget* parent = nullptr ); - virtual ~UsersPage(); - - bool isReady(); - - QList< Calamares::job_ptr > createJobs( const QStringList& defaultGroupsList ); - - void onActivate(); - - void setWriteRootPassword( bool show ); - void setAutologinDefault( bool checked ); - void setReusePasswordDefault( bool checked ); - -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& ); - -signals: - void checkReady( bool ); - -private: - Ui::Page_UserSetup* ui; - - const QRegExp USERNAME_RX = QRegExp( "^[a-z_][a-z0-9_-]*[$]?$" ); - const QRegExp HOSTNAME_RX = QRegExp( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); - const int USERNAME_MAX_LENGTH = 31; - const int HOSTNAME_MIN_LENGTH = 2; - const int HOSTNAME_MAX_LENGTH = 24; - - bool m_readyFullName; - bool m_readyUsername; - bool m_customUsername; - bool m_readyHostname; - bool m_customHostname; - 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 d601014ae..782a6a846 100644 --- a/src/modules/users/UsersViewStep.cpp +++ b/src/modules/users/UsersViewStep.cpp @@ -18,7 +18,7 @@ #include "UsersViewStep.h" -#include "UsersPage.h" +#include "gui/UsersPage.h" #include "JobQueue.h" #include "GlobalStorage.h" @@ -139,6 +139,12 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) configurationMap.value( "autologinGroup" ).toString() ); } + if ( configurationMap.contains( "doAutologin" ) && + configurationMap.value( "doAutologin" ).type() == QVariant::Bool ) + { + m_widget->setAutologin( configurationMap.value( "doAutologin" ).toBool() ); + } + if ( configurationMap.contains( "sudoersGroup" ) && configurationMap.value( "sudoersGroup" ).type() == QVariant::String ) { @@ -151,19 +157,18 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", configurationMap.value( "setRootPassword" ).toBool() ); - m_widget->setWriteRootPassword( configurationMap.value( "setRootPassword" ).toBool() ); + m_widget->setHaveRootPassword( configurationMap.value( "setRootPassword" ).toBool() ); } - if ( configurationMap.contains( "doAutologin" ) && - configurationMap.value( "doAutologin" ).type() == QVariant::Bool ) + if ( configurationMap.contains( "availableShells" ) && + configurationMap.value( "availableShells" ).type() == QVariant::List ) { - m_widget->setAutologinDefault( configurationMap.value( "doAutologin" ).toBool() ); + m_widget->setAvailableShells( configurationMap.value( "availableShells" ).toStringList() ); } - - if ( configurationMap.contains( "doReusePassword" ) && - configurationMap.value( "doReusePassword" ).type() == QVariant::Bool ) + + if ( configurationMap.contains( "avatarFilePath" ) && + configurationMap.value( "avatarFilePath").type() == QVariant::String ) { - m_widget->setReusePasswordDefault( configurationMap.value( "doReusePassword" ).toBool() ); + m_widget->setAvatarFilePath( configurationMap.value( "avatarFilePath" ).toString() ); } } - diff --git a/src/modules/users/gui/AddUserDialog.cpp b/src/modules/users/gui/AddUserDialog.cpp new file mode 100644 index 000000000..c480f1c80 --- /dev/null +++ b/src/modules/users/gui/AddUserDialog.cpp @@ -0,0 +1,217 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Lisa Vitolo + * + * Portions from Tribe (Chakra Linux) + * by Dario Freddi, Drake Justice and Manuel Tortosa + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * 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 + +#include +#include +#include +#include +#include + +#include "AddUserDialog.h" +#include "utils/CalamaresUtilsGui.h" + +AddUserDialog::AddUserDialog(const QStringList& existingUsers, bool avatar, bool autologin, const QStringList& shells, bool haveRootPassword, QWidget* parent) + : QDialog(parent), + m_existingUsers(existingUsers) +{ + m_badUsernames + << "root" + << "bin" + << "daemon" + << "mail" + << "ftp" + << "http" + << "nobody" + << "dbus" + << "avahi" + << "usbmux" + << "postgres" + << "quassel" + << "rtkit" + << "git" + << "polkitd" + << "nm-openconnect" + << "kdm" + << "uuidd" + << "ntp" + << "mysql" + << "clamav" + << "postfix" + << "lightdm"; + + ui.setupUi(this); + + ui.passLine->setEchoMode(QLineEdit::Password); + ui.confirmPassLine->setEchoMode(QLineEdit::Password); + ui.labelUsernameError->setFont( QFont("Arial", 10) ); + + ui.loginShellSelection->setAutoCompletion(true); + ui.loginShellSelection->addItems(shells); + + connect(ui.userNameLine, &QLineEdit::textEdited, this, + &AddUserDialog::validateUsername); + + m_passwordsMatch = m_passwordsEmpty = true; + connect(ui.passLine, &QLineEdit::textChanged, this, &AddUserDialog::passwordChanged); + connect(ui.confirmPassLine, &QLineEdit::textChanged, this, &AddUserDialog::passwordChanged); + + if (!haveRootPassword) { + ui.rootUsesUserPwCheckBox->setEnabled(false); + } + + if (!autologin) { + ui.autoLoginCheckBox->setEnabled(false); + } + + if (!avatar) { + ui.avatarFileLine->setEnabled(false); + ui.selectImageButton->setEnabled(false); + } + + connect(ui.selectImageButton, &QPushButton::clicked, this, &AddUserDialog::avatarClicked); + + // Do not enable until valid information have been entered. + ui.dialogButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(ui.dialogButtonBox, &QDialogButtonBox::accepted, this, &AddUserDialog::accept); + connect(ui.dialogButtonBox, &QDialogButtonBox::rejected, this, &AddUserDialog::reject); +} + +AddUserDialog::~AddUserDialog() {} + +void AddUserDialog::accept() { + // Store all the information from the dialog in member variables. + login = ui.userNameLine->text(); + password = ui.passLine->text(); + shell = ui.loginShellSelection->currentText(); + fullName = ui.nameLine->text(); + + autoLogin = ui.autoLoginCheckBox->isEnabled(); + useUserPw = ui.rootUsesUserPwCheckBox->isChecked(); + + QDialog::accept(); +} + +void AddUserDialog::validateUsername(const QString& username) { + QRegExp rx( USERNAME_RX ); + QRegExpValidator val( rx ); + int pos = -1; + + if ( username.isEmpty() ) + { + ui.labelUsernameError->clear(); + ui.iconUsername->clear(); + m_validUsername = false; + } + else if ( username.length() > USERNAME_MAX_LENGTH ) + { + ui.iconUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui.iconUsername->size() ) ); + ui.labelUsernameError->setText( + tr( "Your username is too long." ) ); + + m_validUsername = false; + } + else if ( val.validate( (QString &)username, pos ) == QValidator::Invalid ) + { + ui.iconUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui.iconUsername->size() ) ); + ui.labelUsernameError->setText( + tr( "Only lowercase letters and numbers are allowed." ) ); + + m_validUsername = false; + } + else if ( m_existingUsers.contains(username) ) { + ui.iconUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui.iconUsername->size() ) ); + ui.labelUsernameError->setText( + tr( "This username was already created." ) ); + + m_validUsername = false; + } + else if ( m_badUsernames.contains(username) ) { + ui.iconUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui.iconUsername->size() ) ); + ui.labelUsernameError->setText( + tr( "Username is already in use in the system." ) ); + + m_validUsername = false; + } + else { + ui.iconUsername->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, + CalamaresUtils::Original, + ui.iconUsername->size() ) ); + ui.labelUsernameError->setText("Username valid."); + m_validUsername = true; + } + + updateValidityUi(); +} + +void AddUserDialog::passwordChanged() { + m_passwordsMatch = (ui.passLine->text() == ui.confirmPassLine->text()); + m_passwordsEmpty = (ui.passLine->text().length() == 0); + + if (m_passwordsMatch && !m_passwordsEmpty) { + ui.confirmPwCheck->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, + CalamaresUtils::Original, + ui.confirmPwCheck->size()) ); + } else { + ui.confirmPwCheck->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui.confirmPwCheck->size()) ); + } + + updateValidityUi(); +} + +void AddUserDialog::updateValidityUi() +{ + if (m_validUsername && !m_passwordsEmpty && m_passwordsMatch) { + ui.dialogButtonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + } else { + ui.dialogButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + } +} + +// TODO: support getting the avatar from a URL too. +void AddUserDialog::avatarClicked() +{ + // Open a file dialog restricted to images. + QFileDialog dlg( this ); + dlg.setNameFilter(tr("Images (*.png *.xpm *.jpg)")); + dlg.setViewMode(QFileDialog::Detail); + + // TODO: read this from configuration as we could have a + // preferred directory to store avatars in the live installation. + dlg.setDirectory("/home"); + + if ( dlg.exec() == QDialog::Accepted ) { + avatarFile = dlg.selectedFiles().at(0); + ui.avatarFileLine->setText(avatarFile); + } +} diff --git a/src/modules/users/gui/AddUserDialog.h b/src/modules/users/gui/AddUserDialog.h new file mode 100644 index 000000000..411c4a030 --- /dev/null +++ b/src/modules/users/gui/AddUserDialog.h @@ -0,0 +1,82 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Lisa Vitolo + * + * Portions from Tribe (Chakra Linux) + * by Dario Freddi, Drake Justice and Manuel Tortosa + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * 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 ADDUSERDIALOG_H +#define ADDUSERDIALOG_H + +#include "ui_adduserdialog.h" + +#include +#include +#include + +class AddUserDialog : public QDialog +{ + Q_OBJECT + +public: + /* + * - existingUsers: username of the users that were already created. + * This avoids us creating a second one with the same name. + * - avatar: whether we should allow to choose an avatar. + * - autologin: whether we should allow the autologin option. + * - shells: the available login shells for users. + */ + AddUserDialog(const QStringList &existingUsers, bool avatar, bool autologin, const QStringList &shells, bool haveRootPassword, QWidget *parent = 0); + virtual ~AddUserDialog(); + + QString login; + QString shell; + QString password; + QString avatarFile; + QString fullName; + + bool autoLogin; + bool useUserPw; + + void validateUsername(const QString&); + +public slots: + void accept() override; + +signals: + void addUserClicked(); + +private slots: + void avatarClicked(); + void passwordChanged(); + void updateValidityUi(); + +private: + Ui::AddUserDialog ui; + + QStringList m_existingUsers; + QStringList m_badUsernames; + bool m_validUsername; + bool m_passwordsMatch; + bool m_passwordsEmpty; + + const QRegExp USERNAME_RX = QRegExp( "^[a-z_][a-z0-9_-]*[$]?$" ); + const int USERNAME_MAX_LENGTH = 31; +}; + +#endif /* ADDUSERDIALOG_H */ diff --git a/src/modules/users/gui/UsersPage.cpp b/src/modules/users/gui/UsersPage.cpp new file mode 100644 index 000000000..ec8b835d8 --- /dev/null +++ b/src/modules/users/gui/UsersPage.cpp @@ -0,0 +1,428 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * + * Portions from the Manjaro Installation Framework + * by Roland Singer + * Copyright (C) 2007 Free Software Foundation, Inc. + * + * 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 "UsersPage.h" +#include "ui_page_usersetup.h" +#include "AddUserDialog.h" +#include +#include +#include +#include +#include "JobQueue.h" +#include "GlobalStorage.h" +#include "utils/Logger.h" +#include "utils/CalamaresUtilsGui.h" +#include "utils/Retranslator.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +UsersListModel::~UsersListModel() { + qDeleteAll(m_currentUsers); +} + +int UsersListModel::rowCount(const QModelIndex &parent) const { + return m_currentUsers.size(); +} + +int UsersListModel::columnCount(const QModelIndex &parent) const { + return 4; +} + +QVariant UsersListModel::headerData(int section, Qt::Orientation orientation, int role) const { + QVariant header; + + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + switch (section) { + case 0: + header = QVariant(tr("User name")); + break; + + case 1: + header = QVariant(tr("Full name")); + break; + + case 2: + header = QVariant(tr("Shell")); + break; + + case 3: + header = QVariant(tr("Autologin?")); + break; + } + } + + return header; +} + +QVariant UsersListModel::data(const QModelIndex &index, int role) const { + if (role == Qt::DisplayRole) { + User* user = m_currentUsers.at( index.row() ); + QVariant data; + + switch (index.column()) { + case 0: + data = QVariant( user->toString() ); + break; + + case 1: + data = QVariant( user->fullname ); + break; + + case 2: + data = QVariant( user->shell ); + break; + + case 3: + data = (user->autologin ? "yes" : "no"); + } + + return data; + } + + if (role == Qt::TextAlignmentRole) { + return Qt::AlignCenter; + } + + return QVariant(); +} + +Qt::ItemFlags UsersListModel::flags(const QModelIndex & /*index*/) const { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled ; +} + +void UsersListModel::addUser(User* user) { + beginInsertRows( QModelIndex(), 0, 0); + m_currentUsers.append(user); + endInsertRows(); +} + +void UsersListModel::deleteUser(int index) { + if (index < 0 || index >= m_currentUsers.size()) { + return; + } + + beginRemoveRows(QModelIndex(), 0, 0); + m_currentUsers.removeAt(index); + endRemoveRows(); +} + +QList UsersListModel::getUsers() const { + return m_currentUsers; +} + +UsersPage::UsersPage( QWidget* parent ) + : QWidget( parent ) + , ui( new Ui::UserCreation ) + , m_readyHostname( false ) + , m_readyRootPassword( false ) + , m_haveRootPassword( true ) +{ + ui->setupUi( this ); + + connect(ui->addUser, &QPushButton::clicked, this, &UsersPage::addUserClicked); + connect(ui->deleteUser, &QPushButton::clicked, this, &UsersPage::deleteUserClicked); + + connect(ui->hostname, &QLineEdit::textChanged, this, &UsersPage::onHostnameTextEdited); + + // The columns will occupy all the space available in the view. + ui->usersView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + // You can only select entire rows, not single cells, i.e. you + // always select the user as a whole. + ui->usersView->setSelectionBehavior(QAbstractItemView::SelectRows); + + ui->usersView->setModel(&m_userModel); + ui->usersView->show(); + CALAMARES_RETRANSLATE( ui->retranslateUi( this ); ); +} + +UsersPage::~UsersPage() +{ + delete ui; +} + +void +UsersPage::addUserClicked() { + QStringList existingUsers; + bool userHasAutologin = false; + + for (const User* user : m_userModel.getUsers()) { + existingUsers.append( user->username ); + + if (user->autologin) { + userHasAutologin = true; + } + } + + // Only allow autologin if the module configuration allows it, + // and there exists no other users with autologin enabled. + bool allowAutologin = m_autologin && !userHasAutologin; + + QPointer dlg = new AddUserDialog( + existingUsers, !m_avatarFilePath.isEmpty(), allowAutologin, m_availableShells, m_haveRootPassword, this ); + + if ( dlg->exec() == QDialog::Accepted ) { + addUser(dlg->login, dlg->fullName, dlg->password, dlg->shell, dlg->avatarFile, dlg->autoLogin); + + if (dlg->useUserPw && m_haveRootPassword) { + ui->rootPw->setText(dlg->password); + ui->confirmRootPw->setText(dlg->password); + } + } + + delete dlg; +} + +void +UsersPage::deleteUserClicked() { + QItemSelectionModel* selectionModel = ui->usersView->selectionModel(); + + if (!selectionModel->hasSelection()) { + return; + } + + QModelIndex selectionIndex = selectionModel->currentIndex(); + if (!selectionIndex.isValid()) { + return; + } + + m_userModel.deleteUser(selectionIndex.row()); +} + +void +UsersPage::addUser(const QString &login, const QString &fullName, const QString &password, const QString &shell, const QString& avatarFile, bool autologin) { + User* newUser = new User(login, fullName, password, shell, avatarFile, autologin); + m_userModel.addUser(newUser); + + ui->hostname->setText( login + "-pc" ); + + emit checkReady( isReady() ); +} + +bool +UsersPage::isReady() +{ + return (m_readyHostname && + // At least one user should have been created. + m_userModel.getUsers().size() > 0 && + (!m_haveRootPassword || m_readyRootPassword)); +} + + +QList< Calamares::job_ptr > +UsersPage::createJobs( const QStringList& defaultGroupsList ) +{ + QList< Calamares::job_ptr > list; + if ( !isReady() ) + return list; + + for (const User* user : m_userModel.getUsers()) { + + Calamares::Job* j; + j = new CreateUserJob( user->username, + (!user->fullname.isEmpty()) ? user->fullname : user->username, + user->shell, + user->autologin, + defaultGroupsList ); + + list.append( Calamares::job_ptr( j ) ); + + j = new SetPasswordJob( user->username, user->password ); + list.append( Calamares::job_ptr( j ) ); + + j = new SetHostNameJob( ui->hostname->text() ); + list.append( Calamares::job_ptr( j ) ); + + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + gs->insert( "hostname", ui->hostname->text() ); + + // Given the validation we do, we assume this is always true + // for at most one user in the page. + if ( user->autologin ) + gs->insert( "autologinUser", user->username ); + + if ( !user->avatarFile.isEmpty() ) { + if (m_avatarFilePath.contains("~")) { + QString home( "/home/" + user->username); + m_avatarFilePath.replace("~", home); + } + + j = new SetAvatarJob( user->avatarFile, m_avatarFilePath ); + list.append( Calamares::job_ptr(j) ); + } + } + + if ( m_haveRootPassword ) + { + Calamares::Job* j = new SetPasswordJob( "root", ui->rootPw->text() ); + list.append( Calamares::job_ptr( j ) ); + } + + return list; +} + + +void +UsersPage::onActivate() +{ + emit checkReady( isReady() ); + + // If there is no user yet, open the dialog as soon as the + // page is enabled. This makes it easy for people who want + // to create one user. + if (m_userModel.rowCount() == 0) { + addUserClicked(); + } +} + +void +UsersPage::onHostnameTextEdited( const QString& textRef ) +{ + validateHostnameText( textRef ); +} + +void +UsersPage::validateHostnameText( const QString& textRef ) +{ + QString text = textRef; + QRegExp rx( HOSTNAME_RX ); + QRegExpValidator val( rx ); + int pos = -1; + + if ( text.isEmpty() ) + { + ui->labelHostnameError->clear(); + ui->labelHostname->clear(); + m_readyHostname= false; + } + else if ( text.length() < HOSTNAME_MIN_LENGTH ) + { + ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui->labelHostname->size() ) ); + ui->labelHostnameError->setText( + tr( "Your hostname is too short." ) ); + + m_readyHostname = false; + + } + else if ( text.length() > HOSTNAME_MAX_LENGTH ) + { + ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui->labelHostname->size() ) ); + ui->labelHostnameError->setText( + tr( "Your hostname is too long." ) ); + + m_readyHostname = false; + + } + else if ( val.validate( text, pos ) == QValidator::Invalid ) + { + ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui->labelHostname->size() ) ); + ui->labelHostnameError->setText( + tr( "Your hostname contains invalid characters. Only letters, numbers and dashes are allowed." ) ); + + m_readyHostname = false; + } + else + { + ui->labelHostname->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, + CalamaresUtils::Original, + ui->labelHostname->size() ) ); + ui->labelHostnameError->clear(); + m_readyHostname = true; + } + + emit checkReady( isReady() ); +} + +void +UsersPage::onRootPasswordTextChanged( const QString& textRef ) +{ + QString pw1 = ui->rootPw->text(); + QString pw2 = ui->confirmRootPw->text(); + + if ( pw1.isEmpty() && pw2.isEmpty() ) + { + ui->labelRootPwError->clear(); + ui->labelRootPwIcon->clear(); + m_readyRootPassword = false; + } + else if ( pw1 != pw2 ) + { + ui->labelRootPwError->setText( tr( "Your passwords do not match!" ) ); + ui->labelRootPwIcon->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::No, + CalamaresUtils::Original, + ui->labelRootPwIcon->size() ) ); + m_readyRootPassword = false; + } + else + { + ui->labelRootPwError->clear(); + ui->labelRootPwIcon->setPixmap( CalamaresUtils::defaultPixmap( CalamaresUtils::Yes, + CalamaresUtils::Original, + ui->labelRootPwIcon->size() ) ); + m_readyRootPassword = true; + } + + emit checkReady( isReady() ); +} + +void UsersPage::setAutologin(bool autologin) { + m_autologin = autologin; +} + +void UsersPage::setHaveRootPassword(bool haveRootPassword) { + m_haveRootPassword = haveRootPassword; + + if (m_haveRootPassword) { + ui->rootPw->show(); + ui->confirmRootPw->show(); + ui->labelRootPw->show(); + ui->labelConfirmRootPw->show(); + + connect(ui->rootPw, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged); + connect(ui->confirmRootPw, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged); + } else { + ui->rootPw->hide(); + ui->confirmRootPw->hide(); + ui->labelRootPw->hide(); + ui->labelConfirmRootPw->hide(); + } +} + +void UsersPage::setAvatarFilePath(const QString &path) { + m_avatarFilePath = path; +} + +void UsersPage::setAvailableShells(const QStringList &shells) { + m_availableShells = shells; +} diff --git a/src/modules/users/gui/UsersPage.h b/src/modules/users/gui/UsersPage.h new file mode 100644 index 000000000..aa027fa3a --- /dev/null +++ b/src/modules/users/gui/UsersPage.h @@ -0,0 +1,139 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * + * Portions from the Manjaro Installation Framework + * by Roland Singer + * Copyright (C) 2007 Free Software Foundation, Inc. + * + * 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 USERSPAGE_H +#define USERSPAGE_H + +#include "Typedefs.h" + +#include +#include +#include +#include +#include + +namespace Ui { +class UserCreation; +} + +/* + * Represents the properties of a user in the system. + * + * The fullname and avatar file can be empty and they will be ignored. + * The password can technically be empty too, but that is of course + * not recommended in general. + */ +struct User { + User(const QString& username, const QString& fullname, const QString& password, const QString& shell, const QString& avatarFile, bool autologin) + : username(username), fullname(fullname), password(password), shell(shell), avatarFile(avatarFile), autologin(autologin) {} + + QString toString() const { + return username; + } + + QString username; + QString fullname; + QString password; + QString shell; + QString avatarFile; + bool autologin; +}; + +/* + * Model to contain the users created during the installation + * process. This is associated to a QTableView in the main UI. + * + * Each row is a user and each column a property of the user. + */ +class UsersListModel : public QAbstractTableModel { + Q_OBJECT + +public: + virtual ~UsersListModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + Qt::ItemFlags flags(const QModelIndex & /*index*/) const Q_DECL_OVERRIDE; + + void addUser(User *user); + void deleteUser(int index); + QList getUsers() const; + +private: + QList m_currentUsers; +}; + +class UsersPage : public QWidget +{ + Q_OBJECT + +public: + explicit UsersPage( QWidget* parent = nullptr ); + virtual ~UsersPage(); + + bool isReady(); + + QList< Calamares::job_ptr > createJobs( const QStringList& defaultGroupsList ); + + void onActivate(); + + // Setters for properties of this page, usually from configuration. + void setAutologin( bool autologin ); + void setAvatarFilePath( const QString& path ); + void setAvailableShells( const QStringList& shells); + void setHaveRootPassword(bool); + +protected slots: + void onHostnameTextEdited( const QString& ); + void validateHostnameText( const QString& ); + void onRootPasswordTextChanged( const QString& ); + + void addUserClicked(); + void deleteUserClicked(); + +signals: + void checkReady( bool ); + +private: + void addUser(const QString& login, const QString& fullName, const QString& password, const QString& shell, const QString& avatarFile, bool autologin); + + Ui::UserCreation* ui; + UsersListModel m_userModel; + + // Hostname validation. + const QRegExp HOSTNAME_RX = QRegExp( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); + const int HOSTNAME_MIN_LENGTH = 2; + const int HOSTNAME_MAX_LENGTH = 24; + + // Are the following fields valid with their current value? + bool m_readyHostname; + bool m_readyRootPassword; + + bool m_autologin; + bool m_haveRootPassword; + QString m_avatarFilePath; + QStringList m_availableShells; +}; + +#endif // USERSPAGE_H diff --git a/src/modules/users/gui/adduserdialog.ui b/src/modules/users/gui/adduserdialog.ui new file mode 100644 index 000000000..0c5a3ab83 --- /dev/null +++ b/src/modules/users/gui/adduserdialog.ui @@ -0,0 +1,382 @@ + + + AddUserDialog + + + + 0 + 0 + 712 + 383 + + + + + 0 + 0 + + + + + + + + 3 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 13 + 20 + + + + + + + + Confirm Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 15 + + + 5 + + + + + Qt::Vertical + + + + 45 + 40 + + + + + + + + User name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 150 + 0 + + + + <html><head/><body><p>The user name may consist of a lowercase character or &quot;_&quot;, followed by up to 31 lowercase characters, digits or &quot;_&quot; and &quot;-&quot;.</p></body></html> + + + + + + + + + + + + 0 + 0 + + + + + 40 + 16777215 + + + + + + + + + + + + + + + + + + + + + + + 150 + 0 + + + + + + + + + 150 + 0 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 50 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + Qt::Horizontal + + + + + + + + + + Select to use the above password +as the Administrator password + + + + + + + + + + + 75 + true + + + + Advanced settings + + + + + + + Full name: + + + + + + + Login Automatically + + + + + + + + + + + 75 + true + + + + Avatar selection + + + + + + + + + + Image file... + + + + + + + + + + Login shell: + + + + + + + + 180 + 16777215 + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + + + userNameLine + passLine + confirmPassLine + + + + diff --git a/src/modules/users/gui/page_usersetup.ui b/src/modules/users/gui/page_usersetup.ui new file mode 100644 index 000000000..2bdbf4830 --- /dev/null +++ b/src/modules/users/gui/page_usersetup.ui @@ -0,0 +1,277 @@ + + + UserCreation + + + + 0 + 0 + 694 + 474 + + + + + + + + + + Enter at least one login name and password. + + + true + + + true + + + + + + + 0 + + + + + + 48 + 48 + + + + + 80 + 48 + + + + Add another user account. + + + Create user + + + + + + + true + + + + 0 + 0 + + + + + 80 + 48 + + + + Delete user + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + true + + + + + 0 + 0 + 678 + 248 + + + + + 0 + 0 + + + + + QLayout::SetNoConstraint + + + + + + + + + Qt::Horizontal + + + + + + + 11 + + + + + Confirm: + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + + 700 + 16777215 + + + + + + + + QLineEdit::Password + + + + + + + Root password: + + + + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + Hostname: + + + Qt::AlignCenter + + + + + + + QLineEdit::Password + + + + + + + + 230 + 0 + + + + + 230 + 16777215 + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + + + + + + + + + 50 + 16777215 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/users/CreateUserJob.cpp b/src/modules/users/jobs/CreateUserJob.cpp similarity index 98% rename from src/modules/users/CreateUserJob.cpp rename to src/modules/users/jobs/CreateUserJob.cpp index c666bd9ad..e3d3dce13 100644 --- a/src/modules/users/CreateUserJob.cpp +++ b/src/modules/users/jobs/CreateUserJob.cpp @@ -16,7 +16,7 @@ * along with Calamares. If not, see . */ -#include +#include #include "JobQueue.h" #include "GlobalStorage.h" @@ -33,11 +33,13 @@ CreateUserJob::CreateUserJob( const QString& userName, const QString& fullName, + const QString& shell, bool autologin, const QStringList& defaultGroups ) : Calamares::Job() , m_userName( userName ) , m_fullName( fullName ) + , m_shell( shell ) , m_autologin( autologin ) , m_defaultGroups( defaultGroups ) { @@ -151,7 +153,7 @@ CreateUserJob::exec() targetEnvCall( { "useradd", "-m", "-s", - "/bin/bash", + m_shell, "-U", "-c", m_fullName, diff --git a/src/modules/users/jobs/CreateUserJob.cpp.orig b/src/modules/users/jobs/CreateUserJob.cpp.orig new file mode 100644 index 000000000..d6383a6a0 --- /dev/null +++ b/src/modules/users/jobs/CreateUserJob.cpp.orig @@ -0,0 +1,182 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2016, Teo Mrnjavac + * + * 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 + +#include "JobQueue.h" +#include "GlobalStorage.h" +#include "utils/Logger.h" +#include "utils/CalamaresUtilsSystem.h" + +#include +#include +#include +#include +#include +#include + + +CreateUserJob::CreateUserJob( const QString& userName, + const QString& fullName, + const QString& shell, + bool autologin, + const QStringList& defaultGroups ) + : Calamares::Job() + , m_userName( userName ) + , m_fullName( fullName ) + , m_shell( shell ) + , m_autologin( autologin ) + , m_defaultGroups( defaultGroups ) +{ +} + + +QString +CreateUserJob::prettyName() const +{ + return tr( "Create user %1" ).arg( m_userName ); +} + + +QString +CreateUserJob::prettyDescription() const +{ + return tr( "Create user %1." ).arg( m_userName ); +} + + +QString +CreateUserJob::prettyStatusMessage() const +{ + return tr( "Creating user %1." ).arg( m_userName ); +} + + +Calamares::JobResult +CreateUserJob::exec() +{ + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QDir destDir( gs->value( "rootMountPoint" ).toString() ); + + if ( gs->contains( "sudoersGroup" ) && + !gs->value( "sudoersGroup" ).toString().isEmpty() ) + { + QFileInfo sudoersFi( destDir.absoluteFilePath( "etc/sudoers.d/10-installer" ) ); + + if ( !sudoersFi.absoluteDir().exists() ) + return Calamares::JobResult::error( tr( "Sudoers dir is not writable." ) ); + + QFile sudoersFile( sudoersFi.absoluteFilePath() ); + if (!sudoersFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) + return Calamares::JobResult::error( tr( "Cannot create sudoers file for writing." ) ); + + QString sudoersGroup = gs->value( "sudoersGroup" ).toString(); + + QTextStream sudoersOut( &sudoersFile ); + sudoersOut << QString( "%%1 ALL=(ALL) ALL\n" ).arg( sudoersGroup ); + + if ( QProcess::execute( "chmod", { "440", sudoersFi.absoluteFilePath() } ) ) + return Calamares::JobResult::error( tr( "Cannot chmod sudoers file." ) ); + } + + QFileInfo groupsFi( destDir.absoluteFilePath( "etc/group" ) ); + QFile groupsFile( groupsFi.absoluteFilePath() ); + if ( !groupsFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) + return Calamares::JobResult::error( tr( "Cannot open groups file for reading." ) ); + QString groupsData = QString::fromLocal8Bit( groupsFile.readAll() ); + QStringList groupsLines = groupsData.split( '\n' ); + for ( QStringList::iterator it = groupsLines.begin(); + it != groupsLines.end(); ++it ) + { + int indexOfFirstToDrop = it->indexOf( ':' ); + it->truncate( indexOfFirstToDrop ); + } + + foreach ( const QString& group, m_defaultGroups ) + if ( !groupsLines.contains( group ) ) + CalamaresUtils::System::instance()-> + targetEnvCall( { "groupadd", group } ); + + QString defaultGroups = m_defaultGroups.join( ',' ); + if ( m_autologin ) + { + QString autologinGroup; + if ( gs->contains( "autologinGroup" ) && + !gs->value( "autologinGroup" ).toString().isEmpty() ) + autologinGroup = gs->value( "autologinGroup" ).toString(); + else + autologinGroup = QStringLiteral( "autologin" ); + + CalamaresUtils::System::instance()->targetEnvCall( { "groupadd", autologinGroup } ); + defaultGroups.append( QString( ",%1" ).arg( autologinGroup ) ); + } + + // If we're looking to reuse the contents of an existing /home + if ( gs->value( "reuseHome" ).toBool() ) + { + QString shellFriendlyHome = "/home/" + m_userName; + QDir existingHome( destDir.absolutePath() + shellFriendlyHome ); + if ( existingHome.exists() ) + { + QString backupDirName = "dotfiles_backup_" + + QDateTime::currentDateTime() + .toString( "yyyy-MM-dd_HH-mm-ss" ); + existingHome.mkdir( backupDirName ); + + CalamaresUtils::System::instance()-> + targetEnvCall( { "sh", + "-c", + "mv -f " + + shellFriendlyHome + "/.* " + + shellFriendlyHome + "/" + + backupDirName + } ); + } + } + + int ec = CalamaresUtils::System::instance()-> + targetEnvCall( { "useradd", + "-m", + "-s", + m_shell, + "-U", + "-G", + defaultGroups, + "-c", + m_fullName, + m_userName } ); + if ( ec ) + return Calamares::JobResult::error( tr( "Cannot create user %1." ) + .arg( m_userName ), + tr( "useradd terminated with error code %1." ) + .arg( ec ) ); + + ec = CalamaresUtils::System::instance()-> + targetEnvCall( { "chown", + "-R", + QString( "%1:%2" ).arg( m_userName ) + .arg( m_userName ), + QString( "/home/%1" ).arg( m_userName ) } ); + if ( ec ) + return Calamares::JobResult::error( tr( "Cannot set home directory ownership for user %1." ) + .arg( m_userName ), + tr( "chown terminated with error code %1." ) + .arg( ec ) ); + + return Calamares::JobResult::ok(); +} diff --git a/src/modules/users/CreateUserJob.h b/src/modules/users/jobs/CreateUserJob.h similarity index 95% rename from src/modules/users/CreateUserJob.h rename to src/modules/users/jobs/CreateUserJob.h index d32f12210..6ebb993d0 100644 --- a/src/modules/users/CreateUserJob.h +++ b/src/modules/users/jobs/CreateUserJob.h @@ -29,6 +29,7 @@ class CreateUserJob : public Calamares::Job public: CreateUserJob( const QString& userName, const QString& fullName, + const QString& shell, bool autologin, const QStringList& defaultGroups ); QString prettyName() const override; @@ -39,6 +40,7 @@ public: private: QString m_userName; QString m_fullName; + QString m_shell; bool m_autologin; QStringList m_defaultGroups; }; diff --git a/src/modules/users/jobs/SetAvatarJob.cpp b/src/modules/users/jobs/SetAvatarJob.cpp new file mode 100644 index 000000000..6a582f1c8 --- /dev/null +++ b/src/modules/users/jobs/SetAvatarJob.cpp @@ -0,0 +1,85 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Lisa Vitolo + * + * 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 + +#include "GlobalStorage.h" +#include "utils/Logger.h" +#include "JobQueue.h" + +#include +#include + +SetAvatarJob::SetAvatarJob(const QString &avatarFile, const QString &destPath) + : Calamares::Job() + , m_avatarFile(avatarFile) + , m_destPath(destPath) +{ +} + +QString SetAvatarJob::prettyName() const +{ + return tr( "Set avatar %1" ).arg( m_avatarFile ); +} + + +QString +SetAvatarJob::prettyDescription() const +{ + return tr( "Set avatar from %1." ).arg( m_avatarFile ); +} + + +QString +SetAvatarJob::prettyStatusMessage() const +{ + return tr( "Setting avatar from %1." ).arg( m_avatarFile ); +} + +Calamares::JobResult SetAvatarJob::exec() +{ + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + + if ( !gs || !gs->contains( "rootMountPoint" ) ) + { + cLog() << "No rootMountPoint in global storage"; + return Calamares::JobResult::error( tr( "Internal Error" ) ); + } + + QString destDir = gs->value( "rootMountPoint" ).toString(); + if ( !QDir( destDir ).exists() ) + { + cLog() << "rootMountPoint points to a dir which does not exist"; + return Calamares::JobResult::error( tr( "Internal Error" ) ); + } + + QString destination( destDir + m_destPath ); + QFile avatarFile( m_avatarFile ); + + if (!avatarFile.exists()) { + cLog() << "Avatar file does not exist"; + return Calamares::JobResult::error( tr("Avatar file does not exist") ); + } + + if (!avatarFile.copy(destination)) { + cLog() << "Error copying:" << avatarFile.errorString(); + return Calamares::JobResult::error( tr("Error copying") ); + } + + return Calamares::JobResult::ok(); +} diff --git a/src/modules/users/jobs/SetAvatarJob.h b/src/modules/users/jobs/SetAvatarJob.h new file mode 100644 index 000000000..fddf42a99 --- /dev/null +++ b/src/modules/users/jobs/SetAvatarJob.h @@ -0,0 +1,39 @@ +/* === This file is part of Calamares - === + * + * Copyright 2016, Lisa Vitolo + * + * 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 SETAVATARJOB_CPP_H +#define SETAVATARJOB_CPP_H + +#include + +class SetAvatarJob : public Calamares::Job +{ + Q_OBJECT +public: + SetAvatarJob( const QString& avatarFile, const QString& destPath ); + QString prettyName() const override; + QString prettyDescription() const override; + QString prettyStatusMessage() const override; + Calamares::JobResult exec() override; +private: + const QString m_avatarFile; + const QString m_destPath; +}; + + +#endif // SETAVATARJOB_CPP_H diff --git a/src/modules/users/SetHostNameJob.cpp b/src/modules/users/jobs/SetHostNameJob.cpp similarity index 98% rename from src/modules/users/SetHostNameJob.cpp rename to src/modules/users/jobs/SetHostNameJob.cpp index 20f6c09db..5833d9602 100644 --- a/src/modules/users/SetHostNameJob.cpp +++ b/src/modules/users/jobs/SetHostNameJob.cpp @@ -17,7 +17,7 @@ * along with Calamares. If not, see . */ -#include "SetHostNameJob.h" +#include #include "GlobalStorage.h" #include "utils/Logger.h" diff --git a/src/modules/users/SetHostNameJob.h b/src/modules/users/jobs/SetHostNameJob.h similarity index 100% rename from src/modules/users/SetHostNameJob.h rename to src/modules/users/jobs/SetHostNameJob.h diff --git a/src/modules/users/SetPasswordJob.cpp b/src/modules/users/jobs/SetPasswordJob.cpp similarity index 98% rename from src/modules/users/SetPasswordJob.cpp rename to src/modules/users/jobs/SetPasswordJob.cpp index 4828aec93..d3d4b3669 100644 --- a/src/modules/users/SetPasswordJob.cpp +++ b/src/modules/users/jobs/SetPasswordJob.cpp @@ -16,7 +16,7 @@ * along with Calamares. If not, see . */ -#include +#include #include "JobQueue.h" #include "GlobalStorage.h" diff --git a/src/modules/users/SetPasswordJob.h b/src/modules/users/jobs/SetPasswordJob.h similarity index 100% rename from src/modules/users/SetPasswordJob.h rename to src/modules/users/jobs/SetPasswordJob.h diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui deleted file mode 100644 index 67c7f618c..000000000 --- a/src/modules/users/page_usersetup.ui +++ /dev/null @@ -1,609 +0,0 @@ - - - Page_UserSetup - - - - 0 - 0 - 862 - 683 - - - - Form - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 6 - - - - - - - - What is your name? - - - - - - - - - - 200 - 0 - - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - - - - true - - - - - - - - 1 - 0 - - - - - - - true - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 6 - - - - - - - - What name do you want to use to log in? - - - false - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - true - - - - - - - - 1 - 0 - - - - - 200 - 0 - - - - - - - Qt::AlignVCenter - - - true - - - - - - - - - font-weight: normal - - - <small>If more than one person will use this computer, you can set up multiple accounts after installation.</small> - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 6 - - - - - - - - What is the name of this computer? - - - false - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - true - - - - - - - - 1 - 0 - - - - - 200 - 0 - - - - - - - Qt::AlignVCenter - - - true - - - - - - - - - font-weight: normal - - - <small>This name will be used if you make the computer visible to others on a network.</small> - - - false - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 6 - - - - - - - - Choose a password to keep your account safe. - - - false - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - true - - - - - - - - 1 - 0 - - - - - 100 - 0 - - - - - - - Qt::AlignVCenter - - - true - - - - - - - - - font-weight: normal - - - <small>Enter the same password twice, so that it can be checked for typing errors. A good password will contain a mixture of letters, numbers and punctuation, should be at least eight characters long, and should be changed at regular intervals.</small> - - - true - - - - - - - Log in automatically without asking for the password. - - - - - - - Use the same password for the administrator account. - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 6 - - - - - - - - Choose a password for the administrator account. - - - false - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - true - - - - - - - - 1 - 0 - - - - - 100 - 0 - - - - - - - Qt::AlignVCenter - - - true - - - - - - - - - font-weight: normal - - - <small>Enter the same password twice, so that it can be checked for typing errors.</small> - - - true - - - - - - - Qt::Vertical - - - - 20 - 1 - - - - - - - - - diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 34a5fcfc9..f583f16ec 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -8,7 +8,7 @@ defaultGroups: - wheel - audio autologinGroup: autologin -doAutologin: true +doAutologin: false sudoersGroup: wheel -setRootPassword: true -doReusePassword: true +setRootPassword: false +availableShells: /bin/bash, /bin/zsh diff --git a/src/modules/users/users.qrc b/src/modules/users/users.qrc index 70392c32b..be034adfa 100644 --- a/src/modules/users/users.qrc +++ b/src/modules/users/users.qrc @@ -2,5 +2,7 @@ images/invalid.png images/valid.png + images/arrow-down.png + images/arrow-up.png