Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On systems with keychain support, automatically just create a random master authentication password and store in keychain #55144

Merged
merged 5 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/PyQt6/core/auto_generated/auth/qgsauthmanager.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and to utilize configurations through various authentication method plugins
%End
public:


enum MessageLevel /BaseType=IntEnum/
{
INFO,
Expand Down Expand Up @@ -161,6 +162,7 @@ Returns the authentication database connection URI with the password stripped.
.. versionadded:: 3.40
%End


bool setMasterPassword( bool verify = false );
%Docstring
Main call to initially set or continually check master password is set
Expand Down
2 changes: 2 additions & 0 deletions python/core/auto_generated/auth/qgsauthmanager.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and to utilize configurations through various authentication method plugins
%End
public:


enum MessageLevel
{
INFO,
Expand Down Expand Up @@ -161,6 +162,7 @@ Returns the authentication database connection URI with the password stripped.
.. versionadded:: 3.40
%End


bool setMasterPassword( bool verify = false );
%Docstring
Main call to initially set or continually check master password is set
Expand Down
7 changes: 7 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16689,6 +16689,13 @@ void QgisApp::masterPasswordSetup()
connect( QgsApplication::authManager(), &QgsAuthManager::messageLog, this, &QgisApp::authMessageLog );
connect( QgsApplication::authManager(), &QgsAuthManager::passwordHelperMessageLog, this, &QgisApp::authMessageLog );
connect( QgsApplication::authManager(), &QgsAuthManager::authDatabaseEraseRequested, this, &QgisApp::eraseAuthenticationDatabase );

if ( QgsAuthManager::settingsGenerateRandomPasswordForPasswordHelper->value()
&& !QgsApplication::authManager()->masterPasswordHashInDatabase() && QgsApplication::authManager()->passwordHelperEnabled() )
{
// if no master password set by user yet, just generate a new one and store it in the system keychain
QgsApplication::authManager()->createAndStoreRandomMasterPasswordInKeyChain();
}
}

void QgisApp::eraseAuthenticationDatabase()
Expand Down
63 changes: 56 additions & 7 deletions src/core/auth/qgsauthmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
#include "qgsvariantutils.h"
#include "qgssettings.h"
#include "qgsruntimeprofiler.h"
#include "qgssettingsentryimpl.h"
#include "qgssettingstree.h"

QgsAuthManager *QgsAuthManager::sInstance = nullptr;

Expand All @@ -69,7 +71,7 @@ const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[
const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME_BASE( "QGIS-Master-Password" );
const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" );


const QgsSettingsEntryBool *QgsAuthManager::settingsGenerateRandomPasswordForPasswordHelper = new QgsSettingsEntryBool( QStringLiteral( "generate-random-password-for-keychain" ), QgsSettingsTree::sTreeAuthentication, true, QStringLiteral( "Whether a random password should be automatically generated for the authentication database and stored in the system keychain." ) );

Q_NOWARN_DEPRECATED_PUSH
#if defined(Q_OS_MAC)
Expand Down Expand Up @@ -382,6 +384,19 @@ void QgsAuthManager::setup( const QString &pluginPath, const QString &authDataba
}
}

QString QgsAuthManager::generatePassword()
{
QRandomGenerator generator = QRandomGenerator::securelySeeded();
QString pw;
pw.resize( 32 );
static const QString sPwChars = QStringLiteral( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_-{}[]" );
for ( int i = 0; i < pw.size(); ++i )
{
pw[i] = sPwChars.at( generator.bounded( 0, sPwChars.length() ) );
}
return pw;
}

bool QgsAuthManager::isDisabled() const
{
ensureInitialized();
Expand All @@ -400,6 +415,45 @@ const QString QgsAuthManager::disabledMessage() const
return tr( "Authentication system is DISABLED:\n%1" ).arg( mAuthDisabledMessage );
}

bool QgsAuthManager::createAndStoreRandomMasterPasswordInKeyChain()
nyalldawson marked this conversation as resolved.
Show resolved Hide resolved
{
QMutexLocker locker( mMasterPasswordMutex.get() );
if ( isDisabled() )
return false;

if ( mScheduledDbErase )
return false;

if ( !passwordHelperEnabled() )
return false;

if ( !mMasterPass.isEmpty() )
{
QgsDebugError( QStringLiteral( "Master password is already set!" ) );
return false;
}

const QString newPassword = generatePassword();
if ( passwordHelperWrite( newPassword ) )
{
mMasterPass = newPassword;
}
else
{
emit passwordHelperMessageLog( tr( "Master password could not be written to your %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
return false;
}

if ( !verifyMasterPassword() )
nyalldawson marked this conversation as resolved.
Show resolved Hide resolved
{
emit passwordHelperMessageLog( tr( "Master password was written to the %1 but could not be verified" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
return false;
}

QgsDebugMsgLevel( QStringLiteral( "Master password is set and verified" ), 2 );
return true;
}


QString QgsAuthManager::sqliteDatabasePath() const
{
Expand Down Expand Up @@ -3454,7 +3508,6 @@ bool QgsAuthManager::masterPasswordInput()
{
ok = true;
storedPasswordIsValid = true;
emit passwordHelperMessageLog( tr( "Master password has been successfully read from your %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Info );
}
else
{
Expand All @@ -3474,11 +3527,7 @@ bool QgsAuthManager::masterPasswordInput()
mMasterPass = pass;
if ( passwordHelperEnabled() && ! storedPasswordIsValid )
{
if ( passwordHelperWrite( pass ) )
{
emit passwordHelperMessageLog( tr( "Master password has been successfully written to your %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Info );
}
else
if ( !passwordHelperWrite( pass ) )
{
emit passwordHelperMessageLog( tr( "Master password could not be written to your %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
}
Expand Down
17 changes: 17 additions & 0 deletions src/core/auth/qgsauthmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class QgsAuthMethod;
class QgsAuthMethodEdit;
class QgsAuthProvider;
class QgsAuthMethodMetadata;
class QgsSettingsEntryBool;
class QTimer;
class QgsAuthConfigurationStorage;
class QgsAuthConfigurationStorageDb;
Expand All @@ -73,6 +74,8 @@ class CORE_EXPORT QgsAuthManager : public QObject

public:

static const QgsSettingsEntryBool *settingsGenerateRandomPasswordForPasswordHelper SIP_SKIP;
nyalldawson marked this conversation as resolved.
Show resolved Hide resolved

//! Message log level (mirrors that of QgsMessageLog, so it can also output there)
enum MessageLevel
{
Expand Down Expand Up @@ -186,6 +189,15 @@ class CORE_EXPORT QgsAuthManager : public QObject
*/
const QString authenticationDatabaseUriStripped() const;

/**
* Creates a new securely seeded random password and stores it in the
* system keychain as the new master password.
*
* \note Not available in Python bindings
* \since QGIS 3.42
*/
bool createAndStoreRandomMasterPasswordInKeyChain() SIP_SKIP;

/**
* Main call to initially set or continually check master password is set
* \note If it is not set, the user is asked for its input
Expand Down Expand Up @@ -891,6 +903,11 @@ class CORE_EXPORT QgsAuthManager : public QObject

private:

/**
* Generates a random, securely seeded password.
*/
static QString generatePassword();

bool initPrivate( const QString &pluginPath );

//////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/core/settings/qgssettingstree.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class CORE_EXPORT QgsSettingsTree
static inline QgsSettingsTreeNode *sTreeNetworkCache = treeRoot()->createChildNode( QStringLiteral( "cache" ) );
static inline QgsSettingsTreeNode *sTreeAttributeTable = treeRoot()->createChildNode( QStringLiteral( "attribute-table" ) );
static inline QgsSettingsTreeNode *sTreeWindowState = sTreeGui->createChildNode( QStringLiteral( "window-state" ) );
static inline QgsSettingsTreeNode *sTreeAuthentication = treeRoot()->createChildNode( QStringLiteral( "authentication" ) );

#endif

Expand Down