diff --git a/src/configdialog.cpp b/src/configdialog.cpp index 01817fb..8646908 100644 --- a/src/configdialog.cpp +++ b/src/configdialog.cpp @@ -1,320 +1,320 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "configdialog.h" #include "mainwindow.h" #include "settings.h" #include "ui_configdialog.h" #include "util.h" #include #include #include #include #include #include #include /** * @brief ConfigDialog::ConfigDialog this sets up the configuration screen. * @param parent */ ConfigDialog::ConfigDialog(MainWindow *parent) : QDialog(parent) , ui(new Ui::ConfigDialog) { mainWindow = parent; ui->setupUi(this); ui->profileStorePath->setText(Settings::getPassStore()); ui->currentPathLineEdit->setText(Settings::getPassStore()); ui->spinBoxAutoclearSeconds->setValue(Settings::getAutoclearSeconds()); ui->spinBoxAutoclearPanelSeconds->setValue(Settings::getAutoclearPanelSeconds()); ui->checkBoxHideContent->setChecked(Settings::isHideContent()); ui->checkBoxDisplayAsIs->setChecked(Settings::isDisplayAsIs()); ui->checkBoxNoLineWrapping->setChecked(Settings::isNoLineWrapping()); ui->plainTextEditTemplate->setPlainText(Settings::getPassTemplate()); ui->checkBoxTemplateAllFields->setChecked(Settings::isTemplateAllFields()); setProfiles(Settings::getProfiles(), Settings::getProfile()); setPasswordConfiguration(Settings::getPasswordConfiguration()); useAutoclear(Settings::isUseAutoclear()); useAutoclearPanel(Settings::isUseAutoclearPanel()); validateNewProfile(); useTemplate(Settings::isUseTemplate()); ui->profileTable->verticalHeader()->hide(); ui->profileTable->horizontalHeader()->setStretchLastSection(true); ui->label->setText(ui->label->text() + QString::fromUtf8(GPGPASS_VERSION_STRING)); // Generic setup connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted); // General tab connect(ui->toolButtonStore, &QAbstractButton::clicked, this, &ConfigDialog::selectStoreFolder); connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &ConfigDialog::toggleAutoClearPanelSubentries); // Profiles tab connect(ui->addButton, &QAbstractButton::clicked, this, &ConfigDialog::addProfile); connect(ui->deleteButton, &QAbstractButton::clicked, this, &ConfigDialog::deleteSelectedProfile); connect(ui->createNewProfileFolder, &QAbstractButton::toggled, this, &ConfigDialog::validateNewProfile); connect(ui->newProfileNameEdit, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile); connect(ui->profileStorePath, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile); connect(ui->profileTable, &QTableWidget::itemSelectionChanged, this, [this]() { ui->deleteButton->setEnabled(!ui->profileTable->selectedItems().isEmpty()); }); // template tab connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &ConfigDialog::toggleTemplateSubentries); } bool ConfigDialog::checkNewProfileData() { QString name = ui->newProfileNameEdit->text(); if (name.isEmpty()) { return false; } QString path = ui->profileStorePath->text(); if (path.isEmpty()) { return false; } auto nameItem = ui->profileTable->findItems(name, Qt::MatchExactly); if (!nameItem.empty()) { return false; } auto pathItem = ui->profileTable->findItems(path, Qt::MatchExactly); if (!pathItem.empty()) { return false; } QFileInfo fi(path); if (fi.exists()) { return fi.isDir(); } else { return ui->createNewProfileFolder->isChecked(); } } void ConfigDialog::validateNewProfile() { ui->addButton->setEnabled(checkNewProfileData()); } ConfigDialog::~ConfigDialog() = default; void ConfigDialog::on_accepted() { Settings::setPassStore(Util::normalizeFolderPath(ui->currentPathLineEdit->text())); Settings::setUseAutoclear(ui->checkBoxAutoclear->isChecked()); Settings::setAutoclearSeconds(ui->spinBoxAutoclearSeconds->value()); Settings::setUseAutoclearPanel(ui->checkBoxAutoclearPanel->isChecked()); Settings::setAutoclearPanelSeconds(ui->spinBoxAutoclearPanelSeconds->value()); Settings::setHideContent(ui->checkBoxHideContent->isChecked()); Settings::setDisplayAsIs(ui->checkBoxDisplayAsIs->isChecked()); Settings::setNoLineWrapping(ui->checkBoxNoLineWrapping->isChecked()); Settings::setProfiles(getProfiles()); Settings::setPasswordConfiguration(getPasswordConfiguration()); Settings::setUseTemplate(ui->checkBoxUseTemplate->isChecked()); Settings::setPassTemplate(ui->plainTextEditTemplate->toPlainText()); Settings::setTemplateAllFields(ui->checkBoxTemplateAllFields->isChecked()); Settings::setVersion(QString::fromUtf8(GPGPASS_VERSION_STRING)); } /** * @brief ConfigDialog::selectFolder pop-up to choose a folder. * @return */ QString ConfigDialog::selectFolder() { QFileDialog dialog(this); dialog.setFileMode(QFileDialog::Directory); dialog.setFilter(QDir::NoFilter); dialog.setOption(QFileDialog::ShowDirsOnly); if (dialog.exec()) return dialog.selectedFiles().constFirst(); return QString(); } /** * @brief ConfigDialog::on_toolButtonStore_clicked get .password-store * location.s */ void ConfigDialog::selectStoreFolder() { QString store = selectFolder(); if (!store.isEmpty()) ui->profileStorePath->setText(store); } /** * @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable * options based on autoclear use. */ void ConfigDialog::toggleAutoClearPanelSubentries(bool state) { ui->spinBoxAutoclearPanelSeconds->setEnabled(state); ui->labelPanelSeconds->setEnabled(state); } /** * @brief ConfigDialog::useAutoclear set the clipboard autoclear use from * MainWindow. * @param useAutoclear */ void ConfigDialog::useAutoclear(bool useAutoclear) { ui->checkBoxAutoclear->setChecked(useAutoclear); } /** * @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from * MainWindow. * @param useAutoclearPanel */ void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel) { ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel); toggleAutoClearPanelSubentries(useAutoclearPanel); } static std::unique_ptr createTableWidgetItem(const QString &str) { auto item = std::make_unique(str); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); return item; } /** * @brief ConfigDialog::setProfiles set the profiles and chosen profile from * MainWindow. * @param profiles * @param profile */ void ConfigDialog::setProfiles(const QHash& profiles, const QString& profile) { ui->profileTable->clear(); ui->profileTable->setSortingEnabled(false); QHashIterator i(profiles); while (i.hasNext()) { i.next(); if (!i.value().isEmpty() && !i.key().isEmpty()) { ui->profileTable->insertRow(0); ui->profileTable->setItem(0, 0, createTableWidgetItem(i.key()).release()); ui->profileTable->setItem(0, 1, createTableWidgetItem(i.value()).release()); } } auto selected = ui->profileTable->findItems(profile, Qt::MatchExactly); if (!selected.empty()) { ui->profileTable->selectRow(selected.front()->row()); } ui->profileTable->setSortingEnabled(true); } /** * @brief ConfigDialog::getProfiles return profile list. * @return */ QHash ConfigDialog::getProfiles() { QHash profiles; // Check? for (int i = 0; i < ui->profileTable->rowCount(); ++i) { QTableWidgetItem *pathItem = ui->profileTable->item(i, 1); if (nullptr != pathItem) { QTableWidgetItem *item = ui->profileTable->item(i, 0); if (item == nullptr) { continue; } profiles.insert(item->text(), pathItem->text()); } } return profiles; } /** * @brief ConfigDialog::on_addButton_clicked add a profile row. */ void ConfigDialog::addProfile() { if (!checkNewProfileData()) { return; } ui->profileTable->setSortingEnabled(false); int n = ui->profileTable->rowCount(); ui->profileTable->insertRow(n); ui->profileTable->setItem(n, 0, createTableWidgetItem(ui->newProfileNameEdit->text()).release()); ui->profileTable->setItem(n, 1, createTableWidgetItem(ui->profileStorePath->text()).release()); ui->profileTable->selectRow(n); ui->deleteButton->setEnabled(true); ui->profileTable->setSortingEnabled(true); } /** * @brief ConfigDialog::on_deleteButton_clicked remove a profile row. */ void ConfigDialog::deleteSelectedProfile() { QSet selectedRows; // we use a set to prevent doubles const QList itemList = ui->profileTable->selectedItems(); if (itemList.count() == 0) { QMessageBox::warning(this, i18n("No profile selected"), i18n("No profile selected to delete")); return; } ui->profileTable->setSortingEnabled(false); for (const auto *item : itemList) selectedRows.insert(item->row()); // get a list, and sort it big to small QList rows = selectedRows.values(); std::sort(rows.begin(), rows.end()); // now actually do the removing: - for (int row : qAsConst(rows)) + for (int row : std::as_const(rows)) ui->profileTable->removeRow(row); if (ui->profileTable->rowCount() < 1) ui->deleteButton->setEnabled(false); ui->profileTable->setSortingEnabled(true); } void ConfigDialog::setPasswordConfiguration(const PasswordConfiguration &config) { ui->spinBoxPasswordLength->setValue(config.length); ui->passwordCharTemplateSelector->setCurrentIndex(config.selected); } PasswordConfiguration ConfigDialog::getPasswordConfiguration() { PasswordConfiguration config; config.length = ui->spinBoxPasswordLength->value(); config.selected = static_cast(ui->passwordCharTemplateSelector->currentIndex()); return config; } /** * @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the * template field and options. */ void ConfigDialog::toggleTemplateSubentries(bool enable) { ui->plainTextEditTemplate->setEnabled(enable); ui->checkBoxTemplateAllFields->setEnabled(enable); } /** * @brief ConfigDialog::useTemplate set preference for using templates. * @param useTemplate */ void ConfigDialog::useTemplate(bool useTemplate) { ui->checkBoxUseTemplate->setChecked(useTemplate); toggleTemplateSubentries(useTemplate); } diff --git a/src/filecontent.cpp b/src/filecontent.cpp index 196ad9f..3f4c297 100644 --- a/src/filecontent.cpp +++ b/src/filecontent.cpp @@ -1,88 +1,88 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Lukas Vogel SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "filecontent.h" static bool isLineHidden(const QString &line) { return line.startsWith(QStringLiteral("otpauth://"), Qt::CaseInsensitive); } FileContent FileContent::parse(const QString &fileContent, const QStringList &templateFields, bool allFields) { QStringList lines = fileContent.split(QLatin1Char('\n')); QString password = lines.takeFirst(); QStringList remainingData, remainingDataDisplay; NamedValues namedValues; - for (const QString &line : qAsConst(lines)) { + for (const QString &line : std::as_const(lines)) { if (line.contains(QLatin1Char(':'))) { int colon = line.indexOf(QLatin1Char(':')); QString name = line.left(colon); QString value = line.right(line.length() - colon - 1); if ((allFields && !value.startsWith(QStringLiteral("//"))) // if value startswith // colon is probably from a url || templateFields.contains(name)) { namedValues.append({name.trimmed(), value.trimmed()}); continue; } } remainingData.append(line); if (!isLineHidden(line)) remainingDataDisplay.append(line); } return FileContent(password, namedValues, remainingData.join(QLatin1Char('\n')), remainingDataDisplay.join(QLatin1Char('\n'))); } QString FileContent::getPassword() const { return this->password; } NamedValues FileContent::getNamedValues() const { return this->namedValues; } QString FileContent::getRemainingData() const { return this->remainingData; } QString FileContent::getRemainingDataForDisplay() const { return this->remainingDataDisplay; } FileContent::FileContent(const QString &password, const NamedValues &namedValues, const QString &remainingData, const QString &remainingDataDisplay) : password(password) , namedValues(namedValues) , remainingData(remainingData) , remainingDataDisplay(remainingDataDisplay) { } NamedValues::NamedValues() : QList() { } NamedValues::NamedValues(std::initializer_list values) : QList(values) { } QString NamedValues::takeValue(const QString &name) { for (int i = 0; i < length(); ++i) { if (at(i).name == name) { return takeAt(i).value; } } return QString(); } diff --git a/src/pass.cpp b/src/pass.cpp index 0393128..53d8e82 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -1,437 +1,437 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2017 Jason A. Donenfeld SPDX-FileCopyrightText: 2020 Charlie Waters SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "pass.h" #include "gpgmehelpers.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO remove #include using namespace std; // TODO remove Q_REQUIRED_RESULT static bool ensurePassStore(const QString &file, const char *location) { if (file.startsWith(Settings::getPassStore())) { return true; } QMessageBox::critical(nullptr, QStringLiteral("error"), QStringLiteral("file not in pass store called from %1 %2 %3").arg(QString::fromLocal8Bit(location),file,Settings::getPassStore())); return false; } // TODO remove Q_REQUIRED_RESULT static bool ensureSuffix(const QString &file, const char *location) { if (file.endsWith(QStringLiteral(".gpg"))) { return true; } qWarning() << "file without suffix called from " << location; QMessageBox::critical(nullptr, QStringLiteral("error"), QStringLiteral("file without suffix called from %1 %2").arg(QString::fromLocal8Bit(location),file)); return false; } /** * @brief Pass::Pass wrapper for using either pass or the pass imitation */ Pass::Pass() { } /** * @brief Pass::Generate use either pwgen or internal password * generator * @param length of the desired password * @param charset to use for generation * @return the password */ QString Pass::Generate_b(unsigned int length, const QString &charset) { if (charset.length() > 0) { return generateRandomPassword(charset, length); } else { Q_EMIT critical(i18n("No characters chosen"), i18n("Can't generate password, there are no characters to choose from " "set in the configuration!")); } return {}; } /** * @brief Pass::listKeys list users * @param keystrings * @param secret list private keys * @return QList users */ QList Pass::listKeys(const QStringList& keystrings, bool secret) { auto job = protocol->keyListJob(); std::vector keys; job->addMode(GpgME::KeyListMode::WithSecret); auto result = job->exec(keystrings, secret, keys); if (!isSuccess(result.error())) { return {}; } QList users; for (const auto &key : keys) { UserInfo ui; ui.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime()); ui.key_id = fromGpgmeCharStar(key.keyID()); ui.name = createCombinedNameString(key.userID(0)); ui.validity = key.userID(0).validityAsString(); ui.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime()); ui.have_secret = key.hasSecret(); users.append(ui); } return users; } /** * @brief Pass::listKeys list users * @param keystring * @param secret list private keys * @return QList users */ QList Pass::listKeys(const QString& keystring, bool secret) { return listKeys(QStringList(keystring), secret); } /** * @brief Pass::getRecipientList return list of gpg-id's to encrypt for * @param for_file which file (folder) would you like recepients for * @return recepients gpg-id contents */ QStringList Pass::getRecipientList(const QString& for_file) { if (!ensurePassStore(for_file, Q_FUNC_INFO)) { return {}; } QDir gpgIdPath(QFileInfo(for_file).absoluteDir()); bool found = false; while (gpgIdPath.exists() && gpgIdPath.absolutePath().startsWith(Settings::getPassStore())) { if (QFile(gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id"))).exists()) { found = true; break; } if (!gpgIdPath.cdUp()) break; } QFile gpgId(found ? gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id")) : Settings::getPassStore() + QStringLiteral(".gpg-id")); if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) return QStringList(); QStringList recipients; while (!gpgId.atEnd()) { QString recipient(QString::fromLocal8Bit(gpgId.readLine())); recipient = recipient.trimmed(); if (!recipient.isEmpty()) recipients += recipient; } return recipients; } void Pass::Show(const QString& file) { if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file,Q_FUNC_INFO))) { return; } QFile f(file); f.open(QIODevice::ReadOnly); auto data = f.readAll(); auto job = protocol->decryptJob(); connect(job, &QGpgME::DecryptJob::result, this, [this](auto &&result, QByteArray plainText, QString auditLog, auto &&logError) { if (isSuccess(result.error())) { Q_EMIT finishedShow(QString::fromUtf8(plainText)); } else { Q_EMIT errorString(QString::fromLocal8Bit(result.error().asString())); Q_EMIT decryptionError(); } // Q_EMIT log(auditLog); Q_UNUSED(logError); Q_UNUSED(auditLog); }); job->start(data); } void Pass::Insert(const QString& file, const QString& newValue) { if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file,Q_FUNC_INFO))) { return; } QStringList recipients = Pass::getRecipientList(file); auto job = protocol->encryptJob(); std::vector keys; auto ctx = QGpgME::Job::context(job); for (const auto &keyId : recipients) { GpgME::Error error; auto key = ctx->key(keyId.toUtf8().data(), error, false); if (!error && !key.isNull()) { keys.push_back(key); } } if (keys.empty()) { Q_EMIT critical(i18n("Can not edit"), i18n("Could not read encryption key to use, .gpg-id " "file missing or invalid.")); return; } connect(job, &QGpgME::EncryptJob::result, this, [this, file](auto &&result, const QByteArray &ciphertext, const QString &log, auto &&auditResult) { if (isSuccess(result.error())) { QSaveFile f(file); bool open = f.open(QIODevice::WriteOnly | QIODevice::Truncate); if (!open) { Q_EMIT errorString(QStringLiteral("File open failed: %1").arg(f.errorString())); return; } f.write(ciphertext); if (f.error() == QFileDevice::NoError) { f.commit(); Q_EMIT finishedInsert(); } else { Q_EMIT errorString(QStringLiteral("File write failed: %1").arg(f.errorString())); } } else { Q_EMIT errorString(QString::fromUtf8(result.error().asString())); } Q_UNUSED(log); Q_UNUSED(auditResult); }); job->start(keys, newValue.toUtf8()); } void Pass::Remove(const QString& file, bool isDir) { if (!ensurePassStore(file, Q_FUNC_INFO)) { return; } if (!isDir) { if(!ensureSuffix(file, Q_FUNC_INFO)) { return; } QFile(file).remove(); } else { QDir dir(file); dir.removeRecursively(); } } void Pass::Init(const QString& path, const QList &users) { if (!ensurePassStore(path, Q_FUNC_INFO)) { return; } QString gpgIdFile = path + QStringLiteral(".gpg-id"); QFile gpgId(gpgIdFile); if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) { Q_EMIT critical(i18n("Cannot update"), i18n("Failed to open .gpg-id for writing.")); return; } bool secret_selected = false; for (const UserInfo &user : users) { if (user.enabled) { gpgId.write((user.key_id + QStringLiteral("\n")).toUtf8()); secret_selected |= user.have_secret; } } gpgId.close(); if (!secret_selected) { Q_EMIT critical(i18n("Check selected users!"), i18n("None of the selected keys have a secret key available.\n" "You will not be able to decrypt any newly added passwords!")); return; } reencryptPath(path); } /** * @brief ImitatePass::reencryptPath reencrypt all files under the chosen * directory * * This is stil quite experimental.. * @param dir */ void Pass::reencryptPath(const QString &dir) { if (!ensurePassStore(dir, Q_FUNC_INFO)) { return; } Q_EMIT startReencryptPath(); QDir currentDir; QDirIterator gpgFiles(dir, QStringList() << QStringLiteral("*.gpg"), QDir::Files, QDirIterator::Subdirectories); QStringList gpgId; while (gpgFiles.hasNext()) { QString fileName = gpgFiles.next(); if (gpgFiles.fileInfo().path() != currentDir.path()) { gpgId = getRecipientList(fileName); gpgId.sort(); } QByteArray plainText; QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) { Q_EMIT errorString(QStringLiteral("Failed to open file: %1").arg(fileName)); continue; } QByteArray cipherText = f.readAll(); f.close(); auto decryptJob = protocol->decryptJob(); auto context = QGpgME::Job::context(decryptJob); auto decryptResult = decryptJob->exec(cipherText, plainText); if (!isSuccess(decryptResult.error())) { Q_EMIT errorString(fromGpgmeCharStar(decryptResult.error().asString())); continue; } auto actualRecipients = decryptResult.recipients(); QStringList actualIds; for (auto &recipient : actualRecipients) { GpgME::Error error; auto key = context->key(recipient.keyID(), error, false); if (!error) { actualIds.append(fromGpgmeCharStar(key.keyID())); } } actualIds.sort(); if (actualIds != gpgId) { auto encryptJob = protocol->encryptJob(); std::vector keys; auto ctx = QGpgME::Job::context(encryptJob); - for (const auto &keyId : qAsConst(gpgId)) { + for (const auto &keyId : std::as_const(gpgId)) { GpgME::Error error; auto key = ctx->key(keyId.toUtf8().data(), error, false); if (!error && !key.isNull()) { keys.push_back(key); } } auto encryptResult = encryptJob->exec(keys, plainText, false, cipherText); if (!isSuccess(encryptResult.error())) { Q_EMIT errorString(fromGpgmeCharStar(encryptResult.error().asString())); continue; } QSaveFile save(fileName); bool open = save.open(QIODevice::WriteOnly | QIODevice::Truncate); if (!open) { Q_EMIT errorString(QStringLiteral("open file failed %1").arg(fileName)); continue; } save.write(cipherText); if (save.error() != QFileDevice::NoError) { Q_EMIT errorString(QStringLiteral("Writing file failed: %1").arg(fileName)); continue; } else { save.commit(); } } } Q_EMIT endReencryptPath(); } void Pass::Move(const QString& src, const QString& dest, const bool force) { QFileInfo srcFileInfo(src); QFileInfo destFileInfo(dest); QString destFile; QString srcFileBaseName = srcFileInfo.fileName(); if (srcFileInfo.isFile()) { if (destFileInfo.isFile()) { if (!force) { return; } } else if (destFileInfo.isDir()) { destFile = QDir(dest).filePath(srcFileBaseName); } else { destFile = dest; } if (destFile.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive)) destFile.chop(4); // make sure suffix is lowercase destFile.append(QStringLiteral(".gpg")); } else if (srcFileInfo.isDir()) { if (destFileInfo.isDir()) { destFile = QDir(dest).filePath(srcFileBaseName); } else if (destFileInfo.isFile()) { return; } else { destFile = dest; } } else { return; } QDir qDir; if (force) { qDir.remove(destFile); } qDir.rename(src, destFile); } void Pass::Copy(const QString& src, const QString& dest, const bool force) { QFileInfo destFileInfo(dest); QDir qDir; if (force) { qDir.remove(dest); } if (!QFile::copy(src, dest)) { Q_EMIT errorString(QStringLiteral("Failed to copy file")); } // reecrypt all files under the new folder if (destFileInfo.isDir()) { reencryptPath(destFileInfo.absoluteFilePath()); } else if (destFileInfo.isFile()) { reencryptPath(destFileInfo.dir().path()); } } quint32 Pass::boundedRandom(quint32 bound) { if (bound < 2) { return 0; } quint32 randval; const quint32 max_mod_bound = (1 + ~bound) % bound; do { randval = QRandomGenerator::system()->generate(); } while (randval < max_mod_bound); return randval % bound; } QString Pass::generateRandomPassword(const QString &charset, unsigned int length) { QString out; for (unsigned int i = 0; i < length; ++i) { out.append(charset.at(static_cast(boundedRandom(static_cast(charset.length()))))); } return out; } diff --git a/src/passworddialog.cpp b/src/passworddialog.cpp index db5d7b3..e211b74 100644 --- a/src/passworddialog.cpp +++ b/src/passworddialog.cpp @@ -1,242 +1,242 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Lukas Vogel SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "passworddialog.h" #include "filecontent.h" #include "pass.h" #include "passwordconfiguration.h" #include "settings.h" #include "ui_passworddialog.h" #include #include #include /** * @brief PasswordDialog::PasswordDialog basic constructor. * @param passConfig configuration constant * @param parent */ PasswordDialog::PasswordDialog(Pass &pass, const PasswordConfiguration &passConfig, QWidget *parent) : QDialog(parent) , ui(std::make_unique()) , m_pass(pass) , m_passConfig(passConfig) { m_templating = false; m_allFields = false; m_isNew = false; ui->setupUi(this); setLength(m_passConfig.length); setPasswordCharTemplate(m_passConfig.selected); connect(&m_pass, &Pass::finishedShow, this, &PasswordDialog::setPass); } /** * @brief PasswordDialog::PasswordDialog complete constructor. * @param file * @param isNew * @param parent pointer */ PasswordDialog::PasswordDialog(Pass &pass, const QString &file, const bool &isNew, QWidget *parent) : QDialog(parent) , ui(std::make_unique()) , m_pass(pass) , m_file(file) , m_isNew(isNew) { if (!isNew) m_pass.Show(m_file); ui->setupUi(this); setWindowTitle(this->windowTitle() + QStringLiteral(" ") + QFileInfo(m_file).baseName()); m_passConfig = Settings::getPasswordConfiguration(); setTemplate(Settings::getPassTemplate(), Settings::isUseTemplate()); templateAll(Settings::isTemplateAllFields()); setLength(m_passConfig.length); setPasswordCharTemplate(m_passConfig.selected); connect(&m_pass, &Pass::finishedShow, this, &PasswordDialog::setPass); connect(&m_pass, &Pass::decryptionError, this, &PasswordDialog::close); connect(this, &PasswordDialog::accepted, this, &PasswordDialog::dialogAccepted); connect(this, &PasswordDialog::rejected, this, &PasswordDialog::dialogCancelled); connect(ui->createPasswordButton, &QAbstractButton::clicked, this, &PasswordDialog::createPassword); connect(ui->checkBoxShow, &QCheckBox::stateChanged, this, &PasswordDialog::showPasswordChanged); } /** * @brief Pass{}{}wordDialog::~PasswordDialog basic destructor. */ PasswordDialog::~PasswordDialog() = default; /** * @brief PasswordDialog::on_checkBoxShow_stateChanged hide or show passwords. * @param arg1 */ void PasswordDialog::showPasswordChanged(int arg1) { if (arg1) ui->lineEditPassword->setEchoMode(QLineEdit::Normal); else ui->lineEditPassword->setEchoMode(QLineEdit::Password); } /** * @brief PasswordDialog::on_createPasswordButton_clicked generate a random * passwords. * @todo refactor when process is untangled from MainWindow class. */ void PasswordDialog::createPassword() { ui->widget->setEnabled(false); QString newPass = m_pass.Generate_b(static_cast(ui->spinBox_pwdLength->value()), m_passConfig.Characters[static_cast(ui->passwordTemplateSwitch->currentIndex())]); if (newPass.length() > 0) ui->lineEditPassword->setText(newPass); ui->widget->setEnabled(true); } /** * @brief PasswordDialog::on_accepted handle Ok click for QDialog */ void PasswordDialog::dialogAccepted() { QString newValue = getPassword(); if (newValue.isEmpty()) return; if (newValue.right(1) != QLatin1Char('\n')) newValue += QLatin1Char('\n'); m_pass.Insert(m_file, newValue); } /** * @brief PasswordDialog::on_rejected handle Cancel click for QDialog */ void PasswordDialog::dialogCancelled() { setPassword(QString()); } /** * @brief PasswordDialog::setPassword populate the (templated) fields. * @param password */ void PasswordDialog::setPassword(const QString& password) { FileContent fileContent = FileContent::parse(password, m_templating ? m_fields : QStringList(), m_allFields); ui->lineEditPassword->setText(fileContent.getPassword()); QWidget *previous = ui->checkBoxShow; // first set templated values NamedValues namedValues = fileContent.getNamedValues(); - for (QLineEdit *line : qAsConst(templateLines)) { + for (QLineEdit *line : std::as_const(templateLines)) { line->setText(namedValues.takeValue(line->objectName())); previous = line; } // show remaining values (if there are) otherLines.clear(); - for (const NamedValue &nv : qAsConst(namedValues)) { + for (const NamedValue &nv : std::as_const(namedValues)) { auto *line = new QLineEdit(); line->setObjectName(nv.name); line->setText(nv.value); ui->formLayout->addRow(new QLabel(nv.name), line); setTabOrder(previous, line); otherLines.append(line); previous = line; } ui->plainTextEdit->insertPlainText(fileContent.getRemainingData()); } /** * @brief PasswordDialog::getPassword join the (templated) fields to a QString * for writing back. * @return collappsed password. */ QString PasswordDialog::getPassword() { QString passFile = ui->lineEditPassword->text() + QLatin1Char('\n'); QList allLines(templateLines); allLines.append(otherLines); for (QLineEdit *line : allLines) { QString text = line->text(); if (text.isEmpty()) continue; passFile += line->objectName() + QStringLiteral(": ") + text + QLatin1Char('\n'); } passFile += ui->plainTextEdit->toPlainText(); return passFile; } /** * @brief PasswordDialog::setTemplate set the template and create the fields. * @param rawFields */ void PasswordDialog::setTemplate(const QString& rawFields, bool useTemplate) { m_fields = rawFields.split(QLatin1Char('\n')); m_templating = useTemplate; templateLines.clear(); if (m_templating) { QWidget *previous = ui->checkBoxShow; - for (const QString &field : qAsConst(m_fields)) { + for (const QString &field : std::as_const(m_fields)) { if (field.isEmpty()) continue; auto *line = new QLineEdit(); line->setObjectName(field); ui->formLayout->addRow(new QLabel(field), line); setTabOrder(previous, line); templateLines.append(line); previous = line; } } } /** * @brief PasswordDialog::templateAll basic setter for use in * PasswordDialog::setPassword templating all tokenisable lines. * @param templateAll */ void PasswordDialog::templateAll(bool templateAll) { m_allFields = templateAll; } /** * @brief PasswordDialog::setLength * PasswordDialog::setLength password length. * @param l */ void PasswordDialog::setLength(int l) { ui->spinBox_pwdLength->setValue(l); } /** * @brief PasswordDialog::setPasswordCharTemplate * PasswordDialog::setPasswordCharTemplate chose the template style. * @param t */ void PasswordDialog::setPasswordCharTemplate(int t) { ui->passwordTemplateSwitch->setCurrentIndex(t); } void PasswordDialog::setPass(const QString &output) { setPassword(output); // TODO(bezet): enable ui } diff --git a/src/usersdialog.cpp b/src/usersdialog.cpp index 469bdf5..f3ba519 100644 --- a/src/usersdialog.cpp +++ b/src/usersdialog.cpp @@ -1,182 +1,182 @@ /* SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer SPDX-FileCopyrightText: 2018 Claudio Maradonna SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Sune Stolborg Vuorela SPDX-License-Identifier: GPL-3.0-or-later */ #include "usersdialog.h" #include "pass.h" #include "ui_usersdialog.h" #include "ui_userswidget.h" #include #include #include #include #include void UsersWidgetData::generateUserList() { QList users = m_pass.listKeys(); QList selected_users; int count = 0; QStringList recipients = m_pass.getRecipientList(m_dir); if (!recipients.isEmpty()) selected_users = m_pass.listKeys(recipients); - for (const UserInfo &sel : qAsConst(selected_users)) { + for (const UserInfo &sel : std::as_const(selected_users)) { for (auto &user : users) if (sel.key_id == user.key_id) user.enabled = true; } if (count > selected_users.size()) { // Some keys seem missing from keyring, add them separately const QStringList missingRecipients = m_pass.getRecipientList(m_dir.isEmpty() ? QString{} : m_dir); - for (const QString &missingRecipient : qAsConst(recipients)) { + for (const QString &missingRecipient : std::as_const(recipients)) { if (m_pass.listKeys(missingRecipient).empty()) { UserInfo i; i.enabled = true; i.key_id = missingRecipient; i.name = QStringLiteral(" ?? ") + i18n("Key not found in keyring"); users.append(i); } } } m_userList = users; } void UsersWidgetData::init() { m_pass.Init(m_dir, m_userList); } void UsersWidgetData::setUi(Ui_UsersWidget *u) { this->ui = u; QObject::connect(ui->listWidget, &QListWidget::itemChanged, ui->listWidget, [this](auto item) { itemChange(item); }); QObject::connect(ui->checkBox, &QCheckBox::stateChanged, ui->checkBox, [this]() { populateList(); }); QObject::connect(ui->lineEdit, &QLineEdit::textChanged, ui->lineEdit, [this]() { populateList(); }); } /** * @brief UsersDialog::UsersDialog basic constructor * @param parent */ UsersDialog::UsersDialog(const QString& dir, Pass &pass, QWidget *parent) : QDialog(parent) , dialogUi(std::make_unique()) , ui(std::make_unique()) , d(pass) { d.m_dir = dir; dialogUi->setupUi(this); ui->setupUi(dialogUi->widget); d.generateUserList(); d.setUi(ui.get()); if (d.m_userList.isEmpty()) { QMessageBox::critical(parent, i18n("Keylist missing"), i18n("Could not fetch list of available GPG keys")); return; } d.populateList(); connect(dialogUi->buttonBox, &QDialogButtonBox::accepted, this, &UsersDialog::accept); connect(dialogUi->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); ui->lineEdit->setClearButtonEnabled(true); } /** * @brief UsersDialog::~UsersDialog basic destructor. */ UsersDialog::~UsersDialog() = default; Q_DECLARE_METATYPE(UserInfo *) /** * @brief UsersDialog::accept */ void UsersDialog::accept() { d.init(); QDialog::accept(); } /** * @brief UsersDialog::itemChange update the item information. * @param item */ void UsersWidgetData::itemChange(QListWidgetItem *item) { if (!item) return; auto *info = item->data(Qt::UserRole).value(); if (!info) return; info->enabled = item->checkState() == Qt::Checked; } /** * @brief UsersDialog::populateList update the view based on filter options * (such as searching). * @param filter */ void UsersWidgetData::populateList() { const auto filter = ui->lineEdit->text(); QRegularExpression nameFilter(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*") + filter + QStringLiteral("*")), QRegularExpression::CaseInsensitiveOption); ui->listWidget->clear(); if (!m_userList.isEmpty()) { for (auto &user : m_userList) { if (filter.isEmpty() || nameFilter.match(user.name).hasMatch()) { if (!user.isValid() && !ui->checkBox->isChecked()) continue; if (user.expiry.toSecsSinceEpoch() > 0 && user.expiry.daysTo(QDateTime::currentDateTime()) > 0 && !ui->checkBox->isChecked()) continue; QString userText = user.name + QStringLiteral("\n") + user.key_id; if (user.created.toSecsSinceEpoch() > 0) { userText += QStringLiteral(" ") + i18nc("time of key creation","created %1", QLocale::system().toString(user.created, QLocale::ShortFormat)); } if (user.expiry.toSecsSinceEpoch() > 0) userText += QStringLiteral(" ") + i18nc("Time of key expiry","expires %1", QLocale::system().toString(user.expiry, QLocale::ShortFormat)); auto *item = new QListWidgetItem(userText, ui->listWidget); item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole, QVariant::fromValue(&user)); if (user.have_secret) { // item->setForeground(QColor(32, 74, 135)); item->setForeground(Qt::blue); QFont font; font.setFamily(font.defaultFamily()); font.setBold(true); item->setFont(font); } else if (!user.isValid()) { item->setBackground(QColor(164, 0, 0)); item->setForeground(Qt::white); } else if (user.expiry.toSecsSinceEpoch() > 0 && user.expiry.daysTo(QDateTime::currentDateTime()) > 0) { item->setForeground(QColor(164, 0, 0)); } else if (!user.fullyValid()) { item->setBackground(QColor(164, 80, 0)); item->setForeground(Qt::white); } ui->listWidget->addItem(item); } } } }