diff --git a/src/modules/usersq/CMakeLists.txt b/src/modules/usersq/CMakeLists.txt new file mode 100644 index 000000000..d780ec9c4 --- /dev/null +++ b/src/modules/usersq/CMakeLists.txt @@ -0,0 +1,45 @@ +if( NOT WITH_QML ) + calamares_skip_module( "usersq (QML is not supported in this build)" ) + return() +endif() + +find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED Core DBus Network ) +find_package( Crypt REQUIRED ) + +# Add optional libraries here +set( USER_EXTRA_LIB ) +set( _users ${CMAKE_CURRENT_SOURCE_DIR}/../users ) + +include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui ${CMAKE_CURRENT_SOURCE_DIR}/../../libcalamares ${_users} ) + +find_package( LibPWQuality ) +set_package_properties( + LibPWQuality PROPERTIES + PURPOSE "Extra checks of password quality" +) + +if( LibPWQuality_FOUND ) + list( APPEND USER_EXTRA_LIB ${LibPWQuality_LIBRARIES} ) + include_directories( ${LibPWQuality_INCLUDE_DIRS} ) + add_definitions( -DCHECK_PWQUALITY -DHAVE_LIBPWQUALITY ) +endif() + +calamares_add_plugin( usersq + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ${_users}/Config.cpp + ${_users}/CreateUserJob.cpp + ${_users}/SetPasswordJob.cpp + UsersQmlViewStep.cpp + ${_users}/SetHostNameJob.cpp + ${_users}/CheckPWQuality.cpp + RESOURCES + usersq.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + ${CRYPT_LIBRARIES} + ${USER_EXTRA_LIB} + Qt5::DBus + SHARED_LIB +) diff --git a/src/modules/usersq/UsersQmlViewStep.cpp b/src/modules/usersq/UsersQmlViewStep.cpp new file mode 100644 index 000000000..bb5fbd9a6 --- /dev/null +++ b/src/modules/usersq/UsersQmlViewStep.cpp @@ -0,0 +1,199 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2017-2018, Adriaan de Groot + * Copyright 2017, Gabriel Craciunescu + * Copyright 2020, Camilo Higuita + * + * 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 "UsersQmlViewStep.h" + +#include "SetHostNameJob.h" +#include "SetPasswordJob.h" + +#include "utils/Logger.h" +#include "utils/NamedEnum.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" + +CALAMARES_PLUGIN_FACTORY_DEFINITION( UsersQmlViewStepFactory, registerPlugin< UsersQmlViewStep >(); ) + +/*static const NamedEnumTable< SetHostNameJob::Action >& +hostnameActions() +{ + using Action = SetHostNameJob::Action; + + // *INDENT-OFF* + // clang-format off + static const NamedEnumTable< Action > names { + { QStringLiteral( "none" ), Action::None }, + { QStringLiteral( "etcfile" ), Action::EtcHostname }, + { QStringLiteral( "hostnamed" ), Action::SystemdHostname } + }; + // clang-format on + // *INDENT-ON* + + return names; +}*/ + +UsersQmlViewStep::UsersQmlViewStep( QObject* parent ) +: Calamares::QmlViewStep( parent ) +, m_config( new Config(this) ) +{ + emit nextStatusChanged( true ); + //connect( m_config, &Config::checkReady, this, &UsersQmlViewStep::nextStatusChanged ); +} + +QString +UsersQmlViewStep::prettyName() const +{ + return tr( "Users" ); +} + +bool +UsersQmlViewStep::isNextEnabled() const +{ + //return m_config->isReady(); + return true; +} + + +bool +UsersQmlViewStep::isBackEnabled() const +{ + return true; +} + + +bool +UsersQmlViewStep::isAtBeginning() const +{ + return true; +} + + +bool +UsersQmlViewStep::isAtEnd() const +{ + return true; +} + + +QList< Calamares::job_ptr > +UsersQmlViewStep::jobs() const +{ + return m_jobs; +} + + +void +UsersQmlViewStep::onActivate() +{ + //m_config->onActivate(); +} + + +void +UsersQmlViewStep::onLeave() +{ + m_jobs.clear(); + + //m_jobs.append( m_config->createJobs( m_defaultGroups ) ); +} + + +void +UsersQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + using CalamaresUtils::getBool; + + if ( configurationMap.contains( "defaultGroups" ) + && configurationMap.value( "defaultGroups" ).type() == QVariant::List ) + { + m_defaultGroups = configurationMap.value( "defaultGroups" ).toStringList(); + } + else + { + cWarning() << "Using fallback groups. Please check defaultGroups in users.conf"; + 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 ); + + //m_config->writeRootPassword( setRootPassword ); + //m_config->setAutologinGroup( getBool( configurationMap, "doAutologin", false ) ); + //m_config->setReusePasswordDefault( getBool( configurationMap, "doReusePassword", false ) ); + + if ( configurationMap.contains( "passwordRequirements" ) + && configurationMap.value( "passwordRequirements" ).type() == QVariant::Map ) + { + auto pr_checks( configurationMap.value( "passwordRequirements" ).toMap() ); + + for ( decltype( pr_checks )::const_iterator i = pr_checks.constBegin(); i != pr_checks.constEnd(); ++i ) + { + //m_config->passwordChecks( i.key(), i.value() ); + } + } + + //m_config->setPasswordCheckboxVisible( getBool( configurationMap, "allowWeakPasswords", false ) ); + //m_config->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" ); + if ( hostnameActionString.isEmpty() ) + { + hostnameActionString = QStringLiteral( "EtcFile" ); + } + bool ok = false; + auto hostnameAction = hostnameActions().find( hostnameActionString, ok ); + if ( !ok ) + { + hostnameAction = Action::EtcHostname; + } + + Action hostsfileAction = getBool( configurationMap, "writeHostsFile", true ) ? Action::WriteEtcHosts : Action::None; + m_actions = hostsfileAction | hostnameAction;*/ + + Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last + setContextProperty( "Users", m_config ); +} diff --git a/src/modules/usersq/UsersQmlViewStep.h b/src/modules/usersq/UsersQmlViewStep.h new file mode 100644 index 000000000..c40b35f47 --- /dev/null +++ b/src/modules/usersq/UsersQmlViewStep.h @@ -0,0 +1,72 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot + * Copyright 2020, Camilo Higuita + * + * 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 USERSQMLVIEWSTEP_H +#define USERSQMLVIEWSTEP_H + +#include +//#include "SetHostNameJob.h" + +#include +#include + +#include + +#include +#include "Config.h" + +class PLUGINDLLEXPORT UsersQmlViewStep : public Calamares::QmlViewStep +{ + Q_OBJECT + +public: + explicit UsersQmlViewStep( QObject* parent = nullptr ); + + QString prettyName() const override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + QList< Calamares::job_ptr > jobs() const override; + + void onActivate() override; + void onLeave() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + + QObject * getConfig() override + { + return m_config; + } + +private: + Config *m_config; + QList< Calamares::job_ptr > m_jobs; + + QStringList m_defaultGroups; + //SetHostNameJob::Actions m_actions; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( UsersQmlViewStepFactory ) + +#endif // USERSQMLVIEWSTEP_H diff --git a/src/modules/usersq/usersq.conf b/src/modules/usersq/usersq.conf new file mode 100644 index 000000000..f416a5c39 --- /dev/null +++ b/src/modules/usersq/usersq.conf @@ -0,0 +1,42 @@ +# For documentation see Users Module users.conf +# +--- +# Used as default groups for the created user. +# Adjust to your Distribution defaults. +defaultGroups: + - users + - lp + - video + - network + - storage + - wheel + - audio + - lpadmin + +autologinGroup: autologin + +doAutologin: true + +sudoersGroup: wheel + +setRootPassword: true + +doReusePassword: true + +passwordRequirements: + nonempty: true + minLength: -1 # Password at least this many characters + maxLength: -1 # Password at most this many characters + libpwquality: + - minlen=0 + - minclass=0 + +allowWeakPasswords: false + +allowWeakPasswordsDefault: false + +userShell: /bin/bash + +setHostname: EtcFile + +writeHostsFile: true diff --git a/src/modules/usersq/usersq.qml b/src/modules/usersq/usersq.qml new file mode 100644 index 000000000..42bb6321d --- /dev/null +++ b/src/modules/usersq/usersq.qml @@ -0,0 +1,336 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2020 Anke Boersma + * 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 . + */ + +import io.calamares.core 1.0 +import io.calamares.ui 1.0 + +import QtQuick 2.10 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.7 as Kirigami +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.3 + +Kirigami.ScrollablePage { + + width: parent.width + height: parent.height + + Kirigami.Theme.backgroundColor: "#EFF0F1" + Kirigami.Theme.textColor: "#1F1F1F" + + header: Kirigami.Heading { + + Layout.fillWidth: true + height: 50 + horizontalAlignment: Qt.AlignHCenter + color: Kirigami.Theme.textColor + font.weight: Font.Medium + font.pointSize: 12 + text: qsTr("Pick your user name and credentials to login and perform admin tasks") + } + + ColumnLayout { + + id: _formLayout + spacing: Kirigami.Units.smallSpacing + + Column { + + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Label { + + width: parent.width + text: qsTr("What is your name?") + } + + TextField { + + id: _userNameField + width: parent.width + placeholderText: qsTr("Your Full Name") + onTextChanged: config.fullNameChanged(text) + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + radius: 2 + opacity: 0.9 + //border.color: _userNameField.text === "" ? Kirigami.Theme.backgroundColor : ( config.fullNameReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _userNameField.text === "" ? "#FBFBFB" : ( config.fullNameReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + } + + Column { + + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Label { + + width: parent.width + text: qsTr("What name do you want to use to log in?") + } + + TextField { + + id: _userLoginField + width: parent.width + placeholderText: qsTr("Login Name") + //text: config.userName + onTextEdited: config.loginNameChanged(text) + + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _userLoginField.text === "" ? Kirigami.Theme.backgroundColor : ( config.userNameReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _userLoginField.text === "" ? "#FBFBFB" : ( config.userNameReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + + Label { + + width: parent.width + text: qsTr("If more than one person will use this computer, you can create multiple accounts after installation.") + font.weight: Font.Thin + font.pointSize: 8 + color: "#6D6D6D" + } + } + + Column { + + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Label { + + width: parent.width + text: qsTr("What is the name of this computer?") + } + + TextField { + + id: _hostName + width: parent.width + placeholderText: qsTr("Computer Name") + text: config.hostName + onTextEdited: config.hostNameChanged(text) + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _hostName.text === "" ? Kirigami.Theme.backgroundColor : ( config.hostNameReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _hostName.text === "" ? "#FBFBFB" : ( config.hostNameReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + + Label { + + width: parent.width + text: qsTr("This name will be used if you make the computer visible to others on a network.") + font.weight: Font.Thin + font.pointSize: 8 + color: "#6D6D6D" + } + } + + Column { + + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Label { + + width: parent.width + text: qsTr("Choose a password to keep your account safe.") + } + + Row { + width: parent.width + spacing: 20 + + TextField { + + id: _passwordField + width: parent.width / 2 - 10 + placeholderText: qsTr("Password") + echoMode: TextInput.Password + passwordMaskDelay: 300 + inputMethodHints: Qt.ImhNoAutoUppercase + onTextChanged: config.userPasswordChanged(text, _verificationPasswordField.text) + + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _passwordField.text === "" ? Kirigami.Theme.backgroundColor : ( config.passwordReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _passwordField.text === "" ? "#FBFBFB" : ( config.passwordReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + + TextField { + + id: _verificationPasswordField + width: parent.width / 2 - 10 + placeholderText: qsTr("Repeat Password") + echoMode: TextInput.Password + passwordMaskDelay: 300 + inputMethodHints: Qt.ImhNoAutoUppercase + onTextChanged: config.userPasswordSecondaryChanged(_passwordField.text, text) + + background: Rectangle { + + color: "#FBFBFB" //Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _verificationpasswordField.text === "" ? Kirigami.Theme.backgroundColor : ( config.passwordReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _verificationpasswordField.text === "" ? "#FBFBFB" : ( config.passwordReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + } + + Label { + + width: parent.width + text: qsTr("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.") + font.weight: Font.Thin + font.pointSize: 8 + wrapMode: Text.WordWrap + color: "#6D6D6D" + } + } + + CheckBox { + + visible: config.allowWeakPasswords + //visible: false + text: qsTr("Validate passwords quality") + checked: config.allowWeakPasswordsDefault + onToggled: config.allowWeakPasswordsDefault = !config.allowWeakPasswordsDefault + } + + Label { + + visible: config.allowWeakPasswords + //visible: false + width: parent.width + text: qsTr("When this box is checked, password-strength checking is done and you will not be able to use a weak password..") + font.weight: Font.Thin + font.pointSize: 8 + color: "#6D6D6D" + } + + CheckBox { + + text: qsTr("Log in automatically without asking for the password") + checked: config.doAutologin + onToggled: config.doAutologin = !config.doAutologin + } + + CheckBox { + + id: root + visible: config.doReusePassword + text: qsTr("Reuse user password as root password") + checked: config.reuseUserPasswordForRoot + //checked: false + onToggled: config.reuseUserPasswordForRoot = !config.reuseUserPasswordForRoot + } + + Label { + + visible: root.checked + width: parent.width + text: qsTr("Use the same password for the administrator account.") + font.weight: Font.Thin + font.pointSize: 8 + color: "#6D6D6D" + } + + Column { + + visible: ! root.checked + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Label { + + width: parent.width + text: qsTr("Choose a root password to keep your account safe.") + } + + Row { + width: parent.width + spacing: 20 + + TextField { + + id: _rootPasswordField + width: parent.width / 2 -10 + placeholderText: qsTr("Root Password") + echoMode: TextInput.Password + passwordMaskDelay: 300 + inputMethodHints: Qt.ImhNoAutoUppercase + onTextChanged: config.rootPasswordChanged(text, _verificationRootPasswordField.text) + + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _rootPasswordField.text === "" ? Kirigami.Theme.backgroundColor : ( config.rootPasswordReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _rootPasswordField.text === "" ? "#FBFBFB" : ( config.rootPasswordReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + + TextField { + + id: _verificationRootPasswordField + width: parent.width / 2 -10 + placeholderText: qsTr("Repeat Root Password") + echoMode: TextInput.Password + passwordMaskDelay: 300 + inputMethodHints: Qt.ImhNoAutoUppercase + onTextChanged: config.rootPasswordSecondaryChanged(_rootPasswordField.text, text) + + background: Rectangle { + + color: "#FBFBFB" // Kirigami.Theme.backgroundColor + opacity: 0.9 + //border.color: _verificationRootPasswordField.text === "" ? Kirigami.Theme.backgroundColor : ( config.rootPasswordReady ? Kirigami.Theme.backgroundColor : Kirigami.Theme.negativeTextColor) + border.color: _verificationRootPasswordField.text === "" ? "#FBFBFB" : ( config.rootPasswordReady ? "#FBFBFB" : Kirigami.Theme.negativeTextColor) + } + } + } + + Label { + + visible: ! root.checked + width: parent.width + text: qsTr("Enter the same password twice, so that it can be checked for typing errors.") + font.weight: Font.Thin + font.pointSize: 8 + color: "#6D6D6D" + } + } + } +} diff --git a/src/modules/usersq/usersq.qrc b/src/modules/usersq/usersq.qrc new file mode 100644 index 000000000..8c1c4f986 --- /dev/null +++ b/src/modules/usersq/usersq.qrc @@ -0,0 +1,5 @@ + + + usersq.qml + +