Skip to content

Commit

Permalink
On systems with keychain support, automatically just create a random
Browse files Browse the repository at this point in the history
master authentication password and store in keychain

This avoids the need for users to manually create a pw, and makes
things MUCH nicer for plugins which want to utilise the secure
authentication framework.

Right now the options for plugins are:
1. Create a auto generated password themselves and force it on
the user (basically what we are doing here automatically now)
2. Show a confusing/scary message to users asking them to set
a master password. From my experience users have NO idea what
this means and consider it a QGIS bug.
  • Loading branch information
nyalldawson committed Nov 9, 2023
1 parent 62f4fa1 commit 4eee81b
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 0 deletions.
1 change: 1 addition & 0 deletions python/core/auto_generated/auth/qgsauthmanager.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The standard authentication database file in ~/.qgis3/ or defined location
.. seealso:: :py:func:`QgsApplication.qgisAuthDatabaseFilePath`
%End


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

if ( !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
51 changes: 51 additions & 0 deletions src/core/auth/qgsauthmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <QDomElement>
#include <QDomDocument>
#include <QRegularExpression>
#include <QRandomGenerator>

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
Expand Down Expand Up @@ -480,6 +481,19 @@ bool QgsAuthManager::createCertTables()
return true;
}

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
{
if ( mAuthDisabled )
Expand All @@ -494,6 +508,43 @@ const QString QgsAuthManager::disabledMessage() const
return tr( "Authentication system is DISABLED:\n%1" ).arg( mAuthDisabledMessage );
}

bool QgsAuthManager::createAndStoreRandomMasterPasswordInKeyChain()
{
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 ) )
{
emit passwordHelperMessageOut( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO );
mMasterPass = newPassword;
}
else
{
emit passwordHelperMessageOut( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING );
return false;
}

if ( !verifyMasterPassword() )
return false;

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

bool QgsAuthManager::setMasterPassword( bool verify )
{
QMutexLocker locker( mMasterPasswordMutex.get() );
Expand Down
11 changes: 11 additions & 0 deletions src/core/auth/qgsauthmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ class CORE_EXPORT QgsAuthManager : public QObject
*/
const QString authenticationDatabasePath() const { return mAuthDbPath; }

/**
* 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.36
*/
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 @@ -826,6 +835,8 @@ class CORE_EXPORT QgsAuthManager : public QObject

bool createCertTables();

static QString generatePassword();

bool masterPasswordInput();

bool masterPasswordRowsInDb( int *rows ) const;
Expand Down

0 comments on commit 4eee81b

Please sign in to comment.