Add TOTP support

This commit is contained in:
Weslly 2017-04-13 07:05:36 -03:00
parent 7040bef27e
commit bf57a28654
22 changed files with 1120 additions and 2 deletions

View file

@ -42,6 +42,8 @@
#include "gui/ChangeMasterKeyWidget.h"
#include "gui/Clipboard.h"
#include "gui/CloneDialog.h"
#include "gui/SetupTotpDialog.h"
#include "gui/TotpDialog.h"
#include "gui/DatabaseOpenWidget.h"
#include "gui/DatabaseSettingsWidget.h"
#include "gui/KeePass1OpenWidget.h"
@ -333,6 +335,48 @@ void DatabaseWidget::cloneEntry()
return;
}
void DatabaseWidget::showTotp()
{
Entry* currentEntry = m_entryView->currentEntry();
if (!currentEntry) {
Q_ASSERT(false);
return;
}
TotpDialog* totpDialog = new TotpDialog(this, currentEntry);
totpDialog->open();
}
void DatabaseWidget::copyTotp()
{
Entry* currentEntry = m_entryView->currentEntry();
if (!currentEntry) {
Q_ASSERT(false);
return;
}
setClipboardTextAndMinimize(currentEntry->totp());
}
void DatabaseWidget::setupTotp()
{
Entry* currentEntry = m_entryView->currentEntry();
if (!currentEntry) {
Q_ASSERT(false);
return;
}
SetupTotpDialog* setupTotpDialog = new SetupTotpDialog(this, currentEntry);
if (currentEntry->hasTotp()) {
setupTotpDialog->setSeed(currentEntry->totpSeed());
setupTotpDialog->setStep(currentEntry->totpStep());
setupTotpDialog->setDigits(currentEntry->totpDigits());
}
setupTotpDialog->open();
}
void DatabaseWidget::deleteEntries()
{
const QModelIndexList selected = m_entryView->selectionModel()->selectedRows();
@ -1225,6 +1269,17 @@ bool DatabaseWidget::currentEntryHasUrl()
return !currentEntry->resolveMultiplePlaceholders(currentEntry->url()).isEmpty();
}
bool DatabaseWidget::currentEntryHasTotp()
{
Entry* currentEntry = m_entryView->currentEntry();
if (!currentEntry) {
Q_ASSERT(false);
return false;
}
return currentEntry->hasTotp();
}
bool DatabaseWidget::currentEntryHasNotes()
{
Entry* currentEntry = m_entryView->currentEntry();

View file

@ -96,6 +96,7 @@ public:
bool currentEntryHasPassword();
bool currentEntryHasUrl();
bool currentEntryHasNotes();
bool currentEntryHasTotp();
GroupView* groupView();
EntryView* entryView();
void showUnlockDialog();
@ -133,6 +134,9 @@ public slots:
void copyURL();
void copyNotes();
void copyAttribute(QAction* action);
void showTotp();
void copyTotp();
void setupTotp();
void performAutoType();
void openUrl();
void openUrlForEntry(Entry* entry);

View file

@ -167,6 +167,8 @@ MainWindow::MainWindow()
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T);
m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V);
@ -275,6 +277,13 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()),
SLOT(deleteEntries()));
m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()),
SLOT(showTotp()));
m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()),
SLOT(setupTotp()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyTotp, SIGNAL(triggered()),
SLOT(copyTotp()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()),
SLOT(copyTitle()));
m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()),
@ -428,8 +437,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes());
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
m_ui->menuEntryTotp->setEnabled(true);
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
m_ui->actionGroupNew->setEnabled(groupSelected);
m_ui->actionGroupEdit->setEnabled(groupSelected);
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
@ -463,6 +476,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
@ -495,6 +509,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionEntryCopyURL->setEnabled(false);
m_ui->actionEntryCopyNotes->setEnabled(false);
m_ui->menuEntryCopyAttribute->setEnabled(false);
m_ui->menuEntryTotp->setEnabled(false);
m_ui->actionChangeMasterKey->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);

View file

@ -220,9 +220,21 @@
<addaction name="actionEntryCopyNotes"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuEntryTotp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Timed one-time password</string>
</property>
<addaction name="actionEntryCopyTotp"/>
<addaction name="actionEntryTotp"/>
<addaction name="actionEntrySetupTotp"/>
</widget>
<addaction name="actionEntryCopyUsername"/>
<addaction name="actionEntryCopyPassword"/>
<addaction name="menuEntryCopyAttribute"/>
<addaction name="menuEntryTotp"/>
<addaction name="actionEntryAutoType"/>
<addaction name="actionEntryOpenUrl"/>
<addaction name="actionEntryEdit"/>
@ -523,6 +535,21 @@
<string>Re&amp;pair database</string>
</property>
</action>
<action name="actionEntryTotp">
<property name="text">
<string>Show TOTP</string>
</property>
</action>
<action name="actionEntrySetupTotp">
<property name="text">
<string>Setup TOTP</string>
</property>
</action>
<action name="actionEntryCopyTotp">
<property name="text">
<string>Copy &amp;TOTP</string>
</property>
</action>
<action name="actionGroupEmptyRecycleBin">
<property name="text">
<string>Empty recycle bin</string>

101
src/gui/SetupTotpDialog.cpp Normal file
View file

@ -0,0 +1,101 @@
/*
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SetupTotpDialog.h"
#include "ui_SetupTotpDialog.h"
#include "totp/totp.h"
SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry)
: QDialog(parent)
, m_ui(new Ui::SetupTotpDialog())
{
m_entry = entry;
m_parent = parent;
m_ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
this->setFixedSize(this->sizeHint());
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp()));
connect(m_ui->customSettingsCheckBox, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
}
void SetupTotpDialog::setupTotp()
{
quint8 digits;
if (m_ui->radio8Digits->isChecked()) {
digits = 8;
} else {
digits = 6;
}
quint8 step = m_ui->stepSpinBox->value();
QString seed = m_ui->seedEdit->text();
m_entry->setTotp(seed, step, digits);
emit m_parent->entrySelectionChanged();
close();
}
void SetupTotpDialog::toggleCustom(bool status)
{
m_ui->digitsLabel->setEnabled(status);
m_ui->radio6Digits->setEnabled(status);
m_ui->radio8Digits->setEnabled(status);
m_ui->stepLabel->setEnabled(status);
m_ui->stepSpinBox->setEnabled(status);
}
void SetupTotpDialog::setSeed(QString value)
{
m_ui->seedEdit->setText(value);
}
void SetupTotpDialog::setStep(quint8 step)
{
m_ui->stepSpinBox->setValue(step);
if (step != QTotp::defaultStep) {
m_ui->customSettingsCheckBox->setChecked(true);
}
}
void SetupTotpDialog::setDigits(quint8 digits)
{
if (digits == 8) {
m_ui->radio8Digits->setChecked(true);
m_ui->radio6Digits->setChecked(false);
} else {
m_ui->radio6Digits->setChecked(true);
m_ui->radio8Digits->setChecked(false);
}
if (digits != QTotp::defaultDigits) {
m_ui->customSettingsCheckBox->setChecked(true);
}
}
SetupTotpDialog::~SetupTotpDialog()
{
}

54
src/gui/SetupTotpDialog.h Normal file
View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_SETUPTOTPDIALOG_H
#define KEEPASSX_SETUPTOTPDIALOG_H
#include <QDialog>
#include <QScopedPointer>
#include "core/Entry.h"
#include "core/Database.h"
#include "gui/DatabaseWidget.h"
namespace Ui {
class SetupTotpDialog;
}
class SetupTotpDialog : public QDialog
{
Q_OBJECT
public:
explicit SetupTotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
~SetupTotpDialog();
void setSeed(QString value);
void setStep(quint8 step);
void setDigits(quint8 digits);
private Q_SLOTS:
void toggleCustom(bool status);
void setupTotp();
private:
QScopedPointer<Ui::SetupTotpDialog> m_ui;
protected:
Entry* m_entry;
DatabaseWidget* m_parent;
};
#endif // KEEPASSX_SETUPTOTPDIALOG_H

137
src/gui/SetupTotpDialog.ui Normal file
View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SetupTotpDialog</class>
<widget class="QDialog" name="SetupTotpDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>282</width>
<height>257</height>
</rect>
</property>
<property name="windowTitle">
<string>Setup TOTP</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Key:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="seedEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="customSettingsCheckBox">
<property name="text">
<string>Use custom settings</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Note: Change these settings only if you know what you are doing.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="rowWrapPolicy">
<enum>QFormLayout::DontWrapRows</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
<item row="1" column="0">
<widget class="QLabel" name="stepLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Time step:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QRadioButton" name="radio8Digits">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>8 digits</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="radio6Digits">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>6 digits</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="digitsLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Code size:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="stepSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string> sec</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>60</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

100
src/gui/TotpDialog.cpp Normal file
View file

@ -0,0 +1,100 @@
/*
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TotpDialog.h"
#include "ui_TotpDialog.h"
#include "core/Config.h"
#include "core/Entry.h"
#include "gui/DatabaseWidget.h"
#include "gui/Clipboard.h"
#include <QTimer>
#include <QDateTime>
#include <QPushButton>
TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry)
: QDialog(parent)
, m_ui(new Ui::TotpDialog())
{
m_entry = entry;
m_parent = parent;
m_step = m_entry->totpStep();
m_ui->setupUi(this);
uCounter = resetCounter();
updateProgressBar();
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
connect(timer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
timer->start(m_step * 10);
updateTotp();
setAttribute(Qt::WA_DeleteOnClose);
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy"));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
}
void TotpDialog::copyToClipboard()
{
clipboard()->setText(m_entry->totp());
if (config()->get("MinimizeOnCopy").toBool()) {
m_parent->window()->showMinimized();
}
}
void TotpDialog::updateProgressBar()
{
if (uCounter < 100) {
m_ui->progressBar->setValue(100 - uCounter);
m_ui->progressBar->update();
uCounter++;
} else {
updateTotp();
uCounter = resetCounter();
}
}
void TotpDialog::updateSeconds()
{
uint epoch = QDateTime::currentDateTime().toTime_t() - 1;
m_ui->timerLabel->setText(tr("Expires in") + " <b>" + QString::number(m_step - (epoch % m_step)) + "</b> " + tr("seconds"));
}
void TotpDialog::updateTotp()
{
m_ui->totpLabel->setText(m_entry->totp());
}
double TotpDialog::resetCounter()
{
uint epoch = QDateTime::currentDateTime().toTime_t();
double counter = qRound(static_cast<double>(epoch % m_step) / m_step * 100);
return counter;
}
TotpDialog::~TotpDialog()
{
}

56
src/gui/TotpDialog.h Normal file
View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TOTPDIALOG_H
#define KEEPASSX_TOTPDIALOG_H
#include <QDialog>
#include <QScopedPointer>
#include "core/Entry.h"
#include "core/Database.h"
#include "gui/DatabaseWidget.h"
namespace Ui {
class TotpDialog;
}
class TotpDialog : public QDialog
{
Q_OBJECT
public:
explicit TotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
~TotpDialog();
private:
double uCounter;
quint8 m_step;
QScopedPointer<Ui::TotpDialog> m_ui;
private Q_SLOTS:
void updateTotp();
void updateProgressBar();
void updateSeconds();
void copyToClipboard();
double resetCounter();
protected:
Entry* m_entry;
DatabaseWidget* m_parent;
};
#endif // KEEPASSX_TOTPDIALOG_H

60
src/gui/TotpDialog.ui Normal file
View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TotpDialog</class>
<widget class="QWidget" name="TotpDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>264</width>
<height>194</height>
</rect>
</property>
<property name="windowTitle">
<string>Timed Password</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="totpLabel">
<property name="font">
<font>
<pointsize>53</pointsize>
</font>
</property>
<property name="text">
<string>000000</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timerLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>