Skip to content

Commit

Permalink
Improve CSV export and import capability
Browse files Browse the repository at this point in the history
* Fixes #3541
* CSV export now includes TOTP settings, Entry Icon (database icon number only), Modified Time, and Created Time.
* CSV import properly understands time in ISO 8601 format and Unix Timestamp.
* CSV import will set the TOTP settings and entry icon based on the chosen column.
  • Loading branch information
droidmonkey committed Sep 1, 2020
1 parent f49f62d commit f947c96
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 65 deletions.
12 changes: 11 additions & 1 deletion src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,8 @@ void Entry::setTotp(QSharedPointer<Totp::Settings> settings)
m_data.totpSettings.reset();
} else {
m_data.totpSettings = std::move(settings);
auto text = Totp::writeSettings(m_data.totpSettings, title(), username());
auto text = Totp::writeSettings(
m_data.totpSettings, resolveMultiplePlaceholders(title()), resolveMultiplePlaceholders(username()));
if (m_data.totpSettings->format != Totp::StorageFormat::LEGACY) {
m_attributes->set(Totp::ATTRIBUTE_OTP, text, true);
} else {
Expand Down Expand Up @@ -491,6 +492,15 @@ QSharedPointer<Totp::Settings> Entry::totpSettings() const
return m_data.totpSettings;
}

QString Entry::totpSettingsString() const
{
if (m_data.totpSettings) {
return Totp::writeSettings(
m_data.totpSettings, resolveMultiplePlaceholders(title()), resolveMultiplePlaceholders(username()), true);
}
return {};
}

void Entry::setUuid(const QUuid& uuid)
{
Q_ASSERT(!uuid.isNull());
Expand Down
1 change: 1 addition & 0 deletions src/core/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class Entry : public QObject
QString notes() const;
QString attribute(const QString& key) const;
QString totp() const;
QString totpSettingsString() const;
QSharedPointer<Totp::Settings> totpSettings() const;
int size() const;

Expand Down
8 changes: 8 additions & 0 deletions src/format/CsvExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ QString CsvExporter::exportHeader()
addColumn(header, "Password");
addColumn(header, "URL");
addColumn(header, "Notes");
addColumn(header, "TOTP");
addColumn(header, "Icon");
addColumn(header, "Last Modified");
addColumn(header, "Created");
return header + QString("\n");
}

Expand All @@ -88,6 +92,10 @@ QString CsvExporter::exportGroup(const Group* group, QString groupPath)
addColumn(line, entry->password());
addColumn(line, entry->url());
addColumn(line, entry->notes());
addColumn(line, entry->totpSettingsString());
addColumn(line, QString::number(entry->iconNumber()));
addColumn(line, entry->timeInfo().lastModificationTime().toString(Qt::ISODate));
addColumn(line, entry->timeInfo().creationTime().toString(Qt::ISODate));

line.append("\n");
response.append(line);
Expand Down
43 changes: 33 additions & 10 deletions src/gui/csvImport/CsvImportWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "format/KeePass2Writer.h"
#include "gui/MessageBox.h"
#include "gui/MessageWidget.h"
#include "totp/totp.h"

// I wanted to make the CSV import GUI future-proof, so if one day you need a new field,
// all you have to do is add a field to m_columnHeader, and the GUI will follow:
Expand All @@ -39,7 +40,8 @@ CsvImportWidget::CsvImportWidget(QWidget* parent)
, m_comboModel(new QStringListModel(this))
, m_columnHeader(QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username")
<< QObject::tr("Password") << QObject::tr("URL") << QObject::tr("Notes")
<< QObject::tr("Last Modified") << QObject::tr("Created"))
<< QObject::tr("TOTP") << QObject::tr("Icon") << QObject::tr("Last Modified")
<< QObject::tr("Created"))
, m_fieldSeparatorList(QStringList() << ","
<< ";"
<< "-"
Expand All @@ -54,7 +56,7 @@ CsvImportWidget::CsvImportWidget(QWidget* parent)
m_ui->messageWidget->setHidden(true);
m_combos << m_ui->groupCombo << m_ui->titleCombo << m_ui->usernameCombo << m_ui->passwordCombo << m_ui->urlCombo
<< m_ui->notesCombo << m_ui->lastModifiedCombo << m_ui->createdCombo;
<< m_ui->notesCombo << m_ui->totpCombo << m_ui->iconCombo << m_ui->lastModifiedCombo << m_ui->createdCombo;
for (auto combo : m_combos) {
combo->setModel(m_comboModel);
Expand Down Expand Up @@ -206,17 +208,38 @@ void CsvImportWidget::writeDatabase()
entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString());
entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString());

TimeInfo timeInfo;
if (m_parserModel->data(m_parserModel->index(r, 6)).isValid()) {
qint64 lastModified = m_parserModel->data(m_parserModel->index(r, 6)).toString().toLongLong();
if (lastModified) {
timeInfo.setLastModificationTime(Clock::datetimeUtc(lastModified * 1000));
auto totp = Totp::parseSettings(m_parserModel->data(m_parserModel->index(r, 6)).toString());
entry->setTotp(totp);
}

bool ok;
int icon = m_parserModel->data(m_parserModel->index(r, 7)).toInt(&ok);
if (ok) {
entry->setIcon(icon);
}

TimeInfo timeInfo;
if (m_parserModel->data(m_parserModel->index(r, 8)).isValid()) {
auto datetime = m_parserModel->data(m_parserModel->index(r, 8)).toString();
if (datetime.contains(QRegularExpression("^\\d+$"))) {
timeInfo.setLastModificationTime(Clock::datetimeUtc(datetime.toLongLong() * 1000));
} else {
auto lastModified = QDateTime::fromString(datetime, Qt::ISODate);
if (lastModified.isValid()) {
timeInfo.setLastModificationTime(lastModified);
}
}
}
if (m_parserModel->data(m_parserModel->index(r, 7)).isValid()) {
qint64 created = m_parserModel->data(m_parserModel->index(r, 7)).toString().toLongLong();
if (created) {
timeInfo.setCreationTime(Clock::datetimeUtc(created * 1000));
if (m_parserModel->data(m_parserModel->index(r, 9)).isValid()) {
auto datetime = m_parserModel->data(m_parserModel->index(r, 9)).toString();
if (datetime.contains(QRegularExpression("^\\d+$"))) {
timeInfo.setCreationTime(Clock::datetimeUtc(datetime.toLongLong() * 1000));
} else {
auto created = QDateTime::fromString(datetime, Qt::ISODate);
if (created.isValid()) {
timeInfo.setCreationTime(created);
}
}
}
entry->setTimeInfo(timeInfo);
Expand Down
Loading

0 comments on commit f947c96

Please sign in to comment.