Commit b9963133 authored by Rodney's avatar Rodney
Browse files

Let AccountModel take the settings object as argument.

To enable testing against false data, we need to enable the use of a
temporary settings file, by passing the QSettings object into the ctor.
parent 1d3daf1f
......@@ -2,6 +2,9 @@ stages:
- coverage
- buildclick
variables:
GIT_SUBMODULE_STRATEGY: 'recursive'
before_script:
- apt-get update
- apt-get install -y ca-certificates apt-transport-https wget gnupg2 gpgv
......@@ -10,14 +13,15 @@ before_script:
- . /etc/os-release
- echo "deb https://repo.ubports.com/ ${VERSION_CODENAME} main" | tee /etc/apt/sources.list.d/ubports.list
- apt-get update
- apt-get install -y cmake cmake-extras libjpeg-dev libssl-dev libv4l-dev libzbar-dev click click-dev suru-icon-theme
- apt-get install -y qtbase5-dev qtbase5-dev-tools qtchooser gcovr lcov qtdeclarative5-dev qtdeclarative5-dev-tools qtquickcontrols2-5-dev libqt5test5 intltool git doxygen libdbus-1-dev
- apt-get install -y cmake cmake-extras click click-dev suru-icon-theme
- apt-get install -y qtbase5-dev qtbase5-dev-tools qtchooser gcovr lcov qtdeclarative5-dev qtdeclarative5-dev-tools qtmultimedia5-dev qtquickcontrols2-5-dev libqt5test5 intltool git doxygen libdbus-1-dev libqt5svg5-dev libssl-dev
coverage:bionic:amd64:
stage: coverage
image: ubuntu:bionic
script:
- export QT_SELECT="qt5"
- export QT_QPA_PLATFORM=minimal
- mkdir -p build
- cd build
- cmake -DCMAKE_BUILD_TYPE=coverage ..
......@@ -34,11 +38,12 @@ buildclick:xenial:armhf:
image: clickable/ci-16.04-armhf
before_script:
- apt-get update
- apt-get install -y libglib2.0-dev libgdk-pixbuf2.0-dev libcairo2-dev libzbar-dev:armhf libmagick++-dev:armhf libjpeg-dev:armhf libv4l-dev:armhf libssl-dev:armhf
- apt-get install -y libglib2.0-dev:armhf libpulse-dev:armhf
- apt-get install -y cmake cmake-extras doxygen intltool xvfb
- apt-get install -y qtbase5-dev:armhf qtbase5-dev-tools qtchooser qtdeclarative5-dev:armhf qtdeclarative5-dev-tools libqt5test5:armhf libdbus-1-dev:armhf
- apt-get install -y qtbase5-dev:armhf qtbase5-dev-tools qtchooser qtdeclarative5-dev:armhf qtdeclarative5-dev-tools qtmultimedia5-dev:armhf libqt5test5:armhf libdbus-1-dev:armhf libqt5svg5-dev:armhf libssl-dev:armhf
script:
- export QT_SELECT="qt5"
- export QT_QPA_PLATFORM=minimal
- git clone https://gitlab.com/dobey/ergo.git
- cd ergo
- mkdir -p build
......@@ -62,11 +67,12 @@ buildclick:xenial:arm64:
image: clickable/ci-16.04-arm64
before_script:
- apt-get update
- apt-get install -y libglib2.0-dev libgdk-pixbuf2.0-dev libcairo2-dev libzbar-dev:arm64 libmagick++-dev:arm64 libjpeg-dev:arm64 libv4l-dev:arm64 libssl-dev:arm64
- apt-get install -y libglib2.0-dev:arm64 libpulse-dev:arm64
- apt-get install -y cmake cmake-extras doxygen intltool xvfb
- apt-get install -y qtbase5-dev:arm64 qtbase5-dev-tools qtchooser qtdeclarative5-dev:arm64 qtdeclarative5-dev-tools libqt5test5:arm64 libdbus-1-dev:arm64
- apt-get install -y qtbase5-dev:arm64 qtbase5-dev-tools qtchooser qtdeclarative5-dev:arm64 qtdeclarative5-dev-tools qtmultimedia5-dev:arm64 libqt5test5:arm64 libdbus-1-dev:arm64 libqt5svg5-dev:arm64 libssl-dev:arm64
script:
- export QT_SELECT="qt5"
- export QT_QPA_PLATFORM=minimal
- git clone https://gitlab.com/dobey/ergo.git
- cd ergo
- mkdir -p build
......@@ -90,6 +96,7 @@ buildclick:xenial:amd64:
image: ubuntu:xenial
script:
- export QT_SELECT="qt5"
- export QT_QPA_PLATFORM=minimal
- git clone https://gitlab.com/dobey/ergo.git
- cd ergo
- mkdir -p build
......
[submodule "qzxing"]
path = qzxing
url = https://github.com/ftylitak/qzxing.git
//------------------------------------------------------------------------
// Copyright 2008-2009 (c) Jeff Brown <spadix@users.sourceforge.net>
//
// This file is part of the ZBar Bar Code Reader.
//
// The ZBar Bar Code Reader is free software; you can redistribute it
// and/or modify it under the terms of the GNU Lesser Public License as
// published by the Free Software Foundation; either version 2.1 of
// the License, or (at your option) any later version.
//
// The ZBar Bar Code Reader 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 Lesser Public License for more details.
//
// You should have received a copy of the GNU Lesser Public License
// along with the ZBar Bar Code Reader; if not, write to the Free
// Software Foundation, Inc., 51 Franklin St, Fifth Floor,
// Boston, MA 02110-1301 USA
//
// http://sourceforge.net/projects/zbar
//------------------------------------------------------------------------
#ifndef _QZBARIMAGE_H_
#define _QZBARIMAGE_H_
/// @file
/// QImage to Image type conversion wrapper
#include <qimage.h>
#include <zbar.h>
namespace zbar {
/// wrap a QImage and convert into a format suitable for scanning.
class QZBarImage
: public Image
{
public:
/// construct a zbar library image based on an existing QImage.
QZBarImage (const QImage &qimg)
: qimg(qimg)
{
QImage::Format fmt = qimg.format();
if(fmt != QImage::Format_RGB32 &&
fmt != QImage::Format_ARGB32 &&
fmt != QImage::Format_ARGB32_Premultiplied)
throw FormatError();
unsigned bpl = qimg.bytesPerLine();
unsigned width = bpl / 4;
unsigned height = qimg.height();
set_size(width, height);
set_format('B' | ('G' << 8) | ('R' << 16) | ('4' << 24));
unsigned long datalen = qimg.byteCount();
set_data(qimg.bits(), datalen);
if((width * 4 != bpl) ||
(width * height * 4 > datalen))
throw FormatError();
}
private:
QImage qimg;
};
};
#endif
......@@ -53,19 +53,8 @@ find_package(PkgConfig REQUIRED)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
pkg_check_modules(AUTH_STATIC REQUIRED
zbar
)
pkg_check_modules(AUTH_DYNAMIC REQUIRED
dbus-1
libcrypto
libjpeg
libv4l2
)
include_directories(SYSTEM
${AUTH_STATIC_INCLUDE_DIRS}
${AUTH_DYNAMIC_INCLUDE_DIRS}
)
pkg_check_modules(AUTH_DYNAMIC REQUIRED libcrypto)
include_directories(SYSTEM ${AUTH_DYNAMIC_INCLUDE_DIRS})
configure_file(manifest.json.in manifest.json)
if(CLICK_MODE)
......@@ -88,12 +77,24 @@ else(CLICK_MODE)
endif(CLICK_MODE)
add_subdirectory(liboath)
add_subdirectory(app)
set(QZXING_MULTIMEDIA ON)
add_definitions(
-DENABLE_ENCODER_GENERIC
-DQZXING_QML
-DQZXING_MULTIMEDIA
-DQZXING_LIBRARY
-DZXING_ICONV_CONST
-DDISABLE_LIBRARY_FEATURES
-DENABLE_DECODER_QR_CODE
)
add_subdirectory(qzxing/src)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/qzxing/src)
add_subdirectory(src)
add_subdirectory(po)
find_package(CoverageReport)
enable_coverage_report(
TARGETS liboath authenticator-ng
TARGETS liboath ${CMAKE_PROJECT_NAME} ${CMAKE_PROJECT_NAME}_priv
TESTS ${COVERAGE_TEST_TARGETS}
FILTER /usr/include ${CMAKE_BINARY_DIR}/*
)
/*
* Copyright © 2020 Rodney Dawes
* Copyright: 2013 Michael Zanetti <michael_zanetti@gmx.net>
*
* 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 "accountmodel.h"
#include "account.h"
#include <QSettings>
#include <QStringList>
//#include <QDebug>
AccountModel::AccountModel(QObject *parent) :
QAbstractListModel(parent)
{
QSettings settings("authenticator-ng.dobey", "authenticator");
// qDebug() << "loading settings file:" << settings.fileName();
foreach(const QString & group, settings.childGroups()) {
// qDebug() << "found group" << group << QUuid(group).toString();
QUuid id = QUuid(group);
bool migrateAccount = false;
if (id.isNull()) {
migrateAccount = true;
id = QUuid::createUuid();
}
settings.beginGroup(group);
Account *account = new Account(id, this);
account->setName(settings.value("account").toString());
account->setType(settings.value("type", "hotp").toString() == "totp" ? Account::TypeTOTP : Account::TypeHOTP);
account->setSecret(settings.value("secret").toString());
account->setCounter(settings.value("counter").toInt());
account->setTimeStep(settings.value("timeStep").toInt());
account->setPinLength(settings.value("pinLength", 6).toInt());
account->setAlgorithm(static_cast<Account::Algorithm>(settings.value("algorithm", Account::Algorithm::SHA1).toInt()));
connect(account, SIGNAL(nameChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(typeChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(secretChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(counterChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(timeStepChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(pinLengthChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(algorithmChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(otpChanged()), SLOT(accountChanged()));
m_accounts.append(account);
if (migrateAccount) {
settings.remove("");
storeAccount(account);
}
settings.endGroup();
}
}
int AccountModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_accounts.count();
}
QVariant AccountModel::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleName:
return m_accounts.at(index.row())->name();
case RoleType:
return m_accounts.at(index.row())->type();
case RoleSecret:
return m_accounts.at(index.row())->secret();
case RoleCounter:
return m_accounts.at(index.row())->counter();
case RoleTimeStep:
return m_accounts.at(index.row())->timeStep();
case RolePinLength:
return m_accounts.at(index.row())->pinLength();
case RoleAlgorithm:
return m_accounts.at(index.row())->algorithm();
case RoleOtp:
return m_accounts.at(index.row())->otp();
}
return QVariant();
}
Account *AccountModel::get(int index) const
{
if (index > -1 && m_accounts.count() > index) {
return m_accounts.at(index);
}
return 0;
}
Account *AccountModel::createAccount()
{
Account *account = new Account(QUuid::createUuid(), this);
beginInsertRows(QModelIndex(), m_accounts.count(), m_accounts.count());
m_accounts.append(account);
connect(account, SIGNAL(nameChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(typeChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(secretChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(counterChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(pinLengthChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(algorithmChanged()), SLOT(accountChanged()));
connect(account, SIGNAL(otpChanged()), SLOT(accountChanged()));
storeAccount(account);
endInsertRows();
return account;
}
void AccountModel::deleteAccount(int index)
{
// qDebug() << "starting deleteAccount" << index << m_accounts.count();
beginRemoveRows(QModelIndex(), index, index);
Account *account = m_accounts.takeAt(index);
// qDebug() << "got account" << account;
QSettings settings("authenticator-ng.dobey", "authenticator");
settings.beginGroup(account->id().toString());
settings.remove("");
settings.endGroup();
// qDebug() << "removed from settings";
account->deleteLater();
endRemoveRows();
// qDebug() << "done with deleteAccount";
}
void AccountModel::deleteAccount(Account *account)
{
int index = m_accounts.indexOf(account);
deleteAccount(index);
}
QHash<int, QByteArray> AccountModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleName, "name");
roles.insert(RoleType, "type");
roles.insert(RoleSecret, "secret");
roles.insert(RoleCounter, "counter");
roles.insert(RoleTimeStep, "timeStep");
roles.insert(RolePinLength, "pinLength");
roles.insert(RoleAlgorithm, "algorithm");
roles.insert(RoleOtp, "otp");
return roles;
}
void AccountModel::generateNext(int account)
{
m_accounts.at(account)->next();
emit dataChanged(index(account), index(account), QVector<int>() << RoleCounter << RoleOtp);
}
void AccountModel::refresh()
{
emit beginResetModel();
emit endResetModel();
}
void AccountModel::accountChanged()
{
Account *account = qobject_cast<Account*>(sender());
storeAccount(account);
// qDebug() << "account changed";
int accountIndex = m_accounts.indexOf(account);
emit dataChanged(index(accountIndex), index(accountIndex));
}
void AccountModel::storeAccount(Account *account)
{
QSettings settings("authenticator-ng.dobey", "authenticator");
settings.beginGroup(account->id().toString());
settings.setValue("account", account->name());
settings.setValue("type", account->type() == Account::TypeTOTP ? "totp" : "hotp");
settings.setValue("secret", account->secret());
settings.setValue("counter", account->counter());
settings.setValue("timeStep", account->timeStep());
settings.setValue("pinLength", account->pinLength());
settings.setValue("algorithm", account->algorithm());
settings.endGroup();
// qDebug() << "saved to" << settings.fileName();
}
This diff is collapsed.
/*
* Copyright © 2020 Rodney Dawes
* Copyright: 2013 Michael Zanetti <michael_zanetti@gmx.net>
*
* 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 "qrcodereader.h"
#include "../3rdParty/QZBarImage.h"
#include <zbar/ImageScanner.h>
#include <QGuiApplication>
#include <QWindow>
//#include <QDebug>
#include <QUrlQuery>
QRCodeReader::QRCodeReader(QObject *parent) :
QObject(parent),
m_mainWindow(0),
m_counter(0)
{
QGuiApplication *app = qobject_cast<QGuiApplication*>(qApp);
foreach (QWindow *win, app->allWindows()) {
QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
if (quickWin) {
m_mainWindow = quickWin;
}
}
}
bool QRCodeReader::valid() const
{
return !m_accountName.isEmpty() && !m_secret.isEmpty();
}
QString QRCodeReader::accountName() const
{
return m_accountName;
}
Account::Type QRCodeReader::type() const
{
return m_type;
}
QString QRCodeReader::secret() const
{
return m_secret;
}
quint64 QRCodeReader::counter() const
{
return m_counter;
}
int QRCodeReader::timeStep() const
{
return m_timeStep;
}
int QRCodeReader::pinLength() const
{
return m_pinLength;
}
Account::Algorithm QRCodeReader::algorithm() const
{
return m_algorithm;
}
void QRCodeReader::grab()
{
if (!m_mainWindow) {
return;
}
m_accountName.clear();
m_secret.clear();
m_counter = 0;
m_timeStep = 30;
m_pinLength = 6;
m_algorithm = Account::Algorithm::SHA1;
emit validChanged();
QImage img = m_mainWindow->grabWindow();
Reader *reader = new Reader;
connect(reader, SIGNAL(resultReady(QString, QString)), this, SLOT(handleResults(QString, QString)));
QMetaObject::invokeMethod(reader, "doWork", Q_ARG(QImage, img));
}
void QRCodeReader::handleResults(const QString &type, const QString &text)
{
// qDebug() << "parsed:" << type << text;
if (type == "QR-Code" && text.startsWith(QStringLiteral("otpauth://"))) {
QUrl url(text);
// qDebug() << url.host() << url.path() << url.query() << url.authority();
m_type = Account::TypeHOTP;
if (url.host() == "totp") {
m_type = Account::TypeTOTP;
}
m_accountName = url.path();
if (m_accountName.startsWith('/')) {
m_accountName.remove(0, 1);
}
QUrlQuery query(url.query());
for (int i = 0; i < query.queryItems().count(); ++i) {
if (query.queryItems().at(i).first == "secret") {
m_secret = query.queryItems().at(i).second;
}
if (query.queryItems().at(i).first == "counter") {
m_counter = query.queryItems().at(i).second.toULong();
}
if (query.queryItems().at(i).first == "period") {
m_timeStep = query.queryItems().at(i).second.toInt();
}
if (query.queryItems().at(i).first == "digits") {
m_pinLength = query.queryItems().at(i).second.toInt();
}
if (query.queryItems().at(i).first == "algorithm" && m_type == Account::TypeTOTP) {
auto algo = query.queryItems().at(i).second.toUpper();
if (algo == "SHA256") {
m_algorithm = Account::Algorithm::SHA256;
} else if (algo == "SHA512") {
m_algorithm = Account::Algorithm::SHA512;
} else {
m_algorithm = Account::Algorithm::SHA1;
}
}
}
// qDebug() << "Account:" << m_accountName;
// qDebug() << "Secret:" << m_secret;
// qDebug() << "Counter:" << m_counter;
// qDebug() << "Timestep:" << m_timeStep;
// qDebug() << "Pin length:" << m_pinLength;
emit validChanged();
} else {
qWarning() << "Invalid QR code scanned, with text of:" << text;
}
}
void Reader::doWork(const QImage &image)
{
zbar::QZBarImage img(image.convertToFormat(QImage::Format_RGB32));
zbar::Image tmp = img.convert(*(long*)"Y800");
// create a reader
zbar::ImageScanner scanner;
// configure the reader
scanner.set_config(zbar::ZBAR_QRCODE, zbar::ZBAR_CFG_ENABLE, 1);
// scan the image for barcodes
if (scanner.scan(tmp) <= 0) {
return;
}
img.set_symbols(tmp.get_symbols());
// extract results
for(zbar::Image::SymbolIterator symbol = img.symbol_begin();
symbol != img.symbol_end();
++symbol) {
QString typeName = QString::fromStdString(symbol->get_type_name());
QString symbolString = QString::fromStdString(symbol->get_data());
// qDebug() << "Code recognized:" << typeName << ", Text:" << symbolString;
emit resultReady(typeName, symbolString);
}
tmp.set_data(NULL, 0);
img.set_data(NULL, 0);
}
/*
* Copyright © 2020 Rodney Dawes
* Copyright: 2013 Michael Zanetti <michael_zanetti@gmx.net>
*
* 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/>.
*
*/
#pragma once
#include "account.h"
#include <QObject>