2018-10-01 14:26:24 +00:00
/*
* Copyright ( C ) 2018 KeePassXC Team < team @ keepassxc . org >
*
* This program 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 2 or ( at your option )
* version 3 of the License .
*
* This program 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 this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "ShareObserver.h"
2019-01-03 07:46:32 +00:00
# include "config-keepassx.h"
2018-10-01 14:26:24 +00:00
# include "core/Clock.h"
# include "core/Config.h"
# include "core/CustomData.h"
# include "core/Database.h"
# include "core/DatabaseIcons.h"
# include "core/Entry.h"
# include "core/FilePath.h"
# include "core/FileWatcher.h"
# include "core/Group.h"
# include "core/Merger.h"
# include "core/Metadata.h"
# include "format/KeePass2Reader.h"
# include "format/KeePass2Writer.h"
# include "keeshare/KeeShare.h"
# include "keeshare/KeeShareSettings.h"
# include "keeshare/Signature.h"
# include "keys/PasswordKey.h"
# include <QBuffer>
# include <QDebug>
# include <QFileInfo>
# include <QIcon>
# include <QMessageBox>
# include <QPainter>
# include <QPushButton>
# include <QStringBuilder>
2019-01-03 07:46:32 +00:00
# if defined(WITH_XC_KEESHARE_SECURE)
2018-10-01 14:26:24 +00:00
# include <quazip5/quazip.h>
# include <quazip5/quazipfile.h>
2019-01-03 07:46:32 +00:00
# endif
2018-10-01 14:26:24 +00:00
namespace
{
2018-12-19 22:52:12 +00:00
static const QString KeeShare_Signature ( " container.share.signature " ) ;
static const QString KeeShare_Container ( " container.share.kdbx " ) ;
2018-10-01 14:26:24 +00:00
2018-12-19 22:52:12 +00:00
enum Trust
{
2019-01-07 11:02:21 +00:00
Invalid ,
Own ,
UntrustedForever ,
UntrustedOnce ,
TrustedOnce ,
TrustedForever ,
2018-12-19 22:52:12 +00:00
} ;
2019-01-03 07:46:32 +00:00
bool isOfExportType ( const QFileInfo & fileInfo , const QString type )
{
2019-01-07 11:02:21 +00:00
return fileInfo . fileName ( ) . endsWith ( type , Qt : : CaseInsensitive ) ;
2019-01-03 07:46:32 +00:00
}
2018-12-19 22:52:12 +00:00
QPair < Trust , KeeShareSettings : : Certificate > check ( QByteArray & data ,
2019-01-07 11:02:21 +00:00
const KeeShareSettings : : Reference & reference ,
const KeeShareSettings : : Certificate & ownCertificate ,
const QList < KeeShareSettings : : ScopedCertificate > & knownCertificates ,
const KeeShareSettings : : Sign & sign )
2018-12-19 22:52:12 +00:00
{
2019-01-07 11:02:21 +00:00
KeeShareSettings : : Certificate certificate ;
if ( ! sign . signature . isEmpty ( ) ) {
certificate = sign . certificate ;
auto key = sign . certificate . sshKey ( ) ;
key . openKey ( QString ( ) ) ;
const auto signer = Signature ( ) ;
if ( ! signer . verify ( data , sign . signature , key ) ) {
qCritical ( " Invalid signature for sharing container %s. " , qPrintable ( reference . path ) ) ;
return { Invalid , KeeShareSettings : : Certificate ( ) } ;
}
if ( ownCertificate . key = = sign . certificate . key ) {
return { Own , ownCertificate } ;
}
}
enum Scope { Invalid , Global , Local } ;
Scope scope = Invalid ;
KeeShareSettings : : Trust trusted = KeeShareSettings : : Trust : : Ask ;
for ( const auto & scopedCertificate : knownCertificates ) {
if ( scopedCertificate . certificate . key = = certificate . key & & scopedCertificate . path = = reference . path ) {
// Global scope is overwritten by local scope
scope = Global ;
trusted = scopedCertificate . trust ;
}
if ( scopedCertificate . certificate . key = = certificate . key & & scopedCertificate . path = = reference . path ) {
scope = Local ;
trusted = scopedCertificate . trust ;
break ;
}
}
if ( scope ! = Invalid & & trusted ! = KeeShareSettings : : Trust : : Ask ) {
// we introduce now scopes if there is a global
return { trusted = = KeeShareSettings : : Trust : : Trusted ? TrustedForever : UntrustedForever , certificate } ;
}
QMessageBox warning ;
if ( sign . signature . isEmpty ( ) ) {
warning . setIcon ( QMessageBox : : Warning ) ;
warning . setWindowTitle ( ShareObserver : : tr ( " Import from container without signature " ) ) ;
warning . setText ( ShareObserver : : tr ( " We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1? " )
. arg ( reference . path ) ) ;
}
else {
warning . setIcon ( QMessageBox : : Question ) ;
warning . setWindowTitle ( ShareObserver : : tr ( " Import from container with certificate " ) ) ;
warning . setText ( ShareObserver : : tr ( " Do you want to trust %1 with the fingerprint of %2 from %3 " )
. arg ( certificate . signer , certificate . fingerprint ( ) , reference . path ) ) ;
}
auto untrustedOnce = warning . addButton ( ShareObserver : : tr ( " Not this time " ) , QMessageBox : : ButtonRole : : NoRole ) ;
auto untrustedForever = warning . addButton ( ShareObserver : : tr ( " Never " ) , QMessageBox : : ButtonRole : : NoRole ) ;
auto trustedForever = warning . addButton ( ShareObserver : : tr ( " Always " ) , QMessageBox : : ButtonRole : : YesRole ) ;
auto trustedOnce = warning . addButton ( ShareObserver : : tr ( " Just this time " ) , QMessageBox : : ButtonRole : : YesRole ) ;
warning . setDefaultButton ( untrustedOnce ) ;
warning . exec ( ) ;
if ( warning . clickedButton ( ) = = trustedForever ) {
return { TrustedForever , certificate } ;
}
if ( warning . clickedButton ( ) = = trustedOnce ) {
return { TrustedOnce , certificate } ;
}
if ( warning . clickedButton ( ) = = untrustedOnce ) {
return { UntrustedOnce , certificate } ;
}
if ( warning . clickedButton ( ) = = untrustedForever ) {
return { UntrustedForever , certificate } ;
}
return { UntrustedOnce , certificate } ;
2018-10-01 14:26:24 +00:00
}
2018-12-19 22:52:12 +00:00
} // End Namespace
2018-12-19 03:28:56 +00:00
ShareObserver : : ShareObserver ( QSharedPointer < Database > db , QObject * parent )
2019-01-07 11:02:21 +00:00
: QObject ( parent )
, m_db ( std : : move ( db ) )
, m_fileWatcher ( new BulkFileWatcher ( this ) )
2018-10-01 14:26:24 +00:00
{
2019-01-07 11:02:21 +00:00
connect ( KeeShare : : instance ( ) , SIGNAL ( activeChanged ( ) ) , SLOT ( handleDatabaseChanged ( ) ) ) ;
2018-12-19 22:52:12 +00:00
2019-01-07 11:02:21 +00:00
connect ( m_db . data ( ) , SIGNAL ( databaseModified ( ) ) , SLOT ( handleDatabaseChanged ( ) ) ) ;
connect ( m_db . data ( ) , SIGNAL ( databaseSaved ( ) ) , SLOT ( handleDatabaseSaved ( ) ) ) ;
2018-10-01 14:26:24 +00:00
2019-01-07 11:02:21 +00:00
connect ( m_fileWatcher , SIGNAL ( fileCreated ( QString ) ) , SLOT ( handleFileUpdated ( QString ) ) ) ;
connect ( m_fileWatcher , SIGNAL ( fileChanged ( QString ) ) , SLOT ( handleFileUpdated ( QString ) ) ) ;
connect ( m_fileWatcher , SIGNAL ( fileRemoved ( QString ) ) , SLOT ( handleFileUpdated ( QString ) ) ) ;
2018-10-01 14:26:24 +00:00
2019-01-07 11:02:21 +00:00
const auto active = KeeShare : : active ( ) ;
if ( ! active . in & & ! active . out ) {
deinitialize ( ) ;
} else {
reinitialize ( ) ;
}
2018-10-01 14:26:24 +00:00
}
ShareObserver : : ~ ShareObserver ( )
{
}
void ShareObserver : : deinitialize ( )
{
2019-01-07 11:02:21 +00:00
m_fileWatcher - > clear ( ) ;
m_groupToReference . clear ( ) ;
m_referenceToGroup . clear ( ) ;
2018-10-01 14:26:24 +00:00
}
void ShareObserver : : reinitialize ( )
{
2019-01-07 11:02:21 +00:00
struct Update
{
Group * group ;
KeeShareSettings : : Reference oldReference ;
KeeShareSettings : : Reference newReference ;
} ;
const auto active = KeeShare : : active ( ) ;
QList < Update > updated ;
QList < Group * > groups = m_db - > rootGroup ( ) - > groupsRecursive ( true ) ;
for ( Group * group : groups ) {
Update couple { group , m_groupToReference . value ( group ) , KeeShare : : referenceOf ( group ) } ;
if ( couple . oldReference = = couple . newReference ) {
continue ;
}
m_groupToReference . remove ( couple . group ) ;
m_referenceToGroup . remove ( couple . oldReference ) ;
m_shareToGroup . remove ( couple . oldReference . path ) ;
if ( couple . newReference . isValid ( ) & & ( ( active . in & & couple . newReference . isImporting ( ) )
| | ( active . out & & couple . newReference . isExporting ( ) ) ) ) {
m_groupToReference [ couple . group ] = couple . newReference ;
m_referenceToGroup [ couple . newReference ] = couple . group ;
m_shareToGroup [ couple . newReference . path ] = couple . group ;
}
updated < < couple ;
}
QStringList success ;
QStringList warning ;
QStringList error ;
for ( const auto & update : updated ) {
if ( ! update . oldReference . path . isEmpty ( ) ) {
m_fileWatcher - > removePath ( update . oldReference . path ) ;
}
if ( ! update . newReference . path . isEmpty ( ) & & update . newReference . type ! = KeeShareSettings : : Inactive ) {
m_fileWatcher - > addPath ( update . newReference . path ) ;
}
if ( update . newReference . isImporting ( ) ) {
const auto result = this - > importFromReferenceContainer ( update . newReference . path ) ;
if ( ! result . isValid ( ) ) {
// tolerable result - blocked import or missing source
continue ;
}
if ( result . isError ( ) ) {
error < < tr ( " Import from %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isWarning ( ) ) {
warning < < tr ( " Import from %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isInfo ( ) ) {
success < < tr ( " Import from %1 successful (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else {
success < < tr ( " Imported from %1 " ) . arg ( result . path ) ;
}
}
}
notifyAbout ( success , warning , error ) ;
2018-10-01 14:26:24 +00:00
}
void ShareObserver : : notifyAbout ( const QStringList & success , const QStringList & warning , const QStringList & error )
{
2019-01-07 11:02:21 +00:00
if ( error . isEmpty ( ) & & warning . isEmpty ( ) & & success . isEmpty ( ) ) {
return ;
}
MessageWidget : : MessageType type = MessageWidget : : Positive ;
if ( ! warning . isEmpty ( ) ) {
type = MessageWidget : : Warning ;
}
if ( ! error . isEmpty ( ) ) {
type = MessageWidget : : Error ;
}
emit sharingMessage ( ( success + warning + error ) . join ( " \n " ) , type ) ;
2018-10-01 14:26:24 +00:00
}
void ShareObserver : : handleDatabaseChanged ( )
{
2019-01-07 11:02:21 +00:00
if ( ! m_db ) {
Q_ASSERT ( m_db ) ;
return ;
}
const auto active = KeeShare : : active ( ) ;
if ( ! active . out & & ! active . in ) {
deinitialize ( ) ;
} else {
reinitialize ( ) ;
}
2018-10-01 14:26:24 +00:00
}
2018-12-19 22:52:12 +00:00
void ShareObserver : : handleFileUpdated ( const QString & path )
2018-10-01 14:26:24 +00:00
{
2019-01-07 11:02:21 +00:00
const Result result = this - > importFromReferenceContainer ( path ) ;
if ( ! result . isValid ( ) ) {
return ;
}
QStringList success ;
QStringList warning ;
QStringList error ;
if ( result . isError ( ) ) {
error < < tr ( " Import from %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isWarning ( ) ) {
warning < < tr ( " Import from %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isInfo ( ) ) {
success < < tr ( " Import from %1 successful (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else {
success < < tr ( " Imported from %1 " ) . arg ( result . path ) ;
}
notifyAbout ( success , warning , error ) ;
2018-10-01 14:26:24 +00:00
}
2019-01-07 11:02:21 +00:00
ShareObserver : : Result ShareObserver : : importSingedContainerInto ( const KeeShareSettings : : Reference & reference , Group * targetGroup )
2018-10-01 14:26:24 +00:00
{
2019-01-03 07:46:32 +00:00
# if !defined(WITH_XC_KEESHARE_SECURE)
2019-01-07 11:02:21 +00:00
Q_UNUSED ( targetGroup ) ;
return { reference . path , Result : : Warning , tr ( " Signed share container are not supported - import prevented " ) } ;
2019-01-03 07:46:32 +00:00
# else
2019-01-07 11:02:21 +00:00
QuaZip zip ( reference . path ) ;
if ( ! zip . open ( QuaZip : : mdUnzip ) ) {
qCritical ( " Unable to open file %s. " , qPrintable ( reference . path ) ) ;
return { reference . path , Result : : Error , tr ( " File is not readable " ) } ;
}
const auto expected = QSet < QString > ( ) < < KeeShare_Signature < < KeeShare_Container ;
const auto files = zip . getFileInfoList ( ) ;
QSet < QString > actual ;
for ( const auto & file : files ) {
actual < < file . name ;
}
if ( expected ! = actual ) {
qCritical ( " Invalid sharing container %s. " , qPrintable ( reference . path ) ) ;
return { reference . path , Result : : Error , tr ( " Invalid sharing container " ) } ;
}
zip . setCurrentFile ( KeeShare_Signature ) ;
QuaZipFile signatureFile ( & zip ) ;
signatureFile . open ( QuaZipFile : : ReadOnly ) ;
QTextStream stream ( & signatureFile ) ;
const auto sign = KeeShareSettings : : Sign : : deserialize ( stream . readAll ( ) ) ;
signatureFile . close ( ) ;
zip . setCurrentFile ( KeeShare_Container ) ;
QuaZipFile databaseFile ( & zip ) ;
databaseFile . open ( QuaZipFile : : ReadOnly ) ;
auto payload = databaseFile . readAll ( ) ;
databaseFile . close ( ) ;
QBuffer buffer ( & payload ) ;
buffer . open ( QIODevice : : ReadOnly ) ;
KeePass2Reader reader ;
auto key = QSharedPointer < CompositeKey > : : create ( ) ;
key - > addKey ( QSharedPointer < PasswordKey > : : create ( reference . password ) ) ;
auto sourceDb = QSharedPointer < Database > : : create ( ) ;
if ( ! reader . readDatabase ( & buffer , key , sourceDb . data ( ) ) ) {
qCritical ( " Error while parsing the database: %s " , qPrintable ( reader . errorString ( ) ) ) ;
return { reference . path , Result : : Error , reader . errorString ( ) } ;
}
auto foreign = KeeShare : : foreign ( ) ;
auto own = KeeShare : : own ( ) ;
auto trust = check ( payload , reference , own . certificate , foreign . certificates , sign ) ;
switch ( trust . first ) {
case Invalid :
qWarning ( " Prevent untrusted import " ) ;
return { reference . path , Result : : Error , tr ( " Untrusted import prevented " ) } ;
case UntrustedForever :
case TrustedForever : {
bool found = false ;
const auto trusted = trust . first = = TrustedForever ? KeeShareSettings : : Trust : : Trusted : KeeShareSettings : : Trust : : Untrusted ;
for ( KeeShareSettings : : ScopedCertificate & scopedCertificate : foreign . certificates ) {
if ( scopedCertificate . certificate . key = = trust . second . key & & scopedCertificate . path = = reference . path ) {
scopedCertificate . certificate . signer = trust . second . signer ;
scopedCertificate . path = reference . path ;
scopedCertificate . trust = trusted ;
found = true ;
}
}
if ( ! found ) {
foreign . certificates < < KeeShareSettings : : ScopedCertificate { reference . path , trust . second , trusted } ;
// we need to update with the new signer
KeeShare : : setForeign ( foreign ) ;
}
if ( trust . first = = TrustedForever ) {
qDebug ( " Synchronize %s %s with %s " ,
qPrintable ( reference . path ) ,
qPrintable ( targetGroup - > name ( ) ) ,
qPrintable ( sourceDb - > rootGroup ( ) - > name ( ) ) ) ;
Merger merger ( sourceDb - > rootGroup ( ) , targetGroup ) ;
merger . setForcedMergeMode ( Group : : Synchronize ) ;
const bool changed = merger . merge ( ) ;
if ( changed ) {
return { reference . path , Result : : Success , tr ( " Successful signed import " ) } ;
}
}
// Silent ignore of untrusted import or unchanging import
return { } ;
}
case TrustedOnce :
case Own : {
qDebug ( " Synchronize %s %s with %s " ,
qPrintable ( reference . path ) ,
qPrintable ( targetGroup - > name ( ) ) ,
qPrintable ( sourceDb - > rootGroup ( ) - > name ( ) ) ) ;
Merger merger ( sourceDb - > rootGroup ( ) , targetGroup ) ;
merger . setForcedMergeMode ( Group : : Synchronize ) ;
const bool changed = merger . merge ( ) ;
if ( changed ) {
return { reference . path , Result : : Success , tr ( " Successful signed import " ) } ;
}
return { } ;
}
default :
Q_ASSERT ( false ) ;
return { reference . path , Result : : Error , tr ( " Unexpected error " ) } ;
}
2019-01-03 07:46:32 +00:00
# endif
}
2019-01-07 11:02:21 +00:00
ShareObserver : : Result ShareObserver : : importUnsignedContainerInto ( const KeeShareSettings : : Reference & reference , Group * targetGroup )
2019-01-03 07:46:32 +00:00
{
# if !defined(WITH_XC_KEESHARE_INSECURE)
2019-01-07 11:02:21 +00:00
Q_UNUSED ( targetGroup ) ;
return { reference . path , Result : : Warning , tr ( " Unsigned share container are not supported - import prevented " ) } ;
2019-01-03 07:46:32 +00:00
# else
2019-01-07 11:02:21 +00:00
QFile file ( reference . path ) ;
if ( ! file . open ( QIODevice : : ReadOnly ) ) {
qCritical ( " Unable to open file %s. " , qPrintable ( reference . path ) ) ;
return { reference . path , Result : : Error , tr ( " File is not readable " ) } ;
}
auto payload = file . readAll ( ) ;
file . close ( ) ;
QBuffer buffer ( & payload ) ;
buffer . open ( QIODevice : : ReadOnly ) ;
KeePass2Reader reader ;
auto key = QSharedPointer < CompositeKey > : : create ( ) ;
key - > addKey ( QSharedPointer < PasswordKey > : : create ( reference . password ) ) ;
auto sourceDb = QSharedPointer < Database > : : create ( ) ;
if ( ! reader . readDatabase ( & buffer , key , sourceDb . data ( ) ) ) {
qCritical ( " Error while parsing the database: %s " , qPrintable ( reader . errorString ( ) ) ) ;
return { reference . path , Result : : Error , reader . errorString ( ) } ;
}
auto foreign = KeeShare : : foreign ( ) ;
const auto own = KeeShare : : own ( ) ;
const auto sign = KeeShareSettings : : Sign ( ) ; // invalid sign
auto trust = check ( payload , reference , own . certificate , foreign . certificates , sign ) ;
switch ( trust . first ) {
case UntrustedForever :
case TrustedForever : {
bool found = false ;
const auto trusted = trust . first = = TrustedForever ? KeeShareSettings : : Trust : : Trusted : KeeShareSettings : : Trust : : Untrusted ;
for ( KeeShareSettings : : ScopedCertificate & scopedCertificate : foreign . certificates ) {
if ( scopedCertificate . certificate . key = = trust . second . key & & scopedCertificate . path = = reference . path ) {
scopedCertificate . certificate . signer = trust . second . signer ;
scopedCertificate . path = reference . path ;
scopedCertificate . trust = trusted ;
found = true ;
}
}
if ( ! found ) {
foreign . certificates < < KeeShareSettings : : ScopedCertificate { reference . path , trust . second , trusted } ;
// we need to update with the new signer
KeeShare : : setForeign ( foreign ) ;
}
if ( trust . first = = TrustedForever ) {
qDebug ( " Synchronize %s %s with %s " ,
qPrintable ( reference . path ) ,
qPrintable ( targetGroup - > name ( ) ) ,
qPrintable ( sourceDb - > rootGroup ( ) - > name ( ) ) ) ;
Merger merger ( sourceDb - > rootGroup ( ) , targetGroup ) ;
merger . setForcedMergeMode ( Group : : Synchronize ) ;
const bool changed = merger . merge ( ) ;
if ( changed ) {
return { reference . path , Result : : Success , tr ( " Successful signed import " ) } ;
}
}
return { } ;
}
case TrustedOnce : {
qDebug ( " Synchronize %s %s with %s " ,
qPrintable ( reference . path ) ,
qPrintable ( targetGroup - > name ( ) ) ,
qPrintable ( sourceDb - > rootGroup ( ) - > name ( ) ) ) ;
Merger merger ( sourceDb - > rootGroup ( ) , targetGroup ) ;
merger . setForcedMergeMode ( Group : : Synchronize ) ;
const bool changed = merger . merge ( ) ;
if ( changed ) {
return { reference . path , Result : : Success , tr ( " Successful unsigned import " ) } ;
}
return { } ;
}
default :
qWarning ( " Prevent untrusted import " ) ;
return { reference . path , Result : : Warning , tr ( " Untrusted import prevented " ) } ;
}
2019-01-03 07:46:32 +00:00
# endif
}
ShareObserver : : Result ShareObserver : : importContainerInto ( const KeeShareSettings : : Reference & reference , Group * targetGroup )
{
2019-01-07 11:02:21 +00:00
const QFileInfo info ( reference . path ) ;
if ( ! info . exists ( ) ) {
qCritical ( " File %s does not exist. " , qPrintable ( info . absoluteFilePath ( ) ) ) ;
return { reference . path , Result : : Warning , tr ( " File does not exist " ) } ;
}
if ( isOfExportType ( info , KeeShare : : signedContainerFileType ( ) ) ) {
return importSingedContainerInto ( reference , targetGroup ) ;
}
if ( isOfExportType ( info , KeeShare : : unsignedContainerFileType ( ) ) ) {
return importUnsignedContainerInto ( reference , targetGroup ) ;
}
return { reference . path , Result : : Error , tr ( " Unknown share container type " ) } ;
2018-10-01 14:26:24 +00:00
}
ShareObserver : : Result ShareObserver : : importFromReferenceContainer ( const QString & path )
{
2019-01-07 11:02:21 +00:00
if ( ! KeeShare : : active ( ) . in ) {
return { } ;
}
auto shareGroup = m_shareToGroup . value ( path ) ;
if ( ! shareGroup ) {
qWarning ( " Source for %s does not exist " , qPrintable ( path ) ) ;
Q_ASSERT ( shareGroup ) ;
return { } ;
}
const auto reference = KeeShare : : referenceOf ( shareGroup ) ;
if ( reference . type = = KeeShareSettings : : Inactive ) {
qDebug ( " Ignore change of inactive reference %s " , qPrintable ( reference . path ) ) ;
return { } ;
}
if ( reference . type = = KeeShareSettings : : ExportTo ) {
qDebug ( " Ignore change of export reference %s " , qPrintable ( reference . path ) ) ;
return { } ;
}
Q_ASSERT ( shareGroup - > database ( ) = = m_db ) ;
Q_ASSERT ( shareGroup = = m_db - > rootGroup ( ) - > findGroupByUuid ( shareGroup - > uuid ( ) ) ) ;
return importContainerInto ( reference , shareGroup ) ;
2018-10-01 14:26:24 +00:00
}
void ShareObserver : : resolveReferenceAttributes ( Entry * targetEntry , const Database * sourceDb )
{
2019-01-07 11:02:21 +00:00
for ( const auto & attribute : EntryAttributes : : DefaultAttributes ) {
const auto standardValue = targetEntry - > attributes ( ) - > value ( attribute ) ;
const auto type = targetEntry - > placeholderType ( standardValue ) ;
if ( type ! = Entry : : PlaceholderType : : Reference ) {
// No reference to resolve
continue ;
}
const auto * referencedTargetEntry = targetEntry - > resolveReference ( standardValue ) ;
if ( referencedTargetEntry ) {
// References is within scope, no resolving needed
continue ;
}
// We could do more sophisticated **** trying to point the reference to the next in-scope reference
// but those cases with high propability constructed examples and very rare in real usage
const auto * sourceReference = sourceDb - > rootGroup ( ) - > findEntryByUuid ( targetEntry - > uuid ( ) ) ;
const auto resolvedValue = sourceReference - > resolveMultiplePlaceholders ( standardValue ) ;
targetEntry - > setUpdateTimeinfo ( false ) ;
targetEntry - > attributes ( ) - > set ( attribute , resolvedValue , targetEntry - > attributes ( ) - > isProtected ( attribute ) ) ;
targetEntry - > setUpdateTimeinfo ( true ) ;
}
2018-10-01 14:26:24 +00:00
}
Database * ShareObserver : : exportIntoContainer ( const KeeShareSettings : : Reference & reference , const Group * sourceRoot )
{
2019-01-07 11:02:21 +00:00
const auto * sourceDb = sourceRoot - > database ( ) ;
auto * targetDb = new Database ( ) ;
targetDb - > metadata ( ) - > setRecycleBinEnabled ( false ) ;
auto key = QSharedPointer < CompositeKey > : : create ( ) ;
key - > addKey ( QSharedPointer < PasswordKey > : : create ( reference . password ) ) ;
// Copy the source root as the root of the export database, memory manage the old root node
auto * targetRoot = sourceRoot - > clone ( Entry : : CloneNoFlags , Group : : CloneNoFlags ) ;
const bool updateTimeinfo = targetRoot - > canUpdateTimeinfo ( ) ;
targetRoot - > setUpdateTimeinfo ( false ) ;
KeeShare : : setReferenceTo ( targetRoot , KeeShareSettings : : Reference ( ) ) ;
targetRoot - > setUpdateTimeinfo ( updateTimeinfo ) ;
const auto sourceEntries = sourceRoot - > entriesRecursive ( false ) ;
for ( const Entry * sourceEntry : sourceEntries ) {
auto * targetEntry = sourceEntry - > clone ( Entry : : CloneIncludeHistory ) ;
const bool updateTimeinfo = targetEntry - > canUpdateTimeinfo ( ) ;
targetEntry - > setUpdateTimeinfo ( false ) ;
targetEntry - > setGroup ( targetRoot ) ;
targetEntry - > setUpdateTimeinfo ( updateTimeinfo ) ;
const auto iconUuid = targetEntry - > iconUuid ( ) ;
if ( ! iconUuid . isNull ( ) ) {
targetDb - > metadata ( ) - > addCustomIcon ( iconUuid , sourceEntry - > icon ( ) ) ;
}
}
targetDb - > setKey ( key ) ;
auto * obsoleteRoot = targetDb - > rootGroup ( ) ;
targetDb - > setRootGroup ( targetRoot ) ;
delete obsoleteRoot ;
targetDb - > metadata ( ) - > setName ( sourceRoot - > name ( ) ) ;
// Push all deletions of the source database to the target
// simple moving out of a share group will not trigger a deletion in the
// target - a more elaborate mechanism may need the use of another custom
// attribute to share unshared entries from the target db
for ( const auto & object : sourceDb - > deletedObjects ( ) ) {
targetDb - > addDeletedObject ( object ) ;
}
for ( auto * targetEntry : targetRoot - > entriesRecursive ( false ) ) {
if ( targetEntry - > hasReferences ( ) ) {
resolveReferenceAttributes ( targetEntry , sourceDb ) ;
}
}
return targetDb ;
2018-10-01 14:26:24 +00:00
}
2018-12-19 03:28:56 +00:00
QSharedPointer < Database > ShareObserver : : database ( )
2018-10-01 14:26:24 +00:00
{
2019-01-07 11:02:21 +00:00
return m_db ;
2018-10-01 14:26:24 +00:00
}
2019-01-07 11:02:21 +00:00
ShareObserver : : Result ShareObserver : : exportIntoReferenceSignedContainer ( const KeeShareSettings : : Reference & reference , Database * targetDb )
2019-01-03 07:46:32 +00:00
{
# if !defined(WITH_XC_KEESHARE_SECURE)
2019-01-07 11:02:21 +00:00
Q_UNUSED ( targetDb ) ;
return { reference . path , Result : : Warning , tr ( " Overwriting signed share container is not supported - export prevented " ) } ;
2019-01-03 07:46:32 +00:00
# else
2019-01-07 11:02:21 +00:00
QByteArray bytes ;
{
QBuffer buffer ( & bytes ) ;
buffer . open ( QIODevice : : WriteOnly ) ;
KeePass2Writer writer ;
writer . writeDatabase ( & buffer , targetDb ) ;
if ( writer . hasError ( ) ) {
qWarning ( " Serializing export dabase failed: %s. " , writer . errorString ( ) . toLatin1 ( ) . data ( ) ) ;
return { reference . path , Result : : Error , writer . errorString ( ) } ;
}
}
const auto own = KeeShare : : own ( ) ;
QuaZip zip ( reference . path ) ;
zip . setFileNameCodec ( " UTF-8 " ) ;
const bool zipOpened = zip . open ( QuaZip : : mdCreate ) ;
if ( ! zipOpened ) {
: : qWarning ( " Opening export file failed: %d " , zip . getZipError ( ) ) ;
return { reference . path , Result : : Error , tr ( " Could not write export container (%1) " ) . arg ( zip . getZipError ( ) ) } ;
}
{
QuaZipFile file ( & zip ) ;
const auto signatureOpened = file . open ( QIODevice : : WriteOnly , QuaZipNewInfo ( KeeShare_Signature ) ) ;
if ( ! signatureOpened ) {
: : qWarning ( " Embedding signature failed: %d " , zip . getZipError ( ) ) ;
return { reference . path , Result : : Error , tr ( " Could not embed signature (%1) " ) . arg ( file . getZipError ( ) ) } ;
}
QTextStream stream ( & file ) ;
KeeShareSettings : : Sign sign ;
auto sshKey = own . key . sshKey ( ) ;
sshKey . openKey ( QString ( ) ) ;
const Signature signer ;
sign . signature = signer . create ( bytes , sshKey ) ;
sign . certificate = own . certificate ;
stream < < KeeShareSettings : : Sign : : serialize ( sign ) ;
stream . flush ( ) ;
if ( file . getZipError ( ) ! = ZIP_OK ) {
: : qWarning ( " Embedding signature failed: %d " , zip . getZipError ( ) ) ;
return { reference . path , Result : : Error , tr ( " Could not embed signature (%1) " ) . arg ( file . getZipError ( ) ) } ;
}
file . close ( ) ;
}
{
QuaZipFile file ( & zip ) ;
const auto dbOpened = file . open ( QIODevice : : WriteOnly , QuaZipNewInfo ( KeeShare_Container ) ) ;
if ( ! dbOpened ) {
: : qWarning ( " Embedding database failed: %d " , zip . getZipError ( ) ) ;
return { reference . path , Result : : Error , tr ( " Could not embed database (%1) " ) . arg ( file . getZipError ( ) ) } ;
}
if ( file . getZipError ( ) ! = ZIP_OK ) {
: : qWarning ( " Embedding database failed: %d " , zip . getZipError ( ) ) ;
return { reference . path , Result : : Error , tr ( " Could not embed database (%1) " ) . arg ( file . getZipError ( ) ) } ;
}
file . write ( bytes ) ;
file . close ( ) ;
}
zip . close ( ) ;
return { reference . path } ;
2019-01-03 07:46:32 +00:00
# endif
}
2019-01-07 11:02:21 +00:00
ShareObserver : : Result ShareObserver : : exportIntoReferenceUnsignedContainer ( const KeeShareSettings : : Reference & reference , Database * targetDb )
2019-01-03 07:46:32 +00:00
{
# if !defined(WITH_XC_KEESHARE_INSECURE)
2019-01-07 11:02:21 +00:00
Q_UNUSED ( targetDb ) ;
return { reference . path , Result : : Warning , tr ( " Overwriting unsigned share container is not supported - export prevented " ) } ;
2019-01-03 07:46:32 +00:00
# else
2019-01-07 11:02:21 +00:00
QFile file ( reference . path ) ;
const bool fileOpened = file . open ( QIODevice : : WriteOnly ) ;
if ( ! fileOpened ) {
: : qWarning ( " Opening export file failed " ) ;
return { reference . path , Result : : Error , tr ( " Could not write export container " ) } ;
}
KeePass2Writer writer ;
writer . writeDatabase ( & file , targetDb ) ;
if ( writer . hasError ( ) ) {
qWarning ( " Exporting dabase failed: %s. " , writer . errorString ( ) . toLatin1 ( ) . data ( ) ) ;
return { reference . path , Result : : Error , writer . errorString ( ) } ;
}
file . close ( ) ;
2019-01-03 07:46:32 +00:00
# endif
2019-01-07 11:02:21 +00:00
return { reference . path } ;
2019-01-03 07:46:32 +00:00
}
2018-10-01 14:26:24 +00:00
QList < ShareObserver : : Result > ShareObserver : : exportIntoReferenceContainers ( )
{
2019-01-07 11:02:21 +00:00
QList < Result > results ;
const auto groups = m_db - > rootGroup ( ) - > groupsRecursive ( true ) ;
for ( const auto * group : groups ) {
const auto reference = KeeShare : : referenceOf ( group ) ;
if ( ! reference . isExporting ( ) ) {
continue ;
}
m_fileWatcher - > ignoreFileChanges ( reference . path ) ;
QScopedPointer < Database > targetDb ( exportIntoContainer ( reference , group ) ) ;
QFileInfo info ( reference . path ) ;
if ( isOfExportType ( info , KeeShare : : signedContainerFileType ( ) ) ) {
results < < exportIntoReferenceSignedContainer ( reference , targetDb . data ( ) ) ;
m_fileWatcher - > observeFileChanges ( true ) ;
continue ;
}
if ( isOfExportType ( info , KeeShare : : unsignedContainerFileType ( ) ) ) {
results < < exportIntoReferenceUnsignedContainer ( reference , targetDb . data ( ) ) ;
m_fileWatcher - > observeFileChanges ( true ) ;
continue ;
}
Q_ASSERT ( false ) ;
results < < Result { reference . path , Result : : Error , tr ( " Unexpected export error occurred " ) } ;
}
return results ;
2018-10-01 14:26:24 +00:00
}
void ShareObserver : : handleDatabaseSaved ( )
{
2019-01-07 11:02:21 +00:00
if ( ! KeeShare : : active ( ) . out ) {
return ;
}
QStringList error ;
QStringList warning ;
QStringList success ;
const auto results = exportIntoReferenceContainers ( ) ;
for ( const Result & result : results ) {
if ( ! result . isValid ( ) ) {
Q_ASSERT ( result . isValid ( ) ) ;
continue ;
}
if ( result . isError ( ) ) {
error < < tr ( " Export to %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isWarning ( ) ) {
warning < < tr ( " Export to %1 failed (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else if ( result . isInfo ( ) ) {
success < < tr ( " Export to %1 successful (%2) " ) . arg ( result . path ) . arg ( result . message ) ;
} else {
success < < tr ( " Export to %1 " ) . arg ( result . path ) ;
}
}
notifyAbout ( success , warning , error ) ;
2018-10-01 14:26:24 +00:00
}
ShareObserver : : Result : : Result ( const QString & path , ShareObserver : : Result : : Type type , const QString & message )
2019-01-07 11:02:21 +00:00
: path ( path )
, type ( type )
, message ( message )
2018-10-01 14:26:24 +00:00
{
}
bool ShareObserver : : Result : : isValid ( ) const
{
2019-01-07 11:02:21 +00:00
return ! path . isEmpty ( ) | | ! message . isEmpty ( ) | | ! message . isEmpty ( ) | | ! message . isEmpty ( ) ;
2018-10-01 14:26:24 +00:00
}
bool ShareObserver : : Result : : isError ( ) const
{
2019-01-07 11:02:21 +00:00
return ! message . isEmpty ( ) & & type = = Error ;
2018-10-01 14:26:24 +00:00
}
bool ShareObserver : : Result : : isInfo ( ) const
{
2019-01-07 11:02:21 +00:00
return ! message . isEmpty ( ) & & type = = Info ;
2018-10-01 14:26:24 +00:00
}
bool ShareObserver : : Result : : isWarning ( ) const
{
2019-01-07 11:02:21 +00:00
return ! message . isEmpty ( ) & & type = = Warning ;
2018-10-01 14:26:24 +00:00
}