mirror of https://github.com/cutefishos/calamares
Merge branch 'issue-1462' into calamares
This does about half of the move-settings-from-Widget-internals to Config. By having the configuration **and** the business logic in a Config object, we can hook up other UIs more easily while preserving the business logic. (e.g. this is a prerequisite for QML uis, but also for scripting and quickstart logic). SEE #1462main
commit
506ea39508
@ -0,0 +1,372 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/String.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QRegExp>
|
||||
#include <QRegExpValidator>
|
||||
|
||||
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 )
|
||||
{
|
||||
}
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
void
|
||||
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 );
|
||||
}
|
||||
|
||||
static inline void
|
||||
insertInGlobalStorage( 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 )
|
||||
{
|
||||
insertInGlobalStorage( QStringLiteral( "autologinGroup" ), group );
|
||||
emit autologinGroupChanged( group );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setSudoersGroup( const QString& group )
|
||||
{
|
||||
insertInGlobalStorage( QStringLiteral( "sudoersGroup" ), group );
|
||||
emit sudoersGroupChanged( group );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
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 );
|
||||
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();
|
||||
}
|
||||
|
||||
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." );
|
||||
}
|
||||
if ( validateEntireLoginName.validate( login, pos ) == QValidator::Invalid )
|
||||
{
|
||||
return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." );
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void
|
||||
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 );
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
/** @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() || parts.first().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();
|
||||
}
|
||||
|
||||
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::setFullName( const QString& name )
|
||||
{
|
||||
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 fullNameChanged( 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 );
|
||||
emit loginNameStatusChanged( loginNameStatus() );
|
||||
}
|
||||
}
|
||||
if ( !m_customHostName )
|
||||
{
|
||||
QString hostname = makeHostnameSuggestion( cleanParts );
|
||||
if ( !hostname.isEmpty() && hostname != m_hostName )
|
||||
{
|
||||
m_hostName = hostname;
|
||||
emit hostNameChanged( hostname );
|
||||
emit hostNameStatusChanged( hostNameStatus() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
setAutologinGroup( CalamaresUtils::getString( configurationMap, "autologinGroup" ) );
|
||||
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 );
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef USERS_CONFIG_H
|
||||
#define USERS_CONFIG_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
class Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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 )
|
||||
|
||||
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 )
|
||||
|
||||
Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged )
|
||||
Q_PROPERTY( QString hostNameStatus READ hostNameStatus NOTIFY hostNameStatusChanged )
|
||||
|
||||
public:
|
||||
Config( QObject* parent = nullptr );
|
||||
~Config();
|
||||
|
||||
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; }
|
||||
|
||||
/// 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; }
|
||||
|
||||
/// The full (GECOS) name of the user
|
||||
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; }
|
||||
/// Status message about hostname -- empty for "ok"
|
||||
QString hostNameStatus() const;
|
||||
|
||||
/// 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();
|
||||
|
||||
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 );
|
||||
|
||||
/// Sets the autologin group; empty is ignored
|
||||
void setAutologinGroup( const QString& group );
|
||||
/// Sets the sudoer group; empty is ignored
|
||||
void setSudoersGroup( const QString& group );
|
||||
|
||||
/// Sets the full name, may guess a loginName
|
||||
void setFullName( const QString& name );
|
||||
/// 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 );
|
||||
|
||||
/// Sets the autologin flag
|
||||
void setAutoLogin( bool b );
|
||||
|
||||
signals:
|
||||
void userShellChanged( const QString& );
|
||||
void autologinGroupChanged( const QString& );
|
||||
void sudoersGroupChanged( const QString& );
|
||||
void fullNameChanged( const QString& );
|
||||
void loginNameChanged( const QString& );
|
||||
void loginNameStatusChanged( const QString& );
|
||||
void hostNameChanged( const QString& );
|
||||
void hostNameStatusChanged( const QString& );
|
||||
void autoLoginChanged( bool );
|
||||
|
||||
private:
|
||||
QString m_userShell;
|
||||
QString m_autologinGroup;
|
||||
QString m_sudoersGroup;
|
||||
QString m_fullName;
|
||||
QString m_loginName;
|
||||
QString m_hostName;
|
||||
bool m_doAutoLogin = false;
|
||||
bool m_writeRootPassword = true;
|
||||
|
||||
bool m_customLoginName = false;
|
||||
bool m_customHostName = false;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue