feat: Support configuring the issuer when adding a new account in Keysmith.

With this change, issuers are now fully supported.

Resolves: #13
master
Johan Ouwerkerk 2020-06-10 17:40:37 +02:00
parent 0d40912360
commit d358d8abcc
7 changed files with 151 additions and 5 deletions

View File

@ -1,11 +1,12 @@
#
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2019 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
# SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(validator_lib_test_SRCS
base32-validator.cpp
name-validator.cpp
issuer-validator.cpp
unsigned-long-validator.cpp
unsigned-long-parsing.cpp
)

View File

@ -0,0 +1,45 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "test-util.h"
#include "validators/issuervalidator.h"
using namespace validators::test;
static void define_valid_table(void)
{
define_test_case(QLatin1String(""), QLatin1String(""), QValidator::Acceptable);
define_test_case(QLatin1String("Issuer"), QLatin1String("Issuer"), QValidator::Acceptable);
define_test_case(QLatin1String("test issuer"), QLatin1String("test issuer"), QValidator::Acceptable);
}
static void define_invalid_table(void)
{
define_test_case(QLatin1String("test\tissuer"), QLatin1String("test issuer"), QValidator::Invalid);
define_test_case(QLatin1String("\r \n\ttest\r\t \nissuer \r\t\n"), QLatin1String("test issuer"), QValidator::Invalid);
define_test_case(QLatin1String("test "), QLatin1String("test "), QValidator::Invalid);
define_test_case(QLatin1String("test:issuer"), QLatin1String("testissuer"), QValidator::Invalid);
}
static void define_empty_table(void)
{
define_test_case(QLatin1String(" "), QLatin1String(""), QValidator::Invalid);
define_test_case(QLatin1String("\t"), QLatin1String(""), QValidator::Invalid);
define_test_case(QLatin1String("\r\n"), QLatin1String(""), QValidator::Invalid);
}
static void define_data(void)
{
define_empty_table();
define_valid_table();
define_invalid_table();
}
DEFINE_VALIDATOR_TEST(IssuerValidatorTest, validators::IssuerValidator, define_data);
QTEST_APPLESS_MAIN(IssuerValidatorTest)
#include "issuer-validator.moc"

View File

@ -17,7 +17,7 @@ Kirigami.Page {
title: i18nc("@title:window", "Add new account")
signal dismissed
property Models.AccountListModel accounts: Keysmith.accountListModel()
property bool acceptable: accountName.acceptableInput && tokenDetails.acceptable
property bool acceptable: accountName.acceptableInput && issuerName.acceptableInput && tokenDetails.acceptable
ColumnLayout {
anchors {
@ -28,8 +28,29 @@ Kirigami.Page {
id: accountName
Kirigami.FormData.label: i18nc("@label:textbox", "Account Name:")
validator: Validators.AccountNameValidator {
id: accountNameValidator
accounts: root.accounts
issuer: ""
issuer: issuerName.text
}
}
Controls.TextField {
id: issuerName
Kirigami.FormData.label: i18nc("@label:textbox", "Account Issuer:")
validator: Validators.AccountIssuerValidator {}
/*
* When the issuer changes, the account name should be revalidated as well. It may have become eligible or in-eligible depending on
* whether or not other accounts with the same name for the same new issuer value already exist.
*
* Unfortunately because the property binding only affects the validator, there seems to be nothing to explicitly trigger revalidation
* on the text field. Work around is to force revalidation to happen by "editing" the value in the text field directly.
*/
onTextChanged: {
/*
* This signal handler may run before property bindings have been fully (re-)evaluated.
* First update the account name validator to the correct new issuer value before triggering revalidation.
*/
accountNameValidator.issuer = issuerName.text;
accountName.insert(accountName.text.length, "");
}
}
}
@ -44,10 +65,10 @@ Kirigami.Page {
enabled: acceptable
onTriggered: {
if (tokenDetails.isTotp) {
accounts.addTotp(accountName.text, "", tokenDetails.secret, parseInt(tokenDetails.timeStep), tokenDetails.tokenLength);
accounts.addTotp(accountName.text, issuerName.text, tokenDetails.secret, parseInt(tokenDetails.timeStep), tokenDetails.tokenLength);
}
if (tokenDetails.isHotp) {
accounts.addHotp(accountName.text, "", tokenDetails.secret, parseInt(tokenDetails.counter), tokenDetails.tokenLength);
accounts.addHotp(accountName.text, issuerName.text, tokenDetails.secret, parseInt(tokenDetails.counter), tokenDetails.tokenLength);
}
root.dismissed();
}

View File

@ -15,6 +15,7 @@
#include "app/keysmith.h"
#include "model/accounts.h"
#include "validators/countervalidator.h"
#include "validators/issuervalidator.h"
#include "validators/secretvalidator.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
@ -36,6 +37,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterUncreatableType<model::AccountView>("Keysmith.Models", 1, 0, "Account", "Use an AccountListModel from the Keysmith singleton to obtain an Account");
qmlRegisterType<model::SortedAccountsListModel>("Keysmith.Models", 1, 0, "SortedAccountListModel");
qmlRegisterType<model::AccountNameValidator>("Keysmith.Validators", 1, 0, "AccountNameValidator");
qmlRegisterType<validators::IssuerValidator>("Keysmith.Validators", 1, 0, "AccountIssuerValidator");
qmlRegisterType<validators::Base32Validator>("Keysmith.Validators", 1, 0, "Base32SecretValidator");
qmlRegisterType<validators::UnsignedLongValidator>("Keysmith.Validators", 1, 0, "HOTPCounterValidator");
qmlRegisterSingletonType<app::Keysmith>("Keysmith.Application", 1, 0, "Keysmith", [](QQmlEngine *qml, QJSEngine *js) -> QObject *

View File

@ -6,6 +6,7 @@
set(validator_SRCS
countervalidator.cpp
namevalidator.cpp
issuervalidator.cpp
secretvalidator.cpp
util.cpp
)

View File

@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "issuervalidator.h"
#include <QRegularExpression>
#include <QString>
static const QRegularExpression& match_pattern(void)
{
/*
* Pattern to check that issuer names:
*
* - do not contain colons
* - start and end with a character which is not whitespace
* - do not contain any other whitespace besides spaces
* - have at most one space between non-whitespace characters
*/
static const QRegularExpression re(QLatin1String("^[^\\s:]+( [^\\s:]+)*$"));
re.optimize();
return re;
}
namespace validators
{
IssuerValidator::IssuerValidator(QObject *parent):
QValidator(parent),
m_pattern(match_pattern())
{
}
void IssuerValidator::fixup(QString &input) const
{
QString fixed = input.simplified().remove(QLatin1Char(':'));
// make sure the user can type in at least one space
if (input.endsWith(QLatin1Char(' ')) && fixed.size() > 0) {
fixed += QLatin1Char(' ');
}
input = fixed;
}
QValidator::State IssuerValidator::validate(QString &input, int &cursor) const
{
return input.isEmpty() ? QValidator::Acceptable : m_pattern.validate(input, cursor);
}
}

View File

@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#ifndef ISSUER_VALIDATOR_H
#define ISSUER_VALIDATOR_H
#include <QRegularExpressionValidator>
#include <QValidator>
namespace validators
{
class IssuerValidator: public QValidator
{
Q_OBJECT
public:
explicit IssuerValidator(QObject *parent = nullptr);
QValidator::State validate(QString &input, int &pos) const override;
void fixup(QString &input) const override;
private:
const QRegularExpressionValidator m_pattern;
};
}
#endif