2
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
3
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 2 or (at your option)
8
* version 3 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#include "EditEntryWidget.h"
20
#include "ui_EditEntryWidgetAdvanced.h"
21
#include "ui_EditEntryWidgetAutoType.h"
22
#include "ui_EditEntryWidgetBrowser.h"
23
#include "ui_EditEntryWidgetHistory.h"
24
#include "ui_EditEntryWidgetMain.h"
25
#include "ui_EditEntryWidgetSSHAgent.h"
27
#include <QColorDialog>
28
#include <QDesktopServices>
29
#include <QSortFilterProxyModel>
30
#include <QStringListModel>
32
#include "autotype/AutoType.h"
33
#include "core/AutoTypeAssociations.h"
34
#include "core/Clock.h"
35
#include "core/Config.h"
36
#include "core/Database.h"
37
#include "core/Entry.h"
38
#include "core/EntryAttributes.h"
39
#include "core/Group.h"
40
#include "core/Metadata.h"
41
#include "core/TimeDelta.h"
42
#ifdef WITH_XC_SSHAGENT
43
#include "sshagent/OpenSSHKey.h"
44
#include "sshagent/OpenSSHKeyGenDialog.h"
45
#include "sshagent/SSHAgent.h"
48
#include "EntryURLModel.h"
49
#include "browser/BrowserService.h"
51
#include "gui/Clipboard.h"
52
#include "gui/EditWidgetIcons.h"
53
#include "gui/EditWidgetProperties.h"
54
#include "gui/FileDialog.h"
57
#include "gui/MessageBox.h"
58
#include "gui/entry/AutoTypeAssociationsModel.h"
59
#include "gui/entry/EntryAttributesModel.h"
60
#include "gui/entry/EntryHistoryModel.h"
62
EditEntryWidget::EditEntryWidget(QWidget* parent)
65
, m_mainUi(new Ui::EditEntryWidgetMain())
66
, m_advancedUi(new Ui::EditEntryWidgetAdvanced())
67
, m_autoTypeUi(new Ui::EditEntryWidgetAutoType())
68
, m_sshAgentUi(new Ui::EditEntryWidgetSSHAgent())
69
, m_historyUi(new Ui::EditEntryWidgetHistory())
70
, m_browserUi(new Ui::EditEntryWidgetBrowser())
71
, m_attachments(new EntryAttachments())
72
, m_customData(new CustomData())
73
, m_mainWidget(new QScrollArea(this))
74
, m_advancedWidget(new QWidget(this))
75
, m_iconsWidget(new EditWidgetIcons(this))
76
, m_autoTypeWidget(new QWidget(this))
77
#ifdef WITH_XC_SSHAGENT
78
, m_sshAgentWidget(new QWidget(this))
81
, m_browserSettingsChanged(false)
82
, m_browserWidget(new QWidget(this))
83
, m_additionalURLsDataModel(new EntryURLModel(this))
85
, m_editWidgetProperties(new EditWidgetProperties(this))
86
, m_historyWidget(new QWidget(this))
87
, m_entryAttributes(new EntryAttributes(this))
88
, m_attributesModel(new EntryAttributesModel(m_advancedWidget))
89
, m_historyModel(new EntryHistoryModel(this))
90
, m_sortModel(new QSortFilterProxyModel(this))
91
, m_autoTypeAssoc(new AutoTypeAssociations(this))
92
, m_autoTypeAssocModel(new AutoTypeAssociationsModel(this))
93
, m_autoTypeDefaultSequenceGroup(new QButtonGroup(this))
94
, m_autoTypeWindowSequenceGroup(new QButtonGroup(this))
95
, m_usernameCompleter(new QCompleter(this))
96
, m_usernameCompleterModel(new QStringListModel(this))
103
#ifdef WITH_XC_SSHAGENT
107
#ifdef WITH_XC_BROWSER
115
m_entryModifiedTimer.setSingleShot(true);
116
m_entryModifiedTimer.setInterval(0);
117
connect(&m_entryModifiedTimer, &QTimer::timeout, this, [this] {
118
// TODO: Upon refactor of this widget, this needs to merge unsaved changes in the UI
119
if (isVisible() && m_entry) {
124
connect(this, SIGNAL(accepted()), SLOT(acceptEntry()));
125
connect(this, SIGNAL(rejected()), SLOT(cancel()));
126
connect(this, SIGNAL(apply()), SLOT(commitEntry()));
128
connect(m_iconsWidget,
129
SIGNAL(messageEditEntry(QString,MessageWidget::MessageType)),
130
SLOT(showMessage(QString,MessageWidget::MessageType)));
133
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
135
m_editWidgetProperties->setCustomData(m_customData.data());
137
m_mainUi->passwordEdit->setQualityVisible(true);
140
EditEntryWidget::~EditEntryWidget() = default;
142
void EditEntryWidget::setupMain()
144
m_mainUi->setupUi(m_mainWidget);
145
addPage(tr("Entry"), icons()->icon("document-edit"), m_mainWidget);
147
m_mainUi->usernameComboBox->setEditable(true);
148
m_usernameCompleter->setCompletionMode(QCompleter::InlineCompletion);
149
m_usernameCompleter->setCaseSensitivity(Qt::CaseSensitive);
150
m_usernameCompleter->setModel(m_usernameCompleterModel);
151
m_mainUi->usernameComboBox->setCompleter(m_usernameCompleter);
153
#ifdef WITH_XC_NETWORKING
154
m_mainUi->fetchFaviconButton->setIcon(icons()->icon("favicon-download"));
155
m_mainUi->fetchFaviconButton->setDisabled(true);
157
m_mainUi->fetchFaviconButton->setVisible(false);
160
#ifdef WITH_XC_NETWORKING
161
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
162
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
163
m_mainUi->urlEdit->enableVerifyMode();
165
#ifdef WITH_XC_BROWSER
166
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(entryURLEdited(const QString&)));
168
connect(m_mainUi->expireCheck, &QCheckBox::toggled, [&](bool enabled) {
169
m_mainUi->expireDatePicker->setEnabled(enabled);
171
m_mainUi->expireDatePicker->setDateTime(Clock::currentDateTime());
175
connect(m_mainUi->revealNotesButton, &QToolButton::clicked, this, &EditEntryWidget::toggleHideNotes);
177
m_mainUi->expirePresets->setMenu(createPresetsMenu());
178
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
181
void EditEntryWidget::setupAdvanced()
183
m_advancedUi->setupUi(m_advancedWidget);
184
addPage(tr("Advanced"), icons()->icon("preferences-other"), m_advancedWidget);
186
m_advancedUi->attachmentsWidget->setReadOnly(false);
187
m_advancedUi->attachmentsWidget->setButtonsVisible(true);
189
connect(m_advancedUi->attachmentsWidget,
190
&EntryAttachmentsWidget::errorOccurred,
192
[this](const QString& error) { showMessage(error, MessageWidget::Error); });
194
m_attributesModel->setEntryAttributes(m_entryAttributes);
195
m_advancedUi->attributesView->setModel(m_attributesModel);
198
connect(m_advancedUi->addAttributeButton, SIGNAL(clicked()), SLOT(insertAttribute()));
199
connect(m_advancedUi->editAttributeButton, SIGNAL(clicked()), SLOT(editCurrentAttribute()));
200
connect(m_advancedUi->removeAttributeButton, SIGNAL(clicked()), SLOT(removeCurrentAttribute()));
201
connect(m_advancedUi->protectAttributeButton, SIGNAL(toggled(bool)), SLOT(protectCurrentAttribute(bool)));
202
connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(toggleCurrentAttributeVisibility()));
203
connect(m_advancedUi->attributesView->selectionModel(),
204
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
205
SLOT(updateCurrentAttribute()));
206
connect(m_advancedUi->fgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
207
connect(m_advancedUi->bgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
211
void EditEntryWidget::setupIcon()
213
m_iconsWidget->setShowApplyIconToButton(false);
214
addPage(tr("Icon"), icons()->icon("preferences-desktop-icons"), m_iconsWidget);
215
connect(this, SIGNAL(accepted()), m_iconsWidget, SLOT(abortRequests()));
216
connect(this, SIGNAL(rejected()), m_iconsWidget, SLOT(abortRequests()));
219
void EditEntryWidget::openAutotypeHelp()
221
QDesktopServices::openUrl(
222
QUrl("https://keepassxc.org/docs/KeePassXC_UserGuide.html#_configure_auto_type_sequences"));
225
void EditEntryWidget::setupAutoType()
227
m_autoTypeUi->setupUi(m_autoTypeWidget);
228
addPage(tr("Auto-Type"), icons()->icon("auto-type"), m_autoTypeWidget);
230
m_autoTypeUi->openHelpButton->setIcon(icons()->icon("system-help"));
232
m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton);
233
m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton);
234
m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc);
235
m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel);
236
m_autoTypeUi->assocView->setColumnHidden(1, true);
239
connect(m_autoTypeUi->enableButton, SIGNAL(toggled(bool)), SLOT(updateAutoTypeEnabled()));
240
connect(m_autoTypeUi->customSequenceButton, &QRadioButton::toggled, this, &EditEntryWidget::updateAutoTypeEnabled);
241
connect(m_autoTypeUi->openHelpButton, SIGNAL(clicked()), SLOT(openAutotypeHelp()));
242
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)),
243
m_autoTypeUi->windowSequenceEdit, SLOT(setEnabled(bool)));
244
connect(m_autoTypeUi->assocAddButton, SIGNAL(clicked()), SLOT(insertAutoTypeAssoc()));
245
connect(m_autoTypeUi->assocRemoveButton, SIGNAL(clicked()), SLOT(removeAutoTypeAssoc()));
246
connect(m_autoTypeUi->assocView->selectionModel(),
247
SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
248
SLOT(updateAutoTypeEnabled()));
249
connect(m_autoTypeUi->assocView->selectionModel(),
250
SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
251
SLOT(loadCurrentAssoc(QModelIndex)));
252
connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(updateAutoTypeEnabled()));
253
connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc()));
254
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), SLOT(applyCurrentAssoc()));
255
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)), SLOT(applyCurrentAssoc()));
256
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), SLOT(applyCurrentAssoc()));
260
#ifdef WITH_XC_BROWSER
261
void EditEntryWidget::setupBrowser()
263
if (config()->get(Config::Browser_Enabled).toBool()) {
264
m_browserUi->setupUi(m_browserWidget);
265
addPage(tr("Browser Integration"), icons()->icon("internet-web-browser"), m_browserWidget);
266
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
267
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
269
m_browserUi->messageWidget->setCloseButtonVisible(false);
270
m_browserUi->messageWidget->setAutoHideTimeout(-1);
271
m_browserUi->messageWidget->setAnimate(false);
272
m_browserUi->messageWidget->setVisible(false);
274
// Use a custom item delegate to align the icon to the right side
275
auto iconDelegate = new URLModelIconDelegate(m_browserUi->additionalURLsView);
276
m_browserUi->additionalURLsView->setItemDelegate(iconDelegate);
279
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
280
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
281
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
282
connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
283
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
284
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
285
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
286
connect(m_browserUi->additionalURLsView->selectionModel(),
287
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
288
SLOT(updateCurrentURL()));
289
connect(m_additionalURLsDataModel,
290
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
291
SLOT(updateCurrentAttribute()));
296
void EditEntryWidget::updateBrowserModified()
298
m_browserSettingsChanged = true;
301
void EditEntryWidget::updateBrowser()
303
if (!m_browserSettingsChanged) {
307
// Only update the custom data if no group level settings are used (checkbox is enabled)
308
if (m_browserUi->hideEntryCheckbox->isEnabled()) {
309
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
310
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? TRUE_STR : FALSE_STR));
313
if (m_browserUi->skipAutoSubmitCheckbox->isEnabled()) {
314
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
315
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? TRUE_STR : FALSE_STR));
318
if (m_browserUi->onlyHttpAuthCheckbox->isEnabled()) {
319
auto onlyHttpAuth = m_browserUi->onlyHttpAuthCheckbox->isChecked();
320
m_customData->set(BrowserService::OPTION_ONLY_HTTP_AUTH, (onlyHttpAuth ? TRUE_STR : FALSE_STR));
323
if (m_browserUi->notHttpAuthCheckbox->isEnabled()) {
324
auto notHttpAuth = m_browserUi->notHttpAuthCheckbox->isChecked();
325
m_customData->set(BrowserService::OPTION_NOT_HTTP_AUTH, (notHttpAuth ? TRUE_STR : FALSE_STR));
329
void EditEntryWidget::insertURL()
331
Q_ASSERT(!m_history);
333
QString name(EntryAttributes::AdditionalUrlAttribute);
336
while (m_entryAttributes->keys().contains(name)) {
337
name = QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i));
341
m_entryAttributes->set(name, tr("<empty URL>"));
342
QModelIndex index = m_additionalURLsDataModel->indexByKey(name);
344
m_additionalURLsDataModel->setEntryUrl(m_entry->url());
345
m_browserUi->additionalURLsView->setCurrentIndex(index);
346
m_browserUi->additionalURLsView->edit(index);
351
void EditEntryWidget::removeCurrentURL()
353
Q_ASSERT(!m_history);
355
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
357
if (index.isValid()) {
358
auto name = m_additionalURLsDataModel->keyByIndex(index);
359
auto url = m_entryAttributes->value(name);
360
if (url != tr("<empty URL>")) {
361
auto result = MessageBox::question(this,
362
tr("Confirm Removal"),
363
tr("Are you sure you want to remove this URL?"),
364
MessageBox::Remove | MessageBox::Cancel,
367
if (result != MessageBox::Remove) {
371
m_entryAttributes->remove(m_additionalURLsDataModel->keyByIndex(index));
372
if (m_additionalURLsDataModel->rowCount() == 0) {
373
m_browserUi->editURLButton->setEnabled(false);
374
m_browserUi->removeURLButton->setEnabled(false);
380
void EditEntryWidget::editCurrentURL()
382
Q_ASSERT(!m_history);
384
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
386
if (index.isValid()) {
387
m_browserUi->additionalURLsView->edit(index);
392
void EditEntryWidget::updateCurrentURL()
394
QModelIndex index = m_browserUi->additionalURLsView->currentIndex();
396
if (index.isValid()) {
397
// Don't allow editing in history view
398
m_browserUi->editURLButton->setEnabled(!m_history);
399
m_browserUi->removeURLButton->setEnabled(!m_history);
401
m_browserUi->editURLButton->setEnabled(false);
402
m_browserUi->removeURLButton->setEnabled(false);
406
void EditEntryWidget::entryURLEdited(const QString& url)
408
m_additionalURLsDataModel->setEntryUrl(url);
412
void EditEntryWidget::setupProperties()
414
addPage(tr("Properties"), icons()->icon("document-properties"), m_editWidgetProperties);
417
void EditEntryWidget::setupHistory()
419
m_historyUi->setupUi(m_historyWidget);
420
addPage(tr("History"), icons()->icon("view-history"), m_historyWidget);
422
m_sortModel->setSourceModel(m_historyModel);
423
m_sortModel->setDynamicSortFilter(true);
424
m_sortModel->setSortLocaleAware(true);
425
m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
426
m_sortModel->setSortRole(Qt::UserRole);
428
m_historyUi->historyView->setModel(m_sortModel);
429
m_historyUi->historyView->setRootIsDecorated(false);
432
connect(m_historyUi->historyView, SIGNAL(activated(QModelIndex)), SLOT(histEntryActivated(QModelIndex)));
433
connect(m_historyUi->historyView->selectionModel(),
434
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
435
SLOT(updateHistoryButtons(QModelIndex,QModelIndex)));
437
connect(m_historyUi->showButton, SIGNAL(clicked()), SLOT(showHistoryEntry()));
438
connect(m_historyUi->restoreButton, SIGNAL(clicked()), SLOT(restoreHistoryEntry()));
439
connect(m_historyUi->deleteButton, SIGNAL(clicked()), SLOT(deleteHistoryEntry()));
440
connect(m_historyUi->deleteAllButton, SIGNAL(clicked()), SLOT(deleteAllHistoryEntries()));
444
void EditEntryWidget::setupEntryUpdate()
447
connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
448
connect(m_mainUi->usernameComboBox->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(setModified()));
449
connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
450
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
451
#ifdef WITH_XC_NETWORKING
452
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
454
connect(m_mainUi->tagsList, SIGNAL(tagsEdited()), this, SLOT(setModified()));
455
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
456
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
457
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
460
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
461
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
462
connect(m_advancedUi->excludeReportsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
463
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
464
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
465
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
468
connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
471
connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
472
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
473
connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
474
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
475
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
476
connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
477
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
478
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
480
// Properties and History tabs don't need extra connections
482
#ifdef WITH_XC_SSHAGENT
484
if (sshAgent()->isEnabled()) {
485
connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
486
connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
487
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
488
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
489
connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
490
connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
491
connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
492
connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
493
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
494
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
498
#ifdef WITH_XC_BROWSER
499
if (config()->get(Config::Browser_Enabled).toBool()) {
500
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
501
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
502
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
503
connect(m_browserUi->notHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
504
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
505
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
506
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
511
void EditEntryWidget::emitHistoryEntryActivated(const QModelIndex& index)
513
Q_ASSERT(!m_history);
515
Entry* entry = m_historyModel->entryFromIndex(index);
517
emit historyEntryActivated(entry);
521
void EditEntryWidget::histEntryActivated(const QModelIndex& index)
523
Q_ASSERT(!m_history);
525
QModelIndex indexMapped = m_sortModel->mapToSource(index);
526
if (indexMapped.isValid()) {
527
emitHistoryEntryActivated(indexMapped);
531
void EditEntryWidget::updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous)
535
if (m_historyModel->entryFromIndex(current)) {
536
m_historyUi->showButton->setEnabled(true);
537
m_historyUi->restoreButton->setEnabled(true);
538
m_historyUi->deleteButton->setEnabled(true);
540
m_historyUi->showButton->setEnabled(false);
541
m_historyUi->restoreButton->setEnabled(false);
542
m_historyUi->deleteButton->setEnabled(false);
546
#ifdef WITH_XC_SSHAGENT
547
void EditEntryWidget::setupSSHAgent()
549
m_pendingPrivateKey = "";
550
m_sshAgentUi->setupUi(m_sshAgentWidget);
552
QFont fixedFont = Font::fixedFont();
553
m_sshAgentUi->fingerprintTextLabel->setFont(fixedFont);
554
m_sshAgentUi->commentTextLabel->setFont(fixedFont);
555
m_sshAgentUi->publicKeyEdit->setFont(fixedFont);
558
connect(m_sshAgentUi->attachmentRadioButton, &QRadioButton::clicked,
559
this, &EditEntryWidget::updateSSHAgentKeyInfo);
560
connect(m_sshAgentUi->attachmentComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
561
this, &EditEntryWidget::updateSSHAgentAttachment);
562
connect(m_sshAgentUi->externalFileRadioButton, &QRadioButton::clicked,
563
this, &EditEntryWidget::updateSSHAgentKeyInfo);
564
connect(m_sshAgentUi->externalFileEdit, &QLineEdit::textChanged, this, &EditEntryWidget::updateSSHAgentKeyInfo);
565
connect(m_sshAgentUi->browseButton, &QPushButton::clicked, this, &EditEntryWidget::browsePrivateKey);
566
connect(m_sshAgentUi->addToAgentButton, &QPushButton::clicked, this, &EditEntryWidget::addKeyToAgent);
567
connect(m_sshAgentUi->removeFromAgentButton, &QPushButton::clicked, this, &EditEntryWidget::removeKeyFromAgent);
568
connect(m_sshAgentUi->decryptButton, &QPushButton::clicked, this, &EditEntryWidget::decryptPrivateKey);
569
connect(m_sshAgentUi->copyToClipboardButton, &QPushButton::clicked, this, &EditEntryWidget::copyPublicKey);
570
connect(m_sshAgentUi->generateButton, &QPushButton::clicked, this, &EditEntryWidget::generatePrivateKey);
572
connect(m_attachments.data(), &EntryAttachments::modified,
573
this, &EditEntryWidget::updateSSHAgentAttachments);
576
addPage(tr("SSH Agent"), icons()->icon("utilities-terminal"), m_sshAgentWidget);
579
void EditEntryWidget::setSSHAgentSettings()
581
m_sshAgentUi->addKeyToAgentCheckBox->setChecked(m_sshAgentSettings.addAtDatabaseOpen());
582
m_sshAgentUi->removeKeyFromAgentCheckBox->setChecked(m_sshAgentSettings.removeAtDatabaseClose());
583
m_sshAgentUi->requireUserConfirmationCheckBox->setChecked(m_sshAgentSettings.useConfirmConstraintWhenAdding());
584
m_sshAgentUi->lifetimeCheckBox->setChecked(m_sshAgentSettings.useLifetimeConstraintWhenAdding());
585
m_sshAgentUi->lifetimeSpinBox->setValue(m_sshAgentSettings.lifetimeConstraintDuration());
586
m_sshAgentUi->attachmentComboBox->clear();
587
m_sshAgentUi->addToAgentButton->setEnabled(false);
588
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
589
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
592
void EditEntryWidget::updateSSHAgent()
594
m_sshAgentSettings.reset();
595
m_sshAgentSettings.fromEntry(m_entry);
596
setSSHAgentSettings();
598
if (!m_pendingPrivateKey.isEmpty()) {
599
m_sshAgentSettings.setAttachmentName(m_pendingPrivateKey);
600
m_sshAgentSettings.setSelectedType("attachment");
601
m_pendingPrivateKey = "";
604
updateSSHAgentAttachments();
607
void EditEntryWidget::updateSSHAgentAttachment()
609
m_sshAgentUi->attachmentRadioButton->setChecked(true);
610
updateSSHAgentKeyInfo();
613
void EditEntryWidget::updateSSHAgentAttachments()
615
// detect if KeeAgent.settings was removed by hand and reset settings
616
if (m_entry && KeeAgentSettings::inEntryAttachments(m_entry->attachments())
617
&& !KeeAgentSettings::inEntryAttachments(m_attachments.data())) {
618
m_sshAgentSettings.reset();
619
setSSHAgentSettings();
622
m_sshAgentUi->attachmentComboBox->clear();
623
m_sshAgentUi->attachmentComboBox->addItem("");
625
for (const QString& fileName : m_attachments->keys()) {
626
if (fileName == "KeeAgent.settings") {
630
m_sshAgentUi->attachmentComboBox->addItem(fileName);
633
m_sshAgentUi->attachmentComboBox->setCurrentText(m_sshAgentSettings.attachmentName());
634
m_sshAgentUi->externalFileEdit->setText(m_sshAgentSettings.fileName());
636
if (m_sshAgentSettings.selectedType() == "attachment") {
637
m_sshAgentUi->attachmentRadioButton->setChecked(true);
639
m_sshAgentUi->externalFileRadioButton->setChecked(true);
642
updateSSHAgentKeyInfo();
645
void EditEntryWidget::updateSSHAgentKeyInfo()
647
m_sshAgentUi->addToAgentButton->setEnabled(false);
648
m_sshAgentUi->removeFromAgentButton->setEnabled(false);
649
m_sshAgentUi->copyToClipboardButton->setEnabled(false);
650
m_sshAgentUi->fingerprintTextLabel->setText(tr("n/a"));
651
m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
652
m_sshAgentUi->decryptButton->setEnabled(false);
653
m_sshAgentUi->publicKeyEdit->document()->setPlainText("");
657
if (!getOpenSSHKey(key)) {
661
if (!key.fingerprint().isEmpty()) {
662
m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
663
+ key.fingerprint(QCryptographicHash::Sha256));
665
m_sshAgentUi->fingerprintTextLabel->setText(tr("(encrypted)"));
668
if (!key.comment().isEmpty() || !key.encrypted()) {
669
m_sshAgentUi->commentTextLabel->setText(key.comment());
671
m_sshAgentUi->commentTextLabel->setText(tr("(encrypted)"));
672
m_sshAgentUi->decryptButton->setEnabled(true);
675
if (!key.publicKey().isEmpty()) {
676
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
677
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
679
m_sshAgentUi->publicKeyEdit->document()->setPlainText(tr("(encrypted)"));
680
m_sshAgentUi->copyToClipboardButton->setDisabled(true);
683
// enable agent buttons only if we have an agent running
684
if (sshAgent()->isAgentRunning()) {
685
m_sshAgentUi->addToAgentButton->setEnabled(true);
686
m_sshAgentUi->removeFromAgentButton->setEnabled(true);
688
sshAgent()->setAutoRemoveOnLock(key, m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
692
void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const
694
settings.setAddAtDatabaseOpen(m_sshAgentUi->addKeyToAgentCheckBox->isChecked());
695
settings.setRemoveAtDatabaseClose(m_sshAgentUi->removeKeyFromAgentCheckBox->isChecked());
696
settings.setUseConfirmConstraintWhenAdding(m_sshAgentUi->requireUserConfirmationCheckBox->isChecked());
697
settings.setUseLifetimeConstraintWhenAdding(m_sshAgentUi->lifetimeCheckBox->isChecked());
698
settings.setLifetimeConstraintDuration(m_sshAgentUi->lifetimeSpinBox->value());
700
if (m_sshAgentUi->attachmentRadioButton->isChecked()) {
701
settings.setSelectedType("attachment");
703
settings.setSelectedType("file");
705
settings.setAttachmentName(m_sshAgentUi->attachmentComboBox->currentText());
706
settings.setFileName(m_sshAgentUi->externalFileEdit->text());
708
// we don't use this as we don't run an agent but for compatibility we set it if necessary
709
settings.setAllowUseOfSshKey(settings.addAtDatabaseOpen() || settings.removeAtDatabaseClose());
711
// we don't use this either but we don't want it to dirty flag the config
712
settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile());
715
void EditEntryWidget::updateTotp()
718
m_attributesModel->setEntryAttributes(m_entry->attributes());
722
void EditEntryWidget::browsePrivateKey()
724
auto fileName = fileDialog()->getOpenFileName(this, tr("Select private key"), FileDialog::getLastDir("sshagent"));
725
if (!fileName.isEmpty()) {
726
FileDialog::saveLastDir("sshagent", fileName);
727
m_sshAgentUi->externalFileEdit->setText(fileName);
728
m_sshAgentUi->externalFileRadioButton->setChecked(true);
729
updateSSHAgentKeyInfo();
733
bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
735
KeeAgentSettings settings;
736
toKeeAgentSettings(settings);
738
if (!m_entry || !settings.keyConfigured()) {
742
if (!settings.toOpenSSHKey(m_mainUi->usernameComboBox->lineEdit()->text(),
743
m_mainUi->passwordEdit->text(),
745
m_attachments.data(),
748
showMessage(settings.errorString(), MessageWidget::Error);
755
void EditEntryWidget::addKeyToAgent()
759
if (!getOpenSSHKey(key, true)) {
763
m_sshAgentUi->commentTextLabel->setText(key.comment());
764
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
766
KeeAgentSettings settings;
767
toKeeAgentSettings(settings);
769
if (!sshAgent()->addIdentity(key, settings, m_db->uuid())) {
770
showMessage(sshAgent()->errorString(), MessageWidget::Error);
775
void EditEntryWidget::removeKeyFromAgent()
779
if (!getOpenSSHKey(key)) {
783
if (!sshAgent()->removeIdentity(key)) {
784
showMessage(sshAgent()->errorString(), MessageWidget::Error);
789
void EditEntryWidget::decryptPrivateKey()
793
if (!getOpenSSHKey(key, true)) {
797
if (!key.comment().isEmpty()) {
798
m_sshAgentUi->commentTextLabel->setText(key.comment());
800
m_sshAgentUi->commentTextLabel->setText(tr("n/a"));
803
m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
804
+ key.fingerprint(QCryptographicHash::Sha256));
805
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
806
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
809
void EditEntryWidget::copyPublicKey()
811
clipboard()->setText(m_sshAgentUi->publicKeyEdit->document()->toPlainText());
814
void EditEntryWidget::generatePrivateKey()
816
auto dialog = new OpenSSHKeyGenDialog(this);
819
dialog->setKey(&key);
821
if (dialog->exec()) {
822
// derive openssh naming from type
823
QString keyPrefix = key.type();
824
if (keyPrefix.startsWith("ecdsa")) {
825
keyPrefix = "id_ecdsa";
827
keyPrefix.replace("ssh-", "id_");
830
for (int i = 0; i < 10; i++) {
831
QString keyName = keyPrefix;
834
keyName += "." + QString::number(i);
837
if (!m_entry->attachments()->hasKey(keyName)) {
838
m_pendingPrivateKey = keyName;
839
m_entry->attachments()->set(m_pendingPrivateKey, key.privateKey().toUtf8());
847
void EditEntryWidget::useExpiryPreset(QAction* action)
849
m_mainUi->expireCheck->setChecked(true);
850
auto delta = action->data().value<TimeDelta>();
851
QDateTime now = Clock::currentDateTime();
852
QDateTime expiryDateTime = now + delta;
853
m_mainUi->expireDatePicker->setDateTime(expiryDateTime);
856
void EditEntryWidget::toggleHideNotes(bool visible)
858
m_mainUi->notesEdit->setVisible(visible);
859
m_mainUi->revealNotesButton->setIcon(icons()->onOffIcon("password-show", visible));
862
Entry* EditEntryWidget::currentEntry() const
867
void EditEntryWidget::loadEntry(Entry* entry,
870
const QString& parentName,
871
QSharedPointer<Database> database)
874
m_db = std::move(database);
879
setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Entry history")));
882
setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Add entry")));
884
setHeadline(QString("%1 \u2022 %2 \u2022 %3").arg(parentName, entry->title(), tr("Edit entry")));
885
// Reload entry details if changed outside of the edit dialog
886
connect(m_entry, &Entry::modified, this, [this] { m_entryModifiedTimer.start(); });
891
setReadOnly(m_history);
894
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
895
#ifdef WITH_XC_SSHAGENT
896
setPageHidden(m_sshAgentWidget, !sshAgent()->isEnabled());
899
// Force the user to Save/Discard new entries
900
showApplyButton(!m_create);
905
void EditEntryWidget::setForms(Entry* entry, bool restore)
907
m_attachments->copyDataFrom(entry->attachments());
908
m_customData->copyDataFrom(entry->customData());
910
m_mainUi->titleEdit->setReadOnly(m_history);
911
m_mainUi->usernameComboBox->lineEdit()->setReadOnly(m_history);
912
m_mainUi->urlEdit->setReadOnly(m_history);
913
m_mainUi->passwordEdit->setReadOnly(m_history);
914
m_mainUi->tagsList->tags(entry->tagList());
915
m_mainUi->tagsList->completion(m_db->tagList());
916
m_mainUi->expireCheck->setEnabled(!m_history);
917
m_mainUi->expireDatePicker->setReadOnly(m_history);
918
m_mainUi->revealNotesButton->setIcon(icons()->onOffIcon("password-show", false));
919
m_mainUi->revealNotesButton->setVisible(config()->get(Config::Security_HideNotes).toBool());
920
m_mainUi->notesEdit->setReadOnly(m_history);
921
m_mainUi->notesEdit->setVisible(!config()->get(Config::Security_HideNotes).toBool());
922
if (config()->get(Config::GUI_MonospaceNotes).toBool()) {
923
m_mainUi->notesEdit->setFont(Font::fixedFont());
925
m_mainUi->notesEdit->setFont(Font::defaultFont());
928
m_advancedUi->attachmentsWidget->setReadOnly(m_history);
929
m_advancedUi->addAttributeButton->setEnabled(!m_history);
930
m_advancedUi->editAttributeButton->setEnabled(false);
931
m_advancedUi->removeAttributeButton->setEnabled(false);
932
m_advancedUi->attributesEdit->setReadOnly(m_history);
933
QAbstractItemView::EditTriggers editTriggers;
935
editTriggers = QAbstractItemView::NoEditTriggers;
937
editTriggers = QAbstractItemView::DoubleClicked;
939
m_advancedUi->attributesView->setEditTriggers(editTriggers);
940
m_advancedUi->excludeReportsCheckBox->setChecked(entry->excludeFromReports());
941
setupColorButton(true, entry->foregroundColor());
942
setupColorButton(false, entry->backgroundColor());
943
m_iconsWidget->setEnabled(!m_history);
944
m_autoTypeUi->sequenceEdit->setReadOnly(m_history);
945
m_autoTypeUi->windowTitleCombo->lineEdit()->setReadOnly(m_history);
946
m_autoTypeUi->windowSequenceEdit->setReadOnly(m_history);
947
m_historyWidget->setEnabled(!m_history);
949
m_mainUi->titleEdit->setText(entry->title());
950
m_mainUi->usernameComboBox->lineEdit()->setText(entry->username());
951
m_mainUi->urlEdit->setText(entry->url());
952
m_mainUi->passwordEdit->setText(entry->password());
953
m_mainUi->passwordEdit->setShowPassword(!config()->get(Config::Security_PasswordsHidden).toBool());
955
m_mainUi->passwordEdit->enablePasswordGenerator();
957
m_mainUi->expireCheck->setChecked(entry->timeInfo().expires());
958
m_mainUi->expireDatePicker->setDateTime(entry->timeInfo().expiryTime().toLocalTime());
959
m_mainUi->expirePresets->setEnabled(!m_history);
961
QList<QString> commonUsernames = m_db->commonUsernames();
962
m_usernameCompleterModel->setStringList(commonUsernames);
963
QString usernameToRestore = m_mainUi->usernameComboBox->lineEdit()->text();
964
m_mainUi->usernameComboBox->clear();
965
m_mainUi->usernameComboBox->addItems(commonUsernames);
966
m_mainUi->usernameComboBox->lineEdit()->setText(usernameToRestore);
968
m_mainUi->notesEdit->setPlainText(entry->notes());
970
m_advancedUi->attachmentsWidget->linkAttachments(m_attachments.data());
971
m_entryAttributes->copyCustomKeysFrom(entry->attributes());
973
if (m_attributesModel->rowCount() != 0) {
974
m_advancedUi->attributesView->setCurrentIndex(m_attributesModel->index(0, 0));
976
m_advancedUi->attributesEdit->setPlainText("");
977
m_advancedUi->attributesEdit->setEnabled(false);
980
QList<int> sizes = m_advancedUi->attributesSplitter->sizes();
981
sizes.replace(0, m_advancedUi->attributesSplitter->width() * 0.3);
982
sizes.replace(1, m_advancedUi->attributesSplitter->width() * 0.7);
983
m_advancedUi->attributesSplitter->setSizes(sizes);
985
IconStruct iconStruct;
986
iconStruct.uuid = entry->iconUuid();
987
iconStruct.number = entry->iconNumber();
988
m_iconsWidget->load(entry->uuid(), m_db, iconStruct, entry->webUrl());
990
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
991
if (entry->defaultAutoTypeSequence().isEmpty()) {
992
m_autoTypeUi->inheritSequenceButton->setChecked(true);
994
m_autoTypeUi->customSequenceButton->setChecked(true);
996
m_autoTypeUi->sequenceEdit->setText(entry->effectiveAutoTypeSequence());
997
m_autoTypeUi->windowTitleCombo->lineEdit()->clear();
998
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
999
m_autoTypeUi->windowSequenceEdit->setText("");
1000
m_autoTypeAssoc->copyDataFrom(entry->autoTypeAssociations());
1001
m_autoTypeAssocModel->setEntry(entry);
1002
if (m_autoTypeAssoc->size() != 0) {
1003
m_autoTypeUi->assocView->setCurrentIndex(m_autoTypeAssocModel->index(0, 0));
1006
m_autoTypeUi->windowTitleCombo->refreshWindowList();
1008
updateAutoTypeEnabled();
1010
#ifdef WITH_XC_SSHAGENT
1011
if (sshAgent()->isEnabled()) {
1016
#ifdef WITH_XC_BROWSER
1017
if (config()->get(Config::Browser_Enabled).toBool()) {
1018
if (!hasPage(m_browserWidget)) {
1022
auto hideEntriesCheckBoxEnabled = true;
1023
auto skipAutoSubmitCheckBoxEnabled = true;
1024
auto onlyHttpAuthCheckBoxEnabled = true;
1025
auto notHttpAuthCheckBoxEnabled = true;
1026
auto hideEntries = false;
1027
auto skipAutoSubmit = false;
1028
auto onlyHttpAuth = false;
1029
auto notHttpAuth = false;
1031
const auto group = m_entry->group();
1033
hideEntries = group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY) == Group::Enable;
1034
skipAutoSubmit = group->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == Group::Enable;
1035
onlyHttpAuth = group->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Enable;
1036
notHttpAuth = group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Enable;
1038
hideEntriesCheckBoxEnabled =
1039
group->resolveCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY) == Group::Inherit;
1040
skipAutoSubmitCheckBoxEnabled =
1041
group->resolveCustomDataTriState(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == Group::Inherit;
1042
onlyHttpAuthCheckBoxEnabled =
1043
group->resolveCustomDataTriState(BrowserService::OPTION_ONLY_HTTP_AUTH) == Group::Inherit;
1044
notHttpAuthCheckBoxEnabled =
1045
group->resolveCustomDataTriState(BrowserService::OPTION_NOT_HTTP_AUTH) == Group::Inherit;
1048
// Show information about group level settings
1049
if (!hideEntriesCheckBoxEnabled || !skipAutoSubmitCheckBoxEnabled || !onlyHttpAuthCheckBoxEnabled
1050
|| !notHttpAuthCheckBoxEnabled) {
1051
m_browserUi->messageWidget->showMessage(
1052
tr("Some Browser Integration settings are overridden by group settings."), MessageWidget::Information);
1053
m_browserUi->messageWidget->setVisible(true);
1056
// Disable checkboxes based on group level settings
1057
updateBrowserIntegrationCheckbox(
1058
m_browserUi->hideEntryCheckbox, hideEntriesCheckBoxEnabled, hideEntries, BrowserService::OPTION_HIDE_ENTRY);
1059
updateBrowserIntegrationCheckbox(m_browserUi->skipAutoSubmitCheckbox,
1060
skipAutoSubmitCheckBoxEnabled,
1062
BrowserService::OPTION_SKIP_AUTO_SUBMIT);
1063
updateBrowserIntegrationCheckbox(m_browserUi->onlyHttpAuthCheckbox,
1064
onlyHttpAuthCheckBoxEnabled,
1066
BrowserService::OPTION_ONLY_HTTP_AUTH);
1067
updateBrowserIntegrationCheckbox(m_browserUi->notHttpAuthCheckbox,
1068
notHttpAuthCheckBoxEnabled,
1070
BrowserService::OPTION_NOT_HTTP_AUTH);
1072
m_browserUi->addURLButton->setEnabled(!m_history);
1073
m_browserUi->removeURLButton->setEnabled(false);
1074
m_browserUi->editURLButton->setEnabled(false);
1075
m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
1077
if (m_additionalURLsDataModel->rowCount() != 0) {
1078
m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
1082
setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
1085
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
1087
if (!m_history && !restore) {
1088
m_historyModel->setEntries(entry->historyItems(), entry);
1089
m_historyUi->historyView->sortByColumn(0, Qt::DescendingOrder);
1091
if (m_historyModel->rowCount() > 0) {
1092
m_historyUi->deleteAllButton->setEnabled(true);
1094
m_historyUi->deleteAllButton->setEnabled(false);
1097
updateHistoryButtons(m_historyUi->historyView->currentIndex(), QModelIndex());
1099
m_mainUi->titleEdit->setFocus();
1103
* Commit the form values to in-memory database representation
1105
* @return true is commit successful, otherwise false
1107
bool EditEntryWidget::commitEntry()
1112
emit editFinished(false);
1116
// HACK: Check that entry pointer is still valid, see https://github.com/keepassxreboot/keepassxc/issues/5722
1118
QMessageBox::information(this,
1119
tr("Invalid Entry"),
1120
tr("An external merge operation has invalidated this entry.\n"
1121
"Unfortunately, any changes made have been lost."));
1125
// Check Auto-Type validity early
1127
if (m_autoTypeUi->customSequenceButton->isChecked()
1128
&& !AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text(), m_entry, error)) {
1129
auto res = MessageBox::question(this,
1130
tr("Auto-Type Validation Error"),
1131
tr("An error occurred while validating the custom Auto-Type sequence:\n%1\n"
1132
"Would you like to correct it?")
1134
MessageBox::Yes | MessageBox::No,
1136
if (res == MessageBox::Yes) {
1141
for (const auto& assoc : m_autoTypeAssoc->getAll()) {
1142
if (!AutoType::verifyAutoTypeSyntax(assoc.sequence, m_entry, error)) {
1144
MessageBox::question(this,
1145
tr("Auto-Type Validation Error"),
1146
tr("An error occurred while validating the Auto-Type sequence for \"%1\":\n%2\n"
1147
"Would you like to correct it?")
1148
.arg(assoc.window.left(40), error),
1149
MessageBox::Yes | MessageBox::No,
1151
if (res == MessageBox::Yes) {
1158
if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) {
1159
QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex());
1160
m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(key));
1163
m_currentAttribute = QPersistentModelIndex();
1165
// must stand before beginUpdate()
1166
// we don't want to create a new history item, if only the history has changed
1167
m_entry->removeHistoryItems(m_historyModel->deletedEntries());
1168
m_historyModel->clearDeletedEntries();
1170
m_autoTypeAssoc->removeEmpty();
1172
#ifdef WITH_XC_SSHAGENT
1173
toKeeAgentSettings(m_sshAgentSettings);
1176
// Begin entry update
1178
m_entry->beginUpdate();
1181
#ifdef WITH_XC_BROWSER
1182
if (config()->get(Config::Browser_Enabled).toBool()) {
1187
updateEntryData(m_entry);
1190
m_entry->endUpdate();
1194
m_historyModel->setEntries(m_entry->historyItems(), m_entry);
1195
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
1196
m_advancedUi->attachmentsWidget->linkAttachments(m_entry->attachments());
1198
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
1200
// Prevent a reload due to entry modified signals
1201
m_entryModifiedTimer.stop();
1206
void EditEntryWidget::acceptEntry()
1208
if (commitEntry()) {
1210
emit editFinished(true);
1214
void EditEntryWidget::updateEntryData(Entry* entry) const
1216
QRegularExpression newLineRegex("(?:\r?\n|\r)");
1218
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
1219
entry->attachments()->copyDataFrom(m_attachments.data());
1220
entry->customData()->copyDataFrom(m_customData.data());
1221
entry->setTitle(m_mainUi->titleEdit->text().replace(newLineRegex, " "));
1222
entry->setUsername(m_mainUi->usernameComboBox->lineEdit()->text().replace(newLineRegex, " "));
1223
entry->setUrl(m_mainUi->urlEdit->text().replace(newLineRegex, " "));
1224
entry->setPassword(m_mainUi->passwordEdit->text());
1225
entry->setExpires(m_mainUi->expireCheck->isChecked());
1226
entry->setExpiryTime(m_mainUi->expireDatePicker->dateTime().toUTC());
1227
entry->setTags(m_mainUi->tagsList->tags().toSet().toList().join(";")); // remove repeated tags
1229
entry->setNotes(m_mainUi->notesEdit->toPlainText());
1231
if (entry->excludeFromReports() != m_advancedUi->excludeReportsCheckBox->isChecked()) {
1232
entry->setExcludeFromReports(m_advancedUi->excludeReportsCheckBox->isChecked());
1235
if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
1236
entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
1238
entry->setForegroundColor(QString());
1241
if (m_advancedUi->bgColorCheckBox->isChecked() && m_advancedUi->bgColorButton->property("color").isValid()) {
1242
entry->setBackgroundColor(m_advancedUi->bgColorButton->property("color").toString());
1244
entry->setBackgroundColor(QString());
1247
IconStruct iconStruct = m_iconsWidget->state();
1249
if (iconStruct.number < 0) {
1250
entry->setIcon(Entry::DefaultIconNumber);
1251
} else if (iconStruct.uuid.isNull()) {
1252
entry->setIcon(iconStruct.number);
1254
entry->setIcon(iconStruct.uuid);
1257
entry->setAutoTypeEnabled(m_autoTypeUi->enableButton->isChecked());
1258
if (m_autoTypeUi->inheritSequenceButton->isChecked()) {
1259
entry->setDefaultAutoTypeSequence(QString());
1261
entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text());
1264
entry->autoTypeAssociations()->copyDataFrom(m_autoTypeAssoc);
1266
#ifdef WITH_XC_SSHAGENT
1267
if (sshAgent()->isEnabled()) {
1268
m_sshAgentSettings.toEntry(entry);
1273
void EditEntryWidget::updateBrowserIntegrationCheckbox(QCheckBox* checkBox,
1276
const QString& option)
1278
auto block = checkBox->signalsBlocked();
1279
checkBox->blockSignals(true);
1282
if (m_customData->contains(option)) {
1283
checkBox->setChecked(m_customData->value(option) == TRUE_STR);
1285
checkBox->setChecked(false);
1288
checkBox->setChecked(value);
1290
checkBox->setEnabled(enabled);
1292
checkBox->blockSignals(block);
1295
void EditEntryWidget::cancel()
1300
emit editFinished(false);
1304
if (!m_entry->iconUuid().isNull() && !m_db->metadata()->hasCustomIcon(m_entry->iconUuid())) {
1305
m_entry->setIcon(Entry::DefaultIconNumber);
1308
bool accepted = false;
1310
auto result = MessageBox::question(this,
1311
tr("Unsaved Changes"),
1312
tr("Would you like to save changes to this entry?"),
1313
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
1314
MessageBox::Cancel);
1315
if (result == MessageBox::Cancel) {
1317
} else if (result == MessageBox::Save) {
1319
if (!commitEntry()) {
1326
emit editFinished(accepted);
1329
void EditEntryWidget::clear()
1332
m_entry->disconnect(this);
1338
m_mainUi->titleEdit->setText("");
1339
m_mainUi->passwordEdit->setText("");
1340
m_mainUi->urlEdit->setText("");
1341
m_mainUi->notesEdit->clear();
1343
m_entryAttributes->clear();
1344
m_attachments->clear();
1345
m_customData->clear();
1346
m_autoTypeAssoc->clear();
1347
m_historyModel->clear();
1348
m_iconsWidget->reset();
1352
#ifdef WITH_XC_NETWORKING
1353
void EditEntryWidget::updateFaviconButtonEnable(const QString& url)
1355
m_mainUi->fetchFaviconButton->setDisabled(url.isEmpty());
1359
void EditEntryWidget::insertAttribute()
1361
Q_ASSERT(!m_history);
1363
QString name = tr("New attribute");
1366
while (m_entryAttributes->keys().contains(name)) {
1367
name = tr("New attribute %1").arg(i);
1371
m_entryAttributes->set(name, "");
1372
QModelIndex index = m_attributesModel->indexByKey(name);
1374
m_advancedUi->attributesView->setCurrentIndex(index);
1375
m_advancedUi->attributesView->edit(index);
1380
void EditEntryWidget::editCurrentAttribute()
1382
Q_ASSERT(!m_history);
1384
QModelIndex index = m_advancedUi->attributesView->currentIndex();
1386
if (index.isValid()) {
1387
m_advancedUi->attributesView->edit(index);
1392
void EditEntryWidget::removeCurrentAttribute()
1394
Q_ASSERT(!m_history);
1396
QModelIndex index = m_advancedUi->attributesView->currentIndex();
1398
if (index.isValid()) {
1400
auto result = MessageBox::question(this,
1401
tr("Confirm Removal"),
1402
tr("Are you sure you want to remove this attribute?"),
1403
MessageBox::Remove | MessageBox::Cancel,
1404
MessageBox::Cancel);
1406
if (result == MessageBox::Remove) {
1407
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
1413
void EditEntryWidget::updateCurrentAttribute()
1415
QModelIndex newIndex = m_advancedUi->attributesView->currentIndex();
1416
QString newKey = m_attributesModel->keyByIndex(newIndex);
1418
if (!m_history && m_currentAttribute != newIndex) {
1419
// Save changes to the currently selected attribute if editing is enabled
1420
if (m_currentAttribute.isValid() && m_advancedUi->attributesEdit->isEnabled()) {
1421
QString currKey = m_attributesModel->keyByIndex(m_currentAttribute);
1422
m_entryAttributes->set(
1423
currKey, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(currKey));
1427
displayAttribute(newIndex, m_entryAttributes->isProtected(newKey));
1429
m_currentAttribute = newIndex;
1432
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
1434
// Block signals to prevent modified being set
1435
m_advancedUi->protectAttributeButton->blockSignals(true);
1436
m_advancedUi->attributesEdit->blockSignals(true);
1437
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
1439
if (index.isValid()) {
1440
QString key = m_attributesModel->keyByIndex(index);
1441
if (showProtected) {
1442
m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED] Press Reveal to view or edit"));
1443
m_advancedUi->attributesEdit->setEnabled(false);
1444
m_advancedUi->revealAttributeButton->setEnabled(true);
1445
m_advancedUi->protectAttributeButton->setChecked(true);
1447
m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key));
1448
m_advancedUi->attributesEdit->setEnabled(true);
1449
m_advancedUi->revealAttributeButton->setEnabled(false);
1450
m_advancedUi->protectAttributeButton->setChecked(false);
1453
// Don't allow editing in history view
1454
m_advancedUi->protectAttributeButton->setEnabled(!m_history);
1455
m_advancedUi->editAttributeButton->setEnabled(!m_history);
1456
m_advancedUi->removeAttributeButton->setEnabled(!m_history);
1458
m_advancedUi->attributesEdit->setPlainText("");
1459
m_advancedUi->attributesEdit->setEnabled(false);
1460
m_advancedUi->revealAttributeButton->setEnabled(false);
1461
m_advancedUi->protectAttributeButton->setChecked(false);
1462
m_advancedUi->protectAttributeButton->setEnabled(false);
1463
m_advancedUi->editAttributeButton->setEnabled(false);
1464
m_advancedUi->removeAttributeButton->setEnabled(false);
1467
m_advancedUi->protectAttributeButton->blockSignals(false);
1468
m_advancedUi->attributesEdit->blockSignals(false);
1471
void EditEntryWidget::protectCurrentAttribute(bool state)
1473
QModelIndex index = m_advancedUi->attributesView->currentIndex();
1474
if (!m_history && index.isValid()) {
1475
QString key = m_attributesModel->keyByIndex(index);
1477
// Save the current text and protect the attribute
1478
m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), true);
1480
// Unprotect the current attribute value (don't save text as it is obscured)
1481
m_entryAttributes->set(key, m_entryAttributes->value(key), false);
1484
// Display the attribute
1485
displayAttribute(index, state);
1489
void EditEntryWidget::toggleCurrentAttributeVisibility()
1491
if (!m_advancedUi->attributesEdit->isEnabled()) {
1492
QModelIndex index = m_advancedUi->attributesView->currentIndex();
1493
if (index.isValid()) {
1494
bool oldBlockSignals = m_advancedUi->attributesEdit->blockSignals(true);
1495
QString key = m_attributesModel->keyByIndex(index);
1496
m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key));
1497
m_advancedUi->attributesEdit->setEnabled(true);
1498
m_advancedUi->attributesEdit->blockSignals(oldBlockSignals);
1500
m_advancedUi->revealAttributeButton->setText(tr("Hide"));
1502
protectCurrentAttribute(true);
1503
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
1507
void EditEntryWidget::updateAutoTypeEnabled()
1509
bool autoTypeEnabled = m_autoTypeUi->enableButton->isChecked();
1510
bool validIndex = m_autoTypeUi->assocView->currentIndex().isValid() && m_autoTypeAssoc->size() != 0;
1512
m_autoTypeUi->enableButton->setEnabled(!m_history);
1513
m_autoTypeUi->inheritSequenceButton->setEnabled(!m_history && autoTypeEnabled);
1514
m_autoTypeUi->customSequenceButton->setEnabled(!m_history && autoTypeEnabled);
1515
m_autoTypeUi->sequenceEdit->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
1516
m_autoTypeUi->openHelpButton->setEnabled(autoTypeEnabled && m_autoTypeUi->customSequenceButton->isChecked());
1518
m_autoTypeUi->assocView->setEnabled(autoTypeEnabled);
1519
m_autoTypeUi->assocAddButton->setEnabled(!m_history);
1520
m_autoTypeUi->assocRemoveButton->setEnabled(!m_history && validIndex);
1522
m_autoTypeUi->windowTitleLabel->setEnabled(autoTypeEnabled && validIndex);
1523
m_autoTypeUi->windowTitleCombo->setEnabled(autoTypeEnabled && validIndex);
1524
m_autoTypeUi->customWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex);
1525
m_autoTypeUi->windowSequenceEdit->setEnabled(autoTypeEnabled && validIndex
1526
&& m_autoTypeUi->customWindowSequenceButton->isChecked());
1529
void EditEntryWidget::insertAutoTypeAssoc()
1531
AutoTypeAssociations::Association assoc;
1532
m_autoTypeAssoc->add(assoc);
1533
QModelIndex newIndex = m_autoTypeAssocModel->index(m_autoTypeAssoc->size() - 1, 0);
1534
m_autoTypeUi->assocView->setCurrentIndex(newIndex);
1535
loadCurrentAssoc(newIndex);
1536
m_autoTypeUi->windowTitleCombo->setFocus();
1540
void EditEntryWidget::removeAutoTypeAssoc()
1542
QModelIndex currentIndex = m_autoTypeUi->assocView->currentIndex();
1544
if (currentIndex.isValid()) {
1545
m_autoTypeAssoc->remove(currentIndex.row());
1550
void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current)
1552
bool modified = isModified();
1553
if (current.isValid() && current.row() < m_autoTypeAssoc->size()) {
1554
AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row());
1555
m_autoTypeUi->windowTitleCombo->setEditText(assoc.window);
1556
if (assoc.sequence.isEmpty()) {
1557
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
1558
m_autoTypeUi->windowSequenceEdit->setText(m_entry->effectiveAutoTypeSequence());
1560
m_autoTypeUi->customWindowSequenceButton->setChecked(true);
1561
m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence);
1564
updateAutoTypeEnabled();
1566
clearCurrentAssoc();
1568
setModified(modified);
1571
void EditEntryWidget::clearCurrentAssoc()
1573
m_autoTypeUi->windowTitleCombo->setEditText("");
1575
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
1576
m_autoTypeUi->windowSequenceEdit->setText("");
1578
updateAutoTypeEnabled();
1581
void EditEntryWidget::applyCurrentAssoc()
1583
QModelIndex index = m_autoTypeUi->assocView->currentIndex();
1585
if (!index.isValid() || m_autoTypeAssoc->size() == 0 || m_history) {
1589
AutoTypeAssociations::Association assoc;
1590
assoc.window = m_autoTypeUi->windowTitleCombo->currentText();
1591
if (m_autoTypeUi->customWindowSequenceButton->isChecked()) {
1592
assoc.sequence = m_autoTypeUi->windowSequenceEdit->text();
1595
m_autoTypeAssoc->update(index.row(), assoc);
1598
void EditEntryWidget::showHistoryEntry()
1600
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1601
if (index.isValid()) {
1602
emitHistoryEntryActivated(index);
1606
void EditEntryWidget::restoreHistoryEntry()
1608
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1609
auto entry = m_historyModel->entryFromIndex(index);
1611
setForms(entry, true);
1616
void EditEntryWidget::deleteHistoryEntry()
1618
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
1619
if (m_historyModel->entryFromIndex(index)) {
1620
m_historyModel->deleteIndex(index);
1621
if (m_historyModel->rowCount() > 0) {
1622
m_historyUi->deleteAllButton->setEnabled(true);
1624
m_historyUi->deleteAllButton->setEnabled(false);
1630
void EditEntryWidget::deleteAllHistoryEntries()
1632
m_historyModel->deleteAll();
1633
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
1637
QMenu* EditEntryWidget::createPresetsMenu()
1639
auto* expirePresetsMenu = new QMenu(this);
1640
expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 12))->setData(QVariant::fromValue(TimeDelta::fromHours(12)));
1641
expirePresetsMenu->addAction(tr("%n hour(s)", nullptr, 24))->setData(QVariant::fromValue(TimeDelta::fromHours(24)));
1642
expirePresetsMenu->addSeparator();
1643
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromDays(7)));
1644
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromDays(14)));
1645
expirePresetsMenu->addAction(tr("%n week(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromDays(21)));
1646
expirePresetsMenu->addSeparator();
1647
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromMonths(1)));
1648
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromMonths(2)));
1649
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromMonths(3)));
1650
expirePresetsMenu->addAction(tr("%n month(s)", nullptr, 6))->setData(QVariant::fromValue(TimeDelta::fromMonths(6)));
1651
expirePresetsMenu->addSeparator();
1652
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 1))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
1653
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 2))->setData(QVariant::fromValue(TimeDelta::fromYears(2)));
1654
expirePresetsMenu->addAction(tr("%n year(s)", nullptr, 3))->setData(QVariant::fromValue(TimeDelta::fromYears(3)));
1655
return expirePresetsMenu;
1658
void EditEntryWidget::setupColorButton(bool foreground, const QColor& color)
1660
QWidget* button = m_advancedUi->fgColorButton;
1661
QCheckBox* checkBox = m_advancedUi->fgColorCheckBox;
1663
button = m_advancedUi->bgColorButton;
1664
checkBox = m_advancedUi->bgColorCheckBox;
1667
if (color.isValid()) {
1668
button->setStyleSheet(QString("background-color:%1").arg(color.name()));
1669
button->setProperty("color", color.name());
1670
checkBox->setChecked(true);
1672
button->setStyleSheet("");
1673
button->setProperty("color", QVariant());
1674
checkBox->setChecked(false);
1678
void EditEntryWidget::pickColor()
1680
bool isForeground = (sender() == m_advancedUi->fgColorButton);
1681
QColor oldColor = QColor(m_advancedUi->fgColorButton->property("color").toString());
1682
if (!isForeground) {
1683
oldColor = QColor(m_advancedUi->bgColorButton->property("color").toString());
1686
QColor newColor = QColorDialog::getColor(oldColor);
1687
if (newColor.isValid()) {
1688
setupColorButton(isForeground, newColor);