Merge branch 'calamares' into work-3.3

This commit is contained in:
Adriaan de Groot 2022-05-09 15:03:41 +02:00
commit bbeba5c9ab
20 changed files with 273 additions and 35 deletions

View File

@ -8,6 +8,25 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions. website will have to do for older versions.
# 3.2.58 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Enrique Medina Gremaldos
## Core ##
- Internal improvements to translations-setup means that Catalan (in the
Valencian dialect), Occitan (Lenga d'Oc) and Serbian (in Latin script)
are all better supported. Thanks Enrique.
## Modules ##
- *users* module now has a structured *user* key with settings specific
to the user (shell, in particular). This maintains backwards compatibility
with the *userShell* key.
- *users* module now has lists of forbidden login- and host-names, to
avoid settings that will mess up the install (e.g. using a login-name
that is one of the system's reserved names). #1944
# 3.2.57 (2022-05-04) # # 3.2.57 (2022-05-04) #
This release contains contributions from (alphabetically by first name): This release contains contributions from (alphabetically by first name):

View File

@ -818,6 +818,7 @@ class DMgreetd(DisplayManager):
def desktop_environment_setup(self, default_desktop_environment): def desktop_environment_setup(self, default_desktop_environment):
with open(self.environments_path(), 'w') as envs_file: with open(self.environments_path(), 'w') as envs_file:
envs_file.write(default_desktop_environment.desktop_file) envs_file.write(default_desktop_environment.desktop_file)
envs_file.write("\n")
def greeter_setup(self): def greeter_setup(self):
pass pass

View File

@ -201,10 +201,9 @@ Config::setLoginName( const QString& login )
} }
const QStringList& const QStringList&
Config::forbiddenLoginNames() Config::forbiddenLoginNames() const
{ {
static QStringList forbidden { "root" }; return m_forbiddenLoginNames;
return forbidden;
} }
QString QString
@ -220,13 +219,6 @@ Config::loginNameStatus() const
{ {
return tr( "Your username is too long." ); 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 );
}
}
QRegExp validateFirstLetter( "^[a-z_]" ); QRegExp validateFirstLetter( "^[a-z_]" );
if ( validateFirstLetter.indexIn( m_loginName ) != 0 ) if ( validateFirstLetter.indexIn( m_loginName ) != 0 )
@ -238,6 +230,12 @@ Config::loginNameStatus() const
return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." ); return tr( "Only lowercase letters, numbers, underscore and hyphen are allowed." );
} }
// Although we've made the list lower-case, and the RE above forces lower-case, still pass the flag
if ( forbiddenLoginNames().contains( m_loginName, Qt::CaseInsensitive ) )
{
return tr( "'%1' is not allowed as username." ).arg( m_loginName );
}
return QString(); return QString();
} }
@ -268,10 +266,9 @@ Config::setHostName( const QString& host )
} }
const QStringList& const QStringList&
Config::forbiddenHostNames() Config::forbiddenHostNames() const
{ {
static QStringList forbidden { "localhost" }; return m_forbiddenHostNames;
return forbidden;
} }
QString QString
@ -291,12 +288,11 @@ Config::hostnameStatus() const
{ {
return tr( "Your hostname is too long." ); return tr( "Your hostname is too long." );
} }
for ( const QString& badName : forbiddenHostNames() )
// "LocalHost" is just as forbidden as "localhost"
if ( forbiddenHostNames().contains( m_hostname, Qt::CaseInsensitive ) )
{ {
if ( 0 == QString::compare( badName, m_hostname, Qt::CaseSensitive ) ) return tr( "'%1' is not allowed as hostname." ).arg( m_hostname );
{
return tr( "'%1' is not allowed as hostname." ).arg( badName );
}
} }
if ( !HOSTNAME_RX.exactMatch( m_hostname ) ) if ( !HOSTNAME_RX.exactMatch( m_hostname ) )
@ -881,16 +877,41 @@ copyLegacy( const QVariantMap& source, const QString& sourceKey, QVariantMap& ta
} }
} }
/** @brief Tidy up a list of names
*
* Remove duplicates, apply lowercase, sort.
*/
static void
tidy( QStringList& l )
{
std::for_each( l.begin(), l.end(), []( QString& s ) { s = s.toLower(); } );
l.sort();
l.removeDuplicates();
}
void void
Config::setConfigurationMap( const QVariantMap& configurationMap ) Config::setConfigurationMap( const QVariantMap& configurationMap )
{ {
QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all // Handle *user* key and subkeys and legacy settings
if ( configurationMap.contains( "userShell" ) )
{ {
shell = CalamaresUtils::getString( configurationMap, "userShell" ); bool ok = false; // Ignored
QVariantMap userSettings = CalamaresUtils::getSubMap( configurationMap, "user", ok );
// TODO:3.3: Remove calls to copyLegacy
copyLegacy( configurationMap, "userShell", userSettings, "shell" );
QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all
if ( userSettings.contains( "shell" ) )
{
shell = CalamaresUtils::getString( userSettings, "shell" );
}
// Now it might be explicitly set to empty, which is ok
setUserShell( shell );
m_forbiddenLoginNames = CalamaresUtils::getStringList( userSettings, "forbidden_names" );
m_forbiddenLoginNames << QStringLiteral( "root" ) << QStringLiteral( "nobody" );
tidy( m_forbiddenLoginNames );
} }
// Now it might be explicitly set to empty, which is ok
setUserShell( shell );
setAutoLoginGroup( either< QString, const QString& >( setAutoLoginGroup( either< QString, const QString& >(
CalamaresUtils::getString, configurationMap, "autologinGroup", "autoLoginGroup", QString() ) ); CalamaresUtils::getString, configurationMap, "autologinGroup", "autoLoginGroup", QString() ) );
@ -911,6 +932,10 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
m_writeEtcHosts = CalamaresUtils::getBool( hostnameSettings, "writeHostsFile", true ); m_writeEtcHosts = CalamaresUtils::getBool( hostnameSettings, "writeHostsFile", true );
m_hostnameTemplate m_hostnameTemplate
= CalamaresUtils::getString( hostnameSettings, "template", QStringLiteral( "${first}-${product}" ) ); = CalamaresUtils::getString( hostnameSettings, "template", QStringLiteral( "${first}-${product}" ) );
m_forbiddenHostNames = CalamaresUtils::getStringList( hostnameSettings, "forbidden_names" );
m_forbiddenHostNames << QStringLiteral( "localhost" );
tidy( m_forbiddenHostNames );
} }
setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); setConfigurationDefaultGroups( configurationMap, m_defaultGroups );

View File

@ -252,8 +252,8 @@ public:
bool isReady() const; bool isReady() const;
static const QStringList& forbiddenLoginNames(); const QStringList& forbiddenLoginNames() const;
static const QStringList& forbiddenHostNames(); const QStringList& forbiddenHostNames() const;
public Q_SLOTS: public Q_SLOTS:
/** @brief Sets the user's shell if possible /** @brief Sets the user's shell if possible
@ -347,6 +347,9 @@ private:
bool m_writeEtcHosts = false; bool m_writeEtcHosts = false;
QString m_hostnameTemplate; QString m_hostnameTemplate;
QStringList m_forbiddenHostNames;
QStringList m_forbiddenLoginNames;
PasswordCheckList m_passwordChecks; PasswordCheckList m_passwordChecks;
}; };

View File

@ -53,6 +53,9 @@ private Q_SLOTS:
void testAutoLogin_data(); void testAutoLogin_data();
void testAutoLogin(); void testAutoLogin();
void testUserYAML_data();
void testUserYAML();
}; };
UserTests::UserTests() {} UserTests::UserTests() {}
@ -298,7 +301,8 @@ UserTests::testHostSuggestions_data()
QTest::newRow( "full " ) << QStringLiteral( "${name}" ) << QStringLiteral( "chuckyeager" ); QTest::newRow( "full " ) << QStringLiteral( "${name}" ) << QStringLiteral( "chuckyeager" );
QTest::newRow( "login+ " ) << QStringLiteral( "${login}-${first}" ) << QStringLiteral( "bill-chuck" ); QTest::newRow( "login+ " ) << QStringLiteral( "${login}-${first}" ) << QStringLiteral( "bill-chuck" );
// This is a bit dodgy: assumes CPU architecture of the testing host // This is a bit dodgy: assumes CPU architecture of the testing host
QTest::newRow( " cpu " ) << QStringLiteral( "${cpu}X" ) << QStringLiteral( "x8664X" ); // Assume we don't test on non-amd64 QTest::newRow( " cpu " ) << QStringLiteral( "${cpu}X" )
<< QStringLiteral( "x8664X" ); // Assume we don't test on non-amd64
// These have X X in the template to indicate that they are bogus. Mostly we want // These have X X in the template to indicate that they are bogus. Mostly we want
// to see what the template engine does for these. // to see what the template engine does for these.
QTest::newRow( "@prod " ) << QStringLiteral( "X${product}X" ) << QString(); QTest::newRow( "@prod " ) << QStringLiteral( "X${product}X" ) << QString();
@ -315,10 +319,11 @@ UserTests::testHostSuggestions()
QFETCH( QString, templateString ); QFETCH( QString, templateString );
QFETCH( QString, result ); QFETCH( QString, result );
if ( templateString.startsWith('X') && templateString.endsWith('X')) if ( templateString.startsWith( 'X' ) && templateString.endsWith( 'X' ) )
{ {
QEXPECT_FAIL( "", "Test is too host-specific", Continue ); QEXPECT_FAIL( "", "Test is too host-specific", Continue );
cWarning() << Logger::SubEntry << "Next test" << templateString << "->" << makeHostnameSuggestion( templateString, fullName, login ); cWarning() << Logger::SubEntry << "Next test" << templateString << "->"
<< makeHostnameSuggestion( templateString, fullName, login );
} }
QCOMPARE( makeHostnameSuggestion( templateString, fullName, login ), result ); QCOMPARE( makeHostnameSuggestion( templateString, fullName, login ), result );
} }
@ -453,6 +458,58 @@ UserTests::testAutoLogin()
QCOMPARE( c.autoLoginGroup(), autoLoginGroupName ); QCOMPARE( c.autoLoginGroup(), autoLoginGroupName );
} }
void
UserTests::testUserYAML_data()
{
QTest::addColumn< QString >( "filename" );
QTest::addColumn< QString >( "shell" );
QTest::newRow( "old, unset " ) << "tests/7ao-shell.conf"
<< "/bin/bash";
QTest::newRow( "old, empty " ) << "tests/7bo-shell.conf"
<< "";
QTest::newRow( "old, relative" ) << "tests/7co-shell.conf"
<< "/bin/ls"; // Setting is ignored
QTest::newRow( "old, invalid " ) << "tests/7do-shell.conf"
<< "";
QTest::newRow( "old, absolute" ) << "tests/7eo-shell.conf"
<< "/usr/bin/dash";
QTest::newRow( "new, unset " ) << "tests/7an-shell.conf"
<< "/bin/bash";
QTest::newRow( "new, empty " ) << "tests/7bn-shell.conf"
<< "";
QTest::newRow( "new, relative" ) << "tests/7cn-shell.conf"
<< "/bin/ls"; // Setting is ignored
QTest::newRow( "new, invalid " ) << "tests/7dn-shell.conf"
<< "";
QTest::newRow( "new, absolute" ) << "tests/7en-shell.conf"
<< "/usr/bin/dash";
}
void
UserTests::testUserYAML()
{
Config c;
c.setUserShell( QStringLiteral( "/bin/ls" ) );
QFETCH( QString, filename );
QFETCH( QString, shell );
// BUILD_AS_TEST is the source-directory path
QFile fi( QString( "%1/%2" ).arg( BUILD_AS_TEST, filename ) );
QVERIFY( fi.exists() );
bool ok = false;
const auto map = CalamaresUtils::loadYaml( fi, &ok );
QVERIFY( ok );
QVERIFY( map.count() > 0 );
QCOMPARE( c.userShell(), QStringLiteral( "/bin/ls" ) );
c.setConfigurationMap( map );
QCOMPARE( c.userShell(), shell );
}
QTEST_GUILESS_MAIN( UserTests ) QTEST_GUILESS_MAIN( UserTests )

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Unset (bogus needed to keep it valid YAML)
user:
# shell: /usr/bin/dash
bogus: true

View File

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Unset (bogus needed to keep it valid YAML)
# userShell: /usr/bin/dash
bogus: true

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly empty
user:
shell: ""

View File

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly empty
userShell: ""

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Non-absolute path is ignored
user:
shell: dash

View File

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Non-absolute path is ignored
userShell: dash

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Invalid setting (should be string), won't pass validation
user:
shell: [1]

View File

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Invalid setting (should be string), won't pass validation
userShell: [1]

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly set with full path
user:
shell: /usr/bin/dash

View File

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly set with full path
userShell: /usr/bin/dash

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly set with full path
user:
shell: /usr/bin/new
bogus: true
userShell: /usr/bin/old

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly set with full path
user:
shell: /usr/bin/new
bogus: true
# userShell: /usr/bin/old

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
---
# Explicitly set with full path
user:
# shell: /usr/bin/new
bogus: true
userShell: /usr/bin/old

View File

@ -140,13 +140,30 @@ allowWeakPasswords: false
# to be unchecked. # to be unchecked.
allowWeakPasswordsDefault: false allowWeakPasswordsDefault: false
# Shell to be used for the regular user of the target system. # User settings
# There are three possible kinds of settings: #
# - unset (i.e. commented out, the default), act as if set to /bin/bash # The user can enter a username, but there are some other
# - empty (explicit), don't pass shell information to useradd at all # hidden settings for the user which are configurable in Calamares.
# and rely on a correct configuration file in /etc/default/useradd #
# - set, non-empty, use that path as shell. No validation is done # Key *user* has the following sub-keys:
# that the shell actually exists or is executable. #
# - *shell* Shell to be used for the regular user of the target system.
# There are three possible kinds of settings:
# - unset (i.e. commented out, the default), act as if set to /bin/bash
# - empty (explicit), don't pass shell information to useradd at all
# and rely on a correct configuration file in /etc/default/useradd
# - set, non-empty, use that path as shell. No validation is done
# that the shell actually exists or is executable.
# - *forbidden_names* Login names that may not be used. This list always
# contains "root" and "nobody", but may be extended to list other special
# names for a given distro (eg. "video", or "mysql" might not be a valid
# end-user login name).
user:
shell: /bin/bash
forbidden_names: [ root ]
# TODO:3.3: Remove this setting
#
# This is the legacy setting for user.shell
userShell: /bin/bash userShell: /bin/bash
# Hostname settings # Hostname settings
@ -186,10 +203,14 @@ userShell: /bin/bash
# `${key}` values to something that will fit in a hostname, but does not # `${key}` values to something that will fit in a hostname, but does not
# apply the same to literal text in the template. Do not use invalid # apply the same to literal text in the template. Do not use invalid
# characters in the literal text, or no suggeston will be done. # characters in the literal text, or no suggeston will be done.
# - *forbidden_names* lists hostnames that may not be used. This list
# always contains "localhost", but may list others that are unsuitable
# or broken in special ways.
hostname: hostname:
location: EtcFile location: EtcFile
writeHostsFile: true writeHostsFile: true
template: "derp-${cpu}" template: "derp-${cpu}"
forbidden_names: [ localhost ]
# TODO:3.3: Remove this setting # TODO:3.3: Remove this setting
# #

View File

@ -8,6 +8,12 @@ type: object
properties: properties:
# User shell, should be path to /bin/sh or so # User shell, should be path to /bin/sh or so
userShell: { type: string } userShell: { type: string }
user:
additionalProperties: false
type: object
properties:
shell: { type: string } # Overrides userShell
forbidden_names: { type: array, items: { type: string } }
# Group settings # Group settings
defaultGroups: defaultGroups:
type: array type: array
@ -47,6 +53,7 @@ properties:
location: { type: string, enum: [ None, EtcFile, Hostnamed, Transient ] } location: { type: string, enum: [ None, EtcFile, Hostnamed, Transient ] }
writeHostsFile: { type: boolean, default: true } writeHostsFile: { type: boolean, default: true }
template: { type: string, default: "${first}-${product}" } template: { type: string, default: "${first}-${product}" }
forbidden_names: { type: array, items: { type: string } }
# Legacy Hostname setting # Legacy Hostname setting
setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] } setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] }
writeHostsFile: { type: boolean, default: true } writeHostsFile: { type: boolean, default: true }