Refactor and update input validation

This change fixes input validation for the following cases:

 - Check that entered account names are still available
 - Working validation for time steps (input mask was completely broken)
 - Allow longer tokens: liboath is no longer used, Keysmith can handle it

Additionally the QML code is refactored significantly:

 - Extracted the main accounts overview page
 - Extracted the add an account page
 - Completed the internal renaming of "Oath" to "Keysmith" for QML types
master
Johan Ouwerkerk 2020-03-25 20:19:26 +01:00
parent 2a9c80fff5
commit 768ccdba97
9 changed files with 139 additions and 184 deletions

View File

@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
import QtQuick 2.1
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
Kirigami.ScrollablePage {
id: root
/*
* Explicitly opt-out of scroll-to-refresh/drag-to-refresh behaviour
* Underlying model implementations don't offer the hooks for that.
*/
supportsRefreshing: false
title: i18nc("@title:window", "Accounts")
signal accountWanted
property bool addActionEnabled : true
property Models.AccountListModel accounts: Keysmith.accountListModel()
Component {
id: mainListDelegate
AccountEntryView {
account: model.account
}
}
ListView {
id: mainList
model: accounts
delegate: Kirigami.DelegateRecycler {
width: parent ? parent.width : implicitWidth
sourceComponent: mainListDelegate
}
}
actions.main: Kirigami.Action {
id: addAction
text: i18n("Add")
iconName: "list-add"
visible: addActionEnabled
onTriggered: {
root.accountWanted();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
import QtQuick 2.1
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
import org.kde.kirigami 2.8 as Kirigami
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
import Keysmith.Validators 1.0 as Validators
Kirigami.Page {
id: root
title: i18nc("@title:window", "Add new account")
signal dismissed
property Models.AccountListModel accounts: Keysmith.accountListModel()
ColumnLayout {
anchors {
horizontalCenter: parent.horizontalCenter
}
Kirigami.FormLayout {
Controls.TextField {
id: accountName
Kirigami.FormData.label: i18nc("@label:textbox", "Account Name:")
validator: Validators.AccountNameValidator {
accounts: root.accounts
}
}
}
TokenDetailsForm {
id: tokenDetails
}
}
actions.main: Kirigami.Action {
text: i18n("Add")
iconName: "answer-correct"
onTriggered: {
if (tokenDetails.isTotp) {
accounts.addTotp(accountName.text, tokenDetails.secret, parseInt(tokenDetails.timeStep), tokenDetails.tokenLength);
}
if (tokenDetails.isHotp) {
accounts.addHotp(accountName.text, tokenDetails.secret, parseInt(tokenDetails.counter), tokenDetails.tokenLength);
}
root.dismissed();
}
}
}

View File

@ -1,25 +1,10 @@
/*
* Copyright 2019 Johan Ouwerkerk <jm.ouwerkerk@gmail.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 3 of
* the License or any later version accepted by the membership of
* KDE e.V. (or its successor approved by the membership of KDE
* e.V.), which shall act as a proxy defined in Section 14 of
* 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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2019 Bhushan Shah <bshah@kde.org>
* SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
import Oath.Validators 1.0 as Validators
import Keysmith.Validators 1.0 as Validators
import QtQuick 2.1
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
@ -63,7 +48,9 @@ Kirigami.FormLayout {
Kirigami.FormData.label: i18nc("@label:textbox", "Timer:")
enabled: totpRadio.checked
text: "30"
inputMask: "0009"
validator: IntValidator {
bottom: 1
}
inputMethodHints: Qt.ImhDigitsOnly
}
Controls.TextField {
@ -77,8 +64,9 @@ Kirigami.FormLayout {
inputMethodHints: Qt.ImhDigitsOnly
}
/*
* The liboath API is documented to support tokens which are
* 6, 7 or 8 characters long only.
* OATH tokens are derived from a 32bit value, base-10 encoded.
* Meaning tokens should not be longer than 10 digits max.
* In addition tokens must be 6 digits long at minimum.
*
* Make a virtue of it by offering a spinner for better UX
*/
@ -86,7 +74,7 @@ Kirigami.FormLayout {
id: pinLengthField
Kirigami.FormData.label: i18nc("@label:spinbox", "Token length:")
from: 6
to: 8
to: 10
value: 6
}
}

View File

@ -1,27 +1,11 @@
/*
* Copyright 2019 Bhushan Shah <bshah@kde.org>
*
* 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 3 of
* the License or any later version accepted by the membership of
* KDE e.V. (or its successor approved by the membership of KDE
* e.V.), which shall act as a proxy defined in Section 14 of
* 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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2019 Bhushan Shah <bshah@kde.org>
* SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
import Keysmith.Application 1.0
import Keysmith.Models 1.0 as Models
import Oath.Validators 1.0 as Validators
import QtQuick 2.1
import QtQuick.Layouts 1.2
@ -31,90 +15,30 @@ import org.kde.kirigami 2.4 as Kirigami
Kirigami.ApplicationWindow {
id: root
pageStack.initialPage: mainPageComponent
pageStack.initialPage: accountsOverviewPage
property bool addActionEnabled: true
property Models.AccountListModel accounts: Keysmith.accountListModel()
Kirigami.Action {
id: addAction
text: i18n("Add")
iconName: "list-add"
visible: addActionEnabled
onTriggered: {
pageStack.push(addPageComponent);
addActionEnabled = false
}
}
Component {
id: mainListDelegate
AccountEntryView {
account: model.account
}
}
Component {
id: mainPageComponent
Kirigami.ScrollablePage {
title: i18nc("@title:window", "Accounts")
actions.main: addAction
/*
* Explicitly opt-out of scroll-to-refresh/drag-to-refresh behaviour
* Underlying model implementations don't offer the hooks for that.
*/
supportsRefreshing: false
ListView {
id: mainList
model: accounts
delegate: Kirigami.DelegateRecycler {
width: parent ? parent.width : implicitWidth
sourceComponent: mainListDelegate
}
id: accountsOverviewPage
AccountsOverview {
accounts: root.accounts
addActionEnabled: root.addActionEnabled
onAccountWanted: {
pageStack.push(addPageComponent);
root.addActionEnabled = false;
}
}
}
Component {
id: addPageComponent
Kirigami.Page {
title: i18nc("@title:window", "Add new account")
actions.main: Kirigami.Action {
text: i18n("Add")
iconName: "answer-correct"
onTriggered: {
if (tokenDetails.isTotp) {
accounts.addTotp(accountName.text, tokenDetails.secret, parseInt(tokenDetails.timeStep), tokenDetails.tokenLength);
}
if (tokenDetails.isHotp) {
accounts.addHotp(accountName.text, tokenDetails.secret, parseInt(tokenDetails.counter), tokenDetails.tokenLength);
}
pageStack.pop();
addActionEnabled = true;
}
}
ColumnLayout {
id: addFormLayout
anchors {
horizontalCenter: parent.horizontalCenter
}
Kirigami.FormLayout {
Controls.TextField {
id: accountName
Kirigami.FormData.label: i18nc("@label:textbox", "Account Name:")
validator: Validators.AccountNameValidator {
id: nameValidator
}
}
}
TokenDetailsForm {
id: tokenDetails
}
AddAccount {
accounts: root.accounts
onDismissed: {
pageStack.pop();
addActionEnabled = true;
}
}
}

View File

@ -29,7 +29,8 @@
#include "app/keysmith.h"
#include "model/accounts.h"
#include "../validators/qmlsupport.h"
#include "validators/countervalidator.h"
#include "validators/secretvalidator.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
{
@ -45,9 +46,11 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QQmlApplicationEngine engine;
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
validators::registerValidatorTypes();
qmlRegisterUncreatableType<model::SimpleAccountListModel>("Keysmith.Models", 1, 0, "AccountListModel", "Use the Keysmith singleton to obtain an AccountListModel");
qmlRegisterUncreatableType<model::AccountView>("Keysmith.Models", 1, 0, "Account", "Use an AccountListModel from the Keysmith singleton to obtain an Account");
qmlRegisterType<model::AccountNameValidator>("Keysmith.Validators", 1, 0, "AccountNameValidator");
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 *
{
Q_UNUSED(qml);

View File

@ -3,5 +3,7 @@
<file alias="main.qml">contents/ui/main.qml</file>
<file alias="TokenDetailsForm.qml">contents/ui/TokenDetailsForm.qml</file>
<file alias="AccountEntryView.qml">contents/ui/AccountEntryView.qml</file>
<file alias="AccountsOverview.qml">contents/ui/AccountsOverview.qml</file>
<file alias="AddAccount.qml">contents/ui/AddAccount.qml</file>
</qresource>
</RCC>

View File

@ -2,12 +2,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2019-2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
# This directory contains (custom) validators to support form field validation in the QML UI
# For the QML bindings see: qmlsupport.cpp
#
set(validator_SRCS
qmlsupport.cpp
countervalidator.cpp
namevalidator.cpp
secretvalidator.cpp
@ -15,4 +11,4 @@ set(validator_SRCS
)
add_library(validator_lib STATIC ${validator_SRCS})
target_link_libraries(validator_lib Qt5::Core Qt5::Qml Qt5::Gui base32_lib)
target_link_libraries(validator_lib Qt5::Core Qt5::Gui base32_lib)

View File

@ -1,35 +0,0 @@
/*****************************************************************************
* Copyright: 2019 Johan Ouwerkerk <jm.ouwerkerk@gmail.com> *
* *
* This project 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 3 of the License, or *
* (at your option) any later version. *
* *
* This project 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 "qmlsupport.h"
#include "countervalidator.h"
#include "namevalidator.h"
#include "secretvalidator.h"
#include <QtQml>
namespace validators
{
void registerValidatorTypes(void)
{
qmlRegisterType<validators::NameValidator>("Oath.Validators", 1, 0, "AccountNameValidator");
qmlRegisterType<validators::UnsignedLongValidator>("Oath.Validators", 1, 0, "HOTPCounterValidator");
qmlRegisterType<validators::Base32Validator>("Oath.Validators", 1, 0, "Base32SecretValidator");
}
}

View File

@ -1,27 +0,0 @@
/*****************************************************************************
* Copyright: 2019 Johan Ouwerkerk <jm.ouwerkerk@gmail.com> *
* *
* This project 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 3 of the License, or *
* (at your option) any later version. *
* *
* This project 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 VALIDATOR_QML_SUPPORT_H
#define VALIDATOR_QML_SUPPORT_H
namespace validators
{
void registerValidatorTypes(void);
}
#endif