Implemented Nextcloud Login Flow v2

This commit is contained in:
Scharel Clemens 2020-01-15 00:27:17 +01:00
parent ecebd90dcb
commit 2be8a2675d
9 changed files with 312 additions and 210 deletions

View file

@ -28,7 +28,6 @@ SOURCES += src/harbour-nextcloudnotes.cpp \
DISTFILES += qml/harbour-nextcloudnotes.qml \ DISTFILES += qml/harbour-nextcloudnotes.qml \
qml/cover/CoverPage.qml \ qml/cover/CoverPage.qml \
qml/pages/LoginWebView.qml \
rpm/harbour-nextcloudnotes.changes.run.in \ rpm/harbour-nextcloudnotes.changes.run.in \
rpm/harbour-nextcloudnotes.changes \ rpm/harbour-nextcloudnotes.changes \
rpm/harbour-nextcloudnotes.spec \ rpm/harbour-nextcloudnotes.spec \

View file

@ -131,7 +131,6 @@ ApplicationWindow
id: notesApi id: notesApi
scheme: account.allowUnecrypted ? "http" : "https" scheme: account.allowUnecrypted ? "http" : "https"
host: account.server host: account.server
path: "/index.php/apps/notes/api/" + account.version
username: account.username username: account.username
password: account.password password: account.password
sslVerify: !account.doNotVerifySsl sslVerify: !account.doNotVerifySsl

View file

@ -31,15 +31,39 @@ Dialog {
account.setValue("unsecureConnection", unsecureConnectionTextSwitch.checked) account.setValue("unsecureConnection", unsecureConnectionTextSwitch.checked)
account.setValue("unencryptedConnection", unencryptedConnectionTextSwitch.checked) account.setValue("unencryptedConnection", unencryptedConnectionTextSwitch.checked)
account.sync() account.sync()
api.uuid = accountId
} }
onRejected: { onRejected: {
if (addingNew) appSettings.removeAccount(accountId) if (addingNew) appSettings.removeAccount(accountId)
notesApi.host = value("server", "", String) notesApi.host = account.value("server", "", String)
} }
Timer { Connections {
id: loginPollTimer target: notesApi
onStatusVersionChanged: {
for (var i = 0; i < notesApi.version.length; ++i) {
console.log(notesApi.version[i])
}
}
onLoginUrlChanged: {
if (notesApi.loginUrl)
Qt.openUrlExternally(notesApi.loginUrl)
else {
pushed = true
//console.log("Login successfull")
}
}
onServerChanged: {
console.log("Login server: " + notesApi.server)
serverField.text = notesApi.server
}
onUsernameChanged: {
console.log("Login username: " + notesApi.username)
usernameField.text = notesApi.username
}
onPasswordChanged: {
console.log("Login password: " + notesApi.password)
passwordField.text = notesApi.password
}
} }
SilicaFlickable { SilicaFlickable {
@ -62,6 +86,30 @@ Dialog {
source: "../img/nextcloud-logo-transparent.png" source: "../img/nextcloud-logo-transparent.png"
} }
Column {
id: flowv2LoginColumn
width: parent.width
visible: notesApi.statusVersion.split('.')[0] >= 16
Label {
x: Theme.horizontalPageMargin
width: parent.width - 2*x
text: qsTr("Login Flow v2") + " " + notesApi.statusVersionString
}
}
Column {
id: legacyLoginColumn
width: parent.width
visible: !flowv2LoginColumn.visible
Label {
x: Theme.horizontalPageMargin
width: parent.width - 2*x
text: qsTr("Legacy Login") + " " + notesApi.statusVersionString
}
}
TextField { TextField {
id: nameField id: nameField
width: parent.width width: parent.width
@ -121,10 +169,23 @@ Dialog {
Button { Button {
id: loginButton id: loginButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
property bool pushed: false
text: qsTr("Login") text: qsTr("Login")
enabled: !pushed
onClicked: { onClicked: {
pushed = true
//pageStack.push(Qt.resolvedUrl("LoginWebView.qml"), { loginUrl: serverField.text }) loginTimeout.start()
notesApi.initiateFlowV2Login()
}
BusyIndicator {
id: loginBusyIndicator
anchors.centerIn: parent
running: loginButton.pushed
}
Timer {
id: loginTimeout
interval: 60000
onTriggered: loginButton.pushed = false
} }
} }

View file

@ -1,41 +0,0 @@
import QtQuick 2.2
import Sailfish.Silica 1.0
Page {
id: loginWebView
property url loginUrl
Component.onCompleted: {
var req = new XMLHttpRequest()
req.open("GET", endpoint, true)
req.setRequestHeader('User-Agent', 'SailfishOS/harbour-nextcloudnotes')
req.setRequestHeader("OCS-APIREQUEST", "true")
req.onreadystatechange = function() {
if (req.readyState === XMLHttpRequest.DONE) {
if (req.status === 200) {
ncFlowWebView.data = req.responseText
}
}
}
req.send()
}
SilicaWebView {
id: ncFlow2WebView
anchors.fill: parent
url: loginUrl
header: PageHeader {
title: ncFlow2WebView.title
description: url
BusyIndicator {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Theme.horizontalPageMargin
size: BusyIndicatorSize.Medium
running: ncFlow2WebView.loading
}
}
}
}

View file

@ -6,6 +6,8 @@
NotesApi::NotesApi(QObject *parent) : QObject(parent) NotesApi::NotesApi(QObject *parent) : QObject(parent)
{ {
m_loginPollTimer.setInterval(1000);
connect(&m_loginPollTimer, SIGNAL(timeout()), this, SLOT(pollLoginUrl()));
m_online = m_manager.networkAccessible() == QNetworkAccessManager::Accessible; m_online = m_manager.networkAccessible() == QNetworkAccessManager::Accessible;
mp_model = new NotesModel(this); mp_model = new NotesModel(this);
mp_modelProxy = new NotesProxyModel(this); mp_modelProxy = new NotesProxyModel(this);
@ -20,7 +22,7 @@ NotesApi::NotesApi(QObject *parent) : QObject(parent)
connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
connect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>))); connect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>)));
m_request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); m_request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
m_request.setHeader(QNetworkRequest::UserAgentHeader, QGuiApplication::applicationDisplayName() + " / " + QGuiApplication::applicationVersion()); m_request.setHeader(QNetworkRequest::UserAgentHeader, QGuiApplication::applicationDisplayName() + " " + QGuiApplication::applicationVersion() + " - " + QSysInfo::machineHostName());
m_request.setRawHeader("OCS-APIREQUEST", "true"); m_request.setRawHeader("OCS-APIREQUEST", "true");
m_request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json").toUtf8()); m_request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json").toUtf8());
connect(mp_model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), this, SLOT(saveToFile(QModelIndex,QModelIndex,QVector<int>))); connect(mp_model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), this, SLOT(saveToFile(QModelIndex,QModelIndex,QVector<int>)));
@ -50,9 +52,11 @@ void NotesApi::setSslVerify(bool verify) {
void NotesApi::setUrl(QUrl url) { void NotesApi::setUrl(QUrl url) {
if (url != m_url) { if (url != m_url) {
QUrl oldUrl = m_url; QUrl oldUrl = m_url;
bool oldReady = ready(); QString oldServer = server();
m_url = url; m_url = url;
emit urlChanged(m_url); emit urlChanged(m_url);
if (server() != oldServer)
emit serverChanged(server());
if (m_url.scheme() != oldUrl.scheme()) if (m_url.scheme() != oldUrl.scheme())
emit schemeChanged(m_url.scheme()); emit schemeChanged(m_url.scheme());
if (m_url.host() != oldUrl.host()) if (m_url.host() != oldUrl.host())
@ -65,13 +69,31 @@ void NotesApi::setUrl(QUrl url) {
emit passwordChanged(m_url.password()); emit passwordChanged(m_url.password());
if (m_url.path() != oldUrl.path()) if (m_url.path() != oldUrl.path())
emit pathChanged(m_url.path()); emit pathChanged(m_url.path());
if (ready() != oldReady)
emit readyChanged(ready());
if (m_url.isValid()) if (m_url.isValid())
qDebug() << "API URL:" << m_url.toDisplayString(); qDebug() << "API URL:" << m_url.toDisplayString();
} }
} }
QString NotesApi::server() const {
QUrl server;
server.setScheme(m_url.scheme());
server.setHost(m_url.host());
if (m_url.port() > 0)
server.setPort(m_url.port());
server.setPath(m_url.path());
return server.toString();
}
void NotesApi::setServer(QString serverUrl) {
QUrl url(serverUrl);
if (serverUrl != server()) {
setScheme(url.scheme());
setHost(url.host());
setPort(url.port());
setPath(url.path());
}
}
void NotesApi::setScheme(QString scheme) { void NotesApi::setScheme(QString scheme) {
if (scheme == "http" || scheme == "https") { if (scheme == "http" || scheme == "https") {
QUrl url = m_url; QUrl url = m_url;
@ -89,18 +111,18 @@ void NotesApi::setHost(QString host) {
} }
void NotesApi::setPort(int port) { void NotesApi::setPort(int port) {
if (port >= -1 && port <= 65535) { if (port >= 1 && port <= 65535) {
QUrl url = m_url; QUrl url = m_url;
url.setPort(port); url.setPort(port);
setUrl(url); setUrl(url);
} }
} }
void NotesApi::setUsername(QString username) { void NotesApi::setUsername(QString user) {
if (!username.isEmpty()) { if (!user.isEmpty()) {
QUrl url = m_url; QUrl url = m_url;
url.setUserName(username); url.setUserName(user);
QString concatenated = username + ":" + password(); QString concatenated = user + ":" + password();
QByteArray data = concatenated.toLocal8Bit().toBase64(); QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data; QString headerData = "Basic " + data;
m_request.setRawHeader("Authorization", headerData.toLocal8Bit()); m_request.setRawHeader("Authorization", headerData.toLocal8Bit());
@ -138,18 +160,10 @@ void NotesApi::setDataFile(QString dataFile) {
} }
} }
bool NotesApi::ready() const {
return !m_url.scheme().isEmpty() &&
!m_url.userName().isEmpty() &&
!m_url.host().isEmpty() &&
!m_url.path().isEmpty() &&
!m_url.query().isEmpty();
}
bool NotesApi::busy() const { bool NotesApi::busy() const {
bool busy = false; bool busy = false;
QVector<QNetworkReply*> replies; QVector<QNetworkReply*> replies;
replies << m_replies << m_status_replies << m_login_replies; replies << m_replies << m_status_replies << m_login_replies << m_poll_replies;
for (int i = 0; i < replies.size(); ++i) { for (int i = 0; i < replies.size(); ++i) {
busy |= replies[i]->isRunning(); busy |= replies[i]->isRunning();
} }
@ -157,33 +171,46 @@ bool NotesApi::busy() const {
} }
void NotesApi::getStatus() { void NotesApi::getStatus() {
QUrl url = m_url; QUrl url = server();
url.setPath("/index.php/login/v2"); QNetworkRequest request;
url.setPath(url.path() + "/status.php");
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "POST" << url.toDisplayString(); qDebug() << "POST" << url.toDisplayString();
m_request.setUrl(url); request.setUrl(url);
m_login_replies << m_manager.post(m_request, QByteArray()); m_status_replies << m_manager.post(request, QByteArray());
emit busyChanged(busy()); emit busyChanged(busy());
} }
} }
void NotesApi::initiateFlowV2Login() { void NotesApi::initiateFlowV2Login() {
QUrl url = m_url; QUrl url = server();
url.setPath("/status.php"); url.setPath(url.path() + "/index.php/login/v2");
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "POST" << url.toDisplayString(); qDebug() << "POST" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_status_replies << m_manager.post(m_request, QByteArray()); m_login_replies << m_manager.post(m_request, QByteArray());
m_loginPollTimer.start();
emit busyChanged(busy());
}
}
void NotesApi::pollLoginUrl() {
//QNetworkRequest request;
//request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/x-www-form-urlencoded").toUtf8());
if (m_pollUrl.isValid() && !m_pollUrl.scheme().isEmpty() && !m_pollUrl.host().isEmpty() && !m_pollToken.isEmpty()) {
//qDebug() << "POST" << m_pollUrl.toDisplayString();
m_request.setUrl(m_pollUrl);
m_poll_replies << m_manager.post(m_request, QByteArray("token=").append(m_pollToken));
emit busyChanged(busy()); emit busyChanged(busy());
} }
} }
void NotesApi::getAllNotes(QStringList excludeFields) { void NotesApi::getAllNotes(QStringList excludeFields) {
QUrl url = m_url; QUrl url = server();
url.setPath(url.path() + "/notes"); url.setPath(url.path() + "/index.php/apps/notes/api/v0.2" + "/notes");
if (!excludeFields.isEmpty()) if (!excludeFields.isEmpty())
url.setQuery(QString("exclude=").append(excludeFields.join(","))); url.setQuery(QString("exclude=").append(excludeFields.join(",")));
if (url.isValid()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "GET" << url.toDisplayString(); qDebug() << "GET" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_replies << m_manager.get(m_request); m_replies << m_manager.get(m_request);
@ -193,10 +220,10 @@ void NotesApi::getAllNotes(QStringList excludeFields) {
void NotesApi::getNote(double noteId, QStringList excludeFields) { void NotesApi::getNote(double noteId, QStringList excludeFields) {
QUrl url = m_url; QUrl url = m_url;
url.setPath(url.path() + QString("/notes/%1").arg(noteId)); url.setPath(url.path() + "/index.php/apps/notes/api/v0.2" + QString("/notes/%1").arg(noteId));
if (!excludeFields.isEmpty()) if (!excludeFields.isEmpty())
url.setQuery(QString("exclude=").append(excludeFields.join(","))); url.setQuery(QString("exclude=").append(excludeFields.join(",")));
if (url.isValid()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "GET" << url.toDisplayString(); qDebug() << "GET" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_replies << m_manager.get(m_request); m_replies << m_manager.get(m_request);
@ -211,8 +238,8 @@ void NotesApi::createNote(QVariantMap fields) {
// Create note via the API // Create note via the API
QUrl url = m_url; QUrl url = m_url;
url.setPath(url.path() + "/notes"); url.setPath(url.path() + "/index.php/apps/notes/api/v0.2" + "/notes");
if (url.isValid()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "POST" << url.toDisplayString(); qDebug() << "POST" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_replies << m_manager.post(m_request, note.toJsonDocument().toJson()); m_replies << m_manager.post(m_request, note.toJsonDocument().toJson());
@ -227,8 +254,8 @@ void NotesApi::updateNote(double noteId, QVariantMap fields) {
// Update note on the server // Update note on the server
QUrl url = m_url; QUrl url = m_url;
url.setPath(url.path() + QString("/notes/%1").arg(noteId)); url.setPath(url.path() + "/index.php/apps/notes/api/v0.2" + QString("/notes/%1").arg(noteId));
if (url.isValid()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "PUT" << url.toDisplayString(); qDebug() << "PUT" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_replies << m_manager.put(m_request, note.toJsonDocument().toJson()); m_replies << m_manager.put(m_request, note.toJsonDocument().toJson());
@ -242,8 +269,8 @@ void NotesApi::deleteNote(double noteId) {
// Remove note from the server // Remove note from the server
QUrl url = m_url; QUrl url = m_url;
url.setPath(url.path() + QString("/notes/%1").arg(noteId)); url.setPath(url.path() + "/index.php/apps/notes/api/v0.2" + QString("/notes/%1").arg(noteId));
if (url.isValid()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "DELETE" << url.toDisplayString(); qDebug() << "DELETE" << url.toDisplayString();
m_request.setUrl(url); m_request.setUrl(url);
m_replies << m_manager.deleteResource(m_request); m_replies << m_manager.deleteResource(m_request);
@ -254,7 +281,7 @@ void NotesApi::deleteNote(double noteId) {
void NotesApi::verifyUrl(QUrl url) { void NotesApi::verifyUrl(QUrl url) {
emit urlValidChanged(url.isValid()); emit urlValidChanged(url.isValid());
if (url.isValid()) { if (m_url.isValid() && !m_url.scheme().isEmpty() && !m_url.host().isEmpty()) {
getStatus(); getStatus();
} }
} }
@ -274,20 +301,28 @@ void NotesApi::onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessib
} }
void NotesApi::replyFinished(QNetworkReply *reply) { void NotesApi::replyFinished(QNetworkReply *reply) {
qDebug() << reply->error() << reply->errorString(); //qDebug() << reply->error() << reply->errorString();
bool deleteReply = false;
if (reply->error() == QNetworkReply::NoError) { if (reply->error() == QNetworkReply::NoError) {
emit error(NoError); emit error(NoError);
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QJsonDocument json = QJsonDocument::fromJson(data); QJsonDocument json = QJsonDocument::fromJson(data);
if (m_login_replies.contains(reply)) { if (m_login_replies.contains(reply)) {
if (json.isObject()) { if (json.isObject())
updateLogin(json.object()); updateLoginFlow(json.object());
} m_login_replies.removeAll(reply);
} }
if (m_status_replies.contains(reply)) { else if (m_poll_replies.contains(reply)) {
if (json.isObject()) { if (json.isObject())
updateLoginCredentials(json.object());
m_poll_replies.removeAll(reply);
m_loginPollTimer.stop();
m_loginUrl.clear();
}
else if (m_status_replies.contains(reply)) {
if (json.isObject())
updateStatus(json.object()); updateStatus(json.object());
} m_status_replies.removeAll(reply);
} }
else { else {
if (mp_model) { if (mp_model) {
@ -296,18 +331,26 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
emit lastSyncChanged(m_lastSync); emit lastSyncChanged(m_lastSync);
} }
} }
m_replies.removeAll(reply);
} }
//qDebug() << data; //qDebug() << data;
deleteReply = true;
} }
else if (reply->error() == QNetworkReply::AuthenticationRequiredError) else if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
emit error(AuthenticationError); emit error(AuthenticationError);
else deleteReply = true;
emit error(CommunicationError); }
m_login_replies.removeAll(reply); else {
m_status_replies.removeAll(reply); if (!m_poll_replies.contains(reply)) {
m_replies.removeAll(reply); emit error(CommunicationError);
reply->deleteLater(); deleteReply = true;
}
else {
//qDebug() << "Polling not finished yet" << m_pollUrl;
}
}
emit busyChanged(busy()); emit busyChanged(busy());
//if (deleteReply) reply->deleteLater();
} }
void NotesApi::sslError(QNetworkReply *reply, const QList<QSslError> &errors) { void NotesApi::sslError(QNetworkReply *reply, const QList<QSslError> &errors) {
@ -344,12 +387,8 @@ void NotesApi::updateStatus(const QJsonObject &status) {
m_status_needsDbUpgrade = status.value("needsDbUpgrade").toBool(); m_status_needsDbUpgrade = status.value("needsDbUpgrade").toBool();
emit statusNeedsDbUpgradeChanged(m_status_needsDbUpgrade); emit statusNeedsDbUpgradeChanged(m_status_needsDbUpgrade);
} }
QStringList versionStr = status.value("version").toString().split('.'); if (m_status_version != status.value("version").toString()) {
QVector<int> version; m_status_version = status.value("version").toString();
for(int i = 0; i < versionStr.size(); ++i)
version << versionStr[i].toInt();
if (m_status_version != version) {
m_status_version = version;
emit statusVersionChanged(m_status_version); emit statusVersionChanged(m_status_version);
} }
if (m_status_versionstring != status.value("versionstring").toString()) { if (m_status_versionstring != status.value("versionstring").toString()) {
@ -362,7 +401,6 @@ void NotesApi::updateStatus(const QJsonObject &status) {
} }
if (m_status_productname != status.value("productname").toString()) { if (m_status_productname != status.value("productname").toString()) {
m_status_productname = status.value("productname").toString(); m_status_productname = status.value("productname").toString();
qDebug() << m_status_productname;
emit statusProductNameChanged(m_status_productname); emit statusProductNameChanged(m_status_productname);
} }
if (m_status_extendedSupport != status.value("extendedSupport").toBool()) { if (m_status_extendedSupport != status.value("extendedSupport").toBool()) {
@ -372,31 +410,51 @@ void NotesApi::updateStatus(const QJsonObject &status) {
} }
} }
void NotesApi::updateLogin(const QJsonObject &login) { void NotesApi::updateLoginFlow(const QJsonObject &login) {
QUrl url; QUrl url;
QString token; QString token;
if (!login.isEmpty()) { if (!login.isEmpty()) {
url = login.value("login").toString();
if (m_login_loginUrl != url && url.isValid()) {
m_login_loginUrl = url;
emit loginLoginUrlChanged(m_login_loginUrl);
}
QJsonObject poll = login.value("poll").toObject(); QJsonObject poll = login.value("poll").toObject();
if (!poll.isEmpty()) { if (!poll.isEmpty()) {
url = poll.value("endpoint").toString() ; url = poll.value("endpoint").toString() ;
if (m_login_pollUrl != url && urlValid()) {
m_login_pollUrl = url;
emit loginPollUrlChanged(m_login_pollUrl);
}
token = poll.value("token").toString(); token = poll.value("token").toString();
if (m_login_pollToken != token) { if (url.isValid() && !token.isEmpty()) {
m_login_pollToken = token; m_pollUrl = url;
emit loginPollTokenChanged(m_login_pollToken); qDebug() << "Poll URL: " << m_pollUrl;
m_pollToken = token;
qDebug() << "Poll Token: " << m_pollToken;
}
else {
qDebug() << "Invalid Poll URL:" << url;
} }
} }
url = login.value("login").toString();
if (m_loginUrl != url && url.isValid()) {
m_loginUrl = url;
qDebug() << "Login URL: " << m_loginUrl;
emit loginUrlChanged(m_loginUrl);
}
} }
} }
void NotesApi::updateLoginCredentials(const QJsonObject &credentials) {
QString serverAddr;
QString loginName;
QString appPassword;
if (!credentials.isEmpty()) {
serverAddr = credentials.value("server").toString();
if (serverAddr != server())
setServer(serverAddr);
loginName = credentials.value("loginName").toString();
if (loginName != username())
setUsername(loginName);
appPassword = credentials.value("appPassword").toString();
if (appPassword != password())
setPassword(appPassword);
}
qDebug() << "Login successfull for user" << loginName << "on" << serverAddr;
}
const QString NotesApi::errorMessage(int error) const { const QString NotesApi::errorMessage(int error) const {
QString message; QString message;
switch (error) { switch (error) {

View file

@ -6,6 +6,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QFile> #include <QFile>
#include <QTimer>
#include <QDebug> #include <QDebug>
#include "notesmodel.h" #include "notesmodel.h"
@ -27,6 +28,10 @@ public:
Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged) Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
bool urlValid() const { return m_url.isValid(); } bool urlValid() const { return m_url.isValid(); }
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
QString server() const;
void setServer(QString server);
Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged) Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged)
QString scheme() const { return m_url.scheme(); } QString scheme() const { return m_url.scheme(); }
void setScheme(QString scheme); void setScheme(QString scheme);
@ -61,9 +66,6 @@ public:
Q_PROPERTY(QDateTime lastSync READ lastSync NOTIFY lastSyncChanged) Q_PROPERTY(QDateTime lastSync READ lastSync NOTIFY lastSyncChanged)
QDateTime lastSync() const { return m_lastSync; } QDateTime lastSync() const { return m_lastSync; }
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
bool ready() const;
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
bool busy() const; bool busy() const;
@ -73,8 +75,8 @@ public:
bool statusMaintenance() const { return m_status_maintenance; } bool statusMaintenance() const { return m_status_maintenance; }
Q_PROPERTY(bool statusNeedsDbUpgrade READ statusNeedsDbUpgrade NOTIFY statusNeedsDbUpgradeChanged) Q_PROPERTY(bool statusNeedsDbUpgrade READ statusNeedsDbUpgrade NOTIFY statusNeedsDbUpgradeChanged)
bool statusNeedsDbUpgrade() const { return m_status_needsDbUpgrade; } bool statusNeedsDbUpgrade() const { return m_status_needsDbUpgrade; }
Q_PROPERTY(QVector<int> statusVersion READ statusVersion NOTIFY statusVersionChanged) Q_PROPERTY(QString statusVersion READ statusVersion NOTIFY statusVersionChanged)
QVector<int> statusVersion() const { return m_status_version; } QString statusVersion() const { return m_status_version; }
Q_PROPERTY(QString statusVersionString READ statusVersionString NOTIFY statusVersionStringChanged) Q_PROPERTY(QString statusVersionString READ statusVersionString NOTIFY statusVersionStringChanged)
QString statusVersionString() const { return m_status_versionstring; } QString statusVersionString() const { return m_status_versionstring; }
Q_PROPERTY(QString statusEdition READ statusEdition NOTIFY statusEditionChanged) Q_PROPERTY(QString statusEdition READ statusEdition NOTIFY statusEditionChanged)
@ -83,12 +85,8 @@ public:
QString statusProductName() const { return m_status_productname; } QString statusProductName() const { return m_status_productname; }
Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged) Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged)
bool statusExtendedSupport() const { return m_status_extendedSupport; } bool statusExtendedSupport() const { return m_status_extendedSupport; }
Q_PROPERTY(QUrl loginPollUrl READ loginPollUrl NOTIFY loginPollUrlChanged) Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged)
QUrl loginPollUrl() const { return m_login_pollUrl; } QUrl loginUrl() const { return m_loginUrl; }
Q_PROPERTY(QString loginPollToken READ loginPollToken NOTIFY loginPollTokenChanged)
QString loginPollToken() const { return m_login_pollToken; }
Q_PROPERTY(QUrl loginLoginUrl READ loginLoginUrl NOTIFY loginLoginUrlChanged)
QUrl loginLoginUrl() const { return m_login_loginUrl; }
Q_INVOKABLE void getStatus(); Q_INVOKABLE void getStatus();
Q_INVOKABLE void initiateFlowV2Login(); Q_INVOKABLE void initiateFlowV2Login();
@ -114,6 +112,7 @@ signals:
void sslVerifyChanged(bool verify); void sslVerifyChanged(bool verify);
void urlChanged(QUrl url); void urlChanged(QUrl url);
void urlValidChanged(bool valid); void urlValidChanged(bool valid);
void serverChanged(QString server);
void schemeChanged(QString scheme); void schemeChanged(QString scheme);
void hostChanged(QString host); void hostChanged(QString host);
void portChanged(int port); void portChanged(int port);
@ -123,19 +122,16 @@ signals:
void dataFileChanged(QString dataFile); void dataFileChanged(QString dataFile);
void networkAccessibleChanged(bool accessible); void networkAccessibleChanged(bool accessible);
void lastSyncChanged(QDateTime lastSync); void lastSyncChanged(QDateTime lastSync);
void readyChanged(bool ready);
void busyChanged(bool busy); void busyChanged(bool busy);
void statusInstalledChanged(bool installed); void statusInstalledChanged(bool installed);
void statusMaintenanceChanged(bool maintenance); void statusMaintenanceChanged(bool maintenance);
void statusNeedsDbUpgradeChanged(bool needsDbUpgrade); void statusNeedsDbUpgradeChanged(bool needsDbUpgrade);
void statusVersionChanged(QVector<int> version); void statusVersionChanged(QString version);
void statusVersionStringChanged(QString versionString); void statusVersionStringChanged(QString versionString);
void statusEditionChanged(QString edition); void statusEditionChanged(QString edition);
void statusProductNameChanged(QString productName); void statusProductNameChanged(QString productName);
void statusExtendedSupportChanged(bool extendedSupport); void statusExtendedSupportChanged(bool extendedSupport);
void loginPollUrlChanged(QUrl url); void loginUrlChanged(QUrl url);
void loginPollTokenChanged(QString token);
void loginLoginUrlChanged(QUrl url);
void error(int error); void error(int error);
public slots: public slots:
@ -145,6 +141,7 @@ private slots:
void requireAuthentication(QNetworkReply * reply, QAuthenticator * authenticator); void requireAuthentication(QNetworkReply * reply, QAuthenticator * authenticator);
void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible); void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible);
void replyFinished(QNetworkReply* reply); void replyFinished(QNetworkReply* reply);
void pollLoginUrl();
void sslError(QNetworkReply* reply, const QList<QSslError> &errors); void sslError(QNetworkReply* reply, const QList<QSslError> &errors);
void saveToFile(QModelIndex,QModelIndex,QVector<int>); void saveToFile(QModelIndex,QModelIndex,QVector<int>);
@ -164,17 +161,20 @@ private:
bool m_status_installed; bool m_status_installed;
bool m_status_maintenance; bool m_status_maintenance;
bool m_status_needsDbUpgrade; bool m_status_needsDbUpgrade;
QVector<int> m_status_version; QString m_status_version;
QString m_status_versionstring; QString m_status_versionstring;
QString m_status_edition; QString m_status_edition;
QString m_status_productname; QString m_status_productname;
bool m_status_extendedSupport; bool m_status_extendedSupport;
void updateLogin(const QJsonObject &login); void updateLoginFlow(const QJsonObject &login);
void updateLoginCredentials(const QJsonObject &credentials);
QVector<QNetworkReply*> m_login_replies; QVector<QNetworkReply*> m_login_replies;
QUrl m_login_pollUrl; QVector<QNetworkReply*> m_poll_replies;
QString m_login_pollToken; QTimer m_loginPollTimer;
QUrl m_login_loginUrl; QUrl m_loginUrl;
QUrl m_pollUrl;
QString m_pollToken;
}; };
#endif // NOTESAPI_H #endif // NOTESAPI_H

View file

@ -103,45 +103,49 @@
</context> </context>
<context> <context>
<name>LoginDialog</name> <name>LoginDialog</name>
<message>
<source></source>
<translation></translation>
</message>
<message> <message>
<source>Login</source> <source>Login</source>
<translation>Verbinden</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
<translation>Speichern</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Login Flow v2</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Legacy Login</source>
<translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Account name</source> <source>Account name</source>
<translation>Kontoname</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Nextcloud server</source> <source>Nextcloud server</source>
<translation>Nextcloud Server URL</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Username</source> <source>Username</source>
<translation>Benutzername</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Password</source> <source>Password</source>
<translation>Passwort</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Security</source> <source>Security</source>
<translation>Sicherheit</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source> <source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source>
<translation>&lt;strong&gt;ACHTUNG: Dein Password wird unverschlüsselt auf dem Gerät gespeichert!&lt;/strong&gt;&lt;br&gt;Erwäge ein eigenes App-Passwort zu erstellen! Öffne deine Nextcloud in einem Webbrowser und gehe zu &lt;i&gt;Einstellungen&lt;/i&gt; --&gt; &lt;i&gt;Sicherheit&lt;/i&gt;.</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Allow unencrypted connections</source>
<translation>Unverschlüsselte Verbindungen zulassen</translation>
</message>
<message>
<source></source>
<translation></translation>
</message> </message>
<message> <message>
<source>Do not check certificates</source> <source>Do not check certificates</source>
@ -151,6 +155,10 @@
<source>Enable this option to allow selfsigned certificates</source> <source>Enable this option to allow selfsigned certificates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Allow unencrypted connections</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MITLicense</name> <name>MITLicense</name>

View file

@ -103,45 +103,49 @@
</context> </context>
<context> <context>
<name>LoginDialog</name> <name>LoginDialog</name>
<message>
<source>Login</source>
<translation>Logga in</translation>
</message>
<message>
<source>Username</source>
<translation>Användarnamn</translation>
</message>
<message>
<source>Nextcloud server</source>
<translation>Nextcloud server</translation>
</message>
<message>
<source>Account name</source>
<translation>Kontonamn</translation>
</message>
<message>
<source>Security</source>
<translation>Säkerhet</translation>
</message>
<message> <message>
<source></source> <source></source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source> <source>Login</source>
<translation>&lt;strong&gt;OBS! Ditt lösenord kommer att sparas okrypterat enheten!&lt;/strong&gt;&lt;br&gt;Överväg att skapa ett dedikerat app-lösenord! Öppna din Nextcloud i en webbläsare och gå till &lt;i&gt;Inställningar&lt;/i&gt; &lt;i&gt;Säkerhet&lt;/i&gt;.</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Password</source>
<translation>Lösenord</translation>
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
<translation>Spara</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Allow unencrypted connections</source> <source>Login Flow v2</source>
<translation>Tillåt okrypterade anslutningar</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Legacy Login</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Account name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Nextcloud server</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Username</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Security</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source>
<translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Do not check certificates</source> <source>Do not check certificates</source>
@ -151,6 +155,10 @@
<source>Enable this option to allow selfsigned certificates</source> <source>Enable this option to allow selfsigned certificates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Allow unencrypted connections</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MITLicense</name> <name>MITLicense</name>

View file

@ -125,66 +125,76 @@
<context> <context>
<name>LoginDialog</name> <name>LoginDialog</name>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="55"/> <location filename="../qml/pages/LoginDialog.qml" line="133"/>
<location filename="../qml/pages/LoginDialog.qml" line="124"/> <source></source>
<translation></translation>
</message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="79"/>
<location filename="../qml/pages/LoginDialog.qml" line="175"/>
<source>Login</source> <source>Login</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="55"/> <location filename="../qml/pages/LoginDialog.qml" line="79"/>
<source>Save</source> <source>Save</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="71"/> <location filename="../qml/pages/LoginDialog.qml" line="97"/>
<source>Login Flow v2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="111"/>
<source>Legacy Login</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="121"/>
<source>Account name</source> <source>Account name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="86"/> <location filename="../qml/pages/LoginDialog.qml" line="136"/>
<source>Nextcloud server</source> <source>Nextcloud server</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="101"/> <location filename="../qml/pages/LoginDialog.qml" line="151"/>
<source>Username</source> <source>Username</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="114"/> <location filename="../qml/pages/LoginDialog.qml" line="164"/>
<source>Password</source> <source>Password</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="132"/> <location filename="../qml/pages/LoginDialog.qml" line="195"/>
<source>Security</source> <source>Security</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="139"/> <location filename="../qml/pages/LoginDialog.qml" line="202"/>
<source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source> <source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="143"/> <location filename="../qml/pages/LoginDialog.qml" line="206"/>
<source>Do not check certificates</source> <source>Do not check certificates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="144"/> <location filename="../qml/pages/LoginDialog.qml" line="207"/>
<source>Enable this option to allow selfsigned certificates</source> <source>Enable this option to allow selfsigned certificates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/LoginDialog.qml" line="150"/> <location filename="../qml/pages/LoginDialog.qml" line="213"/>
<source>Allow unencrypted connections</source> <source>Allow unencrypted connections</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="133"/>
<source></source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>MITLicense</name> <name>MITLicense</name>
@ -263,37 +273,37 @@
<context> <context>
<name>NotesApi</name> <name>NotesApi</name>
<message> <message>
<location filename="../src/notesapi.cpp" line="406"/> <location filename="../src/notesapi.cpp" line="464"/>
<source>No network connection available</source> <source>No network connection available</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="409"/> <location filename="../src/notesapi.cpp" line="467"/>
<source>Failed to communicate with the Nextcloud server</source> <source>Failed to communicate with the Nextcloud server</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="412"/> <location filename="../src/notesapi.cpp" line="470"/>
<source>An error happened while reading from the local storage</source> <source>An error happened while reading from the local storage</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="415"/> <location filename="../src/notesapi.cpp" line="473"/>
<source>An error happened while writing to the local storage</source> <source>An error happened while writing to the local storage</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="418"/> <location filename="../src/notesapi.cpp" line="476"/>
<source>An error occured while establishing an encrypted connection</source> <source>An error occured while establishing an encrypted connection</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="421"/> <location filename="../src/notesapi.cpp" line="479"/>
<source>Could not authenticate to the Nextcloud instance</source> <source>Could not authenticate to the Nextcloud instance</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="424"/> <location filename="../src/notesapi.cpp" line="482"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>