Verified Commit 0c006512 authored by Melvin Keskin's avatar Melvin Keskin Committed by Jonah Brüchert

Provide credentials for account transfer

Co-authored-by: Jonah Brüchert's avatarJonah Brüchert <jbb.prv@gmx.de>
parent b1a690d9
Pipeline #16344 failed with stages
in 41 minutes and 32 seconds
......@@ -61,6 +61,7 @@
<file alias="go-next-symbolic.svg">3rdparty/breeze-icons/icons/actions/symbolic/go-next-symbolic.svg</file>
<file alias="go-previous-symbolic.svg">3rdparty/breeze-icons/icons/actions/symbolic/go-previous-symbolic.svg</file>
<file alias="edit-symbolic.svg">3rdparty/breeze-icons/icons/actions/symbolic/edit-symbolic.svg</file>
<file alias="send-to-symbolic.svg">3rdparty/breeze-icons/icons/actions/symbolic/send-to-symbolic.svg</file>
</qresource>
<qresource prefix="/icons/breeze/applets/22">
......
......@@ -29,6 +29,7 @@ set(KAIDAN_SOURCES
src/QmlUtils.cpp
src/Utils.cpp
src/QrCodeDecoder.cpp
src/QrCodeGenerator.cpp
src/QrCodeScannerFilter.cpp
src/QrCodeVideoFrame.cpp
src/CameraModel.cpp
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2020 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QrCodeGenerator.h"
#include <QDebug>
#include <QImage>
#include <QRgb>
#include "Kaidan.h"
#include "qxmpp-exts/QXmppUri.h"
#define COLOR_TABLE_INDEX_FOR_WHITE 0
#define COLOR_TABLE_INDEX_FOR_BLACK 1
QrCodeGenerator::QrCodeGenerator(QObject *parent)
: QObject(parent)
{
}
QImage QrCodeGenerator::generateLoginUriQrCode(int edgePixelCount)
{
QXmppUri uri;
uri.setJid(Kaidan::instance()->getJid());
uri.setAction(QXmppUri::Login);
uri.setPassword(Kaidan::instance()->getPassword());
return generateQrCode(uri.toString(), edgePixelCount);
}
QImage QrCodeGenerator::generateQrCode(const QString &text, int edgePixelCount)
{
try {
ZXing::MultiFormatWriter writer(ZXing::BarcodeFormat::QR_CODE);
const ZXing::BitMatrix &bitMatrix = writer.encode(text.toStdWString(), edgePixelCount, edgePixelCount);
return toImage(bitMatrix);
} catch (const std::invalid_argument &e) {
Kaidan::instance()->passiveNotificationRequested(tr("Generating the QR code failed: %1").arg(e.what()));
}
return {};
}
QImage QrCodeGenerator::toImage(const ZXing::BitMatrix &bitMatrix)
{
QImage monochromeImage(bitMatrix.width(), bitMatrix.height(), QImage::Format_Mono);
createColorTable(monochromeImage);
for (int y = 0; y < bitMatrix.height(); ++y) {
for (int x = 0; x < bitMatrix.width(); ++x) {
int colorTableIndex = bitMatrix.get(x, y) ? COLOR_TABLE_INDEX_FOR_BLACK : COLOR_TABLE_INDEX_FOR_WHITE;
monochromeImage.setPixel(y, x, colorTableIndex);
}
}
return monochromeImage;
}
void QrCodeGenerator::createColorTable(QImage &blackAndWhiteImage)
{
blackAndWhiteImage.setColor(COLOR_TABLE_INDEX_FOR_WHITE, qRgb(255, 255, 255));
blackAndWhiteImage.setColor(COLOR_TABLE_INDEX_FOR_BLACK, qRgb(0, 0, 0));
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2020 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QRCODEGENERATOR_H
#define QRCODEGENERATOR_H
#include <QObject>
#include <ZXing/BarcodeFormat.h>
#include <ZXing/MultiFormatWriter.h>
#include <ZXing/BitMatrix.h>
class QImage;
class QrCodeGenerator : public QObject
{
Q_OBJECT
public:
/**
* Instantiates a QR code generator.
*
* @param parent parent object
*/
explicit QrCodeGenerator(QObject *parent = nullptr);
/**
* Gerenates a QR code encoding the credentials of the currently used account to log into it with another client.
*
* @param edgePixelCount number of pixels as the width and height of the QR code
*/
Q_INVOKABLE static QImage generateLoginUriQrCode(int edgePixelCount);
/**
* Gerenates a QR code.
*
* @param text string to be encoded as a QR code
* @param edgePixelCount number of pixels as the width and height of the QR code
*/
Q_INVOKABLE static QImage generateQrCode(const QString &text, int edgePixelCount);
private:
/**
* Generates an image with black and white pixels from a given matrix of bits representing a QR code.
*
* @param bitMatrix matrix of bits representing the two colors black and white
*/
static QImage toImage(const ZXing::BitMatrix &bitMatrix);
/**
* Sets up a color image for a given monochrome image consisting only of black and white pixels.
* @param blackAndWhiteImage image for which a color table with the colors black and white is created
*/
static void createColorTable(QImage &blackAndWhiteImage);
};
#endif // QRCODEGENERATOR_H
......@@ -61,6 +61,7 @@
#include "UploadManager.h"
#include "EmojiModel.h"
#include "Utils.h"
#include "QrCodeGenerator.h"
#include "QrCodeScannerFilter.h"
#include "VCardModel.h"
#include "CameraModel.h"
......@@ -351,6 +352,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<MediaSettingsVideoFrameRateModel>(APPLICATION_ID, 1, 0, "MediaSettingsVideoFrameRateModel");
qmlRegisterType<MediaRecorder>(APPLICATION_ID, 1, 0, "MediaRecorder");
qmlRegisterType<CredentialsValidator>(APPLICATION_ID, 1, 0, "CredentialsValidator");
qmlRegisterType<QrCodeGenerator>(APPLICATION_ID, 1, 0, "QrCodeGenerator");
qmlRegisterUncreatableType<QAbstractItemModel>("EmojiModel", 0, 1, "QAbstractItemModel", "Used by proxy models");
qmlRegisterUncreatableType<Emoji>("EmojiModel", 0, 1, "Emoji", "Used by emoji models");
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.3 as Controls
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.4 as Kirigami
import im.kaidan.kaidan 1.0
import "elements"
/**
* This page shows the user's credentials as a QR code or as cleartext, which allows the user to log in on another device.
*/
Kirigami.Page {
id: root
title: qsTr("Transfer account to another device")
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
QrCodeGenerator {
id: qrCodeGenerator
}
ColumnLayout {
z: 1
anchors.margins: 18
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
CenteredAdaptiveText {
id: explanation
text: qsTr("Scan the QR code or enter the credentials as text on another device to log in on it.\n\nAttention:\nNever show this QR code to anyone else. It would allow unlimited access to your account!")
width: parent.width
Layout.topMargin: 10
scaleFactor: 2
}
// placeholder
Item {
Layout.fillHeight: true
}
Kirigami.Icon {
id: qrCode
width: parent.width
height: width
visible: false
source: visible ? qrCodeGenerator.generateLoginUriQrCode(width) : ""
Layout.fillWidth: true
Layout.fillHeight: true
}
Kirigami.FormLayout {
id: cleartext
visible: false
Controls.Label {
text: Kaidan.jid
Kirigami.FormData.label: qsTr("Chat address:")
}
Controls.Label {
text: Kaidan.password
Kirigami.FormData.label: qsTr("Password:")
}
}
// placeholder
Item {
Layout.fillHeight: true
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: largeButtonWidth
// button for showing or hiding the credentials as a QR code
CenteredAdaptiveHighlightedButton {
label.text: checked ? qsTr("Hide QR code") : qsTr("Show as QR code")
checkable: true
// If that was not used, this button would change its label text but not its checked state when the button for showing the cleartext is clicked right after it.
checked: qrCode.visible
onClicked: {
if (qrCode.visible) {
qrCode.visible = false
cleartext.visible = false
explanation.visible = true
} else {
qrCode.visible = true
cleartext.visible = false
explanation.visible = false
}
}
}
// button for showing or hiding the credentials as cleartext
CenteredAdaptiveButton {
label.text: checked ? qsTr("Hide text") : qsTr("Show as text")
checkable: true
// If that was not used, this button would change its label text but not its checked state when the button for showing the QR code is clicked right after it.
checked: cleartext.visible
onClicked: {
if (cleartext.visible) {
cleartext.visible = false
qrCode.visible = false
explanation.visible = true
} else {
cleartext.visible = true
qrCode.visible = false
explanation.visible = false
}
}
}
}
}
}
......@@ -90,6 +90,14 @@ Kirigami.GlobalDrawer {
passiveNotification(qsTr("Invitation link copied to clipboard"))
}
},
Kirigami.Action {
text: qsTr("Transfer account")
icon.name: "send-to-symbolic"
onTriggered: {
pageStack.layers.push(accountTransferPage)
}
},
Kirigami.Action {
text: qsTr("Delete account")
icon.name: "delete"
......
......@@ -83,6 +83,7 @@ Kirigami.ApplicationWindow {
Component {id: settingsPage; SettingsPage {}}
Component {id: qrCodeScannerPage; QrCodeScannerPage {}}
Component {id: userProfilePage; UserProfilePage {}}
Component {id: accountTransferPage; AccountTransferPage {}}
Component {id: accountDeletionPage; AccountDeletionPage {}}
Component {id: accountDeletionFromClientConfirmationPage; AccountDeletionFromClientConfirmationPage {}}
Component {id: accountDeletionFromClientAndServerConfirmationPage; AccountDeletionFromClientAndServerConfirmationPage {}}
......
......@@ -13,6 +13,7 @@
<file>AccountDeletionPage.qml</file>
<file>AccountDeletionFromClientConfirmationPage.qml</file>
<file>AccountDeletionFromClientAndServerConfirmationPage.qml</file>
<file>AccountTransferPage.qml</file>
<file>elements/SubRequestAcceptSheet.qml</file>
<file>elements/RosterAddContactSheet.qml</file>
......@@ -26,12 +27,12 @@
<file>elements/Button.qml</file>
<file>elements/CenteredAdaptiveButton.qml</file>
<file>elements/CenteredAdaptiveHighlightedButton.qml</file>
<file>elements/CenteredAdaptiveText.qml</file>
<file>elements/IconButton.qml</file>
<file>elements/FileChooser.qml</file>
<file>elements/SendMediaSheet.qml</file>
<file>elements/BinaryDecisionPage.qml</file>
<file>elements/ConfirmationPage.qml</file>
<file>elements/CenteredAdaptiveText.qml</file>
<file>elements/MediaPreview.qml</file>
<file>elements/MediaPreviewImage.qml</file>
......
......@@ -31,6 +31,11 @@
#include "QXmppUri.h"
#include <QUrlQuery>
const QString URI_SCHEME = QStringLiteral("xmpp");
const QChar URI_QUERY_SEPARATOR = '?';
const QChar URI_QUERY_VALUE_DELIMITER = '=';
const QChar URI_QUERY_PAIR_DELIMITER = ';';
/// actions, e.g. "join" in "xmpp:group@example.org?join" for joining a group chat
const QStringList ACTION_STRINGS = QStringList()
......@@ -76,11 +81,10 @@ const QStringList MESSAGE_ATTRIBUTES = QStringList()
/// @param key key of the given value
/// @param val value of given key
void helperToAddPair(QList<QPair<QString, QString>> &pairs, const QString &key,
const QString &val)
void helperToAddPair(QUrlQuery &query, const QString &key, const QString &val)
{
if (!val.isEmpty())
pairs << qMakePair(key, val);
query.addQueryItem(key, val);
}
/// @return value of a given key in a given query
......@@ -93,29 +97,30 @@ QString helperToGetQueryItemValue(const QUrlQuery &query, const QString &key)
}
/// Parses the URI from a string.
///
/// @param input string which may present an XMPP URI
QXmppUri::QXmppUri(QString input)
{
// We're replacing ';' with '&', so we can reuse the QUrlQuery.
QUrl url(input.replace(";", "&"));
if (!url.isValid() || url.scheme() != "xmpp")
QUrl url(input);
if (!url.isValid() || url.scheme() != URI_SCHEME)
return;
// set JID
setJid(url.path());
if (!input.contains("?"))
if (!url.hasQuery())
return;
QUrlQuery query(url.query());
QUrlQuery query;
query.setQueryDelimiters(URI_QUERY_VALUE_DELIMITER, URI_QUERY_PAIR_DELIMITER);
query.setQuery(url.query(QUrl::FullyEncoded));
// check that there are query items (key-value pairs)
if (!query.queryItems().size())
return;
m_action = static_cast<Action>(
ACTION_STRINGS.indexOf(query.queryItems().first().first)
);
m_action = Action(ACTION_STRINGS.indexOf(query.queryItems().first().first));
switch (m_action) {
case Message:
......@@ -125,8 +130,8 @@ QXmppUri::QXmppUri(QString input)
m_message.setId(helperToGetQueryItemValue(query, "id"));
m_message.setFrom(helperToGetQueryItemValue(query, "from"));
if (!helperToGetQueryItemValue(query, "type").isEmpty())
m_message.setType(static_cast<QXmppMessage::Type>(
MESSAGE_TYPE_STRINGS.indexOf(helperToGetQueryItemValue(query, "type"))
m_message.setType(QXmppMessage::Type(
MESSAGE_TYPE_STRINGS.indexOf(helperToGetQueryItemValue(query, "type"))
));
else
m_hasMessageType = false;
......@@ -141,49 +146,48 @@ QXmppUri::QXmppUri(QString input)
/// Decodes the URI to a string.
///
/// \returns Full XMPP URI
/// @return full XMPP URI
QString QXmppUri::toString()
{
QUrl url;
url.setScheme("xmpp");
url.setScheme(URI_SCHEME);
url.setPath(m_jid);
// Create query items (parameters)
QUrlQuery query;
QList<QPair<QString, QString>> queryItems;
query.setQueryDelimiters(URI_QUERY_VALUE_DELIMITER, URI_QUERY_PAIR_DELIMITER);
switch (m_action) {
case Message:
helperToAddPair(queryItems, "body", m_message.body());
helperToAddPair(queryItems, "from", m_message.from());
helperToAddPair(queryItems, "id", m_message.id());
helperToAddPair(queryItems, "thread", m_message.thread());
if (m_hasMessageType)
helperToAddPair(queryItems, "type", MESSAGE_TYPE_STRINGS.at(
int(m_message.type())));
helperToAddPair(queryItems, "subject", m_message.subject());
break;
case Login:
helperToAddPair(queryItems, "password", m_password);
break;
helperToAddPair(query, "body", m_message.body());
helperToAddPair(query, "from", m_message.from());
helperToAddPair(query, "id", m_message.id());
helperToAddPair(query, "thread", m_message.thread());
if (m_hasMessageType)
helperToAddPair(query, "type", MESSAGE_TYPE_STRINGS.at(
int(m_message.type())));
helperToAddPair(query, "subject", m_message.subject());
break;
case Login:
helperToAddPair(query, "password", m_password);
break;
default:
break;
break;
}
query.setQueryItems(queryItems);
QString output = url.toEncoded();
if (m_action != None) {
// add action
output += "?";
output += ACTION_STRINGS.at(int(m_action));
// add parameters
QString queryStr = QUrl::toPercentEncoding(query.toString().replace("&", ";"), ";=");
if (!query.isEmpty()) {
output += ";";
output += queryStr;
}
// add action
output += URI_QUERY_SEPARATOR;
output += ACTION_STRINGS.at(int(m_action));
// add parameters
QString queryStr = query.toString(QUrl::FullyEncoded);
if (!query.isEmpty()) {
output += URI_QUERY_PAIR_DELIMITER;
output += queryStr;
}
}
return output;
......@@ -235,11 +239,22 @@ bool QXmppUri::hasAction(const Action &action)
return m_action == action;
}
/// Returns the password of a login action
QString QXmppUri::password() const
{
return m_password;
}
/// Sets the password of a login action
///
/// @param password
void QXmppUri::setPassword(const QString &password)
{
m_password = password;
}
/// In case the URI has a message query, this can be used to get the attached
/// message content directly as \c QXmppMessage.
......
......@@ -70,6 +70,7 @@ public:
// login
QString password() const;
void setPassword(const QString &password);
// 'message' query
QXmppMessage message() const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment