From caba45875f5dac0bfadc6189f643dd71ced9830d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:05:04 +0900 Subject: [PATCH 001/127] =?UTF-8?q?http=E3=82=A2=E3=82=AF=E3=82=BB?= =?UTF-8?q?=E3=82=B9=E6=99=82=E3=81=AE=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=B9=E5=88=A4=E5=AE=9A=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/httpaccess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http/httpaccess.cpp b/lib/http/httpaccess.cpp index f3bd9eda..caa9aa87 100644 --- a/lib/http/httpaccess.cpp +++ b/lib/http/httpaccess.cpp @@ -138,7 +138,7 @@ bool HttpAccess::Private::process(HttpReply *reply) QByteArray::fromStdString(header.second)); } reply->setRecvData(QByteArray::fromStdString(res->body)); - if (res->status == 200) { + if (res->status >= 200 && res->status <= 299) { reply->setError(HttpReply::Success); result = true; } else { From cabc51cf46dc75f03d37dc1c1ec67b1819aaaaf8 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:08:29 +0900 Subject: [PATCH 002/127] =?UTF-8?q?=E7=B0=A1=E6=98=93HTTP=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=83=90=E3=83=BC=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/simplehttpserver.cpp | 41 +++++++++++++++++++++++++++++++++++ lib/http/simplehttpserver.h | 23 ++++++++++++++++++++ lib/lib.pri | 4 +++- lib/lib.qrc | 6 +++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lib/http/simplehttpserver.cpp create mode 100644 lib/http/simplehttpserver.h create mode 100644 lib/lib.qrc diff --git a/lib/http/simplehttpserver.cpp b/lib/http/simplehttpserver.cpp new file mode 100644 index 00000000..97494414 --- /dev/null +++ b/lib/http/simplehttpserver.cpp @@ -0,0 +1,41 @@ +#include "simplehttpserver.h" +#include + +SimpleHttpServer::SimpleHttpServer(QObject *parent) : QAbstractHttpServer { parent } { } + +void SimpleHttpServer::setTimeout(int sec) +{ + QTimer::singleShot(sec * 1000, [=]() { emit timeout(); }); +} + +bool SimpleHttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) +{ + bool result = false; + QByteArray data; + QByteArray mime_type; + emit received(request, result, data, mime_type); + if (result) { + makeResponder(request, socket).write(data, mime_type, QHttpServerResponder::StatusCode::Ok); + } else { + makeResponder(request, socket).write(QHttpServerResponder::StatusCode::InternalServerError); + } + return true; +} + +QString SimpleHttpServer::convertResoucePath(const QUrl &url) +{ + QFileInfo file_info(url.path()); + return ":" + file_info.filePath(); +} + +bool SimpleHttpServer::readFile(const QString &path, QByteArray &data) +{ + QFile file(path); + if (file.open(QFile::ReadOnly)) { + data = file.readAll(); + file.close(); + return true; + } else { + return false; + } +} diff --git a/lib/http/simplehttpserver.h b/lib/http/simplehttpserver.h new file mode 100644 index 00000000..335582bc --- /dev/null +++ b/lib/http/simplehttpserver.h @@ -0,0 +1,23 @@ +#ifndef SIMPLEHTTPSERVER_H +#define SIMPLEHTTPSERVER_H + +#include + +class SimpleHttpServer : public QAbstractHttpServer +{ + Q_OBJECT +public: + explicit SimpleHttpServer(QObject *parent = nullptr); + + void setTimeout(int sec); + bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override; + + static QString convertResoucePath(const QUrl &url); + static bool readFile(const QString &path, QByteArray &data); +signals: + void received(const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type); + void timeout(); +}; + +#endif // SIMPLEHTTPSERVER_H diff --git a/lib/lib.pri b/lib/lib.pri index 54edd99d..834383e7 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -1,4 +1,4 @@ -QT += xml sql websockets +QT += xml sql websockets httpserver INCLUDEPATH += $$PWD \ $$PWD/../3rdparty/cpp-httplib @@ -87,6 +87,7 @@ SOURCES += \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ $$PWD/http/httpreply.cpp \ + $$PWD/http/simplehttpserver.cpp \ $$PWD/log/logaccess.cpp \ $$PWD/log/logmanager.cpp \ $$PWD/realtime/abstractpostselector.cpp \ @@ -194,6 +195,7 @@ HEADERS += \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ $$PWD/http/httpreply.h \ + $$PWD/http/simplehttpserver.h \ $$PWD/log/logaccess.h \ $$PWD/log/logmanager.h \ $$PWD/realtime/abstractpostselector.h \ diff --git a/lib/lib.qrc b/lib/lib.qrc new file mode 100644 index 00000000..4b6bda64 --- /dev/null +++ b/lib/lib.qrc @@ -0,0 +1,6 @@ + + + tools/oauth/oauth_fail.html + tools/oauth/oauth_success.html + + From 5f7a70a4d9ea745609c971510000a8eb569bd05a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:08:51 +0900 Subject: [PATCH 003/127] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=81=AE=E4=BD=9C=E6=88=90=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 200 ++++++++++++++++++++++++++++ lib/tools/authorization.h | 39 ++++++ lib/tools/oauth/oauth_fail.html | 8 ++ lib/tools/oauth/oauth_success.html | 8 ++ tests/oauth_test/oauth_test.pro | 13 ++ tests/oauth_test/tst_oauth_test.cpp | 86 ++++++++++++ 6 files changed, 354 insertions(+) create mode 100644 lib/tools/authorization.cpp create mode 100644 lib/tools/authorization.h create mode 100644 lib/tools/oauth/oauth_fail.html create mode 100644 lib/tools/oauth/oauth_success.html create mode 100644 tests/oauth_test/oauth_test.pro create mode 100644 tests/oauth_test/tst_oauth_test.cpp diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp new file mode 100644 index 00000000..b2feec76 --- /dev/null +++ b/lib/tools/authorization.cpp @@ -0,0 +1,200 @@ +#include "authorization.h" +#include "http/httpaccess.h" +#include "http/simplehttpserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Authorization::Authorization(QObject *parent) : QObject { parent } { } + +void Authorization::reset() +{ + m_codeChallenge.clear(); + m_codeVerifier.clear(); +} + +void Authorization::makeCodeChallenge() +{ + m_codeChallenge = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + m_codeVerifier = + QCryptographicHash::hash(m_codeChallenge, QCryptographicHash::Sha256) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); +} + +void Authorization::makeParPayload() +{ + makeCodeChallenge(); + m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString client_id = "http://localhost/tech/relog/" + "hagoromo?redirect_uri=" + + simplyEncode(redirect_uri); + QStringList scopes_supported; + scopes_supported << "offline_access" + << "openid" + // << "email" + // << "phone" + << "profile"; + QString login_hint = "ioriayane2.bsky.social"; + + QUrlQuery query; + query.addQueryItem("response_type", "code"); + query.addQueryItem("code_challenge", m_codeChallenge); + query.addQueryItem("code_challenge_method", "S256"); + query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("state", m_state); + query.addQueryItem("redirect_uri", simplyEncode(redirect_uri)); + query.addQueryItem("scope", scopes_supported.join(" ")); + query.addQueryItem("login_hint", simplyEncode(login_hint)); + + m_parPlayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); +} + +void Authorization::par() +{ + if (m_parPlayload.isEmpty()) + return; + + QString endpoint = "https://bsky.social/oauth/par"; + QNetworkRequest request((QUrl(endpoint))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QPointer alive = this; + HttpAccess *access = new HttpAccess(this); + HttpReply *reply = new HttpReply(access); + reply->setOperation(HttpReply::Operation::PostOperation); + reply->setRequest(request); + reply->setSendData(m_parPlayload); + connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + if (alive && reply != nullptr) { + qDebug().noquote() << reply->error() << reply->url().toString(); + // emit finished(success); + qDebug().noquote() << reply->contentType(); + qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + qDebug().noquote() + << "request_uri" << json_doc.object().value("request_uri").toString(); + authorization(json_doc.object().value("request_uri").toString()); + } else { + // error + qDebug() << "PAR Error"; + } + } else { + qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + } + access->deleteLater(); + }); + access->process(reply); +} + +void Authorization::authorization(const QString &request_uri) +{ + if (request_uri.isEmpty()) + return; + + QString authorization_endpoint = "https://bsky.social/oauth/authorize"; + QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString client_id = "http://localhost/tech/relog/" + "hagoromo?redirect_uri=" + + simplyEncode(redirect_uri); + + QUrl url(authorization_endpoint); + QUrlQuery query; + query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("request_uri", simplyEncode(request_uri)); + url.setQuery(query); + + qDebug().noquote() << "redirect" << url.toEncoded(); + + QDesktopServices::openUrl(url); +} + +void Authorization::startRedirectServer() +{ + SimpleHttpServer *server = new SimpleHttpServer(this); + connect(server, &SimpleHttpServer::received, this, + [=](const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type) { + SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + mime_type = "text/html"; + result = true; + + // TODO : verify url and parameters + requestToken(); + + // delete after 10 sec. + QTimer::singleShot(10 * 1000, [=]() { + emit finished(true); // temporary + server->deleteLater(); + }); + }); + connect(server, &SimpleHttpServer::timeout, this, [=]() { + // token取得に進んでたらfinishedは発火しない + qDebug().noquote() << "Authorization timeout"; + emit finished(false); + server->deleteLater(); + }); + server->setTimeout(50); // 300); + quint16 port = server->listen(QHostAddress::LocalHost, 0); + m_listenPort = QString::number(port); + + qDebug().noquote() << "Listen" << m_listenPort; +} + +void Authorization::requestToken() +{ + + // +} + +QByteArray Authorization::generateRandomValues() const +{ + QByteArray values; +#ifdef HAGOROMO_UNIT_TEST1 + const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, + 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, + 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; + for (int i = 0; i < sizeof(base); i++) { + values.append(base[i]); + } +#else + for (int i = 0; i < 32; i++) { + values.append(static_cast(QRandomGenerator::global()->bounded(256))); + } +#endif + return values; +} + +QString Authorization::simplyEncode(QString text) const +{ + return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); +} + +QByteArray Authorization::ParPlayload() const +{ + return m_parPlayload; +} + +QByteArray Authorization::codeChallenge() const +{ + return m_codeChallenge; +} + +QByteArray Authorization::codeVerifier() const +{ + return m_codeVerifier; +} diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h new file mode 100644 index 00000000..7f1168fd --- /dev/null +++ b/lib/tools/authorization.h @@ -0,0 +1,39 @@ +#ifndef AUTHORIZATION_H +#define AUTHORIZATION_H + +#include + +class Authorization : public QObject +{ + Q_OBJECT +public: + explicit Authorization(QObject *parent = nullptr); + + void reset(); + void makeCodeChallenge(); + void makeParPayload(); + void par(); + void authorization(const QString &request_uri); + void startRedirectServer(); + void requestToken(); + + QByteArray codeVerifier() const; + QByteArray codeChallenge() const; + QByteArray ParPlayload() const; + +signals: + void finished(bool success); + +private: + QByteArray generateRandomValues() const; + QString simplyEncode(QString text) const; + + QByteArray m_codeChallenge; + QByteArray m_codeVerifier; + QByteArray m_state; + QByteArray m_parPlayload; + + QString m_listenPort; +}; + +#endif // AUTHORIZATION_H diff --git a/lib/tools/oauth/oauth_fail.html b/lib/tools/oauth/oauth_fail.html new file mode 100644 index 00000000..2e19f74d --- /dev/null +++ b/lib/tools/oauth/oauth_fail.html @@ -0,0 +1,8 @@ + + + Hagoromo + + + Authorization fail. + + diff --git a/lib/tools/oauth/oauth_success.html b/lib/tools/oauth/oauth_success.html new file mode 100644 index 00000000..b04c54c8 --- /dev/null +++ b/lib/tools/oauth/oauth_success.html @@ -0,0 +1,8 @@ + + + Hagoromo + + + Authorization success. + + diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro new file mode 100644 index 00000000..59229caa --- /dev/null +++ b/tests/oauth_test/oauth_test.pro @@ -0,0 +1,13 @@ +QT += testlib httpserver gui + +CONFIG += qt console warn_on depend_includepath testcase +CONFIG -= app_bundle + +TEMPLATE = app +DEFINES += HAGOROMO_UNIT_TEST + +SOURCES += tst_oauth_test.cpp + +include(../common/common.pri) +include(../../lib/lib.pri) +include(../../openssl/openssl.pri) diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp new file mode 100644 index 00000000..c3a9c604 --- /dev/null +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "tools/authorization.h" + +class oauth_test : public QObject +{ + Q_OBJECT + +public: + oauth_test(); + ~oauth_test(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void test_oauth_process(); + void test_oauth_server(); +}; + +oauth_test::oauth_test() { } + +oauth_test::~oauth_test() { } + +void oauth_test::initTestCase() { } + +void oauth_test::cleanupTestCase() { } + +void oauth_test::test_oauth_process() +{ + QString code_challenge; + + const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, + 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, + 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; + QByteArray base_ba; //(QByteArray::fromRawData(static_cast(base), sizeof(base))); + for (int i = 0; i < sizeof(base); i++) { + base_ba.append(base[i]); + } + qDebug() << sizeof(base) << base_ba.size() + << base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QString msg; + QByteArray sha256 = QCryptographicHash::hash( + base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals), + QCryptographicHash::Sha256); + for (const auto s : sha256) { + msg += QString::number(static_cast(s)) + ", "; + } + qDebug() << msg; + qDebug() << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + Authorization oauth; + oauth.makeCodeChallenge(); + qDebug() << "codeChallenge" << oauth.codeChallenge(); + qDebug() << "codeVerifier" << oauth.codeVerifier(); + + // QVERIFY(oauth.codeChallenge() + // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + // QVERIFY(oauth.codeVerifier() + // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + + oauth.makeParPayload(); + qDebug() << "ParPlayload" << oauth.ParPlayload(); + + oauth.par(); +} + +void oauth_test::test_oauth_server() +{ + + Authorization oauth; + + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.startRedirectServer(); + spy.wait(60 * 1000); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } +} + +QTEST_MAIN(oauth_test) + +#include "tst_oauth_test.moc" From 4b34c49021d34bb594e8e57ee058de7a7bee64a9 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 23 Aug 2024 01:30:13 +0900 Subject: [PATCH 004/127] =?UTF-8?q?token=E8=A6=81=E6=B1=82=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 117 ++++++++++++++++++++++++++++++------ lib/tools/authorization.h | 15 ++++- 2 files changed, 110 insertions(+), 22 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index b2feec76..2c093822 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -18,8 +18,27 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + m_redirectUri.clear(); + m_clientId.clear(); m_codeChallenge.clear(); m_codeVerifier.clear(); + m_state.clear(); + m_parPayload.clear(); + m_requestTokenPayload.clear(); +} + +void Authorization::makeClientId() +{ + QString port; + if (!m_listenPort.isEmpty()) { + port.append(":"); + port.append(m_listenPort); + } + m_redirectUri.append("http://127.0.0.1"); + m_redirectUri.append(port); + m_redirectUri.append("/tech/relog/hagoromo/oauth-callback"); + m_clientId.append("http://localhost/tech/relog/hagoromo?redirect_uri="); + m_clientId.append(simplyEncode(m_redirectUri)); } void Authorization::makeCodeChallenge() @@ -33,14 +52,11 @@ void Authorization::makeCodeChallenge() void Authorization::makeParPayload() { + makeClientId(); makeCodeChallenge(); m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; - QString client_id = "http://localhost/tech/relog/" - "hagoromo?redirect_uri=" - + simplyEncode(redirect_uri); QStringList scopes_supported; scopes_supported << "offline_access" << "openid" @@ -53,18 +69,18 @@ void Authorization::makeParPayload() query.addQueryItem("response_type", "code"); query.addQueryItem("code_challenge", m_codeChallenge); query.addQueryItem("code_challenge_method", "S256"); - query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("state", m_state); - query.addQueryItem("redirect_uri", simplyEncode(redirect_uri)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); query.addQueryItem("scope", scopes_supported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); - m_parPlayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); } void Authorization::par() { - if (m_parPlayload.isEmpty()) + if (m_parPayload.isEmpty()) return; QString endpoint = "https://bsky.social/oauth/par"; @@ -76,13 +92,13 @@ void Authorization::par() HttpReply *reply = new HttpReply(access); reply->setOperation(HttpReply::Operation::PostOperation); reply->setRequest(request); - reply->setSendData(m_parPlayload); + reply->setSendData(m_parPayload); connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { if (alive && reply != nullptr) { qDebug().noquote() << reply->error() << reply->url().toString(); - // emit finished(success); qDebug().noquote() << reply->contentType(); qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); @@ -92,6 +108,7 @@ void Authorization::par() } else { // error qDebug() << "PAR Error"; + emit finished(false); } } else { qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; @@ -129,12 +146,26 @@ void Authorization::startRedirectServer() connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { - SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") + && request.query().hasQueryItem("code")) { + // authorize + QString state = request.query().queryItemValue("state"); + result = (state.toUtf8() == m_state); + if (result) { + m_code = request.query().queryItemValue("code").toUtf8(); + // requestToken(); + } else { + qDebug().noquote() << "Unknown state in authorization redirect :" << state; + emit finished(false); + m_code.clear(); + } + } + if (result) { + SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + } else { + SimpleHttpServer::readFile(":/tools/oauth/oauth_fail.html", data); + } mime_type = "text/html"; - result = true; - - // TODO : verify url and parameters - requestToken(); // delete after 10 sec. QTimer::singleShot(10 * 1000, [=]() { @@ -155,10 +186,58 @@ void Authorization::startRedirectServer() qDebug().noquote() << "Listen" << m_listenPort; } -void Authorization::requestToken() +void Authorization::makeRequestTokenPayload() { + QUrlQuery query; + + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", m_code); + query.addQueryItem("code_verifier", m_codeVerifier); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + + m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); +} + +bool Authorization::requestToken() +{ + + QString endpoint = "https://bsky.social/oauth/token"; + QNetworkRequest request((QUrl(endpoint))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader(QByteArray("DPoP"), QByteArray("")); + + QPointer alive = this; + HttpAccess *access = new HttpAccess(this); + HttpReply *reply = new HttpReply(access); + reply->setOperation(HttpReply::Operation::PostOperation); + reply->setRequest(request); + // reply->setSendData(m_parPayload); + connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + if (alive && reply != nullptr) { + qDebug().noquote() << reply->error() << reply->url().toString(); + + qDebug().noquote() << reply->contentType(); + qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + qDebug().noquote() + << "request_uri" << json_doc.object().value("request_uri").toString(); + } else { + // error + qDebug() << "Request token Error"; + } + } else { + qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + } + access->deleteLater(); + }); + qDebug() << "request token 1"; + access->process(reply); + qDebug() << "request token 2"; - // + return true; } QByteArray Authorization::generateRandomValues() const @@ -184,9 +263,9 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } -QByteArray Authorization::ParPlayload() const +QByteArray Authorization::ParPayload() const { - return m_parPlayload; + return m_parPayload; } QByteArray Authorization::codeChallenge() const diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 7f1168fd..1c7fab34 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -10,16 +10,19 @@ class Authorization : public QObject explicit Authorization(QObject *parent = nullptr); void reset(); + void makeClientId(); void makeCodeChallenge(); void makeParPayload(); void par(); void authorization(const QString &request_uri); void startRedirectServer(); - void requestToken(); + + void makeRequestTokenPayload(); + bool requestToken(); QByteArray codeVerifier() const; QByteArray codeChallenge() const; - QByteArray ParPlayload() const; + QByteArray ParPayload() const; signals: void finished(bool success); @@ -28,10 +31,16 @@ class Authorization : public QObject QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; + QString m_redirectUri; + QString m_clientId; + // par QByteArray m_codeChallenge; QByteArray m_codeVerifier; QByteArray m_state; - QByteArray m_parPlayload; + QByteArray m_parPayload; + // request token + QByteArray m_code; + QByteArray m_requestTokenPayload; QString m_listenPort; }; From 0b7b33376a1db76ac6302c94b2930d4ce1b20c30 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 24 Aug 2024 10:02:21 +0900 Subject: [PATCH 005/127] =?UTF-8?q?jwt=E5=AF=BE=E5=BF=9C=E3=81=AE=E6=BA=96?= =?UTF-8?q?=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lib.pri | 9 +++ lib/tools/es256.cpp | 9 +++ lib/tools/es256.h | 13 ++++ lib/tools/jsonwebtoken.cpp | 107 ++++++++++++++++++++++++++++ lib/tools/jsonwebtoken.h | 15 ++++ openssl/openssl.pri | 2 + tests/oauth_test/tst_oauth_test.cpp | 47 +++++++++--- tests/tests.pro | 1 + 8 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 lib/tools/es256.cpp create mode 100644 lib/tools/es256.h create mode 100644 lib/tools/jsonwebtoken.cpp create mode 100644 lib/tools/jsonwebtoken.h diff --git a/lib/lib.pri b/lib/lib.pri index 834383e7..9b30975c 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -99,11 +99,14 @@ SOURCES += \ $$PWD/realtime/notpostselector.cpp \ $$PWD/realtime/orpostselector.cpp \ $$PWD/realtime/xorpostselector.cpp \ + $$PWD/tools/authorization.cpp \ $$PWD/tools/base32.cpp \ $$PWD/tools/cardecoder.cpp \ $$PWD/tools/chatlogsubscriber.cpp \ $$PWD/tools/configurablelabels.cpp \ + $$PWD/tools/es256.cpp \ $$PWD/tools/imagecompressor.cpp \ + $$PWD/tools/jsonwebtoken.cpp \ $$PWD/tools/labelerprovider.cpp \ $$PWD/tools/leb128.cpp \ $$PWD/tools/listitemscache.cpp \ @@ -208,14 +211,20 @@ HEADERS += \ $$PWD/realtime/orpostselector.h \ $$PWD/realtime/xorpostselector.h \ $$PWD/search/search.h \ + $$PWD/tools/authorization.h \ $$PWD/tools/base32.h \ $$PWD/tools/cardecoder.h \ $$PWD/tools/chatlogsubscriber.h \ $$PWD/tools/configurablelabels.h \ + $$PWD/tools/es256.h \ $$PWD/tools/imagecompressor.h \ + $$PWD/tools/jsonwebtoken.h \ $$PWD/tools/labelerprovider.h \ $$PWD/tools/leb128.h \ $$PWD/tools/listitemscache.h \ $$PWD/tools/opengraphprotocol.h \ $$PWD/tools/pinnedpostcache.h \ $$PWD/tools/qstringex.h + +RESOURCES += \ + $$PWD/lib.qrc diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp new file mode 100644 index 00000000..7ac4acb5 --- /dev/null +++ b/lib/tools/es256.cpp @@ -0,0 +1,9 @@ +#include "es256.h" + +Es256::Es256() { } + +Es256 *Es256::getInstance() +{ + static Es256 instance; + return &instance; +} diff --git a/lib/tools/es256.h b/lib/tools/es256.h new file mode 100644 index 00000000..c8c64bfd --- /dev/null +++ b/lib/tools/es256.h @@ -0,0 +1,13 @@ +#ifndef ES256_H +#define ES256_H + +class Es256 +{ + Es256(); + ~Es256() { } + +public: + static Es256 *getInstance(); +}; + +#endif // ES256_H diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp new file mode 100644 index 00000000..09da8cb5 --- /dev/null +++ b/lib/tools/jsonwebtoken.cpp @@ -0,0 +1,107 @@ +#include "jsonwebtoken.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QString JsonWebToken::generate(const QString &endpoint) +{ + // ヘッダー + QJsonObject header; + header["alg"] = "ES256"; + header["typ"] = "dpop+jwt"; + QJsonObject jwk; + jwk["kty"] = "EC"; + jwk["x"] = "TUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUVzQlVDaE9vbVB2UXhnMlZkZ05RNnc2NE1LS3hhVmQ" + "vRQ"; + jwk["y"] = "MVg2Y05QMU5DMFA4TzYrNVVsYm1LT1BGYlVsYk5FTGpFSVJtY3VraGlrMTFaSnp4UXY5dFJ3"; + jwk["crv"] = "P-256"; + header["jwk"] = jwk; + QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); + QByteArray headerBase64 = JsonWebToken::base64UrlEncode(headerJson); + + // ペイロード + QJsonObject payload; + payload["sub"] = "1234567890"; // ユーザーIDなど + payload["name"] = "John Doe"; + payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 + QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); + QByteArray payloadBase64 = JsonWebToken::base64UrlEncode(payloadJson); + + // 署名 + QByteArray message = headerBase64 + "." + payloadBase64; + QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); + QByteArray signatureBase64 = JsonWebToken::base64UrlEncode(signature); + + // JWTトークン + QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; + return jwt; +} + +QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyPath) +{ + QByteArray signature; + + // OpenSSLで秘密鍵を読み込む + FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + return signature; + } + + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + + if (!pkey) { + qWarning() << "Failed to read private key"; + return signature; + } + + // ECDSA署名を生成 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) <= 0) { + qWarning() << "Failed to initialize digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { + qWarning() << "Failed to update digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + size_t sigLen; + if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { + qWarning() << "Failed to finalize digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + signature.resize(sigLen); + if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) + <= 0) { + qWarning() << "Failed to finalize digest sign"; + } + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + + return signature; +} + +QByteArray JsonWebToken::base64UrlEncode(const QByteArray &data) +{ + QByteArray encoded = data.toBase64(); + encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + return encoded; +} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h new file mode 100644 index 00000000..0a53d103 --- /dev/null +++ b/lib/tools/jsonwebtoken.h @@ -0,0 +1,15 @@ +#ifndef JSONWEBTOKEN_H +#define JSONWEBTOKEN_H + +#include + +class JsonWebToken +{ +public: + static QString generate(const QString &endpoint); + + static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); + static QByteArray base64UrlEncode(const QByteArray &data); +}; + +#endif // JSONWEBTOKEN_H diff --git a/openssl/openssl.pri b/openssl/openssl.pri index ec09ce0e..70fed754 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -4,6 +4,8 @@ open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSL open_ssl_dir=$$clean_path($$open_ssl_dir) win32:{ + SOURCES += $${open_ssl_dir}/src/ms/applink.c + open_ssl_dir=$${open_ssl_dir}/Win_x64 LIBS += $${open_ssl_dir}/lib/libssl.lib \ diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index c3a9c604..410555df 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -2,6 +2,8 @@ #include #include "tools/authorization.h" +#include "tools/jsonwebtoken.h" +#include "http/simplehttpserver.h" class oauth_test : public QObject { @@ -16,9 +18,23 @@ private slots: void cleanupTestCase(); void test_oauth_process(); void test_oauth_server(); + void test_jwt(); + +private: + SimpleHttpServer m_server; + quint16 m_listenPort; }; -oauth_test::oauth_test() { } +oauth_test::oauth_test() +{ + + m_listenPort = m_server.listen(QHostAddress::LocalHost, 0); + connect(&m_server, &SimpleHttpServer::received, this, + [=](const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type) { + // + }); +} oauth_test::~oauth_test() { } @@ -61,9 +77,9 @@ void oauth_test::test_oauth_process() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); oauth.makeParPayload(); - qDebug() << "ParPlayload" << oauth.ParPlayload(); + qDebug() << "ParPlayload" << oauth.ParPayload(); - oauth.par(); + // oauth.par(); } void oauth_test::test_oauth_server() @@ -71,14 +87,23 @@ void oauth_test::test_oauth_server() Authorization oauth; - { - QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - oauth.startRedirectServer(); - spy.wait(60 * 1000); - QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - QList arguments = spy.takeFirst(); - QVERIFY(arguments.at(0).toBool()); - } + // { + // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + // oauth.startRedirectServer(); + // spy.wait(60 * 1000); + // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + // QList arguments = spy.takeFirst(); + // QVERIFY(arguments.at(0).toBool()); + // } +} + +void oauth_test::test_jwt() +{ + QString jwt = JsonWebToken::generate("https://hoge"); + + qDebug().noquote() << jwt; + + QVERIFY(true); } QTEST_MAIN(oauth_test) diff --git a/tests/tests.pro b/tests/tests.pro index f2d16827..cc215ebb 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -7,6 +7,7 @@ SUBDIRS += \ hagoromo_test2 \ http_test \ log_test \ + oauth_test \ realtime_test \ search_test \ tools_test From 0fce64e57cb519128edd92e5b73cdf5f2c358588 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 24 Aug 2024 17:23:31 +0900 Subject: [PATCH 006/127] =?UTF-8?q?jwk=E3=82=92=E7=94=9F=E6=88=90=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 134 +++++++++++++++++++++++++++++++------ lib/tools/jsonwebtoken.h | 1 - 2 files changed, 113 insertions(+), 22 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 09da8cb5..fbc05447 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -9,22 +9,82 @@ #include #include #include +#include + +QByteArray base64UrlEncode(const QByteArray &data) +{ + QByteArray encoded = data.toBase64(); + encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + return encoded; +} + +QJsonObject createJwk(EVP_PKEY *pkey) +{ + QJsonObject jwk; + + // ECキーの抽出 + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); + if (!ec_key) { + qWarning() << "Failed to get EC key from EVP_PKEY"; + return jwk; + } + + const EC_GROUP *group = EC_KEY_get0_group(ec_key); + const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + + // X, Y座標の抽出 + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + qWarning() << "Failed to get affine coordinates"; + BN_free(x); + BN_free(y); + return jwk; + } + + // X, Y座標をBase64URLエンコード + QByteArray xCoord = base64UrlEncode( + QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), BN_num_bytes(x))); + QByteArray yCoord = base64UrlEncode( + QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), BN_num_bytes(y))); + + jwk["kty"] = "EC"; // Key Type + jwk["crv"] = "P-256"; // Curve + jwk["x"] = QString::fromUtf8(xCoord); + jwk["y"] = QString::fromUtf8(yCoord); + + BN_free(x); + BN_free(y); + + return jwk; +} QString JsonWebToken::generate(const QString &endpoint) { + // OpenSSLで秘密鍵を読み込む + FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + return QString(); + } + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + if (!pkey) { + qWarning() << "Failed to read private key"; + return QString(); + } + + // JWKを生成 + QJsonObject jwk = createJwk(pkey); + EVP_PKEY_free(pkey); + // ヘッダー QJsonObject header; header["alg"] = "ES256"; header["typ"] = "dpop+jwt"; - QJsonObject jwk; - jwk["kty"] = "EC"; - jwk["x"] = "TUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUVzQlVDaE9vbVB2UXhnMlZkZ05RNnc2NE1LS3hhVmQ" - "vRQ"; - jwk["y"] = "MVg2Y05QMU5DMFA4TzYrNVVsYm1LT1BGYlVsYk5FTGpFSVJtY3VraGlrMTFaSnp4UXY5dFJ3"; - jwk["crv"] = "P-256"; header["jwk"] = jwk; QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); - QByteArray headerBase64 = JsonWebToken::base64UrlEncode(headerJson); + QByteArray headerBase64 = base64UrlEncode(headerJson); // ペイロード QJsonObject payload; @@ -32,12 +92,12 @@ QString JsonWebToken::generate(const QString &endpoint) payload["name"] = "John Doe"; payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); - QByteArray payloadBase64 = JsonWebToken::base64UrlEncode(payloadJson); + QByteArray payloadBase64 = base64UrlEncode(payloadJson); // 署名 QByteArray message = headerBase64 + "." + payloadBase64; QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); - QByteArray signatureBase64 = JsonWebToken::base64UrlEncode(signature); + QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; @@ -52,7 +112,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); if (!fp) { qWarning() << "Failed to open private key file"; - return signature; + return QByteArray(); } EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); @@ -60,7 +120,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP if (!pkey) { qWarning() << "Failed to read private key"; - return signature; + return QByteArray(); } // ECDSA署名を生成 @@ -69,14 +129,14 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to initialize digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { qWarning() << "Failed to update digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } size_t sigLen; @@ -84,7 +144,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to finalize digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } signature.resize(sigLen); @@ -93,15 +153,47 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to finalize digest sign"; } + //////// + /// + + /// Convert from ASN1 to DER format + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); + if (ec_key == NULL) + return QByteArray(); + + int degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key)); + EC_KEY_free(ec_key); + + /* Get the sig from the DER encoded version. */ + const unsigned char *temp = reinterpret_cast(signature.constData()); + ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); + if (ec_sig == NULL) + return QByteArray(); + + const BIGNUM *ec_sig_r = NULL; + const BIGNUM *ec_sig_s = NULL; + ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); + int r_len = BN_num_bytes(ec_sig_r); + int s_len = BN_num_bytes(ec_sig_s); + int bn_len = (degree + 7) / 8; + if ((r_len > bn_len) || (s_len > bn_len)) + return QByteArray(); + + /// Attention!!! std::vector from C++17, you can use unsigned char* but this C-style + /// char's array need allocate zeros, how I member it's memset function. Or use + /// std::vector. + std::vector raw_buf(static_cast(bn_len) * 2); + BN_bn2bin(ec_sig_r, reinterpret_cast(raw_buf.data()) + bn_len - r_len); + BN_bn2bin(ec_sig_s, reinterpret_cast(raw_buf.data()) + raw_buf.size() - s_len); + + std::string str(reinterpret_cast(raw_buf.data()), raw_buf.size()); + + qDebug().noquote() << "sig" << base64UrlEncode(QByteArray::fromStdString(str)); + /// + /// //// + EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); return signature; } - -QByteArray JsonWebToken::base64UrlEncode(const QByteArray &data) -{ - QByteArray encoded = data.toBase64(); - encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); - return encoded; -} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index 0a53d103..47d76e4b 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -9,7 +9,6 @@ class JsonWebToken static QString generate(const QString &endpoint); static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); - static QByteArray base64UrlEncode(const QByteArray &data); }; #endif // JSONWEBTOKEN_H From 37e5b662ab249238fef43873e617658b25fc5657 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 00:15:10 +0900 Subject: [PATCH 007/127] =?UTF-8?q?jwt=E3=81=AE=E7=BD=B2=E5=90=8D=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 51 ++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index fbc05447..562c63c1 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -18,6 +18,14 @@ QByteArray base64UrlEncode(const QByteArray &data) return encoded; } +QByteArray bn2ba(const BIGNUM *bn) +{ + QByteArray ba; + ba.resize(BN_num_bytes(bn)); + BN_bn2bin(bn, reinterpret_cast(ba.data())); + return ba; +} + QJsonObject createJwk(EVP_PKEY *pkey) { QJsonObject jwk; @@ -151,49 +159,32 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) <= 0) { qWarning() << "Failed to finalize digest sign"; - } - - //////// - /// - - /// Convert from ASN1 to DER format - EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); - if (ec_key == NULL) return QByteArray(); + } - int degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key)); - EC_KEY_free(ec_key); - - /* Get the sig from the DER encoded version. */ + // Convert DER to IEEE P1363 + const int ec_sig_len = 64; // ES256のときの値 const unsigned char *temp = reinterpret_cast(signature.constData()); ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); - if (ec_sig == NULL) + if (ec_sig == NULL) { return QByteArray(); + } const BIGNUM *ec_sig_r = NULL; const BIGNUM *ec_sig_s = NULL; ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); - int r_len = BN_num_bytes(ec_sig_r); - int s_len = BN_num_bytes(ec_sig_s); - int bn_len = (degree + 7) / 8; - if ((r_len > bn_len) || (s_len > bn_len)) - return QByteArray(); - /// Attention!!! std::vector from C++17, you can use unsigned char* but this C-style - /// char's array need allocate zeros, how I member it's memset function. Or use - /// std::vector. - std::vector raw_buf(static_cast(bn_len) * 2); - BN_bn2bin(ec_sig_r, reinterpret_cast(raw_buf.data()) + bn_len - r_len); - BN_bn2bin(ec_sig_s, reinterpret_cast(raw_buf.data()) + raw_buf.size() - s_len); + QByteArray rr = bn2ba(ec_sig_r); + QByteArray ss = bn2ba(ec_sig_s); - std::string str(reinterpret_cast(raw_buf.data()), raw_buf.size()); - - qDebug().noquote() << "sig" << base64UrlEncode(QByteArray::fromStdString(str)); - /// - /// //// + if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { + return QByteArray(); + } + rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); + ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return rr + ss; } From eb069aa10b9733aba66e492d78c10d85f2481c8f Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 02:18:02 +0900 Subject: [PATCH 008/127] =?UTF-8?q?jwt=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 12 ++++---- lib/tools/jsonwebtoken.h | 2 +- tests/oauth_test/tst_oauth_test.cpp | 46 ++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 562c63c1..271dc652 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -13,8 +13,8 @@ QByteArray base64UrlEncode(const QByteArray &data) { - QByteArray encoded = data.toBase64(); - encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + QByteArray encoded = + data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); return encoded; } @@ -67,19 +67,19 @@ QJsonObject createJwk(EVP_PKEY *pkey) return jwk; } -QString JsonWebToken::generate(const QString &endpoint) +QByteArray JsonWebToken::generate(const QString &endpoint) { // OpenSSLで秘密鍵を読み込む FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); if (!fp) { qWarning() << "Failed to open private key file"; - return QString(); + return QByteArray(); } EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); fclose(fp); if (!pkey) { qWarning() << "Failed to read private key"; - return QString(); + return QByteArray(); } // JWKを生成 @@ -108,7 +108,7 @@ QString JsonWebToken::generate(const QString &endpoint) QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン - QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; + QByteArray jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; return jwt; } diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index 47d76e4b..b6514f3a 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,7 @@ class JsonWebToken { public: - static QString generate(const QString &endpoint); + static QByteArray generate(const QString &endpoint); static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); }; diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 410555df..2bc508dd 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -1,6 +1,12 @@ #include #include +#include +#include +#include +#include +#include + #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "http/simplehttpserver.h" @@ -23,6 +29,8 @@ private slots: private: SimpleHttpServer m_server; quint16 m_listenPort; + + void verify_jwt(const QByteArray &jwt); }; oauth_test::oauth_test() @@ -99,13 +107,49 @@ void oauth_test::test_oauth_server() void oauth_test::test_jwt() { - QString jwt = JsonWebToken::generate("https://hoge"); + QByteArray jwt = JsonWebToken::generate("https://hoge"); qDebug().noquote() << jwt; + verify_jwt(jwt); + QVERIFY(true); } +void oauth_test::verify_jwt(const QByteArray &jwt) +{ + const QByteArrayList jwt_parts = jwt.split('.'); + QVERIFY2(jwt_parts.length() == 3, jwt); + + QByteArray sig = QByteArray::fromBase64( + jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + QByteArray sig_rr = sig.left(32); + QByteArray sig_ss = sig.right(32); + + BIGNUM *ec_sig_r = NULL; + BIGNUM *ec_sig_s = NULL; + ec_sig_r = BN_bin2bn(reinterpret_cast(sig_rr.constData()), + sig_rr.length(), NULL); + ec_sig_s = BN_bin2bn(reinterpret_cast(sig_ss.constData()), + sig_ss.length(), NULL); + QVERIFY(ec_sig_r != NULL); + QVERIFY(ec_sig_s != NULL); + + ECDSA_SIG *ec_sig = ECDSA_SIG_new(); + + QVERIFY(ECDSA_SIG_set0(ec_sig, ec_sig_r, ec_sig_s) == 1); + + unsigned char *der_sig; + int sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); + QVERIFY(sig_len > 0); + + BN_free(ec_sig_r); + BN_free(ec_sig_s); + OPENSSL_free(der_sig); + ECDSA_SIG_free(ec_sig); +} + QTEST_MAIN(oauth_test) #include "tst_oauth_test.moc" From c694cbbe21231a18e8d54c18ec0e0f6d0da48226 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 23:32:28 +0900 Subject: [PATCH 009/127] =?UTF-8?q?=E5=8D=98=E4=BD=93=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=A7jwt=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/oauth_test/tst_oauth_test.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 2bc508dd..66b951c7 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -121,6 +121,7 @@ void oauth_test::verify_jwt(const QByteArray &jwt) const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); + QByteArray message = jwt_parts[0] + '.' + jwt_parts[1]; QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); @@ -137,12 +138,26 @@ void oauth_test::verify_jwt(const QByteArray &jwt) QVERIFY(ec_sig_s != NULL); ECDSA_SIG *ec_sig = ECDSA_SIG_new(); - QVERIFY(ECDSA_SIG_set0(ec_sig, ec_sig_r, ec_sig_s) == 1); - unsigned char *der_sig; - int sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); - QVERIFY(sig_len > 0); + unsigned char *der_sig = NULL; + int der_sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); + QVERIFY(der_sig_len > 0); + + FILE *fp = fopen("c:\\temp\\public_key.pem", "r"); + QVERIFY(fp != NULL); + EVP_PKEY *pkey = PEM_read_PUBKEY(fp, nullptr, nullptr, nullptr); + fclose(fp); + QVERIFY(pkey != NULL); + + // ECDSA署名の検証 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + QVERIFY(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) > 0); + QVERIFY(EVP_DigestVerifyUpdate(mdctx, message.constData(), message.length()) > 0); + QVERIFY(EVP_DigestVerifyFinal(mdctx, der_sig, der_sig_len) > 0); + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); BN_free(ec_sig_r); BN_free(ec_sig_s); From 1dd73236f28463bbfa54bf199542d7a6db56ef6e Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 28 Aug 2024 00:41:02 +0900 Subject: [PATCH 010/127] =?UTF-8?q?=E7=BD=B2=E5=90=8D=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/es256.cpp | 202 +++++++++++++++++++++++++++- lib/tools/es256.h | 24 +++- lib/tools/jsonwebtoken.cpp | 155 ++------------------- lib/tools/jsonwebtoken.h | 2 - tests/oauth_test/tst_oauth_test.cpp | 58 ++++++-- 5 files changed, 280 insertions(+), 161 deletions(-) diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp index 7ac4acb5..c17b21f2 100644 --- a/lib/tools/es256.cpp +++ b/lib/tools/es256.cpp @@ -1,9 +1,209 @@ #include "es256.h" -Es256::Es256() { } +#include +#include +#include +#include +#include + +Es256::Es256() : m_pKey(nullptr) +{ + qDebug().noquote() << this << "Es256()"; +} + +Es256::~Es256() +{ + qDebug().noquote() << this << "~Es256()"; +} Es256 *Es256::getInstance() { static Es256 instance; return &instance; } + +void Es256::clear() +{ + if (m_pKey != nullptr) { + EVP_PKEY_free(m_pKey); + m_pKey = nullptr; + } +} + +QByteArray Es256::sign(const QByteArray &data) +{ + loadKey(); + if (m_pKey == nullptr) { + return QByteArray(); + } + + QByteArray der_sig; + + // ECDSA署名を生成 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, m_pKey) <= 0) { + qWarning() << "Failed to initialize digest sign"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { + qWarning() << "Failed to update digest sign"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + size_t sigLen; + if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { + qWarning() << "Failed to finalize digest sign 1"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + der_sig.resize(sigLen); + if (EVP_DigestSignFinal(mdctx, reinterpret_cast(der_sig.data()), &sigLen) + <= 0) { + qWarning() << "Failed to finalize digest sign 2"; + return QByteArray(); + } + + // Convert DER to IEEE P1363 + const int ec_sig_len = 64; // ES256のときの値 + const unsigned char *temp = reinterpret_cast(der_sig.constData()); + ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, der_sig.length()); + if (ec_sig == NULL) { + return QByteArray(); + } + + const BIGNUM *ec_sig_r = NULL; + const BIGNUM *ec_sig_s = NULL; + ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); + + QByteArray rr = bn2ba(ec_sig_r); + QByteArray ss = bn2ba(ec_sig_s); + + if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { + return QByteArray(); + } + rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); + ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); + + EVP_MD_CTX_free(mdctx); + + return rr + ss; +} + +void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) +{ + loadKey(); + + // ECキーの抽出 + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(m_pKey); + if (!ec_key) { + qWarning() << "Failed to get EC key from EVP_PKEY"; + } + + const EC_GROUP *group = EC_KEY_get0_group(ec_key); + const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + + // X, Y座標の抽出 + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + qWarning() << "Failed to get affine coordinates"; + x_coord.clear(); + y_coord.clear(); + } else { + // X, Y座標をBase64URLエンコード + x_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), + BN_num_bytes(x)) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + y_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), + BN_num_bytes(y)) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + } + BN_free(x); + BN_free(y); +} + +void Es256::loadKey() +{ + if (m_pKey != nullptr) { + return; + } + + QString path = + QString("%1/%2/%3%4/private_key.pem") + .arg(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()) + .arg( +#if defined(HAGOROMO_UNIT_TEST) + QStringLiteral("_unittest") +#elif defined(QT_DEBUG) + QStringLiteral("_debug") +#else + QString() +#endif + ); + + if (!QFile::exists(path)) { + createPrivateKey(path); + } else { + FILE *fp = fopen(path.toStdString().c_str(), "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + } else { + m_pKey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + } + } +} + +void Es256::createPrivateKey(const QString &path) +{ + if (m_pKey != nullptr) { + return; + } + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr); + if (pctx == nullptr) { + return; + } + + if (EVP_PKEY_keygen_init(pctx) <= 0) { + qWarning() << "Failed to initialize keygen context"; + } else if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) <= 0) { + qWarning() << "Failed to set curve parameter"; + } else if (EVP_PKEY_keygen(pctx, &m_pKey) <= 0) { + qWarning() << "Failed to generate EC key"; + m_pKey = nullptr; + } else { + FILE *fp = fopen(path.toStdString().c_str(), "w"); + if (!fp) { + qWarning() << "Failed to open private key file"; + } else { + if (PEM_write_PrivateKey(fp, m_pKey, nullptr, nullptr, 0, nullptr, nullptr) <= 0) { + qWarning() << "Failed to write private key to file"; + } + fclose(fp); + } + } + EVP_PKEY_CTX_free(pctx); +} + +QByteArray Es256::bn2ba(const BIGNUM *bn) +{ + QByteArray ba; + ba.resize(BN_num_bytes(bn)); + BN_bn2bin(bn, reinterpret_cast(ba.data())); + return ba; +} + +EVP_PKEY *Es256::pKey() const +{ + return m_pKey; +} diff --git a/lib/tools/es256.h b/lib/tools/es256.h index c8c64bfd..38879ae4 100644 --- a/lib/tools/es256.h +++ b/lib/tools/es256.h @@ -1,13 +1,33 @@ #ifndef ES256_H #define ES256_H +#include + +#include +#include +#include +#include +#include + class Es256 { - Es256(); - ~Es256() { } + explicit Es256(); + ~Es256(); public: static Es256 *getInstance(); + + void clear(); + void loadKey(); + QByteArray sign(const QByteArray &data); + void getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord); + EVP_PKEY *pKey() const; + +private: + void createPrivateKey(const QString &path); + QByteArray bn2ba(const BIGNUM *bn); + + EVP_PKEY *m_pKey; }; #endif // ES256_H diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 271dc652..8cf7b704 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -1,96 +1,44 @@ #include "jsonwebtoken.h" +#include "es256.h" #include #include #include #include #include -#include -#include -#include -#include -#include -QByteArray base64UrlEncode(const QByteArray &data) +inline QByteArray base64UrlEncode(const QByteArray &data) { QByteArray encoded = data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); return encoded; } -QByteArray bn2ba(const BIGNUM *bn) -{ - QByteArray ba; - ba.resize(BN_num_bytes(bn)); - BN_bn2bin(bn, reinterpret_cast(ba.data())); - return ba; -} - -QJsonObject createJwk(EVP_PKEY *pkey) +inline QJsonObject createJwk() { QJsonObject jwk; - // ECキーの抽出 - EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); - if (!ec_key) { - qWarning() << "Failed to get EC key from EVP_PKEY"; - return jwk; - } - - const EC_GROUP *group = EC_KEY_get0_group(ec_key); - const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + QByteArray x_coord; + QByteArray y_coord; + Es256::getInstance()->getAffineCoordinates(x_coord, y_coord); - // X, Y座標の抽出 - BIGNUM *x = BN_new(); - BIGNUM *y = BN_new(); - if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { - qWarning() << "Failed to get affine coordinates"; - BN_free(x); - BN_free(y); - return jwk; + if (!x_coord.isEmpty() && !y_coord.isEmpty()) { + jwk["kty"] = "EC"; // Key Type + jwk["crv"] = "P-256"; // Curve + jwk["x"] = QString::fromUtf8(x_coord); + jwk["y"] = QString::fromUtf8(y_coord); } - // X, Y座標をBase64URLエンコード - QByteArray xCoord = base64UrlEncode( - QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), BN_num_bytes(x))); - QByteArray yCoord = base64UrlEncode( - QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), BN_num_bytes(y))); - - jwk["kty"] = "EC"; // Key Type - jwk["crv"] = "P-256"; // Curve - jwk["x"] = QString::fromUtf8(xCoord); - jwk["y"] = QString::fromUtf8(yCoord); - - BN_free(x); - BN_free(y); - return jwk; } QByteArray JsonWebToken::generate(const QString &endpoint) { - // OpenSSLで秘密鍵を読み込む - FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); - if (!fp) { - qWarning() << "Failed to open private key file"; - return QByteArray(); - } - EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); - fclose(fp); - if (!pkey) { - qWarning() << "Failed to read private key"; - return QByteArray(); - } - - // JWKを生成 - QJsonObject jwk = createJwk(pkey); - EVP_PKEY_free(pkey); - // ヘッダー QJsonObject header; header["alg"] = "ES256"; header["typ"] = "dpop+jwt"; - header["jwk"] = jwk; + header["jwk"] = createJwk(); QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); QByteArray headerBase64 = base64UrlEncode(headerJson); @@ -104,87 +52,10 @@ QByteArray JsonWebToken::generate(const QString &endpoint) // 署名 QByteArray message = headerBase64 + "." + payloadBase64; - QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); + QByteArray signature = Es256::getInstance()->sign(message); QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン QByteArray jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; return jwt; } - -QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyPath) -{ - QByteArray signature; - - // OpenSSLで秘密鍵を読み込む - FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); - if (!fp) { - qWarning() << "Failed to open private key file"; - return QByteArray(); - } - - EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); - fclose(fp); - - if (!pkey) { - qWarning() << "Failed to read private key"; - return QByteArray(); - } - - // ECDSA署名を生成 - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) <= 0) { - qWarning() << "Failed to initialize digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { - qWarning() << "Failed to update digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - size_t sigLen; - if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { - qWarning() << "Failed to finalize digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - signature.resize(sigLen); - if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) - <= 0) { - qWarning() << "Failed to finalize digest sign"; - return QByteArray(); - } - - // Convert DER to IEEE P1363 - const int ec_sig_len = 64; // ES256のときの値 - const unsigned char *temp = reinterpret_cast(signature.constData()); - ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); - if (ec_sig == NULL) { - return QByteArray(); - } - - const BIGNUM *ec_sig_r = NULL; - const BIGNUM *ec_sig_s = NULL; - ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); - - QByteArray rr = bn2ba(ec_sig_r); - QByteArray ss = bn2ba(ec_sig_s); - - if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { - return QByteArray(); - } - rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); - ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); - - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - - return rr + ss; -} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index b6514f3a..ff43ee22 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -7,8 +7,6 @@ class JsonWebToken { public: static QByteArray generate(const QString &endpoint); - - static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); }; #endif // JSONWEBTOKEN_H diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 66b951c7..aaa48a4d 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -9,6 +9,7 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" +#include "tools/es256.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -25,16 +26,19 @@ private slots: void test_oauth_process(); void test_oauth_server(); void test_jwt(); + void test_es256(); private: SimpleHttpServer m_server; quint16 m_listenPort; - void verify_jwt(const QByteArray &jwt); + void verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey); }; oauth_test::oauth_test() { + QCoreApplication::setOrganizationName(QStringLiteral("relog")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); m_listenPort = m_server.listen(QHostAddress::LocalHost, 0); connect(&m_server, &SimpleHttpServer::received, this, @@ -111,12 +115,44 @@ void oauth_test::test_jwt() qDebug().noquote() << jwt; - verify_jwt(jwt); + verify_jwt(jwt, Es256::getInstance()->pKey()); QVERIFY(true); } -void oauth_test::verify_jwt(const QByteArray &jwt) +void oauth_test::test_es256() +{ + QString private_key_path = + QString("%1/%2/%3%4/private_key.pem") + .arg(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()) + .arg(QStringLiteral("_unittest")); + QFile::remove(private_key_path); + Es256::getInstance()->clear(); + + { + QString message = "header.payload"; + QByteArray sign = Es256::getInstance()->sign(message.toUtf8()); + QByteArray jwt = message.toUtf8() + '.' + + sign.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QVERIFY(QFile::exists(private_key_path)); + verify_jwt(jwt, Es256::getInstance()->pKey()); + } + QFile::remove(private_key_path); + QVERIFY(!QFile::exists(private_key_path)); + { + QString message = "header2.payload2"; + QByteArray sign = Es256::getInstance()->sign(message.toUtf8()); + QByteArray jwt = message.toUtf8() + '.' + + sign.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + verify_jwt(jwt, Es256::getInstance()->pKey()); + } +} + +void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); @@ -125,9 +161,10 @@ void oauth_test::verify_jwt(const QByteArray &jwt) QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + + // convert IEEE P1363 to DER QByteArray sig_rr = sig.left(32); QByteArray sig_ss = sig.right(32); - BIGNUM *ec_sig_r = NULL; BIGNUM *ec_sig_s = NULL; ec_sig_r = BN_bin2bn(reinterpret_cast(sig_rr.constData()), @@ -144,25 +181,18 @@ void oauth_test::verify_jwt(const QByteArray &jwt) int der_sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); QVERIFY(der_sig_len > 0); - FILE *fp = fopen("c:\\temp\\public_key.pem", "r"); - QVERIFY(fp != NULL); - EVP_PKEY *pkey = PEM_read_PUBKEY(fp, nullptr, nullptr, nullptr); - fclose(fp); - QVERIFY(pkey != NULL); - // ECDSA署名の検証 EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + QVERIFY(pkey != nullptr); QVERIFY(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) > 0); QVERIFY(EVP_DigestVerifyUpdate(mdctx, message.constData(), message.length()) > 0); QVERIFY(EVP_DigestVerifyFinal(mdctx, der_sig, der_sig_len) > 0); - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - BN_free(ec_sig_r); - BN_free(ec_sig_s); OPENSSL_free(der_sig); ECDSA_SIG_free(ec_sig); + BN_free(ec_sig_s); + BN_free(ec_sig_r); } QTEST_MAIN(oauth_test) From 9504edc982a2f184bd918abd4d9fbc28a609ba1e Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 28 Aug 2024 03:12:53 +0900 Subject: [PATCH 011/127] =?UTF-8?q?=E3=83=98=E3=83=83=E3=83=80=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E9=8D=B5=E3=81=A7=E7=BD=B2=E5=90=8D=E3=82=92=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/es256.cpp | 14 ++------- tests/oauth_test/tst_oauth_test.cpp | 44 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp index c17b21f2..d90e758b 100644 --- a/lib/tools/es256.cpp +++ b/lib/tools/es256.cpp @@ -100,7 +100,6 @@ void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) { loadKey(); - // ECキーの抽出 EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(m_pKey); if (!ec_key) { qWarning() << "Failed to get EC key from EVP_PKEY"; @@ -108,22 +107,15 @@ void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) const EC_GROUP *group = EC_KEY_get0_group(ec_key); const EC_POINT *point = EC_KEY_get0_public_key(ec_key); - - // X, Y座標の抽出 BIGNUM *x = BN_new(); BIGNUM *y = BN_new(); - if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + if (!EC_POINT_get_affine_coordinates(group, point, x, y, nullptr)) { qWarning() << "Failed to get affine coordinates"; x_coord.clear(); y_coord.clear(); } else { - // X, Y座標をBase64URLエンコード - x_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), - BN_num_bytes(x)) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - y_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), - BN_num_bytes(y)) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + x_coord = bn2ba(x).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + y_coord = bn2ba(y).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } BN_free(x); BN_free(y); diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index aaa48a4d..8307a514 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -154,6 +156,8 @@ void oauth_test::test_es256() void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { + EC_KEY *ec_key = nullptr; + const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); @@ -161,6 +165,41 @@ void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + QByteArray header = QByteArray::fromBase64( + jwt_parts.first(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + bool use_jwk = false; + QJsonDocument json_doc = QJsonDocument::fromJson(header); + if (json_doc.object().contains("jwk")) { + QJsonObject jwk_obj = json_doc.object().value("jwk").toObject(); + QByteArray x_coord = QByteArray::fromBase64(jwk_obj.value("x").toString().toUtf8(), + QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + QByteArray y_coord = QByteArray::fromBase64(jwk_obj.value("y").toString().toUtf8(), + QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + QVERIFY(!x_coord.isEmpty()); + QVERIFY(!y_coord.isEmpty()); + + BIGNUM *x = BN_bin2bn(reinterpret_cast(x_coord.constData()), + x_coord.length(), nullptr); + BIGNUM *y = BN_bin2bn(reinterpret_cast(y_coord.constData()), + y_coord.length(), nullptr); + QVERIFY(x); + QVERIFY(y); + + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + QVERIFY(ec_key); + QVERIFY(EC_KEY_set_public_key_affine_coordinates(ec_key, x, y)); + + pkey = EVP_PKEY_new(); + QVERIFY(EVP_PKEY_assign_EC_KEY(pkey, ec_key)); + + BN_free(y); + BN_free(x); + + use_jwk = true; + } // convert IEEE P1363 to DER QByteArray sig_rr = sig.left(32); @@ -193,6 +232,11 @@ void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) ECDSA_SIG_free(ec_sig); BN_free(ec_sig_s); BN_free(ec_sig_r); + + if (use_jwk) { + EVP_PKEY_free(pkey); + // EC_KEY_free(ec_key); EVP_PKEY_freeで解放される + } } QTEST_MAIN(oauth_test) From 192557c72ce86b77be7d0c1dc8244901aa31212a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 29 Aug 2024 01:43:11 +0900 Subject: [PATCH 012/127] =?UTF-8?q?=E8=87=AA=E5=8B=95=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E3=81=AEAPI=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/comatprotorepodescriberepo.cpp | 71 +++++++++++++++++++ .../atproto/repo/comatprotorepodescriberepo.h | 33 +++++++++ lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func_unknown.cpp | 10 +++ lib/lib.pri | 2 + scripts/defs2struct.py | 5 +- 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp create mode 100644 lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h diff --git a/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp new file mode 100644 index 00000000..18c27f5c --- /dev/null +++ b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp @@ -0,0 +1,71 @@ +#include "comatprotorepodescriberepo.h" +#include "atprotocol/lexicons_func.h" +#include "atprotocol/lexicons_func_unknown.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +ComAtprotoRepoDescribeRepo::ComAtprotoRepoDescribeRepo(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void ComAtprotoRepoDescribeRepo::describeRepo(const QString &repo) +{ + QUrlQuery url_query; + if (!repo.isEmpty()) { + url_query.addQueryItem(QStringLiteral("repo"), repo); + } + + get(QStringLiteral("xrpc/com.atproto.repo.describeRepo"), url_query, false); +} + +const QString &ComAtprotoRepoDescribeRepo::handle() const +{ + return m_handle; +} + +const QString &ComAtprotoRepoDescribeRepo::did() const +{ + return m_did; +} + +const QVariant &ComAtprotoRepoDescribeRepo::didDoc() const +{ + return m_didDoc; +} + +const QStringList &ComAtprotoRepoDescribeRepo::collectionsList() const +{ + return m_collectionsList; +} + +const bool &ComAtprotoRepoDescribeRepo::handleIsCorrect() const +{ + return m_handleIsCorrect; +} + +bool ComAtprotoRepoDescribeRepo::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::LexiconsTypeUnknown::copyString(json_doc.object().value("handle"), + m_handle); + AtProtocolType::LexiconsTypeUnknown::copyString(json_doc.object().value("did"), m_did); + AtProtocolType::LexiconsTypeUnknown::copyUnknown( + json_doc.object().value("didDoc").toObject(), m_didDoc); + AtProtocolType::LexiconsTypeUnknown::copyStringList( + json_doc.object().value("collections").toArray(), m_collectionsList); + AtProtocolType::LexiconsTypeUnknown::copyBool(json_doc.object().value("handleIsCorrect"), + m_handleIsCorrect); + } + + return success; +} + +} diff --git a/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h new file mode 100644 index 00000000..717cb12b --- /dev/null +++ b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h @@ -0,0 +1,33 @@ +#ifndef COMATPROTOREPODESCRIBEREPO_H +#define COMATPROTOREPODESCRIBEREPO_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class ComAtprotoRepoDescribeRepo : public AccessAtProtocol +{ +public: + explicit ComAtprotoRepoDescribeRepo(QObject *parent = nullptr); + + void describeRepo(const QString &repo); + + const QString &handle() const; + const QString &did() const; + const QVariant &didDoc() const; + const QStringList &collectionsList() const; + const bool &handleIsCorrect() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + QString m_handle; + QString m_did; + QVariant m_didDoc; + QStringList m_collectionsList; + bool m_handleIsCorrect; +}; + +} + +#endif // COMATPROTOREPODESCRIBEREPO_H diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 63995222..2a12a6d1 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2063,5 +2063,6 @@ Q_DECLARE_METATYPE(AtProtocolType::AppBskyActorProfile::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyGraphList::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedThreadgate::Main) Q_DECLARE_METATYPE(AtProtocolType::ComWhtwndBlogEntry::Main) +Q_DECLARE_METATYPE(AtProtocolType::DirectoryPlcDefs::DidDoc) #endif // LEXICONS_H diff --git a/lib/atprotocol/lexicons_func_unknown.cpp b/lib/atprotocol/lexicons_func_unknown.cpp index 5c1cb304..b78e4218 100644 --- a/lib/atprotocol/lexicons_func_unknown.cpp +++ b/lib/atprotocol/lexicons_func_unknown.cpp @@ -22,6 +22,12 @@ void copyUnknown(const QJsonObject &src, QVariant &dest) return; QString type = src.value("$type").toString(); + QStringList context; + if (src.contains("@context")) { + for (const auto item : src.value("@context").toArray()) { + context.append(item.toString()); + } + } if (type == QStringLiteral("app.bsky.feed.post")) { AppBskyFeedPost::Main record; AppBskyFeedPost::copyMain(src, record); @@ -54,6 +60,10 @@ void copyUnknown(const QJsonObject &src, QVariant &dest) ComWhtwndBlogEntry::Main record; ComWhtwndBlogEntry::copyMain(src, record); dest.setValue(record); + } else if (context.contains("https://www.w3.org/ns/did/v1")) { + DirectoryPlcDefs::DidDoc doc; + DirectoryPlcDefs::copyDidDoc(src, doc); + dest.setValue(doc); } } diff --git a/lib/lib.pri b/lib/lib.pri index 9b30975c..ff6f77b7 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -63,6 +63,7 @@ SOURCES += \ $$PWD/atprotocol/com/atproto/moderation/comatprotomoderationcreatereport.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepocreaterecord.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepodeleterecord.cpp \ + $$PWD/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepogetrecord.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepolistrecords.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepoputrecord.cpp \ @@ -173,6 +174,7 @@ HEADERS += \ $$PWD/atprotocol/com/atproto/moderation/comatprotomoderationcreatereport.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepocreaterecord.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepodeleterecord.h \ + $$PWD/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepogetrecord.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepolistrecords.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepoputrecord.h \ diff --git a/scripts/defs2struct.py b/scripts/defs2struct.py index 7b7b519d..09b5ea25 100644 --- a/scripts/defs2struct.py +++ b/scripts/defs2struct.py @@ -121,7 +121,8 @@ def __init__(self) -> None: 'AppBskyActorProfile::Main', 'AppBskyGraphList::Main', 'AppBskyFeedThreadgate::Main', - 'ComWhtwndBlogEntry::Main' + 'ComWhtwndBlogEntry::Main', + 'DirectoryPlcDefs::DidDoc', ) self.inheritance = { 'app.bsky.actor.defs#profileView': { @@ -163,7 +164,6 @@ def __init__(self) -> None: 'com.atproto.identity.', 'com.atproto.label.', 'com.atproto.repo.applyWrites', - 'com.atproto.repo.describeRepo', 'com.atproto.repo.importRepo', 'com.atproto.repo.uploadBlob', 'com.atproto.repo.listMissingBlobs', @@ -218,6 +218,7 @@ def __init__(self) -> None: self.unuse_auth = [ 'com.atproto.server.createSession', 'com.atproto.sync.getBlob', + 'com.atproto.repo.describeRepo', 'com.atproto.repo.listRecords' ] self.need_extension = [ From f69784c0b0d01743c016d9cd3a803f0460d36a30 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 29 Aug 2024 01:45:39 +0900 Subject: [PATCH 013/127] =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=88=E3=81=AE=E5=8F=96=E5=BE=97=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 45 ++++++++++ lib/tools/authorization.h | 10 +++ tests/oauth_test/oauth_test.pro | 3 + tests/oauth_test/oauth_test.qrc | 7 ++ .../1/.well-known/oauth-protected-resource | 15 ++++ .../2/.well-known/oauth-authorization-server | 90 +++++++++++++++++++ .../2/xrpc/com.atproto.repo.describeRepo | 46 ++++++++++ tests/oauth_test/tst_oauth_test.cpp | 30 +++++++ 8 files changed, 246 insertions(+) create mode 100644 tests/oauth_test/oauth_test.qrc create mode 100644 tests/oauth_test/response/1/.well-known/oauth-protected-resource create mode 100644 tests/oauth_test/response/2/.well-known/oauth-authorization-server create mode 100644 tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 2c093822..7e30869b 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -1,6 +1,8 @@ #include "authorization.h" #include "http/httpaccess.h" #include "http/simplehttpserver.h" +#include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" +#include "atprotocol/lexicons_func_unknown.h" #include #include @@ -14,10 +16,13 @@ #include #include +using AtProtocolInterface::ComAtprotoRepoDescribeRepo; + Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + m_serviceEndpoint.clear(); m_redirectUri.clear(); m_clientId.clear(); m_codeChallenge.clear(); @@ -27,6 +32,33 @@ void Authorization::reset() m_requestTokenPayload.clear(); } +void Authorization::start(const QString &pds, const QString &handle) +{ + if (pds.isEmpty() || handle.isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = pds; + + ComAtprotoRepoDescribeRepo *repo = new ComAtprotoRepoDescribeRepo(this); + connect(repo, &ComAtprotoRepoDescribeRepo::finished, this, [=](bool success) { + if (success) { + auto did_doc = AtProtocolType::LexiconsTypeUnknown::fromQVariant< + AtProtocolType::DirectoryPlcDefs::DidDoc>(repo->didDoc()); + if (!did_doc.service.isEmpty()) { + setServiceEndpoint(did_doc.service.first().serviceEndpoint); + + // next step + } + } else { + emit errorOccured(repo->errorCode(), repo->errorMessage()); + } + repo->deleteLater(); + }); + repo->setAccount(account); + repo->describeRepo(handle); +} + void Authorization::makeClientId() { QString port; @@ -263,6 +295,19 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } +void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) +{ + if (m_serviceEndpoint == newServiceEndpoint) + return; + m_serviceEndpoint = newServiceEndpoint; + emit serviceEndpointChanged(); +} + +QString Authorization::serviceEndpoint() const +{ + return m_serviceEndpoint; +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 1c7fab34..31ea7dee 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -10,6 +10,9 @@ class Authorization : public QObject explicit Authorization(QObject *parent = nullptr); void reset(); + + void start(const QString &pds, const QString &handle); + void makeClientId(); void makeCodeChallenge(); void makeParPayload(); @@ -20,17 +23,24 @@ class Authorization : public QObject void makeRequestTokenPayload(); bool requestToken(); + QString serviceEndpoint() const; + void setServiceEndpoint(const QString &newServiceEndpoint); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; signals: + void errorOccured(const QString &code, const QString &message); + void serviceEndpointChanged(); void finished(bool success); private: QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; + // server + QString m_serviceEndpoint; + // QString m_redirectUri; QString m_clientId; // par diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro index 59229caa..d831444e 100644 --- a/tests/oauth_test/oauth_test.pro +++ b/tests/oauth_test/oauth_test.pro @@ -11,3 +11,6 @@ SOURCES += tst_oauth_test.cpp include(../common/common.pri) include(../../lib/lib.pri) include(../../openssl/openssl.pri) + +RESOURCES += \ + oauth_test.qrc diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc new file mode 100644 index 00000000..d909191c --- /dev/null +++ b/tests/oauth_test/oauth_test.qrc @@ -0,0 +1,7 @@ + + + response/1/.well-known/oauth-protected-resource + response/2/xrpc/com.atproto.repo.describeRepo + response/2/.well-known/oauth-authorization-server + + diff --git a/tests/oauth_test/response/1/.well-known/oauth-protected-resource b/tests/oauth_test/response/1/.well-known/oauth-protected-resource new file mode 100644 index 00000000..b29c0ff7 --- /dev/null +++ b/tests/oauth_test/response/1/.well-known/oauth-protected-resource @@ -0,0 +1,15 @@ +{ + "resource": "http://localhost:%1/response/2", + "authorization_servers": [ + "http://localhost:%1/response/2" + ], + "scopes_supported": [ + "profile", + "email", + "phone" + ], + "bearer_methods_supported": [ + "header" + ], + "resource_documentation": "https://atproto.com" +} diff --git a/tests/oauth_test/response/2/.well-known/oauth-authorization-server b/tests/oauth_test/response/2/.well-known/oauth-authorization-server new file mode 100644 index 00000000..5536c0db --- /dev/null +++ b/tests/oauth_test/response/2/.well-known/oauth-authorization-server @@ -0,0 +1,90 @@ +{ + "issuer": "http://localhost:%1/response/2/issuer", + "scopes_supported": [ + "atproto", + "transition:generic", + "transition:chat.bsky" + ], + "subject_types_supported": [ + "public" + ], + "response_types_supported": [ + "code" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256", + "plain" + ], + "ui_locales_supported": [ + "en-US" + ], + "display_values_supported": [ + "page", + "popup", + "touch" + ], + "authorization_response_iss_parameter_supported": true, + "request_object_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512", + "none" + ], + "request_object_encryption_alg_values_supported": [], + "request_object_encryption_enc_values_supported": [], + "request_parameter_supported": true, + "request_uri_parameter_supported": true, + "require_request_uri_registration": true, + "jwks_uri": "http://localhost:%1/response/2/oauth/jwks", + "authorization_endpoint": "http://localhost:%1/response/2/oauth/authorize", + "token_endpoint": "http://localhost:%1/response/2/oauth/token", + "token_endpoint_auth_methods_supported": [ + "none", + "private_key_jwt" + ], + "token_endpoint_auth_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512" + ], + "revocation_endpoint": "http://localhost:%1/response/2/oauth/revoke", + "introspection_endpoint": "http://localhost:%1/response/2/oauth/introspect", + "pushed_authorization_request_endpoint": "http://localhost:%1/response/2/oauth/par", + "require_pushed_authorization_requests": true, + "dpop_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512" + ], + "client_id_metadata_document_supported": true +} diff --git a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo new file mode 100644 index 00000000..36fa95c0 --- /dev/null +++ b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo @@ -0,0 +1,46 @@ +{ + "handle": "ioriayane.relog.tech", + "did": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "didDoc": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "alsoKnownAs": [ + "at://ioriayane.relog.tech" + ], + "verificationMethod": [ + { + "id": "did:plc:ipj5qejfoqu6eukvt72uhyit#atproto", + "type": "Multikey", + "controller": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "publicKeyMultibase": "zQ3shYNo9wSChjqSMPZUmgwjEFMgsk5efZX5UYm8SFrXR5YUd" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "http://localhost:%1/response/1" + } + ] + }, + "collections": [ + "app.bsky.actor.profile", + "app.bsky.feed.generator", + "app.bsky.feed.like", + "app.bsky.feed.post", + "app.bsky.feed.repost", + "app.bsky.feed.threadgate", + "app.bsky.graph.block", + "app.bsky.graph.follow", + "app.bsky.graph.list", + "app.bsky.graph.listitem", + "app.bsky.graph.starterpack", + "chat.bsky.actor.declaration", + "com.whtwnd.blog.entry" + ], + "handleIsCorrect": true +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 8307a514..72c69ac2 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -27,6 +27,7 @@ private slots: void cleanupTestCase(); void test_oauth_process(); void test_oauth_server(); + void test_oauth(); void test_jwt(); void test_es256(); @@ -47,6 +48,16 @@ oauth_test::oauth_test() [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { // + qDebug().noquote() << request.url(); + QString path = SimpleHttpServer::convertResoucePath(request.url()); + qDebug().noquote() << " res path =" << path; + if (!QFile::exists(path)) { + result = false; + } else { + mime_type = "application/json"; + result = SimpleHttpServer::readFile(path, data); + qDebug().noquote() << " result =" << result; + } }); } @@ -111,6 +122,25 @@ void oauth_test::test_oauth_server() // } } +void oauth_test::test_oauth() +{ + + Authorization oauth; + QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); + QString handle = "ioriayane.relog.tech"; + + oauth.reset(); + { + QSignalSpy spy(&oauth, SIGNAL(serviceEndpointChanged())); + oauth.start(pds, handle); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + oauth.setServiceEndpoint(oauth.serviceEndpoint().arg(m_listenPort)); + + QVERIFY(oauth.serviceEndpoint() == QString("http://localhost:%1/response/1").arg(m_listenPort)); +} + void oauth_test::test_jwt() { QByteArray jwt = JsonWebToken::generate("https://hoge"); From e381bccc084915e2d14c32d6766054af4a19a3f1 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 3 Aug 2024 01:59:08 +0900 Subject: [PATCH 014/127] =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hagoromo.pro | 2 +- app/qml/compat/ColorOverlayC.qml | 4 ++-- app/qml/compat/GlowC.qml | 4 ++-- app/qml/compat/OpacityMaskC.qml | 4 ++-- app/qml/compat/SettingsC.qml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Hagoromo.pro b/Hagoromo.pro index 7c53f308..a01f6372 100644 --- a/Hagoromo.pro +++ b/Hagoromo.pro @@ -2,6 +2,6 @@ TEMPLATE = subdirs SUBDIRS += \ app \ - tests \ + # tests \ tools/decodecar \ tools/firehosereader diff --git a/app/qml/compat/ColorOverlayC.qml b/app/qml/compat/ColorOverlayC.qml index b58f6840..63d2039e 100644 --- a/app/qml/compat/ColorOverlayC.qml +++ b/app/qml/compat/ColorOverlayC.qml @@ -1,5 +1,5 @@ -import QtGraphicalEffects 1.15 -// import Qt5Compat.GraphicalEffects +// import QtGraphicalEffects 1.15 +import Qt5Compat.GraphicalEffects ColorOverlay { diff --git a/app/qml/compat/GlowC.qml b/app/qml/compat/GlowC.qml index b013b8bd..97c7108a 100644 --- a/app/qml/compat/GlowC.qml +++ b/app/qml/compat/GlowC.qml @@ -1,5 +1,5 @@ -import QtGraphicalEffects 1.15 -// import Qt5Compat.GraphicalEffects +// import QtGraphicalEffects 1.15 +import Qt5Compat.GraphicalEffects Glow { diff --git a/app/qml/compat/OpacityMaskC.qml b/app/qml/compat/OpacityMaskC.qml index 86e37287..4144f3b8 100644 --- a/app/qml/compat/OpacityMaskC.qml +++ b/app/qml/compat/OpacityMaskC.qml @@ -1,5 +1,5 @@ -import QtGraphicalEffects 1.15 -// import Qt5Compat.GraphicalEffects +// import QtGraphicalEffects 1.15 +import Qt5Compat.GraphicalEffects OpacityMask { diff --git a/app/qml/compat/SettingsC.qml b/app/qml/compat/SettingsC.qml index 2f7ff511..dafd691c 100644 --- a/app/qml/compat/SettingsC.qml +++ b/app/qml/compat/SettingsC.qml @@ -1,5 +1,5 @@ -import Qt.labs.settings 1.0 -// import QtCore +// import Qt.labs.settings 1.0 +import QtCore Settings { } From 562aa32e6d9c95db126cef8c0bf93caf710642ae Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 3 Aug 2024 02:00:18 +0900 Subject: [PATCH 015/127] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1stparty/atproto | 2 +- 3rdparty/openssl | 2 +- app/qml/view/TimelineView.qml | 26 ++++++++++++++++---------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/1stparty/atproto b/1stparty/atproto index 6bc7faf0..c007e930 160000 --- a/1stparty/atproto +++ b/1stparty/atproto @@ -1 +1 @@ -Subproject commit 6bc7faf0873236ac3567439393a692c627171aac +Subproject commit c007e930edaf65cba9e9ec94376be27cd3ae2bbf diff --git a/3rdparty/openssl b/3rdparty/openssl index 70c2912f..db2ac4f6 160000 --- a/3rdparty/openssl +++ b/3rdparty/openssl @@ -1 +1 @@ -Subproject commit 70c2912f635aac8ab28629a2b5ea0c09740d2bda +Subproject commit db2ac4f6ebd8f3d7b2a60882992fbea1269114e2 diff --git a/app/qml/view/TimelineView.qml b/app/qml/view/TimelineView.qml index f102714f..564f1c17 100644 --- a/app/qml/view/TimelineView.qml +++ b/app/qml/view/TimelineView.qml @@ -109,17 +109,31 @@ ScrollView { onRequestViewSearchPosts: (text) => timelineView.requestViewSearchPosts(text) onRequestAddMutedWord: (text) => timelineView.requestAddMutedWord(text) + moderationFrame.visible: model.muted userFilterMatched: model.userFilterMatched userFilterMessage: model.userFilterMessage repostReactionAuthor.visible: model.isRepostedBy + replyReactionAuthor.visible: model.hasReply + pinnedIndicatorLabel.visible: (model.pinned && model.index === 0) + + contentFilterFrame.visible: model.contentFilterMatched + contentMediaFilterFrame.visible: model.contentMediaFilterMatched + postImagePreview.visible: contentMediaFilterFrame.showContent && model.embedImages.length > 0 + + quoteFilterFrame.visible: model.quoteFilterMatched && !model.quoteRecordBlocked + blockedQuoteFrame.visible: model.quoteRecordBlocked + + externalLinkFrame.visible: model.hasExternalLink && contentMediaFilterFrame.showContent + feedGeneratorFrame.visible: model.hasFeedGenerator && contentMediaFilterFrame.showContent + listLinkCardFrame.visible: model.hasListLink && contentMediaFilterFrame.showContent + + repostReactionAuthor.displayName: model.repostedByDisplayName repostReactionAuthor.handle: model.repostedByHandle - replyReactionAuthor.visible: model.hasReply replyReactionAuthor.displayName: model.replyParentDisplayName replyReactionAuthor.handle: model.replyParentHandle - pinnedIndicatorLabel.visible: (model.pinned && model.index === 0) postAvatarImage.source: model.avatar postAvatarImage.onClicked: requestViewProfile(model.did) @@ -133,19 +147,14 @@ ScrollView { } return text } - contentFilterFrame.visible: model.contentFilterMatched contentFilterFrame.labelText: model.contentFilterMessage - contentMediaFilterFrame.visible: model.contentMediaFilterMatched contentMediaFilterFrame.labelText: model.contentMediaFilterMessage - postImagePreview.visible: contentMediaFilterFrame.showContent && model.embedImages.length > 0 postImagePreview.layoutType: timelineView.imageLayoutType postImagePreview.embedImages: model.embedImages postImagePreview.embedAlts: model.embedImagesAlt postImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.embedImagesFull, model.embedImagesAlt) - quoteFilterFrame.visible: model.quoteFilterMatched && !model.quoteRecordBlocked quoteFilterFrame.labelText: qsTr("Quoted content warning") - blockedQuoteFrame.visible: model.quoteRecordBlocked quoteRecordStatus: model.quoteRecordBlockedStatus hasQuote: model.hasQuoteRecord && !model.quoteRecordBlocked quoteRecordFrame.onClicked: (mouse) => { @@ -163,21 +172,18 @@ ScrollView { quoteRecordImagePreview.embedAlts: model.quoteRecordEmbedImagesAlt quoteRecordImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.quoteRecordEmbedImagesFull, model.quoteRecordEmbedImagesAlt) - externalLinkFrame.visible: model.hasExternalLink && contentMediaFilterFrame.showContent externalLinkFrame.onClicked: Qt.openUrlExternally(model.externalLinkUri) externalLinkFrame.thumbImage.source: model.externalLinkThumb externalLinkFrame.titleLabel.text: model.externalLinkTitle externalLinkFrame.uriLabel.text: model.externalLinkUri externalLinkFrame.descriptionLabel.text: model.externalLinkDescription - feedGeneratorFrame.visible: model.hasFeedGenerator && contentMediaFilterFrame.showContent feedGeneratorFrame.onClicked: requestViewFeedGenerator(model.feedGeneratorDisplayName, model.feedGeneratorUri) feedGeneratorFrame.avatarImage.source: model.feedGeneratorAvatar feedGeneratorFrame.displayNameLabel.text: model.feedGeneratorDisplayName feedGeneratorFrame.creatorHandleLabel.text: model.feedGeneratorCreatorHandle feedGeneratorFrame.likeCountLabel.text: model.feedGeneratorLikeCount - listLinkCardFrame.visible: model.hasListLink && contentMediaFilterFrame.showContent listLinkCardFrame.onClicked: requestViewListFeed(model.listLinkUri, model.listLinkDisplayName) listLinkCardFrame.avatarImage.source: model.listLinkAvatar listLinkCardFrame.displayNameLabel.text: model.listLinkDisplayName From 1540d5ff69b55b14a6ae0cd018275f9849549dc1 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 7 Aug 2024 00:53:12 +0900 Subject: [PATCH 016/127] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AEQt6?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Hagoromo.pro | 2 +- lib/lib.pri | 3 ++ tests/common/webserver.cpp | 60 ++++++++++++++++------- tests/common/webserver.h | 6 +++ tests/hagoromo_test/tst_hagoromo_test.cpp | 2 + tests/http_test/tst_http_test.cpp | 6 +++ 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Hagoromo.pro b/Hagoromo.pro index a01f6372..7c53f308 100644 --- a/Hagoromo.pro +++ b/Hagoromo.pro @@ -2,6 +2,6 @@ TEMPLATE = subdirs SUBDIRS += \ app \ - # tests \ + tests \ tools/decodecar \ tools/firehosereader diff --git a/lib/lib.pri b/lib/lib.pri index 54edd99d..b4b8b44c 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -1,4 +1,7 @@ QT += xml sql websockets +greaterThan(QT_MAJOR_VERSION, 5) { +QT += core5compat +} INCLUDEPATH += $$PWD \ $$PWD/../3rdparty/cpp-httplib diff --git a/tests/common/webserver.cpp b/tests/common/webserver.cpp index f18ca3ad..b0fab792 100644 --- a/tests/common/webserver.cpp +++ b/tests/common/webserver.cpp @@ -2,12 +2,18 @@ WebServer::WebServer(QObject *parent) : QAbstractHttpServer(parent) { } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) bool WebServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) +# define MAKE_RESPONDER makeResponder(request, socket) +#else +# define MAKE_RESPONDER responder +bool WebServer::handleRequest(const QHttpServerRequest &request, QHttpServerResponder &responder) +#endif { if (!verifyHttpHeader(request)) { - makeResponder(request, socket).write(QHttpServerResponder::StatusCode::BadRequest); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::BadRequest); } else if (request.url().path().contains("//")) { - makeResponder(request, socket).write(QHttpServerResponder::StatusCode::NotFound); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::NotFound); } else if (request.method() == QHttpServerRequest::Method::Post) { bool result = false; QString json; @@ -23,22 +29,19 @@ bool WebServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *soc std::make_pair(QByteArray("ratelimit-policy"), QByteArray("30;w=300")) }; if (request.url().path().endsWith("/limit/xrpc/com.atproto.server.createSession")) { - makeResponder(request, socket) - .write(json.toUtf8(), headers, - QHttpServerResponder::StatusCode::Unauthorized); + MAKE_RESPONDER.write(json.toUtf8(), headers, + QHttpServerResponder::StatusCode::Unauthorized); } else { - makeResponder(request, socket) - .write(json.toUtf8(), headers, QHttpServerResponder::StatusCode::Ok); + MAKE_RESPONDER.write(json.toUtf8(), headers, + QHttpServerResponder::StatusCode::Ok); } } else { - makeResponder(request, socket) - .write(json.toUtf8(), - m_MimeDb.mimeTypeForFile("result.json").name().toUtf8(), - QHttpServerResponder::StatusCode::Ok); + MAKE_RESPONDER.write(json.toUtf8(), + m_MimeDb.mimeTypeForFile("result.json").name().toUtf8(), + QHttpServerResponder::StatusCode::Ok); } } else { - makeResponder(request, socket) - .write(QHttpServerResponder::StatusCode::InternalServerError); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::InternalServerError); } } else { QString path = WebServer::convertResoucePath(request.url()); @@ -56,7 +59,7 @@ bool WebServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *soc } qDebug().noquote() << "SERVER PATH=" << path; if (!QFile::exists(path)) { - makeResponder(request, socket).write(QHttpServerResponder::StatusCode::NotFound); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::NotFound); } else { QFileInfo file_info(request.url().path()); QByteArray data; @@ -65,17 +68,25 @@ bool WebServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *soc mime_type = "application/vnd.ipld.car"; } if (WebServer::readFile(path, data)) { - makeResponder(request, socket) - .write(data, mime_type.toUtf8(), QHttpServerResponder::StatusCode::Ok); + MAKE_RESPONDER.write(data, mime_type.toUtf8(), + QHttpServerResponder::StatusCode::Ok); } else { - makeResponder(request, socket) - .write(QHttpServerResponder::StatusCode::InternalServerError); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::InternalServerError); } } } return true; } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +#else +void WebServer::missingHandler(const QHttpServerRequest &request, QHttpServerResponder &&responder) +{ + Q_UNUSED(request) + Q_UNUSED(responder) +} +#endif + QString WebServer::convertResoucePath(const QUrl &url) { QFileInfo file_info(url.path()); @@ -96,6 +107,7 @@ bool WebServer::readFile(const QString &path, QByteArray &data) bool WebServer::verifyHttpHeader(const QHttpServerRequest &request) const { +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) if (request.headers().contains("atproto-accept-labelers")) { QStringList dids = request.headers().value("atproto-accept-labelers").toString().split(","); for (const auto &did : dids) { @@ -104,5 +116,17 @@ bool WebServer::verifyHttpHeader(const QHttpServerRequest &request) const } } } +#else + for (const auto &header : request.headers()) { + if (header.first.contains("atproto-accept-labelers")) { + QList dids = header.second.split(','); + for (const auto &did : dids) { + if (!did.startsWith("did:")) { + return false; + } + } + } + } +#endif return true; } diff --git a/tests/common/webserver.h b/tests/common/webserver.h index 2cc2f866..af0c1cee 100644 --- a/tests/common/webserver.h +++ b/tests/common/webserver.h @@ -9,7 +9,13 @@ class WebServer : public QAbstractHttpServer public: explicit WebServer(QObject *parent = nullptr); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override; +#else + virtual bool handleRequest(const QHttpServerRequest &request, QHttpServerResponder &responder); + virtual void missingHandler(const QHttpServerRequest &request, + QHttpServerResponder &&responder); +#endif static QString convertResoucePath(const QUrl &url); static bool readFile(const QString &path, QByteArray &data); diff --git a/tests/hagoromo_test/tst_hagoromo_test.cpp b/tests/hagoromo_test/tst_hagoromo_test.cpp index 01c2602f..895ef183 100644 --- a/tests/hagoromo_test/tst_hagoromo_test.cpp +++ b/tests/hagoromo_test/tst_hagoromo_test.cpp @@ -1504,7 +1504,9 @@ void hagoromo_test::test_charCount() QVERIFY(file.open(QFile::ReadOnly)); QTextStream ts(&file); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) ts.setCodec("utf-8"); +#endif QStringEx text = ts.readAll(); qDebug() << text << text.length() << text.count(); for (int i = 0; i < text.count(); i++) { diff --git a/tests/http_test/tst_http_test.cpp b/tests/http_test/tst_http_test.cpp index d3075cbe..29bfcb21 100644 --- a/tests/http_test/tst_http_test.cpp +++ b/tests/http_test/tst_http_test.cpp @@ -39,8 +39,14 @@ http_test::http_test() qDebug() << "receive POST" << request.url().path(); QByteArray data; if (request.url().path() == "/response/xrpc/com.atproto.repo.uploadBlob") { +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) for (const auto key : request.headers().keys()) { QString value = request.headers().value(key).toString(); +#else + for(const auto &header: request.headers()){ + QString value = header.second; + QString key = header.first; +#endif qDebug().noquote() << " header:" << key << value; if (key == "PostFile") { QVERIFY(QFile::exists(value)); From 839c2374d1f032236bf8eea12b41c856f9652b61 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 7 Aug 2024 23:34:12 +0900 Subject: [PATCH 017/127] =?UTF-8?q?Mac=E3=81=A7=E3=81=AEx86=E3=83=93?= =?UTF-8?q?=E3=83=AB=E3=83=89=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/build.sh b/scripts/build.sh index 1d8ec565..704263c7 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -106,6 +106,18 @@ if [ -z "${QT_BIN_FOLDER}" ] || [ -z "${PLATFORM_TYPE}" ]; then exit 1 fi +if [ "${PLATFORM_TYPE}" = "mac" ]; then + if [ "$(uname -m)" != "x86_64" ]; then + echo "============ Warning ================" + echo " Requires startup in x86_64 mode" + echo "=====================================" + echo "usage arch -x86_64 $(basename $0) PLATFORM_TYPE QT_BIN_FOLDER" + echo " PLATFORM_TYPE linux or mac" + echo " QT_BIN_FOLDER ex: ~/Qt/5.15.2/gcc_64/bin/" + exit 1 + fi +fi + VERSION_NO=$(cat app/main.cpp | grep "app.setApplicationVersion" | grep -oE "[0-9]+.[0-9]+.[0-9]+") build_openssl From 45b2913dbdc63bdf43ec041bd4c2a6366c0c032f Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 8 Aug 2024 01:41:11 +0900 Subject: [PATCH 018/127] =?UTF-8?q?Windows=E3=81=AEOpenSSL=E3=82=923?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openssl/openssl.pri | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openssl/openssl.pri b/openssl/openssl.pri index ec09ce0e..2b8c8a1d 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -1,6 +1,6 @@ bin_dir=$$dirname(QMAKE_QMAKE) -open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSL +open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSLv3 open_ssl_dir=$$clean_path($$open_ssl_dir) win32:{ @@ -15,8 +15,8 @@ win32:{ depend_files.path = $$install_dir depend_files.files = \ - $${open_ssl_dir}/bin/libcrypto-1_1-x64.dll \ - $${open_ssl_dir}/bin/libssl-1_1-x64.dll + $${open_ssl_dir}/bin/libcrypto-3-x64.dll \ + $${open_ssl_dir}/bin/libssl-3-x64.dll INSTALLS += depend_files QMAKE_POST_LINK += nmake -f $(MAKEFILE) install From 1cc8d745f4b127dff69fd9824142cb3aaf700200 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 9 Aug 2024 01:56:37 +0900 Subject: [PATCH 019/127] =?UTF-8?q?Linux=E3=81=AEQt6=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + openssl/openssl.pri | 20 +- scripts/deploy/linux_lib.txt | 100 +-- scripts/deploy/linux_plugin.txt | 16 +- scripts/deploy/linux_qml.txt | 1175 +++++++++++-------------------- 5 files changed, 493 insertions(+), 819 deletions(-) diff --git a/.gitignore b/.gitignore index bbecb2ab..6c3c5b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ openssl/bin openssl/certs openssl/include openssl/lib +openssl/lib64 openssl/misc openssl/private openssl/share diff --git a/openssl/openssl.pri b/openssl/openssl.pri index 2b8c8a1d..bbc64630 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -1,9 +1,7 @@ - -bin_dir=$$dirname(QMAKE_QMAKE) -open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSLv3 -open_ssl_dir=$$clean_path($$open_ssl_dir) - win32:{ + bin_dir=$$dirname(QMAKE_QMAKE) + open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSLv3 + open_ssl_dir=$$clean_path($$open_ssl_dir) open_ssl_dir=$${open_ssl_dir}/Win_x64 LIBS += $${open_ssl_dir}/lib/libssl.lib \ @@ -20,9 +18,13 @@ win32:{ INSTALLS += depend_files QMAKE_POST_LINK += nmake -f $(MAKEFILE) install -} -unix: { + +}else{ open_ssl_dir=$${PWD} INCLUDEPATH += $${open_ssl_dir}/include - LIBS += -L$${open_ssl_dir}/lib -lssl -lcrypto -} + mac:{ + LIBS += -L$${open_ssl_dir}/lib -lssl -lcrypto + }else{ + LIBS += -L$${open_ssl_dir}/lib64 -lssl -lcrypto + } +} \ No newline at end of file diff --git a/scripts/deploy/linux_lib.txt b/scripts/deploy/linux_lib.txt index d42c0435..2aad98ea 100644 --- a/scripts/deploy/linux_lib.txt +++ b/scripts/deploy/linux_lib.txt @@ -1,40 +1,60 @@ -libQt5Core.so.5 -libQt5Core.so.5.15.2 -libQt5DBus.so.5 -libQt5DBus.so.5.15.2 -libQt5EglFSDeviceIntegration.so.5 -libQt5EglFSDeviceIntegration.so.5.15.2 -libQt5Gui.so.5 -libQt5Gui.so.5.15.2 -libQt5Network.so.5 -libQt5Network.so.5.15.2 -libQt5Qml.so.5 -libQt5Qml.so.5.15.2 -libQt5QmlModels.so.5 -libQt5QmlModels.so.5.15.2 -libQt5QmlWorkerScript.so.5 -libQt5QmlWorkerScript.so.5.15.2 -libQt5Quick.so.5 -libQt5Quick.so.5.15.2 -libQt5QuickControls2.so.5 -libQt5QuickControls2.so.5.15.2 -libQt5QuickTemplates2.so.5 -libQt5QuickTemplates2.so.5.15.2 -libQt5Sql.so.5 -libQt5Sql.so.5.15.2 -libQt5Svg.so.5 -libQt5Svg.so.5.15.2 -libQt5WebSockets.so.5 -libQt5WebSockets.so.5.15.2 -libQt5Widgets.so.5 -libQt5Widgets.so.5.15.2 -libQt5XcbQpa.so.5 -libQt5XcbQpa.so.5.15.2 -libQt5Xml.so.5 -libQt5Xml.so.5.15.2 -libicudata.so.56 -libicudata.so.56.1 -libicui18n.so.56 -libicui18n.so.56.1 -libicuuc.so.56 -libicuuc.so.56.1 +libQt6Core.so.6 +libQt6Core.so.6.7.2 +libQt6Core5Compat.so.6 +libQt6Core5Compat.so.6.7.2 +libQt6DBus.so.6 +libQt6DBus.so.6.7.2 +libQt6Gui.so.6 +libQt6Gui.so.6.7.2 +libQt6Network.so.6 +libQt6Network.so.6.7.2 +libQt6OpenGL.so.6 +libQt6OpenGL.so.6.7.2 +libQt6Qml.so.6 +libQt6Qml.so.6.7.2 +libQt6QmlCore.so.6 +libQt6QmlCore.so.6.7.2 +libQt6QmlModels.so.6 +libQt6QmlModels.so.6.7.2 +libQt6QmlWorkerScript.so.6 +libQt6QmlWorkerScript.so.6.7.2 +libQt6Quick.so.6 +libQt6Quick.so.6.7.2 +libQt6QuickControls2.so.6 +libQt6QuickControls2.so.6.7.2 +libQt6QuickControls2Basic.so.6 +libQt6QuickControls2Basic.so.6.7.2 +libQt6QuickControls2BasicStyleImpl.so.6 +libQt6QuickControls2BasicStyleImpl.so.6.7.2 +libQt6QuickControls2Impl.so.6 +libQt6QuickControls2Impl.so.6.7.2 +libQt6QuickControls2Material.so.6 +libQt6QuickControls2Material.so.6.7.2 +libQt6QuickControls2MaterialStyleImpl.so.6 +libQt6QuickControls2MaterialStyleImpl.so.6.7.2 +libQt6QuickLayouts.so.6 +libQt6QuickLayouts.so.6.7.2 +libQt6QuickTemplates2.so.6 +libQt6QuickTemplates2.so.6.7.2 +libQt6ShaderTools.so.6 +libQt6ShaderTools.so.6.7.2 +libQt6Sql.so.6 +libQt6Sql.so.6.7.2 +libQt6Svg.so.6 +libQt6Svg.so.6.7.2 +libQt6WaylandClient.so.6 +libQt6WaylandClient.so.6.7.2 +libQt6WaylandEglClientHwIntegration.so.6 +libQt6WaylandEglClientHwIntegration.so.6.7.2 +libQt6WebSockets.so.6 +libQt6WebSockets.so.6.7.2 +libQt6Widgets.so.6 +libQt6Widgets.so.6.7.2 +libQt6Xml.so.6 +libQt6Xml.so.6.7.2 +libicudata.so.73 +libicudata.so.73.2 +libicui18n.so.73 +libicui18n.so.73.2 +libicuuc.so.73 +libicuuc.so.73.2 diff --git a/scripts/deploy/linux_plugin.txt b/scripts/deploy/linux_plugin.txt index 638463c8..af08be36 100644 --- a/scripts/deploy/linux_plugin.txt +++ b/scripts/deploy/linux_plugin.txt @@ -17,9 +17,6 @@ platforms/libqoffscreen.so platforms/libqvnc.so platforms/libqwayland-egl.so platforms/libqwayland-generic.so -platforms/libqwayland-xcomposite-egl.so -platforms/libqwayland-xcomposite-glx.so -platforms/libqwebgl.so platforms/libqxcb.so platformthemes/libqgtk3.so platformthemes/libqxdgdesktopportal.so @@ -34,6 +31,19 @@ qmltooling/libqmldbg_profiler.so qmltooling/libqmldbg_quickprofiler.so qmltooling/libqmldbg_server.so qmltooling/libqmldbg_tcp.so +tls/libqcertonlybackend.so +tls/libqopensslbackend.so +wayland-decoration-client/libbradient.so +wayland-graphics-integration-client/libdmabuf-server.so +wayland-graphics-integration-client/libdrm-egl-server.so +wayland-graphics-integration-client/libqt-plugin-wayland-egl.so +wayland-graphics-integration-client/libshm-emulation-server.so +wayland-graphics-integration-client/libvulkan-server.so +wayland-shell-integration/libfullscreen-shell-v1.so +wayland-shell-integration/libivi-shell.so +wayland-shell-integration/libqt-shell.so +wayland-shell-integration/libwl-shell-plugin.so +wayland-shell-integration/libxdg-shell.so sqldrivers/libqsqlite.so sqldrivers/libqsqlodbc.so sqldrivers/libqsqlpsql.so diff --git a/scripts/deploy/linux_qml.txt b/scripts/deploy/linux_qml.txt index 6c6746fe..4b814f03 100644 --- a/scripts/deploy/linux_qml.txt +++ b/scripts/deploy/linux_qml.txt @@ -1,773 +1,414 @@ -Qt/labs/platform/libqtlabsplatformplugin.so Qt/labs/platform/plugins.qmltypes Qt/labs/platform/qmldir -Qt/labs/settings/libqmlsettingsplugin.so -Qt/labs/settings/plugins.qmltypes -Qt/labs/settings/qmldir -QtGraphicalEffects/Blend.qml -QtGraphicalEffects/BrightnessContrast.qml -QtGraphicalEffects/ColorOverlay.qml -QtGraphicalEffects/Colorize.qml -QtGraphicalEffects/ConicalGradient.qml -QtGraphicalEffects/Desaturate.qml -QtGraphicalEffects/DirectionalBlur.qml -QtGraphicalEffects/Displace.qml -QtGraphicalEffects/DropShadow.qml -QtGraphicalEffects/FastBlur.qml -QtGraphicalEffects/GammaAdjust.qml -QtGraphicalEffects/GaussianBlur.qml -QtGraphicalEffects/Glow.qml -QtGraphicalEffects/HueSaturation.qml -QtGraphicalEffects/InnerShadow.qml -QtGraphicalEffects/LevelAdjust.qml -QtGraphicalEffects/LinearGradient.qml -QtGraphicalEffects/MaskedBlur.qml -QtGraphicalEffects/OpacityMask.qml -QtGraphicalEffects/RadialBlur.qml -QtGraphicalEffects/RadialGradient.qml -QtGraphicalEffects/RectangularGlow.qml -QtGraphicalEffects/RecursiveBlur.qml -QtGraphicalEffects/ThresholdMask.qml -QtGraphicalEffects/ZoomBlur.qml -QtGraphicalEffects/libqtgraphicaleffectsplugin.so -QtGraphicalEffects/plugins.qmltypes -QtGraphicalEffects/private/DropShadowBase.qml -QtGraphicalEffects/private/DropShadowBase.qmlc -QtGraphicalEffects/private/FastGlow.qml -QtGraphicalEffects/private/FastGlow.qmlc -QtGraphicalEffects/private/FastInnerShadow.qml -QtGraphicalEffects/private/FastInnerShadow.qmlc -QtGraphicalEffects/private/FastMaskedBlur.qml -QtGraphicalEffects/private/FastMaskedBlur.qmlc -QtGraphicalEffects/private/GaussianDirectionalBlur.qml -QtGraphicalEffects/private/GaussianDirectionalBlur.qmlc -QtGraphicalEffects/private/GaussianGlow.qml -QtGraphicalEffects/private/GaussianGlow.qmlc -QtGraphicalEffects/private/GaussianInnerShadow.qml -QtGraphicalEffects/private/GaussianInnerShadow.qmlc -QtGraphicalEffects/private/GaussianMaskedBlur.qml -QtGraphicalEffects/private/GaussianMaskedBlur.qmlc -QtGraphicalEffects/private/libqtgraphicaleffectsprivate.so -QtGraphicalEffects/private/qmldir -QtGraphicalEffects/qmldir -QtQuick.2/libqtquick2plugin.so -QtQuick.2/plugins.qmltypes -QtQuick.2/qmldir -QtQuick/Controls.2/AbstractButton.qml -QtQuick/Controls.2/Action.qml -QtQuick/Controls.2/ActionGroup.qml -QtQuick/Controls.2/ApplicationWindow.qml -QtQuick/Controls.2/BusyIndicator.qml -QtQuick/Controls.2/Button.qml -QtQuick/Controls.2/ButtonGroup.qml -QtQuick/Controls.2/CheckBox.qml -QtQuick/Controls.2/CheckDelegate.qml -QtQuick/Controls.2/ComboBox.qml -QtQuick/Controls.2/Container.qml -QtQuick/Controls.2/Control.qml -QtQuick/Controls.2/DelayButton.qml -QtQuick/Controls.2/Dial.qml -QtQuick/Controls.2/Dialog.qml -QtQuick/Controls.2/DialogButtonBox.qml -QtQuick/Controls.2/Drawer.qml -QtQuick/Controls.2/Frame.qml -QtQuick/Controls.2/Fusion/ApplicationWindow.qml -QtQuick/Controls.2/Fusion/BusyIndicator.qml -QtQuick/Controls.2/Fusion/Button.qml -QtQuick/Controls.2/Fusion/ButtonPanel.qml -QtQuick/Controls.2/Fusion/CheckBox.qml -QtQuick/Controls.2/Fusion/CheckDelegate.qml -QtQuick/Controls.2/Fusion/CheckIndicator.qml -QtQuick/Controls.2/Fusion/ComboBox.qml -QtQuick/Controls.2/Fusion/DelayButton.qml -QtQuick/Controls.2/Fusion/Dial.qml -QtQuick/Controls.2/Fusion/Dialog.qml -QtQuick/Controls.2/Fusion/DialogButtonBox.qml -QtQuick/Controls.2/Fusion/Drawer.qml -QtQuick/Controls.2/Fusion/Frame.qml -QtQuick/Controls.2/Fusion/GroupBox.qml -QtQuick/Controls.2/Fusion/HorizontalHeaderView.qml -QtQuick/Controls.2/Fusion/ItemDelegate.qml -QtQuick/Controls.2/Fusion/Label.qml -QtQuick/Controls.2/Fusion/Menu.qml -QtQuick/Controls.2/Fusion/MenuBar.qml -QtQuick/Controls.2/Fusion/MenuBarItem.qml -QtQuick/Controls.2/Fusion/MenuItem.qml -QtQuick/Controls.2/Fusion/MenuSeparator.qml -QtQuick/Controls.2/Fusion/Page.qml -QtQuick/Controls.2/Fusion/PageIndicator.qml -QtQuick/Controls.2/Fusion/Pane.qml -QtQuick/Controls.2/Fusion/Popup.qml -QtQuick/Controls.2/Fusion/ProgressBar.qml -QtQuick/Controls.2/Fusion/RadioButton.qml -QtQuick/Controls.2/Fusion/RadioDelegate.qml -QtQuick/Controls.2/Fusion/RadioIndicator.qml -QtQuick/Controls.2/Fusion/RangeSlider.qml -QtQuick/Controls.2/Fusion/RoundButton.qml -QtQuick/Controls.2/Fusion/ScrollBar.qml -QtQuick/Controls.2/Fusion/ScrollIndicator.qml -QtQuick/Controls.2/Fusion/Slider.qml -QtQuick/Controls.2/Fusion/SliderGroove.qml -QtQuick/Controls.2/Fusion/SliderHandle.qml -QtQuick/Controls.2/Fusion/SpinBox.qml -QtQuick/Controls.2/Fusion/SplitView.qml -QtQuick/Controls.2/Fusion/SwipeDelegate.qml -QtQuick/Controls.2/Fusion/Switch.qml -QtQuick/Controls.2/Fusion/SwitchDelegate.qml -QtQuick/Controls.2/Fusion/SwitchIndicator.qml -QtQuick/Controls.2/Fusion/TabBar.qml -QtQuick/Controls.2/Fusion/TabButton.qml -QtQuick/Controls.2/Fusion/TextArea.qml -QtQuick/Controls.2/Fusion/TextField.qml -QtQuick/Controls.2/Fusion/ToolBar.qml -QtQuick/Controls.2/Fusion/ToolButton.qml -QtQuick/Controls.2/Fusion/ToolSeparator.qml -QtQuick/Controls.2/Fusion/ToolTip.qml -QtQuick/Controls.2/Fusion/Tumbler.qml -QtQuick/Controls.2/Fusion/VerticalHeaderView.qml -QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin.so -QtQuick/Controls.2/Fusion/plugins.qmltypes -QtQuick/Controls.2/Fusion/qmldir -QtQuick/Controls.2/GroupBox.qml -QtQuick/Controls.2/HorizontalHeaderView.qml -QtQuick/Controls.2/Imagine/ApplicationWindow.qml -QtQuick/Controls.2/Imagine/BusyIndicator.qml -QtQuick/Controls.2/Imagine/Button.qml -QtQuick/Controls.2/Imagine/CheckBox.qml -QtQuick/Controls.2/Imagine/CheckDelegate.qml -QtQuick/Controls.2/Imagine/ComboBox.qml -QtQuick/Controls.2/Imagine/DelayButton.qml -QtQuick/Controls.2/Imagine/Dial.qml -QtQuick/Controls.2/Imagine/Dialog.qml -QtQuick/Controls.2/Imagine/DialogButtonBox.qml -QtQuick/Controls.2/Imagine/Drawer.qml -QtQuick/Controls.2/Imagine/Frame.qml -QtQuick/Controls.2/Imagine/GroupBox.qml -QtQuick/Controls.2/Imagine/HorizontalHeaderView.qml -QtQuick/Controls.2/Imagine/ItemDelegate.qml -QtQuick/Controls.2/Imagine/Label.qml -QtQuick/Controls.2/Imagine/Menu.qml -QtQuick/Controls.2/Imagine/MenuItem.qml -QtQuick/Controls.2/Imagine/MenuSeparator.qml -QtQuick/Controls.2/Imagine/Page.qml -QtQuick/Controls.2/Imagine/PageIndicator.qml -QtQuick/Controls.2/Imagine/Pane.qml -QtQuick/Controls.2/Imagine/Popup.qml -QtQuick/Controls.2/Imagine/ProgressBar.qml -QtQuick/Controls.2/Imagine/RadioButton.qml -QtQuick/Controls.2/Imagine/RadioDelegate.qml -QtQuick/Controls.2/Imagine/RangeSlider.qml -QtQuick/Controls.2/Imagine/RoundButton.qml -QtQuick/Controls.2/Imagine/ScrollBar.qml -QtQuick/Controls.2/Imagine/ScrollIndicator.qml -QtQuick/Controls.2/Imagine/Slider.qml -QtQuick/Controls.2/Imagine/SpinBox.qml -QtQuick/Controls.2/Imagine/SplitView.qml -QtQuick/Controls.2/Imagine/StackView.qml -QtQuick/Controls.2/Imagine/SwipeDelegate.qml -QtQuick/Controls.2/Imagine/SwipeView.qml -QtQuick/Controls.2/Imagine/Switch.qml -QtQuick/Controls.2/Imagine/SwitchDelegate.qml -QtQuick/Controls.2/Imagine/TabBar.qml -QtQuick/Controls.2/Imagine/TabButton.qml -QtQuick/Controls.2/Imagine/TextArea.qml -QtQuick/Controls.2/Imagine/TextField.qml -QtQuick/Controls.2/Imagine/ToolBar.qml -QtQuick/Controls.2/Imagine/ToolButton.qml -QtQuick/Controls.2/Imagine/ToolSeparator.qml -QtQuick/Controls.2/Imagine/ToolTip.qml -QtQuick/Controls.2/Imagine/Tumbler.qml -QtQuick/Controls.2/Imagine/VerticalHeaderView.qml -QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin.so -QtQuick/Controls.2/Imagine/plugins.qmltypes -QtQuick/Controls.2/Imagine/qmldir -QtQuick/Controls.2/ItemDelegate.qml -QtQuick/Controls.2/Label.qml -QtQuick/Controls.2/Material/ApplicationWindow.qml -QtQuick/Controls.2/Material/BoxShadow.qml -QtQuick/Controls.2/Material/BusyIndicator.qml -QtQuick/Controls.2/Material/Button.qml -QtQuick/Controls.2/Material/CheckBox.qml -QtQuick/Controls.2/Material/CheckDelegate.qml -QtQuick/Controls.2/Material/CheckIndicator.qml -QtQuick/Controls.2/Material/ComboBox.qml -QtQuick/Controls.2/Material/CursorDelegate.qml -QtQuick/Controls.2/Material/DelayButton.qml -QtQuick/Controls.2/Material/Dial.qml -QtQuick/Controls.2/Material/Dialog.qml -QtQuick/Controls.2/Material/DialogButtonBox.qml -QtQuick/Controls.2/Material/Drawer.qml -QtQuick/Controls.2/Material/ElevationEffect.qml -QtQuick/Controls.2/Material/Frame.qml -QtQuick/Controls.2/Material/GroupBox.qml -QtQuick/Controls.2/Material/HorizontalHeaderView.qml -QtQuick/Controls.2/Material/ItemDelegate.qml -QtQuick/Controls.2/Material/Label.qml -QtQuick/Controls.2/Material/Menu.qml -QtQuick/Controls.2/Material/MenuBar.qml -QtQuick/Controls.2/Material/MenuBarItem.qml -QtQuick/Controls.2/Material/MenuItem.qml -QtQuick/Controls.2/Material/MenuSeparator.qml -QtQuick/Controls.2/Material/Page.qml -QtQuick/Controls.2/Material/PageIndicator.qml -QtQuick/Controls.2/Material/Pane.qml -QtQuick/Controls.2/Material/Popup.qml -QtQuick/Controls.2/Material/ProgressBar.qml -QtQuick/Controls.2/Material/RadioButton.qml -QtQuick/Controls.2/Material/RadioDelegate.qml -QtQuick/Controls.2/Material/RadioIndicator.qml -QtQuick/Controls.2/Material/RangeSlider.qml -QtQuick/Controls.2/Material/RectangularGlow.qml -QtQuick/Controls.2/Material/RoundButton.qml -QtQuick/Controls.2/Material/ScrollBar.qml -QtQuick/Controls.2/Material/ScrollIndicator.qml -QtQuick/Controls.2/Material/Slider.qml -QtQuick/Controls.2/Material/SliderHandle.qml -QtQuick/Controls.2/Material/SpinBox.qml -QtQuick/Controls.2/Material/SplitView.qml -QtQuick/Controls.2/Material/StackView.qml -QtQuick/Controls.2/Material/SwipeDelegate.qml -QtQuick/Controls.2/Material/SwipeView.qml -QtQuick/Controls.2/Material/Switch.qml -QtQuick/Controls.2/Material/SwitchDelegate.qml -QtQuick/Controls.2/Material/SwitchIndicator.qml -QtQuick/Controls.2/Material/TabBar.qml -QtQuick/Controls.2/Material/TabButton.qml -QtQuick/Controls.2/Material/TextArea.qml -QtQuick/Controls.2/Material/TextField.qml -QtQuick/Controls.2/Material/ToolBar.qml -QtQuick/Controls.2/Material/ToolButton.qml -QtQuick/Controls.2/Material/ToolSeparator.qml -QtQuick/Controls.2/Material/ToolTip.qml -QtQuick/Controls.2/Material/Tumbler.qml -QtQuick/Controls.2/Material/VerticalHeaderView.qml -QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin.so -QtQuick/Controls.2/Material/plugins.qmltypes -QtQuick/Controls.2/Material/qmldir -QtQuick/Controls.2/Menu.qml -QtQuick/Controls.2/MenuBar.qml -QtQuick/Controls.2/MenuBarItem.qml -QtQuick/Controls.2/MenuItem.qml -QtQuick/Controls.2/MenuSeparator.qml -QtQuick/Controls.2/Page.qml -QtQuick/Controls.2/PageIndicator.qml -QtQuick/Controls.2/Pane.qml -QtQuick/Controls.2/Popup.qml -QtQuick/Controls.2/ProgressBar.qml -QtQuick/Controls.2/RadioButton.qml -QtQuick/Controls.2/RadioDelegate.qml -QtQuick/Controls.2/RangeSlider.qml -QtQuick/Controls.2/RoundButton.qml -QtQuick/Controls.2/ScrollBar.qml -QtQuick/Controls.2/ScrollIndicator.qml -QtQuick/Controls.2/ScrollView.qml -QtQuick/Controls.2/Slider.qml -QtQuick/Controls.2/SpinBox.qml -QtQuick/Controls.2/SplitView.qml -QtQuick/Controls.2/StackView.qml -QtQuick/Controls.2/SwipeDelegate.qml -QtQuick/Controls.2/SwipeView.qml -QtQuick/Controls.2/Switch.qml -QtQuick/Controls.2/SwitchDelegate.qml -QtQuick/Controls.2/TabBar.qml -QtQuick/Controls.2/TabButton.qml -QtQuick/Controls.2/TextArea.qml -QtQuick/Controls.2/TextField.qml -QtQuick/Controls.2/ToolBar.qml -QtQuick/Controls.2/ToolButton.qml -QtQuick/Controls.2/ToolSeparator.qml -QtQuick/Controls.2/ToolTip.qml -QtQuick/Controls.2/Tumbler.qml -QtQuick/Controls.2/Universal/ApplicationWindow.qml -QtQuick/Controls.2/Universal/BusyIndicator.qml -QtQuick/Controls.2/Universal/Button.qml -QtQuick/Controls.2/Universal/CheckBox.qml -QtQuick/Controls.2/Universal/CheckDelegate.qml -QtQuick/Controls.2/Universal/CheckIndicator.qml -QtQuick/Controls.2/Universal/ComboBox.qml -QtQuick/Controls.2/Universal/DelayButton.qml -QtQuick/Controls.2/Universal/Dial.qml -QtQuick/Controls.2/Universal/Dialog.qml -QtQuick/Controls.2/Universal/DialogButtonBox.qml -QtQuick/Controls.2/Universal/Drawer.qml -QtQuick/Controls.2/Universal/Frame.qml -QtQuick/Controls.2/Universal/GroupBox.qml -QtQuick/Controls.2/Universal/HorizontalHeaderView.qml -QtQuick/Controls.2/Universal/ItemDelegate.qml -QtQuick/Controls.2/Universal/Label.qml -QtQuick/Controls.2/Universal/Menu.qml -QtQuick/Controls.2/Universal/MenuBar.qml -QtQuick/Controls.2/Universal/MenuBarItem.qml -QtQuick/Controls.2/Universal/MenuItem.qml -QtQuick/Controls.2/Universal/MenuSeparator.qml -QtQuick/Controls.2/Universal/Page.qml -QtQuick/Controls.2/Universal/PageIndicator.qml -QtQuick/Controls.2/Universal/Pane.qml -QtQuick/Controls.2/Universal/Popup.qml -QtQuick/Controls.2/Universal/ProgressBar.qml -QtQuick/Controls.2/Universal/RadioButton.qml -QtQuick/Controls.2/Universal/RadioDelegate.qml -QtQuick/Controls.2/Universal/RadioIndicator.qml -QtQuick/Controls.2/Universal/RangeSlider.qml -QtQuick/Controls.2/Universal/RoundButton.qml -QtQuick/Controls.2/Universal/ScrollBar.qml -QtQuick/Controls.2/Universal/ScrollIndicator.qml -QtQuick/Controls.2/Universal/Slider.qml -QtQuick/Controls.2/Universal/SpinBox.qml -QtQuick/Controls.2/Universal/SplitView.qml -QtQuick/Controls.2/Universal/StackView.qml -QtQuick/Controls.2/Universal/SwipeDelegate.qml -QtQuick/Controls.2/Universal/Switch.qml -QtQuick/Controls.2/Universal/SwitchDelegate.qml -QtQuick/Controls.2/Universal/SwitchIndicator.qml -QtQuick/Controls.2/Universal/TabBar.qml -QtQuick/Controls.2/Universal/TabButton.qml -QtQuick/Controls.2/Universal/TextArea.qml -QtQuick/Controls.2/Universal/TextField.qml -QtQuick/Controls.2/Universal/ToolBar.qml -QtQuick/Controls.2/Universal/ToolButton.qml -QtQuick/Controls.2/Universal/ToolSeparator.qml -QtQuick/Controls.2/Universal/ToolTip.qml -QtQuick/Controls.2/Universal/Tumbler.qml -QtQuick/Controls.2/Universal/VerticalHeaderView.qml -QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin.so -QtQuick/Controls.2/Universal/plugins.qmltypes -QtQuick/Controls.2/Universal/qmldir -QtQuick/Controls.2/VerticalHeaderView.qml -QtQuick/Controls.2/designer/AbstractButtonSection.qml -QtQuick/Controls.2/designer/BusyIndicatorSpecifics.qml -QtQuick/Controls.2/designer/ButtonSection.qml -QtQuick/Controls.2/designer/ButtonSpecifics.qml -QtQuick/Controls.2/designer/CheckBoxSpecifics.qml -QtQuick/Controls.2/designer/CheckDelegateSpecifics.qml -QtQuick/Controls.2/designer/CheckSection.qml -QtQuick/Controls.2/designer/ComboBoxSpecifics.qml -QtQuick/Controls.2/designer/ContainerSection.qml -QtQuick/Controls.2/designer/ControlSection.qml -QtQuick/Controls.2/designer/ControlSpecifics.qml -QtQuick/Controls.2/designer/DelayButtonSpecifics.qml -QtQuick/Controls.2/designer/DialSpecifics.qml -QtQuick/Controls.2/designer/FrameSpecifics.qml -QtQuick/Controls.2/designer/GroupBoxSpecifics.qml -QtQuick/Controls.2/designer/InsetSection.qml -QtQuick/Controls.2/designer/ItemDelegateSection.qml -QtQuick/Controls.2/designer/ItemDelegateSpecifics.qml -QtQuick/Controls.2/designer/LabelSpecifics.qml -QtQuick/Controls.2/designer/PaddingSection.qml -QtQuick/Controls.2/designer/PageIndicatorSpecifics.qml -QtQuick/Controls.2/designer/PageSpecifics.qml -QtQuick/Controls.2/designer/PaneSection.qml -QtQuick/Controls.2/designer/PaneSpecifics.qml -QtQuick/Controls.2/designer/ProgressBarSpecifics.qml -QtQuick/Controls.2/designer/RadioButtonSpecifics.qml -QtQuick/Controls.2/designer/RadioDelegateSpecifics.qml -QtQuick/Controls.2/designer/RangeSliderSpecifics.qml -QtQuick/Controls.2/designer/RoundButtonSpecifics.qml -QtQuick/Controls.2/designer/ScrollViewSpecifics.qml -QtQuick/Controls.2/designer/SliderSpecifics.qml -QtQuick/Controls.2/designer/SpinBoxSpecifics.qml -QtQuick/Controls.2/designer/StackViewSpecifics.qml -QtQuick/Controls.2/designer/SwipeDelegateSpecifics.qml -QtQuick/Controls.2/designer/SwipeViewSpecifics.qml -QtQuick/Controls.2/designer/SwitchDelegateSpecifics.qml -QtQuick/Controls.2/designer/SwitchSpecifics.qml -QtQuick/Controls.2/designer/TabBarSpecifics.qml -QtQuick/Controls.2/designer/TabButtonSpecifics.qml -QtQuick/Controls.2/designer/TextAreaSpecifics.qml -QtQuick/Controls.2/designer/TextFieldSpecifics.qml -QtQuick/Controls.2/designer/ToolBarSpecifics.qml -QtQuick/Controls.2/designer/ToolButtonSpecifics.qml -QtQuick/Controls.2/designer/ToolSeparatorSpecifics.qml -QtQuick/Controls.2/designer/TumblerSpecifics.qml -QtQuick/Controls.2/designer/images/busyindicator-icon.png -QtQuick/Controls.2/designer/images/busyindicator-icon16.png -QtQuick/Controls.2/designer/images/busyindicator-icon@2x.png -QtQuick/Controls.2/designer/images/button-icon.png -QtQuick/Controls.2/designer/images/button-icon16.png -QtQuick/Controls.2/designer/images/button-icon@2x.png -QtQuick/Controls.2/designer/images/checkbox-icon.png -QtQuick/Controls.2/designer/images/checkbox-icon16.png -QtQuick/Controls.2/designer/images/checkbox-icon@2x.png -QtQuick/Controls.2/designer/images/combobox-icon.png -QtQuick/Controls.2/designer/images/combobox-icon16.png -QtQuick/Controls.2/designer/images/combobox-icon@2x.png -QtQuick/Controls.2/designer/images/delaybutton-icon.png -QtQuick/Controls.2/designer/images/delaybutton-icon16.png -QtQuick/Controls.2/designer/images/delaybutton-icon@2x.png -QtQuick/Controls.2/designer/images/dial-icon.png -QtQuick/Controls.2/designer/images/dial-icon16.png -QtQuick/Controls.2/designer/images/dial-icon@2x.png -QtQuick/Controls.2/designer/images/frame-icon.png -QtQuick/Controls.2/designer/images/frame-icon16.png -QtQuick/Controls.2/designer/images/frame-icon@2x.png -QtQuick/Controls.2/designer/images/groupbox-icon.png -QtQuick/Controls.2/designer/images/groupbox-icon16.png -QtQuick/Controls.2/designer/images/groupbox-icon@2x.png -QtQuick/Controls.2/designer/images/itemdelegate-icon.png -QtQuick/Controls.2/designer/images/itemdelegate-icon16.png -QtQuick/Controls.2/designer/images/itemdelegate-icon@2x.png -QtQuick/Controls.2/designer/images/label-icon.png -QtQuick/Controls.2/designer/images/label-icon16.png -QtQuick/Controls.2/designer/images/label-icon@2x.png -QtQuick/Controls.2/designer/images/page-icon.png -QtQuick/Controls.2/designer/images/page-icon16.png -QtQuick/Controls.2/designer/images/page-icon@2x.png -QtQuick/Controls.2/designer/images/pageindicator-icon.png -QtQuick/Controls.2/designer/images/pageindicator-icon16.png -QtQuick/Controls.2/designer/images/pageindicator-icon@2x.png -QtQuick/Controls.2/designer/images/pane-icon.png -QtQuick/Controls.2/designer/images/pane-icon16.png -QtQuick/Controls.2/designer/images/pane-icon@2x.png -QtQuick/Controls.2/designer/images/progressbar-icon.png -QtQuick/Controls.2/designer/images/progressbar-icon16.png -QtQuick/Controls.2/designer/images/progressbar-icon@2x.png -QtQuick/Controls.2/designer/images/radiobutton-icon.png -QtQuick/Controls.2/designer/images/radiobutton-icon16.png -QtQuick/Controls.2/designer/images/radiobutton-icon@2x.png -QtQuick/Controls.2/designer/images/rangeslider-icon.png -QtQuick/Controls.2/designer/images/rangeslider-icon16.png -QtQuick/Controls.2/designer/images/rangeslider-icon@2x.png -QtQuick/Controls.2/designer/images/roundbutton-icon.png -QtQuick/Controls.2/designer/images/roundbutton-icon16.png -QtQuick/Controls.2/designer/images/roundbutton-icon@2x.png -QtQuick/Controls.2/designer/images/scrollview-icon.png -QtQuick/Controls.2/designer/images/scrollview-icon16.png -QtQuick/Controls.2/designer/images/scrollview-icon@2x.png -QtQuick/Controls.2/designer/images/slider-icon.png -QtQuick/Controls.2/designer/images/slider-icon16.png -QtQuick/Controls.2/designer/images/slider-icon@2x.png -QtQuick/Controls.2/designer/images/spinbox-icon.png -QtQuick/Controls.2/designer/images/spinbox-icon16.png -QtQuick/Controls.2/designer/images/spinbox-icon@2x.png -QtQuick/Controls.2/designer/images/stackview-icon.png -QtQuick/Controls.2/designer/images/stackview-icon16.png -QtQuick/Controls.2/designer/images/stackview-icon@2x.png -QtQuick/Controls.2/designer/images/swipeview-icon.png -QtQuick/Controls.2/designer/images/swipeview-icon16.png -QtQuick/Controls.2/designer/images/swipeview-icon@2x.png -QtQuick/Controls.2/designer/images/switch-icon.png -QtQuick/Controls.2/designer/images/switch-icon16.png -QtQuick/Controls.2/designer/images/switch-icon@2x.png -QtQuick/Controls.2/designer/images/textarea-icon.png -QtQuick/Controls.2/designer/images/textarea-icon16.png -QtQuick/Controls.2/designer/images/textarea-icon@2x.png -QtQuick/Controls.2/designer/images/textfield-icon.png -QtQuick/Controls.2/designer/images/textfield-icon16.png -QtQuick/Controls.2/designer/images/textfield-icon@2x.png -QtQuick/Controls.2/designer/images/toolbar-icon.png -QtQuick/Controls.2/designer/images/toolbar-icon16.png -QtQuick/Controls.2/designer/images/toolbar-icon@2x.png -QtQuick/Controls.2/designer/images/toolbutton-icon.png -QtQuick/Controls.2/designer/images/toolbutton-icon16.png -QtQuick/Controls.2/designer/images/toolbutton-icon@2x.png -QtQuick/Controls.2/designer/images/toolseparator-icon.png -QtQuick/Controls.2/designer/images/toolseparator-icon16.png -QtQuick/Controls.2/designer/images/toolseparator-icon@2x.png -QtQuick/Controls.2/designer/images/tumbler-icon.png -QtQuick/Controls.2/designer/images/tumbler-icon16.png -QtQuick/Controls.2/designer/images/tumbler-icon@2x.png -QtQuick/Controls.2/designer/qtquickcontrols2.metainfo -QtQuick/Controls.2/libqtquickcontrols2plugin.so -QtQuick/Controls.2/plugins.qmltypes -QtQuick/Controls.2/qmldir -QtQuick/Controls/ApplicationWindow.qml -QtQuick/Controls/ApplicationWindow.qmlc -QtQuick/Controls/BusyIndicator.qml -QtQuick/Controls/BusyIndicator.qmlc -QtQuick/Controls/Button.qml -QtQuick/Controls/Button.qmlc -QtQuick/Controls/Calendar.qml -QtQuick/Controls/Calendar.qmlc -QtQuick/Controls/CheckBox.qml -QtQuick/Controls/CheckBox.qmlc -QtQuick/Controls/ComboBox.qml -QtQuick/Controls/ComboBox.qmlc -QtQuick/Controls/GroupBox.qml -QtQuick/Controls/GroupBox.qmlc -QtQuick/Controls/Label.qml -QtQuick/Controls/Label.qmlc -QtQuick/Controls/Menu.qml -QtQuick/Controls/Menu.qmlc -QtQuick/Controls/MenuBar.qml -QtQuick/Controls/MenuBar.qmlc -QtQuick/Controls/Private/AbstractCheckable.qml -QtQuick/Controls/Private/AbstractCheckable.qmlc -QtQuick/Controls/Private/BasicButton.qml -QtQuick/Controls/Private/BasicButton.qmlc -QtQuick/Controls/Private/BasicTableView.qml -QtQuick/Controls/Private/BasicTableView.qmlc -QtQuick/Controls/Private/CalendarHeaderModel.qml -QtQuick/Controls/Private/CalendarHeaderModel.qmlc -QtQuick/Controls/Private/CalendarUtils.js -QtQuick/Controls/Private/CalendarUtils.jsc -QtQuick/Controls/Private/ColumnMenuContent.qml -QtQuick/Controls/Private/ColumnMenuContent.qmlc -QtQuick/Controls/Private/ContentItem.qml -QtQuick/Controls/Private/ContentItem.qmlc -QtQuick/Controls/Private/Control.qml -QtQuick/Controls/Private/Control.qmlc -QtQuick/Controls/Private/EditMenu.qml -QtQuick/Controls/Private/EditMenu.qmlc -QtQuick/Controls/Private/EditMenu_base.qml -QtQuick/Controls/Private/EditMenu_base.qmlc -QtQuick/Controls/Private/FastGlow.qml -QtQuick/Controls/Private/FastGlow.qmlc -QtQuick/Controls/Private/FocusFrame.qml -QtQuick/Controls/Private/FocusFrame.qmlc -QtQuick/Controls/Private/HoverButton.qml -QtQuick/Controls/Private/HoverButton.qmlc -QtQuick/Controls/Private/MenuContentItem.qml -QtQuick/Controls/Private/MenuContentItem.qmlc -QtQuick/Controls/Private/MenuContentScroller.qml -QtQuick/Controls/Private/MenuContentScroller.qmlc -QtQuick/Controls/Private/MenuItemSubControls.qml -QtQuick/Controls/Private/MenuItemSubControls.qmlc -QtQuick/Controls/Private/ModalPopupBehavior.qml -QtQuick/Controls/Private/ModalPopupBehavior.qmlc -QtQuick/Controls/Private/ScrollBar.qml -QtQuick/Controls/Private/ScrollBar.qmlc -QtQuick/Controls/Private/ScrollViewHelper.qml -QtQuick/Controls/Private/ScrollViewHelper.qmlc -QtQuick/Controls/Private/SourceProxy.qml -QtQuick/Controls/Private/SourceProxy.qmlc -QtQuick/Controls/Private/StackView.js -QtQuick/Controls/Private/StackView.jsc -QtQuick/Controls/Private/StackViewSlideDelegate.qml -QtQuick/Controls/Private/StackViewSlideDelegate.qmlc -QtQuick/Controls/Private/Style.qml -QtQuick/Controls/Private/Style.qmlc -QtQuick/Controls/Private/SystemPaletteSingleton.qml -QtQuick/Controls/Private/SystemPaletteSingleton.qmlc -QtQuick/Controls/Private/TabBar.qml -QtQuick/Controls/Private/TabBar.qmlc -QtQuick/Controls/Private/TableViewItemDelegateLoader.qml -QtQuick/Controls/Private/TableViewItemDelegateLoader.qmlc -QtQuick/Controls/Private/TableViewSelection.qml -QtQuick/Controls/Private/TableViewSelection.qmlc -QtQuick/Controls/Private/TextHandle.qml -QtQuick/Controls/Private/TextHandle.qmlc -QtQuick/Controls/Private/TextInputWithHandles.qml -QtQuick/Controls/Private/TextInputWithHandles.qmlc -QtQuick/Controls/Private/TextSingleton.qml -QtQuick/Controls/Private/TextSingleton.qmlc -QtQuick/Controls/Private/ToolMenuButton.qml -QtQuick/Controls/Private/ToolMenuButton.qmlc -QtQuick/Controls/Private/TreeViewItemDelegateLoader.qml -QtQuick/Controls/Private/TreeViewItemDelegateLoader.qmlc -QtQuick/Controls/Private/qmldir -QtQuick/Controls/Private/style.js -QtQuick/Controls/Private/style.jsc -QtQuick/Controls/ProgressBar.qml -QtQuick/Controls/ProgressBar.qmlc -QtQuick/Controls/RadioButton.qml -QtQuick/Controls/RadioButton.qmlc -QtQuick/Controls/ScrollView.qml -QtQuick/Controls/ScrollView.qmlc -QtQuick/Controls/Slider.qml -QtQuick/Controls/Slider.qmlc -QtQuick/Controls/SpinBox.qml -QtQuick/Controls/SpinBox.qmlc -QtQuick/Controls/SplitView.qml -QtQuick/Controls/SplitView.qmlc -QtQuick/Controls/StackView.qml -QtQuick/Controls/StackView.qmlc -QtQuick/Controls/StackViewDelegate.qml -QtQuick/Controls/StackViewDelegate.qmlc -QtQuick/Controls/StackViewTransition.qml -QtQuick/Controls/StackViewTransition.qmlc -QtQuick/Controls/StatusBar.qml -QtQuick/Controls/StatusBar.qmlc -QtQuick/Controls/Styles/Base/ApplicationWindowStyle.qml -QtQuick/Controls/Styles/Base/ApplicationWindowStyle.qmlc -QtQuick/Controls/Styles/Base/BasicTableViewStyle.qml -QtQuick/Controls/Styles/Base/BasicTableViewStyle.qmlc -QtQuick/Controls/Styles/Base/BusyIndicatorStyle.qml -QtQuick/Controls/Styles/Base/BusyIndicatorStyle.qmlc -QtQuick/Controls/Styles/Base/ButtonStyle.qml -QtQuick/Controls/Styles/Base/ButtonStyle.qmlc -QtQuick/Controls/Styles/Base/CalendarStyle.qml -QtQuick/Controls/Styles/Base/CalendarStyle.qmlc -QtQuick/Controls/Styles/Base/CheckBoxStyle.qml -QtQuick/Controls/Styles/Base/CheckBoxStyle.qmlc -QtQuick/Controls/Styles/Base/CircularButtonStyle.qml -QtQuick/Controls/Styles/Base/CircularButtonStyle.qmlc -QtQuick/Controls/Styles/Base/CircularGaugeStyle.qml -QtQuick/Controls/Styles/Base/CircularGaugeStyle.qmlc -QtQuick/Controls/Styles/Base/CircularTickmarkLabelStyle.qml -QtQuick/Controls/Styles/Base/CircularTickmarkLabelStyle.qmlc -QtQuick/Controls/Styles/Base/ComboBoxStyle.qml -QtQuick/Controls/Styles/Base/ComboBoxStyle.qmlc -QtQuick/Controls/Styles/Base/CommonStyleHelper.qml -QtQuick/Controls/Styles/Base/CommonStyleHelper.qmlc -QtQuick/Controls/Styles/Base/DelayButtonStyle.qml -QtQuick/Controls/Styles/Base/DelayButtonStyle.qmlc -QtQuick/Controls/Styles/Base/DialStyle.qml -QtQuick/Controls/Styles/Base/DialStyle.qmlc -QtQuick/Controls/Styles/Base/FocusFrameStyle.qml -QtQuick/Controls/Styles/Base/FocusFrameStyle.qmlc -QtQuick/Controls/Styles/Base/GaugeStyle.qml -QtQuick/Controls/Styles/Base/GaugeStyle.qmlc -QtQuick/Controls/Styles/Base/GroupBoxStyle.qml -QtQuick/Controls/Styles/Base/GroupBoxStyle.qmlc -QtQuick/Controls/Styles/Base/HandleStyle.qml -QtQuick/Controls/Styles/Base/HandleStyle.qmlc -QtQuick/Controls/Styles/Base/HandleStyleHelper.qml -QtQuick/Controls/Styles/Base/HandleStyleHelper.qmlc -QtQuick/Controls/Styles/Base/MenuBarStyle.qml -QtQuick/Controls/Styles/Base/MenuBarStyle.qmlc -QtQuick/Controls/Styles/Base/MenuStyle.qml -QtQuick/Controls/Styles/Base/MenuStyle.qmlc -QtQuick/Controls/Styles/Base/PieMenuStyle.qml -QtQuick/Controls/Styles/Base/PieMenuStyle.qmlc -QtQuick/Controls/Styles/Base/ProgressBarStyle.qml -QtQuick/Controls/Styles/Base/ProgressBarStyle.qmlc -QtQuick/Controls/Styles/Base/RadioButtonStyle.qml -QtQuick/Controls/Styles/Base/RadioButtonStyle.qmlc -QtQuick/Controls/Styles/Base/ScrollViewStyle.qml -QtQuick/Controls/Styles/Base/ScrollViewStyle.qmlc -QtQuick/Controls/Styles/Base/SliderStyle.qml -QtQuick/Controls/Styles/Base/SliderStyle.qmlc -QtQuick/Controls/Styles/Base/SpinBoxStyle.qml -QtQuick/Controls/Styles/Base/SpinBoxStyle.qmlc -QtQuick/Controls/Styles/Base/StatusBarStyle.qml -QtQuick/Controls/Styles/Base/StatusBarStyle.qmlc -QtQuick/Controls/Styles/Base/StatusIndicatorStyle.qml -QtQuick/Controls/Styles/Base/StatusIndicatorStyle.qmlc -QtQuick/Controls/Styles/Base/SwitchStyle.qml -QtQuick/Controls/Styles/Base/SwitchStyle.qmlc -QtQuick/Controls/Styles/Base/TabViewStyle.qml -QtQuick/Controls/Styles/Base/TabViewStyle.qmlc -QtQuick/Controls/Styles/Base/TableViewStyle.qml -QtQuick/Controls/Styles/Base/TableViewStyle.qmlc -QtQuick/Controls/Styles/Base/TextAreaStyle.qml -QtQuick/Controls/Styles/Base/TextAreaStyle.qmlc -QtQuick/Controls/Styles/Base/TextFieldStyle.qml -QtQuick/Controls/Styles/Base/TextFieldStyle.qmlc -QtQuick/Controls/Styles/Base/ToggleButtonStyle.qml -QtQuick/Controls/Styles/Base/ToggleButtonStyle.qmlc -QtQuick/Controls/Styles/Base/ToolBarStyle.qml -QtQuick/Controls/Styles/Base/ToolBarStyle.qmlc -QtQuick/Controls/Styles/Base/ToolButtonStyle.qml -QtQuick/Controls/Styles/Base/ToolButtonStyle.qmlc -QtQuick/Controls/Styles/Base/TreeViewStyle.qml -QtQuick/Controls/Styles/Base/TreeViewStyle.qmlc -QtQuick/Controls/Styles/Base/TumblerStyle.qml -QtQuick/Controls/Styles/Base/TumblerStyle.qmlc -QtQuick/Controls/Styles/Base/images/arrow-down.png -QtQuick/Controls/Styles/Base/images/arrow-down@2x.png -QtQuick/Controls/Styles/Base/images/arrow-left.png -QtQuick/Controls/Styles/Base/images/arrow-left@2x.png -QtQuick/Controls/Styles/Base/images/arrow-right.png -QtQuick/Controls/Styles/Base/images/arrow-right@2x.png -QtQuick/Controls/Styles/Base/images/arrow-up.png -QtQuick/Controls/Styles/Base/images/arrow-up@2x.png -QtQuick/Controls/Styles/Base/images/button.png -QtQuick/Controls/Styles/Base/images/button_down.png -QtQuick/Controls/Styles/Base/images/check.png -QtQuick/Controls/Styles/Base/images/check@2x.png -QtQuick/Controls/Styles/Base/images/editbox.png -QtQuick/Controls/Styles/Base/images/focusframe.png -QtQuick/Controls/Styles/Base/images/groupbox.png -QtQuick/Controls/Styles/Base/images/header.png -QtQuick/Controls/Styles/Base/images/knob.png -QtQuick/Controls/Styles/Base/images/leftanglearrow.png -QtQuick/Controls/Styles/Base/images/needle.png -QtQuick/Controls/Styles/Base/images/progress-indeterminate.png -QtQuick/Controls/Styles/Base/images/rightanglearrow.png -QtQuick/Controls/Styles/Base/images/scrollbar-handle-horizontal.png -QtQuick/Controls/Styles/Base/images/scrollbar-handle-transient.png -QtQuick/Controls/Styles/Base/images/scrollbar-handle-vertical.png -QtQuick/Controls/Styles/Base/images/slider-groove.png -QtQuick/Controls/Styles/Base/images/slider-handle.png -QtQuick/Controls/Styles/Base/images/spinner_large.png -QtQuick/Controls/Styles/Base/images/spinner_medium.png -QtQuick/Controls/Styles/Base/images/spinner_small.png -QtQuick/Controls/Styles/Base/images/tab.png -QtQuick/Controls/Styles/Base/images/tab_selected.png -QtQuick/Controls/Styles/Desktop/ApplicationWindowStyle.qml -QtQuick/Controls/Styles/Desktop/ApplicationWindowStyle.qmlc -QtQuick/Controls/Styles/Desktop/BusyIndicatorStyle.qml -QtQuick/Controls/Styles/Desktop/BusyIndicatorStyle.qmlc -QtQuick/Controls/Styles/Desktop/ButtonStyle.qml -QtQuick/Controls/Styles/Desktop/ButtonStyle.qmlc -QtQuick/Controls/Styles/Desktop/CalendarStyle.qml -QtQuick/Controls/Styles/Desktop/CalendarStyle.qmlc -QtQuick/Controls/Styles/Desktop/CheckBoxStyle.qml -QtQuick/Controls/Styles/Desktop/CheckBoxStyle.qmlc -QtQuick/Controls/Styles/Desktop/ComboBoxStyle.qml -QtQuick/Controls/Styles/Desktop/ComboBoxStyle.qmlc -QtQuick/Controls/Styles/Desktop/FocusFrameStyle.qml -QtQuick/Controls/Styles/Desktop/FocusFrameStyle.qmlc -QtQuick/Controls/Styles/Desktop/GroupBoxStyle.qml -QtQuick/Controls/Styles/Desktop/GroupBoxStyle.qmlc -QtQuick/Controls/Styles/Desktop/MenuBarStyle.qml -QtQuick/Controls/Styles/Desktop/MenuBarStyle.qmlc -QtQuick/Controls/Styles/Desktop/MenuStyle.qml -QtQuick/Controls/Styles/Desktop/MenuStyle.qmlc -QtQuick/Controls/Styles/Desktop/ProgressBarStyle.qml -QtQuick/Controls/Styles/Desktop/ProgressBarStyle.qmlc -QtQuick/Controls/Styles/Desktop/RadioButtonStyle.qml -QtQuick/Controls/Styles/Desktop/RadioButtonStyle.qmlc -QtQuick/Controls/Styles/Desktop/RowItemSingleton.qml -QtQuick/Controls/Styles/Desktop/RowItemSingleton.qmlc -QtQuick/Controls/Styles/Desktop/ScrollViewStyle.qml -QtQuick/Controls/Styles/Desktop/ScrollViewStyle.qmlc -QtQuick/Controls/Styles/Desktop/SliderStyle.qml -QtQuick/Controls/Styles/Desktop/SliderStyle.qmlc -QtQuick/Controls/Styles/Desktop/SpinBoxStyle.qml -QtQuick/Controls/Styles/Desktop/SpinBoxStyle.qmlc -QtQuick/Controls/Styles/Desktop/StatusBarStyle.qml -QtQuick/Controls/Styles/Desktop/StatusBarStyle.qmlc -QtQuick/Controls/Styles/Desktop/SwitchStyle.qml -QtQuick/Controls/Styles/Desktop/SwitchStyle.qmlc -QtQuick/Controls/Styles/Desktop/TabViewStyle.qml -QtQuick/Controls/Styles/Desktop/TabViewStyle.qmlc -QtQuick/Controls/Styles/Desktop/TableViewStyle.qml -QtQuick/Controls/Styles/Desktop/TableViewStyle.qmlc -QtQuick/Controls/Styles/Desktop/TextAreaStyle.qml -QtQuick/Controls/Styles/Desktop/TextAreaStyle.qmlc -QtQuick/Controls/Styles/Desktop/TextFieldStyle.qml -QtQuick/Controls/Styles/Desktop/TextFieldStyle.qmlc -QtQuick/Controls/Styles/Desktop/ToolBarStyle.qml -QtQuick/Controls/Styles/Desktop/ToolBarStyle.qmlc -QtQuick/Controls/Styles/Desktop/ToolButtonStyle.qml -QtQuick/Controls/Styles/Desktop/ToolButtonStyle.qmlc -QtQuick/Controls/Styles/Desktop/TreeViewStyle.qml -QtQuick/Controls/Styles/Desktop/TreeViewStyle.qmlc -QtQuick/Controls/Styles/Desktop/qmldir -QtQuick/Controls/Styles/Flat/libqtquickextrasflatplugin.so -QtQuick/Controls/Styles/Flat/plugins.qmltypes -QtQuick/Controls/Styles/Flat/qmldir -QtQuick/Controls/Styles/qmldir -QtQuick/Controls/Switch.qml -QtQuick/Controls/Switch.qmlc -QtQuick/Controls/Tab.qml -QtQuick/Controls/Tab.qmlc -QtQuick/Controls/TabView.qml -QtQuick/Controls/TabView.qmlc -QtQuick/Controls/TableView.qml -QtQuick/Controls/TableView.qmlc -QtQuick/Controls/TableViewColumn.qml -QtQuick/Controls/TableViewColumn.qmlc -QtQuick/Controls/TextArea.qml -QtQuick/Controls/TextArea.qmlc -QtQuick/Controls/TextField.qml -QtQuick/Controls/TextField.qmlc -QtQuick/Controls/ToolBar.qml -QtQuick/Controls/ToolBar.qmlc -QtQuick/Controls/ToolButton.qml -QtQuick/Controls/ToolButton.qmlc -QtQuick/Controls/TreeView.qml -QtQuick/Controls/TreeView.qmlc -QtQuick/Controls/libqtquickcontrolsplugin.so +Qt/labs/platform/libqtlabsplatformplugin.so +Qt5Compat/GraphicalEffects/Blend.qml +Qt5Compat/GraphicalEffects/BrightnessContrast.qml +Qt5Compat/GraphicalEffects/Colorize.qml +Qt5Compat/GraphicalEffects/ColorOverlay.qml +Qt5Compat/GraphicalEffects/ConicalGradient.qml +Qt5Compat/GraphicalEffects/Desaturate.qml +Qt5Compat/GraphicalEffects/DirectionalBlur.qml +Qt5Compat/GraphicalEffects/Displace.qml +Qt5Compat/GraphicalEffects/DropShadow.qml +Qt5Compat/GraphicalEffects/FastBlur.qml +Qt5Compat/GraphicalEffects/GammaAdjust.qml +Qt5Compat/GraphicalEffects/GaussianBlur.qml +Qt5Compat/GraphicalEffects/Glow.qml +Qt5Compat/GraphicalEffects/HueSaturation.qml +Qt5Compat/GraphicalEffects/InnerShadow.qml +Qt5Compat/GraphicalEffects/LevelAdjust.qml +Qt5Compat/GraphicalEffects/LinearGradient.qml +Qt5Compat/GraphicalEffects/MaskedBlur.qml +Qt5Compat/GraphicalEffects/OpacityMask.qml +Qt5Compat/GraphicalEffects/plugins.qmltypes +Qt5Compat/GraphicalEffects/qmldir +Qt5Compat/GraphicalEffects/libqtgraphicaleffectsplugin.so +Qt5Compat/GraphicalEffects/RadialBlur.qml +Qt5Compat/GraphicalEffects/RadialGradient.qml +Qt5Compat/GraphicalEffects/RectangularGlow.qml +Qt5Compat/GraphicalEffects/RecursiveBlur.qml +Qt5Compat/GraphicalEffects/ThresholdMask.qml +Qt5Compat/GraphicalEffects/ZoomBlur.qml +Qt5Compat/GraphicalEffects/private/DropShadowBase.qml +Qt5Compat/GraphicalEffects/private/FastGlow.qml +Qt5Compat/GraphicalEffects/private/FastInnerShadow.qml +Qt5Compat/GraphicalEffects/private/GaussianDirectionalBlur.qml +Qt5Compat/GraphicalEffects/private/GaussianGlow.qml +Qt5Compat/GraphicalEffects/private/GaussianInnerShadow.qml +Qt5Compat/GraphicalEffects/private/GaussianMaskedBlur.qml +Qt5Compat/GraphicalEffects/private/plugins.qmltypes +Qt5Compat/GraphicalEffects/private/qmldir +Qt5Compat/GraphicalEffects/private/libqtgraphicaleffectsprivateplugin.so +QtCore/plugins.qmltypes +QtCore/qmldir +QtCore/libqtqmlcoreplugin.so +QtQml/qmldir +QtQml/libqmlmetaplugin.so +QtQml/Base/plugins.qmltypes +QtQml/Base/qmldir +QtQml/Base/libqmlplugin.so +QtQml/Models/libmodelsplugin.so +QtQml/Models/plugins.qmltypes +QtQml/Models/qmldir +QtQml/WorkerScript/plugins.qmltypes +QtQml/WorkerScript/qmldir +QtQml/WorkerScript/libworkerscriptplugin.so +QtQuick/plugins.qmltypes +QtQuick/qmldir +QtQuick/libqtquick2plugin.so QtQuick/Controls/plugins.qmltypes QtQuick/Controls/qmldir -QtQuick/Layouts/libqquicklayoutsplugin.so +QtQuick/Controls/libqtquickcontrols2plugin.so +QtQuick/Controls/Basic/AbstractButton.qml +QtQuick/Controls/Basic/Action.qml +QtQuick/Controls/Basic/ActionGroup.qml +QtQuick/Controls/Basic/ApplicationWindow.qml +QtQuick/Controls/Basic/BusyIndicator.qml +QtQuick/Controls/Basic/Button.qml +QtQuick/Controls/Basic/ButtonGroup.qml +QtQuick/Controls/Basic/Calendar.qml +QtQuick/Controls/Basic/CalendarModel.qml +QtQuick/Controls/Basic/CheckBox.qml +QtQuick/Controls/Basic/CheckDelegate.qml +QtQuick/Controls/Basic/ComboBox.qml +QtQuick/Controls/Basic/Container.qml +QtQuick/Controls/Basic/Control.qml +QtQuick/Controls/Basic/DayOfWeekRow.qml +QtQuick/Controls/Basic/DelayButton.qml +QtQuick/Controls/Basic/Dial.qml +QtQuick/Controls/Basic/Dialog.qml +QtQuick/Controls/Basic/DialogButtonBox.qml +QtQuick/Controls/Basic/Drawer.qml +QtQuick/Controls/Basic/Frame.qml +QtQuick/Controls/Basic/GroupBox.qml +QtQuick/Controls/Basic/HorizontalHeaderView.qml +QtQuick/Controls/Basic/ItemDelegate.qml +QtQuick/Controls/Basic/Label.qml +QtQuick/Controls/Basic/Menu.qml +QtQuick/Controls/Basic/MenuBar.qml +QtQuick/Controls/Basic/MenuBarItem.qml +QtQuick/Controls/Basic/MenuItem.qml +QtQuick/Controls/Basic/MenuSeparator.qml +QtQuick/Controls/Basic/MonthGrid.qml +QtQuick/Controls/Basic/Page.qml +QtQuick/Controls/Basic/PageIndicator.qml +QtQuick/Controls/Basic/Pane.qml +QtQuick/Controls/Basic/plugins.qmltypes +QtQuick/Controls/Basic/Popup.qml +QtQuick/Controls/Basic/ProgressBar.qml +QtQuick/Controls/Basic/qmldir +QtQuick/Controls/Basic/libqtquickcontrols2basicstyleplugin.so +QtQuick/Controls/Basic/RadioButton.qml +QtQuick/Controls/Basic/RadioDelegate.qml +QtQuick/Controls/Basic/RangeSlider.qml +QtQuick/Controls/Basic/RoundButton.qml +QtQuick/Controls/Basic/ScrollBar.qml +QtQuick/Controls/Basic/ScrollIndicator.qml +QtQuick/Controls/Basic/ScrollView.qml +QtQuick/Controls/Basic/SelectionRectangle.qml +QtQuick/Controls/Basic/Slider.qml +QtQuick/Controls/Basic/SpinBox.qml +QtQuick/Controls/Basic/SplitView.qml +QtQuick/Controls/Basic/StackView.qml +QtQuick/Controls/Basic/SwipeDelegate.qml +QtQuick/Controls/Basic/SwipeView.qml +QtQuick/Controls/Basic/Switch.qml +QtQuick/Controls/Basic/SwitchDelegate.qml +QtQuick/Controls/Basic/TabBar.qml +QtQuick/Controls/Basic/TabButton.qml +QtQuick/Controls/Basic/TextArea.qml +QtQuick/Controls/Basic/TextField.qml +QtQuick/Controls/Basic/ToolBar.qml +QtQuick/Controls/Basic/ToolButton.qml +QtQuick/Controls/Basic/ToolSeparator.qml +QtQuick/Controls/Basic/ToolTip.qml +QtQuick/Controls/Basic/TreeViewDelegate.qml +QtQuick/Controls/Basic/Tumbler.qml +QtQuick/Controls/Basic/VerticalHeaderView.qml +QtQuick/Controls/Basic/WeekNumberColumn.qml +QtQuick/Controls/Basic/impl/plugins.qmltypes +QtQuick/Controls/Basic/impl/qmldir +QtQuick/Controls/Basic/impl/libqtquickcontrols2basicstyleimplplugin.so +QtQuick/Controls/Fusion/ApplicationWindow.qml +QtQuick/Controls/Fusion/BusyIndicator.qml +QtQuick/Controls/Fusion/Button.qml +QtQuick/Controls/Fusion/CheckBox.qml +QtQuick/Controls/Fusion/CheckDelegate.qml +QtQuick/Controls/Fusion/ComboBox.qml +QtQuick/Controls/Fusion/DelayButton.qml +QtQuick/Controls/Fusion/Dial.qml +QtQuick/Controls/Fusion/Dialog.qml +QtQuick/Controls/Fusion/DialogButtonBox.qml +QtQuick/Controls/Fusion/Drawer.qml +QtQuick/Controls/Fusion/Frame.qml +QtQuick/Controls/Fusion/GroupBox.qml +QtQuick/Controls/Fusion/HorizontalHeaderView.qml +QtQuick/Controls/Fusion/ItemDelegate.qml +QtQuick/Controls/Fusion/Label.qml +QtQuick/Controls/Fusion/Menu.qml +QtQuick/Controls/Fusion/MenuBar.qml +QtQuick/Controls/Fusion/MenuBarItem.qml +QtQuick/Controls/Fusion/MenuItem.qml +QtQuick/Controls/Fusion/MenuSeparator.qml +QtQuick/Controls/Fusion/Page.qml +QtQuick/Controls/Fusion/PageIndicator.qml +QtQuick/Controls/Fusion/Pane.qml +QtQuick/Controls/Fusion/plugins.qmltypes +QtQuick/Controls/Fusion/Popup.qml +QtQuick/Controls/Fusion/ProgressBar.qml +QtQuick/Controls/Fusion/qmldir +QtQuick/Controls/Fusion/libqtquickcontrols2fusionstyleplugin.so +QtQuick/Controls/Fusion/RadioButton.qml +QtQuick/Controls/Fusion/RadioDelegate.qml +QtQuick/Controls/Fusion/RangeSlider.qml +QtQuick/Controls/Fusion/RoundButton.qml +QtQuick/Controls/Fusion/ScrollBar.qml +QtQuick/Controls/Fusion/ScrollIndicator.qml +QtQuick/Controls/Fusion/ScrollView.qml +QtQuick/Controls/Fusion/SelectionRectangle.qml +QtQuick/Controls/Fusion/Slider.qml +QtQuick/Controls/Fusion/SpinBox.qml +QtQuick/Controls/Fusion/SplitView.qml +QtQuick/Controls/Fusion/SwipeDelegate.qml +QtQuick/Controls/Fusion/Switch.qml +QtQuick/Controls/Fusion/SwitchDelegate.qml +QtQuick/Controls/Fusion/TabBar.qml +QtQuick/Controls/Fusion/TabButton.qml +QtQuick/Controls/Fusion/TextArea.qml +QtQuick/Controls/Fusion/TextField.qml +QtQuick/Controls/Fusion/ToolBar.qml +QtQuick/Controls/Fusion/ToolButton.qml +QtQuick/Controls/Fusion/ToolSeparator.qml +QtQuick/Controls/Fusion/ToolTip.qml +QtQuick/Controls/Fusion/TreeViewDelegate.qml +QtQuick/Controls/Fusion/Tumbler.qml +QtQuick/Controls/Fusion/VerticalHeaderView.qml +QtQuick/Controls/Fusion/impl/ButtonPanel.qml +QtQuick/Controls/Fusion/impl/CheckIndicator.qml +QtQuick/Controls/Fusion/impl/plugins.qmltypes +QtQuick/Controls/Fusion/impl/qmldir +QtQuick/Controls/Fusion/impl/libqtquickcontrols2fusionstyleimplplugin.so +QtQuick/Controls/Fusion/impl/RadioIndicator.qml +QtQuick/Controls/Fusion/impl/SliderGroove.qml +QtQuick/Controls/Fusion/impl/SliderHandle.qml +QtQuick/Controls/Fusion/impl/SwitchIndicator.qml +QtQuick/Controls/Imagine/ApplicationWindow.qml +QtQuick/Controls/Imagine/BusyIndicator.qml +QtQuick/Controls/Imagine/Button.qml +QtQuick/Controls/Imagine/CheckBox.qml +QtQuick/Controls/Imagine/CheckDelegate.qml +QtQuick/Controls/Imagine/ComboBox.qml +QtQuick/Controls/Imagine/DelayButton.qml +QtQuick/Controls/Imagine/Dial.qml +QtQuick/Controls/Imagine/Dialog.qml +QtQuick/Controls/Imagine/DialogButtonBox.qml +QtQuick/Controls/Imagine/Drawer.qml +QtQuick/Controls/Imagine/Frame.qml +QtQuick/Controls/Imagine/GroupBox.qml +QtQuick/Controls/Imagine/HorizontalHeaderView.qml +QtQuick/Controls/Imagine/ItemDelegate.qml +QtQuick/Controls/Imagine/Label.qml +QtQuick/Controls/Imagine/Menu.qml +QtQuick/Controls/Imagine/MenuItem.qml +QtQuick/Controls/Imagine/MenuSeparator.qml +QtQuick/Controls/Imagine/Page.qml +QtQuick/Controls/Imagine/PageIndicator.qml +QtQuick/Controls/Imagine/Pane.qml +QtQuick/Controls/Imagine/plugins.qmltypes +QtQuick/Controls/Imagine/Popup.qml +QtQuick/Controls/Imagine/ProgressBar.qml +QtQuick/Controls/Imagine/qmldir +QtQuick/Controls/Imagine/libqtquickcontrols2imaginestyleplugin.so +QtQuick/Controls/Imagine/RadioButton.qml +QtQuick/Controls/Imagine/RadioDelegate.qml +QtQuick/Controls/Imagine/RangeSlider.qml +QtQuick/Controls/Imagine/RoundButton.qml +QtQuick/Controls/Imagine/ScrollBar.qml +QtQuick/Controls/Imagine/ScrollIndicator.qml +QtQuick/Controls/Imagine/ScrollView.qml +QtQuick/Controls/Imagine/SelectionRectangle.qml +QtQuick/Controls/Imagine/Slider.qml +QtQuick/Controls/Imagine/SpinBox.qml +QtQuick/Controls/Imagine/SplitView.qml +QtQuick/Controls/Imagine/StackView.qml +QtQuick/Controls/Imagine/SwipeDelegate.qml +QtQuick/Controls/Imagine/SwipeView.qml +QtQuick/Controls/Imagine/Switch.qml +QtQuick/Controls/Imagine/SwitchDelegate.qml +QtQuick/Controls/Imagine/TabBar.qml +QtQuick/Controls/Imagine/TabButton.qml +QtQuick/Controls/Imagine/TextArea.qml +QtQuick/Controls/Imagine/TextField.qml +QtQuick/Controls/Imagine/ToolBar.qml +QtQuick/Controls/Imagine/ToolButton.qml +QtQuick/Controls/Imagine/ToolSeparator.qml +QtQuick/Controls/Imagine/ToolTip.qml +QtQuick/Controls/Imagine/Tumbler.qml +QtQuick/Controls/Imagine/VerticalHeaderView.qml +QtQuick/Controls/Imagine/impl/OpacityMask.qml +QtQuick/Controls/Imagine/impl/qmldir +QtQuick/Controls/Imagine/impl/libqtquickcontrols2imaginestyleimplplugin.so +QtQuick/Controls/Imagine/impl/QuickControls2ImagineStyleImpl.qmltypes +QtQuick/Controls/impl/plugins.qmltypes +QtQuick/Controls/impl/qmldir +QtQuick/Controls/impl/libqtquickcontrols2implplugin.so +QtQuick/Controls/Material/ApplicationWindow.qml +QtQuick/Controls/Material/BusyIndicator.qml +QtQuick/Controls/Material/Button.qml +QtQuick/Controls/Material/CheckBox.qml +QtQuick/Controls/Material/CheckDelegate.qml +QtQuick/Controls/Material/ComboBox.qml +QtQuick/Controls/Material/DelayButton.qml +QtQuick/Controls/Material/Dial.qml +QtQuick/Controls/Material/Dialog.qml +QtQuick/Controls/Material/DialogButtonBox.qml +QtQuick/Controls/Material/Drawer.qml +QtQuick/Controls/Material/Frame.qml +QtQuick/Controls/Material/GroupBox.qml +QtQuick/Controls/Material/HorizontalHeaderView.qml +QtQuick/Controls/Material/ItemDelegate.qml +QtQuick/Controls/Material/Label.qml +QtQuick/Controls/Material/Menu.qml +QtQuick/Controls/Material/MenuBar.qml +QtQuick/Controls/Material/MenuBarItem.qml +QtQuick/Controls/Material/MenuItem.qml +QtQuick/Controls/Material/MenuSeparator.qml +QtQuick/Controls/Material/Page.qml +QtQuick/Controls/Material/PageIndicator.qml +QtQuick/Controls/Material/Pane.qml +QtQuick/Controls/Material/plugins.qmltypes +QtQuick/Controls/Material/Popup.qml +QtQuick/Controls/Material/ProgressBar.qml +QtQuick/Controls/Material/qmldir +QtQuick/Controls/Material/libqtquickcontrols2materialstyleplugin.so +QtQuick/Controls/Material/RadioButton.qml +QtQuick/Controls/Material/RadioDelegate.qml +QtQuick/Controls/Material/RangeSlider.qml +QtQuick/Controls/Material/RoundButton.qml +QtQuick/Controls/Material/ScrollBar.qml +QtQuick/Controls/Material/ScrollIndicator.qml +QtQuick/Controls/Material/ScrollView.qml +QtQuick/Controls/Material/SelectionRectangle.qml +QtQuick/Controls/Material/Slider.qml +QtQuick/Controls/Material/SpinBox.qml +QtQuick/Controls/Material/SplitView.qml +QtQuick/Controls/Material/StackView.qml +QtQuick/Controls/Material/SwipeDelegate.qml +QtQuick/Controls/Material/SwipeView.qml +QtQuick/Controls/Material/Switch.qml +QtQuick/Controls/Material/SwitchDelegate.qml +QtQuick/Controls/Material/TabBar.qml +QtQuick/Controls/Material/TabButton.qml +QtQuick/Controls/Material/TextArea.qml +QtQuick/Controls/Material/TextField.qml +QtQuick/Controls/Material/ToolBar.qml +QtQuick/Controls/Material/ToolButton.qml +QtQuick/Controls/Material/ToolSeparator.qml +QtQuick/Controls/Material/ToolTip.qml +QtQuick/Controls/Material/TreeViewDelegate.qml +QtQuick/Controls/Material/Tumbler.qml +QtQuick/Controls/Material/VerticalHeaderView.qml +QtQuick/Controls/Material/impl/BoxShadow.qml +QtQuick/Controls/Material/impl/CheckIndicator.qml +QtQuick/Controls/Material/impl/CursorDelegate.qml +QtQuick/Controls/Material/impl/ElevationEffect.qml +QtQuick/Controls/Material/impl/plugins.qmltypes +QtQuick/Controls/Material/impl/qmldir +QtQuick/Controls/Material/impl/libqtquickcontrols2materialstyleimplplugin.so +QtQuick/Controls/Material/impl/RadioIndicator.qml +QtQuick/Controls/Material/impl/RectangularGlow.qml +QtQuick/Controls/Material/impl/RoundedElevationEffect.qml +QtQuick/Controls/Material/impl/SliderHandle.qml +QtQuick/Controls/Material/impl/SwitchIndicator.qml +QtQuick/Controls/Universal/ApplicationWindow.qml +QtQuick/Controls/Universal/BusyIndicator.qml +QtQuick/Controls/Universal/Button.qml +QtQuick/Controls/Universal/CheckBox.qml +QtQuick/Controls/Universal/CheckDelegate.qml +QtQuick/Controls/Universal/ComboBox.qml +QtQuick/Controls/Universal/DelayButton.qml +QtQuick/Controls/Universal/Dial.qml +QtQuick/Controls/Universal/Dialog.qml +QtQuick/Controls/Universal/DialogButtonBox.qml +QtQuick/Controls/Universal/Drawer.qml +QtQuick/Controls/Universal/Frame.qml +QtQuick/Controls/Universal/GroupBox.qml +QtQuick/Controls/Universal/HorizontalHeaderView.qml +QtQuick/Controls/Universal/ItemDelegate.qml +QtQuick/Controls/Universal/Label.qml +QtQuick/Controls/Universal/Menu.qml +QtQuick/Controls/Universal/MenuBar.qml +QtQuick/Controls/Universal/MenuBarItem.qml +QtQuick/Controls/Universal/MenuItem.qml +QtQuick/Controls/Universal/MenuSeparator.qml +QtQuick/Controls/Universal/Page.qml +QtQuick/Controls/Universal/PageIndicator.qml +QtQuick/Controls/Universal/Pane.qml +QtQuick/Controls/Universal/plugins.qmltypes +QtQuick/Controls/Universal/Popup.qml +QtQuick/Controls/Universal/ProgressBar.qml +QtQuick/Controls/Universal/qmldir +QtQuick/Controls/Universal/libqtquickcontrols2universalstyleplugin.so +QtQuick/Controls/Universal/RadioButton.qml +QtQuick/Controls/Universal/RadioDelegate.qml +QtQuick/Controls/Universal/RangeSlider.qml +QtQuick/Controls/Universal/RoundButton.qml +QtQuick/Controls/Universal/ScrollBar.qml +QtQuick/Controls/Universal/ScrollIndicator.qml +QtQuick/Controls/Universal/ScrollView.qml +QtQuick/Controls/Universal/SelectionRectangle.qml +QtQuick/Controls/Universal/Slider.qml +QtQuick/Controls/Universal/SpinBox.qml +QtQuick/Controls/Universal/SplitView.qml +QtQuick/Controls/Universal/StackView.qml +QtQuick/Controls/Universal/SwipeDelegate.qml +QtQuick/Controls/Universal/Switch.qml +QtQuick/Controls/Universal/SwitchDelegate.qml +QtQuick/Controls/Universal/TabBar.qml +QtQuick/Controls/Universal/TabButton.qml +QtQuick/Controls/Universal/TextArea.qml +QtQuick/Controls/Universal/TextField.qml +QtQuick/Controls/Universal/ToolBar.qml +QtQuick/Controls/Universal/ToolButton.qml +QtQuick/Controls/Universal/ToolSeparator.qml +QtQuick/Controls/Universal/ToolTip.qml +QtQuick/Controls/Universal/Tumbler.qml +QtQuick/Controls/Universal/VerticalHeaderView.qml +QtQuick/Controls/Universal/impl/CheckIndicator.qml +QtQuick/Controls/Universal/impl/plugins.qmltypes +QtQuick/Controls/Universal/impl/qmldir +QtQuick/Controls/Universal/impl/libqtquickcontrols2universalstyleimplplugin.so +QtQuick/Controls/Universal/impl/RadioIndicator.qml +QtQuick/Controls/Universal/impl/SwitchIndicator.qml QtQuick/Layouts/plugins.qmltypes QtQuick/Layouts/qmldir -QtQuick/Templates.2/libqtquicktemplates2plugin.so -QtQuick/Templates.2/plugins.qmltypes -QtQuick/Templates.2/qmldir -QtQuick/Window.2/libwindowplugin.so -QtQuick/Window.2/plugins.qmltypes -QtQuick/Window.2/qmldir +QtQuick/Layouts/libqquicklayoutsplugin.so +QtQuick/NativeStyle/plugins.qmltypes +QtQuick/NativeStyle/qmldir +QtQuick/NativeStyle/libqtquickcontrols2nativestyleplugin.so +QtQuick/NativeStyle/controls/DefaultButton.qml +QtQuick/NativeStyle/controls/DefaultCheckBox.qml +QtQuick/NativeStyle/controls/DefaultComboBox.qml +QtQuick/NativeStyle/controls/DefaultDial.qml +QtQuick/NativeStyle/controls/DefaultFrame.qml +QtQuick/NativeStyle/controls/DefaultGroupBox.qml +QtQuick/NativeStyle/controls/DefaultItemDelegate.qml +QtQuick/NativeStyle/controls/DefaultItemDelegateIconLabel.qml +QtQuick/NativeStyle/controls/DefaultProgressBar.qml +QtQuick/NativeStyle/controls/DefaultRadioButton.qml +QtQuick/NativeStyle/controls/DefaultRadioDelegate.qml +QtQuick/NativeStyle/controls/DefaultScrollBar.qml +QtQuick/NativeStyle/controls/DefaultSlider.qml +QtQuick/NativeStyle/controls/DefaultSpinBox.qml +QtQuick/NativeStyle/controls/DefaultTextArea.qml +QtQuick/NativeStyle/controls/DefaultTextField.qml +QtQuick/NativeStyle/controls/DefaultTreeViewDelegate.qml +QtQuick/Shapes/plugins.qmltypes +QtQuick/Shapes/qmldir +QtQuick/Shapes/libqmlshapesplugin.so +QtQuick/Templates/plugins.qmltypes +QtQuick/Templates/qmldir +QtQuick/Templates/libqtquicktemplates2plugin.so +QtQuick/Window/qmldir +QtQuick/Window/quickwindow.qmltypes +QtQuick/Window/libquickwindowplugin.so From f5036ffac1b0fd412bf40cd3a3adf37d714e97d8 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 9 Aug 2024 02:25:05 +0900 Subject: [PATCH 020/127] =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=97=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 704263c7..3621cfc4 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -58,8 +58,8 @@ deploy_hagoromo(){ cp ${build_dir}/Hagoromo ${work_dir}/bin cp ${SCRIPT_FOLDER}/deploy/Hagoromo.sh ${work_dir} - cp "openssl/lib/libcrypto.so.1.1" ${work_dir}/lib - cp "openssl/lib/libssl.so.1.1" ${work_dir}/lib + cp "openssl/lib64/libcrypto.so.3" ${work_dir}/lib + cp "openssl/lib64/libssl.so.3" ${work_dir}/lib cp "app/i18n/app_ja.qm" ${work_dir}/bin/translations cp ${QT_BIN_FOLDER}/../translations/qt_ja.qm ${work_dir}/bin/translations From 6e432ec45250c3003f2d0024edff244e5205e4c3 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 12 Aug 2024 01:28:28 +0900 Subject: [PATCH 021/127] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E3=81=AE=E5=A4=89=E6=95=B0=E5=8F=82=E7=85=A7=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/view/ProfileView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/view/ProfileView.qml b/app/qml/view/ProfileView.qml index 32af03e8..89e23760 100644 --- a/app/qml/view/ProfileView.qml +++ b/app/qml/view/ProfileView.qml @@ -378,7 +378,7 @@ ColumnLayout { onHoveredLinkChanged: profileView.hoveredLink = hoveredLink onLinkActivated: (url) => Qt.openUrlExternally(url) - onContentHeightChanged: Layout.preferredHeight = contentHeight + onContentHeightChanged: Layout.preferredHeight = descriptionLabel.contentHeight Behavior on Layout.preferredHeight { NumberAnimation { duration: 500 } } From 5aa1ec7bd5b4fe8b2538dc4963437c59a01ce7ee Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 12 Aug 2024 01:30:34 +0900 Subject: [PATCH 022/127] =?UTF-8?q?ClickableFrame=E3=81=AE=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=82=BA=E6=8C=87=E5=AE=9A=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/controls/ClickableFrame.qml | 8 +++++--- app/qml/parts/ExternalLinkCard.qml | 3 +++ app/qml/parts/FeedGeneratorLinkCard.qml | 4 ++++ app/qml/parts/ListLinkCard.qml | 4 ++++ app/qml/parts/NotificationDelegate.qml | 6 ++++++ app/qml/parts/PostDelegate.qml | 3 +++ app/qml/parts/QuoteRecord.qml | 2 ++ app/qml/view/BlogEntryListView.qml | 3 +++ app/qml/view/ChatListView.qml | 3 +++ app/qml/view/ChatMessageListView.qml | 3 +++ app/qml/view/ColumnView.qml | 2 ++ app/qml/view/ListDetailView.qml | 3 +++ app/qml/view/ListsListView.qml | 3 +++ app/qml/view/ProfileListView.qml | 3 +++ app/qml/view/SuggestionProfileListView.qml | 3 +++ 15 files changed, 50 insertions(+), 3 deletions(-) diff --git a/app/qml/controls/ClickableFrame.qml b/app/qml/controls/ClickableFrame.qml index e25a9393..7130c40e 100644 --- a/app/qml/controls/ClickableFrame.qml +++ b/app/qml/controls/ClickableFrame.qml @@ -12,7 +12,11 @@ Frame { property string style: "Normal" property color borderColor: Material.color(Material.Grey, Material.Shade600) - background: MouseArea { + contentItem: MouseArea { + onClicked: (mouse) => clickableFrame.clicked(mouse) + } + + background: Item { Rectangle { id: backgroundRect states: [ @@ -65,7 +69,5 @@ Frame { color: "transparent" radius: 2 } - - onClicked: (mouse) => clickableFrame.clicked(mouse) } } diff --git a/app/qml/parts/ExternalLinkCard.qml b/app/qml/parts/ExternalLinkCard.qml index 52d219f1..9a15c14e 100644 --- a/app/qml/parts/ExternalLinkCard.qml +++ b/app/qml/parts/ExternalLinkCard.qml @@ -9,6 +9,8 @@ import "../controls" ClickableFrame { id: externalLinkFrame + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight topInset: 0 leftInset: 0 rightInset: 0 @@ -24,6 +26,7 @@ ClickableFrame { property alias descriptionLabel: descriptionLabel ColumnLayout { + id: contentRootLayout spacing: 3 ImageWithIndicator { id: thumbImage diff --git a/app/qml/parts/FeedGeneratorLinkCard.qml b/app/qml/parts/FeedGeneratorLinkCard.qml index abbb869c..2b99a4de 100644 --- a/app/qml/parts/FeedGeneratorLinkCard.qml +++ b/app/qml/parts/FeedGeneratorLinkCard.qml @@ -9,12 +9,16 @@ import "../controls" import "../compat" ClickableFrame { + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight + property alias avatarImage: feedGeneratorAvatarImage property alias displayNameLabel: feedGeneratorDisplayNameLabel property alias creatorHandleLabel: feedGeneratorCreatorHandleLabel property alias likeCountLabel: feedGeneratorLikeCountLabel ColumnLayout { + id: contentRootLayout GridLayout { columns: 2 rowSpacing: 3 diff --git a/app/qml/parts/ListLinkCard.qml b/app/qml/parts/ListLinkCard.qml index 39c64e24..f4d6f01e 100644 --- a/app/qml/parts/ListLinkCard.qml +++ b/app/qml/parts/ListLinkCard.qml @@ -8,12 +8,16 @@ import tech.relog.hagoromo.singleton 1.0 import "../controls" ClickableFrame { + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight + property alias avatarImage: avatarImage property alias displayNameLabel: displayNameLabel property alias creatorHandleLabel: creatorHandleLabel property alias descriptionLabel: descriptionLabel ColumnLayout { + id: contentRootLayout GridLayout { columns: 2 rowSpacing: 3 diff --git a/app/qml/parts/NotificationDelegate.qml b/app/qml/parts/NotificationDelegate.qml index 88e818ba..3c4ff1e3 100644 --- a/app/qml/parts/NotificationDelegate.qml +++ b/app/qml/parts/NotificationDelegate.qml @@ -12,6 +12,8 @@ import "../compat" ClickableFrame { id: notificationFrame + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight topPadding: 10 leftPadding: 10 rightPadding: 10 @@ -146,6 +148,7 @@ ClickableFrame { ] ColumnLayout { + id: contentRootLayout spacing: 0 CoverFrame { @@ -325,10 +328,13 @@ ClickableFrame { Layout.preferredWidth: parent.width Layout.topMargin: 5 visible: false + contentWidth: quoteRecordContentLayout.implicitWidth + contentHeight: quoteRecordContentLayout.implicitHeight property int basisWidth: parent.width - padding * 2 ColumnLayout { + id: quoteRecordContentLayout RowLayout { id: quoteRecordAuthorLayout AvatarImage { diff --git a/app/qml/parts/PostDelegate.qml b/app/qml/parts/PostDelegate.qml index 4ea37586..ffc62efd 100644 --- a/app/qml/parts/PostDelegate.qml +++ b/app/qml/parts/PostDelegate.qml @@ -10,6 +10,8 @@ import "../controls" ClickableFrame { id: postFrame + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight topPadding: 10 leftPadding: 10 rightPadding: 10 @@ -79,6 +81,7 @@ ClickableFrame { } ColumnLayout { + id: contentRootLayout states: [ State { when: moderationFrame.showContent === false diff --git a/app/qml/parts/QuoteRecord.qml b/app/qml/parts/QuoteRecord.qml index 22ad80d7..889b3d23 100644 --- a/app/qml/parts/QuoteRecord.qml +++ b/app/qml/parts/QuoteRecord.qml @@ -9,6 +9,8 @@ import "../controls" ClickableFrame { id: quoteRecordFrame + contentWidth: quoteRecordLayout.implicitWidth + contentHeight: quoteRecordLayout.implicitHeight property int basisWidth: width property alias quoteRecordAvatarImage: quoteRecordAvatarImage diff --git a/app/qml/view/BlogEntryListView.qml b/app/qml/view/BlogEntryListView.qml index ea6179d4..e4e8492f 100644 --- a/app/qml/view/BlogEntryListView.qml +++ b/app/qml/view/BlogEntryListView.qml @@ -29,8 +29,11 @@ ScrollView { id: blogEntryFrame clip: true hoverEnabled: true + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight ColumnLayout { + id: contentRootLayout property int basisWidth: blogListView.width * 0.8 - blogEntryFrame.leftPadding - blogEntryFrame.rightPadding RowLayout { diff --git a/app/qml/view/ChatListView.qml b/app/qml/view/ChatListView.qml index 3c196739..64695f97 100644 --- a/app/qml/view/ChatListView.qml +++ b/app/qml/view/ChatListView.qml @@ -114,6 +114,8 @@ Item { delegate: ClickableFrame { id: chatItemLayout + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight clip: true topPadding: 10 leftPadding: 10 @@ -130,6 +132,7 @@ Item { } RowLayout { + id: contentRootLayout AvatarImage { id: postAvatarImage Layout.preferredWidth: AdjustedValues.i36 diff --git a/app/qml/view/ChatMessageListView.qml b/app/qml/view/ChatMessageListView.qml index d97dffdf..ed283112 100644 --- a/app/qml/view/ChatMessageListView.qml +++ b/app/qml/view/ChatMessageListView.qml @@ -74,10 +74,13 @@ Item { ClickableFrame { Layout.fillWidth: true Layout.topMargin: 1 + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight topPadding: 5 bottomPadding: 5 visible: rootListView.model.memberHandles.length > 0 RowLayout { + id: contentRootLayout AvatarImage { id: memberAvatarsImage Layout.preferredWidth: AdjustedValues.i24 diff --git a/app/qml/view/ColumnView.qml b/app/qml/view/ColumnView.qml index a90dacce..ab726571 100644 --- a/app/qml/view/ColumnView.qml +++ b/app/qml/view/ColumnView.qml @@ -692,6 +692,8 @@ ColumnLayout { id: profileFrame Layout.fillWidth: true Layout.topMargin: 1 + contentWidth: headerLayout.implicitWidth + contentHeight: headerLayout.implicitHeight leftPadding: 0 topPadding: 0 rightPadding: 10 diff --git a/app/qml/view/ListDetailView.qml b/app/qml/view/ListDetailView.qml index 6669a0ba..cfff0c66 100644 --- a/app/qml/view/ListDetailView.qml +++ b/app/qml/view/ListDetailView.qml @@ -261,6 +261,8 @@ ColumnLayout { delegate: ClickableFrame { id: listItemLayout + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight clip: true style: "Post" topPadding: 10 @@ -273,6 +275,7 @@ ColumnLayout { RowLayout{ + id: contentRootLayout AvatarImage { id: postAvatarImage Layout.preferredWidth: AdjustedValues.i36 diff --git a/app/qml/view/ListsListView.qml b/app/qml/view/ListsListView.qml index a6fd809d..eb2cedd0 100644 --- a/app/qml/view/ListsListView.qml +++ b/app/qml/view/ListsListView.qml @@ -64,6 +64,8 @@ ScrollView { delegate: ClickableFrame { id: listsLayout + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight clip: true style: "Post" topPadding: 10 @@ -111,6 +113,7 @@ ScrollView { ] RowLayout{ + id: contentRootLayout spacing: 10 AvatarImage { id: postAvatarImage diff --git a/app/qml/view/ProfileListView.qml b/app/qml/view/ProfileListView.qml index 5faaffd1..f6fc0236 100644 --- a/app/qml/view/ProfileListView.qml +++ b/app/qml/view/ProfileListView.qml @@ -87,6 +87,8 @@ ScrollView { delegate: ClickableFrame { id: profileLayout + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight clip: true style: "Post" topPadding: 10 @@ -169,6 +171,7 @@ ScrollView { ] RowLayout{ + id: contentRootLayout AvatarImage { id: postAvatarImage Layout.preferredWidth: AdjustedValues.i36 diff --git a/app/qml/view/SuggestionProfileListView.qml b/app/qml/view/SuggestionProfileListView.qml index 45dccd04..d4a43eb6 100644 --- a/app/qml/view/SuggestionProfileListView.qml +++ b/app/qml/view/SuggestionProfileListView.qml @@ -17,6 +17,8 @@ ListView { delegate: ClickableFrame { id: profileLayout + contentWidth: contentRootLayout.implicitWidth + contentHeight: contentRootLayout.implicitHeight clip: true style: "Post" topPadding: 10 @@ -31,6 +33,7 @@ ListView { onClicked: suggestionProfileListView.selectedProfile(model.did) RowLayout{ + id: contentRootLayout AvatarImage { id: postAvatarImage Layout.preferredWidth: AdjustedValues.i36 From aec4633e7494b36ef88ef8cacc1fba8c4b6d3c58 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 17 Aug 2024 21:47:16 +0900 Subject: [PATCH 023/127] =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E3=81=AE?= =?UTF-8?q?=E6=A8=AA=E5=B9=85=E3=81=AE=E5=8F=8D=E6=98=A0=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/parts/ImagePreview.qml | 2 ++ app/qml/parts/PostDelegate.qml | 10 ++++++++-- app/qml/view/NotificationListView.qml | 14 ++++++++------ app/qml/view/TimelineView.qml | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/qml/parts/ImagePreview.qml b/app/qml/parts/ImagePreview.qml index 45ed9467..c531743a 100644 --- a/app/qml/parts/ImagePreview.qml +++ b/app/qml/parts/ImagePreview.qml @@ -12,6 +12,8 @@ GridLayout { columnSpacing: 6 rowSpacing: 6 columns: 2 + implicitWidth: imagePreviewLayout.width + implicitHeight: imagePreviewLayout.height // 0:compact, 1:normal, 2:when one is whole, 3:all whole property int layoutType: 1 diff --git a/app/qml/parts/PostDelegate.qml b/app/qml/parts/PostDelegate.qml index ffc62efd..f3abf1d5 100644 --- a/app/qml/parts/PostDelegate.qml +++ b/app/qml/parts/PostDelegate.qml @@ -82,6 +82,7 @@ ClickableFrame { ColumnLayout { id: contentRootLayout + implicitWidth: postFrame.layoutWidth - postFrame.leftPadding - postFrame.rightPadding states: [ State { when: moderationFrame.showContent === false @@ -95,7 +96,8 @@ ClickableFrame { CoverFrame { id: moderationFrame - Layout.preferredWidth: postFrame.layoutWidth - postFrame.leftPadding - postFrame.rightPadding + Layout.fillWidth: true + // Layout.preferredWidth: postFrame.layoutWidth - postFrame.leftPadding - postFrame.rightPadding Layout.bottomMargin: 8 visible: false labelText: qsTr("Post from an account you muted.") @@ -114,16 +116,19 @@ ClickableFrame { PinnedIndicator { id: pinnedIndicatorLabel Layout.fillWidth: true + Layout.maximumWidth: postFrame.layoutWidth Layout.preferredHeight: AdjustedValues.i12 * 1.2 } ReactionAuthor { id: repostReactionAuthor + Layout.fillWidth: true Layout.maximumWidth: postFrame.layoutWidth source: "../images/repost.png" color: Material.color(Material.Green) } ReactionAuthor { id: replyReactionAuthor + Layout.fillWidth: true Layout.maximumWidth: postFrame.layoutWidth source: "../images/reply.png" color: Material.color(Material.Blue) @@ -132,6 +137,7 @@ ClickableFrame { RowLayout { id: postLayout spacing: 10 + Layout.maximumWidth: parent.implicitWidth AvatarImage { id: postAvatarImage Layout.preferredWidth: AdjustedValues.i36 @@ -163,7 +169,7 @@ ClickableFrame { spacing: 0 property int basisWidth: postFrame.layoutWidth - postFrame.leftPadding - postFrame.rightPadding - - postLayout.spacing - postAvatarImage.Layout.preferredWidth + postLayout.spacing - AdjustedValues.i36 //postAvatarImage.Layout.preferredWidth Author { id: postAuthor Layout.preferredWidth: parent.basisWidth diff --git a/app/qml/view/NotificationListView.qml b/app/qml/view/NotificationListView.qml index c1e31b92..4d06c356 100644 --- a/app/qml/view/NotificationListView.qml +++ b/app/qml/view/NotificationListView.qml @@ -89,6 +89,14 @@ ScrollView { userFilterMatched: model.userFilterMatched userFilterMessage: model.userFilterMessage + contentFilterFrame.visible: model.contentFilterMatched + contentMediaFilterFrame.visible: model.contentMediaFilterMatched + postImagePreview.visible: contentMediaFilterFrame.showContent && model.embedImages.length > 0 + + externalLinkFrame.visible: model.hasExternalLink && contentMediaFilterFrame.showContent + feedGeneratorFrame.visible: model.hasFeedGenerator && contentMediaFilterFrame.showContent + listLinkCardFrame.visible: model.hasListLink && contentMediaFilterFrame.showContent + reason: model.reason postAvatarImage.source: model.avatar postAvatarImage.onClicked: requestViewProfile(model.did) @@ -108,11 +116,8 @@ ScrollView { } return text } - contentFilterFrame.visible: model.contentFilterMatched contentFilterFrame.labelText: model.contentFilterMessage - contentMediaFilterFrame.visible: model.contentMediaFilterMatched contentMediaFilterFrame.labelText: model.contentMediaFilterMessage - postImagePreview.visible: contentMediaFilterFrame.showContent && model.embedImages.length > 0 postImagePreview.layoutType: notificationListView.imageLayoutType postImagePreview.embedImages: model.embedImages postImagePreview.embedAlts: model.embedImagesAlt @@ -128,21 +133,18 @@ ScrollView { quoteRecordImagePreview.embedAlts: model.quoteRecordEmbedImagesAlt quoteRecordImagePreview.onRequestViewImages: (index) => requestViewImages(index, model.quoteRecordEmbedImagesFull, model.quoteRecordEmbedImagesAlt) - externalLinkFrame.visible: model.hasExternalLink && contentMediaFilterFrame.showContent externalLinkFrame.onClicked: Qt.openUrlExternally(model.externalLinkUri) externalLinkFrame.thumbImage.source: model.externalLinkThumb externalLinkFrame.titleLabel.text: model.externalLinkTitle externalLinkFrame.uriLabel.text: model.externalLinkUri externalLinkFrame.descriptionLabel.text: model.externalLinkDescription - feedGeneratorFrame.visible: model.hasFeedGenerator && contentMediaFilterFrame.showContent feedGeneratorFrame.onClicked: requestViewFeedGenerator(model.feedGeneratorDisplayName, model.feedGeneratorUri) feedGeneratorFrame.avatarImage.source: model.feedGeneratorAvatar feedGeneratorFrame.displayNameLabel.text: model.feedGeneratorDisplayName feedGeneratorFrame.creatorHandleLabel.text: model.feedGeneratorCreatorHandle feedGeneratorFrame.likeCountLabel.text: model.feedGeneratorLikeCount - listLinkCardFrame.visible: model.hasListLink && contentMediaFilterFrame.showContent listLinkCardFrame.onClicked: requestViewListFeed(model.listLinkUri, model.listLinkDisplayName) listLinkCardFrame.avatarImage.source: model.listLinkAvatar listLinkCardFrame.displayNameLabel.text: model.listLinkDisplayName diff --git a/app/qml/view/TimelineView.qml b/app/qml/view/TimelineView.qml index 564f1c17..fa1288ba 100644 --- a/app/qml/view/TimelineView.qml +++ b/app/qml/view/TimelineView.qml @@ -101,6 +101,7 @@ ScrollView { delegate: PostDelegate { Layout.preferredWidth: rootListView.width + layoutWidth: rootListView.width logMode: timelineView.logMode From 0d780ff29eff19c3e392fdfd650cb8b03aafc5bf Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 01:32:22 +0900 Subject: [PATCH 024/127] =?UTF-8?q?oauth-protected-resource=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 12 ++++ lib/atprotocol/lexicons_func.cpp | 20 +++++++ lib/atprotocol/lexicons_func.h | 5 ++ .../wellknownoauthprotectedresource.cpp | 41 +++++++++++++ .../wellknownoauthprotectedresource.h | 27 +++++++++ lib/lib.pri | 2 + lib/tools/authorization.cpp | 57 ++++++++++++++++++- lib/tools/authorization.h | 10 +++- .../well.known.oauth.protected.resource.json | 35 ++++++++++++ .../1/.well-known/oauth-protected-resource | 4 +- .../2/.well-known/oauth-authorization-server | 14 ++--- .../2/xrpc/com.atproto.repo.describeRepo | 2 +- tests/oauth_test/tst_oauth_test.cpp | 13 ++++- 13 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 lib/extension/well-known/wellknownoauthprotectedresource.cpp create mode 100644 lib/extension/well-known/wellknownoauthprotectedresource.h create mode 100644 scripts/lexicons/well.known.oauth.protected.resource.json diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 2a12a6d1..aebfe2a0 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,18 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +struct OAuthProtectedResource +{ + QString resource; + QList authorization_servers; + QList scopes_supported; + QList bearer_methods_supported; + QString resource_documentation; +}; +} + } Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedPost::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedLike::Main) diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index d46c868c..612b3466 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,26 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +void copyOAuthProtectedResource(const QJsonObject &src, + WellKnownOauthProtectedResource::OAuthProtectedResource &dest) +{ + if (!src.isEmpty()) { + dest.resource = src.value("resource").toString(); + for (const auto &value : src.value("authorization_servers").toArray()) { + dest.authorization_servers.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("bearer_methods_supported").toArray()) { + dest.bearer_methods_supported.append(value.toString()); + } + dest.resource_documentation = src.value("resource_documentation").toString(); + } +} +} } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 5183654b..f94ccc6d 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +void copyOAuthProtectedResource(const QJsonObject &src, + WellKnownOauthProtectedResource::OAuthProtectedResource &dest); +} } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp new file mode 100644 index 00000000..69ed2ec8 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -0,0 +1,41 @@ +#include "wellknownoauthprotectedresource.h" +#include "atprotocol/lexicons_func.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +WellKnownOauthProtectedResource::WellKnownOauthProtectedResource(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void WellKnownOauthProtectedResource::resource() +{ + QUrlQuery url_query; + + get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); +} + +const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & +WellKnownOauthProtectedResource::OAuthProtectedResource() const +{ + return m_OAuthProtectedResource; +} + +bool WellKnownOauthProtectedResource::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::WellKnownOauthProtectedResource::copyOAuthProtectedResource( + json_doc.object(), m_OAuthProtectedResource); + } + + return success; +} + +} diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h new file mode 100644 index 00000000..980c9fb6 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -0,0 +1,27 @@ +#ifndef WELLKNOWNOAUTHPROTECTEDRESOURCE_H +#define WELLKNOWNOAUTHPROTECTEDRESOURCE_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class WellKnownOauthProtectedResource : public AccessAtProtocol +{ +public: + explicit WellKnownOauthProtectedResource(QObject *parent = nullptr); + + void resource(); + + const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & + OAuthProtectedResource() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource + m_OAuthProtectedResource; +}; + +} + +#endif // WELLKNOWNOAUTHPROTECTEDRESOURCE_H diff --git a/lib/lib.pri b/lib/lib.pri index ff6f77b7..281a78d9 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -85,6 +85,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ $$PWD/http/httpreply.cpp \ @@ -197,6 +198,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ $$PWD/http/httpreply.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 7e30869b..bd8139f8 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -2,6 +2,7 @@ #include "http/httpaccess.h" #include "http/simplehttpserver.h" #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" +#include "extension/well-known/wellknownoauthprotectedresource.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -17,6 +18,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::WellKnownOauthProtectedResource; Authorization::Authorization(QObject *parent) : QObject { parent } { } @@ -49,6 +51,10 @@ void Authorization::start(const QString &pds, const QString &handle) setServiceEndpoint(did_doc.service.first().serviceEndpoint); // next step + requestOauthProtectedResource(); + } else { + emit errorOccured("Invalid oauth-protected-resource", + "authorization_servers is empty."); } } else { emit errorOccured(repo->errorCode(), repo->errorMessage()); @@ -59,6 +65,40 @@ void Authorization::start(const QString &pds, const QString &handle) repo->describeRepo(handle); } +void Authorization::requestOauthProtectedResource() +{ + // /.well-known/oauth-protected-resource + if (serviceEndpoint().isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = serviceEndpoint(); + + WellKnownOauthProtectedResource *resource = new WellKnownOauthProtectedResource(this); + connect(resource, &WellKnownOauthProtectedResource::finished, this, [=](bool success) { + if (success) { + if (!resource->OAuthProtectedResource().authorization_servers.isEmpty()) { + setAuthorizationServer( + resource->OAuthProtectedResource().authorization_servers.first()); + // next step + } else { + emit errorOccured("Invalid oauth-protected-resource", + "authorization_servers is empty."); + } + } else { + emit errorOccured(resource->errorCode(), resource->errorMessage()); + } + resource->deleteLater(); + }); + resource->setAccount(account); + resource->resource(); +} + +void Authorization::requestOauthAuthorizationServer() +{ + // /.well-known/oauth-authorization-server +} + void Authorization::makeClientId() { QString port; @@ -295,6 +335,11 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } +QString Authorization::serviceEndpoint() const +{ + return m_serviceEndpoint; +} + void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) { if (m_serviceEndpoint == newServiceEndpoint) @@ -303,9 +348,17 @@ void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) emit serviceEndpointChanged(); } -QString Authorization::serviceEndpoint() const +QString Authorization::authorizationServer() const { - return m_serviceEndpoint; + return m_authorizationServer; +} + +void Authorization::setAuthorizationServer(const QString &newAuthorizationServer) +{ + if (m_authorizationServer == newAuthorizationServer) + return; + m_authorizationServer = newAuthorizationServer; + emit authorizationServerChanged(); } QByteArray Authorization::ParPayload() const diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 31ea7dee..db2d6b6e 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -25,6 +25,8 @@ class Authorization : public QObject QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); + QString authorizationServer() const; + void setAuthorizationServer(const QString &newAuthorizationServer); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -32,14 +34,20 @@ class Authorization : public QObject signals: void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); + void authorizationServerChanged(); void finished(bool success); private: QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; - // server + // server info + void requestOauthProtectedResource(); + void requestOauthAuthorizationServer(); + + // server info QString m_serviceEndpoint; + QString m_authorizationServer; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json b/scripts/lexicons/well.known.oauth.protected.resource.json new file mode 100644 index 00000000..a03f6aac --- /dev/null +++ b/scripts/lexicons/well.known.oauth.protected.resource.json @@ -0,0 +1,35 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.protected.resource", + "defs": { + "OAuthProtectedResource": { + "type": "object", + "properties": { + "resource": { + "type": "string" + }, + "authorization_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "bearer_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "resource_documentation": { + "type": "string" + } + } + } + } +} diff --git a/tests/oauth_test/response/1/.well-known/oauth-protected-resource b/tests/oauth_test/response/1/.well-known/oauth-protected-resource index b29c0ff7..c16eb650 100644 --- a/tests/oauth_test/response/1/.well-known/oauth-protected-resource +++ b/tests/oauth_test/response/1/.well-known/oauth-protected-resource @@ -1,7 +1,7 @@ { - "resource": "http://localhost:%1/response/2", + "resource": "http://localhost:{{SERVER_PORT_NO}}/response/2", "authorization_servers": [ - "http://localhost:%1/response/2" + "http://localhost:{{SERVER_PORT_NO}}/response/2" ], "scopes_supported": [ "profile", diff --git a/tests/oauth_test/response/2/.well-known/oauth-authorization-server b/tests/oauth_test/response/2/.well-known/oauth-authorization-server index 5536c0db..1f106d8b 100644 --- a/tests/oauth_test/response/2/.well-known/oauth-authorization-server +++ b/tests/oauth_test/response/2/.well-known/oauth-authorization-server @@ -1,5 +1,5 @@ { - "issuer": "http://localhost:%1/response/2/issuer", + "issuer": "http://localhost:{{SERVER_PORT_NO}}/response/2/issuer", "scopes_supported": [ "atproto", "transition:generic", @@ -51,9 +51,9 @@ "request_parameter_supported": true, "request_uri_parameter_supported": true, "require_request_uri_registration": true, - "jwks_uri": "http://localhost:%1/response/2/oauth/jwks", - "authorization_endpoint": "http://localhost:%1/response/2/oauth/authorize", - "token_endpoint": "http://localhost:%1/response/2/oauth/token", + "jwks_uri": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/jwks", + "authorization_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/authorize", + "token_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/token", "token_endpoint_auth_methods_supported": [ "none", "private_key_jwt" @@ -70,9 +70,9 @@ "ES384", "ES512" ], - "revocation_endpoint": "http://localhost:%1/response/2/oauth/revoke", - "introspection_endpoint": "http://localhost:%1/response/2/oauth/introspect", - "pushed_authorization_request_endpoint": "http://localhost:%1/response/2/oauth/par", + "revocation_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/revoke", + "introspection_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/introspect", + "pushed_authorization_request_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/par", "require_pushed_authorization_requests": true, "dpop_signing_alg_values_supported": [ "RS256", diff --git a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo index 36fa95c0..5cfb1896 100644 --- a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo +++ b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo @@ -23,7 +23,7 @@ { "id": "#atproto_pds", "type": "AtprotoPersonalDataServer", - "serviceEndpoint": "http://localhost:%1/response/1" + "serviceEndpoint": "http://localhost:{{SERVER_PORT_NO}}/response/1" } ] }, diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 72c69ac2..8c2942e2 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -56,6 +56,7 @@ oauth_test::oauth_test() } else { mime_type = "application/json"; result = SimpleHttpServer::readFile(path, data); + data.replace("{{SERVER_PORT_NO}}", QString::number(m_listenPort).toLocal8Bit()); qDebug().noquote() << " result =" << result; } }); @@ -124,6 +125,8 @@ void oauth_test::test_oauth_server() void oauth_test::test_oauth() { + // response/1 : pds + // response/2 : entry-way Authorization oauth; QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); @@ -136,9 +139,15 @@ void oauth_test::test_oauth() spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } - oauth.setServiceEndpoint(oauth.serviceEndpoint().arg(m_listenPort)); - QVERIFY(oauth.serviceEndpoint() == QString("http://localhost:%1/response/1").arg(m_listenPort)); + + { + QSignalSpy spy(&oauth, SIGNAL(authorizationServerChanged())); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + QVERIFY(oauth.authorizationServer() + == QString("http://localhost:%1/response/2").arg(m_listenPort)); } void oauth_test::test_jwt() From 7595b641a882f941f58d80383337ea412ff4258a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 02:35:35 +0900 Subject: [PATCH 025/127] =?UTF-8?q?oauth-authorization-server=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 21 +++++ lib/atprotocol/lexicons_func.cpp | 45 +++++++++++ lib/atprotocol/lexicons_func.h | 5 ++ .../wellknownoauthauthorizationserver.cpp | 41 ++++++++++ .../wellknownoauthauthorizationserver.h | 26 +++++++ lib/lib.pri | 2 + lib/tools/authorization.cpp | 43 +++++++++++ lib/tools/authorization.h | 5 ++ ... => directory.plc.log.audit.json.template} | 0 ...known.oauth.authorization.server.defs.json | 77 +++++++++++++++++++ ...n.oauth.authorization.server.json.template | 23 ++++++ tests/oauth_test/tst_oauth_test.cpp | 8 ++ 12 files changed, 296 insertions(+) create mode 100644 lib/extension/well-known/wellknownoauthauthorizationserver.cpp create mode 100644 lib/extension/well-known/wellknownoauthauthorizationserver.h rename scripts/lexicons/{directory.plc.log.audit.json.temple => directory.plc.log.audit.json.template} (100%) create mode 100644 scripts/lexicons/well.known.oauth.authorization.server.defs.json create mode 100644 scripts/lexicons/well.known.oauth.authorization.server.json.template diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index aebfe2a0..c20c5102 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,27 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +struct ServerMetadata +{ + QString issuer; + QList response_types_supported; + QList grant_types_supported; + QList code_challenge_methods_supported; + QList token_endpoint_auth_methods_supported; + QList token_endpoint_auth_signing_alg_values_supported; + QList scopes_supported; + QList subject_types_supported; + bool authorization_response_iss_parameter_supported = false; + QString pushed_authorization_request_endpoint; + bool require_pushed_authorization_requests = false; + QList dpop_signing_alg_values_supported; + bool require_request_uri_registration = false; + bool client_id_metadata_document_supported = false; +}; +} + // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { struct OAuthProtectedResource diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 612b3466..ecf217cf 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,51 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +void copyServerMetadata(const QJsonObject &src, + WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest) +{ + if (!src.isEmpty()) { + dest.issuer = src.value("issuer").toString(); + for (const auto &value : src.value("response_types_supported").toArray()) { + dest.response_types_supported.append(value.toString()); + } + for (const auto &value : src.value("grant_types_supported").toArray()) { + dest.grant_types_supported.append(value.toString()); + } + for (const auto &value : src.value("code_challenge_methods_supported").toArray()) { + dest.code_challenge_methods_supported.append(value.toString()); + } + for (const auto &value : src.value("token_endpoint_auth_methods_supported").toArray()) { + dest.token_endpoint_auth_methods_supported.append(value.toString()); + } + for (const auto &value : + src.value("token_endpoint_auth_signing_alg_values_supported").toArray()) { + dest.token_endpoint_auth_signing_alg_values_supported.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("subject_types_supported").toArray()) { + dest.subject_types_supported.append(value.toString()); + } + dest.authorization_response_iss_parameter_supported = + src.value("authorization_response_iss_parameter_supported").toBool(); + dest.pushed_authorization_request_endpoint = + src.value("pushed_authorization_request_endpoint").toString(); + dest.require_pushed_authorization_requests = + src.value("require_pushed_authorization_requests").toBool(); + for (const auto &value : src.value("dpop_signing_alg_values_supported").toArray()) { + dest.dpop_signing_alg_values_supported.append(value.toString()); + } + dest.require_request_uri_registration = + src.value("require_request_uri_registration").toBool(); + dest.client_id_metadata_document_supported = + src.value("client_id_metadata_document_supported").toBool(); + } +} +} // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { void copyOAuthProtectedResource(const QJsonObject &src, diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index f94ccc6d..fdd61b6e 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +void copyServerMetadata(const QJsonObject &src, + WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); +} // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { void copyOAuthProtectedResource(const QJsonObject &src, diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp new file mode 100644 index 00000000..3b5c5dab --- /dev/null +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp @@ -0,0 +1,41 @@ +#include "wellknownoauthauthorizationserver.h" +#include "atprotocol/lexicons_func.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +WellKnownOauthAuthorizationServer::WellKnownOauthAuthorizationServer(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void WellKnownOauthAuthorizationServer::server() +{ + QUrlQuery url_query; + + get(QStringLiteral(".well-known/oauth-authorization-server"), url_query, false); +} + +const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & +WellKnownOauthAuthorizationServer::serverMetadata() const +{ + return m_serverMetadata; +} + +bool WellKnownOauthAuthorizationServer::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::WellKnownOauthAuthorizationServerDefs::copyServerMetadata(json_doc.object(), + m_serverMetadata); + } + + return success; +} + +} diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.h b/lib/extension/well-known/wellknownoauthauthorizationserver.h new file mode 100644 index 00000000..d644a443 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.h @@ -0,0 +1,26 @@ +#ifndef WELLKNOWNOAUTHAUTHORIZATIONSERVER_H +#define WELLKNOWNOAUTHAUTHORIZATIONSERVER_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class WellKnownOauthAuthorizationServer : public AccessAtProtocol +{ +public: + explicit WellKnownOauthAuthorizationServer(QObject *parent = nullptr); + + void server(); + + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & + serverMetadata() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata m_serverMetadata; +}; + +} + +#endif // WELLKNOWNOAUTHAUTHORIZATIONSERVER_H diff --git a/lib/lib.pri b/lib/lib.pri index 281a78d9..0ecb969c 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -85,6 +85,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ @@ -198,6 +199,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index bd8139f8..cbf09dfe 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -3,6 +3,7 @@ #include "http/simplehttpserver.h" #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" #include "extension/well-known/wellknownoauthprotectedresource.h" +#include "extension/well-known/wellknownoauthauthorizationserver.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -18,6 +19,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; Authorization::Authorization(QObject *parent) : QObject { parent } { } @@ -81,6 +83,7 @@ void Authorization::requestOauthProtectedResource() setAuthorizationServer( resource->OAuthProtectedResource().authorization_servers.first()); // next step + requestOauthAuthorizationServer(); } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); @@ -97,6 +100,33 @@ void Authorization::requestOauthProtectedResource() void Authorization::requestOauthAuthorizationServer() { // /.well-known/oauth-authorization-server + + if (authorizationServer().isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = authorizationServer(); + + WellKnownOauthAuthorizationServer *server = new WellKnownOauthAuthorizationServer(this); + connect(server, &WellKnownOauthAuthorizationServer::finished, this, [=](bool success) { + if (success) { + // TODO: Validate serverMetadata + + if (!server->serverMetadata().pushed_authorization_request_endpoint.isEmpty()) { + setPushedAuthorizationRequestEndpoint( + server->serverMetadata().pushed_authorization_request_endpoint); + + // next step + } else { + emit errorOccured("Invalid oauth-authorization-server", + "pushed_authorization_request_endpoint is empty."); + } + } else { + emit errorOccured(server->errorCode(), server->errorMessage()); + } + }); + server->setAccount(account); + server->server(); } void Authorization::makeClientId() @@ -361,6 +391,19 @@ void Authorization::setAuthorizationServer(const QString &newAuthorizationServer emit authorizationServerChanged(); } +QString Authorization::pushedAuthorizationRequestEndpoint() const +{ + return m_pushedAuthorizationRequestEndpoint; +} + +void Authorization::setPushedAuthorizationRequestEndpoint( + const QString &newPushedAuthorizationRequestEndpoint) +{ + if (m_pushedAuthorizationRequestEndpoint == newPushedAuthorizationRequestEndpoint) + return; + m_pushedAuthorizationRequestEndpoint = newPushedAuthorizationRequestEndpoint; + emit pushedAuthorizationRequestEndpointChanged(); +} QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index db2d6b6e..3b150b30 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -27,6 +27,9 @@ class Authorization : public QObject void setServiceEndpoint(const QString &newServiceEndpoint); QString authorizationServer() const; void setAuthorizationServer(const QString &newAuthorizationServer); + QString pushedAuthorizationRequestEndpoint() const; + void + setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -35,6 +38,7 @@ class Authorization : public QObject void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); void authorizationServerChanged(); + void pushedAuthorizationRequestEndpointChanged(); void finished(bool success); private: @@ -48,6 +52,7 @@ class Authorization : public QObject // server info QString m_serviceEndpoint; QString m_authorizationServer; + QString m_pushedAuthorizationRequestEndpoint; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/directory.plc.log.audit.json.temple b/scripts/lexicons/directory.plc.log.audit.json.template similarity index 100% rename from scripts/lexicons/directory.plc.log.audit.json.temple rename to scripts/lexicons/directory.plc.log.audit.json.template diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/well.known.oauth.authorization.server.defs.json new file mode 100644 index 00000000..f043c978 --- /dev/null +++ b/scripts/lexicons/well.known.oauth.authorization.server.defs.json @@ -0,0 +1,77 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.authorization.server.defs", + "defs": { + "serverMetadata": { + "type": "object", + "properties": { + "issuer": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorization_response_iss_parameter_supported": { + "type": "boolean" + }, + "pushed_authorization_request_endpoint": { + "type": "string" + }, + "require_pushed_authorization_requests": { + "type": "boolean" + }, + "dpop_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "require_request_uri_registration": { + "type": "boolean" + }, + "client_id_metadata_document_supported": { + "type": "boolean" + } + } + } + } +} diff --git a/scripts/lexicons/well.known.oauth.authorization.server.json.template b/scripts/lexicons/well.known.oauth.authorization.server.json.template new file mode 100644 index 00000000..2b6ed4bd --- /dev/null +++ b/scripts/lexicons/well.known.oauth.authorization.server.json.template @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.authorization.server", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + ], + "properties": { + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "well.known.oauth.authorization.server.defs#serverMetadata" + } + } + } + } +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 8c2942e2..d40b6ca0 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -148,6 +148,14 @@ void oauth_test::test_oauth() } QVERIFY(oauth.authorizationServer() == QString("http://localhost:%1/response/2").arg(m_listenPort)); + + { + QSignalSpy spy(&oauth, SIGNAL(pushedAuthorizationRequestEndpointChanged())); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + QVERIFY(oauth.pushedAuthorizationRequestEndpoint() + == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); } void oauth_test::test_jwt() From 392971eb4e65f20be59f6bfee70a327c8e12157c Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 02:49:09 +0900 Subject: [PATCH 026/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 6 ++--- lib/atprotocol/lexicons_func.cpp | 8 +++---- lib/atprotocol/lexicons_func.h | 8 +++---- .../wellknownoauthprotectedresource.cpp | 10 ++++---- .../wellknownoauthprotectedresource.h | 7 +++--- lib/tools/authorization.cpp | 5 ++-- ....known.oauth.protected.resource.defs.json} | 4 ++-- ...own.oauth.protected.resource.json.template | 23 +++++++++++++++++++ 8 files changed, 46 insertions(+), 25 deletions(-) rename scripts/lexicons/{well.known.oauth.protected.resource.json => well.known.oauth.protected.resource.defs.json} (91%) create mode 100644 scripts/lexicons/well.known.oauth.protected.resource.json.template diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index c20c5102..972fa319 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2075,9 +2075,9 @@ struct ServerMetadata }; } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -struct OAuthProtectedResource +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +struct ResourceMetadata { QString resource; QList authorization_servers; diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index ecf217cf..a4aa55c2 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2952,10 +2952,10 @@ void copyServerMetadata(const QJsonObject &src, } } } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -void copyOAuthProtectedResource(const QJsonObject &src, - WellKnownOauthProtectedResource::OAuthProtectedResource &dest) +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +void copyResourceMetadata(const QJsonObject &src, + WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest) { if (!src.isEmpty()) { dest.resource = src.value("resource").toString(); diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index fdd61b6e..4d4ace52 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -417,10 +417,10 @@ namespace WellKnownOauthAuthorizationServerDefs { void copyServerMetadata(const QJsonObject &src, WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -void copyOAuthProtectedResource(const QJsonObject &src, - WellKnownOauthProtectedResource::OAuthProtectedResource &dest); +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +void copyResourceMetadata(const QJsonObject &src, + WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest); } } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp index 69ed2ec8..0f392dee 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.cpp +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -19,10 +19,10 @@ void WellKnownOauthProtectedResource::resource() get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); } -const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & -WellKnownOauthProtectedResource::OAuthProtectedResource() const +const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & +WellKnownOauthProtectedResource::resourceMetadata() const { - return m_OAuthProtectedResource; + return m_resourceMetadata; } bool WellKnownOauthProtectedResource::parseJson(bool success, const QString reply_json) @@ -31,8 +31,8 @@ bool WellKnownOauthProtectedResource::parseJson(bool success, const QString repl if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthProtectedResource::copyOAuthProtectedResource( - json_doc.object(), m_OAuthProtectedResource); + AtProtocolType::WellKnownOauthProtectedResourceDefs::copyResourceMetadata( + json_doc.object(), m_resourceMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h index 980c9fb6..3bc4caa7 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.h +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -12,14 +12,13 @@ class WellKnownOauthProtectedResource : public AccessAtProtocol void resource(); - const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & - OAuthProtectedResource() const; + const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & + resourceMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource - m_OAuthProtectedResource; + AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata m_resourceMetadata; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index cbf09dfe..b3aecc52 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -79,9 +79,8 @@ void Authorization::requestOauthProtectedResource() WellKnownOauthProtectedResource *resource = new WellKnownOauthProtectedResource(this); connect(resource, &WellKnownOauthProtectedResource::finished, this, [=](bool success) { if (success) { - if (!resource->OAuthProtectedResource().authorization_servers.isEmpty()) { - setAuthorizationServer( - resource->OAuthProtectedResource().authorization_servers.first()); + if (!resource->resourceMetadata().authorization_servers.isEmpty()) { + setAuthorizationServer(resource->resourceMetadata().authorization_servers.first()); // next step requestOauthAuthorizationServer(); } else { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json b/scripts/lexicons/well.known.oauth.protected.resource.defs.json similarity index 91% rename from scripts/lexicons/well.known.oauth.protected.resource.json rename to scripts/lexicons/well.known.oauth.protected.resource.defs.json index a03f6aac..fcd6f913 100644 --- a/scripts/lexicons/well.known.oauth.protected.resource.json +++ b/scripts/lexicons/well.known.oauth.protected.resource.defs.json @@ -1,8 +1,8 @@ { "lexicon": 1, - "id": "well.known.oauth.protected.resource", + "id": "well.known.oauth.protected.resource.defs", "defs": { - "OAuthProtectedResource": { + "resourceMetadata": { "type": "object", "properties": { "resource": { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json.template b/scripts/lexicons/well.known.oauth.protected.resource.json.template new file mode 100644 index 00000000..eee48e1e --- /dev/null +++ b/scripts/lexicons/well.known.oauth.protected.resource.json.template @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.protected.resource", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + ], + "properties": { + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "well.known.oauth.protected.resource.defs#resourceMetadata" + } + } + } + } +} From 941fb8747f0ac37061071a0958e341689fc6c193 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 00:42:16 +0900 Subject: [PATCH 027/127] =?UTF-8?q?server=20metadata=E3=81=AE=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 75 ++++++++++++++++++++++++++++++++++--- lib/tools/authorization.h | 5 +++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index b3aecc52..1497fdf5 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -27,13 +27,21 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { m_serviceEndpoint.clear(); + m_authorizationServer.clear(); + m_pushedAuthorizationRequestEndpoint.clear(); + // m_redirectUri.clear(); m_clientId.clear(); + // par m_codeChallenge.clear(); m_codeVerifier.clear(); m_state.clear(); m_parPayload.clear(); + // request token + m_code.clear(); m_requestTokenPayload.clear(); + // + m_listenPort.clear(); } void Authorization::start(const QString &pds, const QString &handle) @@ -109,25 +117,82 @@ void Authorization::requestOauthAuthorizationServer() WellKnownOauthAuthorizationServer *server = new WellKnownOauthAuthorizationServer(this); connect(server, &WellKnownOauthAuthorizationServer::finished, this, [=](bool success) { if (success) { - // TODO: Validate serverMetadata - - if (!server->serverMetadata().pushed_authorization_request_endpoint.isEmpty()) { + QString error_message; + if (validateServerMetadata(server->serverMetadata(), error_message)) { setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); // next step } else { - emit errorOccured("Invalid oauth-authorization-server", - "pushed_authorization_request_endpoint is empty."); + qDebug().noquote() << error_message; + emit errorOccured("Invalid oauth-authorization-server", error_message); } } else { emit errorOccured(server->errorCode(), server->errorMessage()); } + server->deleteLater(); }); server->setAccount(account); server->server(); } +bool Authorization::validateServerMetadata( + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata + &server_metadata, + QString &error_message) +{ + bool ret = false; + if (QUrl(server_metadata.issuer).host() != QUrl(authorizationServer()).host()) { + // リダイレクトされると変わるので対応しないといけない + error_message = QString("'issuer' is an invalid value(%1).").arg(server_metadata.issuer); + } else if (!server_metadata.response_types_supported.contains("code")) { + error_message = QStringLiteral("'response_types_supported' must contain 'code'."); + } else if (!server_metadata.grant_types_supported.contains("authorization_code")) { + error_message = + QStringLiteral("'grant_types_supported' must contain 'authorization_code'."); + } else if (!server_metadata.grant_types_supported.contains("refresh_token")) { + error_message = QStringLiteral("'grant_types_supported' must contain 'refresh_token'."); + } else if (!server_metadata.code_challenge_methods_supported.contains("S256")) { + error_message = QStringLiteral("'code_challenge_methods_supported' must contain 'S256'."); + } else if (!server_metadata.token_endpoint_auth_methods_supported.contains("private_key_jwt")) { + error_message = QStringLiteral( + "'token_endpoint_auth_methods_supported' must contain 'private_key_jwt'."); + } else if (!server_metadata.token_endpoint_auth_methods_supported.contains("none")) { + error_message = + QStringLiteral("'token_endpoint_auth_methods_supported' must contain 'none'."); + } else if (!server_metadata.token_endpoint_auth_signing_alg_values_supported.contains( + "ES256")) { + error_message = QStringLiteral( + "'token_endpoint_auth_signing_alg_values_supported' must contain 'ES256'."); + } else if (!server_metadata.scopes_supported.contains("atproto")) { + error_message = QStringLiteral("'scopes_supported' must contain 'atproto'."); + } else if (!(server_metadata.subject_types_supported.isEmpty() + || (!server_metadata.subject_types_supported.isEmpty() + && server_metadata.subject_types_supported.contains("public")))) { + error_message = QStringLiteral("'subject_types_supported' must contain 'public'."); + } else if (!server_metadata.authorization_response_iss_parameter_supported) { + error_message = + QString("'authorization_response_iss_parameter_supported' is an invalid value(%1).") + .arg(server_metadata.authorization_response_iss_parameter_supported); + } else if (server_metadata.pushed_authorization_request_endpoint.isEmpty()) { + error_message = QStringLiteral("pushed_authorization_request_endpoint must be set'."); + } else if (!server_metadata.require_pushed_authorization_requests) { + error_message = QString("'require_pushed_authorization_requests' is an invalid value(%1).") + .arg(server_metadata.require_pushed_authorization_requests); + } else if (!server_metadata.dpop_signing_alg_values_supported.contains("ES256")) { + error_message = QStringLiteral("'dpop_signing_alg_values_supported' must contain 'ES256'."); + } else if (!server_metadata.require_request_uri_registration) { + error_message = QString("'require_request_uri_registration' is an invalid value(%1).") + .arg(server_metadata.require_request_uri_registration); + } else if (!server_metadata.client_id_metadata_document_supported) { + error_message = QString("'client_id_metadata_document_supported' is an invalid value(%1).") + .arg(server_metadata.client_id_metadata_document_supported); + } else { + ret = true; + } + return ret; +} + void Authorization::makeClientId() { QString port; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 3b150b30..f4dccc86 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -2,6 +2,7 @@ #define AUTHORIZATION_H #include +#include "atprotocol/lexicons.h" class Authorization : public QObject { @@ -48,6 +49,10 @@ class Authorization : public QObject // server info void requestOauthProtectedResource(); void requestOauthAuthorizationServer(); + bool validateServerMetadata( + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata + &server_metadata, + QString &error_message); // server info QString m_serviceEndpoint; From a1297e7e7224f6caa4ee4d76b5b710f16a55f8c7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 01:15:15 +0900 Subject: [PATCH 028/127] =?UTF-8?q?par=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=81=B8=E3=81=AE=E6=8E=A5=E7=B6=9A=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func.cpp | 1 + lib/tools/authorization.cpp | 37 ++++++++++++++----- lib/tools/authorization.h | 9 +++++ ...known.oauth.authorization.server.defs.json | 3 ++ tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/par | 4 ++ tests/oauth_test/tst_oauth_test.cpp | 2 + 8 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 tests/oauth_test/response/2/oauth/par diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 972fa319..96d08959 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2072,6 +2072,7 @@ struct ServerMetadata QList dpop_signing_alg_values_supported; bool require_request_uri_registration = false; bool client_id_metadata_document_supported = false; + QString authorization_endpoint; }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index a4aa55c2..f5144e87 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2949,6 +2949,7 @@ void copyServerMetadata(const QJsonObject &src, src.value("require_request_uri_registration").toBool(); dest.client_id_metadata_document_supported = src.value("client_id_metadata_document_supported").toBool(); + dest.authorization_endpoint = src.value("authorization_endpoint").toString(); } } } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 1497fdf5..5b73481d 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -26,9 +26,15 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + // user + m_handle.clear(); + // server info m_serviceEndpoint.clear(); m_authorizationServer.clear(); + // server meta data m_pushedAuthorizationRequestEndpoint.clear(); + m_authorizationEndpoint.clear(); + m_scopesSupported.clear(); // m_redirectUri.clear(); m_clientId.clear(); @@ -121,8 +127,12 @@ void Authorization::requestOauthAuthorizationServer() if (validateServerMetadata(server->serverMetadata(), error_message)) { setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); + setAuthorizationEndpoint(server->serverMetadata().authorization_endpoint); + m_scopesSupported = server->serverMetadata().scopes_supported; // next step + makeParPayload(); + par(); } else { qDebug().noquote() << error_message; emit errorOccured("Invalid oauth-authorization-server", error_message); @@ -223,12 +233,6 @@ void Authorization::makeParPayload() m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QStringList scopes_supported; - scopes_supported << "offline_access" - << "openid" - // << "email" - // << "phone" - << "profile"; QString login_hint = "ioriayane2.bsky.social"; QUrlQuery query; @@ -238,7 +242,7 @@ void Authorization::makeParPayload() query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("state", m_state); query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); - query.addQueryItem("scope", scopes_supported.join(" ")); + query.addQueryItem("scope", m_scopesSupported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); @@ -246,11 +250,10 @@ void Authorization::makeParPayload() void Authorization::par() { - if (m_parPayload.isEmpty()) + if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) return; - QString endpoint = "https://bsky.social/oauth/par"; - QNetworkRequest request((QUrl(endpoint))); + QNetworkRequest request((QUrl(pushedAuthorizationRequestEndpoint()))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QPointer alive = this; @@ -468,6 +471,20 @@ void Authorization::setPushedAuthorizationRequestEndpoint( m_pushedAuthorizationRequestEndpoint = newPushedAuthorizationRequestEndpoint; emit pushedAuthorizationRequestEndpointChanged(); } + +QString Authorization::authorizationEndpoint() const +{ + return m_authorizationEndpoint; +} + +void Authorization::setAuthorizationEndpoint(const QString &newAuthorizationEndpoint) +{ + if (m_authorizationEndpoint == newAuthorizationEndpoint) + return; + m_authorizationEndpoint = newAuthorizationEndpoint; + emit authorizationEndpointChanged(); +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index f4dccc86..a1974be1 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -31,6 +31,9 @@ class Authorization : public QObject QString pushedAuthorizationRequestEndpoint() const; void setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); + QString authorizationEndpoint() const; + void setAuthorizationEndpoint(const QString &newAuthorizationEndpoint); + QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -40,6 +43,7 @@ class Authorization : public QObject void serviceEndpointChanged(); void authorizationServerChanged(); void pushedAuthorizationRequestEndpointChanged(); + void authorizationEndpointChanged(); void finished(bool success); private: @@ -54,10 +58,15 @@ class Authorization : public QObject &server_metadata, QString &error_message); + // user + QString m_handle; // server info QString m_serviceEndpoint; QString m_authorizationServer; + // server meta data QString m_pushedAuthorizationRequestEndpoint; + QString m_authorizationEndpoint; + QStringList m_scopesSupported; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/well.known.oauth.authorization.server.defs.json index f043c978..1402b92a 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.defs.json +++ b/scripts/lexicons/well.known.oauth.authorization.server.defs.json @@ -70,6 +70,9 @@ }, "client_id_metadata_document_supported": { "type": "boolean" + }, + "authorization_endpoint": { + "type": "string" } } } diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index d909191c..8da511bf 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -3,5 +3,6 @@ response/1/.well-known/oauth-protected-resource response/2/xrpc/com.atproto.repo.describeRepo response/2/.well-known/oauth-authorization-server + response/2/oauth/par diff --git a/tests/oauth_test/response/2/oauth/par b/tests/oauth_test/response/2/oauth/par new file mode 100644 index 00000000..4ac5606e --- /dev/null +++ b/tests/oauth_test/response/2/oauth/par @@ -0,0 +1,4 @@ +{ + "request_uri": "urn:ietf:params:oauth:request_uri:req-05650c01604941dc674f0af9cb032aca", + "expires_in": 299 +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index d40b6ca0..b5310980 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -156,6 +156,8 @@ void oauth_test::test_oauth() } QVERIFY(oauth.pushedAuthorizationRequestEndpoint() == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); + QVERIFY(oauth.authorizationEndpoint() + == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); } void oauth_test::test_jwt() From 825c6cc6bc8d1cbe527bb61909a48fd0a78e0758 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 01:36:53 +0900 Subject: [PATCH 029/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 24 +++++------ lib/atprotocol/lexicons_func.cpp | 43 ++++++++----------- lib/atprotocol/lexicons_func.h | 13 ++---- .../wellknownoauthauthorizationserver.cpp | 7 ++- .../wellknownoauthauthorizationserver.h | 7 ++- .../wellknownoauthprotectedresource.cpp | 7 ++- .../wellknownoauthprotectedresource.h | 7 ++- lib/tools/authorization.cpp | 7 ++- lib/tools/authorization.h | 7 ++- ...l.known.oauth.protected.resource.defs.json | 35 --------------- ...n.server.defs.json => wellKnown.defs.json} | 31 ++++++++++++- ...wn.oauthAuthorizationServer.json.template} | 4 +- ...nown.oauthProtectedResource.json.template} | 4 +- 13 files changed, 85 insertions(+), 111 deletions(-) delete mode 100644 scripts/lexicons/well.known.oauth.protected.resource.defs.json rename scripts/lexicons/{well.known.oauth.authorization.server.defs.json => wellKnown.defs.json} (73%) rename scripts/lexicons/{well.known.oauth.protected.resource.json.template => wellKnown.oauthAuthorizationServer.json.template} (76%) rename scripts/lexicons/{well.known.oauth.authorization.server.json.template => wellKnown.oauthProtectedResource.json.template} (76%) diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 96d08959..8c82a592 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,8 +2054,16 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { +// wellKnown.defs +namespace WellKnownDefs { +struct ResourceMetadata +{ + QString resource; + QList authorization_servers; + QList scopes_supported; + QList bearer_methods_supported; + QString resource_documentation; +}; struct ServerMetadata { QString issuer; @@ -2076,18 +2084,6 @@ struct ServerMetadata }; } -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -struct ResourceMetadata -{ - QString resource; - QList authorization_servers; - QList scopes_supported; - QList bearer_methods_supported; - QString resource_documentation; -}; -} - } Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedPost::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedLike::Main) diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index f5144e87..b3748e61 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,10 +2907,25 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { -void copyServerMetadata(const QJsonObject &src, - WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest) +// wellKnown.defs +namespace WellKnownDefs { +void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest) +{ + if (!src.isEmpty()) { + dest.resource = src.value("resource").toString(); + for (const auto &value : src.value("authorization_servers").toArray()) { + dest.authorization_servers.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("bearer_methods_supported").toArray()) { + dest.bearer_methods_supported.append(value.toString()); + } + dest.resource_documentation = src.value("resource_documentation").toString(); + } +} +void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &dest) { if (!src.isEmpty()) { dest.issuer = src.value("issuer").toString(); @@ -2953,26 +2968,6 @@ void copyServerMetadata(const QJsonObject &src, } } } -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -void copyResourceMetadata(const QJsonObject &src, - WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest) -{ - if (!src.isEmpty()) { - dest.resource = src.value("resource").toString(); - for (const auto &value : src.value("authorization_servers").toArray()) { - dest.authorization_servers.append(value.toString()); - } - for (const auto &value : src.value("scopes_supported").toArray()) { - dest.scopes_supported.append(value.toString()); - } - for (const auto &value : src.value("bearer_methods_supported").toArray()) { - dest.bearer_methods_supported.append(value.toString()); - } - dest.resource_documentation = src.value("resource_documentation").toString(); - } -} -} } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 4d4ace52..904acd72 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,15 +412,10 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { -void copyServerMetadata(const QJsonObject &src, - WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); -} -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -void copyResourceMetadata(const QJsonObject &src, - WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest); +// wellKnown.defs +namespace WellKnownDefs { +void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest); +void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &dest); } } diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp index 3b5c5dab..0f8745cc 100644 --- a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp @@ -12,14 +12,14 @@ WellKnownOauthAuthorizationServer::WellKnownOauthAuthorizationServer(QObject *pa { } -void WellKnownOauthAuthorizationServer::server() +void WellKnownOauthAuthorizationServer::oauthAuthorizationServer() { QUrlQuery url_query; get(QStringLiteral(".well-known/oauth-authorization-server"), url_query, false); } -const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & +const AtProtocolType::WellKnownDefs::ServerMetadata & WellKnownOauthAuthorizationServer::serverMetadata() const { return m_serverMetadata; @@ -31,8 +31,7 @@ bool WellKnownOauthAuthorizationServer::parseJson(bool success, const QString re if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthAuthorizationServerDefs::copyServerMetadata(json_doc.object(), - m_serverMetadata); + AtProtocolType::WellKnownDefs::copyServerMetadata(json_doc.object(), m_serverMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.h b/lib/extension/well-known/wellknownoauthauthorizationserver.h index d644a443..c22122c0 100644 --- a/lib/extension/well-known/wellknownoauthauthorizationserver.h +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.h @@ -10,15 +10,14 @@ class WellKnownOauthAuthorizationServer : public AccessAtProtocol public: explicit WellKnownOauthAuthorizationServer(QObject *parent = nullptr); - void server(); + void oauthAuthorizationServer(); - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & - serverMetadata() const; + const AtProtocolType::WellKnownDefs::ServerMetadata &serverMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata m_serverMetadata; + AtProtocolType::WellKnownDefs::ServerMetadata m_serverMetadata; }; } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp index 0f392dee..aceaf604 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.cpp +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -12,14 +12,14 @@ WellKnownOauthProtectedResource::WellKnownOauthProtectedResource(QObject *parent { } -void WellKnownOauthProtectedResource::resource() +void WellKnownOauthProtectedResource::oauthProtectedResource() { QUrlQuery url_query; get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); } -const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & +const AtProtocolType::WellKnownDefs::ResourceMetadata & WellKnownOauthProtectedResource::resourceMetadata() const { return m_resourceMetadata; @@ -31,8 +31,7 @@ bool WellKnownOauthProtectedResource::parseJson(bool success, const QString repl if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthProtectedResourceDefs::copyResourceMetadata( - json_doc.object(), m_resourceMetadata); + AtProtocolType::WellKnownDefs::copyResourceMetadata(json_doc.object(), m_resourceMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h index 3bc4caa7..f89d0cce 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.h +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -10,15 +10,14 @@ class WellKnownOauthProtectedResource : public AccessAtProtocol public: explicit WellKnownOauthProtectedResource(QObject *parent = nullptr); - void resource(); + void oauthProtectedResource(); - const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & - resourceMetadata() const; + const AtProtocolType::WellKnownDefs::ResourceMetadata &resourceMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata m_resourceMetadata; + AtProtocolType::WellKnownDefs::ResourceMetadata m_resourceMetadata; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 5b73481d..83f442ec 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -107,7 +107,7 @@ void Authorization::requestOauthProtectedResource() resource->deleteLater(); }); resource->setAccount(account); - resource->resource(); + resource->oauthProtectedResource(); } void Authorization::requestOauthAuthorizationServer() @@ -143,12 +143,11 @@ void Authorization::requestOauthAuthorizationServer() server->deleteLater(); }); server->setAccount(account); - server->server(); + server->oauthAuthorizationServer(); } bool Authorization::validateServerMetadata( - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata - &server_metadata, + const AtProtocolType::WellKnownDefs::ServerMetadata &server_metadata, QString &error_message) { bool ret = false; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index a1974be1..5a283482 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -53,10 +53,9 @@ class Authorization : public QObject // server info void requestOauthProtectedResource(); void requestOauthAuthorizationServer(); - bool validateServerMetadata( - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata - &server_metadata, - QString &error_message); + bool + validateServerMetadata(const AtProtocolType::WellKnownDefs::ServerMetadata &server_metadata, + QString &error_message); // user QString m_handle; diff --git a/scripts/lexicons/well.known.oauth.protected.resource.defs.json b/scripts/lexicons/well.known.oauth.protected.resource.defs.json deleted file mode 100644 index fcd6f913..00000000 --- a/scripts/lexicons/well.known.oauth.protected.resource.defs.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "lexicon": 1, - "id": "well.known.oauth.protected.resource.defs", - "defs": { - "resourceMetadata": { - "type": "object", - "properties": { - "resource": { - "type": "string" - }, - "authorization_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes_supported": { - "type": "array", - "items": { - "type": "string" - } - }, - "bearer_methods_supported": { - "type": "array", - "items": { - "type": "string" - } - }, - "resource_documentation": { - "type": "string" - } - } - } - } -} diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/wellKnown.defs.json similarity index 73% rename from scripts/lexicons/well.known.oauth.authorization.server.defs.json rename to scripts/lexicons/wellKnown.defs.json index 1402b92a..248ed0b5 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.defs.json +++ b/scripts/lexicons/wellKnown.defs.json @@ -1,7 +1,36 @@ { "lexicon": 1, - "id": "well.known.oauth.authorization.server.defs", + "id": "wellKnown.defs", "defs": { + "resourceMetadata": { + "type": "object", + "properties": { + "resource": { + "type": "string" + }, + "authorization_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "bearer_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "resource_documentation": { + "type": "string" + } + } + }, "serverMetadata": { "type": "object", "properties": { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json.template b/scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template similarity index 76% rename from scripts/lexicons/well.known.oauth.protected.resource.json.template rename to scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template index eee48e1e..acf6d38b 100644 --- a/scripts/lexicons/well.known.oauth.protected.resource.json.template +++ b/scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "well.known.oauth.protected.resource", + "id": "wellKnown.oauthAuthorizationServer", "defs": { "main": { "type": "query", @@ -15,7 +15,7 @@ "encoding": "application/json", "schema": { "type": "ref", - "ref": "well.known.oauth.protected.resource.defs#resourceMetadata" + "ref": "wellKnown.defs#serverMetadata" } } } diff --git a/scripts/lexicons/well.known.oauth.authorization.server.json.template b/scripts/lexicons/wellKnown.oauthProtectedResource.json.template similarity index 76% rename from scripts/lexicons/well.known.oauth.authorization.server.json.template rename to scripts/lexicons/wellKnown.oauthProtectedResource.json.template index 2b6ed4bd..743af930 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.json.template +++ b/scripts/lexicons/wellKnown.oauthProtectedResource.json.template @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "well.known.oauth.authorization.server", + "id": "wellKnown.oauthProtectedResource", "defs": { "main": { "type": "query", @@ -15,7 +15,7 @@ "encoding": "application/json", "schema": { "type": "ref", - "ref": "well.known.oauth.authorization.server.defs#serverMetadata" + "ref": "wellKnown.defs#resourceMetadata" } } } From c10f43db6836b697e25275ccad0b6f4351a88fb7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 03:12:18 +0900 Subject: [PATCH 030/127] =?UTF-8?q?PAR=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=81=BE=E3=81=A7=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 15 +++++- lib/atprotocol/lexicons.h | 9 ++++ lib/atprotocol/lexicons_func.cpp | 11 +++++ lib/atprotocol/lexicons_func.h | 5 ++ .../oauth/oauthpushedauthorizationrequest.cpp | 38 +++++++++++++++ .../oauth/oauthpushedauthorizationrequest.h | 26 +++++++++++ lib/lib.pri | 2 + lib/tools/authorization.cpp | 46 ++++++++----------- lib/tools/authorization.h | 3 ++ scripts/lexicons/oauth.defs.json | 17 +++++++ ...h.pushedAuthorizationRequest.json.template | 16 +++++++ tests/oauth_test/tst_oauth_test.cpp | 25 ++++++++++ 12 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 lib/extension/oauth/oauthpushedauthorizationrequest.cpp create mode 100644 lib/extension/oauth/oauthpushedauthorizationrequest.h create mode 100644 scripts/lexicons/oauth.defs.json create mode 100644 scripts/lexicons/oauth.pushedAuthorizationRequest.json.template diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index 76e4e2f5..a7ed7064 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -134,7 +134,12 @@ void AccessAtProtocol::get(const QString &endpoint, const QUrlQuery &query, qDebug().noquote() << LOG_DATETIME << " " << endpoint; qDebug().noquote() << LOG_DATETIME << " " << query.toString(); - QUrl url = QString("%1/%2").arg(service(), endpoint); + QUrl url; + if (endpoint.isEmpty()) { + url = service(); + } else { + url = QString("%1/%2").arg(service(), endpoint); + } url.setQuery(query); QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); @@ -191,7 +196,13 @@ void AccessAtProtocol::post(const QString &endpoint, const QByteArray &json, qDebug().noquote() << LOG_DATETIME << " " << endpoint; qDebug().noquote() << LOG_DATETIME << " " << json; - QNetworkRequest request(QUrl(QString("%1/%2").arg(service(), endpoint))); + QUrl url; + if (endpoint.isEmpty()) { + url = service(); + } else { + url = QString("%1/%2").arg(service(), endpoint); + } + QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (with_auth_header) { diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 8c82a592..b15c6dfa 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,15 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// oauth.defs +namespace OauthDefs { +struct PushedAuthorizationResponse +{ + QString request_uri; + int expires_in = 0; +}; +} + // wellKnown.defs namespace WellKnownDefs { struct ResourceMetadata diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index b3748e61..0617271f 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,17 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// oauth.defs +namespace OauthDefs { +void copyPushedAuthorizationResponse(const QJsonObject &src, + OauthDefs::PushedAuthorizationResponse &dest) +{ + if (!src.isEmpty()) { + dest.request_uri = src.value("request_uri").toString(); + dest.expires_in = src.value("expires_in").toInt(); + } +} +} // wellKnown.defs namespace WellKnownDefs { void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest) diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 904acd72..f9831261 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// oauth.defs +namespace OauthDefs { +void copyPushedAuthorizationResponse(const QJsonObject &src, + OauthDefs::PushedAuthorizationResponse &dest); +} // wellKnown.defs namespace WellKnownDefs { void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest); diff --git a/lib/extension/oauth/oauthpushedauthorizationrequest.cpp b/lib/extension/oauth/oauthpushedauthorizationrequest.cpp new file mode 100644 index 00000000..bb226e16 --- /dev/null +++ b/lib/extension/oauth/oauthpushedauthorizationrequest.cpp @@ -0,0 +1,38 @@ +#include "oauthpushedauthorizationrequest.h" +#include "atprotocol/lexicons_func.h" + +#include +#include + +namespace AtProtocolInterface { + +OauthPushedAuthorizationRequest::OauthPushedAuthorizationRequest(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void OauthPushedAuthorizationRequest::pushedAuthorizationRequest(const QByteArray &payload) +{ + post(QString(), payload, false); +} + +const AtProtocolType::OauthDefs::PushedAuthorizationResponse & +OauthPushedAuthorizationRequest::pushedAuthorizationResponse() const +{ + return m_pushedAuthorizationResponse; +} + +bool OauthPushedAuthorizationRequest::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::OauthDefs::copyPushedAuthorizationResponse(json_doc.object(), + m_pushedAuthorizationResponse); + } + + return success; +} + +} diff --git a/lib/extension/oauth/oauthpushedauthorizationrequest.h b/lib/extension/oauth/oauthpushedauthorizationrequest.h new file mode 100644 index 00000000..c229f133 --- /dev/null +++ b/lib/extension/oauth/oauthpushedauthorizationrequest.h @@ -0,0 +1,26 @@ +#ifndef OAUTHPUSHEDAUTHORIZATIONREQUEST_H +#define OAUTHPUSHEDAUTHORIZATIONREQUEST_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class OauthPushedAuthorizationRequest : public AccessAtProtocol +{ +public: + explicit OauthPushedAuthorizationRequest(QObject *parent = nullptr); + + void pushedAuthorizationRequest(const QByteArray &payload); + + const AtProtocolType::OauthDefs::PushedAuthorizationResponse & + pushedAuthorizationResponse() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::OauthDefs::PushedAuthorizationResponse m_pushedAuthorizationResponse; +}; + +} + +#endif // OAUTHPUSHEDAUTHORIZATIONREQUEST_H diff --git a/lib/lib.pri b/lib/lib.pri index 0ecb969c..bac8800d 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -85,6 +85,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/oauth/oauthpushedauthorizationrequest.cpp \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ @@ -199,6 +200,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/oauth/oauthpushedauthorizationrequest.h \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 83f442ec..bd3823e0 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -4,6 +4,7 @@ #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" #include "extension/well-known/wellknownoauthprotectedresource.h" #include "extension/well-known/wellknownoauthauthorizationserver.h" +#include "extension/oauth/oauthpushedauthorizationrequest.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -19,6 +20,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::OauthPushedAuthorizationRequest; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; @@ -252,38 +254,26 @@ void Authorization::par() if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) return; - QNetworkRequest request((QUrl(pushedAuthorizationRequestEndpoint()))); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QPointer alive = this; - HttpAccess *access = new HttpAccess(this); - HttpReply *reply = new HttpReply(access); - reply->setOperation(HttpReply::Operation::PostOperation); - reply->setRequest(request); - reply->setSendData(m_parPayload); - connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - if (alive && reply != nullptr) { - qDebug().noquote() << reply->error() << reply->url().toString(); - qDebug().noquote() << reply->contentType(); - qDebug().noquote() << reply->readAll(); - - if (reply->error() == HttpReply::Success) { - QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + AtProtocolInterface::AccountData account; + account.service = pushedAuthorizationRequestEndpoint(); - qDebug().noquote() - << "request_uri" << json_doc.object().value("request_uri").toString(); - authorization(json_doc.object().value("request_uri").toString()); + OauthPushedAuthorizationRequest *req = new OauthPushedAuthorizationRequest(this); + connect(req, &OauthPushedAuthorizationRequest::finished, this, [=](bool success) { + if (success) { + if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { + qDebug().noquote() << req->pushedAuthorizationResponse().request_uri; + authorization(req->pushedAuthorizationResponse().request_uri); } else { - // error - qDebug() << "PAR Error"; - emit finished(false); + emit errorOccured("Invalid Pushed Authorization Request", + "'request_uri' is empty."); } } else { - qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + emit errorOccured(req->errorCode(), req->errorMessage()); } - access->deleteLater(); + req->deleteLater(); }); - access->process(reply); + req->setAccount(account); + req->pushedAuthorizationRequest(m_parPayload); } void Authorization::authorization(const QString &request_uri) @@ -305,7 +295,11 @@ void Authorization::authorization(const QString &request_uri) qDebug().noquote() << "redirect" << url.toEncoded(); +#ifdef HAGOROMO_UNIT_TEST + emit madeRedirectUrl(url.toString()); +#else QDesktopServices::openUrl(url); +#endif } void Authorization::startRedirectServer() diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 5a283482..04db3491 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -45,6 +45,9 @@ class Authorization : public QObject void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); void finished(bool success); +#ifdef HAGOROMO_UNIT_TEST + void madeRedirectUrl(const QString &url); +#endif private: QByteArray generateRandomValues() const; diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json new file mode 100644 index 00000000..57b2cf31 --- /dev/null +++ b/scripts/lexicons/oauth.defs.json @@ -0,0 +1,17 @@ +{ + "lexicon": 1, + "id": "oauth.defs", + "defs": { + "pushedAuthorizationResponse": { + "type": "object", + "properties": { + "request_uri": { + "type": "string" + }, + "expires_in": { + "type": "integer" + } + } + } + } +} diff --git a/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template b/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template new file mode 100644 index 00000000..4b428b4e --- /dev/null +++ b/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template @@ -0,0 +1,16 @@ +{ + "lexicon": 1, + "id": "oauth.pushedAuthorizationRequest", + "defs": { + "main": { + "type": "procedure", + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "oauth.defs#pushedAuthorizationResponse" + } + } + } + } +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index b5310980..cd4f4dd8 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -158,6 +158,31 @@ void oauth_test::test_oauth() == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); QVERIFY(oauth.authorizationEndpoint() == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); + + // + { + QSignalSpy spy(&oauth, SIGNAL(madeRedirectUrl(const QString &))); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY2(arguments.at(0).toString() + == "https://bsky.social/oauth/" + "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%2Fhagoromo%" + "3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8080%252Ftech%" + "252Frelog%" + "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%3Aparams%" + "3Aoauth%" + "3Arequest_uri%3Areq-05650c01604941dc674f0af9cb032aca", + arguments.at(0).toString().toLocal8Bit()); + } + + // { + // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + // spy.wait(); + // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + // QList arguments = spy.takeFirst(); + // QVERIFY(arguments.at(0).toBool()); + // } } void oauth_test::test_jwt() From 9a08bff7a789cf837282ecc2a5205defbc7cb29a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 5 Sep 2024 01:55:17 +0900 Subject: [PATCH 031/127] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=81=8B=E3=82=89?= =?UTF-8?q?=E3=83=AA=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=B5=81=E3=82=8C=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func.cpp | 1 + lib/tools/authorization.cpp | 131 +++++++++++++------- lib/tools/authorization.h | 15 ++- scripts/lexicons/wellKnown.defs.json | 3 + tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/authorize | 0 tests/oauth_test/tst_oauth_test.cpp | 95 +++++++++++--- 8 files changed, 185 insertions(+), 62 deletions(-) create mode 100644 tests/oauth_test/response/2/oauth/authorize diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index b15c6dfa..dc7abd9c 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2085,6 +2085,7 @@ struct ServerMetadata QList subject_types_supported; bool authorization_response_iss_parameter_supported = false; QString pushed_authorization_request_endpoint; + QString token_endpoint; bool require_pushed_authorization_requests = false; QList dpop_signing_alg_values_supported; bool require_request_uri_registration = false; diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 0617271f..f59f01fa 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2966,6 +2966,7 @@ void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &d src.value("authorization_response_iss_parameter_supported").toBool(); dest.pushed_authorization_request_endpoint = src.value("pushed_authorization_request_endpoint").toString(); + dest.token_endpoint = src.value("token_endpoint").toString(); dest.require_pushed_authorization_requests = src.value("require_pushed_authorization_requests").toBool(); for (const auto &value : src.value("dpop_signing_alg_values_supported").toArray()) { diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index bd3823e0..83f55266 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -24,7 +24,7 @@ using AtProtocolInterface::OauthPushedAuthorizationRequest; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; -Authorization::Authorization(QObject *parent) : QObject { parent } { } +Authorization::Authorization(QObject *parent) : QObject { parent }, m_redirectTimeout(300) { } void Authorization::reset() { @@ -36,6 +36,7 @@ void Authorization::reset() // server meta data m_pushedAuthorizationRequestEndpoint.clear(); m_authorizationEndpoint.clear(); + m_tokenEndopoint.clear(); m_scopesSupported.clear(); // m_redirectUri.clear(); @@ -130,6 +131,7 @@ void Authorization::requestOauthAuthorizationServer() setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); setAuthorizationEndpoint(server->serverMetadata().authorization_endpoint); + setTokenEndopoint(server->serverMetadata().token_endpoint); m_scopesSupported = server->serverMetadata().scopes_supported; // next step @@ -204,6 +206,16 @@ bool Authorization::validateServerMetadata( return ret; } +QByteArray Authorization::state() const +{ + return m_state; +} + +QString Authorization::listenPort() const +{ + return m_listenPort; +} + void Authorization::makeClientId() { QString port; @@ -261,7 +273,8 @@ void Authorization::par() connect(req, &OauthPushedAuthorizationRequest::finished, this, [=](bool success) { if (success) { if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { - qDebug().noquote() << req->pushedAuthorizationResponse().request_uri; + // next step + startRedirectServer(); authorization(req->pushedAuthorizationResponse().request_uri); } else { emit errorOccured("Invalid Pushed Authorization Request", @@ -278,11 +291,12 @@ void Authorization::par() void Authorization::authorization(const QString &request_uri) { - if (request_uri.isEmpty()) + if (request_uri.isEmpty() || authorizationEndpoint().isEmpty() || m_listenPort.isEmpty()) return; - QString authorization_endpoint = "https://bsky.social/oauth/authorize"; - QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString authorization_endpoint = authorizationEndpoint(); + QString redirect_uri = + QString("http://127.0.0.1:%1/tech/relog/hagoromo/oauth-callback").arg(m_listenPort); QString client_id = "http://localhost/tech/relog/" "hagoromo?redirect_uri=" + simplyEncode(redirect_uri); @@ -296,7 +310,7 @@ void Authorization::authorization(const QString &request_uri) qDebug().noquote() << "redirect" << url.toEncoded(); #ifdef HAGOROMO_UNIT_TEST - emit madeRedirectUrl(url.toString()); + emit madeRequestUrl(url.toString()); #else QDesktopServices::openUrl(url); #endif @@ -308,6 +322,8 @@ void Authorization::startRedirectServer() connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { + qDebug().noquote() << "receive by startRedirectServer"; + if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") && request.query().hasQueryItem("code")) { // authorize @@ -315,7 +331,7 @@ void Authorization::startRedirectServer() result = (state.toUtf8() == m_state); if (result) { m_code = request.query().queryItemValue("code").toUtf8(); - // requestToken(); + requestToken(); } else { qDebug().noquote() << "Unknown state in authorization redirect :" << state; emit finished(false); @@ -337,11 +353,13 @@ void Authorization::startRedirectServer() }); connect(server, &SimpleHttpServer::timeout, this, [=]() { // token取得に進んでたらfinishedは発火しない - qDebug().noquote() << "Authorization timeout"; - emit finished(false); + if (m_code.isEmpty()) { + qDebug().noquote() << "Authorization timeout"; + emit finished(false); + } server->deleteLater(); }); - server->setTimeout(50); // 300); + server->setTimeout(redirectTimeout()); quint16 port = server->listen(QHostAddress::LocalHost, 0); m_listenPort = QString::number(port); @@ -361,45 +379,49 @@ void Authorization::makeRequestTokenPayload() m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); } -bool Authorization::requestToken() +void Authorization::requestToken() { + if (tokenEndopoint().isEmpty()) + return; - QString endpoint = "https://bsky.social/oauth/token"; + QString endpoint = tokenEndopoint(); QNetworkRequest request((QUrl(endpoint))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader(QByteArray("DPoP"), QByteArray("")); - QPointer alive = this; - HttpAccess *access = new HttpAccess(this); - HttpReply *reply = new HttpReply(access); - reply->setOperation(HttpReply::Operation::PostOperation); - reply->setRequest(request); - // reply->setSendData(m_parPayload); - connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - if (alive && reply != nullptr) { - qDebug().noquote() << reply->error() << reply->url().toString(); - - qDebug().noquote() << reply->contentType(); - qDebug().noquote() << reply->readAll(); - if (reply->error() == HttpReply::Success) { - QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); - - qDebug().noquote() - << "request_uri" << json_doc.object().value("request_uri").toString(); - } else { - // error - qDebug() << "Request token Error"; - } - } else { - qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; - } - access->deleteLater(); - }); - qDebug() << "request token 1"; - access->process(reply); - qDebug() << "request token 2"; - - return true; + emit finished(true); // temporary + + // QPointer alive = this; + // HttpAccess *access = new HttpAccess(this); + // HttpReply *reply = new HttpReply(access); + // reply->setOperation(HttpReply::Operation::PostOperation); + // reply->setRequest(request); + // // reply->setSendData(m_parPayload); + // connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + // if (alive && reply != nullptr) { + // qDebug().noquote() << reply->error() << reply->url().toString(); + + // qDebug().noquote() << reply->contentType(); + // qDebug().noquote() << reply->readAll(); + // if (reply->error() == HttpReply::Success) { + // QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + // qDebug().noquote() + // << "request_uri" << json_doc.object().value("request_uri").toString(); + // } else { + // // error + // qDebug() << "Request token Error"; + // } + // } else { + // qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + // } + // access->deleteLater(); + // }); + // qDebug() << "request token 1"; + // access->process(reply); + // qDebug() << "request token 2"; + + return; } QByteArray Authorization::generateRandomValues() const @@ -478,6 +500,29 @@ void Authorization::setAuthorizationEndpoint(const QString &newAuthorizationEndp emit authorizationEndpointChanged(); } +QString Authorization::tokenEndopoint() const +{ + return m_tokenEndopoint; +} + +void Authorization::setTokenEndopoint(const QString &newTokenEndopoint) +{ + if (m_tokenEndopoint == newTokenEndopoint) + return; + m_tokenEndopoint = newTokenEndopoint; + emit tokenEndopointChanged(); +} + +int Authorization::redirectTimeout() const +{ + return m_redirectTimeout; +} + +void Authorization::setRedirectTimeout(int newRedirectTimeout) +{ + m_redirectTimeout = newRedirectTimeout; +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 04db3491..c8c07bde 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -22,7 +22,7 @@ class Authorization : public QObject void startRedirectServer(); void makeRequestTokenPayload(); - bool requestToken(); + void requestToken(); QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); @@ -33,20 +33,29 @@ class Authorization : public QObject setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); QString authorizationEndpoint() const; void setAuthorizationEndpoint(const QString &newAuthorizationEndpoint); + QString tokenEndopoint() const; + void setTokenEndopoint(const QString &newTokenEndopoint); + int redirectTimeout() const; + void setRedirectTimeout(int newRedirectTimeout); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; + QString listenPort() const; + + QByteArray state() const; + signals: void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); void authorizationServerChanged(); void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); + void tokenEndopointChanged(); void finished(bool success); #ifdef HAGOROMO_UNIT_TEST - void madeRedirectUrl(const QString &url); + void madeRequestUrl(const QString &url); #endif private: @@ -68,6 +77,7 @@ class Authorization : public QObject // server meta data QString m_pushedAuthorizationRequestEndpoint; QString m_authorizationEndpoint; + QString m_tokenEndopoint; QStringList m_scopesSupported; // QString m_redirectUri; @@ -82,6 +92,7 @@ class Authorization : public QObject QByteArray m_requestTokenPayload; QString m_listenPort; + int m_redirectTimeout; }; #endif // AUTHORIZATION_H diff --git a/scripts/lexicons/wellKnown.defs.json b/scripts/lexicons/wellKnown.defs.json index 248ed0b5..a0d8f875 100644 --- a/scripts/lexicons/wellKnown.defs.json +++ b/scripts/lexicons/wellKnown.defs.json @@ -85,6 +85,9 @@ "pushed_authorization_request_endpoint": { "type": "string" }, + "token_endpoint": { + "type": "string" + }, "require_pushed_authorization_requests": { "type": "boolean" }, diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index 8da511bf..cd94807d 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -4,5 +4,6 @@ response/2/xrpc/com.atproto.repo.describeRepo response/2/.well-known/oauth-authorization-server response/2/oauth/par + response/2/oauth/authorize diff --git a/tests/oauth_test/response/2/oauth/authorize b/tests/oauth_test/response/2/oauth/authorize new file mode 100644 index 00000000..e69de29b diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index cd4f4dd8..5d27654b 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -12,6 +12,7 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "tools/es256.h" +#include "http/httpaccess.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -35,6 +36,7 @@ private slots: SimpleHttpServer m_server; quint16 m_listenPort; + void test_get(const QString &url, const QByteArray &except_data); void verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey); }; @@ -51,6 +53,7 @@ oauth_test::oauth_test() qDebug().noquote() << request.url(); QString path = SimpleHttpServer::convertResoucePath(request.url()); qDebug().noquote() << " res path =" << path; + if (!QFile::exists(path)) { result = false; } else { @@ -129,6 +132,8 @@ void oauth_test::test_oauth() // response/2 : entry-way Authorization oauth; + oauth.setRedirectTimeout(3); + QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); QString handle = "ioriayane.relog.tech"; @@ -160,29 +165,65 @@ void oauth_test::test_oauth() == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); // + QString request_url; { - QSignalSpy spy(&oauth, SIGNAL(madeRedirectUrl(const QString &))); + QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); - QVERIFY2(arguments.at(0).toString() - == "https://bsky.social/oauth/" - "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%2Fhagoromo%" - "3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8080%252Ftech%" - "252Frelog%" - "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%3Aparams%" - "3Aoauth%" - "3Arequest_uri%3Areq-05650c01604941dc674f0af9cb032aca", - arguments.at(0).toString().toLocal8Bit()); + request_url = arguments.at(0).toString(); + QVERIFY2( + request_url + == (QStringLiteral("http://localhost:") + QString::number(m_listenPort) + + QStringLiteral( + "/response/2/oauth/" + "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%" + "2Fhagoromo%3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A") + + oauth.listenPort() + + QStringLiteral( + "%252Ftech%252Frelog%" + "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%" + "3Aparams%3Aoauth%3Arequest_uri%3Areq-" + "05650c01604941dc674f0af9cb032aca")), + request_url.toLocal8Bit()); } - // { - // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // spy.wait(); - // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - // QList arguments = spy.takeFirst(); - // QVERIFY(arguments.at(0).toBool()); - // } + { + // ブラウザで認証ができないのでタイムアウトしてくるのを確認 + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(!arguments.at(0).toBool()); + } + + // ブラウザに認証しにいくURLからリダイレクトURLを取り出す + QUrl redirect_url; + { + QUrl request(request_url); + QUrlQuery request_query(request.query()); + QUrl client_id(request_query.queryItemValue("client_id", QUrl::FullyDecoded)); + QUrlQuery client_query(client_id.query()); + redirect_url = client_query.queryItemValue("redirect_uri", QUrl::FullyDecoded); + QUrlQuery redirect_query; + redirect_query.addQueryItem("iss", "iss-hogehoge"); + redirect_query.addQueryItem("state", oauth.state()); + redirect_query.addQueryItem("code", "code-hogehoge"); + redirect_url.setQuery(redirect_query); + qDebug().noquote() << "extract to " << redirect_url; + } + // 認証終了したていで続き + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.startRedirectServer(); + redirect_url.setPort(oauth.listenPort().toInt()); + qDebug().noquote() << "port updated " << redirect_url; + test_get(redirect_url.toString(), QByteArray()); // ブラウザへのアクセスを模擬 + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } } void oauth_test::test_jwt() @@ -228,6 +269,26 @@ void oauth_test::test_es256() } } +void oauth_test::test_get(const QString &url, const QByteArray &except_data) +{ + QNetworkRequest request((QUrl(url))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, [=](QNetworkReply *reply) { + qDebug() << "test_get reply" << reply->error() << reply->url(); + + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + QVERIFY(reply->error() == QNetworkReply::NoError); + QVERIFY2(reply->readAll() == except_data, json_doc.toJson()); + + reply->deleteLater(); + manager->deleteLater(); + }); + manager->get(request); +} + void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { EC_KEY *ec_key = nullptr; From 9d99bc5669864c672a8312d621289a1b96f73e7d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 6 Sep 2024 01:45:43 +0900 Subject: [PATCH 032/127] =?UTF-8?q?token=E3=81=AE=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83=E3=83=97=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 7 ++ lib/atprotocol/lexicons_func.cpp | 9 ++ lib/atprotocol/lexicons_func.h | 1 + lib/extension/oauth/oauthrequesttoken.cpp | 33 +++++++ lib/extension/oauth/oauthrequesttoken.h | 25 ++++++ lib/lib.pri | 2 + lib/tools/authorization.cpp | 105 +++++++++++++--------- lib/tools/authorization.h | 4 + lib/tools/jsonwebtoken.cpp | 5 +- lib/tools/jsonwebtoken.h | 2 +- scripts/lexicons/oauth.defs.json | 17 ++++ scripts/lexicons/oauth.requestToken.json | 16 ++++ tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/token | 6 ++ tests/oauth_test/tst_oauth_test.cpp | 19 +++- 15 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 lib/extension/oauth/oauthrequesttoken.cpp create mode 100644 lib/extension/oauth/oauthrequesttoken.h create mode 100644 scripts/lexicons/oauth.requestToken.json create mode 100644 tests/oauth_test/response/2/oauth/token diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index dc7abd9c..b1b6bf7e 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2061,6 +2061,13 @@ struct PushedAuthorizationResponse QString request_uri; int expires_in = 0; }; +struct TokenResponse +{ + QString access_token; + QString token_type; + QString refresh_token; + int expires_in = 0; +}; } // wellKnown.defs diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index f59f01fa..da49ccf3 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2917,6 +2917,15 @@ void copyPushedAuthorizationResponse(const QJsonObject &src, dest.expires_in = src.value("expires_in").toInt(); } } +void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest) +{ + if (!src.isEmpty()) { + dest.access_token = src.value("access_token").toString(); + dest.token_type = src.value("token_type").toString(); + dest.refresh_token = src.value("refresh_token").toString(); + dest.expires_in = src.value("expires_in").toInt(); + } +} } // wellKnown.defs namespace WellKnownDefs { diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index f9831261..d1542ee5 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -416,6 +416,7 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) namespace OauthDefs { void copyPushedAuthorizationResponse(const QJsonObject &src, OauthDefs::PushedAuthorizationResponse &dest); +void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest); } // wellKnown.defs namespace WellKnownDefs { diff --git a/lib/extension/oauth/oauthrequesttoken.cpp b/lib/extension/oauth/oauthrequesttoken.cpp new file mode 100644 index 00000000..2c72874f --- /dev/null +++ b/lib/extension/oauth/oauthrequesttoken.cpp @@ -0,0 +1,33 @@ +#include "oauthrequesttoken.h" +#include "atprotocol/lexicons_func.h" + +#include +#include + +namespace AtProtocolInterface { + +OauthRequestToken::OauthRequestToken(QObject *parent) : AccessAtProtocol { parent } { } + +void OauthRequestToken::requestToken(const QByteArray &payload) +{ + post(QString(""), payload, false); +} + +const AtProtocolType::OauthDefs::TokenResponse &OauthRequestToken::tokenResponse() const +{ + return m_tokenResponse; +} + +bool OauthRequestToken::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::OauthDefs::copyTokenResponse(json_doc.object(), m_tokenResponse); + } + + return success; +} + +} diff --git a/lib/extension/oauth/oauthrequesttoken.h b/lib/extension/oauth/oauthrequesttoken.h new file mode 100644 index 00000000..400df02f --- /dev/null +++ b/lib/extension/oauth/oauthrequesttoken.h @@ -0,0 +1,25 @@ +#ifndef OAUTHREQUESTTOKEN_H +#define OAUTHREQUESTTOKEN_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class OauthRequestToken : public AccessAtProtocol +{ +public: + explicit OauthRequestToken(QObject *parent = nullptr); + + void requestToken(const QByteArray &payload); + + const AtProtocolType::OauthDefs::TokenResponse &tokenResponse() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::OauthDefs::TokenResponse m_tokenResponse; +}; + +} + +#endif // OAUTHREQUESTTOKEN_H diff --git a/lib/lib.pri b/lib/lib.pri index bac8800d..c0e7d3f8 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -86,6 +86,7 @@ SOURCES += \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ $$PWD/extension/oauth/oauthpushedauthorizationrequest.cpp \ + $$PWD/extension/oauth/oauthrequesttoken.cpp \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ @@ -201,6 +202,7 @@ HEADERS += \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ $$PWD/extension/oauth/oauthpushedauthorizationrequest.h \ + $$PWD/extension/oauth/oauthrequesttoken.h \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 83f55266..f753e033 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -5,7 +5,9 @@ #include "extension/well-known/wellknownoauthprotectedresource.h" #include "extension/well-known/wellknownoauthauthorizationserver.h" #include "extension/oauth/oauthpushedauthorizationrequest.h" +#include "extension/oauth/oauthrequesttoken.h" #include "atprotocol/lexicons_func_unknown.h" +#include "tools/jsonwebtoken.h" #include #include @@ -21,6 +23,7 @@ using AtProtocolInterface::ComAtprotoRepoDescribeRepo; using AtProtocolInterface::OauthPushedAuthorizationRequest; +using AtProtocolInterface::OauthRequestToken; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; @@ -49,6 +52,7 @@ void Authorization::reset() // request token m_code.clear(); m_requestTokenPayload.clear(); + m_token = AtProtocolType::OauthDefs::TokenResponse(); // m_listenPort.clear(); } @@ -60,6 +64,7 @@ void Authorization::start(const QString &pds, const QString &handle) AtProtocolInterface::AccountData account; account.service = pds; + m_handle = handle; ComAtprotoRepoDescribeRepo *repo = new ComAtprotoRepoDescribeRepo(this); connect(repo, &ComAtprotoRepoDescribeRepo::finished, this, [=](bool success) { @@ -74,9 +79,11 @@ void Authorization::start(const QString &pds, const QString &handle) } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); + emit finished(false); } } else { emit errorOccured(repo->errorCode(), repo->errorMessage()); + emit finished(false); } repo->deleteLater(); }); @@ -103,9 +110,11 @@ void Authorization::requestOauthProtectedResource() } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); + emit finished(false); } } else { emit errorOccured(resource->errorCode(), resource->errorMessage()); + emit finished(false); } resource->deleteLater(); }); @@ -140,9 +149,11 @@ void Authorization::requestOauthAuthorizationServer() } else { qDebug().noquote() << error_message; emit errorOccured("Invalid oauth-authorization-server", error_message); + emit finished(false); } } else { emit errorOccured(server->errorCode(), server->errorMessage()); + emit finished(false); } server->deleteLater(); }); @@ -279,9 +290,11 @@ void Authorization::par() } else { emit errorOccured("Invalid Pushed Authorization Request", "'request_uri' is empty."); + emit finished(false); } } else { emit errorOccured(req->errorCode(), req->errorMessage()); + emit finished(false); } req->deleteLater(); }); @@ -346,10 +359,7 @@ void Authorization::startRedirectServer() mime_type = "text/html"; // delete after 10 sec. - QTimer::singleShot(10 * 1000, [=]() { - emit finished(true); // temporary - server->deleteLater(); - }); + QTimer::singleShot(10 * 1000, [=]() { server->deleteLater(); }); }); connect(server, &SimpleHttpServer::timeout, this, [=]() { // token取得に進んでたらfinishedは発火しない @@ -384,44 +394,40 @@ void Authorization::requestToken() if (tokenEndopoint().isEmpty()) return; - QString endpoint = tokenEndopoint(); - QNetworkRequest request((QUrl(endpoint))); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader(QByteArray("DPoP"), QByteArray("")); - - emit finished(true); // temporary - - // QPointer alive = this; - // HttpAccess *access = new HttpAccess(this); - // HttpReply *reply = new HttpReply(access); - // reply->setOperation(HttpReply::Operation::PostOperation); - // reply->setRequest(request); - // // reply->setSendData(m_parPayload); - // connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - // if (alive && reply != nullptr) { - // qDebug().noquote() << reply->error() << reply->url().toString(); - - // qDebug().noquote() << reply->contentType(); - // qDebug().noquote() << reply->readAll(); - // if (reply->error() == HttpReply::Success) { - // QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); - - // qDebug().noquote() - // << "request_uri" << json_doc.object().value("request_uri").toString(); - // } else { - // // error - // qDebug() << "Request token Error"; - // } - // } else { - // qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; - // } - // access->deleteLater(); - // }); - // qDebug() << "request token 1"; - // access->process(reply); - // qDebug() << "request token 2"; - - return; + makeRequestTokenPayload(); + + AtProtocolInterface::AccountData account; + account.service = tokenEndopoint(); + + OauthRequestToken *req = new OauthRequestToken(this); + connect(req, &OauthRequestToken::finished, this, [=](bool success) { + bool ret = false; + if (success) { + if (!req->tokenResponse().access_token.isEmpty() + && !req->tokenResponse().refresh_token.isEmpty() + && req->tokenResponse().token_type.toLower() == "dpop") { + + setToken(req->tokenResponse()); + + qDebug().noquote() << "--- Success oauth ----"; + qDebug().noquote() << " handle :" << m_handle; + qDebug().noquote() << " access :" << m_token.access_token; + qDebug().noquote() << " refresh:" << m_token.refresh_token; + + // finish oauth sequence + ret = true; + } else { + emit errorOccured("Invalid token response", req->replyJson()); + } + } else { + emit errorOccured(req->errorCode(), req->errorMessage()); + } + emit finished(ret); + req->deleteLater(); + }); + req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->setAccount(account); + req->requestToken(m_requestTokenPayload); } QByteArray Authorization::generateRandomValues() const @@ -523,6 +529,21 @@ void Authorization::setRedirectTimeout(int newRedirectTimeout) m_redirectTimeout = newRedirectTimeout; } +AtProtocolType::OauthDefs::TokenResponse Authorization::token() const +{ + return m_token; +} + +void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken) +{ + if (m_token.access_token == newToken.access_token && m_token.expires_in == newToken.expires_in + && m_token.refresh_token == newToken.refresh_token + && m_token.token_type == newToken.token_type) + return; + m_token = newToken; + emit tokenChanged(); +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index c8c07bde..a6186caf 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -37,6 +37,8 @@ class Authorization : public QObject void setTokenEndopoint(const QString &newTokenEndopoint); int redirectTimeout() const; void setRedirectTimeout(int newRedirectTimeout); + AtProtocolType::OauthDefs::TokenResponse token() const; + void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); QByteArray codeVerifier() const; QByteArray codeChallenge() const; @@ -53,6 +55,7 @@ class Authorization : public QObject void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); void tokenEndopointChanged(); + void tokenChanged(); void finished(bool success); #ifdef HAGOROMO_UNIT_TEST void madeRequestUrl(const QString &url); @@ -90,6 +93,7 @@ class Authorization : public QObject // request token QByteArray m_code; QByteArray m_requestTokenPayload; + AtProtocolType::OauthDefs::TokenResponse m_token; QString m_listenPort; int m_redirectTimeout; diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 8cf7b704..3adf1518 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -32,7 +32,7 @@ inline QJsonObject createJwk() return jwk; } -QByteArray JsonWebToken::generate(const QString &endpoint) +QByteArray JsonWebToken::generate(const QString &handle) { // ヘッダー QJsonObject header; @@ -44,8 +44,7 @@ QByteArray JsonWebToken::generate(const QString &endpoint) // ペイロード QJsonObject payload; - payload["sub"] = "1234567890"; // ユーザーIDなど - payload["name"] = "John Doe"; + payload["sub"] = handle; // ユーザーIDなど payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); QByteArray payloadBase64 = base64UrlEncode(payloadJson); diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index ff43ee22..b1db15b5 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,7 @@ class JsonWebToken { public: - static QByteArray generate(const QString &endpoint); + static QByteArray generate(const QString &handle); }; #endif // JSONWEBTOKEN_H diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json index 57b2cf31..124d0ace 100644 --- a/scripts/lexicons/oauth.defs.json +++ b/scripts/lexicons/oauth.defs.json @@ -12,6 +12,23 @@ "type": "integer" } } + }, + "tokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + } + } } } } diff --git a/scripts/lexicons/oauth.requestToken.json b/scripts/lexicons/oauth.requestToken.json new file mode 100644 index 00000000..babf70de --- /dev/null +++ b/scripts/lexicons/oauth.requestToken.json @@ -0,0 +1,16 @@ +{ + "lexicon": 1, + "id": "oauth.requestToken", + "defs": { + "main": { + "type": "procedure", + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "oauth.defs#tokenResponse" + } + } + } + } +} diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index cd94807d..d5427b01 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -5,5 +5,6 @@ response/2/.well-known/oauth-authorization-server response/2/oauth/par response/2/oauth/authorize + response/2/oauth/token diff --git a/tests/oauth_test/response/2/oauth/token b/tests/oauth_test/response/2/oauth/token new file mode 100644 index 00000000..a84748fd --- /dev/null +++ b/tests/oauth_test/response/2/oauth/token @@ -0,0 +1,6 @@ +{ + "access_token": "access token", + "token_type": "DPoP", + "expires_in": 2677, + "refresh_token": "refresh token" +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 5d27654b..947f0634 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -12,7 +12,6 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "tools/es256.h" -#include "http/httpaccess.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -54,6 +53,12 @@ oauth_test::oauth_test() QString path = SimpleHttpServer::convertResoucePath(request.url()); qDebug().noquote() << " res path =" << path; + if (path.endsWith("/oauth/token")) { + qDebug().noquote() << "Verify jwt"; + QVERIFY(request.headers().contains("DPoP")); + verify_jwt(request.headers().value("DPoP").toByteArray(), nullptr); + } + if (!QFile::exists(path)) { result = false; } else { @@ -214,16 +219,22 @@ void oauth_test::test_oauth() } // 認証終了したていで続き { - QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + QSignalSpy spy(&oauth, SIGNAL(tokenChanged())); oauth.startRedirectServer(); redirect_url.setPort(oauth.listenPort().toInt()); qDebug().noquote() << "port updated " << redirect_url; test_get(redirect_url.toString(), QByteArray()); // ブラウザへのアクセスを模擬 spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - QList arguments = spy.takeFirst(); - QVERIFY(arguments.at(0).toBool()); } + + QVERIFY2(oauth.token().access_token == "access token", + oauth.token().access_token.toLocal8Bit()); + QVERIFY2(oauth.token().token_type == "DPoP", oauth.token().token_type.toLocal8Bit()); + QVERIFY2(oauth.token().refresh_token == "refresh token", + oauth.token().refresh_token.toLocal8Bit()); + QVERIFY2(oauth.token().expires_in == 2677, + QString::number(oauth.token().expires_in).toLocal8Bit()); } void oauth_test::test_jwt() From 204aefcaed7df8bcdd17eda1050cd4e506b3c4f7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 6 Sep 2024 03:13:29 +0900 Subject: [PATCH 033/127] =?UTF-8?q?content=20type=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 10 ++++++++-- lib/atprotocol/accessatprotocol.h | 3 +++ lib/tools/authorization.cpp | 7 +++++-- tests/oauth_test/tst_oauth_test.cpp | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index a7ed7064..fb88906d 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -107,7 +107,8 @@ QString AtProtocolAccount::refreshJwt() const return m_account.refreshJwt; } -AccessAtProtocol::AccessAtProtocol(QObject *parent) : AtProtocolAccount { parent } +AccessAtProtocol::AccessAtProtocol(QObject *parent) + : AtProtocolAccount { parent }, m_contentType("application/json") { qDebug().noquote() << LOG_DATETIME << "AccessAtProtocol::AccessAtProtocol()" << this; if (m_manager == nullptr) { @@ -204,7 +205,7 @@ void AccessAtProtocol::post(const QString &endpoint, const QByteArray &json, } QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::ContentTypeHeader, m_contentType); if (with_auth_header) { if (accessJwt().isEmpty()) { qCritical() << LOG_DATETIME << "AccessAtProtocol::post()" @@ -415,6 +416,11 @@ void AccessAtProtocol::setAdditionalRawHeader(QNetworkRequest &request) } } +void AccessAtProtocol::setContentType(const QString &newContentType) +{ + m_contentType = newContentType; +} + QString AccessAtProtocol::cursor() const { return m_cursor; diff --git a/lib/atprotocol/accessatprotocol.h b/lib/atprotocol/accessatprotocol.h index 7c248ff6..48f80a5c 100644 --- a/lib/atprotocol/accessatprotocol.h +++ b/lib/atprotocol/accessatprotocol.h @@ -88,6 +88,8 @@ class AccessAtProtocol : public AtProtocolAccount void setCursor(const QString &newCursor); void appendRawHeader(const QString &name, const QString &value); + void setContentType(const QString &newContentType); + signals: void finished(bool success); @@ -121,6 +123,7 @@ public slots: QString m_errorMessage; QString m_cursor; + QString m_contentType; QHash m_additionalRawHeaders; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index f753e033..e0ed2a49 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -257,7 +257,7 @@ void Authorization::makeParPayload() m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QString login_hint = "ioriayane2.bsky.social"; + QString login_hint = m_handle; QUrlQuery query; query.addQueryItem("response_type", "code"); @@ -298,6 +298,7 @@ void Authorization::par() } req->deleteLater(); }); + req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->pushedAuthorizationRequest(m_parPayload); } @@ -413,7 +414,8 @@ void Authorization::requestToken() qDebug().noquote() << " handle :" << m_handle; qDebug().noquote() << " access :" << m_token.access_token; qDebug().noquote() << " refresh:" << m_token.refresh_token; - + qDebug().noquote() << req->replyJson(); + qDebug().noquote() << "----------------------"; // finish oauth sequence ret = true; } else { @@ -426,6 +428,7 @@ void Authorization::requestToken() req->deleteLater(); }); req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->requestToken(m_requestTokenPayload); } diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 947f0634..107127be 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -123,8 +123,8 @@ void oauth_test::test_oauth_server() // { // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // oauth.startRedirectServer(); - // spy.wait(60 * 1000); + // oauth.start("https://bsky.social", "ioriayane2.bsky.social"); + // spy.wait(300 * 1000); // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); // QList arguments = spy.takeFirst(); // QVERIFY(arguments.at(0).toBool()); From 30c80018e8fc7f28db906a623cf3bfbd6cc25e3b Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 7 Sep 2024 02:09:00 +0900 Subject: [PATCH 034/127] =?UTF-8?q?token=E5=8F=96=E5=BE=97=E3=82=B9?= =?UTF-8?q?=E3=83=86=E3=83=83=E3=83=97=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 15 +++++++--- lib/atprotocol/accessatprotocol.h | 2 ++ lib/tools/authorization.cpp | 40 ++++++++++++++++--------- lib/tools/authorization.h | 5 +++- lib/tools/jsonwebtoken.cpp | 16 ++++++++-- lib/tools/jsonwebtoken.h | 3 +- tests/oauth_test/tst_oauth_test.cpp | 45 +++++++++++++++++++---------- 7 files changed, 88 insertions(+), 38 deletions(-) diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index fb88906d..32b15d7e 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -306,19 +306,21 @@ bool AccessAtProtocol::checkReply(HttpReply *reply) m_errorCode.clear(); m_errorMessage.clear(); -#ifdef QT_DEBUG + QByteArray header_key; for (const auto &header : reply->rawHeaderPairs()) { - if (header.first.toLower().startsWith("ratelimit-")) { - if (header.first.toLower() == "ratelimit-reset") { + header_key = header.first.toLower(); + if (header_key.startsWith("ratelimit-")) { + if (header_key == "ratelimit-reset") { qDebug().noquote() << LOG_DATETIME << header.first << QDateTime::fromSecsSinceEpoch(header.second.toInt()) .toString("yyyy/MM/dd hh:mm:ss"); } else { qDebug().noquote() << LOG_DATETIME << header.first << header.second; } + } else if (header_key == "dpop-nonce") { + m_dPopNonce = header.second; } } -#endif QJsonDocument json_doc = QJsonDocument::fromJson(m_replyJson.toUtf8()); if (reply->error() != HttpReply::Success) { @@ -416,6 +418,11 @@ void AccessAtProtocol::setAdditionalRawHeader(QNetworkRequest &request) } } +QString AccessAtProtocol::dPopNonce() const +{ + return m_dPopNonce; +} + void AccessAtProtocol::setContentType(const QString &newContentType) { m_contentType = newContentType; diff --git a/lib/atprotocol/accessatprotocol.h b/lib/atprotocol/accessatprotocol.h index 48f80a5c..4ca67880 100644 --- a/lib/atprotocol/accessatprotocol.h +++ b/lib/atprotocol/accessatprotocol.h @@ -89,6 +89,7 @@ class AccessAtProtocol : public AtProtocolAccount void appendRawHeader(const QString &name, const QString &value); void setContentType(const QString &newContentType); + QString dPopNonce() const; signals: void finished(bool success); @@ -125,6 +126,7 @@ public slots: QString m_contentType; QHash m_additionalRawHeaders; + QString m_dPopNonce; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index e0ed2a49..314f6a14 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -62,6 +62,8 @@ void Authorization::start(const QString &pds, const QString &handle) if (pds.isEmpty() || handle.isEmpty()) return; + startRedirectServer(); + AtProtocolInterface::AccountData account; account.service = pds; m_handle = handle; @@ -243,10 +245,10 @@ void Authorization::makeClientId() void Authorization::makeCodeChallenge() { - m_codeChallenge = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals); - m_codeVerifier = - QCryptographicHash::hash(m_codeChallenge, QCryptographicHash::Sha256) + m_codeVerifier = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + m_codeChallenge = + QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } @@ -285,7 +287,7 @@ void Authorization::par() if (success) { if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { // next step - startRedirectServer(); + setDPopNonce(req->dPopNonce()); authorization(req->pushedAuthorizationResponse().request_uri); } else { emit errorOccured("Invalid Pushed Authorization Request", @@ -309,15 +311,10 @@ void Authorization::authorization(const QString &request_uri) return; QString authorization_endpoint = authorizationEndpoint(); - QString redirect_uri = - QString("http://127.0.0.1:%1/tech/relog/hagoromo/oauth-callback").arg(m_listenPort); - QString client_id = "http://localhost/tech/relog/" - "hagoromo?redirect_uri=" - + simplyEncode(redirect_uri); QUrl url(authorization_endpoint); QUrlQuery query; - query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("request_uri", simplyEncode(request_uri)); url.setQuery(query); @@ -337,6 +334,10 @@ void Authorization::startRedirectServer() [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { qDebug().noquote() << "receive by startRedirectServer"; + qDebug().noquote() << " " << request.url().toString(); + qDebug().noquote() << " " << request.url().path(); + + if (request.url().path() == "/tech/relog/hagoromo/oauth-callback") { } if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") && request.query().hasQueryItem("code")) { @@ -408,6 +409,9 @@ void Authorization::requestToken() && !req->tokenResponse().refresh_token.isEmpty() && req->tokenResponse().token_type.toLower() == "dpop") { + if (!req->dPopNonce().isEmpty()) { + setDPopNonce(req->dPopNonce()); + } setToken(req->tokenResponse()); qDebug().noquote() << "--- Success oauth ----"; @@ -427,7 +431,8 @@ void Authorization::requestToken() emit finished(ret); req->deleteLater(); }); - req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->appendRawHeader("DPoP", + JsonWebToken::generate(tokenEndopoint(), m_clientId, "POST", dPopNonce())); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->requestToken(m_requestTokenPayload); @@ -436,7 +441,7 @@ void Authorization::requestToken() QByteArray Authorization::generateRandomValues() const { QByteArray values; -#ifdef HAGOROMO_UNIT_TEST1 +#ifdef HAGOROMO_UNIT_TEST_ const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; @@ -547,6 +552,15 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new emit tokenChanged(); } +QString Authorization::dPopNonce() const +{ + return m_dPopNonce; +} + +void Authorization::setDPopNonce(const QString &newDPopNonce) +{ + m_dPopNonce = newDPopNonce; +} QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index a6186caf..77635076 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -40,12 +40,14 @@ class Authorization : public QObject AtProtocolType::OauthDefs::TokenResponse token() const; void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); + QString dPopNonce() const; + void setDPopNonce(const QString &newDPopNonce); + QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; QString listenPort() const; - QByteArray state() const; signals: @@ -85,6 +87,7 @@ class Authorization : public QObject // QString m_redirectUri; QString m_clientId; + QString m_dPopNonce; // par QByteArray m_codeChallenge; QByteArray m_codeVerifier; diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 3adf1518..5816fa01 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -32,7 +32,8 @@ inline QJsonObject createJwk() return jwk; } -QByteArray JsonWebToken::generate(const QString &handle) +QByteArray JsonWebToken::generate(const QString &endpoint, const QString &client_id, + const QString &method, const QString &nonce) { // ヘッダー QJsonObject header; @@ -43,9 +44,18 @@ QByteArray JsonWebToken::generate(const QString &handle) QByteArray headerBase64 = base64UrlEncode(headerJson); // ペイロード + qint64 epoch = QDateTime::currentSecsSinceEpoch(); QJsonObject payload; - payload["sub"] = handle; // ユーザーIDなど - payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 + payload["iss"] = "tech/relog/hagoromo"; + payload["sub"] = client_id; + payload["htu"] = endpoint; + payload["htm"] = method; + payload["exp"] = epoch + 60000; + payload["jti"] = QString(QString::number(epoch).toUtf8().toBase64()); + payload["iat"] = epoch; // 発行時間 + if (!nonce.isEmpty()) { + payload["nonce"] = nonce; + } QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); QByteArray payloadBase64 = base64UrlEncode(payloadJson); diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index b1db15b5..213cb013 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,8 @@ class JsonWebToken { public: - static QByteArray generate(const QString &handle); + static QByteArray generate(const QString &endpoint, const QString &client_id, + const QString &method, const QString &nonce); }; #endif // JSONWEBTOKEN_H diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 107127be..a190b780 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -87,7 +88,7 @@ void oauth_test::test_oauth_process() for (int i = 0; i < sizeof(base); i++) { base_ba.append(base[i]); } - qDebug() << sizeof(base) << base_ba.size() + qDebug() << "codeVerifier" << sizeof(base) << base_ba.size() << base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QString msg; @@ -98,7 +99,8 @@ void oauth_test::test_oauth_process() msg += QString::number(static_cast(s)) + ", "; } qDebug() << msg; - qDebug() << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + qDebug() << "codeChallenge" + << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); Authorization oauth; oauth.makeCodeChallenge(); @@ -106,9 +108,9 @@ void oauth_test::test_oauth_process() qDebug() << "codeVerifier" << oauth.codeVerifier(); // QVERIFY(oauth.codeChallenge() - // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - // QVERIFY(oauth.codeVerifier() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + // QVERIFY(oauth.codeVerifier() + // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); oauth.makeParPayload(); qDebug() << "ParPlayload" << oauth.ParPayload(); @@ -118,17 +120,27 @@ void oauth_test::test_oauth_process() void oauth_test::test_oauth_server() { - +#if 0 Authorization oauth; - // { - // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // oauth.start("https://bsky.social", "ioriayane2.bsky.social"); - // spy.wait(300 * 1000); - // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - // QList arguments = spy.takeFirst(); - // QVERIFY(arguments.at(0).toBool()); - // } + { + QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); + oauth.start("https://bsky.social", "ioriayane.bsky.social"); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QString request_url = arguments.at(0).toString(); + qDebug().noquote() << "request url:" << request_url; + QDesktopServices::openUrl(request_url); + } + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + spy.wait(5 * 60 * 1000); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } +#endif } void oauth_test::test_oauth() @@ -137,7 +149,7 @@ void oauth_test::test_oauth() // response/2 : entry-way Authorization oauth; - oauth.setRedirectTimeout(3); + oauth.setRedirectTimeout(20); QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); QString handle = "ioriayane.relog.tech"; @@ -196,7 +208,7 @@ void oauth_test::test_oauth() { // ブラウザで認証ができないのでタイムアウトしてくるのを確認 QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - spy.wait(); + spy.wait(20 * 1000); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); QVERIFY(!arguments.at(0).toBool()); @@ -239,7 +251,8 @@ void oauth_test::test_oauth() void oauth_test::test_jwt() { - QByteArray jwt = JsonWebToken::generate("https://hoge"); + QByteArray jwt = JsonWebToken::generate("https://hoge", "client_id", "GET", + "O_m5dyvKO7jNfnsfuYwB5GflhTuVaqCub4x3xVKqJ9Y"); qDebug().noquote() << jwt; From 4202b1af6d2d6fe92d5433b63a3be85d1c051a76 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 7 Sep 2024 02:41:45 +0900 Subject: [PATCH 035/127] =?UTF-8?q?token=E5=8F=96=E5=BE=97=E6=99=82?= =?UTF-8?q?=E3=81=AE=E4=BB=98=E5=B1=9E=E6=83=85=E5=A0=B1=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 2 ++ lib/atprotocol/lexicons_func.cpp | 2 ++ lib/tools/authorization.cpp | 3 ++- scripts/lexicons/oauth.defs.json | 6 ++++++ ...h.requestToken.json => oauth.requestToken.json.template} | 0 5 files changed, 12 insertions(+), 1 deletion(-) rename scripts/lexicons/{oauth.requestToken.json => oauth.requestToken.json.template} (100%) diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index b1b6bf7e..e6c92f0c 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2066,6 +2066,8 @@ struct TokenResponse QString access_token; QString token_type; QString refresh_token; + QString scope; + QString sub; int expires_in = 0; }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index da49ccf3..30a09355 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2923,6 +2923,8 @@ void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest) dest.access_token = src.value("access_token").toString(); dest.token_type = src.value("token_type").toString(); dest.refresh_token = src.value("refresh_token").toString(); + dest.scope = src.value("scope").toString(); + dest.sub = src.value("sub").toString(); dest.expires_in = src.value("expires_in").toInt(); } } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 314f6a14..082352e1 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -546,7 +546,8 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new { if (m_token.access_token == newToken.access_token && m_token.expires_in == newToken.expires_in && m_token.refresh_token == newToken.refresh_token - && m_token.token_type == newToken.token_type) + && m_token.token_type == newToken.token_type && m_token.sub == newToken.sub + && m_token.scope == newToken.scope) return; m_token = newToken; emit tokenChanged(); diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json index 124d0ace..158b4f3c 100644 --- a/scripts/lexicons/oauth.defs.json +++ b/scripts/lexicons/oauth.defs.json @@ -25,6 +25,12 @@ "refresh_token": { "type": "string" }, + "scope": { + "type": "string" + }, + "sub": { + "type": "string" + }, "expires_in": { "type": "integer" } diff --git a/scripts/lexicons/oauth.requestToken.json b/scripts/lexicons/oauth.requestToken.json.template similarity index 100% rename from scripts/lexicons/oauth.requestToken.json rename to scripts/lexicons/oauth.requestToken.json.template From c92390955d09e70a7713f7fa0631da7b57de99d8 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 8 Sep 2024 02:00:52 +0900 Subject: [PATCH 036/127] =?UTF-8?q?=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E3=83=AA=E3=83=95=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 61 ++++++++++++++++++----------- lib/tools/authorization.h | 12 +++--- tests/oauth_test/tst_oauth_test.cpp | 24 ++++++++---- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 082352e1..55d9cfdc 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -48,10 +48,8 @@ void Authorization::reset() m_codeChallenge.clear(); m_codeVerifier.clear(); m_state.clear(); - m_parPayload.clear(); // request token m_code.clear(); - m_requestTokenPayload.clear(); m_token = AtProtocolType::OauthDefs::TokenResponse(); // m_listenPort.clear(); @@ -146,7 +144,6 @@ void Authorization::requestOauthAuthorizationServer() m_scopesSupported = server->serverMetadata().scopes_supported; // next step - makeParPayload(); par(); } else { qDebug().noquote() << error_message; @@ -224,6 +221,11 @@ QByteArray Authorization::state() const return m_state; } +void Authorization::setListenPort(const QString &newListenPort) +{ + m_listenPort = newListenPort; +} + QString Authorization::listenPort() const { return m_listenPort; @@ -252,7 +254,7 @@ void Authorization::makeCodeChallenge() .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } -void Authorization::makeParPayload() +QByteArray Authorization::makeParPayload() { makeClientId(); makeCodeChallenge(); @@ -271,12 +273,12 @@ void Authorization::makeParPayload() query.addQueryItem("scope", m_scopesSupported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); - m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + return query.query(QUrl::FullyEncoded).toLocal8Bit(); } void Authorization::par() { - if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) + if (pushedAuthorizationRequestEndpoint().isEmpty()) return; AtProtocolInterface::AccountData account; @@ -302,7 +304,7 @@ void Authorization::par() }); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); - req->pushedAuthorizationRequest(m_parPayload); + req->pushedAuthorizationRequest(makeParPayload()); } void Authorization::authorization(const QString &request_uri) @@ -371,6 +373,11 @@ void Authorization::startRedirectServer() } server->deleteLater(); }); + connect(server, &QObject::destroyed, [this]() { + qDebug().noquote() << "Destory webserver"; + m_listenPort.clear(); + }); + server->setTimeout(redirectTimeout()); quint16 port = server->listen(QHostAddress::LocalHost, 0); m_listenPort = QString::number(port); @@ -378,26 +385,30 @@ void Authorization::startRedirectServer() qDebug().noquote() << "Listen" << m_listenPort; } -void Authorization::makeRequestTokenPayload() +QByteArray Authorization::makeRequestTokenPayload(bool refresh) { QUrlQuery query; - query.addQueryItem("grant_type", "authorization_code"); - query.addQueryItem("code", m_code); - query.addQueryItem("code_verifier", m_codeVerifier); - query.addQueryItem("client_id", simplyEncode(m_clientId)); - query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + if (refresh) { + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", token().refresh_token); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + } else { + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", m_code); + query.addQueryItem("code_verifier", m_codeVerifier); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + } - m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + return query.query(QUrl::FullyEncoded).toLocal8Bit(); } -void Authorization::requestToken() +void Authorization::requestToken(bool refresh) { if (tokenEndopoint().isEmpty()) return; - makeRequestTokenPayload(); - AtProtocolInterface::AccountData account; account.service = tokenEndopoint(); @@ -435,7 +446,7 @@ void Authorization::requestToken() JsonWebToken::generate(tokenEndopoint(), m_clientId, "POST", dPopNonce())); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); - req->requestToken(m_requestTokenPayload); + req->requestToken(makeRequestTokenPayload(refresh)); } QByteArray Authorization::generateRandomValues() const @@ -553,6 +564,16 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new emit tokenChanged(); } +QString Authorization::clientId() const +{ + return m_clientId; +} + +void Authorization::setClientId(const QString &newClientId) +{ + m_clientId = newClientId; +} + QString Authorization::dPopNonce() const { return m_dPopNonce; @@ -562,10 +583,6 @@ void Authorization::setDPopNonce(const QString &newDPopNonce) { m_dPopNonce = newDPopNonce; } -QByteArray Authorization::ParPayload() const -{ - return m_parPayload; -} QByteArray Authorization::codeChallenge() const { diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 77635076..ac441893 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -16,13 +16,13 @@ class Authorization : public QObject void makeClientId(); void makeCodeChallenge(); - void makeParPayload(); + QByteArray makeParPayload(); void par(); void authorization(const QString &request_uri); void startRedirectServer(); - void makeRequestTokenPayload(); - void requestToken(); + QByteArray makeRequestTokenPayload(bool refresh); + void requestToken(bool refresh = false); QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); @@ -40,14 +40,16 @@ class Authorization : public QObject AtProtocolType::OauthDefs::TokenResponse token() const; void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); + QString clientId() const; + void setClientId(const QString &newClientId); QString dPopNonce() const; void setDPopNonce(const QString &newDPopNonce); QByteArray codeVerifier() const; QByteArray codeChallenge() const; - QByteArray ParPayload() const; QString listenPort() const; + void setListenPort(const QString &newListenPort); QByteArray state() const; signals: @@ -92,10 +94,8 @@ class Authorization : public QObject QByteArray m_codeChallenge; QByteArray m_codeVerifier; QByteArray m_state; - QByteArray m_parPayload; // request token QByteArray m_code; - QByteArray m_requestTokenPayload; AtProtocolType::OauthDefs::TokenResponse m_token; QString m_listenPort; diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index a190b780..b9b12032 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -111,18 +111,12 @@ void oauth_test::test_oauth_process() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); // QVERIFY(oauth.codeVerifier() // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - - oauth.makeParPayload(); - qDebug() << "ParPlayload" << oauth.ParPayload(); - - // oauth.par(); } void oauth_test::test_oauth_server() { -#if 0 Authorization oauth; - +#if 0 { QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); oauth.start("https://bsky.social", "ioriayane.bsky.social"); @@ -140,6 +134,22 @@ void oauth_test::test_oauth_server() QList arguments = spy.takeFirst(); QVERIFY(arguments.at(0).toBool()); } +#elif 0 + AtProtocolType::OauthDefs::TokenResponse token; + token.refresh_token = "ref-121f89618c436ad99cbb792b5bd2b003fdc829dde32d73e02486b4ddb0c7bd99"; + oauth.setToken(token); + oauth.setTokenEndopoint("https://bsky.social/oauth/token"); + oauth.setDPopNonce("8mo0kjoyQ64_uOCBrZ4Q8M8-RT0BkfgAMDB7no5DkmU"); + oauth.setListenPort("65073"); + oauth.makeClientId(); + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.requestToken(true); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } #endif } From 9f1dbe2e07ad0249e1ee35137f44dfc60c5a6f93 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:05:04 +0900 Subject: [PATCH 037/127] =?UTF-8?q?http=E3=82=A2=E3=82=AF=E3=82=BB?= =?UTF-8?q?=E3=82=B9=E6=99=82=E3=81=AE=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=B9=E5=88=A4=E5=AE=9A=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/httpaccess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http/httpaccess.cpp b/lib/http/httpaccess.cpp index f3bd9eda..caa9aa87 100644 --- a/lib/http/httpaccess.cpp +++ b/lib/http/httpaccess.cpp @@ -138,7 +138,7 @@ bool HttpAccess::Private::process(HttpReply *reply) QByteArray::fromStdString(header.second)); } reply->setRecvData(QByteArray::fromStdString(res->body)); - if (res->status == 200) { + if (res->status >= 200 && res->status <= 299) { reply->setError(HttpReply::Success); result = true; } else { From 6fadedfac4fdfc9bea0eecd36cb6c4e3bace0b08 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:08:29 +0900 Subject: [PATCH 038/127] =?UTF-8?q?=E7=B0=A1=E6=98=93HTTP=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=83=90=E3=83=BC=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/simplehttpserver.cpp | 41 +++++++++++++++++++++++++++++++++++ lib/http/simplehttpserver.h | 23 ++++++++++++++++++++ lib/lib.pro | 4 +++- lib/lib.qrc | 6 +++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lib/http/simplehttpserver.cpp create mode 100644 lib/http/simplehttpserver.h create mode 100644 lib/lib.qrc diff --git a/lib/http/simplehttpserver.cpp b/lib/http/simplehttpserver.cpp new file mode 100644 index 00000000..97494414 --- /dev/null +++ b/lib/http/simplehttpserver.cpp @@ -0,0 +1,41 @@ +#include "simplehttpserver.h" +#include + +SimpleHttpServer::SimpleHttpServer(QObject *parent) : QAbstractHttpServer { parent } { } + +void SimpleHttpServer::setTimeout(int sec) +{ + QTimer::singleShot(sec * 1000, [=]() { emit timeout(); }); +} + +bool SimpleHttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) +{ + bool result = false; + QByteArray data; + QByteArray mime_type; + emit received(request, result, data, mime_type); + if (result) { + makeResponder(request, socket).write(data, mime_type, QHttpServerResponder::StatusCode::Ok); + } else { + makeResponder(request, socket).write(QHttpServerResponder::StatusCode::InternalServerError); + } + return true; +} + +QString SimpleHttpServer::convertResoucePath(const QUrl &url) +{ + QFileInfo file_info(url.path()); + return ":" + file_info.filePath(); +} + +bool SimpleHttpServer::readFile(const QString &path, QByteArray &data) +{ + QFile file(path); + if (file.open(QFile::ReadOnly)) { + data = file.readAll(); + file.close(); + return true; + } else { + return false; + } +} diff --git a/lib/http/simplehttpserver.h b/lib/http/simplehttpserver.h new file mode 100644 index 00000000..335582bc --- /dev/null +++ b/lib/http/simplehttpserver.h @@ -0,0 +1,23 @@ +#ifndef SIMPLEHTTPSERVER_H +#define SIMPLEHTTPSERVER_H + +#include + +class SimpleHttpServer : public QAbstractHttpServer +{ + Q_OBJECT +public: + explicit SimpleHttpServer(QObject *parent = nullptr); + + void setTimeout(int sec); + bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override; + + static QString convertResoucePath(const QUrl &url); + static bool readFile(const QString &path, QByteArray &data); +signals: + void received(const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type); + void timeout(); +}; + +#endif // SIMPLEHTTPSERVER_H diff --git a/lib/lib.pro b/lib/lib.pro index ab985f2b..e5a2f125 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -1,4 +1,4 @@ -QT += xml sql websockets +QT += xml sql websockets httpserver TEMPLATE = lib CONFIG += staticlib @@ -94,6 +94,7 @@ SOURCES += \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ $$PWD/http/httpreply.cpp \ + $$PWD/http/simplehttpserver.cpp \ $$PWD/log/logaccess.cpp \ $$PWD/log/logmanager.cpp \ $$PWD/realtime/abstractpostselector.cpp \ @@ -201,6 +202,7 @@ HEADERS += \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ $$PWD/http/httpreply.h \ + $$PWD/http/simplehttpserver.h \ $$PWD/log/logaccess.h \ $$PWD/log/logmanager.h \ $$PWD/realtime/abstractpostselector.h \ diff --git a/lib/lib.qrc b/lib/lib.qrc new file mode 100644 index 00000000..4b6bda64 --- /dev/null +++ b/lib/lib.qrc @@ -0,0 +1,6 @@ + + + tools/oauth/oauth_fail.html + tools/oauth/oauth_success.html + + From 56a5dd76e7c7c2f7ea822de44579173d46fb5366 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 22 Aug 2024 02:08:51 +0900 Subject: [PATCH 039/127] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=81=AE=E4=BD=9C=E6=88=90=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 200 ++++++++++++++++++++++++++++ lib/tools/authorization.h | 39 ++++++ lib/tools/oauth/oauth_fail.html | 8 ++ lib/tools/oauth/oauth_success.html | 8 ++ tests/oauth_test/oauth_test.pro | 13 ++ tests/oauth_test/tst_oauth_test.cpp | 86 ++++++++++++ 6 files changed, 354 insertions(+) create mode 100644 lib/tools/authorization.cpp create mode 100644 lib/tools/authorization.h create mode 100644 lib/tools/oauth/oauth_fail.html create mode 100644 lib/tools/oauth/oauth_success.html create mode 100644 tests/oauth_test/oauth_test.pro create mode 100644 tests/oauth_test/tst_oauth_test.cpp diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp new file mode 100644 index 00000000..b2feec76 --- /dev/null +++ b/lib/tools/authorization.cpp @@ -0,0 +1,200 @@ +#include "authorization.h" +#include "http/httpaccess.h" +#include "http/simplehttpserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Authorization::Authorization(QObject *parent) : QObject { parent } { } + +void Authorization::reset() +{ + m_codeChallenge.clear(); + m_codeVerifier.clear(); +} + +void Authorization::makeCodeChallenge() +{ + m_codeChallenge = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + m_codeVerifier = + QCryptographicHash::hash(m_codeChallenge, QCryptographicHash::Sha256) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); +} + +void Authorization::makeParPayload() +{ + makeCodeChallenge(); + m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString client_id = "http://localhost/tech/relog/" + "hagoromo?redirect_uri=" + + simplyEncode(redirect_uri); + QStringList scopes_supported; + scopes_supported << "offline_access" + << "openid" + // << "email" + // << "phone" + << "profile"; + QString login_hint = "ioriayane2.bsky.social"; + + QUrlQuery query; + query.addQueryItem("response_type", "code"); + query.addQueryItem("code_challenge", m_codeChallenge); + query.addQueryItem("code_challenge_method", "S256"); + query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("state", m_state); + query.addQueryItem("redirect_uri", simplyEncode(redirect_uri)); + query.addQueryItem("scope", scopes_supported.join(" ")); + query.addQueryItem("login_hint", simplyEncode(login_hint)); + + m_parPlayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); +} + +void Authorization::par() +{ + if (m_parPlayload.isEmpty()) + return; + + QString endpoint = "https://bsky.social/oauth/par"; + QNetworkRequest request((QUrl(endpoint))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QPointer alive = this; + HttpAccess *access = new HttpAccess(this); + HttpReply *reply = new HttpReply(access); + reply->setOperation(HttpReply::Operation::PostOperation); + reply->setRequest(request); + reply->setSendData(m_parPlayload); + connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + if (alive && reply != nullptr) { + qDebug().noquote() << reply->error() << reply->url().toString(); + // emit finished(success); + qDebug().noquote() << reply->contentType(); + qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + qDebug().noquote() + << "request_uri" << json_doc.object().value("request_uri").toString(); + authorization(json_doc.object().value("request_uri").toString()); + } else { + // error + qDebug() << "PAR Error"; + } + } else { + qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + } + access->deleteLater(); + }); + access->process(reply); +} + +void Authorization::authorization(const QString &request_uri) +{ + if (request_uri.isEmpty()) + return; + + QString authorization_endpoint = "https://bsky.social/oauth/authorize"; + QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString client_id = "http://localhost/tech/relog/" + "hagoromo?redirect_uri=" + + simplyEncode(redirect_uri); + + QUrl url(authorization_endpoint); + QUrlQuery query; + query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("request_uri", simplyEncode(request_uri)); + url.setQuery(query); + + qDebug().noquote() << "redirect" << url.toEncoded(); + + QDesktopServices::openUrl(url); +} + +void Authorization::startRedirectServer() +{ + SimpleHttpServer *server = new SimpleHttpServer(this); + connect(server, &SimpleHttpServer::received, this, + [=](const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type) { + SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + mime_type = "text/html"; + result = true; + + // TODO : verify url and parameters + requestToken(); + + // delete after 10 sec. + QTimer::singleShot(10 * 1000, [=]() { + emit finished(true); // temporary + server->deleteLater(); + }); + }); + connect(server, &SimpleHttpServer::timeout, this, [=]() { + // token取得に進んでたらfinishedは発火しない + qDebug().noquote() << "Authorization timeout"; + emit finished(false); + server->deleteLater(); + }); + server->setTimeout(50); // 300); + quint16 port = server->listen(QHostAddress::LocalHost, 0); + m_listenPort = QString::number(port); + + qDebug().noquote() << "Listen" << m_listenPort; +} + +void Authorization::requestToken() +{ + + // +} + +QByteArray Authorization::generateRandomValues() const +{ + QByteArray values; +#ifdef HAGOROMO_UNIT_TEST1 + const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, + 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, + 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; + for (int i = 0; i < sizeof(base); i++) { + values.append(base[i]); + } +#else + for (int i = 0; i < 32; i++) { + values.append(static_cast(QRandomGenerator::global()->bounded(256))); + } +#endif + return values; +} + +QString Authorization::simplyEncode(QString text) const +{ + return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); +} + +QByteArray Authorization::ParPlayload() const +{ + return m_parPlayload; +} + +QByteArray Authorization::codeChallenge() const +{ + return m_codeChallenge; +} + +QByteArray Authorization::codeVerifier() const +{ + return m_codeVerifier; +} diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h new file mode 100644 index 00000000..7f1168fd --- /dev/null +++ b/lib/tools/authorization.h @@ -0,0 +1,39 @@ +#ifndef AUTHORIZATION_H +#define AUTHORIZATION_H + +#include + +class Authorization : public QObject +{ + Q_OBJECT +public: + explicit Authorization(QObject *parent = nullptr); + + void reset(); + void makeCodeChallenge(); + void makeParPayload(); + void par(); + void authorization(const QString &request_uri); + void startRedirectServer(); + void requestToken(); + + QByteArray codeVerifier() const; + QByteArray codeChallenge() const; + QByteArray ParPlayload() const; + +signals: + void finished(bool success); + +private: + QByteArray generateRandomValues() const; + QString simplyEncode(QString text) const; + + QByteArray m_codeChallenge; + QByteArray m_codeVerifier; + QByteArray m_state; + QByteArray m_parPlayload; + + QString m_listenPort; +}; + +#endif // AUTHORIZATION_H diff --git a/lib/tools/oauth/oauth_fail.html b/lib/tools/oauth/oauth_fail.html new file mode 100644 index 00000000..2e19f74d --- /dev/null +++ b/lib/tools/oauth/oauth_fail.html @@ -0,0 +1,8 @@ + + + Hagoromo + + + Authorization fail. + + diff --git a/lib/tools/oauth/oauth_success.html b/lib/tools/oauth/oauth_success.html new file mode 100644 index 00000000..b04c54c8 --- /dev/null +++ b/lib/tools/oauth/oauth_success.html @@ -0,0 +1,8 @@ + + + Hagoromo + + + Authorization success. + + diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro new file mode 100644 index 00000000..59229caa --- /dev/null +++ b/tests/oauth_test/oauth_test.pro @@ -0,0 +1,13 @@ +QT += testlib httpserver gui + +CONFIG += qt console warn_on depend_includepath testcase +CONFIG -= app_bundle + +TEMPLATE = app +DEFINES += HAGOROMO_UNIT_TEST + +SOURCES += tst_oauth_test.cpp + +include(../common/common.pri) +include(../../lib/lib.pri) +include(../../openssl/openssl.pri) diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp new file mode 100644 index 00000000..c3a9c604 --- /dev/null +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "tools/authorization.h" + +class oauth_test : public QObject +{ + Q_OBJECT + +public: + oauth_test(); + ~oauth_test(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void test_oauth_process(); + void test_oauth_server(); +}; + +oauth_test::oauth_test() { } + +oauth_test::~oauth_test() { } + +void oauth_test::initTestCase() { } + +void oauth_test::cleanupTestCase() { } + +void oauth_test::test_oauth_process() +{ + QString code_challenge; + + const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, + 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, + 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; + QByteArray base_ba; //(QByteArray::fromRawData(static_cast(base), sizeof(base))); + for (int i = 0; i < sizeof(base); i++) { + base_ba.append(base[i]); + } + qDebug() << sizeof(base) << base_ba.size() + << base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QString msg; + QByteArray sha256 = QCryptographicHash::hash( + base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals), + QCryptographicHash::Sha256); + for (const auto s : sha256) { + msg += QString::number(static_cast(s)) + ", "; + } + qDebug() << msg; + qDebug() << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + Authorization oauth; + oauth.makeCodeChallenge(); + qDebug() << "codeChallenge" << oauth.codeChallenge(); + qDebug() << "codeVerifier" << oauth.codeVerifier(); + + // QVERIFY(oauth.codeChallenge() + // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + // QVERIFY(oauth.codeVerifier() + // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + + oauth.makeParPayload(); + qDebug() << "ParPlayload" << oauth.ParPlayload(); + + oauth.par(); +} + +void oauth_test::test_oauth_server() +{ + + Authorization oauth; + + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.startRedirectServer(); + spy.wait(60 * 1000); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } +} + +QTEST_MAIN(oauth_test) + +#include "tst_oauth_test.moc" From f69c05f4f687a826ccf3aab183f843b6732aa373 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 23 Aug 2024 01:30:13 +0900 Subject: [PATCH 040/127] =?UTF-8?q?token=E8=A6=81=E6=B1=82=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 117 ++++++++++++++++++++++++++++++------ lib/tools/authorization.h | 15 ++++- 2 files changed, 110 insertions(+), 22 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index b2feec76..2c093822 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -18,8 +18,27 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + m_redirectUri.clear(); + m_clientId.clear(); m_codeChallenge.clear(); m_codeVerifier.clear(); + m_state.clear(); + m_parPayload.clear(); + m_requestTokenPayload.clear(); +} + +void Authorization::makeClientId() +{ + QString port; + if (!m_listenPort.isEmpty()) { + port.append(":"); + port.append(m_listenPort); + } + m_redirectUri.append("http://127.0.0.1"); + m_redirectUri.append(port); + m_redirectUri.append("/tech/relog/hagoromo/oauth-callback"); + m_clientId.append("http://localhost/tech/relog/hagoromo?redirect_uri="); + m_clientId.append(simplyEncode(m_redirectUri)); } void Authorization::makeCodeChallenge() @@ -33,14 +52,11 @@ void Authorization::makeCodeChallenge() void Authorization::makeParPayload() { + makeClientId(); makeCodeChallenge(); m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; - QString client_id = "http://localhost/tech/relog/" - "hagoromo?redirect_uri=" - + simplyEncode(redirect_uri); QStringList scopes_supported; scopes_supported << "offline_access" << "openid" @@ -53,18 +69,18 @@ void Authorization::makeParPayload() query.addQueryItem("response_type", "code"); query.addQueryItem("code_challenge", m_codeChallenge); query.addQueryItem("code_challenge_method", "S256"); - query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("state", m_state); - query.addQueryItem("redirect_uri", simplyEncode(redirect_uri)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); query.addQueryItem("scope", scopes_supported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); - m_parPlayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); } void Authorization::par() { - if (m_parPlayload.isEmpty()) + if (m_parPayload.isEmpty()) return; QString endpoint = "https://bsky.social/oauth/par"; @@ -76,13 +92,13 @@ void Authorization::par() HttpReply *reply = new HttpReply(access); reply->setOperation(HttpReply::Operation::PostOperation); reply->setRequest(request); - reply->setSendData(m_parPlayload); + reply->setSendData(m_parPayload); connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { if (alive && reply != nullptr) { qDebug().noquote() << reply->error() << reply->url().toString(); - // emit finished(success); qDebug().noquote() << reply->contentType(); qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); @@ -92,6 +108,7 @@ void Authorization::par() } else { // error qDebug() << "PAR Error"; + emit finished(false); } } else { qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; @@ -129,12 +146,26 @@ void Authorization::startRedirectServer() connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { - SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") + && request.query().hasQueryItem("code")) { + // authorize + QString state = request.query().queryItemValue("state"); + result = (state.toUtf8() == m_state); + if (result) { + m_code = request.query().queryItemValue("code").toUtf8(); + // requestToken(); + } else { + qDebug().noquote() << "Unknown state in authorization redirect :" << state; + emit finished(false); + m_code.clear(); + } + } + if (result) { + SimpleHttpServer::readFile(":/tools/oauth/oauth_success.html", data); + } else { + SimpleHttpServer::readFile(":/tools/oauth/oauth_fail.html", data); + } mime_type = "text/html"; - result = true; - - // TODO : verify url and parameters - requestToken(); // delete after 10 sec. QTimer::singleShot(10 * 1000, [=]() { @@ -155,10 +186,58 @@ void Authorization::startRedirectServer() qDebug().noquote() << "Listen" << m_listenPort; } -void Authorization::requestToken() +void Authorization::makeRequestTokenPayload() { + QUrlQuery query; + + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", m_code); + query.addQueryItem("code_verifier", m_codeVerifier); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + + m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); +} + +bool Authorization::requestToken() +{ + + QString endpoint = "https://bsky.social/oauth/token"; + QNetworkRequest request((QUrl(endpoint))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader(QByteArray("DPoP"), QByteArray("")); + + QPointer alive = this; + HttpAccess *access = new HttpAccess(this); + HttpReply *reply = new HttpReply(access); + reply->setOperation(HttpReply::Operation::PostOperation); + reply->setRequest(request); + // reply->setSendData(m_parPayload); + connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + if (alive && reply != nullptr) { + qDebug().noquote() << reply->error() << reply->url().toString(); + + qDebug().noquote() << reply->contentType(); + qDebug().noquote() << reply->readAll(); + if (reply->error() == HttpReply::Success) { + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + qDebug().noquote() + << "request_uri" << json_doc.object().value("request_uri").toString(); + } else { + // error + qDebug() << "Request token Error"; + } + } else { + qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + } + access->deleteLater(); + }); + qDebug() << "request token 1"; + access->process(reply); + qDebug() << "request token 2"; - // + return true; } QByteArray Authorization::generateRandomValues() const @@ -184,9 +263,9 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } -QByteArray Authorization::ParPlayload() const +QByteArray Authorization::ParPayload() const { - return m_parPlayload; + return m_parPayload; } QByteArray Authorization::codeChallenge() const diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 7f1168fd..1c7fab34 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -10,16 +10,19 @@ class Authorization : public QObject explicit Authorization(QObject *parent = nullptr); void reset(); + void makeClientId(); void makeCodeChallenge(); void makeParPayload(); void par(); void authorization(const QString &request_uri); void startRedirectServer(); - void requestToken(); + + void makeRequestTokenPayload(); + bool requestToken(); QByteArray codeVerifier() const; QByteArray codeChallenge() const; - QByteArray ParPlayload() const; + QByteArray ParPayload() const; signals: void finished(bool success); @@ -28,10 +31,16 @@ class Authorization : public QObject QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; + QString m_redirectUri; + QString m_clientId; + // par QByteArray m_codeChallenge; QByteArray m_codeVerifier; QByteArray m_state; - QByteArray m_parPlayload; + QByteArray m_parPayload; + // request token + QByteArray m_code; + QByteArray m_requestTokenPayload; QString m_listenPort; }; From 4c8276a4ea25a723844db470dc4ce5516e3bee90 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 24 Aug 2024 10:02:21 +0900 Subject: [PATCH 041/127] =?UTF-8?q?jwt=E5=AF=BE=E5=BF=9C=E3=81=AE=E6=BA=96?= =?UTF-8?q?=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lib.pro | 9 +++ lib/tools/es256.cpp | 9 +++ lib/tools/es256.h | 13 ++++ lib/tools/jsonwebtoken.cpp | 107 ++++++++++++++++++++++++++++ lib/tools/jsonwebtoken.h | 15 ++++ openssl/openssl.pri | 2 + tests/oauth_test/tst_oauth_test.cpp | 47 +++++++++--- tests/tests.pro | 1 + 8 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 lib/tools/es256.cpp create mode 100644 lib/tools/es256.h create mode 100644 lib/tools/jsonwebtoken.cpp create mode 100644 lib/tools/jsonwebtoken.h diff --git a/lib/lib.pro b/lib/lib.pro index e5a2f125..a3419e78 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -106,11 +106,14 @@ SOURCES += \ $$PWD/realtime/notpostselector.cpp \ $$PWD/realtime/orpostselector.cpp \ $$PWD/realtime/xorpostselector.cpp \ + $$PWD/tools/authorization.cpp \ $$PWD/tools/base32.cpp \ $$PWD/tools/cardecoder.cpp \ $$PWD/tools/chatlogsubscriber.cpp \ $$PWD/tools/configurablelabels.cpp \ + $$PWD/tools/es256.cpp \ $$PWD/tools/imagecompressor.cpp \ + $$PWD/tools/jsonwebtoken.cpp \ $$PWD/tools/labelerprovider.cpp \ $$PWD/tools/leb128.cpp \ $$PWD/tools/listitemscache.cpp \ @@ -215,14 +218,20 @@ HEADERS += \ $$PWD/realtime/orpostselector.h \ $$PWD/realtime/xorpostselector.h \ $$PWD/search/search.h \ + $$PWD/tools/authorization.h \ $$PWD/tools/base32.h \ $$PWD/tools/cardecoder.h \ $$PWD/tools/chatlogsubscriber.h \ $$PWD/tools/configurablelabels.h \ + $$PWD/tools/es256.h \ $$PWD/tools/imagecompressor.h \ + $$PWD/tools/jsonwebtoken.h \ $$PWD/tools/labelerprovider.h \ $$PWD/tools/leb128.h \ $$PWD/tools/listitemscache.h \ $$PWD/tools/opengraphprotocol.h \ $$PWD/tools/pinnedpostcache.h \ $$PWD/tools/qstringex.h + +RESOURCES += \ + $$PWD/lib.qrc diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp new file mode 100644 index 00000000..7ac4acb5 --- /dev/null +++ b/lib/tools/es256.cpp @@ -0,0 +1,9 @@ +#include "es256.h" + +Es256::Es256() { } + +Es256 *Es256::getInstance() +{ + static Es256 instance; + return &instance; +} diff --git a/lib/tools/es256.h b/lib/tools/es256.h new file mode 100644 index 00000000..c8c64bfd --- /dev/null +++ b/lib/tools/es256.h @@ -0,0 +1,13 @@ +#ifndef ES256_H +#define ES256_H + +class Es256 +{ + Es256(); + ~Es256() { } + +public: + static Es256 *getInstance(); +}; + +#endif // ES256_H diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp new file mode 100644 index 00000000..09da8cb5 --- /dev/null +++ b/lib/tools/jsonwebtoken.cpp @@ -0,0 +1,107 @@ +#include "jsonwebtoken.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QString JsonWebToken::generate(const QString &endpoint) +{ + // ヘッダー + QJsonObject header; + header["alg"] = "ES256"; + header["typ"] = "dpop+jwt"; + QJsonObject jwk; + jwk["kty"] = "EC"; + jwk["x"] = "TUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUVzQlVDaE9vbVB2UXhnMlZkZ05RNnc2NE1LS3hhVmQ" + "vRQ"; + jwk["y"] = "MVg2Y05QMU5DMFA4TzYrNVVsYm1LT1BGYlVsYk5FTGpFSVJtY3VraGlrMTFaSnp4UXY5dFJ3"; + jwk["crv"] = "P-256"; + header["jwk"] = jwk; + QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); + QByteArray headerBase64 = JsonWebToken::base64UrlEncode(headerJson); + + // ペイロード + QJsonObject payload; + payload["sub"] = "1234567890"; // ユーザーIDなど + payload["name"] = "John Doe"; + payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 + QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); + QByteArray payloadBase64 = JsonWebToken::base64UrlEncode(payloadJson); + + // 署名 + QByteArray message = headerBase64 + "." + payloadBase64; + QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); + QByteArray signatureBase64 = JsonWebToken::base64UrlEncode(signature); + + // JWTトークン + QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; + return jwt; +} + +QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyPath) +{ + QByteArray signature; + + // OpenSSLで秘密鍵を読み込む + FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + return signature; + } + + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + + if (!pkey) { + qWarning() << "Failed to read private key"; + return signature; + } + + // ECDSA署名を生成 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) <= 0) { + qWarning() << "Failed to initialize digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { + qWarning() << "Failed to update digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + size_t sigLen; + if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { + qWarning() << "Failed to finalize digest sign"; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return signature; + } + + signature.resize(sigLen); + if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) + <= 0) { + qWarning() << "Failed to finalize digest sign"; + } + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + + return signature; +} + +QByteArray JsonWebToken::base64UrlEncode(const QByteArray &data) +{ + QByteArray encoded = data.toBase64(); + encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + return encoded; +} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h new file mode 100644 index 00000000..0a53d103 --- /dev/null +++ b/lib/tools/jsonwebtoken.h @@ -0,0 +1,15 @@ +#ifndef JSONWEBTOKEN_H +#define JSONWEBTOKEN_H + +#include + +class JsonWebToken +{ +public: + static QString generate(const QString &endpoint); + + static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); + static QByteArray base64UrlEncode(const QByteArray &data); +}; + +#endif // JSONWEBTOKEN_H diff --git a/openssl/openssl.pri b/openssl/openssl.pri index ec09ce0e..70fed754 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -4,6 +4,8 @@ open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSL open_ssl_dir=$$clean_path($$open_ssl_dir) win32:{ + SOURCES += $${open_ssl_dir}/src/ms/applink.c + open_ssl_dir=$${open_ssl_dir}/Win_x64 LIBS += $${open_ssl_dir}/lib/libssl.lib \ diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index c3a9c604..410555df 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -2,6 +2,8 @@ #include #include "tools/authorization.h" +#include "tools/jsonwebtoken.h" +#include "http/simplehttpserver.h" class oauth_test : public QObject { @@ -16,9 +18,23 @@ private slots: void cleanupTestCase(); void test_oauth_process(); void test_oauth_server(); + void test_jwt(); + +private: + SimpleHttpServer m_server; + quint16 m_listenPort; }; -oauth_test::oauth_test() { } +oauth_test::oauth_test() +{ + + m_listenPort = m_server.listen(QHostAddress::LocalHost, 0); + connect(&m_server, &SimpleHttpServer::received, this, + [=](const QHttpServerRequest &request, bool &result, QByteArray &data, + QByteArray &mime_type) { + // + }); +} oauth_test::~oauth_test() { } @@ -61,9 +77,9 @@ void oauth_test::test_oauth_process() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); oauth.makeParPayload(); - qDebug() << "ParPlayload" << oauth.ParPlayload(); + qDebug() << "ParPlayload" << oauth.ParPayload(); - oauth.par(); + // oauth.par(); } void oauth_test::test_oauth_server() @@ -71,14 +87,23 @@ void oauth_test::test_oauth_server() Authorization oauth; - { - QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - oauth.startRedirectServer(); - spy.wait(60 * 1000); - QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - QList arguments = spy.takeFirst(); - QVERIFY(arguments.at(0).toBool()); - } + // { + // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + // oauth.startRedirectServer(); + // spy.wait(60 * 1000); + // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + // QList arguments = spy.takeFirst(); + // QVERIFY(arguments.at(0).toBool()); + // } +} + +void oauth_test::test_jwt() +{ + QString jwt = JsonWebToken::generate("https://hoge"); + + qDebug().noquote() << jwt; + + QVERIFY(true); } QTEST_MAIN(oauth_test) diff --git a/tests/tests.pro b/tests/tests.pro index f2d16827..cc215ebb 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -7,6 +7,7 @@ SUBDIRS += \ hagoromo_test2 \ http_test \ log_test \ + oauth_test \ realtime_test \ search_test \ tools_test From aac558251df7e57ac5b3ca67e5f2a682b344c66c Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 24 Aug 2024 17:23:31 +0900 Subject: [PATCH 042/127] =?UTF-8?q?jwk=E3=82=92=E7=94=9F=E6=88=90=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 134 +++++++++++++++++++++++++++++++------ lib/tools/jsonwebtoken.h | 1 - 2 files changed, 113 insertions(+), 22 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 09da8cb5..fbc05447 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -9,22 +9,82 @@ #include #include #include +#include + +QByteArray base64UrlEncode(const QByteArray &data) +{ + QByteArray encoded = data.toBase64(); + encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + return encoded; +} + +QJsonObject createJwk(EVP_PKEY *pkey) +{ + QJsonObject jwk; + + // ECキーの抽出 + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); + if (!ec_key) { + qWarning() << "Failed to get EC key from EVP_PKEY"; + return jwk; + } + + const EC_GROUP *group = EC_KEY_get0_group(ec_key); + const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + + // X, Y座標の抽出 + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + qWarning() << "Failed to get affine coordinates"; + BN_free(x); + BN_free(y); + return jwk; + } + + // X, Y座標をBase64URLエンコード + QByteArray xCoord = base64UrlEncode( + QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), BN_num_bytes(x))); + QByteArray yCoord = base64UrlEncode( + QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), BN_num_bytes(y))); + + jwk["kty"] = "EC"; // Key Type + jwk["crv"] = "P-256"; // Curve + jwk["x"] = QString::fromUtf8(xCoord); + jwk["y"] = QString::fromUtf8(yCoord); + + BN_free(x); + BN_free(y); + + return jwk; +} QString JsonWebToken::generate(const QString &endpoint) { + // OpenSSLで秘密鍵を読み込む + FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + return QString(); + } + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + if (!pkey) { + qWarning() << "Failed to read private key"; + return QString(); + } + + // JWKを生成 + QJsonObject jwk = createJwk(pkey); + EVP_PKEY_free(pkey); + // ヘッダー QJsonObject header; header["alg"] = "ES256"; header["typ"] = "dpop+jwt"; - QJsonObject jwk; - jwk["kty"] = "EC"; - jwk["x"] = "TUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUVzQlVDaE9vbVB2UXhnMlZkZ05RNnc2NE1LS3hhVmQ" - "vRQ"; - jwk["y"] = "MVg2Y05QMU5DMFA4TzYrNVVsYm1LT1BGYlVsYk5FTGpFSVJtY3VraGlrMTFaSnp4UXY5dFJ3"; - jwk["crv"] = "P-256"; header["jwk"] = jwk; QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); - QByteArray headerBase64 = JsonWebToken::base64UrlEncode(headerJson); + QByteArray headerBase64 = base64UrlEncode(headerJson); // ペイロード QJsonObject payload; @@ -32,12 +92,12 @@ QString JsonWebToken::generate(const QString &endpoint) payload["name"] = "John Doe"; payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); - QByteArray payloadBase64 = JsonWebToken::base64UrlEncode(payloadJson); + QByteArray payloadBase64 = base64UrlEncode(payloadJson); // 署名 QByteArray message = headerBase64 + "." + payloadBase64; QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); - QByteArray signatureBase64 = JsonWebToken::base64UrlEncode(signature); + QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; @@ -52,7 +112,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); if (!fp) { qWarning() << "Failed to open private key file"; - return signature; + return QByteArray(); } EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); @@ -60,7 +120,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP if (!pkey) { qWarning() << "Failed to read private key"; - return signature; + return QByteArray(); } // ECDSA署名を生成 @@ -69,14 +129,14 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to initialize digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { qWarning() << "Failed to update digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } size_t sigLen; @@ -84,7 +144,7 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to finalize digest sign"; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return QByteArray(); } signature.resize(sigLen); @@ -93,15 +153,47 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP qWarning() << "Failed to finalize digest sign"; } + //////// + /// + + /// Convert from ASN1 to DER format + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); + if (ec_key == NULL) + return QByteArray(); + + int degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key)); + EC_KEY_free(ec_key); + + /* Get the sig from the DER encoded version. */ + const unsigned char *temp = reinterpret_cast(signature.constData()); + ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); + if (ec_sig == NULL) + return QByteArray(); + + const BIGNUM *ec_sig_r = NULL; + const BIGNUM *ec_sig_s = NULL; + ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); + int r_len = BN_num_bytes(ec_sig_r); + int s_len = BN_num_bytes(ec_sig_s); + int bn_len = (degree + 7) / 8; + if ((r_len > bn_len) || (s_len > bn_len)) + return QByteArray(); + + /// Attention!!! std::vector from C++17, you can use unsigned char* but this C-style + /// char's array need allocate zeros, how I member it's memset function. Or use + /// std::vector. + std::vector raw_buf(static_cast(bn_len) * 2); + BN_bn2bin(ec_sig_r, reinterpret_cast(raw_buf.data()) + bn_len - r_len); + BN_bn2bin(ec_sig_s, reinterpret_cast(raw_buf.data()) + raw_buf.size() - s_len); + + std::string str(reinterpret_cast(raw_buf.data()), raw_buf.size()); + + qDebug().noquote() << "sig" << base64UrlEncode(QByteArray::fromStdString(str)); + /// + /// //// + EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); return signature; } - -QByteArray JsonWebToken::base64UrlEncode(const QByteArray &data) -{ - QByteArray encoded = data.toBase64(); - encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); - return encoded; -} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index 0a53d103..47d76e4b 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -9,7 +9,6 @@ class JsonWebToken static QString generate(const QString &endpoint); static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); - static QByteArray base64UrlEncode(const QByteArray &data); }; #endif // JSONWEBTOKEN_H From 398c1baff82f249ead14097a8d8aef65ad937882 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 00:15:10 +0900 Subject: [PATCH 043/127] =?UTF-8?q?jwt=E3=81=AE=E7=BD=B2=E5=90=8D=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 51 ++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index fbc05447..562c63c1 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -18,6 +18,14 @@ QByteArray base64UrlEncode(const QByteArray &data) return encoded; } +QByteArray bn2ba(const BIGNUM *bn) +{ + QByteArray ba; + ba.resize(BN_num_bytes(bn)); + BN_bn2bin(bn, reinterpret_cast(ba.data())); + return ba; +} + QJsonObject createJwk(EVP_PKEY *pkey) { QJsonObject jwk; @@ -151,49 +159,32 @@ QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyP if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) <= 0) { qWarning() << "Failed to finalize digest sign"; - } - - //////// - /// - - /// Convert from ASN1 to DER format - EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); - if (ec_key == NULL) return QByteArray(); + } - int degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key)); - EC_KEY_free(ec_key); - - /* Get the sig from the DER encoded version. */ + // Convert DER to IEEE P1363 + const int ec_sig_len = 64; // ES256のときの値 const unsigned char *temp = reinterpret_cast(signature.constData()); ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); - if (ec_sig == NULL) + if (ec_sig == NULL) { return QByteArray(); + } const BIGNUM *ec_sig_r = NULL; const BIGNUM *ec_sig_s = NULL; ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); - int r_len = BN_num_bytes(ec_sig_r); - int s_len = BN_num_bytes(ec_sig_s); - int bn_len = (degree + 7) / 8; - if ((r_len > bn_len) || (s_len > bn_len)) - return QByteArray(); - /// Attention!!! std::vector from C++17, you can use unsigned char* but this C-style - /// char's array need allocate zeros, how I member it's memset function. Or use - /// std::vector. - std::vector raw_buf(static_cast(bn_len) * 2); - BN_bn2bin(ec_sig_r, reinterpret_cast(raw_buf.data()) + bn_len - r_len); - BN_bn2bin(ec_sig_s, reinterpret_cast(raw_buf.data()) + raw_buf.size() - s_len); + QByteArray rr = bn2ba(ec_sig_r); + QByteArray ss = bn2ba(ec_sig_s); - std::string str(reinterpret_cast(raw_buf.data()), raw_buf.size()); - - qDebug().noquote() << "sig" << base64UrlEncode(QByteArray::fromStdString(str)); - /// - /// //// + if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { + return QByteArray(); + } + rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); + ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); - return signature; + return rr + ss; } From ef004a681ce01011c6e53e78fd1e44f1618e703c Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 02:18:02 +0900 Subject: [PATCH 044/127] =?UTF-8?q?jwt=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/jsonwebtoken.cpp | 12 ++++---- lib/tools/jsonwebtoken.h | 2 +- tests/oauth_test/tst_oauth_test.cpp | 46 ++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 562c63c1..271dc652 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -13,8 +13,8 @@ QByteArray base64UrlEncode(const QByteArray &data) { - QByteArray encoded = data.toBase64(); - encoded = encoded.replace('+', '-').replace('/', '_').replace("=", ""); + QByteArray encoded = + data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); return encoded; } @@ -67,19 +67,19 @@ QJsonObject createJwk(EVP_PKEY *pkey) return jwk; } -QString JsonWebToken::generate(const QString &endpoint) +QByteArray JsonWebToken::generate(const QString &endpoint) { // OpenSSLで秘密鍵を読み込む FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); if (!fp) { qWarning() << "Failed to open private key file"; - return QString(); + return QByteArray(); } EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); fclose(fp); if (!pkey) { qWarning() << "Failed to read private key"; - return QString(); + return QByteArray(); } // JWKを生成 @@ -108,7 +108,7 @@ QString JsonWebToken::generate(const QString &endpoint) QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン - QString jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; + QByteArray jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; return jwt; } diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index 47d76e4b..b6514f3a 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,7 @@ class JsonWebToken { public: - static QString generate(const QString &endpoint); + static QByteArray generate(const QString &endpoint); static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); }; diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 410555df..2bc508dd 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -1,6 +1,12 @@ #include #include +#include +#include +#include +#include +#include + #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "http/simplehttpserver.h" @@ -23,6 +29,8 @@ private slots: private: SimpleHttpServer m_server; quint16 m_listenPort; + + void verify_jwt(const QByteArray &jwt); }; oauth_test::oauth_test() @@ -99,13 +107,49 @@ void oauth_test::test_oauth_server() void oauth_test::test_jwt() { - QString jwt = JsonWebToken::generate("https://hoge"); + QByteArray jwt = JsonWebToken::generate("https://hoge"); qDebug().noquote() << jwt; + verify_jwt(jwt); + QVERIFY(true); } +void oauth_test::verify_jwt(const QByteArray &jwt) +{ + const QByteArrayList jwt_parts = jwt.split('.'); + QVERIFY2(jwt_parts.length() == 3, jwt); + + QByteArray sig = QByteArray::fromBase64( + jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + QByteArray sig_rr = sig.left(32); + QByteArray sig_ss = sig.right(32); + + BIGNUM *ec_sig_r = NULL; + BIGNUM *ec_sig_s = NULL; + ec_sig_r = BN_bin2bn(reinterpret_cast(sig_rr.constData()), + sig_rr.length(), NULL); + ec_sig_s = BN_bin2bn(reinterpret_cast(sig_ss.constData()), + sig_ss.length(), NULL); + QVERIFY(ec_sig_r != NULL); + QVERIFY(ec_sig_s != NULL); + + ECDSA_SIG *ec_sig = ECDSA_SIG_new(); + + QVERIFY(ECDSA_SIG_set0(ec_sig, ec_sig_r, ec_sig_s) == 1); + + unsigned char *der_sig; + int sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); + QVERIFY(sig_len > 0); + + BN_free(ec_sig_r); + BN_free(ec_sig_s); + OPENSSL_free(der_sig); + ECDSA_SIG_free(ec_sig); +} + QTEST_MAIN(oauth_test) #include "tst_oauth_test.moc" From 956709896aba3dbd79ebf51ed474d8a87900fba5 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 26 Aug 2024 23:32:28 +0900 Subject: [PATCH 045/127] =?UTF-8?q?=E5=8D=98=E4=BD=93=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=A7jwt=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/oauth_test/tst_oauth_test.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 2bc508dd..66b951c7 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -121,6 +121,7 @@ void oauth_test::verify_jwt(const QByteArray &jwt) const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); + QByteArray message = jwt_parts[0] + '.' + jwt_parts[1]; QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); @@ -137,12 +138,26 @@ void oauth_test::verify_jwt(const QByteArray &jwt) QVERIFY(ec_sig_s != NULL); ECDSA_SIG *ec_sig = ECDSA_SIG_new(); - QVERIFY(ECDSA_SIG_set0(ec_sig, ec_sig_r, ec_sig_s) == 1); - unsigned char *der_sig; - int sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); - QVERIFY(sig_len > 0); + unsigned char *der_sig = NULL; + int der_sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); + QVERIFY(der_sig_len > 0); + + FILE *fp = fopen("c:\\temp\\public_key.pem", "r"); + QVERIFY(fp != NULL); + EVP_PKEY *pkey = PEM_read_PUBKEY(fp, nullptr, nullptr, nullptr); + fclose(fp); + QVERIFY(pkey != NULL); + + // ECDSA署名の検証 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + QVERIFY(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) > 0); + QVERIFY(EVP_DigestVerifyUpdate(mdctx, message.constData(), message.length()) > 0); + QVERIFY(EVP_DigestVerifyFinal(mdctx, der_sig, der_sig_len) > 0); + + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); BN_free(ec_sig_r); BN_free(ec_sig_s); From 5b81031bdbe18115320a21bf6fb00b163cf1faa7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 28 Aug 2024 00:41:02 +0900 Subject: [PATCH 046/127] =?UTF-8?q?=E7=BD=B2=E5=90=8D=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/es256.cpp | 202 +++++++++++++++++++++++++++- lib/tools/es256.h | 24 +++- lib/tools/jsonwebtoken.cpp | 155 ++------------------- lib/tools/jsonwebtoken.h | 2 - tests/oauth_test/tst_oauth_test.cpp | 58 ++++++-- 5 files changed, 280 insertions(+), 161 deletions(-) diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp index 7ac4acb5..c17b21f2 100644 --- a/lib/tools/es256.cpp +++ b/lib/tools/es256.cpp @@ -1,9 +1,209 @@ #include "es256.h" -Es256::Es256() { } +#include +#include +#include +#include +#include + +Es256::Es256() : m_pKey(nullptr) +{ + qDebug().noquote() << this << "Es256()"; +} + +Es256::~Es256() +{ + qDebug().noquote() << this << "~Es256()"; +} Es256 *Es256::getInstance() { static Es256 instance; return &instance; } + +void Es256::clear() +{ + if (m_pKey != nullptr) { + EVP_PKEY_free(m_pKey); + m_pKey = nullptr; + } +} + +QByteArray Es256::sign(const QByteArray &data) +{ + loadKey(); + if (m_pKey == nullptr) { + return QByteArray(); + } + + QByteArray der_sig; + + // ECDSA署名を生成 + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, m_pKey) <= 0) { + qWarning() << "Failed to initialize digest sign"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { + qWarning() << "Failed to update digest sign"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + size_t sigLen; + if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { + qWarning() << "Failed to finalize digest sign 1"; + EVP_MD_CTX_free(mdctx); + // EVP_PKEY_free(pkey); + return QByteArray(); + } + + der_sig.resize(sigLen); + if (EVP_DigestSignFinal(mdctx, reinterpret_cast(der_sig.data()), &sigLen) + <= 0) { + qWarning() << "Failed to finalize digest sign 2"; + return QByteArray(); + } + + // Convert DER to IEEE P1363 + const int ec_sig_len = 64; // ES256のときの値 + const unsigned char *temp = reinterpret_cast(der_sig.constData()); + ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, der_sig.length()); + if (ec_sig == NULL) { + return QByteArray(); + } + + const BIGNUM *ec_sig_r = NULL; + const BIGNUM *ec_sig_s = NULL; + ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); + + QByteArray rr = bn2ba(ec_sig_r); + QByteArray ss = bn2ba(ec_sig_s); + + if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { + return QByteArray(); + } + rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); + ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); + + EVP_MD_CTX_free(mdctx); + + return rr + ss; +} + +void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) +{ + loadKey(); + + // ECキーの抽出 + EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(m_pKey); + if (!ec_key) { + qWarning() << "Failed to get EC key from EVP_PKEY"; + } + + const EC_GROUP *group = EC_KEY_get0_group(ec_key); + const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + + // X, Y座標の抽出 + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + qWarning() << "Failed to get affine coordinates"; + x_coord.clear(); + y_coord.clear(); + } else { + // X, Y座標をBase64URLエンコード + x_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), + BN_num_bytes(x)) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + y_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), + BN_num_bytes(y)) + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + } + BN_free(x); + BN_free(y); +} + +void Es256::loadKey() +{ + if (m_pKey != nullptr) { + return; + } + + QString path = + QString("%1/%2/%3%4/private_key.pem") + .arg(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()) + .arg( +#if defined(HAGOROMO_UNIT_TEST) + QStringLiteral("_unittest") +#elif defined(QT_DEBUG) + QStringLiteral("_debug") +#else + QString() +#endif + ); + + if (!QFile::exists(path)) { + createPrivateKey(path); + } else { + FILE *fp = fopen(path.toStdString().c_str(), "r"); + if (!fp) { + qWarning() << "Failed to open private key file"; + } else { + m_pKey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + fclose(fp); + } + } +} + +void Es256::createPrivateKey(const QString &path) +{ + if (m_pKey != nullptr) { + return; + } + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr); + if (pctx == nullptr) { + return; + } + + if (EVP_PKEY_keygen_init(pctx) <= 0) { + qWarning() << "Failed to initialize keygen context"; + } else if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) <= 0) { + qWarning() << "Failed to set curve parameter"; + } else if (EVP_PKEY_keygen(pctx, &m_pKey) <= 0) { + qWarning() << "Failed to generate EC key"; + m_pKey = nullptr; + } else { + FILE *fp = fopen(path.toStdString().c_str(), "w"); + if (!fp) { + qWarning() << "Failed to open private key file"; + } else { + if (PEM_write_PrivateKey(fp, m_pKey, nullptr, nullptr, 0, nullptr, nullptr) <= 0) { + qWarning() << "Failed to write private key to file"; + } + fclose(fp); + } + } + EVP_PKEY_CTX_free(pctx); +} + +QByteArray Es256::bn2ba(const BIGNUM *bn) +{ + QByteArray ba; + ba.resize(BN_num_bytes(bn)); + BN_bn2bin(bn, reinterpret_cast(ba.data())); + return ba; +} + +EVP_PKEY *Es256::pKey() const +{ + return m_pKey; +} diff --git a/lib/tools/es256.h b/lib/tools/es256.h index c8c64bfd..38879ae4 100644 --- a/lib/tools/es256.h +++ b/lib/tools/es256.h @@ -1,13 +1,33 @@ #ifndef ES256_H #define ES256_H +#include + +#include +#include +#include +#include +#include + class Es256 { - Es256(); - ~Es256() { } + explicit Es256(); + ~Es256(); public: static Es256 *getInstance(); + + void clear(); + void loadKey(); + QByteArray sign(const QByteArray &data); + void getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord); + EVP_PKEY *pKey() const; + +private: + void createPrivateKey(const QString &path); + QByteArray bn2ba(const BIGNUM *bn); + + EVP_PKEY *m_pKey; }; #endif // ES256_H diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 271dc652..8cf7b704 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -1,96 +1,44 @@ #include "jsonwebtoken.h" +#include "es256.h" #include #include #include #include #include -#include -#include -#include -#include -#include -QByteArray base64UrlEncode(const QByteArray &data) +inline QByteArray base64UrlEncode(const QByteArray &data) { QByteArray encoded = data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); return encoded; } -QByteArray bn2ba(const BIGNUM *bn) -{ - QByteArray ba; - ba.resize(BN_num_bytes(bn)); - BN_bn2bin(bn, reinterpret_cast(ba.data())); - return ba; -} - -QJsonObject createJwk(EVP_PKEY *pkey) +inline QJsonObject createJwk() { QJsonObject jwk; - // ECキーの抽出 - EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey); - if (!ec_key) { - qWarning() << "Failed to get EC key from EVP_PKEY"; - return jwk; - } - - const EC_GROUP *group = EC_KEY_get0_group(ec_key); - const EC_POINT *point = EC_KEY_get0_public_key(ec_key); + QByteArray x_coord; + QByteArray y_coord; + Es256::getInstance()->getAffineCoordinates(x_coord, y_coord); - // X, Y座標の抽出 - BIGNUM *x = BN_new(); - BIGNUM *y = BN_new(); - if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { - qWarning() << "Failed to get affine coordinates"; - BN_free(x); - BN_free(y); - return jwk; + if (!x_coord.isEmpty() && !y_coord.isEmpty()) { + jwk["kty"] = "EC"; // Key Type + jwk["crv"] = "P-256"; // Curve + jwk["x"] = QString::fromUtf8(x_coord); + jwk["y"] = QString::fromUtf8(y_coord); } - // X, Y座標をBase64URLエンコード - QByteArray xCoord = base64UrlEncode( - QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), BN_num_bytes(x))); - QByteArray yCoord = base64UrlEncode( - QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), BN_num_bytes(y))); - - jwk["kty"] = "EC"; // Key Type - jwk["crv"] = "P-256"; // Curve - jwk["x"] = QString::fromUtf8(xCoord); - jwk["y"] = QString::fromUtf8(yCoord); - - BN_free(x); - BN_free(y); - return jwk; } QByteArray JsonWebToken::generate(const QString &endpoint) { - // OpenSSLで秘密鍵を読み込む - FILE *fp = fopen("c:\\temp\\private_key.pem", "r"); - if (!fp) { - qWarning() << "Failed to open private key file"; - return QByteArray(); - } - EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); - fclose(fp); - if (!pkey) { - qWarning() << "Failed to read private key"; - return QByteArray(); - } - - // JWKを生成 - QJsonObject jwk = createJwk(pkey); - EVP_PKEY_free(pkey); - // ヘッダー QJsonObject header; header["alg"] = "ES256"; header["typ"] = "dpop+jwt"; - header["jwk"] = jwk; + header["jwk"] = createJwk(); QByteArray headerJson = QJsonDocument(header).toJson(QJsonDocument::Compact); QByteArray headerBase64 = base64UrlEncode(headerJson); @@ -104,87 +52,10 @@ QByteArray JsonWebToken::generate(const QString &endpoint) // 署名 QByteArray message = headerBase64 + "." + payloadBase64; - QByteArray signature = JsonWebToken::sign(message, "c:\\temp\\private_key.pem"); + QByteArray signature = Es256::getInstance()->sign(message); QByteArray signatureBase64 = base64UrlEncode(signature); // JWTトークン QByteArray jwt = headerBase64 + "." + payloadBase64 + "." + signatureBase64; return jwt; } - -QByteArray JsonWebToken::sign(const QByteArray &data, const QString &privateKeyPath) -{ - QByteArray signature; - - // OpenSSLで秘密鍵を読み込む - FILE *fp = fopen(privateKeyPath.toStdString().c_str(), "r"); - if (!fp) { - qWarning() << "Failed to open private key file"; - return QByteArray(); - } - - EVP_PKEY *pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); - fclose(fp); - - if (!pkey) { - qWarning() << "Failed to read private key"; - return QByteArray(); - } - - // ECDSA署名を生成 - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) <= 0) { - qWarning() << "Failed to initialize digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - if (EVP_DigestSignUpdate(mdctx, data.constData(), data.size()) <= 0) { - qWarning() << "Failed to update digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - size_t sigLen; - if (EVP_DigestSignFinal(mdctx, nullptr, &sigLen) <= 0) { - qWarning() << "Failed to finalize digest sign"; - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - return QByteArray(); - } - - signature.resize(sigLen); - if (EVP_DigestSignFinal(mdctx, reinterpret_cast(signature.data()), &sigLen) - <= 0) { - qWarning() << "Failed to finalize digest sign"; - return QByteArray(); - } - - // Convert DER to IEEE P1363 - const int ec_sig_len = 64; // ES256のときの値 - const unsigned char *temp = reinterpret_cast(signature.constData()); - ECDSA_SIG *ec_sig = d2i_ECDSA_SIG(NULL, &temp, signature.length()); - if (ec_sig == NULL) { - return QByteArray(); - } - - const BIGNUM *ec_sig_r = NULL; - const BIGNUM *ec_sig_s = NULL; - ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); - - QByteArray rr = bn2ba(ec_sig_r); - QByteArray ss = bn2ba(ec_sig_s); - - if (rr.size() > (ec_sig_len / 2) || ss.size() > (ec_sig_len / 2)) { - return QByteArray(); - } - rr.insert(0, ec_sig_len / 2 - rr.size(), '\0'); - ss.insert(0, ec_sig_len / 2 - ss.size(), '\0'); - - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - - return rr + ss; -} diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index b6514f3a..ff43ee22 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -7,8 +7,6 @@ class JsonWebToken { public: static QByteArray generate(const QString &endpoint); - - static QByteArray sign(const QByteArray &data, const QString &privateKeyPath); }; #endif // JSONWEBTOKEN_H diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 66b951c7..aaa48a4d 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -9,6 +9,7 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" +#include "tools/es256.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -25,16 +26,19 @@ private slots: void test_oauth_process(); void test_oauth_server(); void test_jwt(); + void test_es256(); private: SimpleHttpServer m_server; quint16 m_listenPort; - void verify_jwt(const QByteArray &jwt); + void verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey); }; oauth_test::oauth_test() { + QCoreApplication::setOrganizationName(QStringLiteral("relog")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); m_listenPort = m_server.listen(QHostAddress::LocalHost, 0); connect(&m_server, &SimpleHttpServer::received, this, @@ -111,12 +115,44 @@ void oauth_test::test_jwt() qDebug().noquote() << jwt; - verify_jwt(jwt); + verify_jwt(jwt, Es256::getInstance()->pKey()); QVERIFY(true); } -void oauth_test::verify_jwt(const QByteArray &jwt) +void oauth_test::test_es256() +{ + QString private_key_path = + QString("%1/%2/%3%4/private_key.pem") + .arg(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()) + .arg(QStringLiteral("_unittest")); + QFile::remove(private_key_path); + Es256::getInstance()->clear(); + + { + QString message = "header.payload"; + QByteArray sign = Es256::getInstance()->sign(message.toUtf8()); + QByteArray jwt = message.toUtf8() + '.' + + sign.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QVERIFY(QFile::exists(private_key_path)); + verify_jwt(jwt, Es256::getInstance()->pKey()); + } + QFile::remove(private_key_path); + QVERIFY(!QFile::exists(private_key_path)); + { + QString message = "header2.payload2"; + QByteArray sign = Es256::getInstance()->sign(message.toUtf8()); + QByteArray jwt = message.toUtf8() + '.' + + sign.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + verify_jwt(jwt, Es256::getInstance()->pKey()); + } +} + +void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); @@ -125,9 +161,10 @@ void oauth_test::verify_jwt(const QByteArray &jwt) QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + + // convert IEEE P1363 to DER QByteArray sig_rr = sig.left(32); QByteArray sig_ss = sig.right(32); - BIGNUM *ec_sig_r = NULL; BIGNUM *ec_sig_s = NULL; ec_sig_r = BN_bin2bn(reinterpret_cast(sig_rr.constData()), @@ -144,25 +181,18 @@ void oauth_test::verify_jwt(const QByteArray &jwt) int der_sig_len = i2d_ECDSA_SIG(ec_sig, &der_sig); QVERIFY(der_sig_len > 0); - FILE *fp = fopen("c:\\temp\\public_key.pem", "r"); - QVERIFY(fp != NULL); - EVP_PKEY *pkey = PEM_read_PUBKEY(fp, nullptr, nullptr, nullptr); - fclose(fp); - QVERIFY(pkey != NULL); - // ECDSA署名の検証 EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + QVERIFY(pkey != nullptr); QVERIFY(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) > 0); QVERIFY(EVP_DigestVerifyUpdate(mdctx, message.constData(), message.length()) > 0); QVERIFY(EVP_DigestVerifyFinal(mdctx, der_sig, der_sig_len) > 0); - EVP_MD_CTX_free(mdctx); - EVP_PKEY_free(pkey); - BN_free(ec_sig_r); - BN_free(ec_sig_s); OPENSSL_free(der_sig); ECDSA_SIG_free(ec_sig); + BN_free(ec_sig_s); + BN_free(ec_sig_r); } QTEST_MAIN(oauth_test) From 43c1ce9ede7a3545a5bbd5f8025e4d275bb73c79 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 28 Aug 2024 03:12:53 +0900 Subject: [PATCH 047/127] =?UTF-8?q?=E3=83=98=E3=83=83=E3=83=80=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E9=8D=B5=E3=81=A7=E7=BD=B2=E5=90=8D=E3=82=92=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/es256.cpp | 14 ++------- tests/oauth_test/tst_oauth_test.cpp | 44 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp index c17b21f2..d90e758b 100644 --- a/lib/tools/es256.cpp +++ b/lib/tools/es256.cpp @@ -100,7 +100,6 @@ void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) { loadKey(); - // ECキーの抽出 EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(m_pKey); if (!ec_key) { qWarning() << "Failed to get EC key from EVP_PKEY"; @@ -108,22 +107,15 @@ void Es256::getAffineCoordinates(QByteArray &x_coord, QByteArray &y_coord) const EC_GROUP *group = EC_KEY_get0_group(ec_key); const EC_POINT *point = EC_KEY_get0_public_key(ec_key); - - // X, Y座標の抽出 BIGNUM *x = BN_new(); BIGNUM *y = BN_new(); - if (!EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)) { + if (!EC_POINT_get_affine_coordinates(group, point, x, y, nullptr)) { qWarning() << "Failed to get affine coordinates"; x_coord.clear(); y_coord.clear(); } else { - // X, Y座標をBase64URLエンコード - x_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(x)), - BN_num_bytes(x)) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - y_coord = QByteArray::fromRawData(reinterpret_cast(BN_bn2hex(y)), - BN_num_bytes(y)) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + x_coord = bn2ba(x).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + y_coord = bn2ba(y).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } BN_free(x); BN_free(y); diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index aaa48a4d..8307a514 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -154,6 +156,8 @@ void oauth_test::test_es256() void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { + EC_KEY *ec_key = nullptr; + const QByteArrayList jwt_parts = jwt.split('.'); QVERIFY2(jwt_parts.length() == 3, jwt); @@ -161,6 +165,41 @@ void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) QByteArray sig = QByteArray::fromBase64( jwt_parts.last(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QVERIFY2(sig.length() == 64, QString::number(sig.length()).toLocal8Bit()); + QByteArray header = QByteArray::fromBase64( + jwt_parts.first(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + bool use_jwk = false; + QJsonDocument json_doc = QJsonDocument::fromJson(header); + if (json_doc.object().contains("jwk")) { + QJsonObject jwk_obj = json_doc.object().value("jwk").toObject(); + QByteArray x_coord = QByteArray::fromBase64(jwk_obj.value("x").toString().toUtf8(), + QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + QByteArray y_coord = QByteArray::fromBase64(jwk_obj.value("y").toString().toUtf8(), + QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + QVERIFY(!x_coord.isEmpty()); + QVERIFY(!y_coord.isEmpty()); + + BIGNUM *x = BN_bin2bn(reinterpret_cast(x_coord.constData()), + x_coord.length(), nullptr); + BIGNUM *y = BN_bin2bn(reinterpret_cast(y_coord.constData()), + y_coord.length(), nullptr); + QVERIFY(x); + QVERIFY(y); + + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + QVERIFY(ec_key); + QVERIFY(EC_KEY_set_public_key_affine_coordinates(ec_key, x, y)); + + pkey = EVP_PKEY_new(); + QVERIFY(EVP_PKEY_assign_EC_KEY(pkey, ec_key)); + + BN_free(y); + BN_free(x); + + use_jwk = true; + } // convert IEEE P1363 to DER QByteArray sig_rr = sig.left(32); @@ -193,6 +232,11 @@ void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) ECDSA_SIG_free(ec_sig); BN_free(ec_sig_s); BN_free(ec_sig_r); + + if (use_jwk) { + EVP_PKEY_free(pkey); + // EC_KEY_free(ec_key); EVP_PKEY_freeで解放される + } } QTEST_MAIN(oauth_test) From 93222198460d757253741ac16529368d0b28b00f Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 29 Aug 2024 01:43:11 +0900 Subject: [PATCH 048/127] =?UTF-8?q?=E8=87=AA=E5=8B=95=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E3=81=AEAPI=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/comatprotorepodescriberepo.cpp | 71 +++++++++++++++++++ .../atproto/repo/comatprotorepodescriberepo.h | 33 +++++++++ lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func_unknown.cpp | 10 +++ lib/lib.pro | 2 + scripts/defs2struct.py | 5 +- 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp create mode 100644 lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h diff --git a/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp new file mode 100644 index 00000000..18c27f5c --- /dev/null +++ b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp @@ -0,0 +1,71 @@ +#include "comatprotorepodescriberepo.h" +#include "atprotocol/lexicons_func.h" +#include "atprotocol/lexicons_func_unknown.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +ComAtprotoRepoDescribeRepo::ComAtprotoRepoDescribeRepo(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void ComAtprotoRepoDescribeRepo::describeRepo(const QString &repo) +{ + QUrlQuery url_query; + if (!repo.isEmpty()) { + url_query.addQueryItem(QStringLiteral("repo"), repo); + } + + get(QStringLiteral("xrpc/com.atproto.repo.describeRepo"), url_query, false); +} + +const QString &ComAtprotoRepoDescribeRepo::handle() const +{ + return m_handle; +} + +const QString &ComAtprotoRepoDescribeRepo::did() const +{ + return m_did; +} + +const QVariant &ComAtprotoRepoDescribeRepo::didDoc() const +{ + return m_didDoc; +} + +const QStringList &ComAtprotoRepoDescribeRepo::collectionsList() const +{ + return m_collectionsList; +} + +const bool &ComAtprotoRepoDescribeRepo::handleIsCorrect() const +{ + return m_handleIsCorrect; +} + +bool ComAtprotoRepoDescribeRepo::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::LexiconsTypeUnknown::copyString(json_doc.object().value("handle"), + m_handle); + AtProtocolType::LexiconsTypeUnknown::copyString(json_doc.object().value("did"), m_did); + AtProtocolType::LexiconsTypeUnknown::copyUnknown( + json_doc.object().value("didDoc").toObject(), m_didDoc); + AtProtocolType::LexiconsTypeUnknown::copyStringList( + json_doc.object().value("collections").toArray(), m_collectionsList); + AtProtocolType::LexiconsTypeUnknown::copyBool(json_doc.object().value("handleIsCorrect"), + m_handleIsCorrect); + } + + return success; +} + +} diff --git a/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h new file mode 100644 index 00000000..717cb12b --- /dev/null +++ b/lib/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h @@ -0,0 +1,33 @@ +#ifndef COMATPROTOREPODESCRIBEREPO_H +#define COMATPROTOREPODESCRIBEREPO_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class ComAtprotoRepoDescribeRepo : public AccessAtProtocol +{ +public: + explicit ComAtprotoRepoDescribeRepo(QObject *parent = nullptr); + + void describeRepo(const QString &repo); + + const QString &handle() const; + const QString &did() const; + const QVariant &didDoc() const; + const QStringList &collectionsList() const; + const bool &handleIsCorrect() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + QString m_handle; + QString m_did; + QVariant m_didDoc; + QStringList m_collectionsList; + bool m_handleIsCorrect; +}; + +} + +#endif // COMATPROTOREPODESCRIBEREPO_H diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 63995222..2a12a6d1 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2063,5 +2063,6 @@ Q_DECLARE_METATYPE(AtProtocolType::AppBskyActorProfile::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyGraphList::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedThreadgate::Main) Q_DECLARE_METATYPE(AtProtocolType::ComWhtwndBlogEntry::Main) +Q_DECLARE_METATYPE(AtProtocolType::DirectoryPlcDefs::DidDoc) #endif // LEXICONS_H diff --git a/lib/atprotocol/lexicons_func_unknown.cpp b/lib/atprotocol/lexicons_func_unknown.cpp index 5c1cb304..b78e4218 100644 --- a/lib/atprotocol/lexicons_func_unknown.cpp +++ b/lib/atprotocol/lexicons_func_unknown.cpp @@ -22,6 +22,12 @@ void copyUnknown(const QJsonObject &src, QVariant &dest) return; QString type = src.value("$type").toString(); + QStringList context; + if (src.contains("@context")) { + for (const auto item : src.value("@context").toArray()) { + context.append(item.toString()); + } + } if (type == QStringLiteral("app.bsky.feed.post")) { AppBskyFeedPost::Main record; AppBskyFeedPost::copyMain(src, record); @@ -54,6 +60,10 @@ void copyUnknown(const QJsonObject &src, QVariant &dest) ComWhtwndBlogEntry::Main record; ComWhtwndBlogEntry::copyMain(src, record); dest.setValue(record); + } else if (context.contains("https://www.w3.org/ns/did/v1")) { + DirectoryPlcDefs::DidDoc doc; + DirectoryPlcDefs::copyDidDoc(src, doc); + dest.setValue(doc); } } diff --git a/lib/lib.pro b/lib/lib.pro index a3419e78..457dcaab 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -70,6 +70,7 @@ SOURCES += \ $$PWD/atprotocol/com/atproto/moderation/comatprotomoderationcreatereport.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepocreaterecord.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepodeleterecord.cpp \ + $$PWD/atprotocol/com/atproto/repo/comatprotorepodescriberepo.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepogetrecord.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepolistrecords.cpp \ $$PWD/atprotocol/com/atproto/repo/comatprotorepoputrecord.cpp \ @@ -180,6 +181,7 @@ HEADERS += \ $$PWD/atprotocol/com/atproto/moderation/comatprotomoderationcreatereport.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepocreaterecord.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepodeleterecord.h \ + $$PWD/atprotocol/com/atproto/repo/comatprotorepodescriberepo.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepogetrecord.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepolistrecords.h \ $$PWD/atprotocol/com/atproto/repo/comatprotorepoputrecord.h \ diff --git a/scripts/defs2struct.py b/scripts/defs2struct.py index 7b7b519d..09b5ea25 100644 --- a/scripts/defs2struct.py +++ b/scripts/defs2struct.py @@ -121,7 +121,8 @@ def __init__(self) -> None: 'AppBskyActorProfile::Main', 'AppBskyGraphList::Main', 'AppBskyFeedThreadgate::Main', - 'ComWhtwndBlogEntry::Main' + 'ComWhtwndBlogEntry::Main', + 'DirectoryPlcDefs::DidDoc', ) self.inheritance = { 'app.bsky.actor.defs#profileView': { @@ -163,7 +164,6 @@ def __init__(self) -> None: 'com.atproto.identity.', 'com.atproto.label.', 'com.atproto.repo.applyWrites', - 'com.atproto.repo.describeRepo', 'com.atproto.repo.importRepo', 'com.atproto.repo.uploadBlob', 'com.atproto.repo.listMissingBlobs', @@ -218,6 +218,7 @@ def __init__(self) -> None: self.unuse_auth = [ 'com.atproto.server.createSession', 'com.atproto.sync.getBlob', + 'com.atproto.repo.describeRepo', 'com.atproto.repo.listRecords' ] self.need_extension = [ From a4aecb26a65cd686a1dc10068f3e61237f4282b3 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 29 Aug 2024 01:45:39 +0900 Subject: [PATCH 049/127] =?UTF-8?q?=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=88=E3=81=AE=E5=8F=96=E5=BE=97=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 45 ++++++++++ lib/tools/authorization.h | 10 +++ tests/oauth_test/oauth_test.pro | 3 + tests/oauth_test/oauth_test.qrc | 7 ++ .../1/.well-known/oauth-protected-resource | 15 ++++ .../2/.well-known/oauth-authorization-server | 90 +++++++++++++++++++ .../2/xrpc/com.atproto.repo.describeRepo | 46 ++++++++++ tests/oauth_test/tst_oauth_test.cpp | 30 +++++++ 8 files changed, 246 insertions(+) create mode 100644 tests/oauth_test/oauth_test.qrc create mode 100644 tests/oauth_test/response/1/.well-known/oauth-protected-resource create mode 100644 tests/oauth_test/response/2/.well-known/oauth-authorization-server create mode 100644 tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 2c093822..7e30869b 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -1,6 +1,8 @@ #include "authorization.h" #include "http/httpaccess.h" #include "http/simplehttpserver.h" +#include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" +#include "atprotocol/lexicons_func_unknown.h" #include #include @@ -14,10 +16,13 @@ #include #include +using AtProtocolInterface::ComAtprotoRepoDescribeRepo; + Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + m_serviceEndpoint.clear(); m_redirectUri.clear(); m_clientId.clear(); m_codeChallenge.clear(); @@ -27,6 +32,33 @@ void Authorization::reset() m_requestTokenPayload.clear(); } +void Authorization::start(const QString &pds, const QString &handle) +{ + if (pds.isEmpty() || handle.isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = pds; + + ComAtprotoRepoDescribeRepo *repo = new ComAtprotoRepoDescribeRepo(this); + connect(repo, &ComAtprotoRepoDescribeRepo::finished, this, [=](bool success) { + if (success) { + auto did_doc = AtProtocolType::LexiconsTypeUnknown::fromQVariant< + AtProtocolType::DirectoryPlcDefs::DidDoc>(repo->didDoc()); + if (!did_doc.service.isEmpty()) { + setServiceEndpoint(did_doc.service.first().serviceEndpoint); + + // next step + } + } else { + emit errorOccured(repo->errorCode(), repo->errorMessage()); + } + repo->deleteLater(); + }); + repo->setAccount(account); + repo->describeRepo(handle); +} + void Authorization::makeClientId() { QString port; @@ -263,6 +295,19 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } +void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) +{ + if (m_serviceEndpoint == newServiceEndpoint) + return; + m_serviceEndpoint = newServiceEndpoint; + emit serviceEndpointChanged(); +} + +QString Authorization::serviceEndpoint() const +{ + return m_serviceEndpoint; +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 1c7fab34..31ea7dee 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -10,6 +10,9 @@ class Authorization : public QObject explicit Authorization(QObject *parent = nullptr); void reset(); + + void start(const QString &pds, const QString &handle); + void makeClientId(); void makeCodeChallenge(); void makeParPayload(); @@ -20,17 +23,24 @@ class Authorization : public QObject void makeRequestTokenPayload(); bool requestToken(); + QString serviceEndpoint() const; + void setServiceEndpoint(const QString &newServiceEndpoint); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; signals: + void errorOccured(const QString &code, const QString &message); + void serviceEndpointChanged(); void finished(bool success); private: QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; + // server + QString m_serviceEndpoint; + // QString m_redirectUri; QString m_clientId; // par diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro index 59229caa..d831444e 100644 --- a/tests/oauth_test/oauth_test.pro +++ b/tests/oauth_test/oauth_test.pro @@ -11,3 +11,6 @@ SOURCES += tst_oauth_test.cpp include(../common/common.pri) include(../../lib/lib.pri) include(../../openssl/openssl.pri) + +RESOURCES += \ + oauth_test.qrc diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc new file mode 100644 index 00000000..d909191c --- /dev/null +++ b/tests/oauth_test/oauth_test.qrc @@ -0,0 +1,7 @@ + + + response/1/.well-known/oauth-protected-resource + response/2/xrpc/com.atproto.repo.describeRepo + response/2/.well-known/oauth-authorization-server + + diff --git a/tests/oauth_test/response/1/.well-known/oauth-protected-resource b/tests/oauth_test/response/1/.well-known/oauth-protected-resource new file mode 100644 index 00000000..b29c0ff7 --- /dev/null +++ b/tests/oauth_test/response/1/.well-known/oauth-protected-resource @@ -0,0 +1,15 @@ +{ + "resource": "http://localhost:%1/response/2", + "authorization_servers": [ + "http://localhost:%1/response/2" + ], + "scopes_supported": [ + "profile", + "email", + "phone" + ], + "bearer_methods_supported": [ + "header" + ], + "resource_documentation": "https://atproto.com" +} diff --git a/tests/oauth_test/response/2/.well-known/oauth-authorization-server b/tests/oauth_test/response/2/.well-known/oauth-authorization-server new file mode 100644 index 00000000..5536c0db --- /dev/null +++ b/tests/oauth_test/response/2/.well-known/oauth-authorization-server @@ -0,0 +1,90 @@ +{ + "issuer": "http://localhost:%1/response/2/issuer", + "scopes_supported": [ + "atproto", + "transition:generic", + "transition:chat.bsky" + ], + "subject_types_supported": [ + "public" + ], + "response_types_supported": [ + "code" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "code_challenge_methods_supported": [ + "S256", + "plain" + ], + "ui_locales_supported": [ + "en-US" + ], + "display_values_supported": [ + "page", + "popup", + "touch" + ], + "authorization_response_iss_parameter_supported": true, + "request_object_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512", + "none" + ], + "request_object_encryption_alg_values_supported": [], + "request_object_encryption_enc_values_supported": [], + "request_parameter_supported": true, + "request_uri_parameter_supported": true, + "require_request_uri_registration": true, + "jwks_uri": "http://localhost:%1/response/2/oauth/jwks", + "authorization_endpoint": "http://localhost:%1/response/2/oauth/authorize", + "token_endpoint": "http://localhost:%1/response/2/oauth/token", + "token_endpoint_auth_methods_supported": [ + "none", + "private_key_jwt" + ], + "token_endpoint_auth_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512" + ], + "revocation_endpoint": "http://localhost:%1/response/2/oauth/revoke", + "introspection_endpoint": "http://localhost:%1/response/2/oauth/introspect", + "pushed_authorization_request_endpoint": "http://localhost:%1/response/2/oauth/par", + "require_pushed_authorization_requests": true, + "dpop_signing_alg_values_supported": [ + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES256K", + "ES384", + "ES512" + ], + "client_id_metadata_document_supported": true +} diff --git a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo new file mode 100644 index 00000000..36fa95c0 --- /dev/null +++ b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo @@ -0,0 +1,46 @@ +{ + "handle": "ioriayane.relog.tech", + "did": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "didDoc": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "alsoKnownAs": [ + "at://ioriayane.relog.tech" + ], + "verificationMethod": [ + { + "id": "did:plc:ipj5qejfoqu6eukvt72uhyit#atproto", + "type": "Multikey", + "controller": "did:plc:ipj5qejfoqu6eukvt72uhyit", + "publicKeyMultibase": "zQ3shYNo9wSChjqSMPZUmgwjEFMgsk5efZX5UYm8SFrXR5YUd" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "http://localhost:%1/response/1" + } + ] + }, + "collections": [ + "app.bsky.actor.profile", + "app.bsky.feed.generator", + "app.bsky.feed.like", + "app.bsky.feed.post", + "app.bsky.feed.repost", + "app.bsky.feed.threadgate", + "app.bsky.graph.block", + "app.bsky.graph.follow", + "app.bsky.graph.list", + "app.bsky.graph.listitem", + "app.bsky.graph.starterpack", + "chat.bsky.actor.declaration", + "com.whtwnd.blog.entry" + ], + "handleIsCorrect": true +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 8307a514..72c69ac2 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -27,6 +27,7 @@ private slots: void cleanupTestCase(); void test_oauth_process(); void test_oauth_server(); + void test_oauth(); void test_jwt(); void test_es256(); @@ -47,6 +48,16 @@ oauth_test::oauth_test() [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { // + qDebug().noquote() << request.url(); + QString path = SimpleHttpServer::convertResoucePath(request.url()); + qDebug().noquote() << " res path =" << path; + if (!QFile::exists(path)) { + result = false; + } else { + mime_type = "application/json"; + result = SimpleHttpServer::readFile(path, data); + qDebug().noquote() << " result =" << result; + } }); } @@ -111,6 +122,25 @@ void oauth_test::test_oauth_server() // } } +void oauth_test::test_oauth() +{ + + Authorization oauth; + QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); + QString handle = "ioriayane.relog.tech"; + + oauth.reset(); + { + QSignalSpy spy(&oauth, SIGNAL(serviceEndpointChanged())); + oauth.start(pds, handle); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + oauth.setServiceEndpoint(oauth.serviceEndpoint().arg(m_listenPort)); + + QVERIFY(oauth.serviceEndpoint() == QString("http://localhost:%1/response/1").arg(m_listenPort)); +} + void oauth_test::test_jwt() { QByteArray jwt = JsonWebToken::generate("https://hoge"); From 15024157d06b868d979227b9667557af27e93471 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 01:32:22 +0900 Subject: [PATCH 050/127] =?UTF-8?q?oauth-protected-resource=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 12 ++++ lib/atprotocol/lexicons_func.cpp | 20 +++++++ lib/atprotocol/lexicons_func.h | 5 ++ .../wellknownoauthprotectedresource.cpp | 41 +++++++++++++ .../wellknownoauthprotectedresource.h | 27 +++++++++ lib/lib.pro | 2 + lib/tools/authorization.cpp | 57 ++++++++++++++++++- lib/tools/authorization.h | 10 +++- .../well.known.oauth.protected.resource.json | 35 ++++++++++++ .../1/.well-known/oauth-protected-resource | 4 +- .../2/.well-known/oauth-authorization-server | 14 ++--- .../2/xrpc/com.atproto.repo.describeRepo | 2 +- tests/oauth_test/tst_oauth_test.cpp | 13 ++++- 13 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 lib/extension/well-known/wellknownoauthprotectedresource.cpp create mode 100644 lib/extension/well-known/wellknownoauthprotectedresource.h create mode 100644 scripts/lexicons/well.known.oauth.protected.resource.json diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 2a12a6d1..aebfe2a0 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,18 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +struct OAuthProtectedResource +{ + QString resource; + QList authorization_servers; + QList scopes_supported; + QList bearer_methods_supported; + QString resource_documentation; +}; +} + } Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedPost::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedLike::Main) diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index d46c868c..612b3466 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,26 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +void copyOAuthProtectedResource(const QJsonObject &src, + WellKnownOauthProtectedResource::OAuthProtectedResource &dest) +{ + if (!src.isEmpty()) { + dest.resource = src.value("resource").toString(); + for (const auto &value : src.value("authorization_servers").toArray()) { + dest.authorization_servers.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("bearer_methods_supported").toArray()) { + dest.bearer_methods_supported.append(value.toString()); + } + dest.resource_documentation = src.value("resource_documentation").toString(); + } +} +} } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 5183654b..f94ccc6d 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// well.known.oauth.protected.resource +namespace WellKnownOauthProtectedResource { +void copyOAuthProtectedResource(const QJsonObject &src, + WellKnownOauthProtectedResource::OAuthProtectedResource &dest); +} } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp new file mode 100644 index 00000000..69ed2ec8 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -0,0 +1,41 @@ +#include "wellknownoauthprotectedresource.h" +#include "atprotocol/lexicons_func.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +WellKnownOauthProtectedResource::WellKnownOauthProtectedResource(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void WellKnownOauthProtectedResource::resource() +{ + QUrlQuery url_query; + + get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); +} + +const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & +WellKnownOauthProtectedResource::OAuthProtectedResource() const +{ + return m_OAuthProtectedResource; +} + +bool WellKnownOauthProtectedResource::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::WellKnownOauthProtectedResource::copyOAuthProtectedResource( + json_doc.object(), m_OAuthProtectedResource); + } + + return success; +} + +} diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h new file mode 100644 index 00000000..980c9fb6 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -0,0 +1,27 @@ +#ifndef WELLKNOWNOAUTHPROTECTEDRESOURCE_H +#define WELLKNOWNOAUTHPROTECTEDRESOURCE_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class WellKnownOauthProtectedResource : public AccessAtProtocol +{ +public: + explicit WellKnownOauthProtectedResource(QObject *parent = nullptr); + + void resource(); + + const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & + OAuthProtectedResource() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource + m_OAuthProtectedResource; +}; + +} + +#endif // WELLKNOWNOAUTHPROTECTEDRESOURCE_H diff --git a/lib/lib.pro b/lib/lib.pro index 457dcaab..b635fbff 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -92,6 +92,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ $$PWD/http/httpreply.cpp \ @@ -204,6 +205,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ $$PWD/http/httpreply.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 7e30869b..bd8139f8 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -2,6 +2,7 @@ #include "http/httpaccess.h" #include "http/simplehttpserver.h" #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" +#include "extension/well-known/wellknownoauthprotectedresource.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -17,6 +18,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::WellKnownOauthProtectedResource; Authorization::Authorization(QObject *parent) : QObject { parent } { } @@ -49,6 +51,10 @@ void Authorization::start(const QString &pds, const QString &handle) setServiceEndpoint(did_doc.service.first().serviceEndpoint); // next step + requestOauthProtectedResource(); + } else { + emit errorOccured("Invalid oauth-protected-resource", + "authorization_servers is empty."); } } else { emit errorOccured(repo->errorCode(), repo->errorMessage()); @@ -59,6 +65,40 @@ void Authorization::start(const QString &pds, const QString &handle) repo->describeRepo(handle); } +void Authorization::requestOauthProtectedResource() +{ + // /.well-known/oauth-protected-resource + if (serviceEndpoint().isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = serviceEndpoint(); + + WellKnownOauthProtectedResource *resource = new WellKnownOauthProtectedResource(this); + connect(resource, &WellKnownOauthProtectedResource::finished, this, [=](bool success) { + if (success) { + if (!resource->OAuthProtectedResource().authorization_servers.isEmpty()) { + setAuthorizationServer( + resource->OAuthProtectedResource().authorization_servers.first()); + // next step + } else { + emit errorOccured("Invalid oauth-protected-resource", + "authorization_servers is empty."); + } + } else { + emit errorOccured(resource->errorCode(), resource->errorMessage()); + } + resource->deleteLater(); + }); + resource->setAccount(account); + resource->resource(); +} + +void Authorization::requestOauthAuthorizationServer() +{ + // /.well-known/oauth-authorization-server +} + void Authorization::makeClientId() { QString port; @@ -295,6 +335,11 @@ QString Authorization::simplyEncode(QString text) const return text.replace("%", "%25").replace(":", "%3A").replace("/", "%2F").replace("?", "%3F"); } +QString Authorization::serviceEndpoint() const +{ + return m_serviceEndpoint; +} + void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) { if (m_serviceEndpoint == newServiceEndpoint) @@ -303,9 +348,17 @@ void Authorization::setServiceEndpoint(const QString &newServiceEndpoint) emit serviceEndpointChanged(); } -QString Authorization::serviceEndpoint() const +QString Authorization::authorizationServer() const { - return m_serviceEndpoint; + return m_authorizationServer; +} + +void Authorization::setAuthorizationServer(const QString &newAuthorizationServer) +{ + if (m_authorizationServer == newAuthorizationServer) + return; + m_authorizationServer = newAuthorizationServer; + emit authorizationServerChanged(); } QByteArray Authorization::ParPayload() const diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 31ea7dee..db2d6b6e 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -25,6 +25,8 @@ class Authorization : public QObject QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); + QString authorizationServer() const; + void setAuthorizationServer(const QString &newAuthorizationServer); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -32,14 +34,20 @@ class Authorization : public QObject signals: void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); + void authorizationServerChanged(); void finished(bool success); private: QByteArray generateRandomValues() const; QString simplyEncode(QString text) const; - // server + // server info + void requestOauthProtectedResource(); + void requestOauthAuthorizationServer(); + + // server info QString m_serviceEndpoint; + QString m_authorizationServer; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json b/scripts/lexicons/well.known.oauth.protected.resource.json new file mode 100644 index 00000000..a03f6aac --- /dev/null +++ b/scripts/lexicons/well.known.oauth.protected.resource.json @@ -0,0 +1,35 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.protected.resource", + "defs": { + "OAuthProtectedResource": { + "type": "object", + "properties": { + "resource": { + "type": "string" + }, + "authorization_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "bearer_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "resource_documentation": { + "type": "string" + } + } + } + } +} diff --git a/tests/oauth_test/response/1/.well-known/oauth-protected-resource b/tests/oauth_test/response/1/.well-known/oauth-protected-resource index b29c0ff7..c16eb650 100644 --- a/tests/oauth_test/response/1/.well-known/oauth-protected-resource +++ b/tests/oauth_test/response/1/.well-known/oauth-protected-resource @@ -1,7 +1,7 @@ { - "resource": "http://localhost:%1/response/2", + "resource": "http://localhost:{{SERVER_PORT_NO}}/response/2", "authorization_servers": [ - "http://localhost:%1/response/2" + "http://localhost:{{SERVER_PORT_NO}}/response/2" ], "scopes_supported": [ "profile", diff --git a/tests/oauth_test/response/2/.well-known/oauth-authorization-server b/tests/oauth_test/response/2/.well-known/oauth-authorization-server index 5536c0db..1f106d8b 100644 --- a/tests/oauth_test/response/2/.well-known/oauth-authorization-server +++ b/tests/oauth_test/response/2/.well-known/oauth-authorization-server @@ -1,5 +1,5 @@ { - "issuer": "http://localhost:%1/response/2/issuer", + "issuer": "http://localhost:{{SERVER_PORT_NO}}/response/2/issuer", "scopes_supported": [ "atproto", "transition:generic", @@ -51,9 +51,9 @@ "request_parameter_supported": true, "request_uri_parameter_supported": true, "require_request_uri_registration": true, - "jwks_uri": "http://localhost:%1/response/2/oauth/jwks", - "authorization_endpoint": "http://localhost:%1/response/2/oauth/authorize", - "token_endpoint": "http://localhost:%1/response/2/oauth/token", + "jwks_uri": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/jwks", + "authorization_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/authorize", + "token_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/token", "token_endpoint_auth_methods_supported": [ "none", "private_key_jwt" @@ -70,9 +70,9 @@ "ES384", "ES512" ], - "revocation_endpoint": "http://localhost:%1/response/2/oauth/revoke", - "introspection_endpoint": "http://localhost:%1/response/2/oauth/introspect", - "pushed_authorization_request_endpoint": "http://localhost:%1/response/2/oauth/par", + "revocation_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/revoke", + "introspection_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/introspect", + "pushed_authorization_request_endpoint": "http://localhost:{{SERVER_PORT_NO}}/response/2/oauth/par", "require_pushed_authorization_requests": true, "dpop_signing_alg_values_supported": [ "RS256", diff --git a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo index 36fa95c0..5cfb1896 100644 --- a/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo +++ b/tests/oauth_test/response/2/xrpc/com.atproto.repo.describeRepo @@ -23,7 +23,7 @@ { "id": "#atproto_pds", "type": "AtprotoPersonalDataServer", - "serviceEndpoint": "http://localhost:%1/response/1" + "serviceEndpoint": "http://localhost:{{SERVER_PORT_NO}}/response/1" } ] }, diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 72c69ac2..8c2942e2 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -56,6 +56,7 @@ oauth_test::oauth_test() } else { mime_type = "application/json"; result = SimpleHttpServer::readFile(path, data); + data.replace("{{SERVER_PORT_NO}}", QString::number(m_listenPort).toLocal8Bit()); qDebug().noquote() << " result =" << result; } }); @@ -124,6 +125,8 @@ void oauth_test::test_oauth_server() void oauth_test::test_oauth() { + // response/1 : pds + // response/2 : entry-way Authorization oauth; QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); @@ -136,9 +139,15 @@ void oauth_test::test_oauth() spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } - oauth.setServiceEndpoint(oauth.serviceEndpoint().arg(m_listenPort)); - QVERIFY(oauth.serviceEndpoint() == QString("http://localhost:%1/response/1").arg(m_listenPort)); + + { + QSignalSpy spy(&oauth, SIGNAL(authorizationServerChanged())); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + QVERIFY(oauth.authorizationServer() + == QString("http://localhost:%1/response/2").arg(m_listenPort)); } void oauth_test::test_jwt() From ee67351c7901e519f3ed662236761d5db1f49e51 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 02:35:35 +0900 Subject: [PATCH 051/127] =?UTF-8?q?oauth-authorization-server=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 21 +++++ lib/atprotocol/lexicons_func.cpp | 45 +++++++++++ lib/atprotocol/lexicons_func.h | 5 ++ .../wellknownoauthauthorizationserver.cpp | 41 ++++++++++ .../wellknownoauthauthorizationserver.h | 26 +++++++ lib/lib.pro | 2 + lib/tools/authorization.cpp | 43 +++++++++++ lib/tools/authorization.h | 5 ++ ... => directory.plc.log.audit.json.template} | 0 ...known.oauth.authorization.server.defs.json | 77 +++++++++++++++++++ ...n.oauth.authorization.server.json.template | 23 ++++++ tests/oauth_test/tst_oauth_test.cpp | 8 ++ 12 files changed, 296 insertions(+) create mode 100644 lib/extension/well-known/wellknownoauthauthorizationserver.cpp create mode 100644 lib/extension/well-known/wellknownoauthauthorizationserver.h rename scripts/lexicons/{directory.plc.log.audit.json.temple => directory.plc.log.audit.json.template} (100%) create mode 100644 scripts/lexicons/well.known.oauth.authorization.server.defs.json create mode 100644 scripts/lexicons/well.known.oauth.authorization.server.json.template diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index aebfe2a0..c20c5102 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,27 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +struct ServerMetadata +{ + QString issuer; + QList response_types_supported; + QList grant_types_supported; + QList code_challenge_methods_supported; + QList token_endpoint_auth_methods_supported; + QList token_endpoint_auth_signing_alg_values_supported; + QList scopes_supported; + QList subject_types_supported; + bool authorization_response_iss_parameter_supported = false; + QString pushed_authorization_request_endpoint; + bool require_pushed_authorization_requests = false; + QList dpop_signing_alg_values_supported; + bool require_request_uri_registration = false; + bool client_id_metadata_document_supported = false; +}; +} + // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { struct OAuthProtectedResource diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 612b3466..ecf217cf 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,51 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +void copyServerMetadata(const QJsonObject &src, + WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest) +{ + if (!src.isEmpty()) { + dest.issuer = src.value("issuer").toString(); + for (const auto &value : src.value("response_types_supported").toArray()) { + dest.response_types_supported.append(value.toString()); + } + for (const auto &value : src.value("grant_types_supported").toArray()) { + dest.grant_types_supported.append(value.toString()); + } + for (const auto &value : src.value("code_challenge_methods_supported").toArray()) { + dest.code_challenge_methods_supported.append(value.toString()); + } + for (const auto &value : src.value("token_endpoint_auth_methods_supported").toArray()) { + dest.token_endpoint_auth_methods_supported.append(value.toString()); + } + for (const auto &value : + src.value("token_endpoint_auth_signing_alg_values_supported").toArray()) { + dest.token_endpoint_auth_signing_alg_values_supported.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("subject_types_supported").toArray()) { + dest.subject_types_supported.append(value.toString()); + } + dest.authorization_response_iss_parameter_supported = + src.value("authorization_response_iss_parameter_supported").toBool(); + dest.pushed_authorization_request_endpoint = + src.value("pushed_authorization_request_endpoint").toString(); + dest.require_pushed_authorization_requests = + src.value("require_pushed_authorization_requests").toBool(); + for (const auto &value : src.value("dpop_signing_alg_values_supported").toArray()) { + dest.dpop_signing_alg_values_supported.append(value.toString()); + } + dest.require_request_uri_registration = + src.value("require_request_uri_registration").toBool(); + dest.client_id_metadata_document_supported = + src.value("client_id_metadata_document_supported").toBool(); + } +} +} // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { void copyOAuthProtectedResource(const QJsonObject &src, diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index f94ccc6d..fdd61b6e 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// well.known.oauth.authorization.server.defs +namespace WellKnownOauthAuthorizationServerDefs { +void copyServerMetadata(const QJsonObject &src, + WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); +} // well.known.oauth.protected.resource namespace WellKnownOauthProtectedResource { void copyOAuthProtectedResource(const QJsonObject &src, diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp new file mode 100644 index 00000000..3b5c5dab --- /dev/null +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp @@ -0,0 +1,41 @@ +#include "wellknownoauthauthorizationserver.h" +#include "atprotocol/lexicons_func.h" + +#include +#include +#include + +namespace AtProtocolInterface { + +WellKnownOauthAuthorizationServer::WellKnownOauthAuthorizationServer(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void WellKnownOauthAuthorizationServer::server() +{ + QUrlQuery url_query; + + get(QStringLiteral(".well-known/oauth-authorization-server"), url_query, false); +} + +const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & +WellKnownOauthAuthorizationServer::serverMetadata() const +{ + return m_serverMetadata; +} + +bool WellKnownOauthAuthorizationServer::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::WellKnownOauthAuthorizationServerDefs::copyServerMetadata(json_doc.object(), + m_serverMetadata); + } + + return success; +} + +} diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.h b/lib/extension/well-known/wellknownoauthauthorizationserver.h new file mode 100644 index 00000000..d644a443 --- /dev/null +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.h @@ -0,0 +1,26 @@ +#ifndef WELLKNOWNOAUTHAUTHORIZATIONSERVER_H +#define WELLKNOWNOAUTHAUTHORIZATIONSERVER_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class WellKnownOauthAuthorizationServer : public AccessAtProtocol +{ +public: + explicit WellKnownOauthAuthorizationServer(QObject *parent = nullptr); + + void server(); + + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & + serverMetadata() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata m_serverMetadata; +}; + +} + +#endif // WELLKNOWNOAUTHAUTHORIZATIONSERVER_H diff --git a/lib/lib.pro b/lib/lib.pro index b635fbff..21aeb6a3 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -92,6 +92,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ $$PWD/http/httpaccessmanager.cpp \ @@ -205,6 +206,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ $$PWD/http/httpaccessmanager.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index bd8139f8..cbf09dfe 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -3,6 +3,7 @@ #include "http/simplehttpserver.h" #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" #include "extension/well-known/wellknownoauthprotectedresource.h" +#include "extension/well-known/wellknownoauthauthorizationserver.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -18,6 +19,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; Authorization::Authorization(QObject *parent) : QObject { parent } { } @@ -81,6 +83,7 @@ void Authorization::requestOauthProtectedResource() setAuthorizationServer( resource->OAuthProtectedResource().authorization_servers.first()); // next step + requestOauthAuthorizationServer(); } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); @@ -97,6 +100,33 @@ void Authorization::requestOauthProtectedResource() void Authorization::requestOauthAuthorizationServer() { // /.well-known/oauth-authorization-server + + if (authorizationServer().isEmpty()) + return; + + AtProtocolInterface::AccountData account; + account.service = authorizationServer(); + + WellKnownOauthAuthorizationServer *server = new WellKnownOauthAuthorizationServer(this); + connect(server, &WellKnownOauthAuthorizationServer::finished, this, [=](bool success) { + if (success) { + // TODO: Validate serverMetadata + + if (!server->serverMetadata().pushed_authorization_request_endpoint.isEmpty()) { + setPushedAuthorizationRequestEndpoint( + server->serverMetadata().pushed_authorization_request_endpoint); + + // next step + } else { + emit errorOccured("Invalid oauth-authorization-server", + "pushed_authorization_request_endpoint is empty."); + } + } else { + emit errorOccured(server->errorCode(), server->errorMessage()); + } + }); + server->setAccount(account); + server->server(); } void Authorization::makeClientId() @@ -361,6 +391,19 @@ void Authorization::setAuthorizationServer(const QString &newAuthorizationServer emit authorizationServerChanged(); } +QString Authorization::pushedAuthorizationRequestEndpoint() const +{ + return m_pushedAuthorizationRequestEndpoint; +} + +void Authorization::setPushedAuthorizationRequestEndpoint( + const QString &newPushedAuthorizationRequestEndpoint) +{ + if (m_pushedAuthorizationRequestEndpoint == newPushedAuthorizationRequestEndpoint) + return; + m_pushedAuthorizationRequestEndpoint = newPushedAuthorizationRequestEndpoint; + emit pushedAuthorizationRequestEndpointChanged(); +} QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index db2d6b6e..3b150b30 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -27,6 +27,9 @@ class Authorization : public QObject void setServiceEndpoint(const QString &newServiceEndpoint); QString authorizationServer() const; void setAuthorizationServer(const QString &newAuthorizationServer); + QString pushedAuthorizationRequestEndpoint() const; + void + setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -35,6 +38,7 @@ class Authorization : public QObject void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); void authorizationServerChanged(); + void pushedAuthorizationRequestEndpointChanged(); void finished(bool success); private: @@ -48,6 +52,7 @@ class Authorization : public QObject // server info QString m_serviceEndpoint; QString m_authorizationServer; + QString m_pushedAuthorizationRequestEndpoint; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/directory.plc.log.audit.json.temple b/scripts/lexicons/directory.plc.log.audit.json.template similarity index 100% rename from scripts/lexicons/directory.plc.log.audit.json.temple rename to scripts/lexicons/directory.plc.log.audit.json.template diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/well.known.oauth.authorization.server.defs.json new file mode 100644 index 00000000..f043c978 --- /dev/null +++ b/scripts/lexicons/well.known.oauth.authorization.server.defs.json @@ -0,0 +1,77 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.authorization.server.defs", + "defs": { + "serverMetadata": { + "type": "object", + "properties": { + "issuer": { + "type": "string" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject_types_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorization_response_iss_parameter_supported": { + "type": "boolean" + }, + "pushed_authorization_request_endpoint": { + "type": "string" + }, + "require_pushed_authorization_requests": { + "type": "boolean" + }, + "dpop_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "require_request_uri_registration": { + "type": "boolean" + }, + "client_id_metadata_document_supported": { + "type": "boolean" + } + } + } + } +} diff --git a/scripts/lexicons/well.known.oauth.authorization.server.json.template b/scripts/lexicons/well.known.oauth.authorization.server.json.template new file mode 100644 index 00000000..2b6ed4bd --- /dev/null +++ b/scripts/lexicons/well.known.oauth.authorization.server.json.template @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.authorization.server", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + ], + "properties": { + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "well.known.oauth.authorization.server.defs#serverMetadata" + } + } + } + } +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 8c2942e2..d40b6ca0 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -148,6 +148,14 @@ void oauth_test::test_oauth() } QVERIFY(oauth.authorizationServer() == QString("http://localhost:%1/response/2").arg(m_listenPort)); + + { + QSignalSpy spy(&oauth, SIGNAL(pushedAuthorizationRequestEndpointChanged())); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } + QVERIFY(oauth.pushedAuthorizationRequestEndpoint() + == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); } void oauth_test::test_jwt() From b673aa05544e886682b5dff7754391d8170f61db Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 3 Sep 2024 02:49:09 +0900 Subject: [PATCH 052/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 6 ++--- lib/atprotocol/lexicons_func.cpp | 8 +++---- lib/atprotocol/lexicons_func.h | 8 +++---- .../wellknownoauthprotectedresource.cpp | 10 ++++---- .../wellknownoauthprotectedresource.h | 7 +++--- lib/tools/authorization.cpp | 5 ++-- ....known.oauth.protected.resource.defs.json} | 4 ++-- ...own.oauth.protected.resource.json.template | 23 +++++++++++++++++++ 8 files changed, 46 insertions(+), 25 deletions(-) rename scripts/lexicons/{well.known.oauth.protected.resource.json => well.known.oauth.protected.resource.defs.json} (91%) create mode 100644 scripts/lexicons/well.known.oauth.protected.resource.json.template diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index c20c5102..972fa319 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2075,9 +2075,9 @@ struct ServerMetadata }; } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -struct OAuthProtectedResource +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +struct ResourceMetadata { QString resource; QList authorization_servers; diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index ecf217cf..a4aa55c2 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2952,10 +2952,10 @@ void copyServerMetadata(const QJsonObject &src, } } } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -void copyOAuthProtectedResource(const QJsonObject &src, - WellKnownOauthProtectedResource::OAuthProtectedResource &dest) +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +void copyResourceMetadata(const QJsonObject &src, + WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest) { if (!src.isEmpty()) { dest.resource = src.value("resource").toString(); diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index fdd61b6e..4d4ace52 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -417,10 +417,10 @@ namespace WellKnownOauthAuthorizationServerDefs { void copyServerMetadata(const QJsonObject &src, WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); } -// well.known.oauth.protected.resource -namespace WellKnownOauthProtectedResource { -void copyOAuthProtectedResource(const QJsonObject &src, - WellKnownOauthProtectedResource::OAuthProtectedResource &dest); +// well.known.oauth.protected.resource.defs +namespace WellKnownOauthProtectedResourceDefs { +void copyResourceMetadata(const QJsonObject &src, + WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest); } } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp index 69ed2ec8..0f392dee 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.cpp +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -19,10 +19,10 @@ void WellKnownOauthProtectedResource::resource() get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); } -const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & -WellKnownOauthProtectedResource::OAuthProtectedResource() const +const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & +WellKnownOauthProtectedResource::resourceMetadata() const { - return m_OAuthProtectedResource; + return m_resourceMetadata; } bool WellKnownOauthProtectedResource::parseJson(bool success, const QString reply_json) @@ -31,8 +31,8 @@ bool WellKnownOauthProtectedResource::parseJson(bool success, const QString repl if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthProtectedResource::copyOAuthProtectedResource( - json_doc.object(), m_OAuthProtectedResource); + AtProtocolType::WellKnownOauthProtectedResourceDefs::copyResourceMetadata( + json_doc.object(), m_resourceMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h index 980c9fb6..3bc4caa7 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.h +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -12,14 +12,13 @@ class WellKnownOauthProtectedResource : public AccessAtProtocol void resource(); - const AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource & - OAuthProtectedResource() const; + const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & + resourceMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthProtectedResource::OAuthProtectedResource - m_OAuthProtectedResource; + AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata m_resourceMetadata; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index cbf09dfe..b3aecc52 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -79,9 +79,8 @@ void Authorization::requestOauthProtectedResource() WellKnownOauthProtectedResource *resource = new WellKnownOauthProtectedResource(this); connect(resource, &WellKnownOauthProtectedResource::finished, this, [=](bool success) { if (success) { - if (!resource->OAuthProtectedResource().authorization_servers.isEmpty()) { - setAuthorizationServer( - resource->OAuthProtectedResource().authorization_servers.first()); + if (!resource->resourceMetadata().authorization_servers.isEmpty()) { + setAuthorizationServer(resource->resourceMetadata().authorization_servers.first()); // next step requestOauthAuthorizationServer(); } else { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json b/scripts/lexicons/well.known.oauth.protected.resource.defs.json similarity index 91% rename from scripts/lexicons/well.known.oauth.protected.resource.json rename to scripts/lexicons/well.known.oauth.protected.resource.defs.json index a03f6aac..fcd6f913 100644 --- a/scripts/lexicons/well.known.oauth.protected.resource.json +++ b/scripts/lexicons/well.known.oauth.protected.resource.defs.json @@ -1,8 +1,8 @@ { "lexicon": 1, - "id": "well.known.oauth.protected.resource", + "id": "well.known.oauth.protected.resource.defs", "defs": { - "OAuthProtectedResource": { + "resourceMetadata": { "type": "object", "properties": { "resource": { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json.template b/scripts/lexicons/well.known.oauth.protected.resource.json.template new file mode 100644 index 00000000..eee48e1e --- /dev/null +++ b/scripts/lexicons/well.known.oauth.protected.resource.json.template @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "well.known.oauth.protected.resource", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + ], + "properties": { + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "well.known.oauth.protected.resource.defs#resourceMetadata" + } + } + } + } +} From d3128f9c95e689ce74ff3e1eacc58c9ab2a50af7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 00:42:16 +0900 Subject: [PATCH 053/127] =?UTF-8?q?server=20metadata=E3=81=AE=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 75 ++++++++++++++++++++++++++++++++++--- lib/tools/authorization.h | 5 +++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index b3aecc52..1497fdf5 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -27,13 +27,21 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { m_serviceEndpoint.clear(); + m_authorizationServer.clear(); + m_pushedAuthorizationRequestEndpoint.clear(); + // m_redirectUri.clear(); m_clientId.clear(); + // par m_codeChallenge.clear(); m_codeVerifier.clear(); m_state.clear(); m_parPayload.clear(); + // request token + m_code.clear(); m_requestTokenPayload.clear(); + // + m_listenPort.clear(); } void Authorization::start(const QString &pds, const QString &handle) @@ -109,25 +117,82 @@ void Authorization::requestOauthAuthorizationServer() WellKnownOauthAuthorizationServer *server = new WellKnownOauthAuthorizationServer(this); connect(server, &WellKnownOauthAuthorizationServer::finished, this, [=](bool success) { if (success) { - // TODO: Validate serverMetadata - - if (!server->serverMetadata().pushed_authorization_request_endpoint.isEmpty()) { + QString error_message; + if (validateServerMetadata(server->serverMetadata(), error_message)) { setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); // next step } else { - emit errorOccured("Invalid oauth-authorization-server", - "pushed_authorization_request_endpoint is empty."); + qDebug().noquote() << error_message; + emit errorOccured("Invalid oauth-authorization-server", error_message); } } else { emit errorOccured(server->errorCode(), server->errorMessage()); } + server->deleteLater(); }); server->setAccount(account); server->server(); } +bool Authorization::validateServerMetadata( + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata + &server_metadata, + QString &error_message) +{ + bool ret = false; + if (QUrl(server_metadata.issuer).host() != QUrl(authorizationServer()).host()) { + // リダイレクトされると変わるので対応しないといけない + error_message = QString("'issuer' is an invalid value(%1).").arg(server_metadata.issuer); + } else if (!server_metadata.response_types_supported.contains("code")) { + error_message = QStringLiteral("'response_types_supported' must contain 'code'."); + } else if (!server_metadata.grant_types_supported.contains("authorization_code")) { + error_message = + QStringLiteral("'grant_types_supported' must contain 'authorization_code'."); + } else if (!server_metadata.grant_types_supported.contains("refresh_token")) { + error_message = QStringLiteral("'grant_types_supported' must contain 'refresh_token'."); + } else if (!server_metadata.code_challenge_methods_supported.contains("S256")) { + error_message = QStringLiteral("'code_challenge_methods_supported' must contain 'S256'."); + } else if (!server_metadata.token_endpoint_auth_methods_supported.contains("private_key_jwt")) { + error_message = QStringLiteral( + "'token_endpoint_auth_methods_supported' must contain 'private_key_jwt'."); + } else if (!server_metadata.token_endpoint_auth_methods_supported.contains("none")) { + error_message = + QStringLiteral("'token_endpoint_auth_methods_supported' must contain 'none'."); + } else if (!server_metadata.token_endpoint_auth_signing_alg_values_supported.contains( + "ES256")) { + error_message = QStringLiteral( + "'token_endpoint_auth_signing_alg_values_supported' must contain 'ES256'."); + } else if (!server_metadata.scopes_supported.contains("atproto")) { + error_message = QStringLiteral("'scopes_supported' must contain 'atproto'."); + } else if (!(server_metadata.subject_types_supported.isEmpty() + || (!server_metadata.subject_types_supported.isEmpty() + && server_metadata.subject_types_supported.contains("public")))) { + error_message = QStringLiteral("'subject_types_supported' must contain 'public'."); + } else if (!server_metadata.authorization_response_iss_parameter_supported) { + error_message = + QString("'authorization_response_iss_parameter_supported' is an invalid value(%1).") + .arg(server_metadata.authorization_response_iss_parameter_supported); + } else if (server_metadata.pushed_authorization_request_endpoint.isEmpty()) { + error_message = QStringLiteral("pushed_authorization_request_endpoint must be set'."); + } else if (!server_metadata.require_pushed_authorization_requests) { + error_message = QString("'require_pushed_authorization_requests' is an invalid value(%1).") + .arg(server_metadata.require_pushed_authorization_requests); + } else if (!server_metadata.dpop_signing_alg_values_supported.contains("ES256")) { + error_message = QStringLiteral("'dpop_signing_alg_values_supported' must contain 'ES256'."); + } else if (!server_metadata.require_request_uri_registration) { + error_message = QString("'require_request_uri_registration' is an invalid value(%1).") + .arg(server_metadata.require_request_uri_registration); + } else if (!server_metadata.client_id_metadata_document_supported) { + error_message = QString("'client_id_metadata_document_supported' is an invalid value(%1).") + .arg(server_metadata.client_id_metadata_document_supported); + } else { + ret = true; + } + return ret; +} + void Authorization::makeClientId() { QString port; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 3b150b30..f4dccc86 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -2,6 +2,7 @@ #define AUTHORIZATION_H #include +#include "atprotocol/lexicons.h" class Authorization : public QObject { @@ -48,6 +49,10 @@ class Authorization : public QObject // server info void requestOauthProtectedResource(); void requestOauthAuthorizationServer(); + bool validateServerMetadata( + const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata + &server_metadata, + QString &error_message); // server info QString m_serviceEndpoint; From a50ae8387ca6116de13bf8e3c5ca4c9575dbb5d8 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 01:15:15 +0900 Subject: [PATCH 054/127] =?UTF-8?q?par=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=81=B8=E3=81=AE=E6=8E=A5=E7=B6=9A=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func.cpp | 1 + lib/tools/authorization.cpp | 37 ++++++++++++++----- lib/tools/authorization.h | 9 +++++ ...known.oauth.authorization.server.defs.json | 3 ++ tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/par | 4 ++ tests/oauth_test/tst_oauth_test.cpp | 2 + 8 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 tests/oauth_test/response/2/oauth/par diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 972fa319..96d08959 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2072,6 +2072,7 @@ struct ServerMetadata QList dpop_signing_alg_values_supported; bool require_request_uri_registration = false; bool client_id_metadata_document_supported = false; + QString authorization_endpoint; }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index a4aa55c2..f5144e87 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2949,6 +2949,7 @@ void copyServerMetadata(const QJsonObject &src, src.value("require_request_uri_registration").toBool(); dest.client_id_metadata_document_supported = src.value("client_id_metadata_document_supported").toBool(); + dest.authorization_endpoint = src.value("authorization_endpoint").toString(); } } } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 1497fdf5..5b73481d 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -26,9 +26,15 @@ Authorization::Authorization(QObject *parent) : QObject { parent } { } void Authorization::reset() { + // user + m_handle.clear(); + // server info m_serviceEndpoint.clear(); m_authorizationServer.clear(); + // server meta data m_pushedAuthorizationRequestEndpoint.clear(); + m_authorizationEndpoint.clear(); + m_scopesSupported.clear(); // m_redirectUri.clear(); m_clientId.clear(); @@ -121,8 +127,12 @@ void Authorization::requestOauthAuthorizationServer() if (validateServerMetadata(server->serverMetadata(), error_message)) { setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); + setAuthorizationEndpoint(server->serverMetadata().authorization_endpoint); + m_scopesSupported = server->serverMetadata().scopes_supported; // next step + makeParPayload(); + par(); } else { qDebug().noquote() << error_message; emit errorOccured("Invalid oauth-authorization-server", error_message); @@ -223,12 +233,6 @@ void Authorization::makeParPayload() m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QStringList scopes_supported; - scopes_supported << "offline_access" - << "openid" - // << "email" - // << "phone" - << "profile"; QString login_hint = "ioriayane2.bsky.social"; QUrlQuery query; @@ -238,7 +242,7 @@ void Authorization::makeParPayload() query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("state", m_state); query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); - query.addQueryItem("scope", scopes_supported.join(" ")); + query.addQueryItem("scope", m_scopesSupported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); @@ -246,11 +250,10 @@ void Authorization::makeParPayload() void Authorization::par() { - if (m_parPayload.isEmpty()) + if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) return; - QString endpoint = "https://bsky.social/oauth/par"; - QNetworkRequest request((QUrl(endpoint))); + QNetworkRequest request((QUrl(pushedAuthorizationRequestEndpoint()))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QPointer alive = this; @@ -468,6 +471,20 @@ void Authorization::setPushedAuthorizationRequestEndpoint( m_pushedAuthorizationRequestEndpoint = newPushedAuthorizationRequestEndpoint; emit pushedAuthorizationRequestEndpointChanged(); } + +QString Authorization::authorizationEndpoint() const +{ + return m_authorizationEndpoint; +} + +void Authorization::setAuthorizationEndpoint(const QString &newAuthorizationEndpoint) +{ + if (m_authorizationEndpoint == newAuthorizationEndpoint) + return; + m_authorizationEndpoint = newAuthorizationEndpoint; + emit authorizationEndpointChanged(); +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index f4dccc86..a1974be1 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -31,6 +31,9 @@ class Authorization : public QObject QString pushedAuthorizationRequestEndpoint() const; void setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); + QString authorizationEndpoint() const; + void setAuthorizationEndpoint(const QString &newAuthorizationEndpoint); + QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; @@ -40,6 +43,7 @@ class Authorization : public QObject void serviceEndpointChanged(); void authorizationServerChanged(); void pushedAuthorizationRequestEndpointChanged(); + void authorizationEndpointChanged(); void finished(bool success); private: @@ -54,10 +58,15 @@ class Authorization : public QObject &server_metadata, QString &error_message); + // user + QString m_handle; // server info QString m_serviceEndpoint; QString m_authorizationServer; + // server meta data QString m_pushedAuthorizationRequestEndpoint; + QString m_authorizationEndpoint; + QStringList m_scopesSupported; // QString m_redirectUri; QString m_clientId; diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/well.known.oauth.authorization.server.defs.json index f043c978..1402b92a 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.defs.json +++ b/scripts/lexicons/well.known.oauth.authorization.server.defs.json @@ -70,6 +70,9 @@ }, "client_id_metadata_document_supported": { "type": "boolean" + }, + "authorization_endpoint": { + "type": "string" } } } diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index d909191c..8da511bf 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -3,5 +3,6 @@ response/1/.well-known/oauth-protected-resource response/2/xrpc/com.atproto.repo.describeRepo response/2/.well-known/oauth-authorization-server + response/2/oauth/par diff --git a/tests/oauth_test/response/2/oauth/par b/tests/oauth_test/response/2/oauth/par new file mode 100644 index 00000000..4ac5606e --- /dev/null +++ b/tests/oauth_test/response/2/oauth/par @@ -0,0 +1,4 @@ +{ + "request_uri": "urn:ietf:params:oauth:request_uri:req-05650c01604941dc674f0af9cb032aca", + "expires_in": 299 +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index d40b6ca0..b5310980 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -156,6 +156,8 @@ void oauth_test::test_oauth() } QVERIFY(oauth.pushedAuthorizationRequestEndpoint() == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); + QVERIFY(oauth.authorizationEndpoint() + == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); } void oauth_test::test_jwt() From 94ad518b38c7eb369488b149ce28949df0e2c91f Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 01:36:53 +0900 Subject: [PATCH 055/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 24 +++++------ lib/atprotocol/lexicons_func.cpp | 43 ++++++++----------- lib/atprotocol/lexicons_func.h | 13 ++---- .../wellknownoauthauthorizationserver.cpp | 7 ++- .../wellknownoauthauthorizationserver.h | 7 ++- .../wellknownoauthprotectedresource.cpp | 7 ++- .../wellknownoauthprotectedresource.h | 7 ++- lib/tools/authorization.cpp | 7 ++- lib/tools/authorization.h | 7 ++- ...l.known.oauth.protected.resource.defs.json | 35 --------------- ...n.server.defs.json => wellKnown.defs.json} | 31 ++++++++++++- ...wn.oauthAuthorizationServer.json.template} | 4 +- ...nown.oauthProtectedResource.json.template} | 4 +- 13 files changed, 85 insertions(+), 111 deletions(-) delete mode 100644 scripts/lexicons/well.known.oauth.protected.resource.defs.json rename scripts/lexicons/{well.known.oauth.authorization.server.defs.json => wellKnown.defs.json} (73%) rename scripts/lexicons/{well.known.oauth.protected.resource.json.template => wellKnown.oauthAuthorizationServer.json.template} (76%) rename scripts/lexicons/{well.known.oauth.authorization.server.json.template => wellKnown.oauthProtectedResource.json.template} (76%) diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 96d08959..8c82a592 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,8 +2054,16 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { +// wellKnown.defs +namespace WellKnownDefs { +struct ResourceMetadata +{ + QString resource; + QList authorization_servers; + QList scopes_supported; + QList bearer_methods_supported; + QString resource_documentation; +}; struct ServerMetadata { QString issuer; @@ -2076,18 +2084,6 @@ struct ServerMetadata }; } -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -struct ResourceMetadata -{ - QString resource; - QList authorization_servers; - QList scopes_supported; - QList bearer_methods_supported; - QString resource_documentation; -}; -} - } Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedPost::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedLike::Main) diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index f5144e87..b3748e61 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,10 +2907,25 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { -void copyServerMetadata(const QJsonObject &src, - WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest) +// wellKnown.defs +namespace WellKnownDefs { +void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest) +{ + if (!src.isEmpty()) { + dest.resource = src.value("resource").toString(); + for (const auto &value : src.value("authorization_servers").toArray()) { + dest.authorization_servers.append(value.toString()); + } + for (const auto &value : src.value("scopes_supported").toArray()) { + dest.scopes_supported.append(value.toString()); + } + for (const auto &value : src.value("bearer_methods_supported").toArray()) { + dest.bearer_methods_supported.append(value.toString()); + } + dest.resource_documentation = src.value("resource_documentation").toString(); + } +} +void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &dest) { if (!src.isEmpty()) { dest.issuer = src.value("issuer").toString(); @@ -2953,26 +2968,6 @@ void copyServerMetadata(const QJsonObject &src, } } } -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -void copyResourceMetadata(const QJsonObject &src, - WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest) -{ - if (!src.isEmpty()) { - dest.resource = src.value("resource").toString(); - for (const auto &value : src.value("authorization_servers").toArray()) { - dest.authorization_servers.append(value.toString()); - } - for (const auto &value : src.value("scopes_supported").toArray()) { - dest.scopes_supported.append(value.toString()); - } - for (const auto &value : src.value("bearer_methods_supported").toArray()) { - dest.bearer_methods_supported.append(value.toString()); - } - dest.resource_documentation = src.value("resource_documentation").toString(); - } -} -} } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 4d4ace52..904acd72 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,15 +412,10 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } -// well.known.oauth.authorization.server.defs -namespace WellKnownOauthAuthorizationServerDefs { -void copyServerMetadata(const QJsonObject &src, - WellKnownOauthAuthorizationServerDefs::ServerMetadata &dest); -} -// well.known.oauth.protected.resource.defs -namespace WellKnownOauthProtectedResourceDefs { -void copyResourceMetadata(const QJsonObject &src, - WellKnownOauthProtectedResourceDefs::ResourceMetadata &dest); +// wellKnown.defs +namespace WellKnownDefs { +void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest); +void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &dest); } } diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp index 3b5c5dab..0f8745cc 100644 --- a/lib/extension/well-known/wellknownoauthauthorizationserver.cpp +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.cpp @@ -12,14 +12,14 @@ WellKnownOauthAuthorizationServer::WellKnownOauthAuthorizationServer(QObject *pa { } -void WellKnownOauthAuthorizationServer::server() +void WellKnownOauthAuthorizationServer::oauthAuthorizationServer() { QUrlQuery url_query; get(QStringLiteral(".well-known/oauth-authorization-server"), url_query, false); } -const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & +const AtProtocolType::WellKnownDefs::ServerMetadata & WellKnownOauthAuthorizationServer::serverMetadata() const { return m_serverMetadata; @@ -31,8 +31,7 @@ bool WellKnownOauthAuthorizationServer::parseJson(bool success, const QString re if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthAuthorizationServerDefs::copyServerMetadata(json_doc.object(), - m_serverMetadata); + AtProtocolType::WellKnownDefs::copyServerMetadata(json_doc.object(), m_serverMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthauthorizationserver.h b/lib/extension/well-known/wellknownoauthauthorizationserver.h index d644a443..c22122c0 100644 --- a/lib/extension/well-known/wellknownoauthauthorizationserver.h +++ b/lib/extension/well-known/wellknownoauthauthorizationserver.h @@ -10,15 +10,14 @@ class WellKnownOauthAuthorizationServer : public AccessAtProtocol public: explicit WellKnownOauthAuthorizationServer(QObject *parent = nullptr); - void server(); + void oauthAuthorizationServer(); - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata & - serverMetadata() const; + const AtProtocolType::WellKnownDefs::ServerMetadata &serverMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata m_serverMetadata; + AtProtocolType::WellKnownDefs::ServerMetadata m_serverMetadata; }; } diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.cpp b/lib/extension/well-known/wellknownoauthprotectedresource.cpp index 0f392dee..aceaf604 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.cpp +++ b/lib/extension/well-known/wellknownoauthprotectedresource.cpp @@ -12,14 +12,14 @@ WellKnownOauthProtectedResource::WellKnownOauthProtectedResource(QObject *parent { } -void WellKnownOauthProtectedResource::resource() +void WellKnownOauthProtectedResource::oauthProtectedResource() { QUrlQuery url_query; get(QStringLiteral(".well-known/oauth-protected-resource"), url_query, false); } -const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & +const AtProtocolType::WellKnownDefs::ResourceMetadata & WellKnownOauthProtectedResource::resourceMetadata() const { return m_resourceMetadata; @@ -31,8 +31,7 @@ bool WellKnownOauthProtectedResource::parseJson(bool success, const QString repl if (json_doc.isEmpty()) { success = false; } else { - AtProtocolType::WellKnownOauthProtectedResourceDefs::copyResourceMetadata( - json_doc.object(), m_resourceMetadata); + AtProtocolType::WellKnownDefs::copyResourceMetadata(json_doc.object(), m_resourceMetadata); } return success; diff --git a/lib/extension/well-known/wellknownoauthprotectedresource.h b/lib/extension/well-known/wellknownoauthprotectedresource.h index 3bc4caa7..f89d0cce 100644 --- a/lib/extension/well-known/wellknownoauthprotectedresource.h +++ b/lib/extension/well-known/wellknownoauthprotectedresource.h @@ -10,15 +10,14 @@ class WellKnownOauthProtectedResource : public AccessAtProtocol public: explicit WellKnownOauthProtectedResource(QObject *parent = nullptr); - void resource(); + void oauthProtectedResource(); - const AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata & - resourceMetadata() const; + const AtProtocolType::WellKnownDefs::ResourceMetadata &resourceMetadata() const; private: virtual bool parseJson(bool success, const QString reply_json); - AtProtocolType::WellKnownOauthProtectedResourceDefs::ResourceMetadata m_resourceMetadata; + AtProtocolType::WellKnownDefs::ResourceMetadata m_resourceMetadata; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 5b73481d..83f442ec 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -107,7 +107,7 @@ void Authorization::requestOauthProtectedResource() resource->deleteLater(); }); resource->setAccount(account); - resource->resource(); + resource->oauthProtectedResource(); } void Authorization::requestOauthAuthorizationServer() @@ -143,12 +143,11 @@ void Authorization::requestOauthAuthorizationServer() server->deleteLater(); }); server->setAccount(account); - server->server(); + server->oauthAuthorizationServer(); } bool Authorization::validateServerMetadata( - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata - &server_metadata, + const AtProtocolType::WellKnownDefs::ServerMetadata &server_metadata, QString &error_message) { bool ret = false; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index a1974be1..5a283482 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -53,10 +53,9 @@ class Authorization : public QObject // server info void requestOauthProtectedResource(); void requestOauthAuthorizationServer(); - bool validateServerMetadata( - const AtProtocolType::WellKnownOauthAuthorizationServerDefs::ServerMetadata - &server_metadata, - QString &error_message); + bool + validateServerMetadata(const AtProtocolType::WellKnownDefs::ServerMetadata &server_metadata, + QString &error_message); // user QString m_handle; diff --git a/scripts/lexicons/well.known.oauth.protected.resource.defs.json b/scripts/lexicons/well.known.oauth.protected.resource.defs.json deleted file mode 100644 index fcd6f913..00000000 --- a/scripts/lexicons/well.known.oauth.protected.resource.defs.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "lexicon": 1, - "id": "well.known.oauth.protected.resource.defs", - "defs": { - "resourceMetadata": { - "type": "object", - "properties": { - "resource": { - "type": "string" - }, - "authorization_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes_supported": { - "type": "array", - "items": { - "type": "string" - } - }, - "bearer_methods_supported": { - "type": "array", - "items": { - "type": "string" - } - }, - "resource_documentation": { - "type": "string" - } - } - } - } -} diff --git a/scripts/lexicons/well.known.oauth.authorization.server.defs.json b/scripts/lexicons/wellKnown.defs.json similarity index 73% rename from scripts/lexicons/well.known.oauth.authorization.server.defs.json rename to scripts/lexicons/wellKnown.defs.json index 1402b92a..248ed0b5 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.defs.json +++ b/scripts/lexicons/wellKnown.defs.json @@ -1,7 +1,36 @@ { "lexicon": 1, - "id": "well.known.oauth.authorization.server.defs", + "id": "wellKnown.defs", "defs": { + "resourceMetadata": { + "type": "object", + "properties": { + "resource": { + "type": "string" + }, + "authorization_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "bearer_methods_supported": { + "type": "array", + "items": { + "type": "string" + } + }, + "resource_documentation": { + "type": "string" + } + } + }, "serverMetadata": { "type": "object", "properties": { diff --git a/scripts/lexicons/well.known.oauth.protected.resource.json.template b/scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template similarity index 76% rename from scripts/lexicons/well.known.oauth.protected.resource.json.template rename to scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template index eee48e1e..acf6d38b 100644 --- a/scripts/lexicons/well.known.oauth.protected.resource.json.template +++ b/scripts/lexicons/wellKnown.oauthAuthorizationServer.json.template @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "well.known.oauth.protected.resource", + "id": "wellKnown.oauthAuthorizationServer", "defs": { "main": { "type": "query", @@ -15,7 +15,7 @@ "encoding": "application/json", "schema": { "type": "ref", - "ref": "well.known.oauth.protected.resource.defs#resourceMetadata" + "ref": "wellKnown.defs#serverMetadata" } } } diff --git a/scripts/lexicons/well.known.oauth.authorization.server.json.template b/scripts/lexicons/wellKnown.oauthProtectedResource.json.template similarity index 76% rename from scripts/lexicons/well.known.oauth.authorization.server.json.template rename to scripts/lexicons/wellKnown.oauthProtectedResource.json.template index 2b6ed4bd..743af930 100644 --- a/scripts/lexicons/well.known.oauth.authorization.server.json.template +++ b/scripts/lexicons/wellKnown.oauthProtectedResource.json.template @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "well.known.oauth.authorization.server", + "id": "wellKnown.oauthProtectedResource", "defs": { "main": { "type": "query", @@ -15,7 +15,7 @@ "encoding": "application/json", "schema": { "type": "ref", - "ref": "well.known.oauth.authorization.server.defs#serverMetadata" + "ref": "wellKnown.defs#resourceMetadata" } } } From 67527204464257f0ed2cc18c1b386dbc21332987 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 4 Sep 2024 03:12:18 +0900 Subject: [PATCH 056/127] =?UTF-8?q?PAR=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=81=BE=E3=81=A7=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 15 +++++- lib/atprotocol/lexicons.h | 9 ++++ lib/atprotocol/lexicons_func.cpp | 11 +++++ lib/atprotocol/lexicons_func.h | 5 ++ .../oauth/oauthpushedauthorizationrequest.cpp | 38 +++++++++++++++ .../oauth/oauthpushedauthorizationrequest.h | 26 +++++++++++ lib/lib.pro | 2 + lib/tools/authorization.cpp | 46 ++++++++----------- lib/tools/authorization.h | 3 ++ scripts/lexicons/oauth.defs.json | 17 +++++++ ...h.pushedAuthorizationRequest.json.template | 16 +++++++ tests/oauth_test/tst_oauth_test.cpp | 25 ++++++++++ 12 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 lib/extension/oauth/oauthpushedauthorizationrequest.cpp create mode 100644 lib/extension/oauth/oauthpushedauthorizationrequest.h create mode 100644 scripts/lexicons/oauth.defs.json create mode 100644 scripts/lexicons/oauth.pushedAuthorizationRequest.json.template diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index 76e4e2f5..a7ed7064 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -134,7 +134,12 @@ void AccessAtProtocol::get(const QString &endpoint, const QUrlQuery &query, qDebug().noquote() << LOG_DATETIME << " " << endpoint; qDebug().noquote() << LOG_DATETIME << " " << query.toString(); - QUrl url = QString("%1/%2").arg(service(), endpoint); + QUrl url; + if (endpoint.isEmpty()) { + url = service(); + } else { + url = QString("%1/%2").arg(service(), endpoint); + } url.setQuery(query); QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); @@ -191,7 +196,13 @@ void AccessAtProtocol::post(const QString &endpoint, const QByteArray &json, qDebug().noquote() << LOG_DATETIME << " " << endpoint; qDebug().noquote() << LOG_DATETIME << " " << json; - QNetworkRequest request(QUrl(QString("%1/%2").arg(service(), endpoint))); + QUrl url; + if (endpoint.isEmpty()) { + url = service(); + } else { + url = QString("%1/%2").arg(service(), endpoint); + } + QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (with_auth_header) { diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 8c82a592..b15c6dfa 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2054,6 +2054,15 @@ struct PlcAuditLogDetail typedef QList PlcAuditLog; } +// oauth.defs +namespace OauthDefs { +struct PushedAuthorizationResponse +{ + QString request_uri; + int expires_in = 0; +}; +} + // wellKnown.defs namespace WellKnownDefs { struct ResourceMetadata diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index b3748e61..0617271f 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2907,6 +2907,17 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) } } } +// oauth.defs +namespace OauthDefs { +void copyPushedAuthorizationResponse(const QJsonObject &src, + OauthDefs::PushedAuthorizationResponse &dest) +{ + if (!src.isEmpty()) { + dest.request_uri = src.value("request_uri").toString(); + dest.expires_in = src.value("expires_in").toInt(); + } +} +} // wellKnown.defs namespace WellKnownDefs { void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest) diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index 904acd72..f9831261 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -412,6 +412,11 @@ void copyCreate(const QJsonObject &src, DirectoryPlcDefs::Create &dest); void copyPlcAuditLogDetail(const QJsonObject &src, DirectoryPlcDefs::PlcAuditLogDetail &dest); void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest); } +// oauth.defs +namespace OauthDefs { +void copyPushedAuthorizationResponse(const QJsonObject &src, + OauthDefs::PushedAuthorizationResponse &dest); +} // wellKnown.defs namespace WellKnownDefs { void copyResourceMetadata(const QJsonObject &src, WellKnownDefs::ResourceMetadata &dest); diff --git a/lib/extension/oauth/oauthpushedauthorizationrequest.cpp b/lib/extension/oauth/oauthpushedauthorizationrequest.cpp new file mode 100644 index 00000000..bb226e16 --- /dev/null +++ b/lib/extension/oauth/oauthpushedauthorizationrequest.cpp @@ -0,0 +1,38 @@ +#include "oauthpushedauthorizationrequest.h" +#include "atprotocol/lexicons_func.h" + +#include +#include + +namespace AtProtocolInterface { + +OauthPushedAuthorizationRequest::OauthPushedAuthorizationRequest(QObject *parent) + : AccessAtProtocol { parent } +{ +} + +void OauthPushedAuthorizationRequest::pushedAuthorizationRequest(const QByteArray &payload) +{ + post(QString(), payload, false); +} + +const AtProtocolType::OauthDefs::PushedAuthorizationResponse & +OauthPushedAuthorizationRequest::pushedAuthorizationResponse() const +{ + return m_pushedAuthorizationResponse; +} + +bool OauthPushedAuthorizationRequest::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::OauthDefs::copyPushedAuthorizationResponse(json_doc.object(), + m_pushedAuthorizationResponse); + } + + return success; +} + +} diff --git a/lib/extension/oauth/oauthpushedauthorizationrequest.h b/lib/extension/oauth/oauthpushedauthorizationrequest.h new file mode 100644 index 00000000..c229f133 --- /dev/null +++ b/lib/extension/oauth/oauthpushedauthorizationrequest.h @@ -0,0 +1,26 @@ +#ifndef OAUTHPUSHEDAUTHORIZATIONREQUEST_H +#define OAUTHPUSHEDAUTHORIZATIONREQUEST_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class OauthPushedAuthorizationRequest : public AccessAtProtocol +{ +public: + explicit OauthPushedAuthorizationRequest(QObject *parent = nullptr); + + void pushedAuthorizationRequest(const QByteArray &payload); + + const AtProtocolType::OauthDefs::PushedAuthorizationResponse & + pushedAuthorizationResponse() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::OauthDefs::PushedAuthorizationResponse m_pushedAuthorizationResponse; +}; + +} + +#endif // OAUTHPUSHEDAUTHORIZATIONREQUEST_H diff --git a/lib/lib.pro b/lib/lib.pro index 21aeb6a3..9e23a137 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -92,6 +92,7 @@ SOURCES += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ + $$PWD/extension/oauth/oauthpushedauthorizationrequest.cpp \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ @@ -206,6 +207,7 @@ HEADERS += \ $$PWD/extension/com/atproto/sync/comatprotosyncsubscribereposex.h \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ + $$PWD/extension/oauth/oauthpushedauthorizationrequest.h \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 83f442ec..bd3823e0 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -4,6 +4,7 @@ #include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" #include "extension/well-known/wellknownoauthprotectedresource.h" #include "extension/well-known/wellknownoauthauthorizationserver.h" +#include "extension/oauth/oauthpushedauthorizationrequest.h" #include "atprotocol/lexicons_func_unknown.h" #include @@ -19,6 +20,7 @@ #include using AtProtocolInterface::ComAtprotoRepoDescribeRepo; +using AtProtocolInterface::OauthPushedAuthorizationRequest; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; @@ -252,38 +254,26 @@ void Authorization::par() if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) return; - QNetworkRequest request((QUrl(pushedAuthorizationRequestEndpoint()))); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QPointer alive = this; - HttpAccess *access = new HttpAccess(this); - HttpReply *reply = new HttpReply(access); - reply->setOperation(HttpReply::Operation::PostOperation); - reply->setRequest(request); - reply->setSendData(m_parPayload); - connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - if (alive && reply != nullptr) { - qDebug().noquote() << reply->error() << reply->url().toString(); - qDebug().noquote() << reply->contentType(); - qDebug().noquote() << reply->readAll(); - - if (reply->error() == HttpReply::Success) { - QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + AtProtocolInterface::AccountData account; + account.service = pushedAuthorizationRequestEndpoint(); - qDebug().noquote() - << "request_uri" << json_doc.object().value("request_uri").toString(); - authorization(json_doc.object().value("request_uri").toString()); + OauthPushedAuthorizationRequest *req = new OauthPushedAuthorizationRequest(this); + connect(req, &OauthPushedAuthorizationRequest::finished, this, [=](bool success) { + if (success) { + if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { + qDebug().noquote() << req->pushedAuthorizationResponse().request_uri; + authorization(req->pushedAuthorizationResponse().request_uri); } else { - // error - qDebug() << "PAR Error"; - emit finished(false); + emit errorOccured("Invalid Pushed Authorization Request", + "'request_uri' is empty."); } } else { - qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + emit errorOccured(req->errorCode(), req->errorMessage()); } - access->deleteLater(); + req->deleteLater(); }); - access->process(reply); + req->setAccount(account); + req->pushedAuthorizationRequest(m_parPayload); } void Authorization::authorization(const QString &request_uri) @@ -305,7 +295,11 @@ void Authorization::authorization(const QString &request_uri) qDebug().noquote() << "redirect" << url.toEncoded(); +#ifdef HAGOROMO_UNIT_TEST + emit madeRedirectUrl(url.toString()); +#else QDesktopServices::openUrl(url); +#endif } void Authorization::startRedirectServer() diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 5a283482..04db3491 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -45,6 +45,9 @@ class Authorization : public QObject void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); void finished(bool success); +#ifdef HAGOROMO_UNIT_TEST + void madeRedirectUrl(const QString &url); +#endif private: QByteArray generateRandomValues() const; diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json new file mode 100644 index 00000000..57b2cf31 --- /dev/null +++ b/scripts/lexicons/oauth.defs.json @@ -0,0 +1,17 @@ +{ + "lexicon": 1, + "id": "oauth.defs", + "defs": { + "pushedAuthorizationResponse": { + "type": "object", + "properties": { + "request_uri": { + "type": "string" + }, + "expires_in": { + "type": "integer" + } + } + } + } +} diff --git a/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template b/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template new file mode 100644 index 00000000..4b428b4e --- /dev/null +++ b/scripts/lexicons/oauth.pushedAuthorizationRequest.json.template @@ -0,0 +1,16 @@ +{ + "lexicon": 1, + "id": "oauth.pushedAuthorizationRequest", + "defs": { + "main": { + "type": "procedure", + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "oauth.defs#pushedAuthorizationResponse" + } + } + } + } +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index b5310980..cd4f4dd8 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -158,6 +158,31 @@ void oauth_test::test_oauth() == QString("http://localhost:%1/response/2/oauth/par").arg(m_listenPort)); QVERIFY(oauth.authorizationEndpoint() == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); + + // + { + QSignalSpy spy(&oauth, SIGNAL(madeRedirectUrl(const QString &))); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY2(arguments.at(0).toString() + == "https://bsky.social/oauth/" + "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%2Fhagoromo%" + "3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8080%252Ftech%" + "252Frelog%" + "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%3Aparams%" + "3Aoauth%" + "3Arequest_uri%3Areq-05650c01604941dc674f0af9cb032aca", + arguments.at(0).toString().toLocal8Bit()); + } + + // { + // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + // spy.wait(); + // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + // QList arguments = spy.takeFirst(); + // QVERIFY(arguments.at(0).toBool()); + // } } void oauth_test::test_jwt() From f459910ff79028bb677a05f50f3dc534c13b4ac5 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 5 Sep 2024 01:55:17 +0900 Subject: [PATCH 057/127] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=81=8B=E3=82=89?= =?UTF-8?q?=E3=83=AA=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=B5=81=E3=82=8C=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 1 + lib/atprotocol/lexicons_func.cpp | 1 + lib/tools/authorization.cpp | 131 +++++++++++++------- lib/tools/authorization.h | 15 ++- scripts/lexicons/wellKnown.defs.json | 3 + tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/authorize | 0 tests/oauth_test/tst_oauth_test.cpp | 95 +++++++++++--- 8 files changed, 185 insertions(+), 62 deletions(-) create mode 100644 tests/oauth_test/response/2/oauth/authorize diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index b15c6dfa..dc7abd9c 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2085,6 +2085,7 @@ struct ServerMetadata QList subject_types_supported; bool authorization_response_iss_parameter_supported = false; QString pushed_authorization_request_endpoint; + QString token_endpoint; bool require_pushed_authorization_requests = false; QList dpop_signing_alg_values_supported; bool require_request_uri_registration = false; diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 0617271f..f59f01fa 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2966,6 +2966,7 @@ void copyServerMetadata(const QJsonObject &src, WellKnownDefs::ServerMetadata &d src.value("authorization_response_iss_parameter_supported").toBool(); dest.pushed_authorization_request_endpoint = src.value("pushed_authorization_request_endpoint").toString(); + dest.token_endpoint = src.value("token_endpoint").toString(); dest.require_pushed_authorization_requests = src.value("require_pushed_authorization_requests").toBool(); for (const auto &value : src.value("dpop_signing_alg_values_supported").toArray()) { diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index bd3823e0..83f55266 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -24,7 +24,7 @@ using AtProtocolInterface::OauthPushedAuthorizationRequest; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; -Authorization::Authorization(QObject *parent) : QObject { parent } { } +Authorization::Authorization(QObject *parent) : QObject { parent }, m_redirectTimeout(300) { } void Authorization::reset() { @@ -36,6 +36,7 @@ void Authorization::reset() // server meta data m_pushedAuthorizationRequestEndpoint.clear(); m_authorizationEndpoint.clear(); + m_tokenEndopoint.clear(); m_scopesSupported.clear(); // m_redirectUri.clear(); @@ -130,6 +131,7 @@ void Authorization::requestOauthAuthorizationServer() setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); setAuthorizationEndpoint(server->serverMetadata().authorization_endpoint); + setTokenEndopoint(server->serverMetadata().token_endpoint); m_scopesSupported = server->serverMetadata().scopes_supported; // next step @@ -204,6 +206,16 @@ bool Authorization::validateServerMetadata( return ret; } +QByteArray Authorization::state() const +{ + return m_state; +} + +QString Authorization::listenPort() const +{ + return m_listenPort; +} + void Authorization::makeClientId() { QString port; @@ -261,7 +273,8 @@ void Authorization::par() connect(req, &OauthPushedAuthorizationRequest::finished, this, [=](bool success) { if (success) { if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { - qDebug().noquote() << req->pushedAuthorizationResponse().request_uri; + // next step + startRedirectServer(); authorization(req->pushedAuthorizationResponse().request_uri); } else { emit errorOccured("Invalid Pushed Authorization Request", @@ -278,11 +291,12 @@ void Authorization::par() void Authorization::authorization(const QString &request_uri) { - if (request_uri.isEmpty()) + if (request_uri.isEmpty() || authorizationEndpoint().isEmpty() || m_listenPort.isEmpty()) return; - QString authorization_endpoint = "https://bsky.social/oauth/authorize"; - QString redirect_uri = "http://127.0.0.1:8080/tech/relog/hagoromo/oauth-callback"; + QString authorization_endpoint = authorizationEndpoint(); + QString redirect_uri = + QString("http://127.0.0.1:%1/tech/relog/hagoromo/oauth-callback").arg(m_listenPort); QString client_id = "http://localhost/tech/relog/" "hagoromo?redirect_uri=" + simplyEncode(redirect_uri); @@ -296,7 +310,7 @@ void Authorization::authorization(const QString &request_uri) qDebug().noquote() << "redirect" << url.toEncoded(); #ifdef HAGOROMO_UNIT_TEST - emit madeRedirectUrl(url.toString()); + emit madeRequestUrl(url.toString()); #else QDesktopServices::openUrl(url); #endif @@ -308,6 +322,8 @@ void Authorization::startRedirectServer() connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { + qDebug().noquote() << "receive by startRedirectServer"; + if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") && request.query().hasQueryItem("code")) { // authorize @@ -315,7 +331,7 @@ void Authorization::startRedirectServer() result = (state.toUtf8() == m_state); if (result) { m_code = request.query().queryItemValue("code").toUtf8(); - // requestToken(); + requestToken(); } else { qDebug().noquote() << "Unknown state in authorization redirect :" << state; emit finished(false); @@ -337,11 +353,13 @@ void Authorization::startRedirectServer() }); connect(server, &SimpleHttpServer::timeout, this, [=]() { // token取得に進んでたらfinishedは発火しない - qDebug().noquote() << "Authorization timeout"; - emit finished(false); + if (m_code.isEmpty()) { + qDebug().noquote() << "Authorization timeout"; + emit finished(false); + } server->deleteLater(); }); - server->setTimeout(50); // 300); + server->setTimeout(redirectTimeout()); quint16 port = server->listen(QHostAddress::LocalHost, 0); m_listenPort = QString::number(port); @@ -361,45 +379,49 @@ void Authorization::makeRequestTokenPayload() m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); } -bool Authorization::requestToken() +void Authorization::requestToken() { + if (tokenEndopoint().isEmpty()) + return; - QString endpoint = "https://bsky.social/oauth/token"; + QString endpoint = tokenEndopoint(); QNetworkRequest request((QUrl(endpoint))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader(QByteArray("DPoP"), QByteArray("")); - QPointer alive = this; - HttpAccess *access = new HttpAccess(this); - HttpReply *reply = new HttpReply(access); - reply->setOperation(HttpReply::Operation::PostOperation); - reply->setRequest(request); - // reply->setSendData(m_parPayload); - connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - if (alive && reply != nullptr) { - qDebug().noquote() << reply->error() << reply->url().toString(); - - qDebug().noquote() << reply->contentType(); - qDebug().noquote() << reply->readAll(); - if (reply->error() == HttpReply::Success) { - QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); - - qDebug().noquote() - << "request_uri" << json_doc.object().value("request_uri").toString(); - } else { - // error - qDebug() << "Request token Error"; - } - } else { - qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; - } - access->deleteLater(); - }); - qDebug() << "request token 1"; - access->process(reply); - qDebug() << "request token 2"; - - return true; + emit finished(true); // temporary + + // QPointer alive = this; + // HttpAccess *access = new HttpAccess(this); + // HttpReply *reply = new HttpReply(access); + // reply->setOperation(HttpReply::Operation::PostOperation); + // reply->setRequest(request); + // // reply->setSendData(m_parPayload); + // connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { + // if (alive && reply != nullptr) { + // qDebug().noquote() << reply->error() << reply->url().toString(); + + // qDebug().noquote() << reply->contentType(); + // qDebug().noquote() << reply->readAll(); + // if (reply->error() == HttpReply::Success) { + // QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + // qDebug().noquote() + // << "request_uri" << json_doc.object().value("request_uri").toString(); + // } else { + // // error + // qDebug() << "Request token Error"; + // } + // } else { + // qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; + // } + // access->deleteLater(); + // }); + // qDebug() << "request token 1"; + // access->process(reply); + // qDebug() << "request token 2"; + + return; } QByteArray Authorization::generateRandomValues() const @@ -478,6 +500,29 @@ void Authorization::setAuthorizationEndpoint(const QString &newAuthorizationEndp emit authorizationEndpointChanged(); } +QString Authorization::tokenEndopoint() const +{ + return m_tokenEndopoint; +} + +void Authorization::setTokenEndopoint(const QString &newTokenEndopoint) +{ + if (m_tokenEndopoint == newTokenEndopoint) + return; + m_tokenEndopoint = newTokenEndopoint; + emit tokenEndopointChanged(); +} + +int Authorization::redirectTimeout() const +{ + return m_redirectTimeout; +} + +void Authorization::setRedirectTimeout(int newRedirectTimeout) +{ + m_redirectTimeout = newRedirectTimeout; +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 04db3491..c8c07bde 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -22,7 +22,7 @@ class Authorization : public QObject void startRedirectServer(); void makeRequestTokenPayload(); - bool requestToken(); + void requestToken(); QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); @@ -33,20 +33,29 @@ class Authorization : public QObject setPushedAuthorizationRequestEndpoint(const QString &newPushedAuthorizationRequestEndpoint); QString authorizationEndpoint() const; void setAuthorizationEndpoint(const QString &newAuthorizationEndpoint); + QString tokenEndopoint() const; + void setTokenEndopoint(const QString &newTokenEndopoint); + int redirectTimeout() const; + void setRedirectTimeout(int newRedirectTimeout); QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; + QString listenPort() const; + + QByteArray state() const; + signals: void errorOccured(const QString &code, const QString &message); void serviceEndpointChanged(); void authorizationServerChanged(); void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); + void tokenEndopointChanged(); void finished(bool success); #ifdef HAGOROMO_UNIT_TEST - void madeRedirectUrl(const QString &url); + void madeRequestUrl(const QString &url); #endif private: @@ -68,6 +77,7 @@ class Authorization : public QObject // server meta data QString m_pushedAuthorizationRequestEndpoint; QString m_authorizationEndpoint; + QString m_tokenEndopoint; QStringList m_scopesSupported; // QString m_redirectUri; @@ -82,6 +92,7 @@ class Authorization : public QObject QByteArray m_requestTokenPayload; QString m_listenPort; + int m_redirectTimeout; }; #endif // AUTHORIZATION_H diff --git a/scripts/lexicons/wellKnown.defs.json b/scripts/lexicons/wellKnown.defs.json index 248ed0b5..a0d8f875 100644 --- a/scripts/lexicons/wellKnown.defs.json +++ b/scripts/lexicons/wellKnown.defs.json @@ -85,6 +85,9 @@ "pushed_authorization_request_endpoint": { "type": "string" }, + "token_endpoint": { + "type": "string" + }, "require_pushed_authorization_requests": { "type": "boolean" }, diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index 8da511bf..cd94807d 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -4,5 +4,6 @@ response/2/xrpc/com.atproto.repo.describeRepo response/2/.well-known/oauth-authorization-server response/2/oauth/par + response/2/oauth/authorize diff --git a/tests/oauth_test/response/2/oauth/authorize b/tests/oauth_test/response/2/oauth/authorize new file mode 100644 index 00000000..e69de29b diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index cd4f4dd8..5d27654b 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -12,6 +12,7 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "tools/es256.h" +#include "http/httpaccess.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -35,6 +36,7 @@ private slots: SimpleHttpServer m_server; quint16 m_listenPort; + void test_get(const QString &url, const QByteArray &except_data); void verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey); }; @@ -51,6 +53,7 @@ oauth_test::oauth_test() qDebug().noquote() << request.url(); QString path = SimpleHttpServer::convertResoucePath(request.url()); qDebug().noquote() << " res path =" << path; + if (!QFile::exists(path)) { result = false; } else { @@ -129,6 +132,8 @@ void oauth_test::test_oauth() // response/2 : entry-way Authorization oauth; + oauth.setRedirectTimeout(3); + QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); QString handle = "ioriayane.relog.tech"; @@ -160,29 +165,65 @@ void oauth_test::test_oauth() == QString("http://localhost:%1/response/2/oauth/authorize").arg(m_listenPort)); // + QString request_url; { - QSignalSpy spy(&oauth, SIGNAL(madeRedirectUrl(const QString &))); + QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); - QVERIFY2(arguments.at(0).toString() - == "https://bsky.social/oauth/" - "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%2Fhagoromo%" - "3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A8080%252Ftech%" - "252Frelog%" - "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%3Aparams%" - "3Aoauth%" - "3Arequest_uri%3Areq-05650c01604941dc674f0af9cb032aca", - arguments.at(0).toString().toLocal8Bit()); + request_url = arguments.at(0).toString(); + QVERIFY2( + request_url + == (QStringLiteral("http://localhost:") + QString::number(m_listenPort) + + QStringLiteral( + "/response/2/oauth/" + "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%" + "2Fhagoromo%3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A") + + oauth.listenPort() + + QStringLiteral( + "%252Ftech%252Frelog%" + "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%" + "3Aparams%3Aoauth%3Arequest_uri%3Areq-" + "05650c01604941dc674f0af9cb032aca")), + request_url.toLocal8Bit()); } - // { - // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // spy.wait(); - // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - // QList arguments = spy.takeFirst(); - // QVERIFY(arguments.at(0).toBool()); - // } + { + // ブラウザで認証ができないのでタイムアウトしてくるのを確認 + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(!arguments.at(0).toBool()); + } + + // ブラウザに認証しにいくURLからリダイレクトURLを取り出す + QUrl redirect_url; + { + QUrl request(request_url); + QUrlQuery request_query(request.query()); + QUrl client_id(request_query.queryItemValue("client_id", QUrl::FullyDecoded)); + QUrlQuery client_query(client_id.query()); + redirect_url = client_query.queryItemValue("redirect_uri", QUrl::FullyDecoded); + QUrlQuery redirect_query; + redirect_query.addQueryItem("iss", "iss-hogehoge"); + redirect_query.addQueryItem("state", oauth.state()); + redirect_query.addQueryItem("code", "code-hogehoge"); + redirect_url.setQuery(redirect_query); + qDebug().noquote() << "extract to " << redirect_url; + } + // 認証終了したていで続き + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.startRedirectServer(); + redirect_url.setPort(oauth.listenPort().toInt()); + qDebug().noquote() << "port updated " << redirect_url; + test_get(redirect_url.toString(), QByteArray()); // ブラウザへのアクセスを模擬 + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } } void oauth_test::test_jwt() @@ -228,6 +269,26 @@ void oauth_test::test_es256() } } +void oauth_test::test_get(const QString &url, const QByteArray &except_data) +{ + QNetworkRequest request((QUrl(url))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, [=](QNetworkReply *reply) { + qDebug() << "test_get reply" << reply->error() << reply->url(); + + QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); + + QVERIFY(reply->error() == QNetworkReply::NoError); + QVERIFY2(reply->readAll() == except_data, json_doc.toJson()); + + reply->deleteLater(); + manager->deleteLater(); + }); + manager->get(request); +} + void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) { EC_KEY *ec_key = nullptr; From e7e6d0050da7e13d70a78c67b9ae459332cb8c26 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 6 Sep 2024 01:45:43 +0900 Subject: [PATCH 058/127] =?UTF-8?q?token=E3=81=AE=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=AE=E3=82=B9=E3=83=86=E3=83=83=E3=83=97=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 7 ++ lib/atprotocol/lexicons_func.cpp | 9 ++ lib/atprotocol/lexicons_func.h | 1 + lib/extension/oauth/oauthrequesttoken.cpp | 33 +++++++ lib/extension/oauth/oauthrequesttoken.h | 25 ++++++ lib/lib.pro | 2 + lib/tools/authorization.cpp | 105 +++++++++++++--------- lib/tools/authorization.h | 4 + lib/tools/jsonwebtoken.cpp | 5 +- lib/tools/jsonwebtoken.h | 2 +- scripts/lexicons/oauth.defs.json | 17 ++++ scripts/lexicons/oauth.requestToken.json | 16 ++++ tests/oauth_test/oauth_test.qrc | 1 + tests/oauth_test/response/2/oauth/token | 6 ++ tests/oauth_test/tst_oauth_test.cpp | 19 +++- 15 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 lib/extension/oauth/oauthrequesttoken.cpp create mode 100644 lib/extension/oauth/oauthrequesttoken.h create mode 100644 scripts/lexicons/oauth.requestToken.json create mode 100644 tests/oauth_test/response/2/oauth/token diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index dc7abd9c..b1b6bf7e 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2061,6 +2061,13 @@ struct PushedAuthorizationResponse QString request_uri; int expires_in = 0; }; +struct TokenResponse +{ + QString access_token; + QString token_type; + QString refresh_token; + int expires_in = 0; +}; } // wellKnown.defs diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index f59f01fa..da49ccf3 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2917,6 +2917,15 @@ void copyPushedAuthorizationResponse(const QJsonObject &src, dest.expires_in = src.value("expires_in").toInt(); } } +void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest) +{ + if (!src.isEmpty()) { + dest.access_token = src.value("access_token").toString(); + dest.token_type = src.value("token_type").toString(); + dest.refresh_token = src.value("refresh_token").toString(); + dest.expires_in = src.value("expires_in").toInt(); + } +} } // wellKnown.defs namespace WellKnownDefs { diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index f9831261..d1542ee5 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -416,6 +416,7 @@ void copyPlcAuditLog(const QJsonArray &src, DirectoryPlcDefs::PlcAuditLog &dest) namespace OauthDefs { void copyPushedAuthorizationResponse(const QJsonObject &src, OauthDefs::PushedAuthorizationResponse &dest); +void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest); } // wellKnown.defs namespace WellKnownDefs { diff --git a/lib/extension/oauth/oauthrequesttoken.cpp b/lib/extension/oauth/oauthrequesttoken.cpp new file mode 100644 index 00000000..2c72874f --- /dev/null +++ b/lib/extension/oauth/oauthrequesttoken.cpp @@ -0,0 +1,33 @@ +#include "oauthrequesttoken.h" +#include "atprotocol/lexicons_func.h" + +#include +#include + +namespace AtProtocolInterface { + +OauthRequestToken::OauthRequestToken(QObject *parent) : AccessAtProtocol { parent } { } + +void OauthRequestToken::requestToken(const QByteArray &payload) +{ + post(QString(""), payload, false); +} + +const AtProtocolType::OauthDefs::TokenResponse &OauthRequestToken::tokenResponse() const +{ + return m_tokenResponse; +} + +bool OauthRequestToken::parseJson(bool success, const QString reply_json) +{ + QJsonDocument json_doc = QJsonDocument::fromJson(reply_json.toUtf8()); + if (json_doc.isEmpty()) { + success = false; + } else { + AtProtocolType::OauthDefs::copyTokenResponse(json_doc.object(), m_tokenResponse); + } + + return success; +} + +} diff --git a/lib/extension/oauth/oauthrequesttoken.h b/lib/extension/oauth/oauthrequesttoken.h new file mode 100644 index 00000000..400df02f --- /dev/null +++ b/lib/extension/oauth/oauthrequesttoken.h @@ -0,0 +1,25 @@ +#ifndef OAUTHREQUESTTOKEN_H +#define OAUTHREQUESTTOKEN_H + +#include "atprotocol/accessatprotocol.h" + +namespace AtProtocolInterface { + +class OauthRequestToken : public AccessAtProtocol +{ +public: + explicit OauthRequestToken(QObject *parent = nullptr); + + void requestToken(const QByteArray &payload); + + const AtProtocolType::OauthDefs::TokenResponse &tokenResponse() const; + +private: + virtual bool parseJson(bool success, const QString reply_json); + + AtProtocolType::OauthDefs::TokenResponse m_tokenResponse; +}; + +} + +#endif // OAUTHREQUESTTOKEN_H diff --git a/lib/lib.pro b/lib/lib.pro index 9e23a137..b41cd4cc 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -93,6 +93,7 @@ SOURCES += \ $$PWD/extension/directory/plc/directoryplc.cpp \ $$PWD/extension/directory/plc/directoryplclogaudit.cpp \ $$PWD/extension/oauth/oauthpushedauthorizationrequest.cpp \ + $$PWD/extension/oauth/oauthrequesttoken.cpp \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.cpp \ $$PWD/extension/well-known/wellknownoauthprotectedresource.cpp \ $$PWD/http/httpaccess.cpp \ @@ -208,6 +209,7 @@ HEADERS += \ $$PWD/extension/directory/plc/directoryplc.h \ $$PWD/extension/directory/plc/directoryplclogaudit.h \ $$PWD/extension/oauth/oauthpushedauthorizationrequest.h \ + $$PWD/extension/oauth/oauthrequesttoken.h \ $$PWD/extension/well-known/wellknownoauthauthorizationserver.h \ $$PWD/extension/well-known/wellknownoauthprotectedresource.h \ $$PWD/http/httpaccess.h \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 83f55266..f753e033 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -5,7 +5,9 @@ #include "extension/well-known/wellknownoauthprotectedresource.h" #include "extension/well-known/wellknownoauthauthorizationserver.h" #include "extension/oauth/oauthpushedauthorizationrequest.h" +#include "extension/oauth/oauthrequesttoken.h" #include "atprotocol/lexicons_func_unknown.h" +#include "tools/jsonwebtoken.h" #include #include @@ -21,6 +23,7 @@ using AtProtocolInterface::ComAtprotoRepoDescribeRepo; using AtProtocolInterface::OauthPushedAuthorizationRequest; +using AtProtocolInterface::OauthRequestToken; using AtProtocolInterface::WellKnownOauthAuthorizationServer; using AtProtocolInterface::WellKnownOauthProtectedResource; @@ -49,6 +52,7 @@ void Authorization::reset() // request token m_code.clear(); m_requestTokenPayload.clear(); + m_token = AtProtocolType::OauthDefs::TokenResponse(); // m_listenPort.clear(); } @@ -60,6 +64,7 @@ void Authorization::start(const QString &pds, const QString &handle) AtProtocolInterface::AccountData account; account.service = pds; + m_handle = handle; ComAtprotoRepoDescribeRepo *repo = new ComAtprotoRepoDescribeRepo(this); connect(repo, &ComAtprotoRepoDescribeRepo::finished, this, [=](bool success) { @@ -74,9 +79,11 @@ void Authorization::start(const QString &pds, const QString &handle) } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); + emit finished(false); } } else { emit errorOccured(repo->errorCode(), repo->errorMessage()); + emit finished(false); } repo->deleteLater(); }); @@ -103,9 +110,11 @@ void Authorization::requestOauthProtectedResource() } else { emit errorOccured("Invalid oauth-protected-resource", "authorization_servers is empty."); + emit finished(false); } } else { emit errorOccured(resource->errorCode(), resource->errorMessage()); + emit finished(false); } resource->deleteLater(); }); @@ -140,9 +149,11 @@ void Authorization::requestOauthAuthorizationServer() } else { qDebug().noquote() << error_message; emit errorOccured("Invalid oauth-authorization-server", error_message); + emit finished(false); } } else { emit errorOccured(server->errorCode(), server->errorMessage()); + emit finished(false); } server->deleteLater(); }); @@ -279,9 +290,11 @@ void Authorization::par() } else { emit errorOccured("Invalid Pushed Authorization Request", "'request_uri' is empty."); + emit finished(false); } } else { emit errorOccured(req->errorCode(), req->errorMessage()); + emit finished(false); } req->deleteLater(); }); @@ -346,10 +359,7 @@ void Authorization::startRedirectServer() mime_type = "text/html"; // delete after 10 sec. - QTimer::singleShot(10 * 1000, [=]() { - emit finished(true); // temporary - server->deleteLater(); - }); + QTimer::singleShot(10 * 1000, [=]() { server->deleteLater(); }); }); connect(server, &SimpleHttpServer::timeout, this, [=]() { // token取得に進んでたらfinishedは発火しない @@ -384,44 +394,40 @@ void Authorization::requestToken() if (tokenEndopoint().isEmpty()) return; - QString endpoint = tokenEndopoint(); - QNetworkRequest request((QUrl(endpoint))); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader(QByteArray("DPoP"), QByteArray("")); - - emit finished(true); // temporary - - // QPointer alive = this; - // HttpAccess *access = new HttpAccess(this); - // HttpReply *reply = new HttpReply(access); - // reply->setOperation(HttpReply::Operation::PostOperation); - // reply->setRequest(request); - // // reply->setSendData(m_parPayload); - // connect(access, &HttpAccess::finished, this, [access, alive, this](HttpReply *reply) { - // if (alive && reply != nullptr) { - // qDebug().noquote() << reply->error() << reply->url().toString(); - - // qDebug().noquote() << reply->contentType(); - // qDebug().noquote() << reply->readAll(); - // if (reply->error() == HttpReply::Success) { - // QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); - - // qDebug().noquote() - // << "request_uri" << json_doc.object().value("request_uri").toString(); - // } else { - // // error - // qDebug() << "Request token Error"; - // } - // } else { - // qDebug().noquote() << "Parent is deleted or reply null !!!!!!!!!!"; - // } - // access->deleteLater(); - // }); - // qDebug() << "request token 1"; - // access->process(reply); - // qDebug() << "request token 2"; - - return; + makeRequestTokenPayload(); + + AtProtocolInterface::AccountData account; + account.service = tokenEndopoint(); + + OauthRequestToken *req = new OauthRequestToken(this); + connect(req, &OauthRequestToken::finished, this, [=](bool success) { + bool ret = false; + if (success) { + if (!req->tokenResponse().access_token.isEmpty() + && !req->tokenResponse().refresh_token.isEmpty() + && req->tokenResponse().token_type.toLower() == "dpop") { + + setToken(req->tokenResponse()); + + qDebug().noquote() << "--- Success oauth ----"; + qDebug().noquote() << " handle :" << m_handle; + qDebug().noquote() << " access :" << m_token.access_token; + qDebug().noquote() << " refresh:" << m_token.refresh_token; + + // finish oauth sequence + ret = true; + } else { + emit errorOccured("Invalid token response", req->replyJson()); + } + } else { + emit errorOccured(req->errorCode(), req->errorMessage()); + } + emit finished(ret); + req->deleteLater(); + }); + req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->setAccount(account); + req->requestToken(m_requestTokenPayload); } QByteArray Authorization::generateRandomValues() const @@ -523,6 +529,21 @@ void Authorization::setRedirectTimeout(int newRedirectTimeout) m_redirectTimeout = newRedirectTimeout; } +AtProtocolType::OauthDefs::TokenResponse Authorization::token() const +{ + return m_token; +} + +void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken) +{ + if (m_token.access_token == newToken.access_token && m_token.expires_in == newToken.expires_in + && m_token.refresh_token == newToken.refresh_token + && m_token.token_type == newToken.token_type) + return; + m_token = newToken; + emit tokenChanged(); +} + QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index c8c07bde..a6186caf 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -37,6 +37,8 @@ class Authorization : public QObject void setTokenEndopoint(const QString &newTokenEndopoint); int redirectTimeout() const; void setRedirectTimeout(int newRedirectTimeout); + AtProtocolType::OauthDefs::TokenResponse token() const; + void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); QByteArray codeVerifier() const; QByteArray codeChallenge() const; @@ -53,6 +55,7 @@ class Authorization : public QObject void pushedAuthorizationRequestEndpointChanged(); void authorizationEndpointChanged(); void tokenEndopointChanged(); + void tokenChanged(); void finished(bool success); #ifdef HAGOROMO_UNIT_TEST void madeRequestUrl(const QString &url); @@ -90,6 +93,7 @@ class Authorization : public QObject // request token QByteArray m_code; QByteArray m_requestTokenPayload; + AtProtocolType::OauthDefs::TokenResponse m_token; QString m_listenPort; int m_redirectTimeout; diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 8cf7b704..3adf1518 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -32,7 +32,7 @@ inline QJsonObject createJwk() return jwk; } -QByteArray JsonWebToken::generate(const QString &endpoint) +QByteArray JsonWebToken::generate(const QString &handle) { // ヘッダー QJsonObject header; @@ -44,8 +44,7 @@ QByteArray JsonWebToken::generate(const QString &endpoint) // ペイロード QJsonObject payload; - payload["sub"] = "1234567890"; // ユーザーIDなど - payload["name"] = "John Doe"; + payload["sub"] = handle; // ユーザーIDなど payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); QByteArray payloadBase64 = base64UrlEncode(payloadJson); diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index ff43ee22..b1db15b5 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,7 @@ class JsonWebToken { public: - static QByteArray generate(const QString &endpoint); + static QByteArray generate(const QString &handle); }; #endif // JSONWEBTOKEN_H diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json index 57b2cf31..124d0ace 100644 --- a/scripts/lexicons/oauth.defs.json +++ b/scripts/lexicons/oauth.defs.json @@ -12,6 +12,23 @@ "type": "integer" } } + }, + "tokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + } + } } } } diff --git a/scripts/lexicons/oauth.requestToken.json b/scripts/lexicons/oauth.requestToken.json new file mode 100644 index 00000000..babf70de --- /dev/null +++ b/scripts/lexicons/oauth.requestToken.json @@ -0,0 +1,16 @@ +{ + "lexicon": 1, + "id": "oauth.requestToken", + "defs": { + "main": { + "type": "procedure", + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "oauth.defs#tokenResponse" + } + } + } + } +} diff --git a/tests/oauth_test/oauth_test.qrc b/tests/oauth_test/oauth_test.qrc index cd94807d..d5427b01 100644 --- a/tests/oauth_test/oauth_test.qrc +++ b/tests/oauth_test/oauth_test.qrc @@ -5,5 +5,6 @@ response/2/.well-known/oauth-authorization-server response/2/oauth/par response/2/oauth/authorize + response/2/oauth/token diff --git a/tests/oauth_test/response/2/oauth/token b/tests/oauth_test/response/2/oauth/token new file mode 100644 index 00000000..a84748fd --- /dev/null +++ b/tests/oauth_test/response/2/oauth/token @@ -0,0 +1,6 @@ +{ + "access_token": "access token", + "token_type": "DPoP", + "expires_in": 2677, + "refresh_token": "refresh token" +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 5d27654b..947f0634 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -12,7 +12,6 @@ #include "tools/authorization.h" #include "tools/jsonwebtoken.h" #include "tools/es256.h" -#include "http/httpaccess.h" #include "http/simplehttpserver.h" class oauth_test : public QObject @@ -54,6 +53,12 @@ oauth_test::oauth_test() QString path = SimpleHttpServer::convertResoucePath(request.url()); qDebug().noquote() << " res path =" << path; + if (path.endsWith("/oauth/token")) { + qDebug().noquote() << "Verify jwt"; + QVERIFY(request.headers().contains("DPoP")); + verify_jwt(request.headers().value("DPoP").toByteArray(), nullptr); + } + if (!QFile::exists(path)) { result = false; } else { @@ -214,16 +219,22 @@ void oauth_test::test_oauth() } // 認証終了したていで続き { - QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + QSignalSpy spy(&oauth, SIGNAL(tokenChanged())); oauth.startRedirectServer(); redirect_url.setPort(oauth.listenPort().toInt()); qDebug().noquote() << "port updated " << redirect_url; test_get(redirect_url.toString(), QByteArray()); // ブラウザへのアクセスを模擬 spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - QList arguments = spy.takeFirst(); - QVERIFY(arguments.at(0).toBool()); } + + QVERIFY2(oauth.token().access_token == "access token", + oauth.token().access_token.toLocal8Bit()); + QVERIFY2(oauth.token().token_type == "DPoP", oauth.token().token_type.toLocal8Bit()); + QVERIFY2(oauth.token().refresh_token == "refresh token", + oauth.token().refresh_token.toLocal8Bit()); + QVERIFY2(oauth.token().expires_in == 2677, + QString::number(oauth.token().expires_in).toLocal8Bit()); } void oauth_test::test_jwt() From 286dc0f936fc14c0073d6bafc576603aa16b2d04 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 6 Sep 2024 03:13:29 +0900 Subject: [PATCH 059/127] =?UTF-8?q?content=20type=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 10 ++++++++-- lib/atprotocol/accessatprotocol.h | 3 +++ lib/tools/authorization.cpp | 7 +++++-- tests/oauth_test/tst_oauth_test.cpp | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index a7ed7064..fb88906d 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -107,7 +107,8 @@ QString AtProtocolAccount::refreshJwt() const return m_account.refreshJwt; } -AccessAtProtocol::AccessAtProtocol(QObject *parent) : AtProtocolAccount { parent } +AccessAtProtocol::AccessAtProtocol(QObject *parent) + : AtProtocolAccount { parent }, m_contentType("application/json") { qDebug().noquote() << LOG_DATETIME << "AccessAtProtocol::AccessAtProtocol()" << this; if (m_manager == nullptr) { @@ -204,7 +205,7 @@ void AccessAtProtocol::post(const QString &endpoint, const QByteArray &json, } QNetworkRequest request(url); request.setRawHeader(QByteArray("Cache-Control"), QByteArray("no-cache")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::ContentTypeHeader, m_contentType); if (with_auth_header) { if (accessJwt().isEmpty()) { qCritical() << LOG_DATETIME << "AccessAtProtocol::post()" @@ -415,6 +416,11 @@ void AccessAtProtocol::setAdditionalRawHeader(QNetworkRequest &request) } } +void AccessAtProtocol::setContentType(const QString &newContentType) +{ + m_contentType = newContentType; +} + QString AccessAtProtocol::cursor() const { return m_cursor; diff --git a/lib/atprotocol/accessatprotocol.h b/lib/atprotocol/accessatprotocol.h index 7c248ff6..48f80a5c 100644 --- a/lib/atprotocol/accessatprotocol.h +++ b/lib/atprotocol/accessatprotocol.h @@ -88,6 +88,8 @@ class AccessAtProtocol : public AtProtocolAccount void setCursor(const QString &newCursor); void appendRawHeader(const QString &name, const QString &value); + void setContentType(const QString &newContentType); + signals: void finished(bool success); @@ -121,6 +123,7 @@ public slots: QString m_errorMessage; QString m_cursor; + QString m_contentType; QHash m_additionalRawHeaders; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index f753e033..e0ed2a49 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -257,7 +257,7 @@ void Authorization::makeParPayload() m_state = QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QString login_hint = "ioriayane2.bsky.social"; + QString login_hint = m_handle; QUrlQuery query; query.addQueryItem("response_type", "code"); @@ -298,6 +298,7 @@ void Authorization::par() } req->deleteLater(); }); + req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->pushedAuthorizationRequest(m_parPayload); } @@ -413,7 +414,8 @@ void Authorization::requestToken() qDebug().noquote() << " handle :" << m_handle; qDebug().noquote() << " access :" << m_token.access_token; qDebug().noquote() << " refresh:" << m_token.refresh_token; - + qDebug().noquote() << req->replyJson(); + qDebug().noquote() << "----------------------"; // finish oauth sequence ret = true; } else { @@ -426,6 +428,7 @@ void Authorization::requestToken() req->deleteLater(); }); req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->requestToken(m_requestTokenPayload); } diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 947f0634..107127be 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -123,8 +123,8 @@ void oauth_test::test_oauth_server() // { // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // oauth.startRedirectServer(); - // spy.wait(60 * 1000); + // oauth.start("https://bsky.social", "ioriayane2.bsky.social"); + // spy.wait(300 * 1000); // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); // QList arguments = spy.takeFirst(); // QVERIFY(arguments.at(0).toBool()); From 9fabd317156d9197f648ddae4d3385605994171d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 7 Sep 2024 02:09:00 +0900 Subject: [PATCH 060/127] =?UTF-8?q?token=E5=8F=96=E5=BE=97=E3=82=B9?= =?UTF-8?q?=E3=83=86=E3=83=83=E3=83=97=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/accessatprotocol.cpp | 15 +++++++--- lib/atprotocol/accessatprotocol.h | 2 ++ lib/tools/authorization.cpp | 40 ++++++++++++++++--------- lib/tools/authorization.h | 5 +++- lib/tools/jsonwebtoken.cpp | 16 ++++++++-- lib/tools/jsonwebtoken.h | 3 +- tests/oauth_test/tst_oauth_test.cpp | 45 +++++++++++++++++++---------- 7 files changed, 88 insertions(+), 38 deletions(-) diff --git a/lib/atprotocol/accessatprotocol.cpp b/lib/atprotocol/accessatprotocol.cpp index fb88906d..32b15d7e 100644 --- a/lib/atprotocol/accessatprotocol.cpp +++ b/lib/atprotocol/accessatprotocol.cpp @@ -306,19 +306,21 @@ bool AccessAtProtocol::checkReply(HttpReply *reply) m_errorCode.clear(); m_errorMessage.clear(); -#ifdef QT_DEBUG + QByteArray header_key; for (const auto &header : reply->rawHeaderPairs()) { - if (header.first.toLower().startsWith("ratelimit-")) { - if (header.first.toLower() == "ratelimit-reset") { + header_key = header.first.toLower(); + if (header_key.startsWith("ratelimit-")) { + if (header_key == "ratelimit-reset") { qDebug().noquote() << LOG_DATETIME << header.first << QDateTime::fromSecsSinceEpoch(header.second.toInt()) .toString("yyyy/MM/dd hh:mm:ss"); } else { qDebug().noquote() << LOG_DATETIME << header.first << header.second; } + } else if (header_key == "dpop-nonce") { + m_dPopNonce = header.second; } } -#endif QJsonDocument json_doc = QJsonDocument::fromJson(m_replyJson.toUtf8()); if (reply->error() != HttpReply::Success) { @@ -416,6 +418,11 @@ void AccessAtProtocol::setAdditionalRawHeader(QNetworkRequest &request) } } +QString AccessAtProtocol::dPopNonce() const +{ + return m_dPopNonce; +} + void AccessAtProtocol::setContentType(const QString &newContentType) { m_contentType = newContentType; diff --git a/lib/atprotocol/accessatprotocol.h b/lib/atprotocol/accessatprotocol.h index 48f80a5c..4ca67880 100644 --- a/lib/atprotocol/accessatprotocol.h +++ b/lib/atprotocol/accessatprotocol.h @@ -89,6 +89,7 @@ class AccessAtProtocol : public AtProtocolAccount void appendRawHeader(const QString &name, const QString &value); void setContentType(const QString &newContentType); + QString dPopNonce() const; signals: void finished(bool success); @@ -125,6 +126,7 @@ public slots: QString m_contentType; QHash m_additionalRawHeaders; + QString m_dPopNonce; }; } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index e0ed2a49..314f6a14 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -62,6 +62,8 @@ void Authorization::start(const QString &pds, const QString &handle) if (pds.isEmpty() || handle.isEmpty()) return; + startRedirectServer(); + AtProtocolInterface::AccountData account; account.service = pds; m_handle = handle; @@ -243,10 +245,10 @@ void Authorization::makeClientId() void Authorization::makeCodeChallenge() { - m_codeChallenge = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals); - m_codeVerifier = - QCryptographicHash::hash(m_codeChallenge, QCryptographicHash::Sha256) + m_codeVerifier = generateRandomValues().toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + m_codeChallenge = + QCryptographicHash::hash(m_codeVerifier, QCryptographicHash::Sha256) .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } @@ -285,7 +287,7 @@ void Authorization::par() if (success) { if (!req->pushedAuthorizationResponse().request_uri.isEmpty()) { // next step - startRedirectServer(); + setDPopNonce(req->dPopNonce()); authorization(req->pushedAuthorizationResponse().request_uri); } else { emit errorOccured("Invalid Pushed Authorization Request", @@ -309,15 +311,10 @@ void Authorization::authorization(const QString &request_uri) return; QString authorization_endpoint = authorizationEndpoint(); - QString redirect_uri = - QString("http://127.0.0.1:%1/tech/relog/hagoromo/oauth-callback").arg(m_listenPort); - QString client_id = "http://localhost/tech/relog/" - "hagoromo?redirect_uri=" - + simplyEncode(redirect_uri); QUrl url(authorization_endpoint); QUrlQuery query; - query.addQueryItem("client_id", simplyEncode(client_id)); + query.addQueryItem("client_id", simplyEncode(m_clientId)); query.addQueryItem("request_uri", simplyEncode(request_uri)); url.setQuery(query); @@ -337,6 +334,10 @@ void Authorization::startRedirectServer() [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { qDebug().noquote() << "receive by startRedirectServer"; + qDebug().noquote() << " " << request.url().toString(); + qDebug().noquote() << " " << request.url().path(); + + if (request.url().path() == "/tech/relog/hagoromo/oauth-callback") { } if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") && request.query().hasQueryItem("code")) { @@ -408,6 +409,9 @@ void Authorization::requestToken() && !req->tokenResponse().refresh_token.isEmpty() && req->tokenResponse().token_type.toLower() == "dpop") { + if (!req->dPopNonce().isEmpty()) { + setDPopNonce(req->dPopNonce()); + } setToken(req->tokenResponse()); qDebug().noquote() << "--- Success oauth ----"; @@ -427,7 +431,8 @@ void Authorization::requestToken() emit finished(ret); req->deleteLater(); }); - req->appendRawHeader("DPoP", JsonWebToken::generate(m_handle)); + req->appendRawHeader("DPoP", + JsonWebToken::generate(tokenEndopoint(), m_clientId, "POST", dPopNonce())); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->requestToken(m_requestTokenPayload); @@ -436,7 +441,7 @@ void Authorization::requestToken() QByteArray Authorization::generateRandomValues() const { QByteArray values; -#ifdef HAGOROMO_UNIT_TEST1 +#ifdef HAGOROMO_UNIT_TEST_ const uint8_t base[] = { 116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121 }; @@ -547,6 +552,15 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new emit tokenChanged(); } +QString Authorization::dPopNonce() const +{ + return m_dPopNonce; +} + +void Authorization::setDPopNonce(const QString &newDPopNonce) +{ + m_dPopNonce = newDPopNonce; +} QByteArray Authorization::ParPayload() const { return m_parPayload; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index a6186caf..77635076 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -40,12 +40,14 @@ class Authorization : public QObject AtProtocolType::OauthDefs::TokenResponse token() const; void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); + QString dPopNonce() const; + void setDPopNonce(const QString &newDPopNonce); + QByteArray codeVerifier() const; QByteArray codeChallenge() const; QByteArray ParPayload() const; QString listenPort() const; - QByteArray state() const; signals: @@ -85,6 +87,7 @@ class Authorization : public QObject // QString m_redirectUri; QString m_clientId; + QString m_dPopNonce; // par QByteArray m_codeChallenge; QByteArray m_codeVerifier; diff --git a/lib/tools/jsonwebtoken.cpp b/lib/tools/jsonwebtoken.cpp index 3adf1518..5816fa01 100644 --- a/lib/tools/jsonwebtoken.cpp +++ b/lib/tools/jsonwebtoken.cpp @@ -32,7 +32,8 @@ inline QJsonObject createJwk() return jwk; } -QByteArray JsonWebToken::generate(const QString &handle) +QByteArray JsonWebToken::generate(const QString &endpoint, const QString &client_id, + const QString &method, const QString &nonce) { // ヘッダー QJsonObject header; @@ -43,9 +44,18 @@ QByteArray JsonWebToken::generate(const QString &handle) QByteArray headerBase64 = base64UrlEncode(headerJson); // ペイロード + qint64 epoch = QDateTime::currentSecsSinceEpoch(); QJsonObject payload; - payload["sub"] = handle; // ユーザーIDなど - payload["iat"] = QDateTime::currentSecsSinceEpoch(); // 発行時間 + payload["iss"] = "tech/relog/hagoromo"; + payload["sub"] = client_id; + payload["htu"] = endpoint; + payload["htm"] = method; + payload["exp"] = epoch + 60000; + payload["jti"] = QString(QString::number(epoch).toUtf8().toBase64()); + payload["iat"] = epoch; // 発行時間 + if (!nonce.isEmpty()) { + payload["nonce"] = nonce; + } QByteArray payloadJson = QJsonDocument(payload).toJson(QJsonDocument::Compact); QByteArray payloadBase64 = base64UrlEncode(payloadJson); diff --git a/lib/tools/jsonwebtoken.h b/lib/tools/jsonwebtoken.h index b1db15b5..213cb013 100644 --- a/lib/tools/jsonwebtoken.h +++ b/lib/tools/jsonwebtoken.h @@ -6,7 +6,8 @@ class JsonWebToken { public: - static QByteArray generate(const QString &handle); + static QByteArray generate(const QString &endpoint, const QString &client_id, + const QString &method, const QString &nonce); }; #endif // JSONWEBTOKEN_H diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 107127be..a190b780 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -87,7 +88,7 @@ void oauth_test::test_oauth_process() for (int i = 0; i < sizeof(base); i++) { base_ba.append(base[i]); } - qDebug() << sizeof(base) << base_ba.size() + qDebug() << "codeVerifier" << sizeof(base) << base_ba.size() << base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); QString msg; @@ -98,7 +99,8 @@ void oauth_test::test_oauth_process() msg += QString::number(static_cast(s)) + ", "; } qDebug() << msg; - qDebug() << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + qDebug() << "codeChallenge" + << sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); Authorization oauth; oauth.makeCodeChallenge(); @@ -106,9 +108,9 @@ void oauth_test::test_oauth_process() qDebug() << "codeVerifier" << oauth.codeVerifier(); // QVERIFY(oauth.codeChallenge() - // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - // QVERIFY(oauth.codeVerifier() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + // QVERIFY(oauth.codeVerifier() + // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); oauth.makeParPayload(); qDebug() << "ParPlayload" << oauth.ParPayload(); @@ -118,17 +120,27 @@ void oauth_test::test_oauth_process() void oauth_test::test_oauth_server() { - +#if 0 Authorization oauth; - // { - // QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - // oauth.start("https://bsky.social", "ioriayane2.bsky.social"); - // spy.wait(300 * 1000); - // QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - // QList arguments = spy.takeFirst(); - // QVERIFY(arguments.at(0).toBool()); - // } + { + QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); + oauth.start("https://bsky.social", "ioriayane.bsky.social"); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QString request_url = arguments.at(0).toString(); + qDebug().noquote() << "request url:" << request_url; + QDesktopServices::openUrl(request_url); + } + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + spy.wait(5 * 60 * 1000); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } +#endif } void oauth_test::test_oauth() @@ -137,7 +149,7 @@ void oauth_test::test_oauth() // response/2 : entry-way Authorization oauth; - oauth.setRedirectTimeout(3); + oauth.setRedirectTimeout(20); QString pds = QString("http://localhost:%1/response/2").arg(m_listenPort); QString handle = "ioriayane.relog.tech"; @@ -196,7 +208,7 @@ void oauth_test::test_oauth() { // ブラウザで認証ができないのでタイムアウトしてくるのを確認 QSignalSpy spy(&oauth, SIGNAL(finished(bool))); - spy.wait(); + spy.wait(20 * 1000); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); QVERIFY(!arguments.at(0).toBool()); @@ -239,7 +251,8 @@ void oauth_test::test_oauth() void oauth_test::test_jwt() { - QByteArray jwt = JsonWebToken::generate("https://hoge"); + QByteArray jwt = JsonWebToken::generate("https://hoge", "client_id", "GET", + "O_m5dyvKO7jNfnsfuYwB5GflhTuVaqCub4x3xVKqJ9Y"); qDebug().noquote() << jwt; From 8a174062b9899f8e20a76a2e2efc7fe49125a19d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 7 Sep 2024 02:41:45 +0900 Subject: [PATCH 061/127] =?UTF-8?q?token=E5=8F=96=E5=BE=97=E6=99=82?= =?UTF-8?q?=E3=81=AE=E4=BB=98=E5=B1=9E=E6=83=85=E5=A0=B1=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 2 ++ lib/atprotocol/lexicons_func.cpp | 2 ++ lib/tools/authorization.cpp | 3 ++- scripts/lexicons/oauth.defs.json | 6 ++++++ ...h.requestToken.json => oauth.requestToken.json.template} | 0 5 files changed, 12 insertions(+), 1 deletion(-) rename scripts/lexicons/{oauth.requestToken.json => oauth.requestToken.json.template} (100%) diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index b1b6bf7e..e6c92f0c 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -2066,6 +2066,8 @@ struct TokenResponse QString access_token; QString token_type; QString refresh_token; + QString scope; + QString sub; int expires_in = 0; }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index da49ccf3..30a09355 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2923,6 +2923,8 @@ void copyTokenResponse(const QJsonObject &src, OauthDefs::TokenResponse &dest) dest.access_token = src.value("access_token").toString(); dest.token_type = src.value("token_type").toString(); dest.refresh_token = src.value("refresh_token").toString(); + dest.scope = src.value("scope").toString(); + dest.sub = src.value("sub").toString(); dest.expires_in = src.value("expires_in").toInt(); } } diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 314f6a14..082352e1 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -546,7 +546,8 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new { if (m_token.access_token == newToken.access_token && m_token.expires_in == newToken.expires_in && m_token.refresh_token == newToken.refresh_token - && m_token.token_type == newToken.token_type) + && m_token.token_type == newToken.token_type && m_token.sub == newToken.sub + && m_token.scope == newToken.scope) return; m_token = newToken; emit tokenChanged(); diff --git a/scripts/lexicons/oauth.defs.json b/scripts/lexicons/oauth.defs.json index 124d0ace..158b4f3c 100644 --- a/scripts/lexicons/oauth.defs.json +++ b/scripts/lexicons/oauth.defs.json @@ -25,6 +25,12 @@ "refresh_token": { "type": "string" }, + "scope": { + "type": "string" + }, + "sub": { + "type": "string" + }, "expires_in": { "type": "integer" } diff --git a/scripts/lexicons/oauth.requestToken.json b/scripts/lexicons/oauth.requestToken.json.template similarity index 100% rename from scripts/lexicons/oauth.requestToken.json rename to scripts/lexicons/oauth.requestToken.json.template From da9bb63159f65e01a75591976540872f10b10e74 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 8 Sep 2024 02:00:52 +0900 Subject: [PATCH 062/127] =?UTF-8?q?=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E3=83=AA=E3=83=95=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 61 ++++++++++++++++++----------- lib/tools/authorization.h | 12 +++--- tests/oauth_test/tst_oauth_test.cpp | 24 ++++++++---- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 082352e1..55d9cfdc 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -48,10 +48,8 @@ void Authorization::reset() m_codeChallenge.clear(); m_codeVerifier.clear(); m_state.clear(); - m_parPayload.clear(); // request token m_code.clear(); - m_requestTokenPayload.clear(); m_token = AtProtocolType::OauthDefs::TokenResponse(); // m_listenPort.clear(); @@ -146,7 +144,6 @@ void Authorization::requestOauthAuthorizationServer() m_scopesSupported = server->serverMetadata().scopes_supported; // next step - makeParPayload(); par(); } else { qDebug().noquote() << error_message; @@ -224,6 +221,11 @@ QByteArray Authorization::state() const return m_state; } +void Authorization::setListenPort(const QString &newListenPort) +{ + m_listenPort = newListenPort; +} + QString Authorization::listenPort() const { return m_listenPort; @@ -252,7 +254,7 @@ void Authorization::makeCodeChallenge() .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } -void Authorization::makeParPayload() +QByteArray Authorization::makeParPayload() { makeClientId(); makeCodeChallenge(); @@ -271,12 +273,12 @@ void Authorization::makeParPayload() query.addQueryItem("scope", m_scopesSupported.join(" ")); query.addQueryItem("login_hint", simplyEncode(login_hint)); - m_parPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + return query.query(QUrl::FullyEncoded).toLocal8Bit(); } void Authorization::par() { - if (m_parPayload.isEmpty() || pushedAuthorizationRequestEndpoint().isEmpty()) + if (pushedAuthorizationRequestEndpoint().isEmpty()) return; AtProtocolInterface::AccountData account; @@ -302,7 +304,7 @@ void Authorization::par() }); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); - req->pushedAuthorizationRequest(m_parPayload); + req->pushedAuthorizationRequest(makeParPayload()); } void Authorization::authorization(const QString &request_uri) @@ -371,6 +373,11 @@ void Authorization::startRedirectServer() } server->deleteLater(); }); + connect(server, &QObject::destroyed, [this]() { + qDebug().noquote() << "Destory webserver"; + m_listenPort.clear(); + }); + server->setTimeout(redirectTimeout()); quint16 port = server->listen(QHostAddress::LocalHost, 0); m_listenPort = QString::number(port); @@ -378,26 +385,30 @@ void Authorization::startRedirectServer() qDebug().noquote() << "Listen" << m_listenPort; } -void Authorization::makeRequestTokenPayload() +QByteArray Authorization::makeRequestTokenPayload(bool refresh) { QUrlQuery query; - query.addQueryItem("grant_type", "authorization_code"); - query.addQueryItem("code", m_code); - query.addQueryItem("code_verifier", m_codeVerifier); - query.addQueryItem("client_id", simplyEncode(m_clientId)); - query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + if (refresh) { + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", token().refresh_token); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + } else { + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", m_code); + query.addQueryItem("code_verifier", m_codeVerifier); + query.addQueryItem("client_id", simplyEncode(m_clientId)); + query.addQueryItem("redirect_uri", simplyEncode(m_redirectUri)); + } - m_requestTokenPayload = query.query(QUrl::FullyEncoded).toLocal8Bit(); + return query.query(QUrl::FullyEncoded).toLocal8Bit(); } -void Authorization::requestToken() +void Authorization::requestToken(bool refresh) { if (tokenEndopoint().isEmpty()) return; - makeRequestTokenPayload(); - AtProtocolInterface::AccountData account; account.service = tokenEndopoint(); @@ -435,7 +446,7 @@ void Authorization::requestToken() JsonWebToken::generate(tokenEndopoint(), m_clientId, "POST", dPopNonce())); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); - req->requestToken(m_requestTokenPayload); + req->requestToken(makeRequestTokenPayload(refresh)); } QByteArray Authorization::generateRandomValues() const @@ -553,6 +564,16 @@ void Authorization::setToken(const AtProtocolType::OauthDefs::TokenResponse &new emit tokenChanged(); } +QString Authorization::clientId() const +{ + return m_clientId; +} + +void Authorization::setClientId(const QString &newClientId) +{ + m_clientId = newClientId; +} + QString Authorization::dPopNonce() const { return m_dPopNonce; @@ -562,10 +583,6 @@ void Authorization::setDPopNonce(const QString &newDPopNonce) { m_dPopNonce = newDPopNonce; } -QByteArray Authorization::ParPayload() const -{ - return m_parPayload; -} QByteArray Authorization::codeChallenge() const { diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index 77635076..ac441893 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -16,13 +16,13 @@ class Authorization : public QObject void makeClientId(); void makeCodeChallenge(); - void makeParPayload(); + QByteArray makeParPayload(); void par(); void authorization(const QString &request_uri); void startRedirectServer(); - void makeRequestTokenPayload(); - void requestToken(); + QByteArray makeRequestTokenPayload(bool refresh); + void requestToken(bool refresh = false); QString serviceEndpoint() const; void setServiceEndpoint(const QString &newServiceEndpoint); @@ -40,14 +40,16 @@ class Authorization : public QObject AtProtocolType::OauthDefs::TokenResponse token() const; void setToken(const AtProtocolType::OauthDefs::TokenResponse &newToken); + QString clientId() const; + void setClientId(const QString &newClientId); QString dPopNonce() const; void setDPopNonce(const QString &newDPopNonce); QByteArray codeVerifier() const; QByteArray codeChallenge() const; - QByteArray ParPayload() const; QString listenPort() const; + void setListenPort(const QString &newListenPort); QByteArray state() const; signals: @@ -92,10 +94,8 @@ class Authorization : public QObject QByteArray m_codeChallenge; QByteArray m_codeVerifier; QByteArray m_state; - QByteArray m_parPayload; // request token QByteArray m_code; - QByteArray m_requestTokenPayload; AtProtocolType::OauthDefs::TokenResponse m_token; QString m_listenPort; diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index a190b780..b9b12032 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -111,18 +111,12 @@ void oauth_test::test_oauth_process() // == sha256.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); // QVERIFY(oauth.codeVerifier() // == base_ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - - oauth.makeParPayload(); - qDebug() << "ParPlayload" << oauth.ParPayload(); - - // oauth.par(); } void oauth_test::test_oauth_server() { -#if 0 Authorization oauth; - +#if 0 { QSignalSpy spy(&oauth, SIGNAL(madeRequestUrl(const QString &))); oauth.start("https://bsky.social", "ioriayane.bsky.social"); @@ -140,6 +134,22 @@ void oauth_test::test_oauth_server() QList arguments = spy.takeFirst(); QVERIFY(arguments.at(0).toBool()); } +#elif 0 + AtProtocolType::OauthDefs::TokenResponse token; + token.refresh_token = "ref-121f89618c436ad99cbb792b5bd2b003fdc829dde32d73e02486b4ddb0c7bd99"; + oauth.setToken(token); + oauth.setTokenEndopoint("https://bsky.social/oauth/token"); + oauth.setDPopNonce("8mo0kjoyQ64_uOCBrZ4Q8M8-RT0BkfgAMDB7no5DkmU"); + oauth.setListenPort("65073"); + oauth.makeClientId(); + { + QSignalSpy spy(&oauth, SIGNAL(finished(bool))); + oauth.requestToken(true); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QList arguments = spy.takeFirst(); + QVERIFY(arguments.at(0).toBool()); + } #endif } From 3c42faf3ea1fe5e0c7abf6cc65ded1b1215bade4 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 8 Sep 2024 23:59:58 +0900 Subject: [PATCH 063/127] =?UTF-8?q?=E3=83=96=E3=83=A9=E3=82=A6=E3=82=B6?= =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=AF=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E5=81=B4=E3=81=8C=E6=B1=BA=E3=82=81=E3=82=8B=E3=83=95=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/deps.pri | 1 + lib/lib.pro | 2 -- lib/tools/authorization.cpp | 6 ++---- lib/tools/authorization.h | 4 +--- tests/deps.pri | 1 + tests/oauth_test/oauth_test.pro | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/deps.pri b/app/deps.pri index 39ae113e..aa3184c5 100644 --- a/app/deps.pri +++ b/app/deps.pri @@ -6,6 +6,7 @@ else:unix: LIBS += -L$$OUT_PWD/../lib/ -llib INCLUDEPATH += $$PWD/../lib DEPENDPATH += $$PWD/../lib +RESOURCES += $$PWD/../lib/lib.qrc win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../lib/release/liblib.a else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../lib/debug/liblib.a diff --git a/lib/lib.pro b/lib/lib.pro index b41cd4cc..4d6e5210 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -3,8 +3,6 @@ QT += xml sql websockets httpserver TEMPLATE = lib CONFIG += staticlib -CONFIG += c++11 - include(../openssl/openssl.pri) INCLUDEPATH += $$PWD \ diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 55d9cfdc..97ef8756 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -322,11 +322,8 @@ void Authorization::authorization(const QString &request_uri) qDebug().noquote() << "redirect" << url.toEncoded(); -#ifdef HAGOROMO_UNIT_TEST emit madeRequestUrl(url.toString()); -#else - QDesktopServices::openUrl(url); -#endif + // QDesktopServices::openUrl(url); } void Authorization::startRedirectServer() @@ -360,6 +357,7 @@ void Authorization::startRedirectServer() } else { SimpleHttpServer::readFile(":/tools/oauth/oauth_fail.html", data); } + qDebug().noquote() << "Result html:" << data; mime_type = "text/html"; // delete after 10 sec. diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index ac441893..c2ab140f 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -61,9 +61,7 @@ class Authorization : public QObject void tokenEndopointChanged(); void tokenChanged(); void finished(bool success); -#ifdef HAGOROMO_UNIT_TEST - void madeRequestUrl(const QString &url); -#endif + void madeRequestUrl(const QString &url); // このシグナルを受けてブラウザに飛ばすなりする private: QByteArray generateRandomValues() const; diff --git a/tests/deps.pri b/tests/deps.pri index 5181738d..b330c454 100644 --- a/tests/deps.pri +++ b/tests/deps.pri @@ -6,6 +6,7 @@ else:unix: LIBS += -L$$OUT_PWD/../../lib/ -llib INCLUDEPATH += $$PWD/../lib DEPENDPATH += $$PWD/../lib +RESOURCES += $$PWD/../lib/lib.qrc win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/release/liblib.a else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/debug/liblib.a diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro index d831444e..8c748c0d 100644 --- a/tests/oauth_test/oauth_test.pro +++ b/tests/oauth_test/oauth_test.pro @@ -9,7 +9,7 @@ DEFINES += HAGOROMO_UNIT_TEST SOURCES += tst_oauth_test.cpp include(../common/common.pri) -include(../../lib/lib.pri) +include(../deps.pri) include(../../openssl/openssl.pri) RESOURCES += \ From 3128829a85cc9323fc44196aff00eba512c2214b Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 9 Sep 2024 01:15:23 +0900 Subject: [PATCH 064/127] =?UTF-8?q?=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97?= =?UTF-8?q?=E3=83=88=E3=81=AB=E3=83=86=E3=82=B9=E3=83=88=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/es256.cpp | 4 +--- scripts/unittest.bat | 8 ++++++-- tests/oauth_test/tst_oauth_test.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/tools/es256.cpp b/lib/tools/es256.cpp index d90e758b..4be3ab57 100644 --- a/lib/tools/es256.cpp +++ b/lib/tools/es256.cpp @@ -133,9 +133,7 @@ void Es256::loadKey() .arg(QCoreApplication::organizationName()) .arg(QCoreApplication::applicationName()) .arg( -#if defined(HAGOROMO_UNIT_TEST) - QStringLiteral("_unittest") -#elif defined(QT_DEBUG) +#if defined(QT_DEBUG) QStringLiteral("_debug") #else QString() diff --git a/scripts/unittest.bat b/scripts/unittest.bat index 86cbaac7..21e2a362 100644 --- a/scripts/unittest.bat +++ b/scripts/unittest.bat @@ -63,10 +63,14 @@ if ERRORLEVEL 1 goto TEST_FAIL if ERRORLEVEL 1 goto TEST_FAIL %BUILD_FOLDER%\tests\http_test\debug\http_test.exe if ERRORLEVEL 1 goto TEST_FAIL -%BUILD_FOLDER%\tests\search_test\debug\search_test.exe -if ERRORLEVEL 1 goto TEST_FAIL %BUILD_FOLDER%\tests\log_test\debug\log_test.exe if ERRORLEVEL 1 goto TEST_FAIL +%BUILD_FOLDER%\tests\oauth_test\debug\oauth_test.exe +if ERRORLEVEL 1 goto TEST_FAIL +%BUILD_FOLDER%\tests\realtime_test\debug\realtime_test.exe +if ERRORLEVEL 1 goto TEST_FAIL +%BUILD_FOLDER%\tests\search_test\debug\search_test.exe +if ERRORLEVEL 1 goto TEST_FAIL %BUILD_FOLDER%\tests\tools_test\debug\tools_test.exe if ERRORLEVEL 1 goto TEST_FAIL diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index b9b12032..426aa8b2 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -278,7 +278,7 @@ void oauth_test::test_es256() .arg(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)) .arg(QCoreApplication::organizationName()) .arg(QCoreApplication::applicationName()) - .arg(QStringLiteral("_unittest")); + .arg(QStringLiteral("_debug")); QFile::remove(private_key_path); Es256::getInstance()->clear(); From 67edab97771740be6a37495c4d7082be999f67ee Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 9 Sep 2024 02:21:20 +0900 Subject: [PATCH 065/127] =?UTF-8?q?=E3=83=AA=E3=83=80=E3=82=A4=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E7=94=A8=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/simplehttpserver.cpp | 22 +++++++++++++++++++--- lib/http/simplehttpserver.h | 5 +++++ lib/tools/authorization.cpp | 12 ++++++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/http/simplehttpserver.cpp b/lib/http/simplehttpserver.cpp index 97494414..e7b8eded 100644 --- a/lib/http/simplehttpserver.cpp +++ b/lib/http/simplehttpserver.cpp @@ -1,11 +1,27 @@ #include "simplehttpserver.h" -#include -SimpleHttpServer::SimpleHttpServer(QObject *parent) : QAbstractHttpServer { parent } { } +SimpleHttpServer::SimpleHttpServer(QObject *parent) : QAbstractHttpServer { parent } +{ + connect(&m_timeout, &QTimer::timeout, this, [=]() { emit timeout(); }); +} void SimpleHttpServer::setTimeout(int sec) { - QTimer::singleShot(sec * 1000, [=]() { emit timeout(); }); + if (m_timeout.isActive()) { + qDebug().noquote() << "SimpleHttpServer::Restart timeout:" << sec; + m_timeout.stop(); + } else { + qDebug().noquote() << "SimpleHttpServer::Start timeout:" << sec; + } + m_timeout.start(sec * 1000); +} + +void SimpleHttpServer::clearTimeout() +{ + if (m_timeout.isActive()) { + qDebug().noquote() << "SimpleHttpServer::Stop timeout"; + m_timeout.stop(); + } } bool SimpleHttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) diff --git a/lib/http/simplehttpserver.h b/lib/http/simplehttpserver.h index 335582bc..88b6d9a9 100644 --- a/lib/http/simplehttpserver.h +++ b/lib/http/simplehttpserver.h @@ -2,6 +2,7 @@ #define SIMPLEHTTPSERVER_H #include +#include class SimpleHttpServer : public QAbstractHttpServer { @@ -10,6 +11,7 @@ class SimpleHttpServer : public QAbstractHttpServer explicit SimpleHttpServer(QObject *parent = nullptr); void setTimeout(int sec); + void clearTimeout(); bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override; static QString convertResoucePath(const QUrl &url); @@ -18,6 +20,9 @@ class SimpleHttpServer : public QAbstractHttpServer void received(const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type); void timeout(); + +private: + QTimer m_timeout; }; #endif // SIMPLEHTTPSERVER_H diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 97ef8756..bfbe9bb7 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -329,6 +329,7 @@ void Authorization::authorization(const QString &request_uri) void Authorization::startRedirectServer() { SimpleHttpServer *server = new SimpleHttpServer(this); + QPointer alive = server; connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { @@ -360,8 +361,15 @@ void Authorization::startRedirectServer() qDebug().noquote() << "Result html:" << data; mime_type = "text/html"; - // delete after 10 sec. - QTimer::singleShot(10 * 1000, [=]() { server->deleteLater(); }); + server->clearTimeout(); + // delete after 5 sec. + QTimer::singleShot(5 * 1000, [=]() { + if (alive) { + server->deleteLater(); + } else { + qDebug().noquote() << "Already deleted server"; + } + }); }); connect(server, &SimpleHttpServer::timeout, this, [=]() { // token取得に進んでたらfinishedは発火しない From 733c377ed3a694a61f86ebdaf1e2f859ed434301 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 9 Sep 2024 22:14:46 +0900 Subject: [PATCH 066/127] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E3=83=A1=E3=83=A2=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=AF=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/unittest.sh | 4 +++- tests/oauth_test/tst_oauth_test.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/unittest.sh b/scripts/unittest.sh index c4207d89..38b0dc76 100755 --- a/scripts/unittest.sh +++ b/scripts/unittest.sh @@ -59,6 +59,8 @@ do_test ${work_dir}/chat_test/chat_test do_test ${work_dir}/hagoromo_test/hagoromo_test do_test ${work_dir}/hagoromo_test2/hagoromo_test2 do_test ${work_dir}/http_test/http_test -do_test ${work_dir}/search_test/search_test do_test ${work_dir}/log_test/log_test +do_test ${work_dir}/oauth_test/oauth_test +do_test ${work_dir}/realtime_test/realtime_test +do_test ${work_dir}/search_test/search_test do_test ${work_dir}/tools_test/tools_test diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 426aa8b2..a99edea4 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -399,8 +399,8 @@ void oauth_test::verify_jwt(const QByteArray &jwt, EVP_PKEY *pkey) OPENSSL_free(der_sig); ECDSA_SIG_free(ec_sig); - BN_free(ec_sig_s); - BN_free(ec_sig_r); + // BN_free(ec_sig_s); // ECDSA_SIG_freeで解放される + // BN_free(ec_sig_r); // ECDSA_SIG_freeで解放される if (use_jwk) { EVP_PKEY_free(pkey); From 519cec40f8e7f315e886bc7735a13ef5eb47966a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 9 Sep 2024 22:59:12 +0900 Subject: [PATCH 067/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 2 +- tests/oauth_test/tst_oauth_test.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index bfbe9bb7..c677ea08 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -449,7 +449,7 @@ void Authorization::requestToken(bool refresh) req->deleteLater(); }); req->appendRawHeader("DPoP", - JsonWebToken::generate(tokenEndopoint(), m_clientId, "POST", dPopNonce())); + JsonWebToken::generate(tokenEndopoint(), clientId(), "POST", dPopNonce())); req->setContentType("application/x-www-form-urlencoded"); req->setAccount(account); req->requestToken(makeRequestTokenPayload(refresh)); diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index a99edea4..11b27397 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -134,6 +134,9 @@ void oauth_test::test_oauth_server() QList arguments = spy.takeFirst(); QVERIFY(arguments.at(0).toBool()); } + qDebug().noquote() << "DPoP for test"; + qDebug().noquote() << JsonWebToken::generate(oauth.tokenEndopoint(), oauth.clientId(), "GET", + oauth.dPopNonce()); #elif 0 AtProtocolType::OauthDefs::TokenResponse token; token.refresh_token = "ref-121f89618c436ad99cbb792b5bd2b003fdc829dde32d73e02486b4ddb0c7bd99"; From f8b6a81a37d70f549a27610169508c800fbadadf Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 13 Sep 2024 01:07:42 +0900 Subject: [PATCH 068/127] =?UTF-8?q?Actions=E3=81=AE=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=81=AE=E9=A0=86=E7=95=AA=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 90d4b3dd..0f472ead 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -30,14 +30,14 @@ jobs: uses: pre-commit/action@v3.0.1 with: extra_args: --all-files - - name: Build - run: scripts/build.sh linux ${Qt5_DIR}/bin - name: Build httpserver run: | mkdir 3rdparty/build-qthttpserver cd 3rdparty/build-qthttpserver qmake CONFIG+=debug_and_release ../qthttpserver/qthttpserver.pro make -j4 && make install + - name: Build + run: scripts/build.sh linux ${Qt5_DIR}/bin - name: Unit test env: TZ: "Asia/Tokyo" From 375474f5dc0e09f94338d6c46472f4e092636281 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 00:20:22 +0900 Subject: [PATCH 069/127] Update oauth_test.pro --- tests/oauth_test/oauth_test.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/oauth_test/oauth_test.pro b/tests/oauth_test/oauth_test.pro index 8c748c0d..eb337074 100644 --- a/tests/oauth_test/oauth_test.pro +++ b/tests/oauth_test/oauth_test.pro @@ -11,6 +11,7 @@ SOURCES += tst_oauth_test.cpp include(../common/common.pri) include(../deps.pri) include(../../openssl/openssl.pri) +include(../../zlib/zlib.pri) RESOURCES += \ oauth_test.qrc From 3dec005a87769f25f43a6d0e973432c578aa3c4b Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:01:25 +0900 Subject: [PATCH 070/127] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 8 +------- lib/lib.pro | 2 +- tests/deps.pri | 3 +++ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 08899754..3c43e217 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,7 +15,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v3 with: - version: '5.15.2' + version: '6.7.2' host: 'linux' target: 'desktop' arch: 'gcc_64' @@ -35,12 +35,6 @@ jobs: extra_args: --all-files - name: Build run: scripts/build.sh linux ${Qt5_DIR}/bin - - name: Build httpserver - run: | - mkdir 3rdparty/build-qthttpserver - cd 3rdparty/build-qthttpserver - qmake CONFIG+=debug_and_release ../qthttpserver/qthttpserver.pro - make -j4 && make install - name: Unit test env: TZ: "Asia/Tokyo" diff --git a/lib/lib.pro b/lib/lib.pro index 7d240095..15e6a6da 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -11,7 +11,7 @@ CONFIG += c++11 INCLUDEPATH += $$PWD \ $$PWD/../3rdparty/cpp-httplib \ $$PWD/../zlib/include -win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSL/Win_x64/include +win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSLv3/Win_x64/include unix:INCLUDEPATH += ../openssl/include DEFINES += CPPHTTPLIB_ZLIB_SUPPORT # zlib support for cpp-httplib diff --git a/tests/deps.pri b/tests/deps.pri index 5181738d..74239fec 100644 --- a/tests/deps.pri +++ b/tests/deps.pri @@ -1,4 +1,7 @@ QT += xml sql websockets +greaterThan(QT_MAJOR_VERSION, 5) { +QT += core5compat +} win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../lib/release/ -llib else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../lib/debug/ -llib From 37e36671dcdc45e42205326ec28851bb73e8817b Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:05:40 +0900 Subject: [PATCH 071/127] Update dev.yml --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 3c43e217..bc58d2f2 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -13,7 +13,7 @@ jobs: - name: Install dependencies (linux) run: sudo apt install ninja-build - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: version: '6.7.2' host: 'linux' From dc7de448509d6f6d1279479cb35276f8567ab949 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:11:54 +0900 Subject: [PATCH 072/127] Update dev.yml --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index bc58d2f2..ac07551d 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,7 +15,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: '6.7.2' + version: '6.2.*' host: 'linux' target: 'desktop' arch: 'gcc_64' From a59f669f455a3c91a0b88cafc6cdedd49b3181cb Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:13:28 +0900 Subject: [PATCH 073/127] Update dev.yml --- .github/workflows/dev.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index ac07551d..06008ee0 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -19,7 +19,6 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' - modules: 'qtwebglplugin' - name: Checkout repository uses: actions/checkout@v4 with: From 0665f6c9e7dc5eb08a27208ce0145a2b6d2cbfb7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:16:40 +0900 Subject: [PATCH 074/127] Update openssl.pri --- openssl/openssl.pri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openssl/openssl.pri b/openssl/openssl.pri index a89a2e2e..3de0aade 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -27,4 +27,4 @@ win32:{ }else{ LIBS += -L$${open_ssl_dir}/lib64 -lssl -lcrypto } -} \ No newline at end of file +} From 95114bfa9312a52b8c8118c3ad1a6f85ab2de58d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:25:50 +0900 Subject: [PATCH 075/127] Update dev.yml --- .github/workflows/dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 06008ee0..5467cad3 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -33,8 +33,8 @@ jobs: with: extra_args: --all-files - name: Build - run: scripts/build.sh linux ${Qt5_DIR}/bin + run: scripts/build.sh linux ${QT_ROOT_DIR}/bin - name: Unit test env: TZ: "Asia/Tokyo" - run: scripts/unittest.sh linux ${Qt5_DIR}/bin + run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin From 3bbc0cd4d1cd6e87a768d58783360a80dec34101 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:38:23 +0900 Subject: [PATCH 076/127] Update dev.yml --- .github/workflows/dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 5467cad3..7e7ba7a0 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -19,6 +19,7 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' + modules: 'qt5compat qtwebsockets qthttpserver' - name: Checkout repository uses: actions/checkout@v4 with: From 63f771749b684265dcf6de09d6f85e833071f2cb Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 23 Sep 2024 01:41:47 +0900 Subject: [PATCH 077/127] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 16 +++++----------- lib/lib.pro | 2 +- openssl/openssl.pri | 2 +- tests/deps.pri | 3 +++ 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 08899754..a3c4cbdc 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -13,13 +13,13 @@ jobs: - name: Install dependencies (linux) run: sudo apt install ninja-build - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '5.15.2' + version: '6.7.2' host: 'linux' target: 'desktop' arch: 'gcc_64' - modules: 'qtwebglplugin' + modules: 'qt5compat qtwebsockets qthttpserver' - name: Checkout repository uses: actions/checkout@v4 with: @@ -34,14 +34,8 @@ jobs: with: extra_args: --all-files - name: Build - run: scripts/build.sh linux ${Qt5_DIR}/bin - - name: Build httpserver - run: | - mkdir 3rdparty/build-qthttpserver - cd 3rdparty/build-qthttpserver - qmake CONFIG+=debug_and_release ../qthttpserver/qthttpserver.pro - make -j4 && make install + run: scripts/build.sh linux ${QT_ROOT_DIR}/bin - name: Unit test env: TZ: "Asia/Tokyo" - run: scripts/unittest.sh linux ${Qt5_DIR}/bin + run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin diff --git a/lib/lib.pro b/lib/lib.pro index 7d240095..15e6a6da 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -11,7 +11,7 @@ CONFIG += c++11 INCLUDEPATH += $$PWD \ $$PWD/../3rdparty/cpp-httplib \ $$PWD/../zlib/include -win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSL/Win_x64/include +win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSLv3/Win_x64/include unix:INCLUDEPATH += ../openssl/include DEFINES += CPPHTTPLIB_ZLIB_SUPPORT # zlib support for cpp-httplib diff --git a/openssl/openssl.pri b/openssl/openssl.pri index a89a2e2e..3de0aade 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -27,4 +27,4 @@ win32:{ }else{ LIBS += -L$${open_ssl_dir}/lib64 -lssl -lcrypto } -} \ No newline at end of file +} diff --git a/tests/deps.pri b/tests/deps.pri index 5181738d..74239fec 100644 --- a/tests/deps.pri +++ b/tests/deps.pri @@ -1,4 +1,7 @@ QT += xml sql websockets +greaterThan(QT_MAJOR_VERSION, 5) { +QT += core5compat +} win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../lib/release/ -llib else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../lib/debug/ -llib From b137b378fa4d934eeddc85349ab60fcf8677c9d4 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 24 Sep 2024 00:10:04 +0900 Subject: [PATCH 078/127] =?UTF-8?q?=E3=83=AA=E3=83=80=E3=82=A4=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AE=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 12 ++++++++-- lib/tools/authorization.h | 2 ++ lib/tools/oauth/oauth_fail.html | 34 ++++++++++++++++++++++++----- lib/tools/oauth/oauth_success.html | 34 ++++++++++++++++++++++++----- tests/deps.pri | 3 +++ tests/oauth_test/tst_oauth_test.cpp | 4 ++-- 6 files changed, 73 insertions(+), 16 deletions(-) diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index c677ea08..2b50609c 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -333,11 +333,18 @@ void Authorization::startRedirectServer() connect(server, &SimpleHttpServer::received, this, [=](const QHttpServerRequest &request, bool &result, QByteArray &data, QByteArray &mime_type) { - qDebug().noquote() << "receive by startRedirectServer"; + qDebug().noquote() << "received by startRedirectServer"; qDebug().noquote() << " " << request.url().toString(); qDebug().noquote() << " " << request.url().path(); - if (request.url().path() == "/tech/relog/hagoromo/oauth-callback") { } + if (request.url().path() != "/tech/relog/hagoromo/oauth-callback") { + result = SimpleHttpServer::readFile(":/tools/oauth/" + request.url().fileName(), + data); + mime_type = m_MimeDb.mimeTypeForFile(request.url().fileName()).name().toUtf8(); + qDebug().noquote() << "Other file:" << result << request.url().fileName() + << ", " << mime_type; + return; + } if (request.query().hasQueryItem("iss") && request.query().hasQueryItem("state") && request.query().hasQueryItem("code")) { @@ -358,6 +365,7 @@ void Authorization::startRedirectServer() } else { SimpleHttpServer::readFile(":/tools/oauth/oauth_fail.html", data); } + data.replace("%HANDLE%", m_handle.toLocal8Bit()); qDebug().noquote() << "Result html:" << data; mime_type = "text/html"; diff --git a/lib/tools/authorization.h b/lib/tools/authorization.h index c2ab140f..0a56649f 100644 --- a/lib/tools/authorization.h +++ b/lib/tools/authorization.h @@ -2,6 +2,7 @@ #define AUTHORIZATION_H #include +#include #include "atprotocol/lexicons.h" class Authorization : public QObject @@ -98,6 +99,7 @@ class Authorization : public QObject QString m_listenPort; int m_redirectTimeout; + QMimeDatabase m_MimeDb; }; #endif // AUTHORIZATION_H diff --git a/lib/tools/oauth/oauth_fail.html b/lib/tools/oauth/oauth_fail.html index 2e19f74d..a5093494 100644 --- a/lib/tools/oauth/oauth_fail.html +++ b/lib/tools/oauth/oauth_fail.html @@ -1,8 +1,30 @@ - - Hagoromo - - - Authorization fail. - + + + + Hagoromo + + + + +

羽衣 -Hagoromo-

+
+

%HANDLE%」の認証処理で失敗しました。

+

このウインドウを閉じて羽衣へ戻ってください。

+
+ +
+

The authentication process for "%HANDLE%" failed.

+

Please close this window and return to Hagoromo.

+
+ + diff --git a/lib/tools/oauth/oauth_success.html b/lib/tools/oauth/oauth_success.html index b04c54c8..744f4990 100644 --- a/lib/tools/oauth/oauth_success.html +++ b/lib/tools/oauth/oauth_success.html @@ -1,8 +1,30 @@ - - Hagoromo - - - Authorization success. - + + + + Hagoromo + + + + +

羽衣 -Hagoromo-

+
+

%HANDLE%」が羽衣でBlueskyを使用する認証処理を実施中です。

+

このウインドウを閉じて羽衣へ戻ってください。

+
+ +
+

Hagoromo is currently processing authentication using Bluesky for "%HANDLE%".

+

Please close this window and return to Hagoromo.

+
+ + diff --git a/tests/deps.pri b/tests/deps.pri index b330c454..ddc20aef 100644 --- a/tests/deps.pri +++ b/tests/deps.pri @@ -13,3 +13,6 @@ else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../l else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/release/lib.lib else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../lib/debug/lib.lib else:unix: PRE_TARGETDEPS += $$OUT_PWD/../../lib/liblib.a + +# openssl.priなどで追加した依存ファイルのコピーに必要 +win32:QMAKE_POST_LINK += nmake -f $(MAKEFILE) install diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 11b27397..021d5b70 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -139,10 +139,10 @@ void oauth_test::test_oauth_server() oauth.dPopNonce()); #elif 0 AtProtocolType::OauthDefs::TokenResponse token; - token.refresh_token = "ref-121f89618c436ad99cbb792b5bd2b003fdc829dde32d73e02486b4ddb0c7bd99"; + token.refresh_token = "ref-121f89618c436"; oauth.setToken(token); oauth.setTokenEndopoint("https://bsky.social/oauth/token"); - oauth.setDPopNonce("8mo0kjoyQ64_uOCBrZ4Q8M8-RT0BkfgAMDB7no5DkmU"); + oauth.setDPopNonce("8mo0kjo"); oauth.setListenPort("65073"); oauth.makeClientId(); { From cae6f0091b4243253a57b0a2155d4c5267242604 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 12 Oct 2024 22:38:48 +0900 Subject: [PATCH 079/127] =?UTF-8?q?lib=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=94=E3=83=BC=E5=AF=BE=E8=B1=A1=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy/linux_lib.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/deploy/linux_lib.txt b/scripts/deploy/linux_lib.txt index d42c0435..bd57c6ad 100644 --- a/scripts/deploy/linux_lib.txt +++ b/scripts/deploy/linux_lib.txt @@ -8,6 +8,8 @@ libQt5Gui.so.5 libQt5Gui.so.5.15.2 libQt5Network.so.5 libQt5Network.so.5.15.2 +libQt5HttpServer.so.5 +libQt5HttpServer.so.5.12.0 libQt5Qml.so.5 libQt5Qml.so.5.15.2 libQt5QmlModels.so.5 From 7befc067a03464e347ee9868d098f314fd41ea3a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 13 Oct 2024 01:42:53 +0900 Subject: [PATCH 080/127] =?UTF-8?q?Qt5=E3=81=A86=E3=81=AE=E3=83=93?= =?UTF-8?q?=E3=83=AB=E3=83=89=E3=82=92=E5=85=BC=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 9 ++++++--- lib/http/simplehttpserver.cpp | 21 +++++++++++++++++++-- lib/http/simplehttpserver.h | 7 ++++++- lib/lib.pro | 4 ++++ openssl/openssl.pri | 29 +++++++++++++++++++++-------- scripts/build.sh | 15 +++++++++++++-- tests/oauth_test/tst_oauth_test.cpp | 12 ++++++++++++ 7 files changed, 81 insertions(+), 16 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index e2c2618e..d027a025 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,7 +15,8 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: '6.2.*' + version: '5.15.2' + # version: '6.2.*' host: 'linux' target: 'desktop' arch: 'gcc_64' @@ -40,8 +41,10 @@ jobs: qmake CONFIG+=debug_and_release ../qthttpserver/qthttpserver.pro make -j4 && make install - name: Build - run: scripts/build.sh linux ${QT_ROOT_DIR}/bin + run: scripts/build.sh linux ${Qt5_DIR}/bin + # run: scripts/build.sh linux ${QT_ROOT_DIR}/bin - name: Unit test env: TZ: "Asia/Tokyo" - run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin + run: scripts/unittest.sh linux ${Qt5_DIR}/bin + # run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin diff --git a/lib/http/simplehttpserver.cpp b/lib/http/simplehttpserver.cpp index e7b8eded..29e1a4e0 100644 --- a/lib/http/simplehttpserver.cpp +++ b/lib/http/simplehttpserver.cpp @@ -24,16 +24,33 @@ void SimpleHttpServer::clearTimeout() } } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +#else +void SimpleHttpServer::missingHandler(const QHttpServerRequest &request, + QHttpServerResponder &&responder) +{ + Q_UNUSED(request) + Q_UNUSED(responder) +} +#endif + +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) bool SimpleHttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) +# define MAKE_RESPONDER makeResponder(request, socket) +#else +# define MAKE_RESPONDER responder +bool SimpleHttpServer::handleRequest(const QHttpServerRequest &request, + QHttpServerResponder &responder) +#endif { bool result = false; QByteArray data; QByteArray mime_type; emit received(request, result, data, mime_type); if (result) { - makeResponder(request, socket).write(data, mime_type, QHttpServerResponder::StatusCode::Ok); + MAKE_RESPONDER.write(data, mime_type, QHttpServerResponder::StatusCode::Ok); } else { - makeResponder(request, socket).write(QHttpServerResponder::StatusCode::InternalServerError); + MAKE_RESPONDER.write(QHttpServerResponder::StatusCode::InternalServerError); } return true; } diff --git a/lib/http/simplehttpserver.h b/lib/http/simplehttpserver.h index 88b6d9a9..45f0b511 100644 --- a/lib/http/simplehttpserver.h +++ b/lib/http/simplehttpserver.h @@ -12,8 +12,13 @@ class SimpleHttpServer : public QAbstractHttpServer void setTimeout(int sec); void clearTimeout(); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override; - +#else + virtual bool handleRequest(const QHttpServerRequest &request, QHttpServerResponder &responder); + virtual void missingHandler(const QHttpServerRequest &request, + QHttpServerResponder &&responder); +#endif static QString convertResoucePath(const QUrl &url); static bool readFile(const QString &path, QByteArray &data); signals: diff --git a/lib/lib.pro b/lib/lib.pro index 1091d160..db6aa7c7 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -11,7 +11,11 @@ CONFIG += c++11 INCLUDEPATH += $$PWD \ $$PWD/../3rdparty/cpp-httplib \ $$PWD/../zlib/include +greaterThan(QT_MAJOR_VERSION, 5) { win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSLv3/Win_x64/include +}else{ +win32:INCLUDEPATH += $$dirname(QMAKE_QMAKE)/../../../Tools/OpenSSL/Win_x64/include +} unix:INCLUDEPATH += ../openssl/include DEFINES += CPPHTTPLIB_ZLIB_SUPPORT # zlib support for cpp-httplib diff --git a/openssl/openssl.pri b/openssl/openssl.pri index 43856afa..5eb86061 100644 --- a/openssl/openssl.pri +++ b/openssl/openssl.pri @@ -1,10 +1,14 @@ win32:{ bin_dir=$$dirname(QMAKE_QMAKE) - open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSLv3 + greaterThan(QT_MAJOR_VERSION, 5) { + open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSLv3 + }else{ + open_ssl_dir=$${bin_dir}/../../../Tools/OpenSSL + } open_ssl_dir=$$clean_path($$open_ssl_dir) - open_ssl_dir=$${open_ssl_dir}/Win_x64 - SOURCES += $${open_ssl_dir}/src/ms/applink.c + + open_ssl_dir=$${open_ssl_dir}/Win_x64 LIBS += $${open_ssl_dir}/lib/libssl.lib \ $${open_ssl_dir}/lib/libcrypto.lib INCLUDEPATH += $${open_ssl_dir}/include @@ -13,10 +17,15 @@ win32:{ else: install_dir = $$OUT_PWD/release depend_files.path = $$install_dir - depend_files.files = \ - $${open_ssl_dir}/bin/libcrypto-3-x64.dll \ - $${open_ssl_dir}/bin/libssl-3-x64.dll - + greaterThan(QT_MAJOR_VERSION, 5) { + depend_files.files = \ + $${open_ssl_dir}/bin/libcrypto-3-x64.dll \ + $${open_ssl_dir}/bin/libssl-3-x64.dll + }else{ + depend_files.files = \ + $${open_ssl_dir}/bin/libcrypto-1_1-x64.dll \ + $${open_ssl_dir}/bin/libssl-1_1-x64.dll + } INSTALLS += depend_files # QMAKE_POST_LINK += nmake -f $(MAKEFILE) install @@ -26,6 +35,10 @@ win32:{ mac:{ LIBS += -L$${open_ssl_dir}/lib -lssl -lcrypto }else{ - LIBS += -L$${open_ssl_dir}/lib64 -lssl -lcrypto + greaterThan(QT_MAJOR_VERSION, 5) { + LIBS += -L$${open_ssl_dir}/lib64 -lssl -lcrypto + }else{ + LIBS += -L$${open_ssl_dir}/lib -lssl -lcrypto + } } } diff --git a/scripts/build.sh b/scripts/build.sh index 6f8f6d57..1c7fbfed 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -80,8 +80,13 @@ deploy_hagoromo(){ cp ${build_dir}/app/Hagoromo ${work_dir}/bin cp ${SCRIPT_FOLDER}/deploy/Hagoromo.sh ${work_dir} - cp "openssl/lib64/libcrypto.so.3" ${work_dir}/lib - cp "openssl/lib64/libssl.so.3" ${work_dir}/lib + if [ $QT_VERSION == 6 ]; then + cp "openssl/lib64/libcrypto.so.3" ${work_dir}/lib + cp "openssl/lib64/libssl.so.3" ${work_dir}/lib + else + cp "openssl/lib/libcrypto.so.1.1" ${work_dir}/lib + cp "openssl/lib/libssl.so.1.1" ${work_dir}/lib + fi cp "zlib/lib/libz.so.1.3.1" ${work_dir}/lib cp "zlib/lib/libz.so.1" ${work_dir}/lib cp "app/i18n/app_ja.qm" ${work_dir}/bin/translations @@ -125,6 +130,12 @@ ROOT_FOLDER=$(pwd) PLATFORM_TYPE=$1 QT_BIN_FOLDER=$2 +if [[ "$QT_BIN_FOLDER" == */6.* ]]; then +QT_VERSION=6 +else +QT_VERSION=5 +fi + if [ -z "${QT_BIN_FOLDER}" ] || [ -z "${PLATFORM_TYPE}" ]; then echo "usage $(basename $0) PLATFORM_TYPE QT_BIN_FOLDER" echo " PLATFORM_TYPE linux or mac" diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 021d5b70..0faceed4 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -56,8 +56,20 @@ oauth_test::oauth_test() if (path.endsWith("/oauth/token")) { qDebug().noquote() << "Verify jwt"; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) QVERIFY(request.headers().contains("DPoP")); verify_jwt(request.headers().value("DPoP").toByteArray(), nullptr); +#else + bool exist = false; + for(const auto &header: request.headers()){ + if(header.first == "DPoP"){ + verify_jwt(header.second, nullptr); + exist = true; + break; + } + } + QVERIFY(exist); +#endif } if (!QFile::exists(path)) { From 82b8f8939cba899dd9c4c08364898acaa8b7abf4 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 13 Oct 2024 01:45:15 +0900 Subject: [PATCH 081/127] =?UTF-8?q?Qt5=E3=81=A7=E3=83=93=E3=83=AB=E3=83=89?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E6=88=BB?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdparty/openssl | 2 +- app/qml/compat/ColorOverlayC.qml | 4 +- app/qml/compat/GlowC.qml | 4 +- app/qml/compat/OpacityMaskC.qml | 4 +- app/qml/compat/SettingsC.qml | 4 +- scripts/deploy/linux_lib.txt | 104 ++- scripts/deploy/linux_plugin.txt | 16 +- scripts/deploy/linux_qml.txt | 1175 +++++++++++++++++++----------- 8 files changed, 821 insertions(+), 492 deletions(-) diff --git a/3rdparty/openssl b/3rdparty/openssl index db2ac4f6..70c2912f 160000 --- a/3rdparty/openssl +++ b/3rdparty/openssl @@ -1 +1 @@ -Subproject commit db2ac4f6ebd8f3d7b2a60882992fbea1269114e2 +Subproject commit 70c2912f635aac8ab28629a2b5ea0c09740d2bda diff --git a/app/qml/compat/ColorOverlayC.qml b/app/qml/compat/ColorOverlayC.qml index 63d2039e..b58f6840 100644 --- a/app/qml/compat/ColorOverlayC.qml +++ b/app/qml/compat/ColorOverlayC.qml @@ -1,5 +1,5 @@ -// import QtGraphicalEffects 1.15 -import Qt5Compat.GraphicalEffects +import QtGraphicalEffects 1.15 +// import Qt5Compat.GraphicalEffects ColorOverlay { diff --git a/app/qml/compat/GlowC.qml b/app/qml/compat/GlowC.qml index 97c7108a..b013b8bd 100644 --- a/app/qml/compat/GlowC.qml +++ b/app/qml/compat/GlowC.qml @@ -1,5 +1,5 @@ -// import QtGraphicalEffects 1.15 -import Qt5Compat.GraphicalEffects +import QtGraphicalEffects 1.15 +// import Qt5Compat.GraphicalEffects Glow { diff --git a/app/qml/compat/OpacityMaskC.qml b/app/qml/compat/OpacityMaskC.qml index 4144f3b8..86e37287 100644 --- a/app/qml/compat/OpacityMaskC.qml +++ b/app/qml/compat/OpacityMaskC.qml @@ -1,5 +1,5 @@ -// import QtGraphicalEffects 1.15 -import Qt5Compat.GraphicalEffects +import QtGraphicalEffects 1.15 +// import Qt5Compat.GraphicalEffects OpacityMask { diff --git a/app/qml/compat/SettingsC.qml b/app/qml/compat/SettingsC.qml index dafd691c..2f7ff511 100644 --- a/app/qml/compat/SettingsC.qml +++ b/app/qml/compat/SettingsC.qml @@ -1,5 +1,5 @@ -// import Qt.labs.settings 1.0 -import QtCore +import Qt.labs.settings 1.0 +// import QtCore Settings { } diff --git a/scripts/deploy/linux_lib.txt b/scripts/deploy/linux_lib.txt index 1b3f8702..bd57c6ad 100644 --- a/scripts/deploy/linux_lib.txt +++ b/scripts/deploy/linux_lib.txt @@ -1,62 +1,42 @@ -libQt6Core.so.6 -libQt6Core.so.6.7.2 -libQt6Core5Compat.so.6 -libQt6Core5Compat.so.6.7.2 -libQt6DBus.so.6 -libQt6DBus.so.6.7.2 -libQt6Gui.so.6 -libQt6Gui.so.6.7.2 -libQt6Network.so.6 -libQt6Network.so.6.7.2 -libQt6HttpServer.so.6 -libQt6HttpServer.so.6.7.2 -libQt6OpenGL.so.6 -libQt6OpenGL.so.6.7.2 -libQt6Qml.so.6 -libQt6Qml.so.6.7.2 -libQt6QmlCore.so.6 -libQt6QmlCore.so.6.7.2 -libQt6QmlModels.so.6 -libQt6QmlModels.so.6.7.2 -libQt6QmlWorkerScript.so.6 -libQt6QmlWorkerScript.so.6.7.2 -libQt6Quick.so.6 -libQt6Quick.so.6.7.2 -libQt6QuickControls2.so.6 -libQt6QuickControls2.so.6.7.2 -libQt6QuickControls2Basic.so.6 -libQt6QuickControls2Basic.so.6.7.2 -libQt6QuickControls2BasicStyleImpl.so.6 -libQt6QuickControls2BasicStyleImpl.so.6.7.2 -libQt6QuickControls2Impl.so.6 -libQt6QuickControls2Impl.so.6.7.2 -libQt6QuickControls2Material.so.6 -libQt6QuickControls2Material.so.6.7.2 -libQt6QuickControls2MaterialStyleImpl.so.6 -libQt6QuickControls2MaterialStyleImpl.so.6.7.2 -libQt6QuickLayouts.so.6 -libQt6QuickLayouts.so.6.7.2 -libQt6QuickTemplates2.so.6 -libQt6QuickTemplates2.so.6.7.2 -libQt6ShaderTools.so.6 -libQt6ShaderTools.so.6.7.2 -libQt6Sql.so.6 -libQt6Sql.so.6.7.2 -libQt6Svg.so.6 -libQt6Svg.so.6.7.2 -libQt6WaylandClient.so.6 -libQt6WaylandClient.so.6.7.2 -libQt6WaylandEglClientHwIntegration.so.6 -libQt6WaylandEglClientHwIntegration.so.6.7.2 -libQt6WebSockets.so.6 -libQt6WebSockets.so.6.7.2 -libQt6Widgets.so.6 -libQt6Widgets.so.6.7.2 -libQt6Xml.so.6 -libQt6Xml.so.6.7.2 -libicudata.so.73 -libicudata.so.73.2 -libicui18n.so.73 -libicui18n.so.73.2 -libicuuc.so.73 -libicuuc.so.73.2 +libQt5Core.so.5 +libQt5Core.so.5.15.2 +libQt5DBus.so.5 +libQt5DBus.so.5.15.2 +libQt5EglFSDeviceIntegration.so.5 +libQt5EglFSDeviceIntegration.so.5.15.2 +libQt5Gui.so.5 +libQt5Gui.so.5.15.2 +libQt5Network.so.5 +libQt5Network.so.5.15.2 +libQt5HttpServer.so.5 +libQt5HttpServer.so.5.12.0 +libQt5Qml.so.5 +libQt5Qml.so.5.15.2 +libQt5QmlModels.so.5 +libQt5QmlModels.so.5.15.2 +libQt5QmlWorkerScript.so.5 +libQt5QmlWorkerScript.so.5.15.2 +libQt5Quick.so.5 +libQt5Quick.so.5.15.2 +libQt5QuickControls2.so.5 +libQt5QuickControls2.so.5.15.2 +libQt5QuickTemplates2.so.5 +libQt5QuickTemplates2.so.5.15.2 +libQt5Sql.so.5 +libQt5Sql.so.5.15.2 +libQt5Svg.so.5 +libQt5Svg.so.5.15.2 +libQt5WebSockets.so.5 +libQt5WebSockets.so.5.15.2 +libQt5Widgets.so.5 +libQt5Widgets.so.5.15.2 +libQt5XcbQpa.so.5 +libQt5XcbQpa.so.5.15.2 +libQt5Xml.so.5 +libQt5Xml.so.5.15.2 +libicudata.so.56 +libicudata.so.56.1 +libicui18n.so.56 +libicui18n.so.56.1 +libicuuc.so.56 +libicuuc.so.56.1 diff --git a/scripts/deploy/linux_plugin.txt b/scripts/deploy/linux_plugin.txt index af08be36..638463c8 100644 --- a/scripts/deploy/linux_plugin.txt +++ b/scripts/deploy/linux_plugin.txt @@ -17,6 +17,9 @@ platforms/libqoffscreen.so platforms/libqvnc.so platforms/libqwayland-egl.so platforms/libqwayland-generic.so +platforms/libqwayland-xcomposite-egl.so +platforms/libqwayland-xcomposite-glx.so +platforms/libqwebgl.so platforms/libqxcb.so platformthemes/libqgtk3.so platformthemes/libqxdgdesktopportal.so @@ -31,19 +34,6 @@ qmltooling/libqmldbg_profiler.so qmltooling/libqmldbg_quickprofiler.so qmltooling/libqmldbg_server.so qmltooling/libqmldbg_tcp.so -tls/libqcertonlybackend.so -tls/libqopensslbackend.so -wayland-decoration-client/libbradient.so -wayland-graphics-integration-client/libdmabuf-server.so -wayland-graphics-integration-client/libdrm-egl-server.so -wayland-graphics-integration-client/libqt-plugin-wayland-egl.so -wayland-graphics-integration-client/libshm-emulation-server.so -wayland-graphics-integration-client/libvulkan-server.so -wayland-shell-integration/libfullscreen-shell-v1.so -wayland-shell-integration/libivi-shell.so -wayland-shell-integration/libqt-shell.so -wayland-shell-integration/libwl-shell-plugin.so -wayland-shell-integration/libxdg-shell.so sqldrivers/libqsqlite.so sqldrivers/libqsqlodbc.so sqldrivers/libqsqlpsql.so diff --git a/scripts/deploy/linux_qml.txt b/scripts/deploy/linux_qml.txt index 4b814f03..6c6746fe 100644 --- a/scripts/deploy/linux_qml.txt +++ b/scripts/deploy/linux_qml.txt @@ -1,414 +1,773 @@ +Qt/labs/platform/libqtlabsplatformplugin.so Qt/labs/platform/plugins.qmltypes Qt/labs/platform/qmldir -Qt/labs/platform/libqtlabsplatformplugin.so -Qt5Compat/GraphicalEffects/Blend.qml -Qt5Compat/GraphicalEffects/BrightnessContrast.qml -Qt5Compat/GraphicalEffects/Colorize.qml -Qt5Compat/GraphicalEffects/ColorOverlay.qml -Qt5Compat/GraphicalEffects/ConicalGradient.qml -Qt5Compat/GraphicalEffects/Desaturate.qml -Qt5Compat/GraphicalEffects/DirectionalBlur.qml -Qt5Compat/GraphicalEffects/Displace.qml -Qt5Compat/GraphicalEffects/DropShadow.qml -Qt5Compat/GraphicalEffects/FastBlur.qml -Qt5Compat/GraphicalEffects/GammaAdjust.qml -Qt5Compat/GraphicalEffects/GaussianBlur.qml -Qt5Compat/GraphicalEffects/Glow.qml -Qt5Compat/GraphicalEffects/HueSaturation.qml -Qt5Compat/GraphicalEffects/InnerShadow.qml -Qt5Compat/GraphicalEffects/LevelAdjust.qml -Qt5Compat/GraphicalEffects/LinearGradient.qml -Qt5Compat/GraphicalEffects/MaskedBlur.qml -Qt5Compat/GraphicalEffects/OpacityMask.qml -Qt5Compat/GraphicalEffects/plugins.qmltypes -Qt5Compat/GraphicalEffects/qmldir -Qt5Compat/GraphicalEffects/libqtgraphicaleffectsplugin.so -Qt5Compat/GraphicalEffects/RadialBlur.qml -Qt5Compat/GraphicalEffects/RadialGradient.qml -Qt5Compat/GraphicalEffects/RectangularGlow.qml -Qt5Compat/GraphicalEffects/RecursiveBlur.qml -Qt5Compat/GraphicalEffects/ThresholdMask.qml -Qt5Compat/GraphicalEffects/ZoomBlur.qml -Qt5Compat/GraphicalEffects/private/DropShadowBase.qml -Qt5Compat/GraphicalEffects/private/FastGlow.qml -Qt5Compat/GraphicalEffects/private/FastInnerShadow.qml -Qt5Compat/GraphicalEffects/private/GaussianDirectionalBlur.qml -Qt5Compat/GraphicalEffects/private/GaussianGlow.qml -Qt5Compat/GraphicalEffects/private/GaussianInnerShadow.qml -Qt5Compat/GraphicalEffects/private/GaussianMaskedBlur.qml -Qt5Compat/GraphicalEffects/private/plugins.qmltypes -Qt5Compat/GraphicalEffects/private/qmldir -Qt5Compat/GraphicalEffects/private/libqtgraphicaleffectsprivateplugin.so -QtCore/plugins.qmltypes -QtCore/qmldir -QtCore/libqtqmlcoreplugin.so -QtQml/qmldir -QtQml/libqmlmetaplugin.so -QtQml/Base/plugins.qmltypes -QtQml/Base/qmldir -QtQml/Base/libqmlplugin.so -QtQml/Models/libmodelsplugin.so -QtQml/Models/plugins.qmltypes -QtQml/Models/qmldir -QtQml/WorkerScript/plugins.qmltypes -QtQml/WorkerScript/qmldir -QtQml/WorkerScript/libworkerscriptplugin.so -QtQuick/plugins.qmltypes -QtQuick/qmldir -QtQuick/libqtquick2plugin.so +Qt/labs/settings/libqmlsettingsplugin.so +Qt/labs/settings/plugins.qmltypes +Qt/labs/settings/qmldir +QtGraphicalEffects/Blend.qml +QtGraphicalEffects/BrightnessContrast.qml +QtGraphicalEffects/ColorOverlay.qml +QtGraphicalEffects/Colorize.qml +QtGraphicalEffects/ConicalGradient.qml +QtGraphicalEffects/Desaturate.qml +QtGraphicalEffects/DirectionalBlur.qml +QtGraphicalEffects/Displace.qml +QtGraphicalEffects/DropShadow.qml +QtGraphicalEffects/FastBlur.qml +QtGraphicalEffects/GammaAdjust.qml +QtGraphicalEffects/GaussianBlur.qml +QtGraphicalEffects/Glow.qml +QtGraphicalEffects/HueSaturation.qml +QtGraphicalEffects/InnerShadow.qml +QtGraphicalEffects/LevelAdjust.qml +QtGraphicalEffects/LinearGradient.qml +QtGraphicalEffects/MaskedBlur.qml +QtGraphicalEffects/OpacityMask.qml +QtGraphicalEffects/RadialBlur.qml +QtGraphicalEffects/RadialGradient.qml +QtGraphicalEffects/RectangularGlow.qml +QtGraphicalEffects/RecursiveBlur.qml +QtGraphicalEffects/ThresholdMask.qml +QtGraphicalEffects/ZoomBlur.qml +QtGraphicalEffects/libqtgraphicaleffectsplugin.so +QtGraphicalEffects/plugins.qmltypes +QtGraphicalEffects/private/DropShadowBase.qml +QtGraphicalEffects/private/DropShadowBase.qmlc +QtGraphicalEffects/private/FastGlow.qml +QtGraphicalEffects/private/FastGlow.qmlc +QtGraphicalEffects/private/FastInnerShadow.qml +QtGraphicalEffects/private/FastInnerShadow.qmlc +QtGraphicalEffects/private/FastMaskedBlur.qml +QtGraphicalEffects/private/FastMaskedBlur.qmlc +QtGraphicalEffects/private/GaussianDirectionalBlur.qml +QtGraphicalEffects/private/GaussianDirectionalBlur.qmlc +QtGraphicalEffects/private/GaussianGlow.qml +QtGraphicalEffects/private/GaussianGlow.qmlc +QtGraphicalEffects/private/GaussianInnerShadow.qml +QtGraphicalEffects/private/GaussianInnerShadow.qmlc +QtGraphicalEffects/private/GaussianMaskedBlur.qml +QtGraphicalEffects/private/GaussianMaskedBlur.qmlc +QtGraphicalEffects/private/libqtgraphicaleffectsprivate.so +QtGraphicalEffects/private/qmldir +QtGraphicalEffects/qmldir +QtQuick.2/libqtquick2plugin.so +QtQuick.2/plugins.qmltypes +QtQuick.2/qmldir +QtQuick/Controls.2/AbstractButton.qml +QtQuick/Controls.2/Action.qml +QtQuick/Controls.2/ActionGroup.qml +QtQuick/Controls.2/ApplicationWindow.qml +QtQuick/Controls.2/BusyIndicator.qml +QtQuick/Controls.2/Button.qml +QtQuick/Controls.2/ButtonGroup.qml +QtQuick/Controls.2/CheckBox.qml +QtQuick/Controls.2/CheckDelegate.qml +QtQuick/Controls.2/ComboBox.qml +QtQuick/Controls.2/Container.qml +QtQuick/Controls.2/Control.qml +QtQuick/Controls.2/DelayButton.qml +QtQuick/Controls.2/Dial.qml +QtQuick/Controls.2/Dialog.qml +QtQuick/Controls.2/DialogButtonBox.qml +QtQuick/Controls.2/Drawer.qml +QtQuick/Controls.2/Frame.qml +QtQuick/Controls.2/Fusion/ApplicationWindow.qml +QtQuick/Controls.2/Fusion/BusyIndicator.qml +QtQuick/Controls.2/Fusion/Button.qml +QtQuick/Controls.2/Fusion/ButtonPanel.qml +QtQuick/Controls.2/Fusion/CheckBox.qml +QtQuick/Controls.2/Fusion/CheckDelegate.qml +QtQuick/Controls.2/Fusion/CheckIndicator.qml +QtQuick/Controls.2/Fusion/ComboBox.qml +QtQuick/Controls.2/Fusion/DelayButton.qml +QtQuick/Controls.2/Fusion/Dial.qml +QtQuick/Controls.2/Fusion/Dialog.qml +QtQuick/Controls.2/Fusion/DialogButtonBox.qml +QtQuick/Controls.2/Fusion/Drawer.qml +QtQuick/Controls.2/Fusion/Frame.qml +QtQuick/Controls.2/Fusion/GroupBox.qml +QtQuick/Controls.2/Fusion/HorizontalHeaderView.qml +QtQuick/Controls.2/Fusion/ItemDelegate.qml +QtQuick/Controls.2/Fusion/Label.qml +QtQuick/Controls.2/Fusion/Menu.qml +QtQuick/Controls.2/Fusion/MenuBar.qml +QtQuick/Controls.2/Fusion/MenuBarItem.qml +QtQuick/Controls.2/Fusion/MenuItem.qml +QtQuick/Controls.2/Fusion/MenuSeparator.qml +QtQuick/Controls.2/Fusion/Page.qml +QtQuick/Controls.2/Fusion/PageIndicator.qml +QtQuick/Controls.2/Fusion/Pane.qml +QtQuick/Controls.2/Fusion/Popup.qml +QtQuick/Controls.2/Fusion/ProgressBar.qml +QtQuick/Controls.2/Fusion/RadioButton.qml +QtQuick/Controls.2/Fusion/RadioDelegate.qml +QtQuick/Controls.2/Fusion/RadioIndicator.qml +QtQuick/Controls.2/Fusion/RangeSlider.qml +QtQuick/Controls.2/Fusion/RoundButton.qml +QtQuick/Controls.2/Fusion/ScrollBar.qml +QtQuick/Controls.2/Fusion/ScrollIndicator.qml +QtQuick/Controls.2/Fusion/Slider.qml +QtQuick/Controls.2/Fusion/SliderGroove.qml +QtQuick/Controls.2/Fusion/SliderHandle.qml +QtQuick/Controls.2/Fusion/SpinBox.qml +QtQuick/Controls.2/Fusion/SplitView.qml +QtQuick/Controls.2/Fusion/SwipeDelegate.qml +QtQuick/Controls.2/Fusion/Switch.qml +QtQuick/Controls.2/Fusion/SwitchDelegate.qml +QtQuick/Controls.2/Fusion/SwitchIndicator.qml +QtQuick/Controls.2/Fusion/TabBar.qml +QtQuick/Controls.2/Fusion/TabButton.qml +QtQuick/Controls.2/Fusion/TextArea.qml +QtQuick/Controls.2/Fusion/TextField.qml +QtQuick/Controls.2/Fusion/ToolBar.qml +QtQuick/Controls.2/Fusion/ToolButton.qml +QtQuick/Controls.2/Fusion/ToolSeparator.qml +QtQuick/Controls.2/Fusion/ToolTip.qml +QtQuick/Controls.2/Fusion/Tumbler.qml +QtQuick/Controls.2/Fusion/VerticalHeaderView.qml +QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin.so +QtQuick/Controls.2/Fusion/plugins.qmltypes +QtQuick/Controls.2/Fusion/qmldir +QtQuick/Controls.2/GroupBox.qml +QtQuick/Controls.2/HorizontalHeaderView.qml +QtQuick/Controls.2/Imagine/ApplicationWindow.qml +QtQuick/Controls.2/Imagine/BusyIndicator.qml +QtQuick/Controls.2/Imagine/Button.qml +QtQuick/Controls.2/Imagine/CheckBox.qml +QtQuick/Controls.2/Imagine/CheckDelegate.qml +QtQuick/Controls.2/Imagine/ComboBox.qml +QtQuick/Controls.2/Imagine/DelayButton.qml +QtQuick/Controls.2/Imagine/Dial.qml +QtQuick/Controls.2/Imagine/Dialog.qml +QtQuick/Controls.2/Imagine/DialogButtonBox.qml +QtQuick/Controls.2/Imagine/Drawer.qml +QtQuick/Controls.2/Imagine/Frame.qml +QtQuick/Controls.2/Imagine/GroupBox.qml +QtQuick/Controls.2/Imagine/HorizontalHeaderView.qml +QtQuick/Controls.2/Imagine/ItemDelegate.qml +QtQuick/Controls.2/Imagine/Label.qml +QtQuick/Controls.2/Imagine/Menu.qml +QtQuick/Controls.2/Imagine/MenuItem.qml +QtQuick/Controls.2/Imagine/MenuSeparator.qml +QtQuick/Controls.2/Imagine/Page.qml +QtQuick/Controls.2/Imagine/PageIndicator.qml +QtQuick/Controls.2/Imagine/Pane.qml +QtQuick/Controls.2/Imagine/Popup.qml +QtQuick/Controls.2/Imagine/ProgressBar.qml +QtQuick/Controls.2/Imagine/RadioButton.qml +QtQuick/Controls.2/Imagine/RadioDelegate.qml +QtQuick/Controls.2/Imagine/RangeSlider.qml +QtQuick/Controls.2/Imagine/RoundButton.qml +QtQuick/Controls.2/Imagine/ScrollBar.qml +QtQuick/Controls.2/Imagine/ScrollIndicator.qml +QtQuick/Controls.2/Imagine/Slider.qml +QtQuick/Controls.2/Imagine/SpinBox.qml +QtQuick/Controls.2/Imagine/SplitView.qml +QtQuick/Controls.2/Imagine/StackView.qml +QtQuick/Controls.2/Imagine/SwipeDelegate.qml +QtQuick/Controls.2/Imagine/SwipeView.qml +QtQuick/Controls.2/Imagine/Switch.qml +QtQuick/Controls.2/Imagine/SwitchDelegate.qml +QtQuick/Controls.2/Imagine/TabBar.qml +QtQuick/Controls.2/Imagine/TabButton.qml +QtQuick/Controls.2/Imagine/TextArea.qml +QtQuick/Controls.2/Imagine/TextField.qml +QtQuick/Controls.2/Imagine/ToolBar.qml +QtQuick/Controls.2/Imagine/ToolButton.qml +QtQuick/Controls.2/Imagine/ToolSeparator.qml +QtQuick/Controls.2/Imagine/ToolTip.qml +QtQuick/Controls.2/Imagine/Tumbler.qml +QtQuick/Controls.2/Imagine/VerticalHeaderView.qml +QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin.so +QtQuick/Controls.2/Imagine/plugins.qmltypes +QtQuick/Controls.2/Imagine/qmldir +QtQuick/Controls.2/ItemDelegate.qml +QtQuick/Controls.2/Label.qml +QtQuick/Controls.2/Material/ApplicationWindow.qml +QtQuick/Controls.2/Material/BoxShadow.qml +QtQuick/Controls.2/Material/BusyIndicator.qml +QtQuick/Controls.2/Material/Button.qml +QtQuick/Controls.2/Material/CheckBox.qml +QtQuick/Controls.2/Material/CheckDelegate.qml +QtQuick/Controls.2/Material/CheckIndicator.qml +QtQuick/Controls.2/Material/ComboBox.qml +QtQuick/Controls.2/Material/CursorDelegate.qml +QtQuick/Controls.2/Material/DelayButton.qml +QtQuick/Controls.2/Material/Dial.qml +QtQuick/Controls.2/Material/Dialog.qml +QtQuick/Controls.2/Material/DialogButtonBox.qml +QtQuick/Controls.2/Material/Drawer.qml +QtQuick/Controls.2/Material/ElevationEffect.qml +QtQuick/Controls.2/Material/Frame.qml +QtQuick/Controls.2/Material/GroupBox.qml +QtQuick/Controls.2/Material/HorizontalHeaderView.qml +QtQuick/Controls.2/Material/ItemDelegate.qml +QtQuick/Controls.2/Material/Label.qml +QtQuick/Controls.2/Material/Menu.qml +QtQuick/Controls.2/Material/MenuBar.qml +QtQuick/Controls.2/Material/MenuBarItem.qml +QtQuick/Controls.2/Material/MenuItem.qml +QtQuick/Controls.2/Material/MenuSeparator.qml +QtQuick/Controls.2/Material/Page.qml +QtQuick/Controls.2/Material/PageIndicator.qml +QtQuick/Controls.2/Material/Pane.qml +QtQuick/Controls.2/Material/Popup.qml +QtQuick/Controls.2/Material/ProgressBar.qml +QtQuick/Controls.2/Material/RadioButton.qml +QtQuick/Controls.2/Material/RadioDelegate.qml +QtQuick/Controls.2/Material/RadioIndicator.qml +QtQuick/Controls.2/Material/RangeSlider.qml +QtQuick/Controls.2/Material/RectangularGlow.qml +QtQuick/Controls.2/Material/RoundButton.qml +QtQuick/Controls.2/Material/ScrollBar.qml +QtQuick/Controls.2/Material/ScrollIndicator.qml +QtQuick/Controls.2/Material/Slider.qml +QtQuick/Controls.2/Material/SliderHandle.qml +QtQuick/Controls.2/Material/SpinBox.qml +QtQuick/Controls.2/Material/SplitView.qml +QtQuick/Controls.2/Material/StackView.qml +QtQuick/Controls.2/Material/SwipeDelegate.qml +QtQuick/Controls.2/Material/SwipeView.qml +QtQuick/Controls.2/Material/Switch.qml +QtQuick/Controls.2/Material/SwitchDelegate.qml +QtQuick/Controls.2/Material/SwitchIndicator.qml +QtQuick/Controls.2/Material/TabBar.qml +QtQuick/Controls.2/Material/TabButton.qml +QtQuick/Controls.2/Material/TextArea.qml +QtQuick/Controls.2/Material/TextField.qml +QtQuick/Controls.2/Material/ToolBar.qml +QtQuick/Controls.2/Material/ToolButton.qml +QtQuick/Controls.2/Material/ToolSeparator.qml +QtQuick/Controls.2/Material/ToolTip.qml +QtQuick/Controls.2/Material/Tumbler.qml +QtQuick/Controls.2/Material/VerticalHeaderView.qml +QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin.so +QtQuick/Controls.2/Material/plugins.qmltypes +QtQuick/Controls.2/Material/qmldir +QtQuick/Controls.2/Menu.qml +QtQuick/Controls.2/MenuBar.qml +QtQuick/Controls.2/MenuBarItem.qml +QtQuick/Controls.2/MenuItem.qml +QtQuick/Controls.2/MenuSeparator.qml +QtQuick/Controls.2/Page.qml +QtQuick/Controls.2/PageIndicator.qml +QtQuick/Controls.2/Pane.qml +QtQuick/Controls.2/Popup.qml +QtQuick/Controls.2/ProgressBar.qml +QtQuick/Controls.2/RadioButton.qml +QtQuick/Controls.2/RadioDelegate.qml +QtQuick/Controls.2/RangeSlider.qml +QtQuick/Controls.2/RoundButton.qml +QtQuick/Controls.2/ScrollBar.qml +QtQuick/Controls.2/ScrollIndicator.qml +QtQuick/Controls.2/ScrollView.qml +QtQuick/Controls.2/Slider.qml +QtQuick/Controls.2/SpinBox.qml +QtQuick/Controls.2/SplitView.qml +QtQuick/Controls.2/StackView.qml +QtQuick/Controls.2/SwipeDelegate.qml +QtQuick/Controls.2/SwipeView.qml +QtQuick/Controls.2/Switch.qml +QtQuick/Controls.2/SwitchDelegate.qml +QtQuick/Controls.2/TabBar.qml +QtQuick/Controls.2/TabButton.qml +QtQuick/Controls.2/TextArea.qml +QtQuick/Controls.2/TextField.qml +QtQuick/Controls.2/ToolBar.qml +QtQuick/Controls.2/ToolButton.qml +QtQuick/Controls.2/ToolSeparator.qml +QtQuick/Controls.2/ToolTip.qml +QtQuick/Controls.2/Tumbler.qml +QtQuick/Controls.2/Universal/ApplicationWindow.qml +QtQuick/Controls.2/Universal/BusyIndicator.qml +QtQuick/Controls.2/Universal/Button.qml +QtQuick/Controls.2/Universal/CheckBox.qml +QtQuick/Controls.2/Universal/CheckDelegate.qml +QtQuick/Controls.2/Universal/CheckIndicator.qml +QtQuick/Controls.2/Universal/ComboBox.qml +QtQuick/Controls.2/Universal/DelayButton.qml +QtQuick/Controls.2/Universal/Dial.qml +QtQuick/Controls.2/Universal/Dialog.qml +QtQuick/Controls.2/Universal/DialogButtonBox.qml +QtQuick/Controls.2/Universal/Drawer.qml +QtQuick/Controls.2/Universal/Frame.qml +QtQuick/Controls.2/Universal/GroupBox.qml +QtQuick/Controls.2/Universal/HorizontalHeaderView.qml +QtQuick/Controls.2/Universal/ItemDelegate.qml +QtQuick/Controls.2/Universal/Label.qml +QtQuick/Controls.2/Universal/Menu.qml +QtQuick/Controls.2/Universal/MenuBar.qml +QtQuick/Controls.2/Universal/MenuBarItem.qml +QtQuick/Controls.2/Universal/MenuItem.qml +QtQuick/Controls.2/Universal/MenuSeparator.qml +QtQuick/Controls.2/Universal/Page.qml +QtQuick/Controls.2/Universal/PageIndicator.qml +QtQuick/Controls.2/Universal/Pane.qml +QtQuick/Controls.2/Universal/Popup.qml +QtQuick/Controls.2/Universal/ProgressBar.qml +QtQuick/Controls.2/Universal/RadioButton.qml +QtQuick/Controls.2/Universal/RadioDelegate.qml +QtQuick/Controls.2/Universal/RadioIndicator.qml +QtQuick/Controls.2/Universal/RangeSlider.qml +QtQuick/Controls.2/Universal/RoundButton.qml +QtQuick/Controls.2/Universal/ScrollBar.qml +QtQuick/Controls.2/Universal/ScrollIndicator.qml +QtQuick/Controls.2/Universal/Slider.qml +QtQuick/Controls.2/Universal/SpinBox.qml +QtQuick/Controls.2/Universal/SplitView.qml +QtQuick/Controls.2/Universal/StackView.qml +QtQuick/Controls.2/Universal/SwipeDelegate.qml +QtQuick/Controls.2/Universal/Switch.qml +QtQuick/Controls.2/Universal/SwitchDelegate.qml +QtQuick/Controls.2/Universal/SwitchIndicator.qml +QtQuick/Controls.2/Universal/TabBar.qml +QtQuick/Controls.2/Universal/TabButton.qml +QtQuick/Controls.2/Universal/TextArea.qml +QtQuick/Controls.2/Universal/TextField.qml +QtQuick/Controls.2/Universal/ToolBar.qml +QtQuick/Controls.2/Universal/ToolButton.qml +QtQuick/Controls.2/Universal/ToolSeparator.qml +QtQuick/Controls.2/Universal/ToolTip.qml +QtQuick/Controls.2/Universal/Tumbler.qml +QtQuick/Controls.2/Universal/VerticalHeaderView.qml +QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin.so +QtQuick/Controls.2/Universal/plugins.qmltypes +QtQuick/Controls.2/Universal/qmldir +QtQuick/Controls.2/VerticalHeaderView.qml +QtQuick/Controls.2/designer/AbstractButtonSection.qml +QtQuick/Controls.2/designer/BusyIndicatorSpecifics.qml +QtQuick/Controls.2/designer/ButtonSection.qml +QtQuick/Controls.2/designer/ButtonSpecifics.qml +QtQuick/Controls.2/designer/CheckBoxSpecifics.qml +QtQuick/Controls.2/designer/CheckDelegateSpecifics.qml +QtQuick/Controls.2/designer/CheckSection.qml +QtQuick/Controls.2/designer/ComboBoxSpecifics.qml +QtQuick/Controls.2/designer/ContainerSection.qml +QtQuick/Controls.2/designer/ControlSection.qml +QtQuick/Controls.2/designer/ControlSpecifics.qml +QtQuick/Controls.2/designer/DelayButtonSpecifics.qml +QtQuick/Controls.2/designer/DialSpecifics.qml +QtQuick/Controls.2/designer/FrameSpecifics.qml +QtQuick/Controls.2/designer/GroupBoxSpecifics.qml +QtQuick/Controls.2/designer/InsetSection.qml +QtQuick/Controls.2/designer/ItemDelegateSection.qml +QtQuick/Controls.2/designer/ItemDelegateSpecifics.qml +QtQuick/Controls.2/designer/LabelSpecifics.qml +QtQuick/Controls.2/designer/PaddingSection.qml +QtQuick/Controls.2/designer/PageIndicatorSpecifics.qml +QtQuick/Controls.2/designer/PageSpecifics.qml +QtQuick/Controls.2/designer/PaneSection.qml +QtQuick/Controls.2/designer/PaneSpecifics.qml +QtQuick/Controls.2/designer/ProgressBarSpecifics.qml +QtQuick/Controls.2/designer/RadioButtonSpecifics.qml +QtQuick/Controls.2/designer/RadioDelegateSpecifics.qml +QtQuick/Controls.2/designer/RangeSliderSpecifics.qml +QtQuick/Controls.2/designer/RoundButtonSpecifics.qml +QtQuick/Controls.2/designer/ScrollViewSpecifics.qml +QtQuick/Controls.2/designer/SliderSpecifics.qml +QtQuick/Controls.2/designer/SpinBoxSpecifics.qml +QtQuick/Controls.2/designer/StackViewSpecifics.qml +QtQuick/Controls.2/designer/SwipeDelegateSpecifics.qml +QtQuick/Controls.2/designer/SwipeViewSpecifics.qml +QtQuick/Controls.2/designer/SwitchDelegateSpecifics.qml +QtQuick/Controls.2/designer/SwitchSpecifics.qml +QtQuick/Controls.2/designer/TabBarSpecifics.qml +QtQuick/Controls.2/designer/TabButtonSpecifics.qml +QtQuick/Controls.2/designer/TextAreaSpecifics.qml +QtQuick/Controls.2/designer/TextFieldSpecifics.qml +QtQuick/Controls.2/designer/ToolBarSpecifics.qml +QtQuick/Controls.2/designer/ToolButtonSpecifics.qml +QtQuick/Controls.2/designer/ToolSeparatorSpecifics.qml +QtQuick/Controls.2/designer/TumblerSpecifics.qml +QtQuick/Controls.2/designer/images/busyindicator-icon.png +QtQuick/Controls.2/designer/images/busyindicator-icon16.png +QtQuick/Controls.2/designer/images/busyindicator-icon@2x.png +QtQuick/Controls.2/designer/images/button-icon.png +QtQuick/Controls.2/designer/images/button-icon16.png +QtQuick/Controls.2/designer/images/button-icon@2x.png +QtQuick/Controls.2/designer/images/checkbox-icon.png +QtQuick/Controls.2/designer/images/checkbox-icon16.png +QtQuick/Controls.2/designer/images/checkbox-icon@2x.png +QtQuick/Controls.2/designer/images/combobox-icon.png +QtQuick/Controls.2/designer/images/combobox-icon16.png +QtQuick/Controls.2/designer/images/combobox-icon@2x.png +QtQuick/Controls.2/designer/images/delaybutton-icon.png +QtQuick/Controls.2/designer/images/delaybutton-icon16.png +QtQuick/Controls.2/designer/images/delaybutton-icon@2x.png +QtQuick/Controls.2/designer/images/dial-icon.png +QtQuick/Controls.2/designer/images/dial-icon16.png +QtQuick/Controls.2/designer/images/dial-icon@2x.png +QtQuick/Controls.2/designer/images/frame-icon.png +QtQuick/Controls.2/designer/images/frame-icon16.png +QtQuick/Controls.2/designer/images/frame-icon@2x.png +QtQuick/Controls.2/designer/images/groupbox-icon.png +QtQuick/Controls.2/designer/images/groupbox-icon16.png +QtQuick/Controls.2/designer/images/groupbox-icon@2x.png +QtQuick/Controls.2/designer/images/itemdelegate-icon.png +QtQuick/Controls.2/designer/images/itemdelegate-icon16.png +QtQuick/Controls.2/designer/images/itemdelegate-icon@2x.png +QtQuick/Controls.2/designer/images/label-icon.png +QtQuick/Controls.2/designer/images/label-icon16.png +QtQuick/Controls.2/designer/images/label-icon@2x.png +QtQuick/Controls.2/designer/images/page-icon.png +QtQuick/Controls.2/designer/images/page-icon16.png +QtQuick/Controls.2/designer/images/page-icon@2x.png +QtQuick/Controls.2/designer/images/pageindicator-icon.png +QtQuick/Controls.2/designer/images/pageindicator-icon16.png +QtQuick/Controls.2/designer/images/pageindicator-icon@2x.png +QtQuick/Controls.2/designer/images/pane-icon.png +QtQuick/Controls.2/designer/images/pane-icon16.png +QtQuick/Controls.2/designer/images/pane-icon@2x.png +QtQuick/Controls.2/designer/images/progressbar-icon.png +QtQuick/Controls.2/designer/images/progressbar-icon16.png +QtQuick/Controls.2/designer/images/progressbar-icon@2x.png +QtQuick/Controls.2/designer/images/radiobutton-icon.png +QtQuick/Controls.2/designer/images/radiobutton-icon16.png +QtQuick/Controls.2/designer/images/radiobutton-icon@2x.png +QtQuick/Controls.2/designer/images/rangeslider-icon.png +QtQuick/Controls.2/designer/images/rangeslider-icon16.png +QtQuick/Controls.2/designer/images/rangeslider-icon@2x.png +QtQuick/Controls.2/designer/images/roundbutton-icon.png +QtQuick/Controls.2/designer/images/roundbutton-icon16.png +QtQuick/Controls.2/designer/images/roundbutton-icon@2x.png +QtQuick/Controls.2/designer/images/scrollview-icon.png +QtQuick/Controls.2/designer/images/scrollview-icon16.png +QtQuick/Controls.2/designer/images/scrollview-icon@2x.png +QtQuick/Controls.2/designer/images/slider-icon.png +QtQuick/Controls.2/designer/images/slider-icon16.png +QtQuick/Controls.2/designer/images/slider-icon@2x.png +QtQuick/Controls.2/designer/images/spinbox-icon.png +QtQuick/Controls.2/designer/images/spinbox-icon16.png +QtQuick/Controls.2/designer/images/spinbox-icon@2x.png +QtQuick/Controls.2/designer/images/stackview-icon.png +QtQuick/Controls.2/designer/images/stackview-icon16.png +QtQuick/Controls.2/designer/images/stackview-icon@2x.png +QtQuick/Controls.2/designer/images/swipeview-icon.png +QtQuick/Controls.2/designer/images/swipeview-icon16.png +QtQuick/Controls.2/designer/images/swipeview-icon@2x.png +QtQuick/Controls.2/designer/images/switch-icon.png +QtQuick/Controls.2/designer/images/switch-icon16.png +QtQuick/Controls.2/designer/images/switch-icon@2x.png +QtQuick/Controls.2/designer/images/textarea-icon.png +QtQuick/Controls.2/designer/images/textarea-icon16.png +QtQuick/Controls.2/designer/images/textarea-icon@2x.png +QtQuick/Controls.2/designer/images/textfield-icon.png +QtQuick/Controls.2/designer/images/textfield-icon16.png +QtQuick/Controls.2/designer/images/textfield-icon@2x.png +QtQuick/Controls.2/designer/images/toolbar-icon.png +QtQuick/Controls.2/designer/images/toolbar-icon16.png +QtQuick/Controls.2/designer/images/toolbar-icon@2x.png +QtQuick/Controls.2/designer/images/toolbutton-icon.png +QtQuick/Controls.2/designer/images/toolbutton-icon16.png +QtQuick/Controls.2/designer/images/toolbutton-icon@2x.png +QtQuick/Controls.2/designer/images/toolseparator-icon.png +QtQuick/Controls.2/designer/images/toolseparator-icon16.png +QtQuick/Controls.2/designer/images/toolseparator-icon@2x.png +QtQuick/Controls.2/designer/images/tumbler-icon.png +QtQuick/Controls.2/designer/images/tumbler-icon16.png +QtQuick/Controls.2/designer/images/tumbler-icon@2x.png +QtQuick/Controls.2/designer/qtquickcontrols2.metainfo +QtQuick/Controls.2/libqtquickcontrols2plugin.so +QtQuick/Controls.2/plugins.qmltypes +QtQuick/Controls.2/qmldir +QtQuick/Controls/ApplicationWindow.qml +QtQuick/Controls/ApplicationWindow.qmlc +QtQuick/Controls/BusyIndicator.qml +QtQuick/Controls/BusyIndicator.qmlc +QtQuick/Controls/Button.qml +QtQuick/Controls/Button.qmlc +QtQuick/Controls/Calendar.qml +QtQuick/Controls/Calendar.qmlc +QtQuick/Controls/CheckBox.qml +QtQuick/Controls/CheckBox.qmlc +QtQuick/Controls/ComboBox.qml +QtQuick/Controls/ComboBox.qmlc +QtQuick/Controls/GroupBox.qml +QtQuick/Controls/GroupBox.qmlc +QtQuick/Controls/Label.qml +QtQuick/Controls/Label.qmlc +QtQuick/Controls/Menu.qml +QtQuick/Controls/Menu.qmlc +QtQuick/Controls/MenuBar.qml +QtQuick/Controls/MenuBar.qmlc +QtQuick/Controls/Private/AbstractCheckable.qml +QtQuick/Controls/Private/AbstractCheckable.qmlc +QtQuick/Controls/Private/BasicButton.qml +QtQuick/Controls/Private/BasicButton.qmlc +QtQuick/Controls/Private/BasicTableView.qml +QtQuick/Controls/Private/BasicTableView.qmlc +QtQuick/Controls/Private/CalendarHeaderModel.qml +QtQuick/Controls/Private/CalendarHeaderModel.qmlc +QtQuick/Controls/Private/CalendarUtils.js +QtQuick/Controls/Private/CalendarUtils.jsc +QtQuick/Controls/Private/ColumnMenuContent.qml +QtQuick/Controls/Private/ColumnMenuContent.qmlc +QtQuick/Controls/Private/ContentItem.qml +QtQuick/Controls/Private/ContentItem.qmlc +QtQuick/Controls/Private/Control.qml +QtQuick/Controls/Private/Control.qmlc +QtQuick/Controls/Private/EditMenu.qml +QtQuick/Controls/Private/EditMenu.qmlc +QtQuick/Controls/Private/EditMenu_base.qml +QtQuick/Controls/Private/EditMenu_base.qmlc +QtQuick/Controls/Private/FastGlow.qml +QtQuick/Controls/Private/FastGlow.qmlc +QtQuick/Controls/Private/FocusFrame.qml +QtQuick/Controls/Private/FocusFrame.qmlc +QtQuick/Controls/Private/HoverButton.qml +QtQuick/Controls/Private/HoverButton.qmlc +QtQuick/Controls/Private/MenuContentItem.qml +QtQuick/Controls/Private/MenuContentItem.qmlc +QtQuick/Controls/Private/MenuContentScroller.qml +QtQuick/Controls/Private/MenuContentScroller.qmlc +QtQuick/Controls/Private/MenuItemSubControls.qml +QtQuick/Controls/Private/MenuItemSubControls.qmlc +QtQuick/Controls/Private/ModalPopupBehavior.qml +QtQuick/Controls/Private/ModalPopupBehavior.qmlc +QtQuick/Controls/Private/ScrollBar.qml +QtQuick/Controls/Private/ScrollBar.qmlc +QtQuick/Controls/Private/ScrollViewHelper.qml +QtQuick/Controls/Private/ScrollViewHelper.qmlc +QtQuick/Controls/Private/SourceProxy.qml +QtQuick/Controls/Private/SourceProxy.qmlc +QtQuick/Controls/Private/StackView.js +QtQuick/Controls/Private/StackView.jsc +QtQuick/Controls/Private/StackViewSlideDelegate.qml +QtQuick/Controls/Private/StackViewSlideDelegate.qmlc +QtQuick/Controls/Private/Style.qml +QtQuick/Controls/Private/Style.qmlc +QtQuick/Controls/Private/SystemPaletteSingleton.qml +QtQuick/Controls/Private/SystemPaletteSingleton.qmlc +QtQuick/Controls/Private/TabBar.qml +QtQuick/Controls/Private/TabBar.qmlc +QtQuick/Controls/Private/TableViewItemDelegateLoader.qml +QtQuick/Controls/Private/TableViewItemDelegateLoader.qmlc +QtQuick/Controls/Private/TableViewSelection.qml +QtQuick/Controls/Private/TableViewSelection.qmlc +QtQuick/Controls/Private/TextHandle.qml +QtQuick/Controls/Private/TextHandle.qmlc +QtQuick/Controls/Private/TextInputWithHandles.qml +QtQuick/Controls/Private/TextInputWithHandles.qmlc +QtQuick/Controls/Private/TextSingleton.qml +QtQuick/Controls/Private/TextSingleton.qmlc +QtQuick/Controls/Private/ToolMenuButton.qml +QtQuick/Controls/Private/ToolMenuButton.qmlc +QtQuick/Controls/Private/TreeViewItemDelegateLoader.qml +QtQuick/Controls/Private/TreeViewItemDelegateLoader.qmlc +QtQuick/Controls/Private/qmldir +QtQuick/Controls/Private/style.js +QtQuick/Controls/Private/style.jsc +QtQuick/Controls/ProgressBar.qml +QtQuick/Controls/ProgressBar.qmlc +QtQuick/Controls/RadioButton.qml +QtQuick/Controls/RadioButton.qmlc +QtQuick/Controls/ScrollView.qml +QtQuick/Controls/ScrollView.qmlc +QtQuick/Controls/Slider.qml +QtQuick/Controls/Slider.qmlc +QtQuick/Controls/SpinBox.qml +QtQuick/Controls/SpinBox.qmlc +QtQuick/Controls/SplitView.qml +QtQuick/Controls/SplitView.qmlc +QtQuick/Controls/StackView.qml +QtQuick/Controls/StackView.qmlc +QtQuick/Controls/StackViewDelegate.qml +QtQuick/Controls/StackViewDelegate.qmlc +QtQuick/Controls/StackViewTransition.qml +QtQuick/Controls/StackViewTransition.qmlc +QtQuick/Controls/StatusBar.qml +QtQuick/Controls/StatusBar.qmlc +QtQuick/Controls/Styles/Base/ApplicationWindowStyle.qml +QtQuick/Controls/Styles/Base/ApplicationWindowStyle.qmlc +QtQuick/Controls/Styles/Base/BasicTableViewStyle.qml +QtQuick/Controls/Styles/Base/BasicTableViewStyle.qmlc +QtQuick/Controls/Styles/Base/BusyIndicatorStyle.qml +QtQuick/Controls/Styles/Base/BusyIndicatorStyle.qmlc +QtQuick/Controls/Styles/Base/ButtonStyle.qml +QtQuick/Controls/Styles/Base/ButtonStyle.qmlc +QtQuick/Controls/Styles/Base/CalendarStyle.qml +QtQuick/Controls/Styles/Base/CalendarStyle.qmlc +QtQuick/Controls/Styles/Base/CheckBoxStyle.qml +QtQuick/Controls/Styles/Base/CheckBoxStyle.qmlc +QtQuick/Controls/Styles/Base/CircularButtonStyle.qml +QtQuick/Controls/Styles/Base/CircularButtonStyle.qmlc +QtQuick/Controls/Styles/Base/CircularGaugeStyle.qml +QtQuick/Controls/Styles/Base/CircularGaugeStyle.qmlc +QtQuick/Controls/Styles/Base/CircularTickmarkLabelStyle.qml +QtQuick/Controls/Styles/Base/CircularTickmarkLabelStyle.qmlc +QtQuick/Controls/Styles/Base/ComboBoxStyle.qml +QtQuick/Controls/Styles/Base/ComboBoxStyle.qmlc +QtQuick/Controls/Styles/Base/CommonStyleHelper.qml +QtQuick/Controls/Styles/Base/CommonStyleHelper.qmlc +QtQuick/Controls/Styles/Base/DelayButtonStyle.qml +QtQuick/Controls/Styles/Base/DelayButtonStyle.qmlc +QtQuick/Controls/Styles/Base/DialStyle.qml +QtQuick/Controls/Styles/Base/DialStyle.qmlc +QtQuick/Controls/Styles/Base/FocusFrameStyle.qml +QtQuick/Controls/Styles/Base/FocusFrameStyle.qmlc +QtQuick/Controls/Styles/Base/GaugeStyle.qml +QtQuick/Controls/Styles/Base/GaugeStyle.qmlc +QtQuick/Controls/Styles/Base/GroupBoxStyle.qml +QtQuick/Controls/Styles/Base/GroupBoxStyle.qmlc +QtQuick/Controls/Styles/Base/HandleStyle.qml +QtQuick/Controls/Styles/Base/HandleStyle.qmlc +QtQuick/Controls/Styles/Base/HandleStyleHelper.qml +QtQuick/Controls/Styles/Base/HandleStyleHelper.qmlc +QtQuick/Controls/Styles/Base/MenuBarStyle.qml +QtQuick/Controls/Styles/Base/MenuBarStyle.qmlc +QtQuick/Controls/Styles/Base/MenuStyle.qml +QtQuick/Controls/Styles/Base/MenuStyle.qmlc +QtQuick/Controls/Styles/Base/PieMenuStyle.qml +QtQuick/Controls/Styles/Base/PieMenuStyle.qmlc +QtQuick/Controls/Styles/Base/ProgressBarStyle.qml +QtQuick/Controls/Styles/Base/ProgressBarStyle.qmlc +QtQuick/Controls/Styles/Base/RadioButtonStyle.qml +QtQuick/Controls/Styles/Base/RadioButtonStyle.qmlc +QtQuick/Controls/Styles/Base/ScrollViewStyle.qml +QtQuick/Controls/Styles/Base/ScrollViewStyle.qmlc +QtQuick/Controls/Styles/Base/SliderStyle.qml +QtQuick/Controls/Styles/Base/SliderStyle.qmlc +QtQuick/Controls/Styles/Base/SpinBoxStyle.qml +QtQuick/Controls/Styles/Base/SpinBoxStyle.qmlc +QtQuick/Controls/Styles/Base/StatusBarStyle.qml +QtQuick/Controls/Styles/Base/StatusBarStyle.qmlc +QtQuick/Controls/Styles/Base/StatusIndicatorStyle.qml +QtQuick/Controls/Styles/Base/StatusIndicatorStyle.qmlc +QtQuick/Controls/Styles/Base/SwitchStyle.qml +QtQuick/Controls/Styles/Base/SwitchStyle.qmlc +QtQuick/Controls/Styles/Base/TabViewStyle.qml +QtQuick/Controls/Styles/Base/TabViewStyle.qmlc +QtQuick/Controls/Styles/Base/TableViewStyle.qml +QtQuick/Controls/Styles/Base/TableViewStyle.qmlc +QtQuick/Controls/Styles/Base/TextAreaStyle.qml +QtQuick/Controls/Styles/Base/TextAreaStyle.qmlc +QtQuick/Controls/Styles/Base/TextFieldStyle.qml +QtQuick/Controls/Styles/Base/TextFieldStyle.qmlc +QtQuick/Controls/Styles/Base/ToggleButtonStyle.qml +QtQuick/Controls/Styles/Base/ToggleButtonStyle.qmlc +QtQuick/Controls/Styles/Base/ToolBarStyle.qml +QtQuick/Controls/Styles/Base/ToolBarStyle.qmlc +QtQuick/Controls/Styles/Base/ToolButtonStyle.qml +QtQuick/Controls/Styles/Base/ToolButtonStyle.qmlc +QtQuick/Controls/Styles/Base/TreeViewStyle.qml +QtQuick/Controls/Styles/Base/TreeViewStyle.qmlc +QtQuick/Controls/Styles/Base/TumblerStyle.qml +QtQuick/Controls/Styles/Base/TumblerStyle.qmlc +QtQuick/Controls/Styles/Base/images/arrow-down.png +QtQuick/Controls/Styles/Base/images/arrow-down@2x.png +QtQuick/Controls/Styles/Base/images/arrow-left.png +QtQuick/Controls/Styles/Base/images/arrow-left@2x.png +QtQuick/Controls/Styles/Base/images/arrow-right.png +QtQuick/Controls/Styles/Base/images/arrow-right@2x.png +QtQuick/Controls/Styles/Base/images/arrow-up.png +QtQuick/Controls/Styles/Base/images/arrow-up@2x.png +QtQuick/Controls/Styles/Base/images/button.png +QtQuick/Controls/Styles/Base/images/button_down.png +QtQuick/Controls/Styles/Base/images/check.png +QtQuick/Controls/Styles/Base/images/check@2x.png +QtQuick/Controls/Styles/Base/images/editbox.png +QtQuick/Controls/Styles/Base/images/focusframe.png +QtQuick/Controls/Styles/Base/images/groupbox.png +QtQuick/Controls/Styles/Base/images/header.png +QtQuick/Controls/Styles/Base/images/knob.png +QtQuick/Controls/Styles/Base/images/leftanglearrow.png +QtQuick/Controls/Styles/Base/images/needle.png +QtQuick/Controls/Styles/Base/images/progress-indeterminate.png +QtQuick/Controls/Styles/Base/images/rightanglearrow.png +QtQuick/Controls/Styles/Base/images/scrollbar-handle-horizontal.png +QtQuick/Controls/Styles/Base/images/scrollbar-handle-transient.png +QtQuick/Controls/Styles/Base/images/scrollbar-handle-vertical.png +QtQuick/Controls/Styles/Base/images/slider-groove.png +QtQuick/Controls/Styles/Base/images/slider-handle.png +QtQuick/Controls/Styles/Base/images/spinner_large.png +QtQuick/Controls/Styles/Base/images/spinner_medium.png +QtQuick/Controls/Styles/Base/images/spinner_small.png +QtQuick/Controls/Styles/Base/images/tab.png +QtQuick/Controls/Styles/Base/images/tab_selected.png +QtQuick/Controls/Styles/Desktop/ApplicationWindowStyle.qml +QtQuick/Controls/Styles/Desktop/ApplicationWindowStyle.qmlc +QtQuick/Controls/Styles/Desktop/BusyIndicatorStyle.qml +QtQuick/Controls/Styles/Desktop/BusyIndicatorStyle.qmlc +QtQuick/Controls/Styles/Desktop/ButtonStyle.qml +QtQuick/Controls/Styles/Desktop/ButtonStyle.qmlc +QtQuick/Controls/Styles/Desktop/CalendarStyle.qml +QtQuick/Controls/Styles/Desktop/CalendarStyle.qmlc +QtQuick/Controls/Styles/Desktop/CheckBoxStyle.qml +QtQuick/Controls/Styles/Desktop/CheckBoxStyle.qmlc +QtQuick/Controls/Styles/Desktop/ComboBoxStyle.qml +QtQuick/Controls/Styles/Desktop/ComboBoxStyle.qmlc +QtQuick/Controls/Styles/Desktop/FocusFrameStyle.qml +QtQuick/Controls/Styles/Desktop/FocusFrameStyle.qmlc +QtQuick/Controls/Styles/Desktop/GroupBoxStyle.qml +QtQuick/Controls/Styles/Desktop/GroupBoxStyle.qmlc +QtQuick/Controls/Styles/Desktop/MenuBarStyle.qml +QtQuick/Controls/Styles/Desktop/MenuBarStyle.qmlc +QtQuick/Controls/Styles/Desktop/MenuStyle.qml +QtQuick/Controls/Styles/Desktop/MenuStyle.qmlc +QtQuick/Controls/Styles/Desktop/ProgressBarStyle.qml +QtQuick/Controls/Styles/Desktop/ProgressBarStyle.qmlc +QtQuick/Controls/Styles/Desktop/RadioButtonStyle.qml +QtQuick/Controls/Styles/Desktop/RadioButtonStyle.qmlc +QtQuick/Controls/Styles/Desktop/RowItemSingleton.qml +QtQuick/Controls/Styles/Desktop/RowItemSingleton.qmlc +QtQuick/Controls/Styles/Desktop/ScrollViewStyle.qml +QtQuick/Controls/Styles/Desktop/ScrollViewStyle.qmlc +QtQuick/Controls/Styles/Desktop/SliderStyle.qml +QtQuick/Controls/Styles/Desktop/SliderStyle.qmlc +QtQuick/Controls/Styles/Desktop/SpinBoxStyle.qml +QtQuick/Controls/Styles/Desktop/SpinBoxStyle.qmlc +QtQuick/Controls/Styles/Desktop/StatusBarStyle.qml +QtQuick/Controls/Styles/Desktop/StatusBarStyle.qmlc +QtQuick/Controls/Styles/Desktop/SwitchStyle.qml +QtQuick/Controls/Styles/Desktop/SwitchStyle.qmlc +QtQuick/Controls/Styles/Desktop/TabViewStyle.qml +QtQuick/Controls/Styles/Desktop/TabViewStyle.qmlc +QtQuick/Controls/Styles/Desktop/TableViewStyle.qml +QtQuick/Controls/Styles/Desktop/TableViewStyle.qmlc +QtQuick/Controls/Styles/Desktop/TextAreaStyle.qml +QtQuick/Controls/Styles/Desktop/TextAreaStyle.qmlc +QtQuick/Controls/Styles/Desktop/TextFieldStyle.qml +QtQuick/Controls/Styles/Desktop/TextFieldStyle.qmlc +QtQuick/Controls/Styles/Desktop/ToolBarStyle.qml +QtQuick/Controls/Styles/Desktop/ToolBarStyle.qmlc +QtQuick/Controls/Styles/Desktop/ToolButtonStyle.qml +QtQuick/Controls/Styles/Desktop/ToolButtonStyle.qmlc +QtQuick/Controls/Styles/Desktop/TreeViewStyle.qml +QtQuick/Controls/Styles/Desktop/TreeViewStyle.qmlc +QtQuick/Controls/Styles/Desktop/qmldir +QtQuick/Controls/Styles/Flat/libqtquickextrasflatplugin.so +QtQuick/Controls/Styles/Flat/plugins.qmltypes +QtQuick/Controls/Styles/Flat/qmldir +QtQuick/Controls/Styles/qmldir +QtQuick/Controls/Switch.qml +QtQuick/Controls/Switch.qmlc +QtQuick/Controls/Tab.qml +QtQuick/Controls/Tab.qmlc +QtQuick/Controls/TabView.qml +QtQuick/Controls/TabView.qmlc +QtQuick/Controls/TableView.qml +QtQuick/Controls/TableView.qmlc +QtQuick/Controls/TableViewColumn.qml +QtQuick/Controls/TableViewColumn.qmlc +QtQuick/Controls/TextArea.qml +QtQuick/Controls/TextArea.qmlc +QtQuick/Controls/TextField.qml +QtQuick/Controls/TextField.qmlc +QtQuick/Controls/ToolBar.qml +QtQuick/Controls/ToolBar.qmlc +QtQuick/Controls/ToolButton.qml +QtQuick/Controls/ToolButton.qmlc +QtQuick/Controls/TreeView.qml +QtQuick/Controls/TreeView.qmlc +QtQuick/Controls/libqtquickcontrolsplugin.so QtQuick/Controls/plugins.qmltypes QtQuick/Controls/qmldir -QtQuick/Controls/libqtquickcontrols2plugin.so -QtQuick/Controls/Basic/AbstractButton.qml -QtQuick/Controls/Basic/Action.qml -QtQuick/Controls/Basic/ActionGroup.qml -QtQuick/Controls/Basic/ApplicationWindow.qml -QtQuick/Controls/Basic/BusyIndicator.qml -QtQuick/Controls/Basic/Button.qml -QtQuick/Controls/Basic/ButtonGroup.qml -QtQuick/Controls/Basic/Calendar.qml -QtQuick/Controls/Basic/CalendarModel.qml -QtQuick/Controls/Basic/CheckBox.qml -QtQuick/Controls/Basic/CheckDelegate.qml -QtQuick/Controls/Basic/ComboBox.qml -QtQuick/Controls/Basic/Container.qml -QtQuick/Controls/Basic/Control.qml -QtQuick/Controls/Basic/DayOfWeekRow.qml -QtQuick/Controls/Basic/DelayButton.qml -QtQuick/Controls/Basic/Dial.qml -QtQuick/Controls/Basic/Dialog.qml -QtQuick/Controls/Basic/DialogButtonBox.qml -QtQuick/Controls/Basic/Drawer.qml -QtQuick/Controls/Basic/Frame.qml -QtQuick/Controls/Basic/GroupBox.qml -QtQuick/Controls/Basic/HorizontalHeaderView.qml -QtQuick/Controls/Basic/ItemDelegate.qml -QtQuick/Controls/Basic/Label.qml -QtQuick/Controls/Basic/Menu.qml -QtQuick/Controls/Basic/MenuBar.qml -QtQuick/Controls/Basic/MenuBarItem.qml -QtQuick/Controls/Basic/MenuItem.qml -QtQuick/Controls/Basic/MenuSeparator.qml -QtQuick/Controls/Basic/MonthGrid.qml -QtQuick/Controls/Basic/Page.qml -QtQuick/Controls/Basic/PageIndicator.qml -QtQuick/Controls/Basic/Pane.qml -QtQuick/Controls/Basic/plugins.qmltypes -QtQuick/Controls/Basic/Popup.qml -QtQuick/Controls/Basic/ProgressBar.qml -QtQuick/Controls/Basic/qmldir -QtQuick/Controls/Basic/libqtquickcontrols2basicstyleplugin.so -QtQuick/Controls/Basic/RadioButton.qml -QtQuick/Controls/Basic/RadioDelegate.qml -QtQuick/Controls/Basic/RangeSlider.qml -QtQuick/Controls/Basic/RoundButton.qml -QtQuick/Controls/Basic/ScrollBar.qml -QtQuick/Controls/Basic/ScrollIndicator.qml -QtQuick/Controls/Basic/ScrollView.qml -QtQuick/Controls/Basic/SelectionRectangle.qml -QtQuick/Controls/Basic/Slider.qml -QtQuick/Controls/Basic/SpinBox.qml -QtQuick/Controls/Basic/SplitView.qml -QtQuick/Controls/Basic/StackView.qml -QtQuick/Controls/Basic/SwipeDelegate.qml -QtQuick/Controls/Basic/SwipeView.qml -QtQuick/Controls/Basic/Switch.qml -QtQuick/Controls/Basic/SwitchDelegate.qml -QtQuick/Controls/Basic/TabBar.qml -QtQuick/Controls/Basic/TabButton.qml -QtQuick/Controls/Basic/TextArea.qml -QtQuick/Controls/Basic/TextField.qml -QtQuick/Controls/Basic/ToolBar.qml -QtQuick/Controls/Basic/ToolButton.qml -QtQuick/Controls/Basic/ToolSeparator.qml -QtQuick/Controls/Basic/ToolTip.qml -QtQuick/Controls/Basic/TreeViewDelegate.qml -QtQuick/Controls/Basic/Tumbler.qml -QtQuick/Controls/Basic/VerticalHeaderView.qml -QtQuick/Controls/Basic/WeekNumberColumn.qml -QtQuick/Controls/Basic/impl/plugins.qmltypes -QtQuick/Controls/Basic/impl/qmldir -QtQuick/Controls/Basic/impl/libqtquickcontrols2basicstyleimplplugin.so -QtQuick/Controls/Fusion/ApplicationWindow.qml -QtQuick/Controls/Fusion/BusyIndicator.qml -QtQuick/Controls/Fusion/Button.qml -QtQuick/Controls/Fusion/CheckBox.qml -QtQuick/Controls/Fusion/CheckDelegate.qml -QtQuick/Controls/Fusion/ComboBox.qml -QtQuick/Controls/Fusion/DelayButton.qml -QtQuick/Controls/Fusion/Dial.qml -QtQuick/Controls/Fusion/Dialog.qml -QtQuick/Controls/Fusion/DialogButtonBox.qml -QtQuick/Controls/Fusion/Drawer.qml -QtQuick/Controls/Fusion/Frame.qml -QtQuick/Controls/Fusion/GroupBox.qml -QtQuick/Controls/Fusion/HorizontalHeaderView.qml -QtQuick/Controls/Fusion/ItemDelegate.qml -QtQuick/Controls/Fusion/Label.qml -QtQuick/Controls/Fusion/Menu.qml -QtQuick/Controls/Fusion/MenuBar.qml -QtQuick/Controls/Fusion/MenuBarItem.qml -QtQuick/Controls/Fusion/MenuItem.qml -QtQuick/Controls/Fusion/MenuSeparator.qml -QtQuick/Controls/Fusion/Page.qml -QtQuick/Controls/Fusion/PageIndicator.qml -QtQuick/Controls/Fusion/Pane.qml -QtQuick/Controls/Fusion/plugins.qmltypes -QtQuick/Controls/Fusion/Popup.qml -QtQuick/Controls/Fusion/ProgressBar.qml -QtQuick/Controls/Fusion/qmldir -QtQuick/Controls/Fusion/libqtquickcontrols2fusionstyleplugin.so -QtQuick/Controls/Fusion/RadioButton.qml -QtQuick/Controls/Fusion/RadioDelegate.qml -QtQuick/Controls/Fusion/RangeSlider.qml -QtQuick/Controls/Fusion/RoundButton.qml -QtQuick/Controls/Fusion/ScrollBar.qml -QtQuick/Controls/Fusion/ScrollIndicator.qml -QtQuick/Controls/Fusion/ScrollView.qml -QtQuick/Controls/Fusion/SelectionRectangle.qml -QtQuick/Controls/Fusion/Slider.qml -QtQuick/Controls/Fusion/SpinBox.qml -QtQuick/Controls/Fusion/SplitView.qml -QtQuick/Controls/Fusion/SwipeDelegate.qml -QtQuick/Controls/Fusion/Switch.qml -QtQuick/Controls/Fusion/SwitchDelegate.qml -QtQuick/Controls/Fusion/TabBar.qml -QtQuick/Controls/Fusion/TabButton.qml -QtQuick/Controls/Fusion/TextArea.qml -QtQuick/Controls/Fusion/TextField.qml -QtQuick/Controls/Fusion/ToolBar.qml -QtQuick/Controls/Fusion/ToolButton.qml -QtQuick/Controls/Fusion/ToolSeparator.qml -QtQuick/Controls/Fusion/ToolTip.qml -QtQuick/Controls/Fusion/TreeViewDelegate.qml -QtQuick/Controls/Fusion/Tumbler.qml -QtQuick/Controls/Fusion/VerticalHeaderView.qml -QtQuick/Controls/Fusion/impl/ButtonPanel.qml -QtQuick/Controls/Fusion/impl/CheckIndicator.qml -QtQuick/Controls/Fusion/impl/plugins.qmltypes -QtQuick/Controls/Fusion/impl/qmldir -QtQuick/Controls/Fusion/impl/libqtquickcontrols2fusionstyleimplplugin.so -QtQuick/Controls/Fusion/impl/RadioIndicator.qml -QtQuick/Controls/Fusion/impl/SliderGroove.qml -QtQuick/Controls/Fusion/impl/SliderHandle.qml -QtQuick/Controls/Fusion/impl/SwitchIndicator.qml -QtQuick/Controls/Imagine/ApplicationWindow.qml -QtQuick/Controls/Imagine/BusyIndicator.qml -QtQuick/Controls/Imagine/Button.qml -QtQuick/Controls/Imagine/CheckBox.qml -QtQuick/Controls/Imagine/CheckDelegate.qml -QtQuick/Controls/Imagine/ComboBox.qml -QtQuick/Controls/Imagine/DelayButton.qml -QtQuick/Controls/Imagine/Dial.qml -QtQuick/Controls/Imagine/Dialog.qml -QtQuick/Controls/Imagine/DialogButtonBox.qml -QtQuick/Controls/Imagine/Drawer.qml -QtQuick/Controls/Imagine/Frame.qml -QtQuick/Controls/Imagine/GroupBox.qml -QtQuick/Controls/Imagine/HorizontalHeaderView.qml -QtQuick/Controls/Imagine/ItemDelegate.qml -QtQuick/Controls/Imagine/Label.qml -QtQuick/Controls/Imagine/Menu.qml -QtQuick/Controls/Imagine/MenuItem.qml -QtQuick/Controls/Imagine/MenuSeparator.qml -QtQuick/Controls/Imagine/Page.qml -QtQuick/Controls/Imagine/PageIndicator.qml -QtQuick/Controls/Imagine/Pane.qml -QtQuick/Controls/Imagine/plugins.qmltypes -QtQuick/Controls/Imagine/Popup.qml -QtQuick/Controls/Imagine/ProgressBar.qml -QtQuick/Controls/Imagine/qmldir -QtQuick/Controls/Imagine/libqtquickcontrols2imaginestyleplugin.so -QtQuick/Controls/Imagine/RadioButton.qml -QtQuick/Controls/Imagine/RadioDelegate.qml -QtQuick/Controls/Imagine/RangeSlider.qml -QtQuick/Controls/Imagine/RoundButton.qml -QtQuick/Controls/Imagine/ScrollBar.qml -QtQuick/Controls/Imagine/ScrollIndicator.qml -QtQuick/Controls/Imagine/ScrollView.qml -QtQuick/Controls/Imagine/SelectionRectangle.qml -QtQuick/Controls/Imagine/Slider.qml -QtQuick/Controls/Imagine/SpinBox.qml -QtQuick/Controls/Imagine/SplitView.qml -QtQuick/Controls/Imagine/StackView.qml -QtQuick/Controls/Imagine/SwipeDelegate.qml -QtQuick/Controls/Imagine/SwipeView.qml -QtQuick/Controls/Imagine/Switch.qml -QtQuick/Controls/Imagine/SwitchDelegate.qml -QtQuick/Controls/Imagine/TabBar.qml -QtQuick/Controls/Imagine/TabButton.qml -QtQuick/Controls/Imagine/TextArea.qml -QtQuick/Controls/Imagine/TextField.qml -QtQuick/Controls/Imagine/ToolBar.qml -QtQuick/Controls/Imagine/ToolButton.qml -QtQuick/Controls/Imagine/ToolSeparator.qml -QtQuick/Controls/Imagine/ToolTip.qml -QtQuick/Controls/Imagine/Tumbler.qml -QtQuick/Controls/Imagine/VerticalHeaderView.qml -QtQuick/Controls/Imagine/impl/OpacityMask.qml -QtQuick/Controls/Imagine/impl/qmldir -QtQuick/Controls/Imagine/impl/libqtquickcontrols2imaginestyleimplplugin.so -QtQuick/Controls/Imagine/impl/QuickControls2ImagineStyleImpl.qmltypes -QtQuick/Controls/impl/plugins.qmltypes -QtQuick/Controls/impl/qmldir -QtQuick/Controls/impl/libqtquickcontrols2implplugin.so -QtQuick/Controls/Material/ApplicationWindow.qml -QtQuick/Controls/Material/BusyIndicator.qml -QtQuick/Controls/Material/Button.qml -QtQuick/Controls/Material/CheckBox.qml -QtQuick/Controls/Material/CheckDelegate.qml -QtQuick/Controls/Material/ComboBox.qml -QtQuick/Controls/Material/DelayButton.qml -QtQuick/Controls/Material/Dial.qml -QtQuick/Controls/Material/Dialog.qml -QtQuick/Controls/Material/DialogButtonBox.qml -QtQuick/Controls/Material/Drawer.qml -QtQuick/Controls/Material/Frame.qml -QtQuick/Controls/Material/GroupBox.qml -QtQuick/Controls/Material/HorizontalHeaderView.qml -QtQuick/Controls/Material/ItemDelegate.qml -QtQuick/Controls/Material/Label.qml -QtQuick/Controls/Material/Menu.qml -QtQuick/Controls/Material/MenuBar.qml -QtQuick/Controls/Material/MenuBarItem.qml -QtQuick/Controls/Material/MenuItem.qml -QtQuick/Controls/Material/MenuSeparator.qml -QtQuick/Controls/Material/Page.qml -QtQuick/Controls/Material/PageIndicator.qml -QtQuick/Controls/Material/Pane.qml -QtQuick/Controls/Material/plugins.qmltypes -QtQuick/Controls/Material/Popup.qml -QtQuick/Controls/Material/ProgressBar.qml -QtQuick/Controls/Material/qmldir -QtQuick/Controls/Material/libqtquickcontrols2materialstyleplugin.so -QtQuick/Controls/Material/RadioButton.qml -QtQuick/Controls/Material/RadioDelegate.qml -QtQuick/Controls/Material/RangeSlider.qml -QtQuick/Controls/Material/RoundButton.qml -QtQuick/Controls/Material/ScrollBar.qml -QtQuick/Controls/Material/ScrollIndicator.qml -QtQuick/Controls/Material/ScrollView.qml -QtQuick/Controls/Material/SelectionRectangle.qml -QtQuick/Controls/Material/Slider.qml -QtQuick/Controls/Material/SpinBox.qml -QtQuick/Controls/Material/SplitView.qml -QtQuick/Controls/Material/StackView.qml -QtQuick/Controls/Material/SwipeDelegate.qml -QtQuick/Controls/Material/SwipeView.qml -QtQuick/Controls/Material/Switch.qml -QtQuick/Controls/Material/SwitchDelegate.qml -QtQuick/Controls/Material/TabBar.qml -QtQuick/Controls/Material/TabButton.qml -QtQuick/Controls/Material/TextArea.qml -QtQuick/Controls/Material/TextField.qml -QtQuick/Controls/Material/ToolBar.qml -QtQuick/Controls/Material/ToolButton.qml -QtQuick/Controls/Material/ToolSeparator.qml -QtQuick/Controls/Material/ToolTip.qml -QtQuick/Controls/Material/TreeViewDelegate.qml -QtQuick/Controls/Material/Tumbler.qml -QtQuick/Controls/Material/VerticalHeaderView.qml -QtQuick/Controls/Material/impl/BoxShadow.qml -QtQuick/Controls/Material/impl/CheckIndicator.qml -QtQuick/Controls/Material/impl/CursorDelegate.qml -QtQuick/Controls/Material/impl/ElevationEffect.qml -QtQuick/Controls/Material/impl/plugins.qmltypes -QtQuick/Controls/Material/impl/qmldir -QtQuick/Controls/Material/impl/libqtquickcontrols2materialstyleimplplugin.so -QtQuick/Controls/Material/impl/RadioIndicator.qml -QtQuick/Controls/Material/impl/RectangularGlow.qml -QtQuick/Controls/Material/impl/RoundedElevationEffect.qml -QtQuick/Controls/Material/impl/SliderHandle.qml -QtQuick/Controls/Material/impl/SwitchIndicator.qml -QtQuick/Controls/Universal/ApplicationWindow.qml -QtQuick/Controls/Universal/BusyIndicator.qml -QtQuick/Controls/Universal/Button.qml -QtQuick/Controls/Universal/CheckBox.qml -QtQuick/Controls/Universal/CheckDelegate.qml -QtQuick/Controls/Universal/ComboBox.qml -QtQuick/Controls/Universal/DelayButton.qml -QtQuick/Controls/Universal/Dial.qml -QtQuick/Controls/Universal/Dialog.qml -QtQuick/Controls/Universal/DialogButtonBox.qml -QtQuick/Controls/Universal/Drawer.qml -QtQuick/Controls/Universal/Frame.qml -QtQuick/Controls/Universal/GroupBox.qml -QtQuick/Controls/Universal/HorizontalHeaderView.qml -QtQuick/Controls/Universal/ItemDelegate.qml -QtQuick/Controls/Universal/Label.qml -QtQuick/Controls/Universal/Menu.qml -QtQuick/Controls/Universal/MenuBar.qml -QtQuick/Controls/Universal/MenuBarItem.qml -QtQuick/Controls/Universal/MenuItem.qml -QtQuick/Controls/Universal/MenuSeparator.qml -QtQuick/Controls/Universal/Page.qml -QtQuick/Controls/Universal/PageIndicator.qml -QtQuick/Controls/Universal/Pane.qml -QtQuick/Controls/Universal/plugins.qmltypes -QtQuick/Controls/Universal/Popup.qml -QtQuick/Controls/Universal/ProgressBar.qml -QtQuick/Controls/Universal/qmldir -QtQuick/Controls/Universal/libqtquickcontrols2universalstyleplugin.so -QtQuick/Controls/Universal/RadioButton.qml -QtQuick/Controls/Universal/RadioDelegate.qml -QtQuick/Controls/Universal/RangeSlider.qml -QtQuick/Controls/Universal/RoundButton.qml -QtQuick/Controls/Universal/ScrollBar.qml -QtQuick/Controls/Universal/ScrollIndicator.qml -QtQuick/Controls/Universal/ScrollView.qml -QtQuick/Controls/Universal/SelectionRectangle.qml -QtQuick/Controls/Universal/Slider.qml -QtQuick/Controls/Universal/SpinBox.qml -QtQuick/Controls/Universal/SplitView.qml -QtQuick/Controls/Universal/StackView.qml -QtQuick/Controls/Universal/SwipeDelegate.qml -QtQuick/Controls/Universal/Switch.qml -QtQuick/Controls/Universal/SwitchDelegate.qml -QtQuick/Controls/Universal/TabBar.qml -QtQuick/Controls/Universal/TabButton.qml -QtQuick/Controls/Universal/TextArea.qml -QtQuick/Controls/Universal/TextField.qml -QtQuick/Controls/Universal/ToolBar.qml -QtQuick/Controls/Universal/ToolButton.qml -QtQuick/Controls/Universal/ToolSeparator.qml -QtQuick/Controls/Universal/ToolTip.qml -QtQuick/Controls/Universal/Tumbler.qml -QtQuick/Controls/Universal/VerticalHeaderView.qml -QtQuick/Controls/Universal/impl/CheckIndicator.qml -QtQuick/Controls/Universal/impl/plugins.qmltypes -QtQuick/Controls/Universal/impl/qmldir -QtQuick/Controls/Universal/impl/libqtquickcontrols2universalstyleimplplugin.so -QtQuick/Controls/Universal/impl/RadioIndicator.qml -QtQuick/Controls/Universal/impl/SwitchIndicator.qml +QtQuick/Layouts/libqquicklayoutsplugin.so QtQuick/Layouts/plugins.qmltypes QtQuick/Layouts/qmldir -QtQuick/Layouts/libqquicklayoutsplugin.so -QtQuick/NativeStyle/plugins.qmltypes -QtQuick/NativeStyle/qmldir -QtQuick/NativeStyle/libqtquickcontrols2nativestyleplugin.so -QtQuick/NativeStyle/controls/DefaultButton.qml -QtQuick/NativeStyle/controls/DefaultCheckBox.qml -QtQuick/NativeStyle/controls/DefaultComboBox.qml -QtQuick/NativeStyle/controls/DefaultDial.qml -QtQuick/NativeStyle/controls/DefaultFrame.qml -QtQuick/NativeStyle/controls/DefaultGroupBox.qml -QtQuick/NativeStyle/controls/DefaultItemDelegate.qml -QtQuick/NativeStyle/controls/DefaultItemDelegateIconLabel.qml -QtQuick/NativeStyle/controls/DefaultProgressBar.qml -QtQuick/NativeStyle/controls/DefaultRadioButton.qml -QtQuick/NativeStyle/controls/DefaultRadioDelegate.qml -QtQuick/NativeStyle/controls/DefaultScrollBar.qml -QtQuick/NativeStyle/controls/DefaultSlider.qml -QtQuick/NativeStyle/controls/DefaultSpinBox.qml -QtQuick/NativeStyle/controls/DefaultTextArea.qml -QtQuick/NativeStyle/controls/DefaultTextField.qml -QtQuick/NativeStyle/controls/DefaultTreeViewDelegate.qml -QtQuick/Shapes/plugins.qmltypes -QtQuick/Shapes/qmldir -QtQuick/Shapes/libqmlshapesplugin.so -QtQuick/Templates/plugins.qmltypes -QtQuick/Templates/qmldir -QtQuick/Templates/libqtquicktemplates2plugin.so -QtQuick/Window/qmldir -QtQuick/Window/quickwindow.qmltypes -QtQuick/Window/libquickwindowplugin.so +QtQuick/Templates.2/libqtquicktemplates2plugin.so +QtQuick/Templates.2/plugins.qmltypes +QtQuick/Templates.2/qmldir +QtQuick/Window.2/libwindowplugin.so +QtQuick/Window.2/plugins.qmltypes +QtQuick/Window.2/qmldir From 39642015150e7e1ca35b72dc4c2048932fb1fc9d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 13 Oct 2024 01:51:13 +0900 Subject: [PATCH 082/127] =?UTF-8?q?dev.yml=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d027a025..55c1eee9 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -20,7 +20,8 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' - modules: 'qt5compat qtwebsockets qthttpserver' + modules: 'qtwebglplugin' + # modules: 'qt5compat qtwebsockets qthttpserver' - name: Checkout repository uses: actions/checkout@v4 with: From d95b090f142b5e9c0062fbb85615b38fd6843447 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 13 Oct 2024 02:03:20 +0900 Subject: [PATCH 083/127] =?UTF-8?q?dev.yml=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 55c1eee9..10642d06 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -42,10 +42,8 @@ jobs: qmake CONFIG+=debug_and_release ../qthttpserver/qthttpserver.pro make -j4 && make install - name: Build - run: scripts/build.sh linux ${Qt5_DIR}/bin - # run: scripts/build.sh linux ${QT_ROOT_DIR}/bin + run: scripts/build.sh linux ${QT_ROOT_DIR}/bin - name: Unit test env: TZ: "Asia/Tokyo" - run: scripts/unittest.sh linux ${Qt5_DIR}/bin - # run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin + run: scripts/unittest.sh linux ${QT_ROOT_DIR}/bin From b58e8d78f0cd3c6f9e2f1c60859a9a770c10ab6d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 13 Oct 2024 13:03:01 +0900 Subject: [PATCH 084/127] =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=97=E3=83=88=E3=81=AE=E4=B8=8D=E8=A6=81?= =?UTF-8?q?=E3=81=AA=E9=83=A8=E5=88=86=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build.sh | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 1c7fbfed..9cdcf7a1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -143,18 +143,6 @@ if [ -z "${QT_BIN_FOLDER}" ] || [ -z "${PLATFORM_TYPE}" ]; then exit 1 fi -if [ "${PLATFORM_TYPE}" = "mac" ]; then - if [ "$(uname -m)" != "x86_64" ]; then - echo "============ Warning ================" - echo " Requires startup in x86_64 mode" - echo "=====================================" - echo "usage arch -x86_64 $(basename $0) PLATFORM_TYPE QT_BIN_FOLDER" - echo " PLATFORM_TYPE linux or mac" - echo " QT_BIN_FOLDER ex: ~/Qt/5.15.2/gcc_64/bin/" - exit 1 - fi -fi - VERSION_NO=$(cat app/main.cpp | grep "app.setApplicationVersion" | grep -oE "[0-9]+.[0-9]+.[0-9]+") build_openssl From da9d2aaa305efe3b1784451b710839147496103e Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 14 Oct 2024 01:39:56 +0900 Subject: [PATCH 085/127] =?UTF-8?q?Linkat=E7=94=A8=E3=81=AElexicon?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 18 ++++++++++++++++ lib/atprotocol/lexicons_func.cpp | 23 +++++++++++++++++++++ lib/atprotocol/lexicons_func.h | 8 ++++++++ lib/atprotocol/lexicons_func_unknown.cpp | 4 ++++ scripts/defs2struct.py | 1 + scripts/lexicons/blue.linkat.board.json | 26 ++++++++++++++++++++++++ scripts/lexicons/blue.linkat.defs.json | 21 +++++++++++++++++++ 7 files changed, 101 insertions(+) create mode 100644 scripts/lexicons/blue.linkat.board.json create mode 100644 scripts/lexicons/blue.linkat.defs.json diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 4a522502..2a837f89 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -1981,6 +1981,23 @@ struct Member }; } +// blue.linkat.defs +namespace BlueLinkatDefs { +struct LinkItem +{ + QString url; + QString text; +}; +} + +// blue.linkat.board +namespace BlueLinkatBoard { +struct Main +{ + QList cards; +}; +} + // com.whtwnd.blog.defs namespace ComWhtwndBlogDefs { struct BlogEntry @@ -2162,6 +2179,7 @@ Q_DECLARE_METATYPE(AtProtocolType::AppBskyGraphList::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedThreadgate::Main) Q_DECLARE_METATYPE(AtProtocolType::AppBskyFeedPostgate::Main) Q_DECLARE_METATYPE(AtProtocolType::ComWhtwndBlogEntry::Main) +Q_DECLARE_METATYPE(AtProtocolType::BlueLinkatBoard::Main) Q_DECLARE_METATYPE(AtProtocolType::DirectoryPlcDefs::DidDoc) #endif // LEXICONS_H diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index a665dcbe..7a694bc2 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2799,6 +2799,29 @@ void copyMember(const QJsonObject &src, ToolsOzoneTeamDefs::Member &dest) } } } +// blue.linkat.defs +namespace BlueLinkatDefs { +void copyLinkItem(const QJsonObject &src, BlueLinkatDefs::LinkItem &dest) +{ + if (!src.isEmpty()) { + dest.url = src.value("url").toString(); + dest.text = src.value("text").toString(); + } +} +} +// blue.linkat.board +namespace BlueLinkatBoard { +void copyMain(const QJsonObject &src, BlueLinkatBoard::Main &dest) +{ + if (!src.isEmpty()) { + for (const auto &s : src.value("cards").toArray()) { + BlueLinkatDefs::LinkItem child; + BlueLinkatDefs::copyLinkItem(s.toObject(), child); + dest.cards.append(child); + } + } +} +} // com.whtwnd.blog.defs namespace ComWhtwndBlogDefs { void copyBlogEntry(const QJsonObject &src, ComWhtwndBlogDefs::BlogEntry &dest) diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index d2d72930..f849339d 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -395,6 +395,14 @@ void copyViewerConfig(const QJsonObject &src, ToolsOzoneServerGetConfig::ViewerC namespace ToolsOzoneTeamDefs { void copyMember(const QJsonObject &src, ToolsOzoneTeamDefs::Member &dest); } +// blue.linkat.defs +namespace BlueLinkatDefs { +void copyLinkItem(const QJsonObject &src, BlueLinkatDefs::LinkItem &dest); +} +// blue.linkat.board +namespace BlueLinkatBoard { +void copyMain(const QJsonObject &src, BlueLinkatBoard::Main &dest); +} // com.whtwnd.blog.defs namespace ComWhtwndBlogDefs { void copyBlogEntry(const QJsonObject &src, ComWhtwndBlogDefs::BlogEntry &dest); diff --git a/lib/atprotocol/lexicons_func_unknown.cpp b/lib/atprotocol/lexicons_func_unknown.cpp index 9f171302..ee8320cc 100644 --- a/lib/atprotocol/lexicons_func_unknown.cpp +++ b/lib/atprotocol/lexicons_func_unknown.cpp @@ -64,6 +64,10 @@ void copyUnknown(const QJsonObject &src, QVariant &dest) ComWhtwndBlogEntry::Main record; ComWhtwndBlogEntry::copyMain(src, record); dest.setValue(record); + } else if (type == QStringLiteral("blue.linkat.board")) { + BlueLinkatBoard::Main record; + BlueLinkatBoard::copyMain(src, record); + dest.setValue(record); } else if (context.contains("https://www.w3.org/ns/did/v1")) { DirectoryPlcDefs::DidDoc doc; DirectoryPlcDefs::copyDidDoc(src, doc); diff --git a/scripts/defs2struct.py b/scripts/defs2struct.py index f4149d92..f727d046 100644 --- a/scripts/defs2struct.py +++ b/scripts/defs2struct.py @@ -123,6 +123,7 @@ def __init__(self) -> None: 'AppBskyFeedThreadgate::Main', 'AppBskyFeedPostgate::Main', 'ComWhtwndBlogEntry::Main', + 'BlueLinkatBoard::Main', 'DirectoryPlcDefs::DidDoc', ) self.inheritance = { diff --git a/scripts/lexicons/blue.linkat.board.json b/scripts/lexicons/blue.linkat.board.json new file mode 100644 index 00000000..6ff55114 --- /dev/null +++ b/scripts/lexicons/blue.linkat.board.json @@ -0,0 +1,26 @@ +{ + "lexicon": 1, + "id": "blue.linkat.board", + "defs": { + "main": { + "type": "record", + "description": "A declaration of links.", + "key": "tid", + "record": { + "type": "object", + "required": [ + "cards" + ], + "properties": { + "cards": { + "type": "array", + "items": { + "type": "ref", + "ref": "blue.linkat.defs#linkItem" + } + } + } + } + } + } +} diff --git a/scripts/lexicons/blue.linkat.defs.json b/scripts/lexicons/blue.linkat.defs.json new file mode 100644 index 00000000..b76ff77e --- /dev/null +++ b/scripts/lexicons/blue.linkat.defs.json @@ -0,0 +1,21 @@ +{ + "lexicon": 1, + "id": "com.whtwnd.blog.defs", + "defs": { + "linkItem": { + "type": "object", + "required": [ + "url", + "text" + ], + "properties": { + "url": { + "type": "string" + }, + "text": { + "type": "string" + } + } + } + } +} From 86ebfb30885c92f9e3a01ad9f10679e7a6323ec1 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 14 Oct 2024 02:00:15 +0900 Subject: [PATCH 086/127] =?UTF-8?q?=E3=83=96=E3=83=AD=E3=82=B0=E3=81=AE?= =?UTF-8?q?=E4=B8=A6=E3=81=B3=E3=81=ABLinkat=E3=81=B8=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/view/ProfileView.qml | 2 + app/qtquick/blog/blogentrylistmodel.cpp | 186 ++++++++++++++---- app/qtquick/blog/blogentrylistmodel.h | 13 ++ .../repo/comatprotorepolistrecordsex.cpp | 6 + .../repo/comatprotorepolistrecordsex.h | 1 + 5 files changed, 168 insertions(+), 40 deletions(-) diff --git a/app/qml/view/ProfileView.qml b/app/qml/view/ProfileView.qml index 91831315..50292f05 100644 --- a/app/qml/view/ProfileView.qml +++ b/app/qml/view/ProfileView.qml @@ -488,6 +488,7 @@ ColumnLayout { } } } + IconLabelFrame { id: moderationFrame Layout.preferredWidth: profileView.width @@ -578,6 +579,7 @@ ColumnLayout { } blogModel: BlogEntryListModel { id: authorBlogEntryListModel + targetHandle: userProfile.handle targetDid: profileView.userDid targetServiceEndpoint: userProfile.serviceEndpoint onTargetServiceEndpointChanged: { diff --git a/app/qtquick/blog/blogentrylistmodel.cpp b/app/qtquick/blog/blogentrylistmodel.cpp index 00c890ad..fc790274 100644 --- a/app/qtquick/blog/blogentrylistmodel.cpp +++ b/app/qtquick/blog/blogentrylistmodel.cpp @@ -21,6 +21,77 @@ QVariant BlogEntryListModel::data(const QModelIndex &index, int role) const } QVariant BlogEntryListModel::item(int row, BlogEntryListModelRoles role) const +{ + if (row < 0 || row >= m_blogEntryRecordList.count()) + return QVariant(); + + if (m_blogEntryRecordList.at(row).uri.split("/").contains("blue.linkat.board")) { + return itemFromLinkat(row, role); + } else { + return itemFromWhiteWind(row, role); + } + + return QVariant(); +} + +AtProtocolInterface::AccountData BlogEntryListModel::account() const +{ + return m_account; +} + +void BlogEntryListModel::setAccount(const QString &service, const QString &did, + const QString &handle, const QString &email, + const QString &accessJwt, const QString &refreshJwt) +{ + m_account.service = service; + m_account.did = did; + m_account.handle = handle; + m_account.email = email; + m_account.accessJwt = accessJwt; + m_account.refreshJwt = refreshJwt; +} + +bool BlogEntryListModel::getLatest() +{ + if (running() || targetDid().isEmpty()) + return false; + setRunning(true); + + if (!m_blogEntryRecordList.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_blogEntryRecordList.count() - 1); + m_blogEntryRecordList.clear(); + endRemoveRows(); + } + + getLatestFromLinkat([=](bool success) { + if (success) { + getLatestFromWhiteWind([=](bool success) { setRunning(false); }); + } else { + setRunning(false); + } + }); + + return true; +} + +QHash BlogEntryListModel::roleNames() const +{ + QHash roles; + + roles[CidRole] = "cid"; + roles[UriRole] = "uri"; + + roles[ServiceNameRole] = "serviceName"; + roles[TitleRole] = "title"; + roles[ContentRole] = "content"; + roles[CreatedAtRole] = "createdAt"; + roles[VisibilityRole] = "visibility"; + roles[PermalinkRole] = "permalink"; + + return roles; +} + +QVariant BlogEntryListModel::itemFromWhiteWind(int row, BlogEntryListModelRoles role) const { if (row < 0 || row >= m_blogEntryRecordList.count()) return QVariant(); @@ -69,35 +140,48 @@ QVariant BlogEntryListModel::item(int row, BlogEntryListModelRoles role) const return QVariant(); } -AtProtocolInterface::AccountData BlogEntryListModel::account() const +QVariant BlogEntryListModel::itemFromLinkat(int row, BlogEntryListModelRoles role) const { - return m_account; -} + if (row < 0 || row >= m_blogEntryRecordList.count()) + return QVariant(); -void BlogEntryListModel::setAccount(const QString &service, const QString &did, - const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt) -{ - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; -} + const auto ¤t = AtProtocolType::LexiconsTypeUnknown::fromQVariant< + AtProtocolType::BlueLinkatBoard::Main>(m_blogEntryRecordList.at(row).value); -bool BlogEntryListModel::getLatest() -{ - if (running() || targetDid().isEmpty()) - return false; - setRunning(true); + if (current.cards.isEmpty()) + return QVariant(); - if (!m_blogEntryRecordList.isEmpty()) { - beginRemoveRows(QModelIndex(), 0, m_blogEntryRecordList.count() - 1); - m_blogEntryRecordList.clear(); - endRemoveRows(); + if (role == CidRole) + return m_blogEntryRecordList.at(row).cid; + else if (role == UriRole) + return m_blogEntryRecordList.at(row).uri; + else if (role == ServiceNameRole) + return "Linkat"; + else if (role == TitleRole) { + if (current.cards.first().text.isEmpty()) { + return current.cards.first().url; + } else { + return current.cards.first().text; + } + } else if (role == ContentRole) + if (current.cards.length() > 1) { + return "and more ..."; + } else { + return QString(); + } + else if (role == CreatedAtRole) + return QString(); + else if (role == VisibilityRole) { + return QString(); + } else if (role == PermalinkRole) { + return QStringLiteral("https://linkat.blue/") + targetHandle(); } + return QVariant(); +} + +void BlogEntryListModel::getLatestFromWhiteWind(std::function callback) +{ ComAtprotoRepoListRecordsEx *record = new ComAtprotoRepoListRecordsEx(this); connect(record, &ComAtprotoRepoListRecordsEx::finished, this, [=](bool success) { if (success) { @@ -116,7 +200,7 @@ bool BlogEntryListModel::getLatest() } else { emit errorOccured(record->errorCode(), record->errorMessage()); } - setRunning(false); + callback(success); record->deleteLater(); }); record->setAccount(account()); @@ -124,25 +208,34 @@ bool BlogEntryListModel::getLatest() record->setService(targetServiceEndpoint()); } record->listWhiteWindItems(targetDid(), QString()); - - return true; } -QHash BlogEntryListModel::roleNames() const +void BlogEntryListModel::getLatestFromLinkat(std::function callback) { - QHash roles; - - roles[CidRole] = "cid"; - roles[UriRole] = "uri"; - - roles[ServiceNameRole] = "serviceName"; - roles[TitleRole] = "title"; - roles[ContentRole] = "content"; - roles[CreatedAtRole] = "createdAt"; - roles[VisibilityRole] = "visibility"; - roles[PermalinkRole] = "permalink"; - - return roles; + ComAtprotoRepoListRecordsEx *record = new ComAtprotoRepoListRecordsEx(this); + connect(record, &ComAtprotoRepoListRecordsEx::finished, this, [=](bool success) { + if (success) { + for (const auto &r : record->recordsList()) { + const auto ¤t = AtProtocolType::LexiconsTypeUnknown::fromQVariant< + AtProtocolType::BlueLinkatBoard::Main>(r.value); + if (!current.cards.isEmpty()) { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_blogEntryRecordList.append(r); + endInsertRows(); + break; + } + } + } else { + emit errorOccured(record->errorCode(), record->errorMessage()); + } + callback(success); + record->deleteLater(); + }); + record->setAccount(account()); + if (!targetServiceEndpoint().isEmpty()) { + record->setService(targetServiceEndpoint()); + } + record->listLinkatItems(targetDid(), QString()); } bool BlogEntryListModel::running() const @@ -183,3 +276,16 @@ void BlogEntryListModel::setTargetServiceEndpoint(const QString &newTargetServic m_targetServiceEndpoint = newTargetServiceEndpoint; emit targetServiceEndpointChanged(); } + +QString BlogEntryListModel::targetHandle() const +{ + return m_targetHandle; +} + +void BlogEntryListModel::setTargetHandle(const QString &newTargetHandle) +{ + if (m_targetHandle == newTargetHandle) + return; + m_targetHandle = newTargetHandle; + emit targetHandleChanged(); +} diff --git a/app/qtquick/blog/blogentrylistmodel.h b/app/qtquick/blog/blogentrylistmodel.h index b713bc2b..ddc81677 100644 --- a/app/qtquick/blog/blogentrylistmodel.h +++ b/app/qtquick/blog/blogentrylistmodel.h @@ -9,6 +9,8 @@ class BlogEntryListModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) + Q_PROPERTY(QString targetHandle READ targetHandle WRITE setTargetHandle NOTIFY + targetHandleChanged FINAL) Q_PROPERTY(QString targetDid READ targetDid WRITE setTargetDid NOTIFY targetDidChanged) Q_PROPERTY(QString targetServiceEndpoint READ targetServiceEndpoint WRITE setTargetServiceEndpoint NOTIFY targetServiceEndpointChanged FINAL) @@ -50,6 +52,9 @@ class BlogEntryListModel : public QAbstractListModel QString targetServiceEndpoint() const; void setTargetServiceEndpoint(const QString &newTargetServiceEndpoint); + QString targetHandle() const; + void setTargetHandle(const QString &newTargetHandle); + signals: void errorOccured(const QString &code, const QString &message); void runningChanged(); @@ -57,15 +62,23 @@ class BlogEntryListModel : public QAbstractListModel void countChanged(); void targetServiceEndpointChanged(); + void targetHandleChanged(); + protected: QHash roleNames() const; private: + QVariant itemFromWhiteWind(int row, BlogEntryListModel::BlogEntryListModelRoles role) const; + QVariant itemFromLinkat(int row, BlogEntryListModel::BlogEntryListModelRoles role) const; + void getLatestFromWhiteWind(std::function callback); + void getLatestFromLinkat(std::function callback); + QList m_blogEntryRecordList; AtProtocolInterface::AccountData m_account; bool m_running; QString m_targetDid; QString m_targetServiceEndpoint; + QString m_targetHandle; }; #endif // BLOGENTRYLISTMODEL_H diff --git a/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.cpp b/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.cpp index 17c0e35d..f0df81f8 100644 --- a/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.cpp +++ b/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.cpp @@ -27,6 +27,12 @@ void ComAtprotoRepoListRecordsEx::listWhiteWindItems(const QString &repo, const listRecords(repo, "com.whtwnd.blog.entry", 10, cursor, false); } +void ComAtprotoRepoListRecordsEx::listLinkatItems(const QString &repo, const QString &cursor) +{ + Q_UNUSED(cursor) + listRecords(repo, "blue.linkat.board", 1, QString(), false); +} + bool ComAtprotoRepoListRecordsEx::parseJson(bool success, const QString reply_json) { success = ComAtprotoRepoListRecords::parseJson(success, reply_json); diff --git a/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.h b/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.h index 98acc449..e45170df 100644 --- a/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.h +++ b/lib/extension/com/atproto/repo/comatprotorepolistrecordsex.h @@ -14,6 +14,7 @@ class ComAtprotoRepoListRecordsEx : public ComAtprotoRepoListRecords void listReposts(const QString &repo, const QString &cursor); void listListItems(const QString &repo, const QString &cursor); void listWhiteWindItems(const QString &repo, const QString &cursor); + void listLinkatItems(const QString &repo, const QString &cursor); private: virtual bool parseJson(bool success, const QString reply_json); From 458b096d875a39d9185ef439e9379fb1825d4594 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 14 Oct 2024 02:06:34 +0900 Subject: [PATCH 087/127] =?UTF-8?q?=E8=AA=A4=E8=A8=98=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lexicons/blue.linkat.defs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lexicons/blue.linkat.defs.json b/scripts/lexicons/blue.linkat.defs.json index b76ff77e..b3ce3e82 100644 --- a/scripts/lexicons/blue.linkat.defs.json +++ b/scripts/lexicons/blue.linkat.defs.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "com.whtwnd.blog.defs", + "id": "blue.linkat.defs", "defs": { "linkItem": { "type": "object", From 38a76b4ae2be9e30e930beb2f579c9fa6134a32a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 14 Oct 2024 02:08:38 +0900 Subject: [PATCH 088/127] =?UTF-8?q?=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/content/docs/release-note.en.md | 3 +++ web/content/docs/release-note.ja.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index fcf8d593..3f069237 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -8,6 +8,9 @@ description: This is a multi-column Bluesky client. ## 2024 +- Add + - Add a link to user's profile if user is registered with Linkat + ### v0.39.0 - 2024/10/12 - Add diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index 378f76a9..d368662e 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -8,6 +8,9 @@ description: マルチカラム対応Blueskyクライアント ## 2024 +- 追加 + - Linkatに登録があるときリンクをプロフィールに追加 + ### v0.39.0 - 2024/10/12 - 追加 From be686715ccf5a9393238c5e20c0fc3e14bf137de Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Mon, 14 Oct 2024 12:24:51 +0900 Subject: [PATCH 089/127] =?UTF-8?q?FIrehose=E3=81=AEwdt=E3=81=AE=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E3=82=82=E3=82=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/realtime/firehosereceiver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/realtime/firehosereceiver.cpp b/lib/realtime/firehosereceiver.cpp index c474b238..48d6ad97 100644 --- a/lib/realtime/firehosereceiver.cpp +++ b/lib/realtime/firehosereceiver.cpp @@ -121,6 +121,7 @@ void FirehoseReceiver::start() void FirehoseReceiver::stop() { + m_wdgTimer.stop(); if (m_client.state() == QAbstractSocket::SocketState::UnconnectedState || m_client.state() == QAbstractSocket::SocketState::ClosingState) return; From de52246cb7f8e9e6e17b9e336130e64b3688a3da Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 15 Oct 2024 01:10:53 +0900 Subject: [PATCH 090/127] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.cpp | 2 +- app/qtquick/account/accountlistmodel.h | 2 +- app/qtquick/operation/translator.cpp | 2 +- app/qtquick/qtquick.pri | 3 --- lib/lib.pro | 3 +++ {app/qtquick => lib/tools}/encryption.cpp | 0 {app/qtquick => lib/tools}/encryption.h | 0 {app/qtquick => lib/tools}/encryption_seed_template.h | 0 8 files changed, 6 insertions(+), 6 deletions(-) rename {app/qtquick => lib/tools}/encryption.cpp (100%) rename {app/qtquick => lib/tools}/encryption.h (100%) rename {app/qtquick => lib/tools}/encryption_seed_template.h (100%) diff --git a/app/main.cpp b/app/main.cpp index 070d4e58..086bfa68 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -38,7 +38,6 @@ #include "qtquick/list/listblockslistmodel.h" #include "qtquick/list/listmuteslistmodel.h" #include "qtquick/thumbnailprovider.h" -#include "qtquick/encryption.h" #include "qtquick/profile/userprofile.h" #include "qtquick/timeline/userpost.h" #include "qtquick/systemtool.h" @@ -60,6 +59,7 @@ #include "qtquick/controls/calendartablemodel.h" #include "qtquick/realtime/realtimefeedlistmodel.h" +#include "tools/encryption.h" #include "tools/translatorchanger.h" void setAppFont(QGuiApplication &app) diff --git a/app/qtquick/account/accountlistmodel.h b/app/qtquick/account/accountlistmodel.h index 37c650ca..d9d2943b 100644 --- a/app/qtquick/account/accountlistmodel.h +++ b/app/qtquick/account/accountlistmodel.h @@ -2,7 +2,7 @@ #define ACCOUNTLISTMODEL_H #include "atprotocol/accessatprotocol.h" -#include "encryption.h" +#include "tools/encryption.h" #include #include diff --git a/app/qtquick/operation/translator.cpp b/app/qtquick/operation/translator.cpp index dea6f7bc..fce01c86 100644 --- a/app/qtquick/operation/translator.cpp +++ b/app/qtquick/operation/translator.cpp @@ -1,5 +1,5 @@ #include "translator.h" -#include "encryption.h" +#include "tools/encryption.h" #include #include diff --git a/app/qtquick/qtquick.pri b/app/qtquick/qtquick.pri index e7ed1942..1122d402 100644 --- a/app/qtquick/qtquick.pri +++ b/app/qtquick/qtquick.pri @@ -14,7 +14,6 @@ SOURCES += \ $$PWD/controls/calendartablemodel.cpp \ $$PWD/controls/embedimagelistmodel.cpp \ $$PWD/controls/languagelistmodel.cpp \ - $$PWD/encryption.cpp \ $$PWD/feedgenerator/actorfeedgeneratorlistmodel.cpp \ $$PWD/feedgenerator/feedgeneratorlistmodel.cpp \ $$PWD/link/externallink.cpp \ @@ -71,8 +70,6 @@ HEADERS += \ $$PWD/controls/calendartablemodel.h \ $$PWD/controls/embedimagelistmodel.h \ $$PWD/controls/languagelistmodel.h \ - $$PWD/encryption.h \ - $$PWD/encryption_seed.h \ $$PWD/feedgenerator/actorfeedgeneratorlistmodel.h \ $$PWD/feedgenerator/feedgeneratorlistmodel.h \ $$PWD/link/externallink.h \ diff --git a/lib/lib.pro b/lib/lib.pro index db6aa7c7..b739d4a8 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -128,6 +128,7 @@ SOURCES += \ $$PWD/tools/cardecoder.cpp \ $$PWD/tools/chatlogsubscriber.cpp \ $$PWD/tools/configurablelabels.cpp \ + $$PWD/tools/encryption.cpp \ $$PWD/tools/es256.cpp \ $$PWD/tools/imagecompressor.cpp \ $$PWD/tools/jsonwebtoken.cpp \ @@ -245,6 +246,8 @@ HEADERS += \ $$PWD/tools/cardecoder.h \ $$PWD/tools/chatlogsubscriber.h \ $$PWD/tools/configurablelabels.h \ + $$PWD/tools/encryption.h \ + $$PWD/tools/encryption_seed.h \ $$PWD/tools/es256.h \ $$PWD/tools/imagecompressor.h \ $$PWD/tools/jsonwebtoken.h \ diff --git a/app/qtquick/encryption.cpp b/lib/tools/encryption.cpp similarity index 100% rename from app/qtquick/encryption.cpp rename to lib/tools/encryption.cpp diff --git a/app/qtquick/encryption.h b/lib/tools/encryption.h similarity index 100% rename from app/qtquick/encryption.h rename to lib/tools/encryption.h diff --git a/app/qtquick/encryption_seed_template.h b/lib/tools/encryption_seed_template.h similarity index 100% rename from app/qtquick/encryption_seed_template.h rename to lib/tools/encryption_seed_template.h From 055e8802a3609fcb26c66c84f2a0ad4a795a39ae Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 15 Oct 2024 23:10:11 +0900 Subject: [PATCH 091/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E7=AE=A1=E7=90=86=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E5=9F=BA=E7=A4=8E=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lib.pro | 2 + lib/tools/accountmanager.cpp | 228 ++++++++++++++++++++++++++++ lib/tools/accountmanager.h | 37 +++++ tests/tools_test/tools_test.pro | 3 +- tests/tools_test/tst_tools_test.cpp | 59 ++++++- 5 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 lib/tools/accountmanager.cpp create mode 100644 lib/tools/accountmanager.h diff --git a/lib/lib.pro b/lib/lib.pro index b739d4a8..0831f8e7 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -123,6 +123,7 @@ SOURCES += \ $$PWD/realtime/notpostselector.cpp \ $$PWD/realtime/orpostselector.cpp \ $$PWD/realtime/xorpostselector.cpp \ + $$PWD/tools/accountmanager.cpp \ $$PWD/tools/authorization.cpp \ $$PWD/tools/base32.cpp \ $$PWD/tools/cardecoder.cpp \ @@ -241,6 +242,7 @@ HEADERS += \ $$PWD/realtime/orpostselector.h \ $$PWD/realtime/xorpostselector.h \ $$PWD/search/search.h \ + $$PWD/tools/accountmanager.h \ $$PWD/tools/authorization.h \ $$PWD/tools/base32.h \ $$PWD/tools/cardecoder.h \ diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp new file mode 100644 index 00000000..5406d61a --- /dev/null +++ b/lib/tools/accountmanager.cpp @@ -0,0 +1,228 @@ +#include "accountmanager.h" +#include "encryption.h" + +#include +#include +#include +#include + +using AtProtocolInterface::AccountData; +using AtProtocolInterface::AccountStatus; + +class AccountManager::Private : public QObject +{ +public: + explicit Private(AccountManager *parent); + ~Private(); + + QJsonObject save() const; + void load(const QJsonObject &object); + + AccountData getAccount() const; + void updateAccount(const QString &uuid, const QString &service, const QString &identifier, + const QString &password, const QString &did, const QString &handle, + const QString &email, const QString &accessJwt, const QString &refreshJwt, + const QString &thread_gate_type, const AccountStatus status); + +private: + AccountManager *q; + + AccountData m_account; + Encryption m_encryption; +}; + +AccountManager::Private::Private(AccountManager *parent) +{ + qDebug().noquote() << __func__; +} + +AccountManager::Private::~Private() +{ + qDebug().noquote() << __func__; +} + +QJsonObject AccountManager::Private::save() const +{ + QJsonObject account_item; + account_item["uuid"] = m_account.uuid; + account_item["is_main"] = m_account.is_main; + account_item["service"] = m_account.service; + account_item["identifier"] = m_account.identifier; + account_item["password"] = m_encryption.encrypt(m_account.password); + account_item["refresh_jwt"] = m_encryption.encrypt(m_account.refreshJwt); + + if (!m_account.post_languages.isEmpty()) { + QJsonArray post_langs; + for (const auto &lang : m_account.post_languages) { + post_langs.append(lang); + } + account_item["post_languages"] = post_langs; + } + + if (m_account.thread_gate_type.isEmpty()) { + account_item["thread_gate_type"] = "everybody"; + } else { + account_item["thread_gate_type"] = m_account.thread_gate_type; + } + if (!m_account.thread_gate_options.isEmpty()) { + QJsonArray thread_gate_options; + for (const auto &option : m_account.thread_gate_options) { + thread_gate_options.append(option); + } + account_item["thread_gate_options"] = thread_gate_options; + } + account_item["post_gate_quote_enabled"] = m_account.post_gate_quote_enabled; + + return account_item; +} + +void AccountManager::Private::load(const QJsonObject &object) +{ + + QString temp_refresh = object.value("refresh_jwt").toString(); + m_account.uuid = object.value("uuid").toString(); + m_account.is_main = object.value("is_main").toBool(); + m_account.service = object.value("service").toString(); + m_account.service_endpoint = m_account.service; + m_account.identifier = object.value("identifier").toString(); + m_account.password = m_encryption.decrypt(object.value("password").toString()); + m_account.refreshJwt = m_encryption.decrypt(temp_refresh); + m_account.handle = m_account.identifier; + for (const auto &value : object.value("post_languages").toArray()) { + m_account.post_languages.append(value.toString()); + } + + m_account.thread_gate_type = object.value("thread_gate_type").toString("everybody"); + if (m_account.thread_gate_type.isEmpty()) { + m_account.thread_gate_type = "everybody"; + } + for (const auto &value : object.value("thread_gate_options").toArray()) { + m_account.thread_gate_options.append(value.toString()); + } + m_account.post_gate_quote_enabled = object.value("post_gate_quote_enabled").toBool(true); + + if (temp_refresh.isEmpty()) { + // createSession(m_accountList.count() - 1); + } else { + // refreshSession(m_accountList.count() - 1, true); + } +} + +AccountData AccountManager::Private::getAccount() const +{ + return m_account; +} + +void AccountManager::Private::updateAccount(const QString &uuid, const QString &service, + const QString &identifier, const QString &password, + const QString &did, const QString &handle, + const QString &email, const QString &accessJwt, + const QString &refreshJwt, + const QString &thread_gate_type, + const AccountStatus status) +{ + m_account.uuid = uuid; + m_account.service = service; + m_account.identifier = identifier; + m_account.password = password; + m_account.did = did; + m_account.handle = handle; + m_account.email = email; + m_account.accessJwt = accessJwt; + m_account.refreshJwt = refreshJwt; + m_account.thread_gate_type = thread_gate_type; + m_account.status = status; +} + +AccountManager::AccountManager(QObject *parent) : QObject { parent } +{ + qDebug().noquote() << __func__; +} + +AccountManager::~AccountManager() +{ + qDebug().noquote() << __func__; +} + +AccountManager *AccountManager::getInstance() +{ + static AccountManager instance; + return &instance; +} + +void AccountManager::clear() +{ + for (const auto &key : d.keys()) { + delete d[key]; + } + d.clear(); +} + +QJsonDocument AccountManager::save() const +{ + QStringList uuids = d.keys(); + uuids.sort(); + + QJsonArray account_array; + for (const auto &uuid : uuids) { + account_array.append(d[uuid]->save()); + } + + return QJsonDocument(account_array); +} + +void AccountManager::load(QJsonDocument &doc) +{ + if (doc.isArray()) { + bool has_main = false; + for (const auto &item : doc.array()) { + QString uuid = item.toObject().value("uuid").toString(); + if (!d.contains(uuid)) { + d[uuid] = new AccountManager::Private(this); + } + d[uuid]->load(item.toObject()); + } + if (!has_main) { + // mainになっているものがない + } + } +} + +AccountData AccountManager::getAccount(const QString &uuid) const +{ + if (!d.contains(uuid)) { + return AccountData(); + } + return d[uuid]->getAccount(); +} + +void AccountManager::updateAccount(const QString &service, const QString &identifier, + const QString &password, const QString &did, + const QString &handle, const QString &email, + const QString &accessJwt, const QString &refreshJwt, + const bool authorized) +{ + bool updated = false; + for (const auto &uuid : d.keys()) { + AccountData account = d[uuid]->getAccount(); + if (account.service == service && account.identifier == identifier) { + d[uuid]->updateAccount(uuid, service, identifier, password, did, handle, email, + accessJwt, refreshJwt, account.thread_gate_type, + authorized ? AccountStatus::Authorized + : AccountStatus::Unauthorized); + } + } + if (!updated) { + // append + QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + d[uuid] = new AccountManager::Private(this); + d[uuid]->updateAccount( + uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, + "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); + } +} + +QStringList AccountManager::getUuids() const +{ + return d.keys(); +} diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h new file mode 100644 index 00000000..13838b7c --- /dev/null +++ b/lib/tools/accountmanager.h @@ -0,0 +1,37 @@ +#ifndef ACCOUNTMANAGER_H +#define ACCOUNTMANAGER_H + +#include "atprotocol/accessatprotocol.h" + +#include +#include +#include + +class AccountManager : public QObject +{ + Q_OBJECT + explicit AccountManager(QObject *parent = nullptr); + ~AccountManager(); + +public: + static AccountManager *getInstance(); + void clear(); + + QJsonDocument save() const; + void load(QJsonDocument &doc); + + AtProtocolInterface::AccountData getAccount(const QString &uuid) const; + void updateAccount(const QString &service, const QString &identifier, const QString &password, + const QString &did, const QString &handle, const QString &email, + const QString &accessJwt, const QString &refreshJwt, const bool authorized); + + QStringList getUuids() const; +signals: + +private: + class Private; + QHash d; + Q_DISABLE_COPY_MOVE(AccountManager) +}; + +#endif // ACCOUNTMANAGER_H diff --git a/tests/tools_test/tools_test.pro b/tests/tools_test/tools_test.pro index 484794c6..b8df96fd 100644 --- a/tests/tools_test/tools_test.pro +++ b/tests/tools_test/tools_test.pro @@ -1,4 +1,4 @@ -QT += testlib httpserver gui +QT += testlib httpserver gui quick CONFIG += qt console warn_on depend_includepath testcase CONFIG -= app_bundle @@ -10,6 +10,7 @@ SOURCES += tst_tools_test.cpp include(../common/common.pri) include(../deps.pri) +include(../../app/qtquick/qtquick.pri) include(../../openssl/openssl.pri) include(../../zlib/zlib.pri) diff --git a/tests/tools_test/tst_tools_test.cpp b/tests/tools_test/tst_tools_test.cpp index 16cabacd..f3c79598 100644 --- a/tests/tools_test/tst_tools_test.cpp +++ b/tests/tools_test/tst_tools_test.cpp @@ -4,9 +4,11 @@ #include #include +#include "common.h" #include "tools/base32.h" #include "tools/leb128.h" #include "tools/cardecoder.h" +#include "tools/accountmanager.h" class tools_test : public QObject { @@ -23,9 +25,14 @@ private slots: void test_base32(); void test_Leb128(); void test_CarDecoder(); + void test_AccountManager(); }; -tools_test::tools_test() { } +tools_test::tools_test() +{ + QCoreApplication::setOrganizationName(QStringLiteral("relog")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); +} tools_test::~tools_test() { } @@ -184,6 +191,56 @@ void tools_test::test_CarDecoder() repo.close(); } +void tools_test::test_AccountManager() +{ + QString temp_path = Common::appDataFolder() + "/account.json"; + if (QFile::exists(temp_path)) { + QFile::remove(temp_path); + } + + AccountManager *manager = AccountManager::getInstance(); + AtProtocolInterface::AccountData account; + QList actuals; + + manager->updateAccount("/account/account1", "id1", "password1", "did:plc:account1", + "account1.relog.tech", "account1@relog.tech", "accessJwt_account1", + "refreshJwt_account1", true); + manager->updateAccount("/account/account2", "id2", "password2", "did:plc:account2", + "account2.relog.tech", "account2@relog.tech", "accessJwt_account2", + "refreshJwt_account2", true); + + QStringList uuids = manager->getUuids(); + + QVERIFY(uuids.count() == 2); + + actuals.append(manager->getAccount(uuids.at(0))); + actuals.append(manager->getAccount(uuids.at(1))); + + for (const auto &actual : actuals) { + if (actual.identifier == "id1") { + QVERIFY(actual.service == "/account/account1"); + QVERIFY(actual.password == "password1"); + QVERIFY(actual.did == "did:plc:account1"); + QVERIFY(actual.handle == "account1.relog.tech"); + QVERIFY(actual.email == "account1@relog.tech"); + QVERIFY(actual.accessJwt == "accessJwt_account1"); + QVERIFY(actual.refreshJwt == "refreshJwt_account1"); + } else if (actual.identifier == "id2") { + QVERIFY(actual.service == "/account/account2"); + QVERIFY(actual.password == "password2"); + QVERIFY(actual.did == "did:plc:account2"); + QVERIFY(actual.handle == "account2.relog.tech"); + QVERIFY(actual.email == "account2@relog.tech"); + QVERIFY(actual.accessJwt == "accessJwt_account2"); + QVERIFY(actual.refreshJwt == "refreshJwt_account2"); + } + } + + QJsonDocument doc = manager->save(); + + Common::saveJsonDocument(doc, QStringLiteral("account.json")); +} + QTEST_MAIN(tools_test) #include "tst_tools_test.moc" From 5a3126f4ca6b3275ca44cf588773dc25220cc3df Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 16 Oct 2024 00:01:21 +0900 Subject: [PATCH 092/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=AE=E7=99=BB=E9=8C=B2=E9=A0=86=E3=82=92=E7=B6=AD?= =?UTF-8?q?=E6=8C=81=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 58 ++++++++++++++++------------- lib/tools/accountmanager.h | 3 +- tests/tools_test/tst_tools_test.cpp | 39 +++++++++---------- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 5406d61a..688244b5 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -33,12 +33,12 @@ class AccountManager::Private : public QObject AccountManager::Private::Private(AccountManager *parent) { - qDebug().noquote() << __func__; + qDebug().noquote() << this << "AccountManager::Private()"; } AccountManager::Private::~Private() { - qDebug().noquote() << __func__; + qDebug().noquote() << this << "AccountManager::~Private()"; } QJsonObject AccountManager::Private::save() const @@ -136,12 +136,13 @@ void AccountManager::Private::updateAccount(const QString &uuid, const QString & AccountManager::AccountManager(QObject *parent) : QObject { parent } { - qDebug().noquote() << __func__; + qDebug().noquote() << this << "AccountManager()"; } AccountManager::~AccountManager() { - qDebug().noquote() << __func__; + qDebug().noquote() << this << "~AccountManager()"; + clear(); } AccountManager *AccountManager::getInstance() @@ -152,20 +153,19 @@ AccountManager *AccountManager::getInstance() void AccountManager::clear() { - for (const auto &key : d.keys()) { - delete d[key]; + for (const auto d : dList) { + delete d; } - d.clear(); + dList.clear(); + dIndex.clear(); } QJsonDocument AccountManager::save() const { - QStringList uuids = d.keys(); - uuids.sort(); - QJsonArray account_array; - for (const auto &uuid : uuids) { - account_array.append(d[uuid]->save()); + + for (const auto d : dList) { + account_array.append(d->save()); } return QJsonDocument(account_array); @@ -177,10 +177,11 @@ void AccountManager::load(QJsonDocument &doc) bool has_main = false; for (const auto &item : doc.array()) { QString uuid = item.toObject().value("uuid").toString(); - if (!d.contains(uuid)) { - d[uuid] = new AccountManager::Private(this); + if (!dIndex.contains(uuid)) { + dList.append(new AccountManager::Private(this)); + dIndex[uuid] = dList.count() - 1; } - d[uuid]->load(item.toObject()); + dList.last()->load(item.toObject()); } if (!has_main) { // mainになっているものがない @@ -190,10 +191,10 @@ void AccountManager::load(QJsonDocument &doc) AccountData AccountManager::getAccount(const QString &uuid) const { - if (!d.contains(uuid)) { + if (!dIndex.contains(uuid)) { return AccountData(); } - return d[uuid]->getAccount(); + return dList.at(dIndex.value(uuid))->getAccount(); } void AccountManager::updateAccount(const QString &service, const QString &identifier, @@ -203,20 +204,21 @@ void AccountManager::updateAccount(const QString &service, const QString &identi const bool authorized) { bool updated = false; - for (const auto &uuid : d.keys()) { - AccountData account = d[uuid]->getAccount(); + for (const auto d : dList) { + // for (const auto &uuid : d.keys()) { + AccountData account = d->getAccount(); if (account.service == service && account.identifier == identifier) { - d[uuid]->updateAccount(uuid, service, identifier, password, did, handle, email, - accessJwt, refreshJwt, account.thread_gate_type, - authorized ? AccountStatus::Authorized - : AccountStatus::Unauthorized); + d->updateAccount(account.uuid, service, identifier, password, did, handle, email, + accessJwt, refreshJwt, account.thread_gate_type, + authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); } } if (!updated) { // append QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); - d[uuid] = new AccountManager::Private(this); - d[uuid]->updateAccount( + dList.append(new AccountManager::Private(this)); + dIndex[uuid] = dList.count() - 1; + dList.last()->updateAccount( uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); } @@ -224,5 +226,9 @@ void AccountManager::updateAccount(const QString &service, const QString &identi QStringList AccountManager::getUuids() const { - return d.keys(); + QStringList uuids; + for (const auto d : dList) { + uuids.append(d->getAccount().uuid); + } + return uuids; } diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 13838b7c..13b242f9 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -30,7 +30,8 @@ class AccountManager : public QObject private: class Private; - QHash d; + QList dList; + QHash dIndex; Q_DISABLE_COPY_MOVE(AccountManager) }; diff --git a/tests/tools_test/tst_tools_test.cpp b/tests/tools_test/tst_tools_test.cpp index f3c79598..4583475a 100644 --- a/tests/tools_test/tst_tools_test.cpp +++ b/tests/tools_test/tst_tools_test.cpp @@ -213,28 +213,23 @@ void tools_test::test_AccountManager() QVERIFY(uuids.count() == 2); - actuals.append(manager->getAccount(uuids.at(0))); - actuals.append(manager->getAccount(uuids.at(1))); - - for (const auto &actual : actuals) { - if (actual.identifier == "id1") { - QVERIFY(actual.service == "/account/account1"); - QVERIFY(actual.password == "password1"); - QVERIFY(actual.did == "did:plc:account1"); - QVERIFY(actual.handle == "account1.relog.tech"); - QVERIFY(actual.email == "account1@relog.tech"); - QVERIFY(actual.accessJwt == "accessJwt_account1"); - QVERIFY(actual.refreshJwt == "refreshJwt_account1"); - } else if (actual.identifier == "id2") { - QVERIFY(actual.service == "/account/account2"); - QVERIFY(actual.password == "password2"); - QVERIFY(actual.did == "did:plc:account2"); - QVERIFY(actual.handle == "account2.relog.tech"); - QVERIFY(actual.email == "account2@relog.tech"); - QVERIFY(actual.accessJwt == "accessJwt_account2"); - QVERIFY(actual.refreshJwt == "refreshJwt_account2"); - } - } + account = manager->getAccount(uuids.at(0)); + QVERIFY(account.service == "/account/account1"); + QVERIFY(account.password == "password1"); + QVERIFY(account.did == "did:plc:account1"); + QVERIFY(account.handle == "account1.relog.tech"); + QVERIFY(account.email == "account1@relog.tech"); + QVERIFY(account.accessJwt == "accessJwt_account1"); + QVERIFY(account.refreshJwt == "refreshJwt_account1"); + + account = manager->getAccount(uuids.at(1)); + QVERIFY(account.service == "/account/account2"); + QVERIFY(account.password == "password2"); + QVERIFY(account.did == "did:plc:account2"); + QVERIFY(account.handle == "account2.relog.tech"); + QVERIFY(account.email == "account2@relog.tech"); + QVERIFY(account.accessJwt == "accessJwt_account2"); + QVERIFY(account.refreshJwt == "refreshJwt_account2"); QJsonDocument doc = manager->save(); From 140d4355f87c188162c45ec9502e4536bb1b8e9e Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 16 Oct 2024 01:29:58 +0900 Subject: [PATCH 093/127] =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 58 +++++++++++++++++ lib/tools/accountmanager.h | 5 ++ tests/hagoromo_test2/tst_hagoromo_test2.cpp | 71 +++++++++++++++++++++ tests/tools_test/tools_test.pro | 3 +- tests/tools_test/tst_tools_test.cpp | 48 -------------- 5 files changed, 135 insertions(+), 50 deletions(-) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 688244b5..b182dadb 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -24,6 +24,8 @@ class AccountManager::Private : public QObject const QString &email, const QString &accessJwt, const QString &refreshJwt, const QString &thread_gate_type, const AccountStatus status); + void getProfile(); + private: AccountManager *q; @@ -134,6 +136,11 @@ void AccountManager::Private::updateAccount(const QString &uuid, const QString & m_account.status = status; } +void AccountManager::Private::getProfile() +{ + // +} + AccountManager::AccountManager(QObject *parent) : QObject { parent } { qDebug().noquote() << this << "AccountManager()"; @@ -224,6 +231,57 @@ void AccountManager::updateAccount(const QString &service, const QString &identi } } +void AccountManager::removeAccount(const QString &uuid) +{ + if (!dIndex.contains(uuid)) { + return; + } + int i = dIndex.value(uuid); + delete dList.at(i); + dList.removeAt(i); + + // refresh index + dIndex.clear(); + for (int i = 0; i < dList.count(); i++) { + dIndex[dList.at(i)->getAccount().uuid] = i; + } +} + +void AccountManager::updateAccountProfile(const QString &uuid) +{ + if (!dIndex.contains(uuid)) { + return; + } + dList.at(dIndex.value(uuid))->getProfile(); +} + +int AccountManager::getMainAccountIndex() const +{ + return 0; +} + +void AccountManager::setMainAccount(const QString &uuid) +{ + for (const auto d : qAsConst(dList)) { + if (d->getAccount().uuid == uuid) { + // true + } else { + // false + } + } +} + +bool AccountManager::checkAllAccountsReady() const +{ + int ready_count = 0; + for (const auto d : dList) { + if (d->getAccount().status == AccountStatus::Authorized) { + ready_count++; + } + } + return (!dList.isEmpty() && dList.count() == ready_count); +} + QStringList AccountManager::getUuids() const { QStringList uuids; diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 13b242f9..606621fe 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -24,6 +24,11 @@ class AccountManager : public QObject void updateAccount(const QString &service, const QString &identifier, const QString &password, const QString &did, const QString &handle, const QString &email, const QString &accessJwt, const QString &refreshJwt, const bool authorized); + void removeAccount(const QString &uuid); + void updateAccountProfile(const QString &uuid); + int getMainAccountIndex() const; + void setMainAccount(const QString &uuid); + bool checkAllAccountsReady() const; QStringList getUuids() const; signals: diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 2d777f80..49f59bb0 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -15,6 +15,7 @@ #include "list/listslistmodel.h" #include "list/listitemlistmodel.h" #include "list/listfeedlistmodel.h" +#include "tools/accountmanager.h" class hagoromo_test : public QObject { @@ -32,6 +33,7 @@ private slots: void test_FeedGeneratorListModel(); void test_FeedGeneratorLink(); void test_AccountListModel(); + void test_AccountManager(); void test_ListsListModel(); void test_ListsListModel_search(); void test_ListsListModel_error(); @@ -291,6 +293,75 @@ void hagoromo_test::test_AccountListModel() model2.item(row, AccountListModel::RefreshJwtRole).toString().toLocal8Bit()); } +void hagoromo_test::test_AccountManager() +{ + + QString temp_path = Common::appDataFolder() + "/account.json"; + if (QFile::exists(temp_path)) { + QFile::remove(temp_path); + } + + AccountManager *manager = AccountManager::getInstance(); + AtProtocolInterface::AccountData account; + + manager->updateAccount(m_service + "/account/account1", "id1", "password1", "did:plc:account1", + "account1.relog.tech", "account1@relog.tech", "accessJwt_account1", + "refreshJwt_account1", false); + manager->updateAccount(m_service + "/account/account2", "id2", "password2", "did:plc:account2", + "account2.relog.tech", "account2@relog.tech", "accessJwt_account2", + "refreshJwt_account2", false); + + QStringList uuids = manager->getUuids(); + + QVERIFY(uuids.count() == 2); + + account = manager->getAccount(uuids.at(0)); + QVERIFY(account.service == m_service + "/account/account1"); + QVERIFY(account.password == "password1"); + QVERIFY(account.did == "did:plc:account1"); + QVERIFY(account.handle == "account1.relog.tech"); + QVERIFY(account.email == "account1@relog.tech"); + QVERIFY(account.accessJwt == "accessJwt_account1"); + QVERIFY(account.refreshJwt == "refreshJwt_account1"); + + account = manager->getAccount(uuids.at(1)); + QVERIFY(account.service == m_service + "/account/account2"); + QVERIFY(account.password == "password2"); + QVERIFY(account.did == "did:plc:account2"); + QVERIFY(account.handle == "account2.relog.tech"); + QVERIFY(account.email == "account2@relog.tech"); + QVERIFY(account.accessJwt == "accessJwt_account2"); + QVERIFY(account.refreshJwt == "refreshJwt_account2"); + + QVERIFY(manager->checkAllAccountsReady() == false); + + QJsonDocument doc = manager->save(); + Common::saveJsonDocument(doc, QStringLiteral("account.json")); + + manager->removeAccount(manager->getUuids().at(0)); + QVERIFY(manager->getUuids().count() == 1); + + account = manager->getAccount(manager->getUuids().at(0)); + QVERIFY(account.service == m_service + "/account/account2"); + QVERIFY(account.password == "password2"); + QVERIFY(account.did == "did:plc:account2"); + QVERIFY(account.handle == "account2.relog.tech"); + QVERIFY(account.email == "account2@relog.tech"); + QVERIFY(account.accessJwt == "accessJwt_account2"); + QVERIFY(account.refreshJwt == "refreshJwt_account2"); + + manager->clear(); + QVERIFY(manager->getUuids().isEmpty()); + + // { + // QSignalSpy spy(&model2, SIGNAL(updatedAccount(int, const QString &))); + // model2.load(); + // spy.wait(10 * 1000); + // spy.wait(10 * 1000); + // QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + // } +} + void hagoromo_test::test_ListsListModel() { ListsListModel model; diff --git a/tests/tools_test/tools_test.pro b/tests/tools_test/tools_test.pro index b8df96fd..484794c6 100644 --- a/tests/tools_test/tools_test.pro +++ b/tests/tools_test/tools_test.pro @@ -1,4 +1,4 @@ -QT += testlib httpserver gui quick +QT += testlib httpserver gui CONFIG += qt console warn_on depend_includepath testcase CONFIG -= app_bundle @@ -10,7 +10,6 @@ SOURCES += tst_tools_test.cpp include(../common/common.pri) include(../deps.pri) -include(../../app/qtquick/qtquick.pri) include(../../openssl/openssl.pri) include(../../zlib/zlib.pri) diff --git a/tests/tools_test/tst_tools_test.cpp b/tests/tools_test/tst_tools_test.cpp index 4583475a..f6fcd2ed 100644 --- a/tests/tools_test/tst_tools_test.cpp +++ b/tests/tools_test/tst_tools_test.cpp @@ -4,11 +4,9 @@ #include #include -#include "common.h" #include "tools/base32.h" #include "tools/leb128.h" #include "tools/cardecoder.h" -#include "tools/accountmanager.h" class tools_test : public QObject { @@ -25,7 +23,6 @@ private slots: void test_base32(); void test_Leb128(); void test_CarDecoder(); - void test_AccountManager(); }; tools_test::tools_test() @@ -191,51 +188,6 @@ void tools_test::test_CarDecoder() repo.close(); } -void tools_test::test_AccountManager() -{ - QString temp_path = Common::appDataFolder() + "/account.json"; - if (QFile::exists(temp_path)) { - QFile::remove(temp_path); - } - - AccountManager *manager = AccountManager::getInstance(); - AtProtocolInterface::AccountData account; - QList actuals; - - manager->updateAccount("/account/account1", "id1", "password1", "did:plc:account1", - "account1.relog.tech", "account1@relog.tech", "accessJwt_account1", - "refreshJwt_account1", true); - manager->updateAccount("/account/account2", "id2", "password2", "did:plc:account2", - "account2.relog.tech", "account2@relog.tech", "accessJwt_account2", - "refreshJwt_account2", true); - - QStringList uuids = manager->getUuids(); - - QVERIFY(uuids.count() == 2); - - account = manager->getAccount(uuids.at(0)); - QVERIFY(account.service == "/account/account1"); - QVERIFY(account.password == "password1"); - QVERIFY(account.did == "did:plc:account1"); - QVERIFY(account.handle == "account1.relog.tech"); - QVERIFY(account.email == "account1@relog.tech"); - QVERIFY(account.accessJwt == "accessJwt_account1"); - QVERIFY(account.refreshJwt == "refreshJwt_account1"); - - account = manager->getAccount(uuids.at(1)); - QVERIFY(account.service == "/account/account2"); - QVERIFY(account.password == "password2"); - QVERIFY(account.did == "did:plc:account2"); - QVERIFY(account.handle == "account2.relog.tech"); - QVERIFY(account.email == "account2@relog.tech"); - QVERIFY(account.accessJwt == "accessJwt_account2"); - QVERIFY(account.refreshJwt == "refreshJwt_account2"); - - QJsonDocument doc = manager->save(); - - Common::saveJsonDocument(doc, QStringLiteral("account.json")); -} - QTEST_MAIN(tools_test) #include "tst_tools_test.moc" From 4ce48704a5695ea314b77e5ffa7848eed12f3efd Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 17 Oct 2024 01:15:41 +0900 Subject: [PATCH 094/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E7=AE=A1=E7=90=86=E3=81=AE=E5=86=85=E5=AE=B9=E3=82=92?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 194 +++++++++++++++++++- lib/tools/accountmanager.h | 16 +- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 20 +- 3 files changed, 213 insertions(+), 17 deletions(-) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index b182dadb..fac55c70 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -1,5 +1,11 @@ #include "accountmanager.h" #include "encryption.h" +#include "extension/com/atproto/server/comatprotoservercreatesessionex.h" +#include "extension/com/atproto/server/comatprotoserverrefreshsessionex.h" +#include "extension/com/atproto/repo/comatprotorepogetrecordex.h" +#include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" +#include "extension/directory/plc/directoryplc.h" +#include "tools/pinnedpostcache.h" #include #include @@ -8,6 +14,11 @@ using AtProtocolInterface::AccountData; using AtProtocolInterface::AccountStatus; +using AtProtocolInterface::AppBskyActorGetProfile; +using AtProtocolInterface::ComAtprotoRepoGetRecordEx; +using AtProtocolInterface::ComAtprotoServerCreateSessionEx; +using AtProtocolInterface::ComAtprotoServerRefreshSessionEx; +using AtProtocolInterface::DirectoryPlc; class AccountManager::Private : public QObject { @@ -24,7 +35,11 @@ class AccountManager::Private : public QObject const QString &email, const QString &accessJwt, const QString &refreshJwt, const QString &thread_gate_type, const AccountStatus status); + void createSession(); + void refreshSession(bool initial = false); void getProfile(); + void getServiceEndpoint(const QString &did, const QString &service, + std::function callback); private: AccountManager *q; @@ -33,7 +48,7 @@ class AccountManager::Private : public QObject Encryption m_encryption; }; -AccountManager::Private::Private(AccountManager *parent) +AccountManager::Private::Private(AccountManager *parent) : q(parent) { qDebug().noquote() << this << "AccountManager::Private()"; } @@ -104,9 +119,9 @@ void AccountManager::Private::load(const QJsonObject &object) m_account.post_gate_quote_enabled = object.value("post_gate_quote_enabled").toBool(true); if (temp_refresh.isEmpty()) { - // createSession(m_accountList.count() - 1); + createSession(); } else { - // refreshSession(m_accountList.count() - 1, true); + refreshSession(true); } } @@ -136,9 +151,140 @@ void AccountManager::Private::updateAccount(const QString &uuid, const QString & m_account.status = status; } +void AccountManager::Private::createSession() +{ + ComAtprotoServerCreateSessionEx *session = new ComAtprotoServerCreateSessionEx(this); + connect(session, &ComAtprotoServerCreateSessionEx::finished, [=](bool success) { + // qDebug() << session << session->service() << session->did() << + // session->handle() + // << session->email() << session->accessJwt() << session->refreshJwt(); + // qDebug() << service << identifier << password; + if (success) { + qDebug() << "Create session" << session->did() << session->handle(); + m_account.did = session->did(); + m_account.handle = session->handle(); + m_account.email = session->email(); + m_account.accessJwt = session->accessJwt(); + m_account.refreshJwt = session->refreshJwt(); + m_account.status = AccountStatus::Authorized; + + emit q->updatedSession(m_account.uuid); + + // 詳細を取得 + getProfile(); + } else { + qDebug() << "Fail createSession."; + m_account.status = AccountStatus::Unauthorized; + emit q->errorOccured(session->errorCode(), session->errorMessage()); + } + q->checkAllAccountsReady(); + if (q->allAccountTried()) { + emit q->finished(); + } + session->deleteLater(); + }); + session->setAccount(m_account); + session->createSession(m_account.identifier, m_account.password, QString()); +} + +void AccountManager::Private::refreshSession(bool initial) +{ + + ComAtprotoServerRefreshSessionEx *session = new ComAtprotoServerRefreshSessionEx(this); + connect(session, &ComAtprotoServerRefreshSessionEx::finished, [=](bool success) { + if (success) { + qDebug() << "Refresh session" << session->did() << session->handle() + << session->email(); + m_account.did = session->did(); + m_account.handle = session->handle(); + m_account.email = session->email(); + m_account.accessJwt = session->accessJwt(); + m_account.refreshJwt = session->refreshJwt(); + m_account.status = AccountStatus::Authorized; + + // 詳細を取得 + getProfile(); + } else { + if (initial) { + // 初期化時のみ(つまりloadから呼ばれたときだけは失敗したらcreateSessionで再スタート) + qDebug() << "Initial refresh session fail."; + m_account.status = AccountStatus::Unknown; + createSession(); + } else { + m_account.status = AccountStatus::Unauthorized; + emit q->errorOccured(session->errorCode(), session->errorMessage()); + } + } + emit q->updatedSession(m_account.uuid); + // emit dataChanged(index(row), index(row)); + q->checkAllAccountsReady(); + if (q->allAccountTried()) { + emit q->finished(); + } + session->deleteLater(); + }); + session->setAccount(m_account); + session->refreshSession(); +} + void AccountManager::Private::getProfile() { - // + + getServiceEndpoint(m_account.did, m_account.service, [=](const QString &service_endpoint) { + m_account.service_endpoint = service_endpoint; + qDebug().noquote() << "Update service endpoint" << m_account.service << "->" + << m_account.service_endpoint; + + AppBskyActorGetProfile *profile = new AppBskyActorGetProfile(this); + connect(profile, &AppBskyActorGetProfile::finished, [=](bool success) { + if (success) { + AtProtocolType::AppBskyActorDefs::ProfileViewDetailed detail = + profile->profileViewDetailed(); + qDebug() << "Update profile detailed" << detail.displayName << detail.description; + m_account.displayName = detail.displayName; + m_account.description = detail.description; + m_account.avatar = detail.avatar; + m_account.banner = detail.banner; + + q->save(); + + emit q->updatedAccount(m_account.uuid); + // emit dataChanged(index(row), index(row)); + + qDebug() << "Update pinned post" << detail.pinnedPost.uri; + PinnedPostCache::getInstance()->update(m_account.did, detail.pinnedPost.uri); + } else { + emit q->errorOccured(profile->errorCode(), profile->errorMessage()); + } + profile->deleteLater(); + }); + profile->setAccount(m_account); + profile->getProfile(m_account.did); + }); +} + +void AccountManager::Private::getServiceEndpoint(const QString &did, const QString &service, + std::function callback) +{ + if (did.isEmpty()) { + callback(service); + return; + } + if (!service.startsWith("https://bsky.social")) { + callback(service); + return; + } + + DirectoryPlc *plc = new DirectoryPlc(this); + connect(plc, &DirectoryPlc::finished, this, [=](bool success) { + if (success) { + callback(plc->serviceEndpoint()); + } else { + callback(service); + } + plc->deleteLater(); + }); + plc->directory(did); } AccountManager::AccountManager(QObject *parent) : QObject { parent } @@ -188,12 +334,15 @@ void AccountManager::load(QJsonDocument &doc) dList.append(new AccountManager::Private(this)); dIndex[uuid] = dList.count() - 1; } - dList.last()->load(item.toObject()); + dList.at(dIndex[uuid])->load(item.toObject()); } if (!has_main) { // mainになっているものがない } } + if (dList.isEmpty()) { + emit finished(); + } } AccountData AccountManager::getAccount(const QString &uuid) const @@ -229,6 +378,7 @@ void AccountManager::updateAccount(const QString &service, const QString &identi uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); } + // save(); } void AccountManager::removeAccount(const QString &uuid) @@ -271,7 +421,7 @@ void AccountManager::setMainAccount(const QString &uuid) } } -bool AccountManager::checkAllAccountsReady() const +bool AccountManager::checkAllAccountsReady() { int ready_count = 0; for (const auto d : dList) { @@ -279,7 +429,13 @@ bool AccountManager::checkAllAccountsReady() const ready_count++; } } - return (!dList.isEmpty() && dList.count() == ready_count); + setAllAccountsReady(!dList.isEmpty() && dList.count() == ready_count); + return allAccountsReady(); +} + +int AccountManager::indexAt(const QString &uuid) +{ + return dIndex.value(uuid, -1); } QStringList AccountManager::getUuids() const @@ -290,3 +446,27 @@ QStringList AccountManager::getUuids() const } return uuids; } + +bool AccountManager::allAccountTried() const +{ + int count = 0; + for (const auto d : dList) { + if (d->getAccount().status != AccountStatus::Unknown) { + count++; + } + } + return (dList.count() == count); +} + +bool AccountManager::allAccountsReady() const +{ + return m_allAccountsReady; +} + +void AccountManager::setAllAccountsReady(bool newAllAccountsReady) +{ + if (m_allAccountsReady == newAllAccountsReady) + return; + m_allAccountsReady = newAllAccountsReady; + emit allAccountsReadyChanged(); +} diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 606621fe..aa9153b8 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -13,6 +13,8 @@ class AccountManager : public QObject explicit AccountManager(QObject *parent = nullptr); ~AccountManager(); + Q_PROPERTY(bool allAccountsReady READ allAccountsReady WRITE setAllAccountsReady NOTIFY + allAccountsReadyChanged FINAL) public: static AccountManager *getInstance(); void clear(); @@ -28,16 +30,28 @@ class AccountManager : public QObject void updateAccountProfile(const QString &uuid); int getMainAccountIndex() const; void setMainAccount(const QString &uuid); - bool checkAllAccountsReady() const; + bool checkAllAccountsReady(); + int indexAt(const QString &uuid); QStringList getUuids() const; + bool allAccountsReady() const; + void setAllAccountsReady(bool newAllAccountsReady); + signals: + void errorOccured(const QString &code, const QString &message); + void updatedSession(const QString &uuid); + void updatedAccount(const QString &uuid); + void finished(); + void allAccountsReadyChanged(); private: class Private; QList dList; QHash dIndex; Q_DISABLE_COPY_MOVE(AccountManager) + + bool allAccountTried() const; + bool m_allAccountsReady; }; #endif // ACCOUNTMANAGER_H diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 49f59bb0..8cc17d5e 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -335,8 +335,10 @@ void hagoromo_test::test_AccountManager() QVERIFY(manager->checkAllAccountsReady() == false); - QJsonDocument doc = manager->save(); - Common::saveJsonDocument(doc, QStringLiteral("account.json")); + { + QJsonDocument doc = manager->save(); + Common::saveJsonDocument(doc, QStringLiteral("account.json")); + } manager->removeAccount(manager->getUuids().at(0)); QVERIFY(manager->getUuids().count() == 1); @@ -353,13 +355,13 @@ void hagoromo_test::test_AccountManager() manager->clear(); QVERIFY(manager->getUuids().isEmpty()); - // { - // QSignalSpy spy(&model2, SIGNAL(updatedAccount(int, const QString &))); - // model2.load(); - // spy.wait(10 * 1000); - // spy.wait(10 * 1000); - // QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); - // } + { + QJsonDocument doc = Common::loadJsonDocument(QStringLiteral("account.json")); + QSignalSpy spy(manager, SIGNAL(finished())); + manager->load(doc); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + } } void hagoromo_test::test_ListsListModel() From fe7b2790a7dbf9450d10a0d26de9cde684cabdf2 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 17 Oct 2024 01:34:32 +0900 Subject: [PATCH 095/127] =?UTF-8?q?common.h=E3=82=92lib=E3=81=B8=E7=A7=BB?= =?UTF-8?q?=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/qtquick.pri | 1 - {app/qtquick => lib}/common.h | 4 +--- lib/lib.pro | 3 ++- tests/atprotocol_test/tst_atprotocol_test.cpp | 2 +- tests/chat_test/tst_chat_test.cpp | 2 +- tests/hagoromo_test/tst_hagoromo_test.cpp | 2 +- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 2 +- tests/http_test/tst_http_test.cpp | 2 +- tests/log_test/tst_log_test.cpp | 2 +- tests/oauth_test/tst_oauth_test.cpp | 2 +- tests/realtime_test/tst_realtime_test.cpp | 2 +- tests/search_test/tst_search_test.cpp | 2 +- tests/tools_test/tst_tools_test.cpp | 2 +- 13 files changed, 13 insertions(+), 15 deletions(-) rename {app/qtquick => lib}/common.h (95%) diff --git a/app/qtquick/qtquick.pri b/app/qtquick/qtquick.pri index 1122d402..cce03fdd 100644 --- a/app/qtquick/qtquick.pri +++ b/app/qtquick/qtquick.pri @@ -66,7 +66,6 @@ HEADERS += \ $$PWD/chat/chatmessagelistmodel.h \ $$PWD/column/columnlistmodel.h \ $$PWD/column/feedtypelistmodel.h \ - $$PWD/common.h \ $$PWD/controls/calendartablemodel.h \ $$PWD/controls/embedimagelistmodel.h \ $$PWD/controls/languagelistmodel.h \ diff --git a/app/qtquick/common.h b/lib/common.h similarity index 95% rename from app/qtquick/common.h rename to lib/common.h index 3fda90bd..331db3c3 100644 --- a/app/qtquick/common.h +++ b/lib/common.h @@ -16,9 +16,7 @@ inline QString appDataFolder() .arg(QCoreApplication::organizationName()) .arg(QCoreApplication::applicationName()) .arg( -#if defined(HAGOROMO_UNIT_TEST) - QStringLiteral("_unittest") -#elif defined(QT_DEBUG) +#ifdef QT_DEBUG QStringLiteral("_debug") #else QString() diff --git a/lib/lib.pro b/lib/lib.pro index 0831f8e7..af39c7ac 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -258,7 +258,8 @@ HEADERS += \ $$PWD/tools/listitemscache.h \ $$PWD/tools/opengraphprotocol.h \ $$PWD/tools/pinnedpostcache.h \ - $$PWD/tools/qstringex.h + $$PWD/tools/qstringex.h \ + common.h RESOURCES += \ $$PWD/lib.qrc diff --git a/tests/atprotocol_test/tst_atprotocol_test.cpp b/tests/atprotocol_test/tst_atprotocol_test.cpp index ea60273d..9035b05d 100644 --- a/tests/atprotocol_test/tst_atprotocol_test.cpp +++ b/tests/atprotocol_test/tst_atprotocol_test.cpp @@ -102,7 +102,7 @@ private slots: atprotocol_test::atprotocol_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/chat_test/tst_chat_test.cpp b/tests/chat_test/tst_chat_test.cpp index be220f27..6dd87743 100644 --- a/tests/chat_test/tst_chat_test.cpp +++ b/tests/chat_test/tst_chat_test.cpp @@ -32,7 +32,7 @@ private slots: chat_test::chat_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/hagoromo_test/tst_hagoromo_test.cpp b/tests/hagoromo_test/tst_hagoromo_test.cpp index 895ef183..36050f7d 100644 --- a/tests/hagoromo_test/tst_hagoromo_test.cpp +++ b/tests/hagoromo_test/tst_hagoromo_test.cpp @@ -77,7 +77,7 @@ private slots: hagoromo_test::hagoromo_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 8cc17d5e..1093be62 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -55,7 +55,7 @@ private slots: hagoromo_test::hagoromo_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/http_test/tst_http_test.cpp b/tests/http_test/tst_http_test.cpp index 29bfcb21..b273409d 100644 --- a/tests/http_test/tst_http_test.cpp +++ b/tests/http_test/tst_http_test.cpp @@ -29,7 +29,7 @@ private slots: http_test::http_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/log_test/tst_log_test.cpp b/tests/log_test/tst_log_test.cpp index 2ffb0ee9..0938d939 100644 --- a/tests/log_test/tst_log_test.cpp +++ b/tests/log_test/tst_log_test.cpp @@ -42,7 +42,7 @@ log_test::log_test() qRegisterMetaType>("QList"); QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 0faceed4..87813e6f 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -43,7 +43,7 @@ private slots: oauth_test::oauth_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_server.listen(QHostAddress::LocalHost, 0); connect(&m_server, &SimpleHttpServer::received, this, diff --git a/tests/realtime_test/tst_realtime_test.cpp b/tests/realtime_test/tst_realtime_test.cpp index c9e7645d..5b1dbcf7 100644 --- a/tests/realtime_test/tst_realtime_test.cpp +++ b/tests/realtime_test/tst_realtime_test.cpp @@ -45,7 +45,7 @@ realtime_test::realtime_test() // : m_server(QStringLiteral("name"), QWebSocketServer::SecureMode, this) { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/search_test/tst_search_test.cpp b/tests/search_test/tst_search_test.cpp index b3442919..5983e056 100644 --- a/tests/search_test/tst_search_test.cpp +++ b/tests/search_test/tst_search_test.cpp @@ -30,7 +30,7 @@ private slots: search_test::search_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); m_listenPort = m_mockServer.listen(QHostAddress::LocalHost, 0); m_service = QString("http://localhost:%1/response").arg(m_listenPort); diff --git a/tests/tools_test/tst_tools_test.cpp b/tests/tools_test/tst_tools_test.cpp index f6fcd2ed..1877901a 100644 --- a/tests/tools_test/tst_tools_test.cpp +++ b/tests/tools_test/tst_tools_test.cpp @@ -28,7 +28,7 @@ private slots: tools_test::tools_test() { QCoreApplication::setOrganizationName(QStringLiteral("relog")); - QCoreApplication::setApplicationName(QStringLiteral("Hagoromo")); + QCoreApplication::setApplicationName(QStringLiteral("Hagoromo_unittest")); } tools_test::~tools_test() { } From 06ff39551d77c8e253b05a61f06fbd59ba17650d Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 17 Oct 2024 01:41:17 +0900 Subject: [PATCH 096/127] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=81=A8=E8=AA=AD?= =?UTF-8?q?=E3=81=BF=E8=BE=BC=E3=81=BF=E3=82=92=E7=AE=A1=E7=90=86=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 15 +++++++++------ lib/tools/accountmanager.h | 4 ++-- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 8 ++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index fac55c70..9dc2838d 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -6,6 +6,7 @@ #include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" #include "extension/directory/plc/directoryplc.h" #include "tools/pinnedpostcache.h" +#include "common.h" #include #include @@ -313,7 +314,7 @@ void AccountManager::clear() dIndex.clear(); } -QJsonDocument AccountManager::save() const +void AccountManager::save() const { QJsonArray account_array; @@ -321,11 +322,13 @@ QJsonDocument AccountManager::save() const account_array.append(d->save()); } - return QJsonDocument(account_array); + Common::saveJsonDocument(QJsonDocument(account_array), QStringLiteral("account.json")); } -void AccountManager::load(QJsonDocument &doc) +void AccountManager::load() { + QJsonDocument doc = Common::loadJsonDocument(QStringLiteral("account.json")); + if (doc.isArray()) { bool has_main = false; for (const auto &item : doc.array()) { @@ -360,7 +363,7 @@ void AccountManager::updateAccount(const QString &service, const QString &identi const bool authorized) { bool updated = false; - for (const auto d : dList) { + for (const auto d : qAsConst(dList)) { // for (const auto &uuid : d.keys()) { AccountData account = d->getAccount(); if (account.service == service && account.identifier == identifier) { @@ -378,7 +381,7 @@ void AccountManager::updateAccount(const QString &service, const QString &identi uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); } - // save(); + save(); } void AccountManager::removeAccount(const QString &uuid) @@ -424,7 +427,7 @@ void AccountManager::setMainAccount(const QString &uuid) bool AccountManager::checkAllAccountsReady() { int ready_count = 0; - for (const auto d : dList) { + for (const auto d : qAsConst(dList)) { if (d->getAccount().status == AccountStatus::Authorized) { ready_count++; } diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index aa9153b8..50c2e230 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -19,8 +19,8 @@ class AccountManager : public QObject static AccountManager *getInstance(); void clear(); - QJsonDocument save() const; - void load(QJsonDocument &doc); + void save() const; + void load(); AtProtocolInterface::AccountData getAccount(const QString &uuid) const; void updateAccount(const QString &service, const QString &identifier, const QString &password, diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 1093be62..d566eb4a 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -335,10 +335,7 @@ void hagoromo_test::test_AccountManager() QVERIFY(manager->checkAllAccountsReady() == false); - { - QJsonDocument doc = manager->save(); - Common::saveJsonDocument(doc, QStringLiteral("account.json")); - } + manager->save(); manager->removeAccount(manager->getUuids().at(0)); QVERIFY(manager->getUuids().count() == 1); @@ -356,9 +353,8 @@ void hagoromo_test::test_AccountManager() QVERIFY(manager->getUuids().isEmpty()); { - QJsonDocument doc = Common::loadJsonDocument(QStringLiteral("account.json")); QSignalSpy spy(manager, SIGNAL(finished())); - manager->load(doc); + manager->load(); spy.wait(); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } From fb97d7d2c285aeb95541bad38c01a7033f91ebe6 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 18 Oct 2024 01:39:54 +0900 Subject: [PATCH 097/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E7=AE=A1=E7=90=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/main.qml | 8 +- app/qtquick/account/accountlistmodel.cpp | 548 +++++--------------- app/qtquick/account/accountlistmodel.h | 16 +- lib/tools/accountmanager.cpp | 170 +++++- lib/tools/accountmanager.h | 44 +- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 91 +++- 6 files changed, 404 insertions(+), 473 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 9daede1b..ce91e29c 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -467,11 +467,11 @@ ApplicationWindow { // アカウント管理で内容が変更されたときにカラムとインデックスの関係が崩れるのでuuidで確認する AccountListModel { id: accountListModel - onUpdatedSession: (row, uuid) => { - console.log("onUpdatedSession:" + row + ", " + uuid) + onUpdatedSession: (uuid) => { + console.log("onUpdatedSession:" + uuid) } - onUpdatedAccount: (row, uuid) => { - console.log("onUpdatedAccount:" + row + ", " + uuid) + onUpdatedAccount: (uuid) => { + console.log("onUpdatedAccount:" + uuid) // カラムを更新しにいく repeater.updateAccount(uuid) } diff --git a/app/qtquick/account/accountlistmodel.cpp b/app/qtquick/account/accountlistmodel.cpp index ce2d06cd..955224a4 100644 --- a/app/qtquick/account/accountlistmodel.cpp +++ b/app/qtquick/account/accountlistmodel.cpp @@ -1,11 +1,8 @@ #include "accountlistmodel.h" -#include "common.h" #include "extension/com/atproto/server/comatprotoservercreatesessionex.h" #include "extension/com/atproto/server/comatprotoserverrefreshsessionex.h" #include "extension/com/atproto/repo/comatprotorepogetrecordex.h" #include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" -#include "atprotocol/lexicons_func_unknown.h" -#include "tools/pinnedpostcache.h" #include "extension/directory/plc/directoryplc.h" #include @@ -24,21 +21,81 @@ using AtProtocolInterface::ComAtprotoServerCreateSessionEx; using AtProtocolInterface::ComAtprotoServerRefreshSessionEx; using AtProtocolInterface::DirectoryPlc; -AccountListModel::AccountListModel(QObject *parent) - : QAbstractListModel { parent }, m_allAccountsReady(false) +AccountListModel::AccountListModel(QObject *parent) : QAbstractListModel { parent } { + m_roleTo[UuidRole] = AccountManager::AccountManagerRoles::UuidRole; + m_roleTo[IsMainRole] = AccountManager::AccountManagerRoles::IsMainRole; + m_roleTo[ServiceRole] = AccountManager::AccountManagerRoles::ServiceRole; + m_roleTo[ServiceEndpointRole] = AccountManager::AccountManagerRoles::ServiceEndpointRole; + m_roleTo[IdentifierRole] = AccountManager::AccountManagerRoles::IdentifierRole; + m_roleTo[PasswordRole] = AccountManager::AccountManagerRoles::PasswordRole; + m_roleTo[DidRole] = AccountManager::AccountManagerRoles::DidRole; + m_roleTo[HandleRole] = AccountManager::AccountManagerRoles::HandleRole; + m_roleTo[EmailRole] = AccountManager::AccountManagerRoles::EmailRole; + m_roleTo[AccessJwtRole] = AccountManager::AccountManagerRoles::AccessJwtRole; + m_roleTo[RefreshJwtRole] = AccountManager::AccountManagerRoles::RefreshJwtRole; + m_roleTo[DisplayNameRole] = AccountManager::AccountManagerRoles::DisplayNameRole; + m_roleTo[DescriptionRole] = AccountManager::AccountManagerRoles::DescriptionRole; + m_roleTo[AvatarRole] = AccountManager::AccountManagerRoles::AvatarRole; + m_roleTo[PostLanguagesRole] = AccountManager::AccountManagerRoles::PostLanguagesRole; + m_roleTo[ThreadGateTypeRole] = AccountManager::AccountManagerRoles::ThreadGateTypeRole; + m_roleTo[ThreadGateOptionsRole] = AccountManager::AccountManagerRoles::ThreadGateOptionsRole; + m_roleTo[PostGateQuoteEnabledRole] = + AccountManager::AccountManagerRoles::PostGateQuoteEnabledRole; + m_roleTo[StatusRole] = AccountManager::AccountManagerRoles::StatusRole; + m_roleTo[AuthorizedRole] = AccountManager::AccountManagerRoles::AuthorizedRole; + connect(&m_timer, &QTimer::timeout, [=]() { - for (int row = 0; row < m_accountList.count(); row++) { + AccountManager *manager = AccountManager::getInstance(); + for (int row = 0; row < manager->count(); row++) { refreshSession(row); } }); m_timer.start(60 * 60 * 1000); + + AccountManager *manager = AccountManager::getInstance(); + + connect(manager, &AccountManager::errorOccured, this, &AccountListModel::errorOccured); + connect(manager, &AccountManager::updatedSession, this, &AccountListModel::updatedSession); + connect(manager, &AccountManager::updatedAccount, this, &AccountListModel::updatedAccount); + connect(manager, &AccountManager::countChanged, this, &AccountListModel::countChanged); + connect(manager, &AccountManager::finished, this, &AccountListModel::finished); + connect(manager, &AccountManager::allAccountsReadyChanged, this, + &AccountListModel::allAccountsReadyChanged); + + // 各カラムがアカウント情報をAccountManagerから取得するようになれば↓の2つをQMLへ投げる必要はなくなる + // dataChangedは必要 + connect(this, &AccountListModel::updatedSession, this, [=](const QString &uuid) { + int row = manager->indexAt(uuid); + if (row >= 0) { + emit dataChanged(index(row), index(row)); + } + }); + connect(this, &AccountListModel::updatedAccount, this, [=](const QString &uuid) { + int row = manager->indexAt(uuid); + if (row >= 0) { + emit dataChanged(index(row), index(row)); + } + }); +} + +AccountListModel::~AccountListModel() +{ + AccountManager *manager = AccountManager::getInstance(); + + disconnect(manager, &AccountManager::errorOccured, this, &AccountListModel::errorOccured); + disconnect(manager, &AccountManager::updatedSession, this, &AccountListModel::updatedSession); + disconnect(manager, &AccountManager::updatedAccount, this, &AccountListModel::updatedAccount); + disconnect(manager, &AccountManager::countChanged, this, &AccountListModel::countChanged); + disconnect(manager, &AccountManager::finished, this, &AccountListModel::finished); + disconnect(manager, &AccountManager::allAccountsReadyChanged, this, + &AccountListModel::allAccountsReadyChanged); } int AccountListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) - return m_accountList.count(); + return count(); } QVariant AccountListModel::data(const QModelIndex &index, int role) const @@ -48,101 +105,63 @@ QVariant AccountListModel::data(const QModelIndex &index, int role) const QVariant AccountListModel::item(int row, AccountListModelRoles role) const { - if (row < 0 || row >= m_accountList.count()) + if (row < 0 || row >= count()) return QVariant(); + AccountManager *manager = AccountManager::getInstance(); + const AccountData account = manager->getAccount(manager->getUuid(row)); + if (role == UuidRole) - return m_accountList.at(row).uuid; + return account.uuid; else if (role == IsMainRole) - return m_accountList.at(row).is_main; + return account.is_main; else if (role == ServiceRole) - return m_accountList.at(row).service; + return account.service; else if (role == ServiceEndpointRole) - return m_accountList.at(row).service_endpoint; + return account.service_endpoint; else if (role == IdentifierRole) - return m_accountList.at(row).identifier; + return account.identifier; else if (role == PasswordRole) - return m_accountList.at(row).password; + return account.password; else if (role == DidRole) - return m_accountList.at(row).did; + return account.did; else if (role == HandleRole) - return m_accountList.at(row).handle; + return account.handle; else if (role == EmailRole) - return m_accountList.at(row).email; + return account.email; else if (role == AccessJwtRole) - return m_accountList.at(row).status == AccountStatus::Authorized - ? m_accountList.at(row).accessJwt - : QString(); + return account.status == AccountStatus::Authorized ? account.accessJwt : QString(); else if (role == RefreshJwtRole) - return m_accountList.at(row).refreshJwt; + return account.refreshJwt; else if (role == DisplayNameRole) - return m_accountList.at(row).displayName; + return account.displayName; else if (role == DescriptionRole) - return m_accountList.at(row).description; + return account.description; else if (role == AvatarRole) - return m_accountList.at(row).avatar; + return account.avatar; else if (role == PostLanguagesRole) - return m_accountList[row].post_languages; + return account.post_languages; else if (role == ThreadGateTypeRole) - return m_accountList[row].thread_gate_type; + return account.thread_gate_type; else if (role == ThreadGateOptionsRole) - return m_accountList[row].thread_gate_options; + return account.thread_gate_options; else if (role == PostGateQuoteEnabledRole) - return m_accountList[row].post_gate_quote_enabled; + return account.post_gate_quote_enabled; else if (role == StatusRole) - return static_cast(m_accountList.at(row).status); + return static_cast(account.status); else if (role == AuthorizedRole) - return m_accountList.at(row).status == AccountStatus::Authorized; + return account.status == AccountStatus::Authorized; return QVariant(); } void AccountListModel::update(int row, AccountListModelRoles role, const QVariant &value) { - if (row < 0 || row >= m_accountList.count()) - return; - - if (role == UuidRole) - m_accountList[row].uuid = value.toString(); - else if (role == ServiceRole) - m_accountList[row].service = value.toString(); - else if (role == IdentifierRole) - m_accountList[row].identifier = value.toString(); - else if (role == PasswordRole) - m_accountList[row].password = value.toString(); - else if (role == DidRole) - m_accountList[row].did = value.toString(); - else if (role == HandleRole) - m_accountList[row].handle = value.toString(); - else if (role == EmailRole) - m_accountList[row].email = value.toString(); - else if (role == AccessJwtRole) - m_accountList[row].accessJwt = value.toString(); - else if (role == RefreshJwtRole) - m_accountList[row].refreshJwt = value.toString(); - - else if (role == DisplayNameRole) - m_accountList[row].displayName = value.toString(); - else if (role == DescriptionRole) - m_accountList[row].description = value.toString(); - else if (role == AvatarRole) - m_accountList[row].avatar = value.toString(); - - else if (role == PostLanguagesRole) { - m_accountList[row].post_languages = value.toStringList(); - save(); - } else if (role == ThreadGateTypeRole) { - m_accountList[row].thread_gate_type = value.toString(); - save(); - } else if (role == ThreadGateOptionsRole) { - m_accountList[row].thread_gate_options = value.toStringList(); - save(); - } else if (role == PostGateQuoteEnabledRole) { - m_accountList[row].post_gate_quote_enabled = value.toBool(); - } + AccountManager::getInstance()->update( + row, m_roleTo.value(role, AccountManager::AccountManagerRoles::UnknownRole), value); emit dataChanged(index(row), index(row)); } @@ -153,125 +172,67 @@ void AccountListModel::updateAccount(const QString &service, const QString &iden const QString &accessJwt, const QString &refreshJwt, const bool authorized) { + AccountManager *manager = AccountManager::getInstance(); + const QStringList uuids = manager->getUuids(); + bool updated = false; - for (int i = 0; i < m_accountList.count(); i++) { - if (m_accountList.at(i).service == service - && m_accountList.at(i).identifier == identifier) { + for (const auto &uuid : uuids) { + int i = manager->indexAt(uuid); + const AtProtocolInterface::AccountData account = manager->getAccount(uuid); + if (account.service == service && account.identifier == identifier) { // update - m_accountList[i].password = password; - m_accountList[i].did = did; - m_accountList[i].handle = handle; - m_accountList[i].email = email; - m_accountList[i].accessJwt = accessJwt; - m_accountList[i].refreshJwt = refreshJwt; - m_accountList[i].status = - authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized; + manager->updateAccount(uuid, service, identifier, password, did, handle, email, + accessJwt, refreshJwt, authorized); updated = true; emit dataChanged(index(i), index(i)); } } if (!updated) { // append - AtProtocolInterface::AccountData item; - item.uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); - item.service = service; - item.identifier = identifier; - item.password = password; - item.did = did; - item.handle = handle; - item.email = email; - item.accessJwt = accessJwt; - item.refreshJwt = refreshJwt; - item.thread_gate_type = "everybody"; - item.status = authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized; - beginInsertRows(QModelIndex(), count(), count()); - m_accountList.append(item); + manager->updateAccount(QString(), service, identifier, password, did, handle, email, + accessJwt, refreshJwt, authorized); endInsertRows(); - - emit countChanged(); } - - checkAllAccountsReady(); - save(); } void AccountListModel::removeAccount(int row) { - if (row < 0 || row >= m_accountList.count()) + AccountManager *manager = AccountManager::getInstance(); + if (row < 0 || row >= manager->count()) return; beginRemoveRows(QModelIndex(), row, row); - m_accountList.removeAt(row); + manager->removeAccount(manager->getUuid(row)); endRemoveRows(); - emit countChanged(); - - checkAllAccountsReady(); - save(); } void AccountListModel::updateAccountProfile(const QString &service, const QString &identifier) { - for (int i = 0; i < m_accountList.count(); i++) { - if (m_accountList.at(i).service == service && m_accountList.at(i).identifier == identifier - && m_accountList.at(i).status == AccountStatus::Authorized) { - getProfile(i); + const QStringList uuids = AccountManager::getInstance()->getUuids(); + for (const auto &uuid : uuids) { + AccountData account = AccountManager::getInstance()->getAccount(uuid); + if (account.service == service && account.identifier == identifier) { + AccountManager::getInstance()->getProfile(indexAt(uuid)); } } } int AccountListModel::indexAt(const QString &uuid) { - if (uuid.isEmpty()) - return -1; - - for (int i = 0; i < m_accountList.count(); i++) { - if (m_accountList.at(i).uuid == uuid) { - return i; - } - } - return -1; + return AccountManager::getInstance()->indexAt(uuid); } int AccountListModel::getMainAccountIndex() const { - if (m_accountList.isEmpty()) - return -1; - - for (int i = 0; i < m_accountList.count(); i++) { - if (m_accountList.at(i).is_main) { - return i; - } - } - return 0; + return AccountManager::getInstance()->getMainAccountIndex(); } void AccountListModel::setMainAccount(int row) { - if (row < 0 || row >= m_accountList.count()) - return; + AccountManager::getInstance()->setMainAccount(row); - for (int i = 0; i < m_accountList.count(); i++) { - bool new_val = (row == i); - if (m_accountList.at(i).is_main != new_val) { - m_accountList[i].is_main = new_val; - emit dataChanged(index(i), index(i)); - } - } - - save(); -} - -bool AccountListModel::checkAllAccountsReady() -{ - int ready_count = 0; - for (const AccountData &item : qAsConst(m_accountList)) { - if (item.status == AccountStatus::Authorized) { - ready_count++; - } - } - setAllAccountsReady(!m_accountList.isEmpty() && m_accountList.count() == ready_count); - return allAccountsReady(); + emit dataChanged(index(0), index(AccountManager::getInstance()->count() - 1)); } void AccountListModel::refreshAccountSession(const QString &uuid) @@ -292,125 +253,32 @@ void AccountListModel::refreshAccountProfile(const QString &uuid) void AccountListModel::save() const { - QSettings settings; - - QJsonArray account_array; - for (const AccountData &item : m_accountList) { - QJsonObject account_item; - account_item["uuid"] = item.uuid; - account_item["is_main"] = item.is_main; - account_item["service"] = item.service; - account_item["identifier"] = item.identifier; - account_item["password"] = m_encryption.encrypt(item.password); - account_item["refresh_jwt"] = m_encryption.encrypt(item.refreshJwt); - - if (!item.post_languages.isEmpty()) { - QJsonArray post_langs; - for (const auto &lang : item.post_languages) { - post_langs.append(lang); - } - account_item["post_languages"] = post_langs; - } - - if (item.thread_gate_type.isEmpty()) { - account_item["thread_gate_type"] = "everybody"; - } else { - account_item["thread_gate_type"] = item.thread_gate_type; - } - if (!item.thread_gate_options.isEmpty()) { - QJsonArray thread_gate_options; - for (const auto &option : item.thread_gate_options) { - thread_gate_options.append(option); - } - account_item["thread_gate_options"] = thread_gate_options; - } - account_item["post_gate_quote_enabled"] = item.post_gate_quote_enabled; - - account_array.append(account_item); - } - - Common::saveJsonDocument(QJsonDocument(account_array), QStringLiteral("account.json")); + AccountManager::getInstance()->save(); } void AccountListModel::load() { - if (!m_accountList.isEmpty()) { + AccountManager *manager = AccountManager::getInstance(); + + if (manager->count() > 0) { beginRemoveRows(QModelIndex(), 0, count() - 1); - m_accountList.clear(); + manager->clear(); endRemoveRows(); - emit countChanged(); } - QJsonDocument doc = Common::loadJsonDocument(QStringLiteral("account.json")); - - if (doc.isArray()) { - bool has_main = false; - for (int i = 0; i < doc.array().count(); i++) { - if (doc.array().at(i).isObject()) { - AccountData item; - QString temp_refresh = doc.array().at(i).toObject().value("refresh_jwt").toString(); - item.uuid = doc.array().at(i).toObject().value("uuid").toString(); - item.is_main = doc.array().at(i).toObject().value("is_main").toBool(); - item.service = doc.array().at(i).toObject().value("service").toString(); - item.service_endpoint = item.service; - item.identifier = doc.array().at(i).toObject().value("identifier").toString(); - item.password = m_encryption.decrypt( - doc.array().at(i).toObject().value("password").toString()); - item.refreshJwt = m_encryption.decrypt(temp_refresh); - item.handle = item.identifier; - for (const auto &value : - doc.array().at(i).toObject().value("post_languages").toArray()) { - item.post_languages.append(value.toString()); - } - - item.thread_gate_type = doc.array() - .at(i) - .toObject() - .value("thread_gate_type") - .toString("everybody"); - if (item.thread_gate_type.isEmpty()) { - item.thread_gate_type = "everybody"; - } - for (const auto &value : - doc.array().at(i).toObject().value("thread_gate_options").toArray()) { - item.thread_gate_options.append(value.toString()); - } - item.post_gate_quote_enabled = - doc.array().at(i).toObject().value("post_gate_quote_enabled").toBool(true); - - beginInsertRows(QModelIndex(), count(), count()); - m_accountList.append(item); - endInsertRows(); - emit countChanged(); - - if (temp_refresh.isEmpty()) { - createSession(m_accountList.count() - 1); - } else { - refreshSession(m_accountList.count() - 1, true); - } - - if (item.is_main) { - has_main = true; - } - } - } - if (!has_main && !m_accountList.isEmpty()) { - // mainになっているものがない - m_accountList[0].is_main = true; - } - } - - checkAllAccountsReady(); - if (m_accountList.isEmpty()) { - emit finished(); - } + manager->load(); + beginInsertRows(QModelIndex(), 0, manager->count() - 1); + endInsertRows(); } QVariant AccountListModel::account(int row) const { - if (row < 0 || row >= m_accountList.count()) + QString uuid = AccountManager::getInstance()->getUuid(row); + if (uuid.isEmpty()) { return QVariant(); - return QVariant::fromValue(m_accountList.at(row)); + } else { + return QVariant::fromValue(AccountManager::getInstance()->getAccount(uuid)); + } } QHash AccountListModel::roleNames() const @@ -443,179 +311,25 @@ QHash AccountListModel::roleNames() const void AccountListModel::createSession(int row) { - if (row < 0 || row >= m_accountList.count()) - return; - - ComAtprotoServerCreateSessionEx *session = new ComAtprotoServerCreateSessionEx(this); - connect(session, &ComAtprotoServerCreateSessionEx::finished, [=](bool success) { - // qDebug() << session << session->service() << session->did() << - // session->handle() - // << session->email() << session->accessJwt() << session->refreshJwt(); - // qDebug() << service << identifier << password; - if (success) { - qDebug() << "Create session" << session->did() << session->handle(); - m_accountList[row].did = session->did(); - m_accountList[row].handle = session->handle(); - m_accountList[row].email = session->email(); - m_accountList[row].accessJwt = session->accessJwt(); - m_accountList[row].refreshJwt = session->refreshJwt(); - m_accountList[row].status = AccountStatus::Authorized; - - emit updatedSession(row, m_accountList[row].uuid); - - // 詳細を取得 - getProfile(row); - } else { - qDebug() << "Fail createSession."; - m_accountList[row].status = AccountStatus::Unauthorized; - emit errorOccured(session->errorCode(), session->errorMessage()); - } - emit dataChanged(index(row), index(row)); - checkAllAccountsReady(); - if (allAccountTried()) { - emit finished(); - } - session->deleteLater(); - }); - session->setAccount(m_accountList.at(row)); - session->createSession(m_accountList.at(row).identifier, m_accountList.at(row).password, - QString()); + AccountManager::getInstance()->createSession(row); } void AccountListModel::refreshSession(int row, bool initial) { - if (row < 0 || row >= m_accountList.count()) - return; - - ComAtprotoServerRefreshSessionEx *session = new ComAtprotoServerRefreshSessionEx(this); - connect(session, &ComAtprotoServerRefreshSessionEx::finished, [=](bool success) { - if (success) { - qDebug() << "Refresh session" << session->did() << session->handle() - << session->email(); - m_accountList[row].did = session->did(); - m_accountList[row].handle = session->handle(); - m_accountList[row].email = session->email(); - m_accountList[row].accessJwt = session->accessJwt(); - m_accountList[row].refreshJwt = session->refreshJwt(); - m_accountList[row].status = AccountStatus::Authorized; - - emit updatedSession(row, m_accountList[row].uuid); - - // 詳細を取得 - getProfile(row); - } else { - if (initial) { - // 初期化時のみ(つまりloadから呼ばれたときだけは失敗したらcreateSessionで再スタート) - qDebug() << "Initial refresh session fail."; - m_accountList[row].status = AccountStatus::Unknown; - createSession(row); - } else { - m_accountList[row].status = AccountStatus::Unauthorized; - emit errorOccured(session->errorCode(), session->errorMessage()); - } - } - emit dataChanged(index(row), index(row)); - checkAllAccountsReady(); - if (allAccountTried()) { - emit finished(); - } - session->deleteLater(); - }); - session->setAccount(m_accountList.at(row)); - session->refreshSession(); + AccountManager::getInstance()->refreshSession(row, initial); } void AccountListModel::getProfile(int row) { - if (row < 0 || row >= m_accountList.count()) - return; - - getServiceEndpoint(m_accountList.at(row).did, m_accountList.at(row).service, - [=](const QString &service_endpoint) { - m_accountList[row].service_endpoint = service_endpoint; - qDebug().noquote() - << "Update service endpoint" << m_accountList.at(row).service - << "->" << m_accountList.at(row).service_endpoint; - - AppBskyActorGetProfile *profile = new AppBskyActorGetProfile(this); - profile->setAccount(m_accountList.at(row)); - connect(profile, &AppBskyActorGetProfile::finished, [=](bool success) { - if (success) { - AtProtocolType::AppBskyActorDefs::ProfileViewDetailed detail = - profile->profileViewDetailed(); - qDebug() << "Update profile detailed" << detail.displayName - << detail.description; - m_accountList[row].displayName = detail.displayName; - m_accountList[row].description = detail.description; - m_accountList[row].avatar = detail.avatar; - m_accountList[row].banner = detail.banner; - - save(); - - emit updatedAccount(row, m_accountList[row].uuid); - emit dataChanged(index(row), index(row)); - - qDebug() << "Update pinned post" << detail.pinnedPost.uri; - PinnedPostCache::getInstance()->update(m_accountList.at(row).did, - detail.pinnedPost.uri); - } else { - emit errorOccured(profile->errorCode(), profile->errorMessage()); - } - profile->deleteLater(); - }); - profile->getProfile(m_accountList.at(row).did); - }); -} - -void AccountListModel::getServiceEndpoint(const QString &did, const QString &service, - std::function callback) -{ - if (did.isEmpty()) { - callback(service); - return; - } - if (!service.startsWith("https://bsky.social")) { - callback(service); - return; - } - - DirectoryPlc *plc = new DirectoryPlc(this); - connect(plc, &DirectoryPlc::finished, this, [=](bool success) { - if (success) { - callback(plc->serviceEndpoint()); - } else { - callback(service); - } - plc->deleteLater(); - }); - plc->directory(did); -} - -bool AccountListModel::allAccountTried() const -{ - int count = 0; - for (const AccountData &item : qAsConst(m_accountList)) { - if (item.status != AccountStatus::Unknown) { - count++; - } - } - return (m_accountList.count() == count); + AccountManager::getInstance()->getProfile(row); } int AccountListModel::count() const { - return m_accountList.count(); + return AccountManager::getInstance()->count(); } bool AccountListModel::allAccountsReady() const { - return m_allAccountsReady; -} - -void AccountListModel::setAllAccountsReady(bool newAllAccountsReady) -{ - if (m_allAccountsReady == newAllAccountsReady) - return; - m_allAccountsReady = newAllAccountsReady; - emit allAccountsReadyChanged(); + return AccountManager::getInstance()->allAccountsReady(); } diff --git a/app/qtquick/account/accountlistmodel.h b/app/qtquick/account/accountlistmodel.h index d9d2943b..b77bacc1 100644 --- a/app/qtquick/account/accountlistmodel.h +++ b/app/qtquick/account/accountlistmodel.h @@ -3,6 +3,7 @@ #include "atprotocol/accessatprotocol.h" #include "tools/encryption.h" +#include "tools/accountmanager.h" #include #include @@ -13,10 +14,10 @@ class AccountListModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged CONSTANT) - Q_PROPERTY(bool allAccountsReady READ allAccountsReady WRITE setAllAccountsReady NOTIFY - allAccountsReadyChanged FINAL) + Q_PROPERTY(bool allAccountsReady READ allAccountsReady NOTIFY allAccountsReadyChanged FINAL) public: explicit AccountListModel(QObject *parent = nullptr); + ~AccountListModel(); // モデルで提供する項目のルールID的な(QML側へ公開するために大文字で始めること) enum AccountListModelRoles { @@ -61,7 +62,6 @@ class AccountListModel : public QAbstractListModel Q_INVOKABLE int indexAt(const QString &uuid); Q_INVOKABLE int getMainAccountIndex() const; Q_INVOKABLE void setMainAccount(int row); - Q_INVOKABLE bool checkAllAccountsReady(); Q_INVOKABLE void refreshAccountSession(const QString &uuid); Q_INVOKABLE void refreshAccountProfile(const QString &uuid); @@ -72,12 +72,11 @@ class AccountListModel : public QAbstractListModel int count() const; bool allAccountsReady() const; - void setAllAccountsReady(bool newAllAccountsReady); signals: void errorOccured(const QString &code, const QString &message); - void updatedSession(int row, const QString &uuid); - void updatedAccount(int row, const QString &uuid); + void updatedSession(const QString &uuid); + void updatedAccount(const QString &uuid); void countChanged(); void finished(); @@ -91,16 +90,13 @@ class AccountListModel : public QAbstractListModel QVariant m_accountTemp; QTimer m_timer; Encryption m_encryption; - bool m_allAccountsReady; + QHash m_roleTo; QString appDataFolder() const; void createSession(int row); void refreshSession(int row, bool initial = false); void getProfile(int row); - void getServiceEndpoint(const QString &did, const QString &service, - std::function callback); - bool allAccountTried() const; }; #endif // ACCOUNTLISTMODEL_H diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 9dc2838d..e2665635 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -30,6 +30,8 @@ class AccountManager::Private : public QObject QJsonObject save() const; void load(const QJsonObject &object); + bool update(AccountManager::AccountManagerRoles role, const QVariant &value); + AccountData getAccount() const; void updateAccount(const QString &uuid, const QString &service, const QString &identifier, const QString &password, const QString &did, const QString &handle, @@ -41,6 +43,7 @@ class AccountManager::Private : public QObject void getProfile(); void getServiceEndpoint(const QString &did, const QString &service, std::function callback); + void setMain(bool is); private: AccountManager *q; @@ -126,6 +129,53 @@ void AccountManager::Private::load(const QJsonObject &object) } } +bool AccountManager::Private::update(AccountManagerRoles role, const QVariant &value) +{ + bool need_save = false; + + if (role == UuidRole) + m_account.uuid = value.toString(); + else if (role == ServiceRole) + m_account.service = value.toString(); + else if (role == ServiceEndpointRole) + m_account.service_endpoint = value.toString(); + else if (role == IdentifierRole) + m_account.identifier = value.toString(); + else if (role == PasswordRole) + m_account.password = value.toString(); + else if (role == DidRole) + m_account.did = value.toString(); + else if (role == HandleRole) + m_account.handle = value.toString(); + else if (role == EmailRole) + m_account.email = value.toString(); + else if (role == AccessJwtRole) + m_account.accessJwt = value.toString(); + else if (role == RefreshJwtRole) + m_account.refreshJwt = value.toString(); + + else if (role == DisplayNameRole) + m_account.displayName = value.toString(); + else if (role == DescriptionRole) + m_account.description = value.toString(); + else if (role == AvatarRole) + m_account.avatar = value.toString(); + + else if (role == PostLanguagesRole) { + m_account.post_languages = value.toStringList(); + need_save = true; + } else if (role == ThreadGateTypeRole) { + m_account.thread_gate_type = value.toString(); + need_save = true; + } else if (role == ThreadGateOptionsRole) { + m_account.thread_gate_options = value.toStringList(); + need_save = true; + } else if (role == PostGateQuoteEnabledRole) { + m_account.post_gate_quote_enabled = value.toBool(); + } + return need_save; +} + AccountData AccountManager::Private::getAccount() const { return m_account; @@ -217,7 +267,7 @@ void AccountManager::Private::refreshSession(bool initial) } } emit q->updatedSession(m_account.uuid); - // emit dataChanged(index(row), index(row)); + q->checkAllAccountsReady(); if (q->allAccountTried()) { emit q->finished(); @@ -250,7 +300,6 @@ void AccountManager::Private::getProfile() q->save(); emit q->updatedAccount(m_account.uuid); - // emit dataChanged(index(row), index(row)); qDebug() << "Update pinned post" << detail.pinnedPost.uri; PinnedPostCache::getInstance()->update(m_account.did, detail.pinnedPost.uri); @@ -288,7 +337,12 @@ void AccountManager::Private::getServiceEndpoint(const QString &did, const QStri plc->directory(did); } -AccountManager::AccountManager(QObject *parent) : QObject { parent } +void AccountManager::Private::setMain(bool is) +{ + m_account.is_main = is; +} + +AccountManager::AccountManager(QObject *parent) : QObject { parent }, m_allAccountsReady(false) { qDebug().noquote() << this << "AccountManager()"; } @@ -312,6 +366,7 @@ void AccountManager::clear() } dList.clear(); dIndex.clear(); + emit countChanged(); } void AccountManager::save() const @@ -336,11 +391,14 @@ void AccountManager::load() if (!dIndex.contains(uuid)) { dList.append(new AccountManager::Private(this)); dIndex[uuid] = dList.count() - 1; + + emit countChanged(); } dList.at(dIndex[uuid])->load(item.toObject()); } - if (!has_main) { + if (!has_main && !dList.isEmpty()) { // mainになっているものがない + dList.at(0)->setMain(true); } } if (dList.isEmpty()) { @@ -348,6 +406,16 @@ void AccountManager::load() } } +void AccountManager::update(int row, AccountManagerRoles role, const QVariant &value) +{ + if (row < 0 || row >= count()) + return; + + if (dList.at(row)->update(role, value)) { + save(); + } +} + AccountData AccountManager::getAccount(const QString &uuid) const { if (!dIndex.contains(uuid)) { @@ -356,32 +424,32 @@ AccountData AccountManager::getAccount(const QString &uuid) const return dList.at(dIndex.value(uuid))->getAccount(); } -void AccountManager::updateAccount(const QString &service, const QString &identifier, - const QString &password, const QString &did, - const QString &handle, const QString &email, +void AccountManager::updateAccount(const QString &uuid, const QString &service, + const QString &identifier, const QString &password, + const QString &did, const QString &handle, const QString &email, const QString &accessJwt, const QString &refreshJwt, const bool authorized) { - bool updated = false; - for (const auto d : qAsConst(dList)) { - // for (const auto &uuid : d.keys()) { - AccountData account = d->getAccount(); - if (account.service == service && account.identifier == identifier) { - d->updateAccount(account.uuid, service, identifier, password, did, handle, email, - accessJwt, refreshJwt, account.thread_gate_type, - authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); - } - } - if (!updated) { + if (!uuid.isEmpty() && dIndex.contains(uuid)) { + AccountData account = dList.at(dIndex[uuid])->getAccount(); + dList.at(dIndex[uuid]) + ->updateAccount(account.uuid, service, identifier, password, did, handle, email, + accessJwt, refreshJwt, account.thread_gate_type, + authorized ? AccountStatus::Authorized + : AccountStatus::Unauthorized); + } else { // append - QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + QString new_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); dList.append(new AccountManager::Private(this)); - dIndex[uuid] = dList.count() - 1; + dIndex[new_uuid] = dList.count() - 1; dList.last()->updateAccount( - uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, + new_uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); + + emit countChanged(); } save(); + checkAllAccountsReady(); } void AccountManager::removeAccount(const QString &uuid) @@ -398,6 +466,9 @@ void AccountManager::removeAccount(const QString &uuid) for (int i = 0; i < dList.count(); i++) { dIndex[dList.at(i)->getAccount().uuid] = i; } + + emit countChanged(); + checkAllAccountsReady(); } void AccountManager::updateAccountProfile(const QString &uuid) @@ -410,18 +481,30 @@ void AccountManager::updateAccountProfile(const QString &uuid) int AccountManager::getMainAccountIndex() const { + if (dList.isEmpty()) + return -1; + + for (int i = 0; i < dList.count(); i++) { + if (dList.at(i)->getAccount().is_main) { + return i; + } + } return 0; } -void AccountManager::setMainAccount(const QString &uuid) +void AccountManager::setMainAccount(int row) { - for (const auto d : qAsConst(dList)) { - if (d->getAccount().uuid == uuid) { - // true - } else { - // false + if (row < 0 || row >= count()) + return; + + for (int i = 0; i < dList.count(); i++) { + bool new_val = (row == i); + if (dList.at(i)->getAccount().is_main != new_val) { + dList.at(i)->setMain(new_val); } } + + save(); } bool AccountManager::checkAllAccountsReady() @@ -450,6 +533,13 @@ QStringList AccountManager::getUuids() const return uuids; } +QString AccountManager::getUuid(int row) const +{ + if (row < 0 || row >= count()) + return QString(); + return dList.at(row)->getAccount().uuid; +} + bool AccountManager::allAccountTried() const { int count = 0; @@ -473,3 +563,29 @@ void AccountManager::setAllAccountsReady(bool newAllAccountsReady) m_allAccountsReady = newAllAccountsReady; emit allAccountsReadyChanged(); } + +int AccountManager::count() const +{ + return dList.count(); +} + +void AccountManager::createSession(int row) +{ + if (row < 0 || row >= count()) + return; + dList.at(row)->createSession(); +} + +void AccountManager::refreshSession(int row, bool initial) +{ + if (row < 0 || row >= count()) + return; + dList.at(row)->refreshSession(initial); +} + +void AccountManager::getProfile(int row) +{ + if (row < 0 || row >= count()) + return; + dList.at(row)->getProfile(); +} diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 50c2e230..4c2b7d12 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -17,30 +17,64 @@ class AccountManager : public QObject allAccountsReadyChanged FINAL) public: static AccountManager *getInstance(); - void clear(); + enum AccountManagerRoles { + UnknownRole, + UuidRole, + IsMainRole, + ServiceRole, + ServiceEndpointRole, + IdentifierRole, + PasswordRole, + DidRole, + HandleRole, + EmailRole, + AccessJwtRole, + RefreshJwtRole, + DisplayNameRole, + DescriptionRole, + AvatarRole, + PostLanguagesRole, + ThreadGateTypeRole, + ThreadGateOptionsRole, + PostGateQuoteEnabledRole, + StatusRole, + AuthorizedRole, + }; + + void clear(); void save() const; void load(); + void update(int row, AccountManager::AccountManagerRoles role, const QVariant &value); + AtProtocolInterface::AccountData getAccount(const QString &uuid) const; - void updateAccount(const QString &service, const QString &identifier, const QString &password, - const QString &did, const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt, const bool authorized); + void updateAccount(const QString &uuid, const QString &service, const QString &identifier, + const QString &password, const QString &did, const QString &handle, + const QString &email, const QString &accessJwt, const QString &refreshJwt, + const bool authorized); void removeAccount(const QString &uuid); void updateAccountProfile(const QString &uuid); int getMainAccountIndex() const; - void setMainAccount(const QString &uuid); + void setMainAccount(int row); bool checkAllAccountsReady(); int indexAt(const QString &uuid); QStringList getUuids() const; + QString getUuid(int row) const; bool allAccountsReady() const; void setAllAccountsReady(bool newAllAccountsReady); + int count() const; + + void createSession(int row); + void refreshSession(int row, bool initial = false); + void getProfile(int row); signals: void errorOccured(const QString &code, const QString &message); void updatedSession(const QString &uuid); void updatedAccount(const QString &uuid); + void countChanged(); void finished(); void allAccountsReadyChanged(); diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index d566eb4a..41b99317 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -272,11 +272,10 @@ void hagoromo_test::test_AccountListModel() AccountListModel model2; { - QSignalSpy spy(&model2, SIGNAL(updatedAccount(int, const QString &))); + QSignalSpy spy(&model2, SIGNAL(finished())); model2.load(); - spy.wait(10 * 1000); - spy.wait(10 * 1000); - QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + spy.wait(); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } QVERIFY2(model2.rowCount() == 2, QString::number(model2.rowCount()).toLocal8Bit()); int row = 0; @@ -291,6 +290,78 @@ void hagoromo_test::test_AccountListModel() QVERIFY2(model2.item(row, AccountListModel::RefreshJwtRole).toString() == "refreshJwt_account2_refresh", model2.item(row, AccountListModel::RefreshJwtRole).toString().toLocal8Bit()); + + // model.update(0, AccountListModel::UuidRole, "UuidRole"); + // model.update(0, AccountListModel::IsMainRole, "IsMainRole"); + model.update(0, AccountListModel::ServiceRole, "ServiceRole"); + model.update(0, AccountListModel::ServiceEndpointRole, "ServiceEndpointRole"); + model.update(0, AccountListModel::IdentifierRole, "IdentifierRole"); + model.update(0, AccountListModel::PasswordRole, "PasswordRole"); + model.update(0, AccountListModel::DidRole, "DidRole"); + model.update(0, AccountListModel::HandleRole, "HandleRole"); + model.update(0, AccountListModel::EmailRole, "EmailRole"); + model.update(0, AccountListModel::AccessJwtRole, "AccessJwtRole"); + model.update(0, AccountListModel::RefreshJwtRole, "RefreshJwtRole"); + model.update(0, AccountListModel::DisplayNameRole, "DisplayNameRole"); + model.update(0, AccountListModel::DescriptionRole, "DescriptionRole"); + model.update(0, AccountListModel::AvatarRole, "AvatarRole"); + model.update(0, AccountListModel::PostLanguagesRole, + QStringList() << "PostLanguagesRole1" + << "PostLanguagesRole2"); + model.update(0, AccountListModel::ThreadGateTypeRole, "ThreadGateTypeRole"); + model.update(0, AccountListModel::ThreadGateOptionsRole, + QStringList() << "ThreadGateOptionsRole1" + << "ThreadGateOptionsRole2"); + model.update(0, AccountListModel::PostGateQuoteEnabledRole, true); + + // QVERIFY2(model.item(0, AccountListModel::UuidRole).toString() == "UuidRole", + // model.item(0, AccountListModel::UuidRole).toString().toLocal8Bit()); + // QVERIFY2(model.item(0, AccountListModel::IsMainRole).toString() == "IsMainRole", + // model.item(0, AccountListModel::IsMainRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::ServiceRole).toString() == "ServiceRole", + model.item(0, AccountListModel::ServiceRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::ServiceEndpointRole).toString() + == "ServiceEndpointRole", + model.item(0, AccountListModel::ServiceEndpointRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::IdentifierRole).toString() == "IdentifierRole", + model.item(0, AccountListModel::IdentifierRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::PasswordRole).toString() == "PasswordRole", + model.item(0, AccountListModel::PasswordRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::DidRole).toString() == "DidRole", + model.item(0, AccountListModel::DidRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::HandleRole).toString() == "HandleRole", + model.item(0, AccountListModel::HandleRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::EmailRole).toString() == "EmailRole", + model.item(0, AccountListModel::EmailRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::AccessJwtRole).toString() == "AccessJwtRole", + model.item(0, AccountListModel::AccessJwtRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::RefreshJwtRole).toString() == "RefreshJwtRole", + model.item(0, AccountListModel::RefreshJwtRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::DisplayNameRole).toString() == "DisplayNameRole", + model.item(0, AccountListModel::DisplayNameRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::DescriptionRole).toString() == "DescriptionRole", + model.item(0, AccountListModel::DescriptionRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::AvatarRole).toString() == "AvatarRole", + model.item(0, AccountListModel::AvatarRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::PostLanguagesRole).toStringList() + == QStringList() << "PostLanguagesRole1" + << "PostLanguagesRole2", + model.item(0, AccountListModel::PostLanguagesRole) + .toStringList() + .join(",") + .toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::ThreadGateTypeRole).toString() == "ThreadGateTypeRole", + model.item(0, AccountListModel::ThreadGateTypeRole).toString().toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::ThreadGateOptionsRole).toStringList() + == QStringList() << "ThreadGateOptionsRole1" + << "ThreadGateOptionsRole2", + model.item(0, AccountListModel::ThreadGateOptionsRole) + .toStringList() + .join(",") + .toLocal8Bit()); + QVERIFY2(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool() == true, + QString::number(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool()) + .toLocal8Bit()); } void hagoromo_test::test_AccountManager() @@ -304,12 +375,12 @@ void hagoromo_test::test_AccountManager() AccountManager *manager = AccountManager::getInstance(); AtProtocolInterface::AccountData account; - manager->updateAccount(m_service + "/account/account1", "id1", "password1", "did:plc:account1", - "account1.relog.tech", "account1@relog.tech", "accessJwt_account1", - "refreshJwt_account1", false); - manager->updateAccount(m_service + "/account/account2", "id2", "password2", "did:plc:account2", - "account2.relog.tech", "account2@relog.tech", "accessJwt_account2", - "refreshJwt_account2", false); + manager->updateAccount(QString(), m_service + "/account/account1", "id1", "password1", + "did:plc:account1", "account1.relog.tech", "account1@relog.tech", + "accessJwt_account1", "refreshJwt_account1", false); + manager->updateAccount(QString(), m_service + "/account/account2", "id2", "password2", + "did:plc:account2", "account2.relog.tech", "account2@relog.tech", + "accessJwt_account2", "refreshJwt_account2", false); QStringList uuids = manager->getUuids(); From 127d7fe573036267bfc1e69752c2ede8f0e1eeb7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 18:12:31 +0900 Subject: [PATCH 098/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E6=83=85=E5=A0=B1=E3=81=AE=E5=8F=97=E3=81=91=E6=B8=A1?= =?UTF-8?q?=E3=81=97=E3=82=92UUID=E3=81=AB=E5=A4=89=E6=9B=B4=EF=BC=88?= =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E3=81=AE=E3=81=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/dialogs/AddColumnDialog.qml | 8 +- app/qml/dialogs/AddListDialog.qml | 5 +- app/qml/dialogs/AddToListDialog.qml | 5 +- app/qml/dialogs/BlockedAccountsDialog.qml | 5 +- app/qml/dialogs/BlockedListsDialog.qml | 5 +- app/qml/dialogs/DiscoverFeedsDialog.qml | 5 +- app/qml/dialogs/EditProfileDialog.qml | 5 +- app/qml/dialogs/LogViewDialog.qml | 12 +- app/qml/dialogs/MutedAccountsDialog.qml | 5 +- app/qml/dialogs/MutedListsDialog.qml | 5 +- app/qml/dialogs/PostDialog.qml | 33 +-- app/qml/dialogs/ReportAccountDialog.qml | 3 +- app/qml/dialogs/ReportMessageDialog.qml | 3 +- app/qml/dialogs/ReportPostDialog.qml | 3 +- app/qml/dialogs/SelectThreadGateDialog.qml | 3 +- app/qml/main.qml | 22 +- app/qml/view/ChatListView.qml | 8 +- app/qml/view/ChatMessageListView.qml | 6 +- app/qml/view/ColumnView.qml | 15 +- app/qml/view/ListDetailView.qml | 6 +- app/qml/view/ProfileListView.qml | 4 +- app/qml/view/ProfileView.qml | 26 +-- app/qtquick/atpabstractlistmodel.cpp | 19 +- app/qtquick/atpabstractlistmodel.h | 8 +- app/qtquick/blog/blogentrylistmodel.cpp | 11 +- app/qtquick/blog/blogentrylistmodel.h | 4 +- app/qtquick/chat/atpchatabstractlistmodel.cpp | 14 +- app/qtquick/chat/atpchatabstractlistmodel.h | 4 +- app/qtquick/link/feedgeneratorlink.cpp | 21 +- app/qtquick/link/feedgeneratorlink.h | 8 +- app/qtquick/link/listlink.cpp | 2 +- app/qtquick/link/postlink.cpp | 2 +- app/qtquick/list/listitemlistmodel.cpp | 3 +- app/qtquick/list/listslistmodel.cpp | 6 +- app/qtquick/log/logfeedlistmodel.cpp | 25 +-- app/qtquick/log/logfeedlistmodel.h | 2 + app/qtquick/moderation/labelerlistmodel.cpp | 1 + app/qtquick/moderation/reporter.cpp | 22 +- app/qtquick/moderation/reporter.h | 5 +- .../notification/notificationlistmodel.cpp | 9 +- app/qtquick/operation/recordoperator.cpp | 107 +++++---- app/qtquick/operation/recordoperator.h | 5 +- app/qtquick/profile/userprofile.cpp | 30 +-- app/qtquick/profile/userprofile.h | 4 +- app/qtquick/timeline/customfeedlistmodel.cpp | 6 +- app/qtquick/timeline/timelinelistmodel.cpp | 15 +- app/qtquick/timeline/userpost.cpp | 17 +- app/qtquick/timeline/userpost.h | 4 +- lib/tools/accountmanager.cpp | 14 +- lib/tools/accountmanager.h | 8 +- tests/chat_test/tst_chat_test.cpp | 20 +- tests/hagoromo_test/tst_hagoromo_test.cpp | 212 +++++++++++++----- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 91 +++++--- tests/log_test/tst_log_test.cpp | 8 +- tests/realtime_test/tst_realtime_test.cpp | 20 +- tests/search_test/tst_search_test.cpp | 14 +- 56 files changed, 492 insertions(+), 441 deletions(-) diff --git a/app/qml/dialogs/AddColumnDialog.qml b/app/qml/dialogs/AddColumnDialog.qml index 75534d6d..489b6c1f 100644 --- a/app/qml/dialogs/AddColumnDialog.qml +++ b/app/qml/dialogs/AddColumnDialog.qml @@ -43,13 +43,7 @@ Dialog { item.visible = (i === index) if(i === index){ if(item.model.rowCount() === 7){ - var service = accountModel.item(index, AccountListModel.ServiceRole) - var did = accountModel.item(index, AccountListModel.DidRole) - item.model.setAccount(service, did, - accountModel.item(index, AccountListModel.HandleRole), - accountModel.item(index, AccountListModel.EmailRole), - accountModel.item(index, AccountListModel.AccessJwtRole), - accountModel.item(index, AccountListModel.RefreshJwtRole)) + item.model.setAccount(accountModel.item(index, AccountListModel.UuidRole)) item.model.getLatest() } diff --git a/app/qml/dialogs/AddListDialog.qml b/app/qml/dialogs/AddListDialog.qml index 738f6627..c60cd085 100644 --- a/app/qml/dialogs/AddListDialog.qml +++ b/app/qml/dialogs/AddListDialog.qml @@ -34,11 +34,10 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - recordOperator.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + recordOperator.setAccount(account.uuid) if(addListDialog.editMode){ if(addListDialog.avatar.length > 0){ avatarImage.source = addListDialog.avatar diff --git a/app/qml/dialogs/AddToListDialog.qml b/app/qml/dialogs/AddToListDialog.qml index d9e450e5..ee9901e2 100644 --- a/app/qml/dialogs/AddToListDialog.qml +++ b/app/qml/dialogs/AddToListDialog.qml @@ -27,7 +27,7 @@ Dialog { signal requestAddList() onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } addListDialog.account.uuid = account.uuid @@ -39,8 +39,7 @@ Dialog { addListDialog.account.avatar = account.avatar listsListModel.clear() - listsListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + listsListModel.setAccount(account.uuid) listsListModel.getLatest() } onClosed: { diff --git a/app/qml/dialogs/BlockedAccountsDialog.qml b/app/qml/dialogs/BlockedAccountsDialog.qml index ca0a3cfd..2f3e008a 100644 --- a/app/qml/dialogs/BlockedAccountsDialog.qml +++ b/app/qml/dialogs/BlockedAccountsDialog.qml @@ -26,12 +26,11 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - blocksListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + blocksListModel.setAccount(account.uuid) blocksListModel.getLatest() profileListScrollView.currentIndex = -1 diff --git a/app/qml/dialogs/BlockedListsDialog.qml b/app/qml/dialogs/BlockedListsDialog.qml index 4b52661a..9cfe4a43 100644 --- a/app/qml/dialogs/BlockedListsDialog.qml +++ b/app/qml/dialogs/BlockedListsDialog.qml @@ -26,11 +26,10 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - listBlocksListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + listBlocksListModel.setAccount(account.uuid) listBlocksListModel.getLatest() listScrollView.currentIndex = -1 diff --git a/app/qml/dialogs/DiscoverFeedsDialog.qml b/app/qml/dialogs/DiscoverFeedsDialog.qml index 55394930..7a5a8e1c 100644 --- a/app/qml/dialogs/DiscoverFeedsDialog.qml +++ b/app/qml/dialogs/DiscoverFeedsDialog.qml @@ -27,12 +27,11 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - feedGeneratorListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + feedGeneratorListModel.setAccount(account.uuid) feedGeneratorListModel.getLatest() generatorScrollView.currentIndex = -1 diff --git a/app/qml/dialogs/EditProfileDialog.qml b/app/qml/dialogs/EditProfileDialog.qml index 1de48657..9fb38d3c 100644 --- a/app/qml/dialogs/EditProfileDialog.qml +++ b/app/qml/dialogs/EditProfileDialog.qml @@ -33,11 +33,10 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - recordOperator.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + recordOperator.setAccount(account.uuid) avatarImage.source = editProfileDialog.avatar bannerImage.source = editProfileDialog.banner displayNameText.text = editProfileDialog.displayName diff --git a/app/qml/dialogs/LogViewDialog.qml b/app/qml/dialogs/LogViewDialog.qml index e04ffa1e..2a4b34df 100644 --- a/app/qml/dialogs/LogViewDialog.qml +++ b/app/qml/dialogs/LogViewDialog.qml @@ -194,9 +194,7 @@ Dialog { text: qsTr("Search") onClicked: { console.log("search:" + searchText.text) - logSearchFeedListModel.setAccount(account.service, account.did, - account.handle, account.email, - account.accessJwt, account.refreshJwt) + logSearchFeedListModel.setAccount(account.uuid) logSearchFeedListModel.selectCondition = searchText.text logSearchFeedListModel.clear() logSearchFeedListModel.getLatest() @@ -252,9 +250,7 @@ Dialog { } onClickedItem: (name) => { console.log("select:" + name) - logDailyFeedListModel.setAccount(account.service, account.did, - account.handle, account.email, - account.accessJwt, account.refreshJwt) + logDailyFeedListModel.setAccount(account.uuid) logDailyFeedListModel.selectCondition = name logDailyFeedListModel.clear() logDailyFeedListModel.getLatest() @@ -304,9 +300,7 @@ Dialog { } onClickedItem: (name) => { console.log("select:" + name) - logMonthlyFeedListModel.setAccount(account.service, account.did, - account.handle, account.email, - account.accessJwt, account.refreshJwt) + logMonthlyFeedListModel.setAccount(account.uuid) logMonthlyFeedListModel.selectCondition = name logMonthlyFeedListModel.clear() logMonthlyFeedListModel.getLatest() diff --git a/app/qml/dialogs/MutedAccountsDialog.qml b/app/qml/dialogs/MutedAccountsDialog.qml index 9d482b15..9ee740f9 100644 --- a/app/qml/dialogs/MutedAccountsDialog.qml +++ b/app/qml/dialogs/MutedAccountsDialog.qml @@ -26,11 +26,10 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - mutesListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + mutesListModel.setAccount(account.uuid) mutesListModel.getLatest() profileListScrollView.currentIndex = -1 diff --git a/app/qml/dialogs/MutedListsDialog.qml b/app/qml/dialogs/MutedListsDialog.qml index 6fdabbd3..6782402d 100644 --- a/app/qml/dialogs/MutedListsDialog.qml +++ b/app/qml/dialogs/MutedListsDialog.qml @@ -26,11 +26,10 @@ Dialog { signal errorOccured(string account_uuid, string code, string message) onOpened: { - if(account.service.length === 0){ + if(account.uuid.length === 0){ return } - listMutesListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + listMutesListModel.setAccount(account.uuid) listMutesListModel.getLatest() listScrollView.currentIndex = -1 diff --git a/app/qml/dialogs/PostDialog.qml b/app/qml/dialogs/PostDialog.qml index 47ddaf88..58e15c0b 100644 --- a/app/qml/dialogs/PostDialog.qml +++ b/app/qml/dialogs/PostDialog.qml @@ -240,10 +240,7 @@ Dialog { selectThreadGateDialog.selectedType = selectThreadGateDialog.initialType selectThreadGateDialog.selectedOptions = selectThreadGateDialog.initialOptions // 入力中にアカウントを切り替えるかもなので選んだ時に設定する - mentionSuggestionView.setAccount(postDialog.accountModel.item(row, AccountListModel.ServiceRole), - postDialog.accountModel.item(row, AccountListModel.DidRole), - postDialog.accountModel.item(row, AccountListModel.HandleRole), - postDialog.accountModel.item(row, AccountListModel.AccessJwtRole)) + mentionSuggestionView.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) } } } @@ -387,28 +384,13 @@ Dialog { var uri = addingExternalLinkUrlText.text var row = accountCombo.currentIndex if(feedGeneratorLink.checkUri(uri, "feed") && !quoteValid){ - feedGeneratorLink.setAccount(postDialog.accountModel.item(row, AccountListModel.ServiceRole), - postDialog.accountModel.item(row, AccountListModel.DidRole), - postDialog.accountModel.item(row, AccountListModel.HandleRole), - postDialog.accountModel.item(row, AccountListModel.EmailRole), - postDialog.accountModel.item(row, AccountListModel.AccessJwtRole), - postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole)) + feedGeneratorLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) feedGeneratorLink.getFeedGenerator(uri) }else if(listLink.checkUri(uri, "lists") && !quoteValid){ - listLink.setAccount(postDialog.accountModel.item(row, AccountListModel.ServiceRole), - postDialog.accountModel.item(row, AccountListModel.DidRole), - postDialog.accountModel.item(row, AccountListModel.HandleRole), - postDialog.accountModel.item(row, AccountListModel.EmailRole), - postDialog.accountModel.item(row, AccountListModel.AccessJwtRole), - postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole)) + listLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) listLink.getList(uri) }else if(postLink.checkUri(uri, "post") && !quoteValid && postType !== "quote"){ - postLink.setAccount(postDialog.accountModel.item(row, AccountListModel.ServiceRole), - postDialog.accountModel.item(row, AccountListModel.DidRole), - postDialog.accountModel.item(row, AccountListModel.HandleRole), - postDialog.accountModel.item(row, AccountListModel.EmailRole), - postDialog.accountModel.item(row, AccountListModel.AccessJwtRole), - postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole)) + postLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) postLink.getPost(uri) }else{ externalLink.getExternalLink(uri) @@ -672,12 +654,7 @@ Dialog { text: qsTr("Post") onClicked: { var row = accountCombo.currentIndex; - createRecord.setAccount(postDialog.accountModel.item(row, AccountListModel.ServiceRole), - postDialog.accountModel.item(row, AccountListModel.DidRole), - postDialog.accountModel.item(row, AccountListModel.HandleRole), - postDialog.accountModel.item(row, AccountListModel.EmailRole), - postDialog.accountModel.item(row, AccountListModel.AccessJwtRole), - postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole)) + createRecord.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) createRecord.clear() createRecord.setText(postText.text) createRecord.setPostLanguages(postDialog.accountModel.item(row, AccountListModel.PostLanguagesRole)) diff --git a/app/qml/dialogs/ReportAccountDialog.qml b/app/qml/dialogs/ReportAccountDialog.qml index 5598ed48..f1531dab 100644 --- a/app/qml/dialogs/ReportAccountDialog.qml +++ b/app/qml/dialogs/ReportAccountDialog.qml @@ -135,8 +135,7 @@ Dialog { font.pointSize: AdjustedValues.f10 text: qsTr("Send report") onClicked: { - reporter.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + reporter.setAccount(account.uuid) reporter.reportAccount(targetDid, reportTextArea.text, [labelerDidComboBox.currentValue], reportTypeButtonGroup.checkedButton.reason) diff --git a/app/qml/dialogs/ReportMessageDialog.qml b/app/qml/dialogs/ReportMessageDialog.qml index d0b80332..15e3be4f 100644 --- a/app/qml/dialogs/ReportMessageDialog.qml +++ b/app/qml/dialogs/ReportMessageDialog.qml @@ -145,8 +145,7 @@ Dialog { font.pointSize: AdjustedValues.f10 text: qsTr("Send report") onClicked: { - reporter.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + reporter.setAccount(account.uuid) reporter.reportMessage(targetAccountDid, targetConvoId, targetMessageId, reportTextArea.text, reportTypeButtonGroup.checkedButton.reason) } diff --git a/app/qml/dialogs/ReportPostDialog.qml b/app/qml/dialogs/ReportPostDialog.qml index 3ea96db6..2087c54b 100644 --- a/app/qml/dialogs/ReportPostDialog.qml +++ b/app/qml/dialogs/ReportPostDialog.qml @@ -165,8 +165,7 @@ Dialog { font.pointSize: AdjustedValues.f10 text: qsTr("Send report") onClicked: { - reporter.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + reporter.setAccount(account.uuid) reporter.reportPost(targetUri, targetCid, reportTextArea.text, [labelerDidComboBox.currentValue], reportTypeButtonGroup.checkedButton.reason) diff --git a/app/qml/dialogs/SelectThreadGateDialog.qml b/app/qml/dialogs/SelectThreadGateDialog.qml index ae6e39f4..7f1543c9 100644 --- a/app/qml/dialogs/SelectThreadGateDialog.qml +++ b/app/qml/dialogs/SelectThreadGateDialog.qml @@ -55,8 +55,7 @@ Dialog { } } listsListModel.clear() - listsListModel.setAccount(account.service, account.did, account.handle, - account.email, account.accessJwt, account.refreshJwt) + listsListModel.setAccount(account.uuid) listsListModel.getLatest() } onClosed: { diff --git a/app/qml/main.qml b/app/qml/main.qml index ce91e29c..fbb71cc8 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -307,12 +307,7 @@ ApplicationWindow { property string updateSequence: "" // threadgate, postgate onOpened: { selectThreadGateDialog.ready = false - postDialog.recordOperator.setAccount(selectThreadGateDialog.account.service, - selectThreadGateDialog.account.did, - selectThreadGateDialog.account.handle, - selectThreadGateDialog.account.email, - selectThreadGateDialog.account.accessJwt, - selectThreadGateDialog.account.refreshJwt) + postDialog.recordOperator.setAccount(selectThreadGateDialog.account.uuid) postDialog.recordOperator.clear() postDialog.recordOperator.requestPostGate(postUri) } @@ -347,12 +342,7 @@ ApplicationWindow { updateSequence = "threadgate" } - postDialog.recordOperator.setAccount(selectThreadGateDialog.account.service, - selectThreadGateDialog.account.did, - selectThreadGateDialog.account.handle, - selectThreadGateDialog.account.email, - selectThreadGateDialog.account.accessJwt, - selectThreadGateDialog.account.refreshJwt) + postDialog.recordOperator.setAccount(selectThreadGateDialog.account.uuid) postDialog.recordOperator.clear() if(updateSequence === "threadgate"){ console.log("Update threadgate") @@ -538,13 +528,7 @@ ApplicationWindow { currentAccountIndex -= 1 load(true) }else{ - setAccount(accountListModel.item(currentAccountIndex, AccountListModel.ServiceRole), - accountListModel.item(currentAccountIndex, AccountListModel.DidRole), - handle, - "email", - accessJwt, - accountListModel.item(currentAccountIndex, AccountListModel.RefreshJwtRole) - ) + setAccount(accountListModel.item(currentAccountIndex, AccountListModel.UuidRole)) actor = did searchTarget = "#cache" if(listsListModel.getLatest()){ diff --git a/app/qml/view/ChatListView.qml b/app/qml/view/ChatListView.qml index 64695f97..5617ce96 100644 --- a/app/qml/view/ChatListView.qml +++ b/app/qml/view/ChatListView.qml @@ -36,10 +36,10 @@ Item { function rowCount() { return rootListView.model.rowCount() } - function setAccount(service, did, handle, email, accessJwt, refreshJwt) { - accountDid = did - searchProfileListModel.setAccount(service, did, handle, email, accessJwt, refreshJwt) - rootListView.model.setAccount(service, did, handle, email, accessJwt, refreshJwt) + function setAccount(uuid) { + searchProfileListModel.setAccount(uuid) + rootListView.model.setAccount(uuid) + accountDid = rootListView.model.did } function getLatest() { rootListView.model.getLatest() diff --git a/app/qml/view/ChatMessageListView.qml b/app/qml/view/ChatMessageListView.qml index ed283112..899f5fba 100644 --- a/app/qml/view/ChatMessageListView.qml +++ b/app/qml/view/ChatMessageListView.qml @@ -55,9 +55,9 @@ Item { function rowCount() { return rootListView.model.rowCount(); } - function setAccount(service, did, handle, email, accessJwt, refreshJwt) { - rootListView.model.setAccount(service, did, handle, email, accessJwt, refreshJwt) - userPost.setAccount(service, did, handle, email, accessJwt, refreshJwt) + function setAccount(uuid) { + rootListView.model.setAccount(uuid) + userPost.setAccount(uuid) } function getLatest() { rootListView.model.getLatest() diff --git a/app/qml/view/ColumnView.qml b/app/qml/view/ColumnView.qml index ab726571..ba64d411 100644 --- a/app/qml/view/ColumnView.qml +++ b/app/qml/view/ColumnView.qml @@ -675,16 +675,12 @@ ColumnLayout { } function reflect(){ + // TODO: account: 不要では? // StackViewに積まれているViewに反映 for(var i=0; igetAccount(m_account.uuid); } -void AtpAbstractListModel::setAccount(const QString &service, const QString &did, - const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt) +void AtpAbstractListModel::setAccount(const QString &uuid) { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + m_account.uuid = uuid; } QString AtpAbstractListModel::getTranslation(const QString &cid) const @@ -1242,6 +1236,11 @@ void AtpAbstractListModel::setLoadingInterval(int newLoadingInterval) emit loadingIntervalChanged(); } +QString AtpAbstractListModel::uuid() const +{ + return account().uuid; +} + QString AtpAbstractListModel::service() const { return account().service; diff --git a/app/qtquick/atpabstractlistmodel.h b/app/qtquick/atpabstractlistmodel.h index 865d496a..4156a558 100644 --- a/app/qtquick/atpabstractlistmodel.h +++ b/app/qtquick/atpabstractlistmodel.h @@ -52,6 +52,7 @@ class AtpAbstractListModel : public QAbstractListModel Q_PROPERTY(bool displayPinnedPost READ displayPinnedPost WRITE setDisplayPinnedPost NOTIFY displayPinnedPostChanged) + Q_PROPERTY(QString uuid READ uuid NOTIFY uuidChanged CONSTANT) Q_PROPERTY(QString service READ service CONSTANT) Q_PROPERTY(QString did READ did CONSTANT) Q_PROPERTY(QString handle READ handle CONSTANT) @@ -133,9 +134,7 @@ class AtpAbstractListModel : public QAbstractListModel Q_INVOKABLE void clear(); AtProtocolInterface::AccountData account() const; - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + Q_INVOKABLE void setAccount(const QString &uuid); virtual Q_INVOKABLE int indexOf(const QString &cid) const = 0; virtual Q_INVOKABLE QString getRecordText(const QString &cid) = 0; virtual Q_INVOKABLE QString getOfficialUrl() const = 0; @@ -160,6 +159,7 @@ class AtpAbstractListModel : public QAbstractListModel bool displayPinnedPost() const; void setDisplayPinnedPost(bool newDisplayPinnedPost); + QString uuid() const; QString service() const; QString did() const; QString handle() const; @@ -180,6 +180,8 @@ class AtpAbstractListModel : public QAbstractListModel void pinnedPostChanged(); void displayPinnedPostChanged(); + void uuidChanged(); + public slots: virtual Q_INVOKABLE bool getLatest() = 0; virtual Q_INVOKABLE bool getNext() = 0; diff --git a/app/qtquick/blog/blogentrylistmodel.cpp b/app/qtquick/blog/blogentrylistmodel.cpp index fc790274..c5ee78e2 100644 --- a/app/qtquick/blog/blogentrylistmodel.cpp +++ b/app/qtquick/blog/blogentrylistmodel.cpp @@ -39,16 +39,9 @@ AtProtocolInterface::AccountData BlogEntryListModel::account() const return m_account; } -void BlogEntryListModel::setAccount(const QString &service, const QString &did, - const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt) +void BlogEntryListModel::setAccount(const QString &uuid) { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + m_account.uuid = uuid; } bool BlogEntryListModel::getLatest() diff --git a/app/qtquick/blog/blogentrylistmodel.h b/app/qtquick/blog/blogentrylistmodel.h index ddc81677..5b8d9597 100644 --- a/app/qtquick/blog/blogentrylistmodel.h +++ b/app/qtquick/blog/blogentrylistmodel.h @@ -39,9 +39,7 @@ class BlogEntryListModel : public QAbstractListModel Q_INVOKABLE QVariant item(int row, BlogEntryListModel::BlogEntryListModelRoles role) const; AtProtocolInterface::AccountData account() const; - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE bool getLatest(); diff --git a/app/qtquick/chat/atpchatabstractlistmodel.cpp b/app/qtquick/chat/atpchatabstractlistmodel.cpp index 4a86b7a3..40046007 100644 --- a/app/qtquick/chat/atpchatabstractlistmodel.cpp +++ b/app/qtquick/chat/atpchatabstractlistmodel.cpp @@ -2,6 +2,7 @@ #include "extension/directory/plc/directoryplc.h" #include "atprotocol/chat/bsky/convo/chatbskyconvoupdateread.h" #include "tools/labelerprovider.h" +#include "tools/accountmanager.h" using AtProtocolInterface::ChatBskyConvoUpdateRead; using AtProtocolInterface::DirectoryPlc; @@ -19,19 +20,12 @@ void AtpChatAbstractListModel::clear() AtProtocolInterface::AccountData AtpChatAbstractListModel::account() const { - return m_account; + return AccountManager::getInstance()->getAccount(m_account.uuid); } -void AtpChatAbstractListModel::setAccount(const QString &service, const QString &did, - const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt) +void AtpChatAbstractListModel::setAccount(const QString &uuid) { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + m_account.uuid = uuid; } void AtpChatAbstractListModel::setServiceEndpoint(const QString &service_endpoint) diff --git a/app/qtquick/chat/atpchatabstractlistmodel.h b/app/qtquick/chat/atpchatabstractlistmodel.h index 5fb841db..2894fd65 100644 --- a/app/qtquick/chat/atpchatabstractlistmodel.h +++ b/app/qtquick/chat/atpchatabstractlistmodel.h @@ -22,9 +22,7 @@ class AtpChatAbstractListModel : public QAbstractListModel Q_INVOKABLE void clear(); AtProtocolInterface::AccountData account() const; - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + Q_INVOKABLE void setAccount(const QString &uuid); void setServiceEndpoint(const QString &service_endpoint); virtual Q_INVOKABLE bool getLatest() = 0; diff --git a/app/qtquick/link/feedgeneratorlink.cpp b/app/qtquick/link/feedgeneratorlink.cpp index bdbbfd11..1c479689 100644 --- a/app/qtquick/link/feedgeneratorlink.cpp +++ b/app/qtquick/link/feedgeneratorlink.cpp @@ -2,6 +2,7 @@ #include "atprotocol/app/bsky/feed/appbskyfeedgetfeedgenerator.h" #include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" #include "atprotocol/lexicons_func_unknown.h" +#include "tools/accountmanager.h" using AtProtocolInterface::AppBskyActorGetProfile; using AtProtocolInterface::AppBskyFeedGetFeedGenerator; @@ -12,16 +13,14 @@ FeedGeneratorLink::FeedGeneratorLink(QObject *parent) m_rxHandle.setPattern(QString("^%1$").arg(REG_EXP_HANDLE)); } -void FeedGeneratorLink::setAccount(const QString &service, const QString &did, - const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt) +AtProtocolInterface::AccountData FeedGeneratorLink::account() const { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + return AccountManager::getInstance()->getAccount(m_account.uuid); +} + +void FeedGeneratorLink::setAccount(const QString &uuid) +{ + m_account.uuid = uuid; } bool FeedGeneratorLink::checkUri(const QString &uri, const QString &type) const @@ -65,7 +64,7 @@ void FeedGeneratorLink::convertToAtUri(const QString &base_at_uri, const QString } profile->deleteLater(); }); - profile->setAccount(m_account); + profile->setAccount(account()); profile->getProfile(user_id); } else { // did @@ -100,7 +99,7 @@ void FeedGeneratorLink::getFeedGenerator(const QString &uri) setRunning(false); generator->deleteLater(); }); - generator->setAccount(m_account); + generator->setAccount(account()); generator->getFeedGenerator(at_uri); }); } diff --git a/app/qtquick/link/feedgeneratorlink.h b/app/qtquick/link/feedgeneratorlink.h index c22a6e6b..db41be5e 100644 --- a/app/qtquick/link/feedgeneratorlink.h +++ b/app/qtquick/link/feedgeneratorlink.h @@ -21,9 +21,8 @@ class FeedGeneratorLink : public QObject public: explicit FeedGeneratorLink(QObject *parent = nullptr); - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + AtProtocolInterface::AccountData account() const; + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE bool checkUri(const QString &uri, const QString &type) const; void convertToAtUri(const QString &base_at_uri, const QString &uri, std::function callback); @@ -60,9 +59,8 @@ class FeedGeneratorLink : public QObject void cidChanged(); protected: - AtProtocolInterface::AccountData m_account; - private: + AtProtocolInterface::AccountData m_account; QRegularExpression m_rxHandle; bool m_running; diff --git a/app/qtquick/link/listlink.cpp b/app/qtquick/link/listlink.cpp index f3a95d6c..f7548acd 100644 --- a/app/qtquick/link/listlink.cpp +++ b/app/qtquick/link/listlink.cpp @@ -33,7 +33,7 @@ void ListLink::getList(const QString &uri) setRunning(false); list->deleteLater(); }); - list->setAccount(m_account); + list->setAccount(account()); list->getList(at_uri, 0, QString()); }); } diff --git a/app/qtquick/link/postlink.cpp b/app/qtquick/link/postlink.cpp index 1506c03d..19ee1c2a 100644 --- a/app/qtquick/link/postlink.cpp +++ b/app/qtquick/link/postlink.cpp @@ -37,7 +37,7 @@ void PostLink::getPost(const QString &uri) setRunning(false); post->deleteLater(); }); - post->setAccount(m_account); + post->setAccount(account()); post->getPosts(QStringList() << at_uri); }); } diff --git a/app/qtquick/list/listitemlistmodel.cpp b/app/qtquick/list/listitemlistmodel.cpp index 15fc123b..2bff9832 100644 --- a/app/qtquick/list/listitemlistmodel.cpp +++ b/app/qtquick/list/listitemlistmodel.cpp @@ -126,8 +126,7 @@ void ListItemListModel::block() } ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (blocked()) { // -> unblock ope->deleteBlockList(blockedUri()); diff --git a/app/qtquick/list/listslistmodel.cpp b/app/qtquick/list/listslistmodel.cpp index 9f5424fa..e2d8d0df 100644 --- a/app/qtquick/list/listslistmodel.cpp +++ b/app/qtquick/list/listslistmodel.cpp @@ -185,8 +185,7 @@ bool ListsListModel::addRemoveFromList(const int row, const QString &did) setRunning(false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (status == SearchStatusTypeContains) { // delete // uriのレコードを消す(リストに登録しているユーザーの情報) @@ -264,8 +263,7 @@ void ListsListModel::block(const int row) } ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (blocked.toBool()) { // -> unblock ope->deleteBlockList(item(row, ListsListModel::BlockedUriRole).toString()); diff --git a/app/qtquick/log/logfeedlistmodel.cpp b/app/qtquick/log/logfeedlistmodel.cpp index 5d5eda5b..f95d5729 100644 --- a/app/qtquick/log/logfeedlistmodel.cpp +++ b/app/qtquick/log/logfeedlistmodel.cpp @@ -1,6 +1,5 @@ #include "logfeedlistmodel.h" #include "log/logmanager.h" -#include "atprotocol/lexicons.h" #include #include @@ -16,7 +15,7 @@ LogFeedListModel::LogFeedListModel(QObject *parent) bool LogFeedListModel::getLatest() { - if (running() || did().isEmpty() || selectCondition().isEmpty()) { + if (running() || targetDid().isEmpty() || selectCondition().isEmpty()) { emit finished(false); return false; } @@ -61,15 +60,15 @@ bool LogFeedListModel::getLatest() manager->deleteLater(); }); manager->setAccount(account()); - emit manager->selectRecords(did(), static_cast(feedType()), selectCondition(), QString(), - 0); + emit manager->selectRecords(targetDid(), static_cast(feedType()), selectCondition(), + QString(), 0); return true; } bool LogFeedListModel::getNext() { - if (running() || did().isEmpty() || selectCondition().isEmpty()) { + if (running() || targetDid().isEmpty() || selectCondition().isEmpty()) { emit finished(false); return false; } @@ -92,8 +91,8 @@ bool LogFeedListModel::getNext() manager->deleteLater(); }); manager->setAccount(account()); - emit manager->selectRecords(did(), static_cast(feedType()), selectCondition(), m_cursor, - 0); + emit manager->selectRecords(targetDid(), static_cast(feedType()), selectCondition(), + m_cursor, 0); return true; } @@ -132,27 +131,27 @@ void LogFeedListModel::setFeedType(LogFeedListModelFeedType newFeedType) QString LogFeedListModel::targetDid() const { - return account().did; + return m_targetDid; } void LogFeedListModel::setTargetDid(const QString &newTargetDid) { - if (account().did == newTargetDid) + if (m_targetDid == newTargetDid) return; - setAccount(service(), newTargetDid, handle(), email(), accessJwt(), refreshJwt()); + m_targetDid = newTargetDid; emit targetDidChanged(); } QString LogFeedListModel::targetHandle() const { - return account().handle; + return m_targetHandle; } void LogFeedListModel::setTargetHandle(const QString &newTargetHandle) { - if (account().handle == newTargetHandle) + if (m_targetHandle == newTargetHandle) return; - setAccount(service(), did(), newTargetHandle, email(), accessJwt(), refreshJwt()); + m_targetHandle = newTargetHandle; emit targetHandleChanged(); } diff --git a/app/qtquick/log/logfeedlistmodel.h b/app/qtquick/log/logfeedlistmodel.h index 0c523379..53a8f4f3 100644 --- a/app/qtquick/log/logfeedlistmodel.h +++ b/app/qtquick/log/logfeedlistmodel.h @@ -55,6 +55,8 @@ class LogFeedListModel : public TimelineListModel private: QString m_selectCondition; LogFeedListModelFeedType m_feedType; + QString m_targetDid; + QString m_targetHandle; QString m_targetAvatar; }; diff --git a/app/qtquick/moderation/labelerlistmodel.cpp b/app/qtquick/moderation/labelerlistmodel.cpp index 8531e1ea..9862db4f 100644 --- a/app/qtquick/moderation/labelerlistmodel.cpp +++ b/app/qtquick/moderation/labelerlistmodel.cpp @@ -47,6 +47,7 @@ void LabelerListModel::load() AtProtocolInterface::AccountData account; + // TODO: account: 替えなくてもOK? account.service = service(); account.handle = handle(); account.accessJwt = accessJwt(); diff --git a/app/qtquick/moderation/reporter.cpp b/app/qtquick/moderation/reporter.cpp index 1ba81cde..5b0cff33 100644 --- a/app/qtquick/moderation/reporter.cpp +++ b/app/qtquick/moderation/reporter.cpp @@ -1,6 +1,7 @@ #include "reporter.h" #include "extension/com/atproto/moderation/comatprotomoderationcreatereportex.h" +#include "tools/accountmanager.h" using AtProtocolInterface::ComAtprotoModerationCreateReportEx; @@ -14,15 +15,14 @@ Reporter::Reporter(QObject *parent) : QObject { parent }, m_running(false) m_reasonHash[ReasonMisleading] = "reasonMisleading"; } -void Reporter::setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, const QString &refreshJwt) +AtProtocolInterface::AccountData Reporter::account() const { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + return AccountManager::getInstance()->getAccount(m_account.uuid); +} + +void Reporter::setAccount(const QString &uuid) +{ + m_account.uuid = uuid; } void Reporter::reportPost(const QString &uri, const QString &cid, const QString &text, @@ -42,7 +42,7 @@ void Reporter::reportPost(const QString &uri, const QString &cid, const QString emit finished(success); report->deleteLater(); }); - report->setAccount(m_account); + report->setAccount(account()); if (!labelers.isEmpty() && !labelers.first().isEmpty()) { report->appendRawHeader("atproto-proxy", labelers.first() + "#atproto_labeler"); } @@ -66,7 +66,7 @@ void Reporter::reportAccount(const QString &did, const QString &text, const QStr emit finished(success); report->deleteLater(); }); - report->setAccount(m_account); + report->setAccount(account()); if (!labelers.isEmpty() && !labelers.first().isEmpty()) { report->appendRawHeader("atproto-proxy", labelers.first() + "#atproto_labeler"); } @@ -91,7 +91,7 @@ void Reporter::reportMessage(const QString &did, const QString &convo_id, const emit finished(success); report->deleteLater(); }); - report->setAccount(m_account); + report->setAccount(account()); report->reportMessage(did, convo_id, message_id, text, m_reasonHash[reason]); } diff --git a/app/qtquick/moderation/reporter.h b/app/qtquick/moderation/reporter.h index f235d0a9..a25a2798 100644 --- a/app/qtquick/moderation/reporter.h +++ b/app/qtquick/moderation/reporter.h @@ -22,9 +22,8 @@ class Reporter : public QObject }; Q_ENUM(ReportReason) - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + AtProtocolInterface::AccountData account() const; + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE void reportPost(const QString &uri, const QString &cid, const QString &text, const QStringList &labelers, Reporter::ReportReason reason); Q_INVOKABLE void reportAccount(const QString &did, const QString &text, diff --git a/app/qtquick/notification/notificationlistmodel.cpp b/app/qtquick/notification/notificationlistmodel.cpp index 4d331b11..ab9e6e34 100644 --- a/app/qtquick/notification/notificationlistmodel.cpp +++ b/app/qtquick/notification/notificationlistmodel.cpp @@ -768,8 +768,7 @@ bool NotificationListModel::repost(int row) setRunningRepost(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (!current) ope->repost(item(row, CidRole).toString(), item(row, UriRole).toString()); else @@ -800,8 +799,7 @@ bool NotificationListModel::like(int row) setRunningLike(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (!current) ope->like(item(row, CidRole).toString(), item(row, UriRole).toString()); else @@ -906,8 +904,7 @@ bool NotificationListModel::detachQuote(int row) } ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); ope->updateDetachedStatusOfQuote(detached, target_uri, detach_uri); return true; } diff --git a/app/qtquick/operation/recordoperator.cpp b/app/qtquick/operation/recordoperator.cpp index 26a197be..79532263 100644 --- a/app/qtquick/operation/recordoperator.cpp +++ b/app/qtquick/operation/recordoperator.cpp @@ -8,6 +8,7 @@ #include "extension/com/atproto/repo/comatprotorepogetrecordex.h" #include "extension/com/atproto/repo/comatprotorepolistrecordsex.h" #include "extension/com/atproto/repo/comatprotorepoputrecordex.h" +#include "tools/accountmanager.h" #include @@ -31,16 +32,14 @@ RecordOperator::RecordOperator(QObject *parent) { } -void RecordOperator::setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt) +AtProtocolInterface::AccountData RecordOperator::account() { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + return AccountManager::getInstance()->getAccount(m_account.uuid); +} + +void RecordOperator::setAccount(const QString &uuid) +{ + m_account.uuid = uuid; } void RecordOperator::setText(const QString &text) @@ -182,7 +181,7 @@ void RecordOperator::post() } LexiconsTypeUnknown::makeFacets( - this, m_account, m_text, + this, account(), m_text, [=](const QList &facets) { ComAtprotoRepoCreateRecordEx *create_record = new ComAtprotoRepoCreateRecordEx(this); @@ -234,7 +233,7 @@ void RecordOperator::post() } create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->setReply(m_replyParent.cid, m_replyParent.uri, m_replyRoot.cid, m_replyRoot.uri); create_record->setQuote(m_embedQuote.cid, m_embedQuote.uri); @@ -289,7 +288,7 @@ void RecordOperator::repost(const QString &cid, const QString &uri) // 成功なら、受け取ったデータでTLデータの更新をしないと値が大きくならない create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->repost(cid, uri); } @@ -313,7 +312,7 @@ void RecordOperator::like(const QString &cid, const QString &uri) // 成功なら、受け取ったデータでTLデータの更新をしないと値が大きくならない create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->like(cid, uri); } @@ -335,7 +334,7 @@ void RecordOperator::follow(const QString &did) setRunning(false); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->follow(did); } @@ -358,7 +357,7 @@ void RecordOperator::mute(const QString &did) setRunning(false); mute->deleteLater(); }); - mute->setAccount(m_account); + mute->setAccount(account()); mute->muteActor(did); } @@ -380,7 +379,7 @@ void RecordOperator::block(const QString &did) setRunning(false); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->block(did); } @@ -402,7 +401,7 @@ void RecordOperator::blockList(const QString &uri) setRunning(false); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->blockList(uri); } @@ -427,7 +426,7 @@ bool RecordOperator::list(const QString &name, const RecordOperator::ListPurpose create_record->deleteLater(); }); create_record->setImageBlobs(m_embedImageBlobs); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->list( name, static_cast( @@ -462,7 +461,7 @@ bool RecordOperator::listItem(const QString &uri, const QString &did) setRunning(false); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->listItem(uri, did); return true; } @@ -487,7 +486,7 @@ void RecordOperator::deletePost(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deletePost(r_key); } @@ -511,7 +510,7 @@ void RecordOperator::deleteLike(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteLike(r_key); } @@ -535,7 +534,7 @@ void RecordOperator::deleteRepost(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteRepost(r_key); } @@ -559,7 +558,7 @@ void RecordOperator::deleteFollow(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->unfollow(r_key); } @@ -582,7 +581,7 @@ void RecordOperator::deleteMute(const QString &did) setRunning(false); unmute->deleteLater(); }); - unmute->setAccount(m_account); + unmute->setAccount(account()); unmute->unmuteActor(did); } @@ -606,7 +605,7 @@ void RecordOperator::deleteBlock(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteBlock(r_key); } @@ -630,7 +629,7 @@ void RecordOperator::deleteBlockList(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteBlockList(r_key); } @@ -662,7 +661,7 @@ bool RecordOperator::deleteList(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteList(r_key); } else { setProgressMessage(QString()); @@ -713,7 +712,7 @@ bool RecordOperator::deleteListItem(const QString &uri) setRunning(false); delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteListItem(r_key); return true; } @@ -725,7 +724,7 @@ void RecordOperator::updateProfile(const QString &avatar_url, const QString &ban return; setRunning(true); - setProgressMessage(tr("Update profile ... (%1)").arg(m_account.handle)); + setProgressMessage(tr("Update profile ... (%1)").arg(account().handle)); QStringList images; QStringList alts; @@ -770,7 +769,7 @@ void RecordOperator::updateProfile(const QString &avatar_url, const QString &ban setRunning(false); new_profile->deleteLater(); }); - new_profile->setAccount(m_account); + new_profile->setAccount(account()); new_profile->profile(avatar, banner, description, display_name, old_record.pinnedPost, old_cid); } else { @@ -787,8 +786,8 @@ void RecordOperator::updateProfile(const QString &avatar_url, const QString &ban } old_profile->deleteLater(); }); - old_profile->setAccount(m_account); - old_profile->profile(m_account.did); + old_profile->setAccount(account()); + old_profile->profile(account().did); } void RecordOperator::updatePostPinning(const QString &post_uri, const QString &post_cid) @@ -797,7 +796,7 @@ void RecordOperator::updatePostPinning(const QString &post_uri, const QString &p return; setRunning(true); - setProgressMessage(tr("Update post pinning ... (%1)").arg(m_account.handle)); + setProgressMessage(tr("Update post pinning ... (%1)").arg(account().handle)); ComAtprotoRepoGetRecordEx *old_profile = new ComAtprotoRepoGetRecordEx(this); connect(old_profile, &ComAtprotoRepoGetRecordEx::finished, [=](bool success1) { @@ -819,7 +818,7 @@ void RecordOperator::updatePostPinning(const QString &post_uri, const QString &p ComAtprotoRepoStrongRef::Main pinned_post; pinned_post.uri = post_uri; pinned_post.cid = post_cid; - new_profile->setAccount(m_account); + new_profile->setAccount(account()); new_profile->profile(old_record.avatar, old_record.banner, old_record.description, old_record.displayName, pinned_post, old_cid); } else { @@ -830,8 +829,8 @@ void RecordOperator::updatePostPinning(const QString &post_uri, const QString &p } old_profile->deleteLater(); }); - old_profile->setAccount(m_account); - old_profile->profile(m_account.did); + old_profile->setAccount(account()); + old_profile->profile(account().did); } void RecordOperator::updateList(const QString &uri, const QString &avatar_url, @@ -877,7 +876,7 @@ void RecordOperator::updateList(const QString &uri, const QString &avatar_url, setRunning(false); new_list->deleteLater(); }); - new_list->setAccount(m_account); + new_list->setAccount(account()); new_list->list(avatar, old_record.purpose, description, name, r_key); } else { setProgressMessage(QString()); @@ -893,8 +892,8 @@ void RecordOperator::updateList(const QString &uri, const QString &avatar_url, } old_list->deleteLater(); }); - old_list->setAccount(m_account); - old_list->list(m_account.did, r_key); + old_list->setAccount(account()); + old_list->list(account().did, r_key); } void RecordOperator::updateThreadGate(const QString &uri, const QString &threadgate_uri, @@ -936,7 +935,7 @@ void RecordOperator::updateThreadGate(const QString &uri, const QString &threadg } delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteThreadGate(r_key); } @@ -971,13 +970,13 @@ void RecordOperator::updateQuoteEnabled(const QString &uri, bool enabled) emit finished(success2, put->uri(), put->cid()); put->deleteLater(); }); - put->setAccount(m_account); + put->setAccount(account()); put->postGate(uri, rule, old_record.detachedEmbeddingUris); record->deleteLater(); }); - record->setAccount(m_account); - record->postGate(m_account.did, target_rkey); + record->setAccount(account()); + record->postGate(account().did, target_rkey); } void RecordOperator::updateDetachedStatusOfQuote(bool detached, QString target_uri, @@ -1031,13 +1030,13 @@ void RecordOperator::updateDetachedStatusOfQuote(bool detached, QString target_u emit finished(success2, put->uri(), put->cid()); put->deleteLater(); }); - put->setAccount(m_account); + put->setAccount(account()); put->postGate(target_uri, rule, old_record.detachedEmbeddingUris); record->deleteLater(); }); - record->setAccount(m_account); - record->postGate(m_account.did, target_rkey); + record->setAccount(account()); + record->postGate(account().did, target_rkey); } void RecordOperator::requestPostGate(const QString &uri) @@ -1063,8 +1062,8 @@ void RecordOperator::requestPostGate(const QString &uri) emit finishedRequestPostGate(success, enabled, uris); record->deleteLater(); }); - record->setAccount(m_account); - record->postGate(m_account.did, rkey); + record->setAccount(account()); + record->postGate(account().did, rkey); } bool RecordOperator::running() const @@ -1122,7 +1121,7 @@ void RecordOperator::uploadBlob(std::function callback) } upload_blob->deleteLater(); }); - upload_blob->setAccount(m_account); + upload_blob->setAccount(account()); upload_blob->uploadBlob(path); } @@ -1165,8 +1164,8 @@ bool RecordOperator::getAllListItems(const QString &list_uri, std::functiondeleteLater(); }); - list->setAccount(m_account); - list->listListItems(m_account.did, cursor); + list->setAccount(account()); + list->listListItems(account().did, cursor); } void RecordOperator::deleteAllListItems(std::function callback) @@ -1190,7 +1189,7 @@ void RecordOperator::deleteAllListItems(std::function callback) } delete_record->deleteLater(); }); - delete_record->setAccount(m_account); + delete_record->setAccount(account()); delete_record->deleteListItem(r_key); } @@ -1239,7 +1238,7 @@ bool RecordOperator::threadGate( callback(success, create_record->uri(), create_record->cid()); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->threadGate(uri, type, rules); return true; } @@ -1270,7 +1269,7 @@ void RecordOperator::postGate(const QString &uri, callback(success, create_record->uri(), create_record->cid()); create_record->deleteLater(); }); - create_record->setAccount(m_account); + create_record->setAccount(account()); create_record->postGate(uri, type, m_postGateDetachedEmbeddingUris); } diff --git a/app/qtquick/operation/recordoperator.h b/app/qtquick/operation/recordoperator.h index c07e63f5..c73d6d80 100644 --- a/app/qtquick/operation/recordoperator.h +++ b/app/qtquick/operation/recordoperator.h @@ -29,9 +29,8 @@ class RecordOperator : public QObject }; Q_ENUM(ListPurpose); - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + AtProtocolInterface::AccountData account(); + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE void setText(const QString &text); Q_INVOKABLE void setReply(const QString &parent_cid, const QString &parent_uri, const QString &root_cid, const QString &root_uri); diff --git a/app/qtquick/profile/userprofile.cpp b/app/qtquick/profile/userprofile.cpp index 123ac5fe..50b85573 100644 --- a/app/qtquick/profile/userprofile.cpp +++ b/app/qtquick/profile/userprofile.cpp @@ -6,6 +6,7 @@ #include "extension/directory/plc/directoryplc.h" #include "extension/directory/plc/directoryplclogaudit.h" #include "tools/labelerprovider.h" +#include "tools/accountmanager.h" #include @@ -38,16 +39,10 @@ UserProfile::~UserProfile() &UserProfile::updatedBelongingLists); } -void UserProfile::setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt) +void UserProfile::setAccount(const QString &uuid) { - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + m_account.uuid = uuid; + m_account = AccountManager::getInstance()->getAccount(m_account.uuid); } void UserProfile::getProfile(const QString &did) @@ -118,7 +113,7 @@ void UserProfile::getProfile(const QString &did) } profile->deleteLater(); }); - profile->setAccount(m_account); + profile->setAccount(AccountManager::getInstance()->getAccount(m_account.uuid)); profile->setLabelers(labelerDids()); profile->getProfile(did); }); @@ -367,7 +362,8 @@ QString UserProfile::formattedDescription() const void UserProfile::updatedBelongingLists(const QString &account_did, const QString &user_did) { - if (m_account.did == account_did && did() == user_did) { + if (AccountManager::getInstance()->getAccount(m_account.uuid).did == account_did + && did() == user_did) { setBelongingLists(ListItemsCache::getInstance()->getListNames(account_did, user_did)); } } @@ -382,8 +378,9 @@ void UserProfile::updateContentFilterLabels(std::function callback) callback(); connector->deleteLater(); }); - provider->setAccount(m_account); - provider->update(m_account, connector, LabelerProvider::RefleshAuto); + provider->setAccount(AccountManager::getInstance()->getAccount(m_account.uuid)); + provider->update(AccountManager::getInstance()->getAccount(m_account.uuid), connector, + LabelerProvider::RefleshAuto); } void UserProfile::getServiceEndpoint(const QString &did, @@ -493,12 +490,15 @@ void UserProfile::getRawProfile() QString UserProfile::labelsTitle(const QString &label, const bool for_image, const QString &labeler_did) const { - return LabelerProvider::getInstance()->title(m_account, label, for_image, labeler_did); + return LabelerProvider::getInstance()->title( + AccountManager::getInstance()->getAccount(m_account.uuid), label, for_image, + labeler_did); } QStringList UserProfile::labelerDids() const { - return LabelerProvider::getInstance()->labelerDids(m_account); + return LabelerProvider::getInstance()->labelerDids( + AccountManager::getInstance()->getAccount(m_account.uuid)); } QStringList UserProfile::labels() const diff --git a/app/qtquick/profile/userprofile.h b/app/qtquick/profile/userprofile.h index 4091364d..07c4007e 100644 --- a/app/qtquick/profile/userprofile.h +++ b/app/qtquick/profile/userprofile.h @@ -62,9 +62,7 @@ class UserProfile : public QObject explicit UserProfile(QObject *parent = nullptr); ~UserProfile(); - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE void getProfile(const QString &did); bool running() const; diff --git a/app/qtquick/timeline/customfeedlistmodel.cpp b/app/qtquick/timeline/customfeedlistmodel.cpp index 2828d8d2..70eb1386 100644 --- a/app/qtquick/timeline/customfeedlistmodel.cpp +++ b/app/qtquick/timeline/customfeedlistmodel.cpp @@ -74,8 +74,7 @@ void CustomFeedListModel::saveGenerator() { if (uri().isEmpty()) return; - m_feedGeneratorListModel.setAccount(account().service, account().did, account().handle, - account().email, account().accessJwt, account().refreshJwt); + m_feedGeneratorListModel.setAccount(account().uuid); m_feedGeneratorListModel.saveGenerator(uri()); } @@ -83,8 +82,7 @@ void CustomFeedListModel::removeGenerator() { if (uri().isEmpty()) return; - m_feedGeneratorListModel.setAccount(account().service, account().did, account().handle, - account().email, account().accessJwt, account().refreshJwt); + m_feedGeneratorListModel.setAccount(account().uuid); m_feedGeneratorListModel.removeGenerator(uri()); } diff --git a/app/qtquick/timeline/timelinelistmodel.cpp b/app/qtquick/timeline/timelinelistmodel.cpp index f4e307b9..0e9e9d35 100644 --- a/app/qtquick/timeline/timelinelistmodel.cpp +++ b/app/qtquick/timeline/timelinelistmodel.cpp @@ -535,8 +535,7 @@ bool TimelineListModel::deletePost(int row) setRunningdeletePost(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); ope->deletePost(item(row, UriRole).toString()); return true; @@ -564,8 +563,7 @@ bool TimelineListModel::repost(int row) setRunningRepost(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (!current) ope->repost(item(row, CidRole).toString(), item(row, UriRole).toString()); else @@ -597,8 +595,7 @@ bool TimelineListModel::like(int row) setRunningLike(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); if (!current) ope->like(item(row, CidRole).toString(), item(row, UriRole).toString()); else @@ -642,8 +639,7 @@ bool TimelineListModel::pin(int row) setRunningPostPinning(row, false); ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); ope->updatePostPinning(pin_uri, pin_cid); return true; @@ -742,8 +738,7 @@ bool TimelineListModel::detachQuote(int row) } ope->deleteLater(); }); - ope->setAccount(account().service, account().did, account().handle, account().email, - account().accessJwt, account().refreshJwt); + ope->setAccount(account().uuid); ope->updateDetachedStatusOfQuote(detached, target_uri, detach_uri); return true; } diff --git a/app/qtquick/timeline/userpost.cpp b/app/qtquick/timeline/userpost.cpp index fd16fa0b..efbf814e 100644 --- a/app/qtquick/timeline/userpost.cpp +++ b/app/qtquick/timeline/userpost.cpp @@ -3,6 +3,7 @@ #include "atprotocol/app/bsky/feed/appbskyfeedgetposts.h" #include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" #include "atprotocol/lexicons_func_unknown.h" +#include "tools/accountmanager.h" #include "tools/labelerprovider.h" using AtProtocolInterface::AppBskyActorGetProfile; @@ -11,16 +12,10 @@ using namespace AtProtocolType; UserPost::UserPost(QObject *parent) : QObject { parent }, m_running(false), m_authorMuted(false) { } -void UserPost::setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, const QString &refreshJwt) +void UserPost::setAccount(const QString &uuid) { - qDebug().noquote() << this << service << handle << accessJwt; - m_account.service = service; - m_account.did = did; - m_account.handle = handle; - m_account.email = email; - m_account.accessJwt = accessJwt; - m_account.refreshJwt = refreshJwt; + qDebug().noquote() << this << uuid; + m_account.uuid = uuid; } void UserPost::getPost(const QString &uri) @@ -69,7 +64,7 @@ void UserPost::getPost(const QString &uri) setRunning(false); posts->deleteLater(); }); - posts->setAccount(m_account); + posts->setAccount(AccountManager::getInstance()->getAccount(m_account.uuid)); posts->setLabelers(labelerDids()); posts->getPosts(QStringList() << at_uri); }); @@ -302,7 +297,7 @@ void UserPost::convertToAtUri(const QString &base_at_uri, const QString &uri, } profile->deleteLater(); }); - profile->setAccount(m_account); + profile->setAccount(AccountManager::getInstance()->getAccount(m_account.uuid)); profile->getProfile(user_id); } } diff --git a/app/qtquick/timeline/userpost.h b/app/qtquick/timeline/userpost.h index a3488931..da2760f7 100644 --- a/app/qtquick/timeline/userpost.h +++ b/app/qtquick/timeline/userpost.h @@ -39,9 +39,7 @@ class UserPost : public QObject public: explicit UserPost(QObject *parent = nullptr); - Q_INVOKABLE void setAccount(const QString &service, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, - const QString &refreshJwt); + Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE void getPost(const QString &uri); Q_INVOKABLE void clear(); diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index e2665635..833ef663 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -424,12 +424,13 @@ AccountData AccountManager::getAccount(const QString &uuid) const return dList.at(dIndex.value(uuid))->getAccount(); } -void AccountManager::updateAccount(const QString &uuid, const QString &service, - const QString &identifier, const QString &password, - const QString &did, const QString &handle, const QString &email, - const QString &accessJwt, const QString &refreshJwt, - const bool authorized) +QString AccountManager::updateAccount(const QString &uuid, const QString &service, + const QString &identifier, const QString &password, + const QString &did, const QString &handle, + const QString &email, const QString &accessJwt, + const QString &refreshJwt, const bool authorized) { + QString ret; if (!uuid.isEmpty() && dIndex.contains(uuid)) { AccountData account = dList.at(dIndex[uuid])->getAccount(); dList.at(dIndex[uuid]) @@ -437,6 +438,7 @@ void AccountManager::updateAccount(const QString &uuid, const QString &service, accessJwt, refreshJwt, account.thread_gate_type, authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); + ret = uuid; } else { // append QString new_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); @@ -446,10 +448,12 @@ void AccountManager::updateAccount(const QString &uuid, const QString &service, new_uuid, service, identifier, password, did, handle, email, accessJwt, refreshJwt, "everybody", authorized ? AccountStatus::Authorized : AccountStatus::Unauthorized); + ret = new_uuid; emit countChanged(); } save(); checkAllAccountsReady(); + return ret; } void AccountManager::removeAccount(const QString &uuid) diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 4c2b7d12..96f7601b 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -49,10 +49,10 @@ class AccountManager : public QObject void update(int row, AccountManager::AccountManagerRoles role, const QVariant &value); AtProtocolInterface::AccountData getAccount(const QString &uuid) const; - void updateAccount(const QString &uuid, const QString &service, const QString &identifier, - const QString &password, const QString &did, const QString &handle, - const QString &email, const QString &accessJwt, const QString &refreshJwt, - const bool authorized); + QString updateAccount(const QString &uuid, const QString &service, const QString &identifier, + const QString &password, const QString &did, const QString &handle, + const QString &email, const QString &accessJwt, const QString &refreshJwt, + const bool authorized); void removeAccount(const QString &uuid); void updateAccountProfile(const QString &uuid); int getMainAccountIndex() const; diff --git a/tests/chat_test/tst_chat_test.cpp b/tests/chat_test/tst_chat_test.cpp index 6dd87743..d9e36e76 100644 --- a/tests/chat_test/tst_chat_test.cpp +++ b/tests/chat_test/tst_chat_test.cpp @@ -5,6 +5,7 @@ #include "chat/chatlistmodel.h" #include "chat/chatmessagelistmodel.h" #include "tools/chatlogsubscriber.h" +#include "tools/accountmanager.h" class chat_test : public QObject { @@ -75,8 +76,11 @@ void chat_test::test_ChatListModel() { int i = 0; ChatListModel model; - model.setAccount(m_service + "/list", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", - "accessJwt", "refreshJwt"); + + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/list", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "handle", "email", "accessJwt", "refreshJwt", true); + model.setAccount(uuid); model.setServiceEndpoint(m_service + "/list"); { @@ -136,8 +140,10 @@ void chat_test::test_ChatMessageListModel() ChatMessageListModel model; int i = 0; - model.setAccount(m_service + "/message/1", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", - "email", "accessJwt", "refreshJwt"); + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/message/1", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "handle", "email", "accessJwt", "refreshJwt", true); + model.setAccount(uuid); model.setServiceEndpoint(m_service + "/message/1"); model.setConvoId("3ksrqt7eebs2b"); @@ -261,8 +267,10 @@ void chat_test::test_ChatMessageListModelByMembers() ChatMessageListModel model; int i = 0; - model.setAccount(m_service + "/message/2", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", - "email", "accessJwt", "refreshJwt"); + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/message/2", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "handle", "email", "accessJwt", "refreshJwt", true); + model.setAccount(uuid); model.setServiceEndpoint(m_service + "/message/2"); model.setMemberDids(QStringList() << "did:plc:mqxsuw5b5rhpwo4lw6iwlid5"); diff --git a/tests/hagoromo_test/tst_hagoromo_test.cpp b/tests/hagoromo_test/tst_hagoromo_test.cpp index 36050f7d..a396368e 100644 --- a/tests/hagoromo_test/tst_hagoromo_test.cpp +++ b/tests/hagoromo_test/tst_hagoromo_test.cpp @@ -19,6 +19,7 @@ #include "moderation/contentfiltersettinglistmodel.h" #include "tools/labelerprovider.h" #include "controls/calendartablemodel.h" +#include "tools/accountmanager.h" class hagoromo_test : public QObject { @@ -128,8 +129,12 @@ void hagoromo_test::test_test_TimelineListModelError() void hagoromo_test::test_TimelineListModelFacet() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/facet", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "handle", "email", "accessJwt", "refreshJwt", true); + TimelineListModel model; - model.setAccount(m_service + "/facet", QString(), QString(), QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -452,9 +457,12 @@ void hagoromo_test::test_ColumnListModelSelected() void hagoromo_test::test_NotificationListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/visible", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "handle", "email", "accessJwt", "refreshJwt", true); + NotificationListModel model; - model.setAccount(m_service + "/notifications/visible", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); { int i = 0; @@ -751,9 +759,12 @@ void hagoromo_test::test_NotificationListModel() void hagoromo_test::test_NotificationListModel2() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/visible", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "handle", "email", "accessJwt", "refreshJwt", true); + NotificationListModel model; - model.setAccount(m_service + "/notifications/visible", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); int i = 0; QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -976,10 +987,13 @@ void hagoromo_test::test_NotificationListModel2() void hagoromo_test::test_NotificationList_collecting() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/collecting/1", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); + int i = 0; NotificationListModel model; - model.setAccount(m_service + "/notifications/collecting/1", "did:plc:ipj5qejfoqu6eukvt72uhyit", - QString(), QString(), "dummy", QString()); + model.setAccount(uuid); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1032,9 +1046,12 @@ void hagoromo_test::test_NotificationList_collecting() void hagoromo_test::test_NotificationList_collecting_next() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/collecting/2", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); + NotificationListModel model; - model.setAccount(m_service + "/notifications/collecting/2", "did:plc:ipj5qejfoqu6eukvt72uhyit", - QString(), QString(), "dummy", QString()); + model.setAccount(uuid); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1146,9 +1163,11 @@ void hagoromo_test::test_NotificationList_collecting_next() void hagoromo_test::test_NotificationList_collecting_visibility() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/collecting/2", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); NotificationListModel model; - model.setAccount(m_service + "/notifications/collecting/2", "did:plc:ipj5qejfoqu6eukvt72uhyit", - QString(), QString(), "dummy", QString()); + model.setAccount(uuid); model.setVisibleLike(false); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1263,10 +1282,12 @@ void hagoromo_test::test_NotificationList_collecting_visibility() void hagoromo_test::test_NotificationList_no_collecting() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/collecting/2", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); NotificationListModel model; - model.setAccount(m_service + "/notifications/collecting/2", "did:plc:ipj5qejfoqu6eukvt72uhyit", - QString(), QString(), "dummy", QString()); + model.setAccount(uuid); model.setAggregateReactions(false); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1518,10 +1539,12 @@ void hagoromo_test::test_charCount() void hagoromo_test::test_TimelineListModel_quote_warn() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/warn", "id", "pass", "did:plc:test", "handle", + "email", "accessJwt", "refreshJwt", true); int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/warn", "did:plc:test", QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); LabelerProvider::getInstance()->clear(); @@ -1578,10 +1601,14 @@ void hagoromo_test::test_TimelineListModel_quote_warn() void hagoromo_test::test_TimelineListModel_quote_hide() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/hide", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/hide", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1636,12 +1663,16 @@ void hagoromo_test::test_TimelineListModel_quote_hide() void hagoromo_test::test_TimelineListModel_quote_hide2() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/hide", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane2.bsky.social", "email", "accessJwt", + "refreshJwt", true); + // 自分のポストが引用されているのを見るイメージ // 自分のポストの引用はHide設定でも隠さない int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/hide", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", - "ioriayane2.bsky.social", QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1707,10 +1738,13 @@ void hagoromo_test::test_TimelineListModel_quote_hide2() void hagoromo_test::test_TimelineListModel_quote_label() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/labels", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane2.bsky.social", "email", "accessJwt", + "refreshJwt", true); int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/labels", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1759,10 +1793,14 @@ void hagoromo_test::test_TimelineListModel_quote_label() void hagoromo_test::test_TimelineListModel_animated_image() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/animated", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane2.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/animated", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1900,10 +1938,14 @@ void hagoromo_test::test_TimelineListModel_animated_image() void hagoromo_test::test_TimelineListModel_threadgate() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/threadgate", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane2.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/threadgate", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -1976,10 +2018,14 @@ void hagoromo_test::test_TimelineListModel_threadgate() void hagoromo_test::test_TimelineListModel_hide_repost() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/hide2", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane2.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/hide2", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", - "ioriayane2.bsky.social", QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2302,10 +2348,14 @@ void hagoromo_test::test_TimelineListModel_hide_repost() void hagoromo_test::test_TimelineListModel_labelers() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/labelers/1", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/labelers/1", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); LabelerProvider::getInstance()->clear(); @@ -2478,10 +2528,14 @@ void hagoromo_test::test_TimelineListModel_labelers() void hagoromo_test::test_TimelineListModel_pinnded() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/pinned/1", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/pinned/1", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); model.setDisplayPinnedPost(true); model.setPinnedPost("at://did:plc:l4fsx4ujos7uw7n4ijq2ulgs/app.bsky.feed.post/3kgbutwycqd2g"); @@ -2510,8 +2564,11 @@ void hagoromo_test::test_TimelineListModel_pinnded() model.item(row, TimelineListModel::UriRole).toString().toLocal8Bit()); } - model.setAccount(m_service + "/timeline/pinned/2", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/pinned/2", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); { QSignalSpy spy(&model, SIGNAL(runningChanged())); QVERIFY(model.getLatest()); @@ -2568,8 +2625,11 @@ void hagoromo_test::test_TimelineListModel_pinnded() } // re-set - model.setAccount(m_service + "/timeline/pinned/3", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/pinned/3", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); model.setPinnedPost("at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.feed.post/3klrvkltf672n"); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2601,8 +2661,11 @@ void hagoromo_test::test_TimelineListModel_pinnded() } // replace pin - model.setAccount(m_service + "/timeline/pinned/2", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", - "ioriayane.bsky.social", QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/pinned/2", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); model.setPinnedPost("at://did:plc:l4fsx4ujos7uw7n4ijq2ulgs/app.bsky.feed.post/3kgbutwycqd2g"); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2636,10 +2699,12 @@ void hagoromo_test::test_TimelineListModel_pinnded() void hagoromo_test::test_NotificationListModel_warn() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/notifications/warn", "id", "pass", "did:plc:test", + "ioriayane.bsky.social", "email", "accessJwt", "refreshJwt", true); int row = 0; NotificationListModel model; - model.setAccount(m_service + "/notifications/warn", "did:plc:test", QString(), QString(), - "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); LabelerProvider::getInstance()->clear(); @@ -2677,10 +2742,13 @@ void hagoromo_test::test_NotificationListModel_warn() void hagoromo_test::test_TimelineListModel_next() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/next", "id", "pass", "did:plc:test", + "ioriayane.bsky.social", "email", "accessJwt", "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/next", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2746,9 +2814,12 @@ void hagoromo_test::test_TimelineListModel_next() void hagoromo_test::test_AnyProfileListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/anyprofile", "id", "pass", "did:plc:test", + "ioriayane.bsky.social", "email", "accessJwt", "refreshJwt", true); + AnyProfileListModel model; - model.setAccount(m_service + "/anyprofile", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setTargetUri("at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.post/3k6tpw4xr4d27"); model.setType(AnyProfileListModel::AnyProfileListModelType::Like); @@ -2774,10 +2845,13 @@ void hagoromo_test::test_AnyProfileListModel() void hagoromo_test::test_TimelineListModel_text() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/text", "id", "pass", "did:plc:test", + "ioriayane.bsky.social", "email", "accessJwt", "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/text", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2809,10 +2883,14 @@ void hagoromo_test::test_TimelineListModel_text() void hagoromo_test::test_TimelineListModel_reply() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/reply", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + int row = 0; TimelineListModel model; - model.setAccount(m_service + "/timeline/reply", "did:plc:ipj5qejfoqu6eukvt72uhyit", QString(), - QString(), "dummy", QString()); + model.setAccount(uuid); model.setDisplayInterval(0); model.setVisibleReplyToUnfollowedUsers(true); @@ -2851,8 +2929,11 @@ void hagoromo_test::test_TimelineListModel_reply() QVERIFY(model.item(2, TimelineListModel::CidRole) == "bafyreievv2yz3obnigwjix5kr2icycfkqdobrfufd3cm4wfavnjfeqhxbe_4"); - model.setAccount(m_service + "/timeline/reply", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", QString(), - QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/timeline/reply", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); model.setVisibleReplyToUnfollowedUsers(false); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -2874,9 +2955,12 @@ void hagoromo_test::test_TimelineListModel_reply() void hagoromo_test::test_PostThreadListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/postthread/1", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); PostThreadListModel model; - model.setAccount(m_service + "/postthread/1", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); model.setDisplayInterval(0); model.setPostThreadUri("at://uri"); @@ -2907,9 +2991,12 @@ void hagoromo_test::test_PostThreadListModel() QVERIFY(model.item(row, PostThreadListModel::ThreadConnectorTopRole).toBool() == true); QVERIFY(model.item(row, PostThreadListModel::ThreadConnectorBottomRole).toBool() == false); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/postthread/2", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); model.clear(); - model.setAccount(m_service + "/postthread/2", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); { QSignalSpy spy(&model, SIGNAL(runningChanged())); model.getLatest(); @@ -2943,9 +3030,12 @@ void hagoromo_test::test_PostThreadListModel() QVERIFY(model.item(row, PostThreadListModel::ThreadConnectorTopRole).toBool() == true); QVERIFY(model.item(row, PostThreadListModel::ThreadConnectorBottomRole).toBool() == false); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/postthread/3", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); model.clear(); - model.setAccount(m_service + "/postthread/3", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); { QSignalSpy spy(&model, SIGNAL(runningChanged())); model.getLatest(); @@ -2990,9 +3080,12 @@ void hagoromo_test::test_SystemTool_ImageClip() void hagoromo_test::test_SearchProfileListModel_suggestion() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/search_profile", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); SearchProfileListModel model; - model.setAccount(m_service + "/search_profile", QString(), QString(), QString(), "dummy", - QString()); + model.setAccount(uuid); QString actual; @@ -3110,8 +3203,11 @@ void hagoromo_test::test_SearchProfileListModel_suggestion() void hagoromo_test::test_SearchPostListModel_text() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/search_profile", "id", "pass", "did:plc:hogehoge", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); SearchPostListModel model; - model.setAccount("", "did:plc:hogehoge", "hogehoge.bsky.sockal", "", "", ""); + model.setAccount(uuid); QVERIFY2(model.replaceSearchCommand("from:me") == "from:hogehoge.bsky.sockal", model.text().toLocal8Bit()); diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 41b99317..287894fb 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -110,8 +110,12 @@ void hagoromo_test::cleanupTestCase() { } void hagoromo_test::test_RecordOperator() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/facet", "id", "pass", "did:plc:hogehoge", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); + RecordOperator ope; - ope.setAccount(m_service + "/facet", QString(), QString(), QString(), "dummy", QString()); + ope.setAccount(uuid); QHash hash = UnitTestCommon::loadPostHash(":/data/com.atproto.repo.createRecord_post.expect"); @@ -119,8 +123,12 @@ void hagoromo_test::test_RecordOperator() while (i.hasNext()) { i.next(); + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/facet", "id", "pass", i.key(), "handle", "email", + "accessJwt", "refreshJwt", true); + ope.clear(); - ope.setAccount(m_service + "/facet", i.key(), "handle", "email", "accessJwt", "refreshJwt"); + ope.setAccount(uuid); ope.setText(i.value()); QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); @@ -136,9 +144,12 @@ void hagoromo_test::test_RecordOperator() void hagoromo_test::test_RecordOperator_profile() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/profile/1", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); + RecordOperator ope; - ope.setAccount(m_service + "/profile/1", "did:plc:ipj5qejfoqu6eukvt72uhyit", QString(), - QString(), "dummy", QString()); + ope.setAccount(uuid); { QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); ope.updateProfile("", "", "description", "display_name"); @@ -150,8 +161,10 @@ void hagoromo_test::test_RecordOperator_profile() QVERIFY(arguments.at(0).toBool() == true); } - ope.setAccount(m_service + "/profile/3.1", "did:plc:ipj5qejfoqu6eukvt72uhyit", QString(), - QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/profile/3.1", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); + ope.setAccount(uuid); { QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); ope.updatePostPinning( @@ -165,8 +178,10 @@ void hagoromo_test::test_RecordOperator_profile() QVERIFY(arguments.at(0).toBool() == true); } - ope.setAccount(m_service + "/profile/3.2", "did:plc:ipj5qejfoqu6eukvt72uhyit", QString(), - QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/profile/3.2", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); + ope.setAccount(uuid); { QSignalSpy spy(&ope, SIGNAL(finished(bool, const QString &, const QString &))); ope.updatePostPinning(QString(), QString()); @@ -182,9 +197,13 @@ void hagoromo_test::test_RecordOperator_profile() void hagoromo_test::test_FeedGeneratorListModel() { FeedGeneratorListModel model; - model.setAccount(m_service + "/generator", QString(), QString(), QString(), "dummy", QString()); model.setDisplayInterval(0); { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/generator", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "hogehoge.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); QSignalSpy spy(&model, SIGNAL(runningChanged())); model.getLatest(); spy.wait(); @@ -206,8 +225,11 @@ void hagoromo_test::test_FeedGeneratorListModel() } { // save - model.setAccount(m_service + "/generator/save", QString(), QString(), QString(), "dummy", - QString()); + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/generator/save", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "hogehoge.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); QSignalSpy spy(&model, SIGNAL(runningChanged())); model.saveGenerator( "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/hot-classic"); @@ -216,8 +238,11 @@ void hagoromo_test::test_FeedGeneratorListModel() } { // remove - model.setAccount(m_service + "/generator/remove", QString(), QString(), QString(), "dummy", - QString()); + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/generator/remove", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "hogehoge.bsky.social", "email", "accessJwt", + "refreshJwt", true); + model.setAccount(uuid); QSignalSpy spy(&model, SIGNAL(runningChanged())); model.removeGenerator( "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends"); @@ -228,6 +253,10 @@ void hagoromo_test::test_FeedGeneratorListModel() void hagoromo_test::test_FeedGeneratorLink() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/generator", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); + FeedGeneratorLink link; QVERIFY(link.checkUri("https://bsky.app/profile/did:plc:hoge/feed/aaaaaaaa", "feed") == true); @@ -240,7 +269,7 @@ void hagoromo_test::test_FeedGeneratorLink() QVERIFY(link.checkUri("https://bsky.app/profile/handle.com/feed/aaaaaaaa", "feed") == true); QVERIFY(link.checkUri("https://bsky.app/profile/@handle.com/feed/aaaaaaaa", "feed") == false); - link.setAccount(m_service + "/generator", QString(), QString(), QString(), "dummy", QString()); + link.setAccount(uuid); { QSignalSpy spy(&link, SIGNAL(runningChanged())); link.getFeedGenerator("https://bsky.app/profile/did:plc:hoge/feed/aaaaaaaa"); @@ -433,10 +462,12 @@ void hagoromo_test::test_AccountManager() void hagoromo_test::test_ListsListModel() { - ListsListModel model; + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/lists/lists", "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); - model.setAccount(m_service + "/lists/lists", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", QString(), - QString(), "dummy", QString()); + ListsListModel model; + model.setAccount(uuid); model.setVisibilityType(ListsListModel::VisibilityTypeAll); { @@ -526,10 +557,13 @@ void hagoromo_test::test_ListsListModel() void hagoromo_test::test_ListsListModel_search() { - ListsListModel model; + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/lists/search", "id", "pass", + "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "hogehoge.bsky.social", "email", "accessJwt", + "refreshJwt", true); - model.setAccount(m_service + "/lists/search", QString(), QString(), QString(), "dummy", - QString()); + ListsListModel model; + model.setAccount(uuid); model.setSearchTarget("did:plc:user_42"); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -584,10 +618,12 @@ void hagoromo_test::test_ListsListModel_error() void hagoromo_test::test_ListItemListModel() { - ListItemListModel model; + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/lists/list", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", + "hogehoge.bsky.social", "email", "accessJwt", "refreshJwt", true); - model.setAccount(m_service + "/lists/list", "did:plc:ipj5qejfoqu6eukvt72uhyit", QString(), - QString(), "dummy", QString()); + ListItemListModel model; + model.setAccount(uuid); model.setUri("at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.graph.list/3k7igyxfizg27"); { @@ -655,10 +691,13 @@ void hagoromo_test::test_ListItemListModel_error() void hagoromo_test::test_ListFeedListModel() { - ListFeedListModel model; + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/lists/feed/0", "id", "pass", + "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", "hogehoge.bsky.social", "email", "accessJwt", + "refreshJwt", true); - model.setAccount(m_service + "/lists/feed/0", "did:plc:l4fsx4ujos7uw7n4ijq2ulgs", QString(), - QString(), "dummy", QString()); + ListFeedListModel model; + model.setAccount(uuid); model.setUri("at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.graph.list/3k7igyxfizg27"); { QSignalSpy spy(&model, SIGNAL(runningChanged())); diff --git a/tests/log_test/tst_log_test.cpp b/tests/log_test/tst_log_test.cpp index 0938d939..6bb1a94b 100644 --- a/tests/log_test/tst_log_test.cpp +++ b/tests/log_test/tst_log_test.cpp @@ -4,6 +4,7 @@ #include #include +#include "tools/accountmanager.h" #include "webserver.h" #include "log/logmanager.h" #include "log/logoperator.h" @@ -604,9 +605,12 @@ void log_test::test_LogFeedListModel() account.handle = "log.manager.test"; account.accessJwt = "access jwt"; + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), account.service + "/posts/10", "id", "pass", account.did, account.handle, + account.email, account.accessJwt, account.refreshJwt, true); + LogFeedListModel model; - model.setAccount(account.service + "/posts/10", account.did, account.handle, account.email, - account.accessJwt, account.refreshJwt); + model.setAccount(uuid); model.setTargetDid(account.did); model.setTargetHandle(account.handle); model.setTargetAvatar("test_avatar.jpg"); diff --git a/tests/realtime_test/tst_realtime_test.cpp b/tests/realtime_test/tst_realtime_test.cpp index 5b1dbcf7..d9ad6f2e 100644 --- a/tests/realtime_test/tst_realtime_test.cpp +++ b/tests/realtime_test/tst_realtime_test.cpp @@ -10,6 +10,7 @@ #include "realtime/abstractpostselector.h" #include "realtime/firehosereceiver.h" #include "realtime/realtimefeedlistmodel.h" +#include "tools/accountmanager.h" using namespace RealtimeFeed; @@ -231,9 +232,12 @@ void realtime_test::test_Websock() void realtime_test::test_RealtimeFeedListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/realtime/1", "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", + "handle", "email", "accessJwt", "refreshJwt", true); + RealtimeFeedListModel model; - model.setAccount(m_service + "/realtime/1", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", QString(), - QString(), "dummy", QString()); + model.setAccount(uuid); // model.setSelectorJson("{\"not\":{\"me\":{}}}"); // model.setSelectorJson("{\"not\":{\"following\":{}}}"); @@ -284,8 +288,10 @@ void realtime_test::test_RealtimeFeedListModel() QVERIFY(model.item(0, TimelineListModel::RecordTextPlainRole).toString() == "reply3"); qDebug().noquote() << "---------------------------"; - model.setAccount(m_service + "/realtime/2", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", QString(), - QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/realtime/2", "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", + "handle", "email", "accessJwt", "refreshJwt", true); + model.setAccount(uuid); json_doc = loadJson(":/data/realtimemodel/recv_data_2.json"); QVERIFY(json_doc.isObject()); { @@ -310,8 +316,10 @@ void realtime_test::test_RealtimeFeedListModel() QVERIFY(model.item(1, TimelineListModel::IsRepostedByRole).toBool() == false); qDebug().noquote() << "---------------------------"; - model.setAccount(m_service + "/realtime/3", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", QString(), - QString(), "dummy", QString()); + uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service + "/realtime/3", "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", + "handle", "email", "accessJwt", "refreshJwt", true); + model.setAccount(uuid); json_doc = loadJson(":/data/realtimemodel/recv_data_3.json"); recv->testReceived(json_doc.object()); QVERIFY2(model.rowCount() == 2, QString::number(model.rowCount()).toLocal8Bit()); diff --git a/tests/search_test/tst_search_test.cpp b/tests/search_test/tst_search_test.cpp index 5983e056..2fc7349e 100644 --- a/tests/search_test/tst_search_test.cpp +++ b/tests/search_test/tst_search_test.cpp @@ -3,9 +3,9 @@ #include #include "webserver.h" - #include "timeline/searchpostlistmodel.h" #include "profile/searchprofilelistmodel.h" +#include "tools/accountmanager.h" class search_test : public QObject { @@ -47,8 +47,12 @@ void search_test::cleanupTestCase() { } void search_test::test_SearchPostListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service, "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "handle", + "email", "accessJwt", "refreshJwt", true); + SearchPostListModel model; - model.setAccount(m_service, QString(), QString(), QString(), "dummy", QString()); + model.setAccount(uuid); model.setText("epub"); QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -61,8 +65,12 @@ void search_test::test_SearchPostListModel() void search_test::test_SearchProfileListModel() { + QString uuid = AccountManager::getInstance()->updateAccount( + QString(), m_service, "id", "pass", "did:plc:mqxsuw5b5rhpwo4lw6iwlid5", "handle", + "email", "accessJwt", "refreshJwt", true); + SearchProfileListModel model; - model.setAccount(m_service, QString(), QString(), QString(), "dummy", QString()); + model.setAccount(uuid); model.setText("epub"); QSignalSpy spy(&model, SIGNAL(runningChanged())); From ce3cd6daeb1419525abf55ed24871e67be888d7f Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 19:28:48 +0900 Subject: [PATCH 099/127] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=81=AEservice=5Fendpoint=E3=81=AE=E7=AE=A1=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/chat/atpchatabstractlistmodel.cpp | 23 ++++++++----------- app/qtquick/chat/atpchatabstractlistmodel.h | 1 - lib/tools/accountmanager.cpp | 6 +++++ lib/tools/accountmanager.h | 1 + tests/chat_test/tst_chat_test.cpp | 6 ++--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/qtquick/chat/atpchatabstractlistmodel.cpp b/app/qtquick/chat/atpchatabstractlistmodel.cpp index 40046007..1f4d0c9a 100644 --- a/app/qtquick/chat/atpchatabstractlistmodel.cpp +++ b/app/qtquick/chat/atpchatabstractlistmodel.cpp @@ -28,11 +28,6 @@ void AtpChatAbstractListModel::setAccount(const QString &uuid) m_account.uuid = uuid; } -void AtpChatAbstractListModel::setServiceEndpoint(const QString &service_endpoint) -{ - m_account.service_endpoint = service_endpoint; -} - void AtpChatAbstractListModel::updateRead(const QString &convoId, const QString &messageId) { if (convoId.isEmpty()) @@ -68,7 +63,7 @@ void AtpChatAbstractListModel::setRunning(bool newRunning) void AtpChatAbstractListModel::getServiceEndpoint(std::function callback) { - if (!m_account.service_endpoint.isEmpty()) { + if (!account().service_endpoint.isEmpty()) { callback(); return; } @@ -77,22 +72,22 @@ void AtpChatAbstractListModel::getServiceEndpoint(std::function callback return; } if (!account().service.startsWith("https://bsky.social")) { - m_account.service_endpoint = m_account.service; - qDebug().noquote() << "Update service endpoint(chat)" << m_account.service << "->" - << m_account.service_endpoint; + account().service_endpoint = account().service; + qDebug().noquote() << "Update service endpoint(chat)" << account().service << "->" + << account().service_endpoint; callback(); return; } DirectoryPlc *plc = new DirectoryPlc(this); connect(plc, &DirectoryPlc::finished, this, [=](bool success) { + QString service_endpoint = account().service; if (success && !plc->serviceEndpoint().isEmpty()) { - m_account.service_endpoint = plc->serviceEndpoint(); - } else { - m_account.service_endpoint = m_account.service; + service_endpoint = plc->serviceEndpoint(); } - qDebug().noquote() << "Update service endpoint(chat)" << m_account.service << "->" - << m_account.service_endpoint; + AccountManager::getInstance()->updateServiceEndpoint(account().uuid, service_endpoint); + qDebug().noquote() << "Update service endpoint(chat)" << account().service << "->" + << account().service_endpoint; callback(); plc->deleteLater(); }); diff --git a/app/qtquick/chat/atpchatabstractlistmodel.h b/app/qtquick/chat/atpchatabstractlistmodel.h index 2894fd65..9fcbcdc1 100644 --- a/app/qtquick/chat/atpchatabstractlistmodel.h +++ b/app/qtquick/chat/atpchatabstractlistmodel.h @@ -23,7 +23,6 @@ class AtpChatAbstractListModel : public QAbstractListModel AtProtocolInterface::AccountData account() const; Q_INVOKABLE void setAccount(const QString &uuid); - void setServiceEndpoint(const QString &service_endpoint); virtual Q_INVOKABLE bool getLatest() = 0; virtual Q_INVOKABLE bool getNext() = 0; diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 833ef663..0f4fc258 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -483,6 +483,12 @@ void AccountManager::updateAccountProfile(const QString &uuid) dList.at(dIndex.value(uuid))->getProfile(); } +void AccountManager::updateServiceEndpoint(const QString &uuid, const QString &service_endpoint) +{ + update(indexAt(uuid), AccountManager::AccountManagerRoles::ServiceEndpointRole, + service_endpoint); +} + int AccountManager::getMainAccountIndex() const { if (dList.isEmpty()) diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index 96f7601b..d7460fe8 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -55,6 +55,7 @@ class AccountManager : public QObject const bool authorized); void removeAccount(const QString &uuid); void updateAccountProfile(const QString &uuid); + void updateServiceEndpoint(const QString &uuid, const QString &service_endpoint); int getMainAccountIndex() const; void setMainAccount(int row); bool checkAllAccountsReady(); diff --git a/tests/chat_test/tst_chat_test.cpp b/tests/chat_test/tst_chat_test.cpp index d9e36e76..b5bc48f6 100644 --- a/tests/chat_test/tst_chat_test.cpp +++ b/tests/chat_test/tst_chat_test.cpp @@ -80,8 +80,8 @@ void chat_test::test_ChatListModel() QString uuid = AccountManager::getInstance()->updateAccount( QString(), m_service + "/list", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); + AccountManager::getInstance()->updateServiceEndpoint(uuid, m_service + "/list"); model.setAccount(uuid); - model.setServiceEndpoint(m_service + "/list"); { QSignalSpy spy(&model, SIGNAL(runningChanged())); @@ -143,8 +143,8 @@ void chat_test::test_ChatMessageListModel() QString uuid = AccountManager::getInstance()->updateAccount( QString(), m_service + "/message/1", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); + AccountManager::getInstance()->updateServiceEndpoint(uuid, m_service + "/message/1"); model.setAccount(uuid); - model.setServiceEndpoint(m_service + "/message/1"); model.setConvoId("3ksrqt7eebs2b"); model.setAutoLoading(false); @@ -270,8 +270,8 @@ void chat_test::test_ChatMessageListModelByMembers() QString uuid = AccountManager::getInstance()->updateAccount( QString(), m_service + "/message/2", "id", "pass", "did:plc:ipj5qejfoqu6eukvt72uhyit", "handle", "email", "accessJwt", "refreshJwt", true); + AccountManager::getInstance()->updateServiceEndpoint(uuid, m_service + "/message/2"); model.setAccount(uuid); - model.setServiceEndpoint(m_service + "/message/2"); model.setMemberDids(QStringList() << "did:plc:mqxsuw5b5rhpwo4lw6iwlid5"); From 6b3a7fa0f60d22a0d4af06f179bc2d3cf114b6a0 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 19:42:49 +0900 Subject: [PATCH 100/127] =?UTF-8?q?tst=5Fhagoromo=5Ftest=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timeline/next/xrpc/app.bsky.feed.getTimeline | 4 +++- tests/hagoromo_test/tst_hagoromo_test.cpp | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/hagoromo_test/response/timeline/next/xrpc/app.bsky.feed.getTimeline b/tests/hagoromo_test/response/timeline/next/xrpc/app.bsky.feed.getTimeline index ce13ba92..86c9ae7f 100644 --- a/tests/hagoromo_test/response/timeline/next/xrpc/app.bsky.feed.getTimeline +++ b/tests/hagoromo_test/response/timeline/next/xrpc/app.bsky.feed.getTimeline @@ -188,7 +188,9 @@ "repostCount": 0, "likeCount": 0, "indexedAt": "2023-05-28T11:30:02.749Z", - "viewer": {}, + "viewer": { + "repost": "at://did:plc:ipj5qejfoqu6eukvt72uhyit/app.bsky.feed.repost/3l5jty5ef3w" + }, "labels": [] }, "reason": { diff --git a/tests/hagoromo_test/tst_hagoromo_test.cpp b/tests/hagoromo_test/tst_hagoromo_test.cpp index a396368e..313cafb2 100644 --- a/tests/hagoromo_test/tst_hagoromo_test.cpp +++ b/tests/hagoromo_test/tst_hagoromo_test.cpp @@ -2743,8 +2743,9 @@ void hagoromo_test::test_NotificationListModel_warn() void hagoromo_test::test_TimelineListModel_next() { QString uuid = AccountManager::getInstance()->updateAccount( - QString(), m_service + "/timeline/next", "id", "pass", "did:plc:test", - "ioriayane.bsky.social", "email", "accessJwt", "refreshJwt", true); + QString(), m_service + "/timeline/next", "id", "pass", + "did:plc:ipj5qejfoqu6eukvt72uhyit", "ioriayane.bsky.social", "email", "accessJwt", + "refreshJwt", true); int row = 0; TimelineListModel model; @@ -3209,19 +3210,19 @@ void hagoromo_test::test_SearchPostListModel_text() SearchPostListModel model; model.setAccount(uuid); - QVERIFY2(model.replaceSearchCommand("from:me") == "from:hogehoge.bsky.sockal", + QVERIFY2(model.replaceSearchCommand("from:me") == "from:hogehoge.bsky.social", model.text().toLocal8Bit()); QVERIFY2(model.replaceSearchCommand("fuga from:me hoge") - == "fuga from:hogehoge.bsky.sockal hoge", + == "fuga from:hogehoge.bsky.social hoge", model.text().toLocal8Bit()); QVERIFY2(model.replaceSearchCommand("fuga\tfrom:me\thoge") - == "fuga from:hogehoge.bsky.sockal hoge", + == "fuga from:hogehoge.bsky.social hoge", model.text().toLocal8Bit()); QVERIFY2(model.replaceSearchCommand(QString("fuga%1from:me%1hoge").arg(QChar(0x3000))) - == "fuga from:hogehoge.bsky.sockal hoge", + == "fuga from:hogehoge.bsky.social hoge", model.text().toLocal8Bit()); } From 4eda6850a34101652b33c92b76a7e58ce26e999b Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 20:13:20 +0900 Subject: [PATCH 101/127] Update tst_hagoromo_test2.cpp --- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 287894fb..50db8956 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -285,6 +285,8 @@ void hagoromo_test::test_FeedGeneratorLink() void hagoromo_test::test_AccountListModel() { + AccountManager::getInstance()->clear(); + QString temp_path = Common::appDataFolder() + "/account.json"; if (QFile::exists(temp_path)) { QFile::remove(temp_path); @@ -391,6 +393,8 @@ void hagoromo_test::test_AccountListModel() QVERIFY2(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool() == true, QString::number(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool()) .toLocal8Bit()); + + // TODO: account: service_endpointの保存 } void hagoromo_test::test_AccountManager() @@ -404,6 +408,7 @@ void hagoromo_test::test_AccountManager() AccountManager *manager = AccountManager::getInstance(); AtProtocolInterface::AccountData account; + manager->clear(); manager->updateAccount(QString(), m_service + "/account/account1", "id1", "password1", "did:plc:account1", "account1.relog.tech", "account1@relog.tech", "accessJwt_account1", "refreshJwt_account1", false); From 70594b0758a8ba3f732198502fc60156545c02b5 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 21:29:00 +0900 Subject: [PATCH 102/127] =?UTF-8?q?service=5Fendpoint=E3=81=AE=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E6=96=B9=E6=B3=95=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 29 +++++++---- tests/hagoromo_test2/hagoromo_test2.qrc | 2 + .../xrpc/com.atproto.repo.describeRepo | 48 +++++++++++++++++++ .../xrpc/com.atproto.repo.describeRepo | 48 +++++++++++++++++++ tests/hagoromo_test2/tst_hagoromo_test2.cpp | 19 ++++++-- 5 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 tests/hagoromo_test2/response/account/account1/xrpc/com.atproto.repo.describeRepo create mode 100644 tests/hagoromo_test2/response/account/account2/xrpc/com.atproto.repo.describeRepo diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 0f4fc258..c964db76 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -4,7 +4,9 @@ #include "extension/com/atproto/server/comatprotoserverrefreshsessionex.h" #include "extension/com/atproto/repo/comatprotorepogetrecordex.h" #include "atprotocol/app/bsky/actor/appbskyactorgetprofile.h" +#include "atprotocol/com/atproto/repo/comatprotorepodescriberepo.h" #include "extension/directory/plc/directoryplc.h" +#include "atprotocol/lexicons_func_unknown.h" #include "tools/pinnedpostcache.h" #include "common.h" @@ -16,6 +18,7 @@ using AtProtocolInterface::AccountData; using AtProtocolInterface::AccountStatus; using AtProtocolInterface::AppBskyActorGetProfile; +using AtProtocolInterface::ComAtprotoRepoDescribeRepo; using AtProtocolInterface::ComAtprotoRepoGetRecordEx; using AtProtocolInterface::ComAtprotoServerCreateSessionEx; using AtProtocolInterface::ComAtprotoServerRefreshSessionEx; @@ -320,21 +323,29 @@ void AccountManager::Private::getServiceEndpoint(const QString &did, const QStri callback(service); return; } - if (!service.startsWith("https://bsky.social")) { - callback(service); - return; - } + // if (!service.startsWith("https://bsky.social")) { + // callback(service); + // return; + // } - DirectoryPlc *plc = new DirectoryPlc(this); - connect(plc, &DirectoryPlc::finished, this, [=](bool success) { + ComAtprotoRepoDescribeRepo *repo = new ComAtprotoRepoDescribeRepo(this); + connect(repo, &ComAtprotoRepoDescribeRepo::finished, this, [=](bool success) { if (success) { - callback(plc->serviceEndpoint()); + AtProtocolType::DirectoryPlcDefs::DidDoc doc = + AtProtocolType::LexiconsTypeUnknown::fromQVariant< + AtProtocolType::DirectoryPlcDefs::DidDoc>(repo->didDoc()); + if (!doc.service.isEmpty()) { + callback(doc.service.first().serviceEndpoint); + } else { + callback(service); + } } else { callback(service); } - plc->deleteLater(); + repo->deleteLater(); }); - plc->directory(did); + repo->setAccount(m_account); + repo->describeRepo(did); } void AccountManager::Private::setMain(bool is) diff --git a/tests/hagoromo_test2/hagoromo_test2.qrc b/tests/hagoromo_test2/hagoromo_test2.qrc index c159b07c..215d3b2b 100644 --- a/tests/hagoromo_test2/hagoromo_test2.qrc +++ b/tests/hagoromo_test2/hagoromo_test2.qrc @@ -33,5 +33,7 @@ data/profile/3.1/com.atproto.repo.putRecord response/profile/3.2/xrpc/com.atproto.repo.getRecord data/profile/3.2/com.atproto.repo.putRecord + response/account/account1/xrpc/com.atproto.repo.describeRepo + response/account/account2/xrpc/com.atproto.repo.describeRepo diff --git a/tests/hagoromo_test2/response/account/account1/xrpc/com.atproto.repo.describeRepo b/tests/hagoromo_test2/response/account/account1/xrpc/com.atproto.repo.describeRepo new file mode 100644 index 00000000..a530cccc --- /dev/null +++ b/tests/hagoromo_test2/response/account/account1/xrpc/com.atproto.repo.describeRepo @@ -0,0 +1,48 @@ +{ + "handle": "account1.relog.tech", + "did": "did:plc:account1", + "didDoc": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:account1", + "alsoKnownAs": [ + "at://account1.relog.tech" + ], + "verificationMethod": [ + { + "id": "did:plc:account1#atproto", + "type": "Multikey", + "controller": "did:plc:account1", + "publicKeyMultibase": "zQ3shYNo9wSChjqSMPZU" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "http://localhost:%1/response/account/account1" + } + ] + }, + "collections": [ + "app.bsky.actor.profile", + "app.bsky.feed.generator", + "app.bsky.feed.like", + "app.bsky.feed.post", + "app.bsky.feed.postgate", + "app.bsky.feed.repost", + "app.bsky.feed.threadgate", + "app.bsky.graph.block", + "app.bsky.graph.follow", + "app.bsky.graph.list", + "app.bsky.graph.listitem", + "app.bsky.graph.starterpack", + "blue.linkat.board", + "chat.bsky.actor.declaration", + "com.whtwnd.blog.entry" + ], + "handleIsCorrect": true +} diff --git a/tests/hagoromo_test2/response/account/account2/xrpc/com.atproto.repo.describeRepo b/tests/hagoromo_test2/response/account/account2/xrpc/com.atproto.repo.describeRepo new file mode 100644 index 00000000..aa7b7778 --- /dev/null +++ b/tests/hagoromo_test2/response/account/account2/xrpc/com.atproto.repo.describeRepo @@ -0,0 +1,48 @@ +{ + "handle": "account2.relog.tech", + "did": "did:plc:account2", + "didDoc": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1" + ], + "id": "did:plc:account2", + "alsoKnownAs": [ + "at://account2.relog.tech" + ], + "verificationMethod": [ + { + "id": "did:plc:account2#atproto", + "type": "Multikey", + "controller": "did:plc:account2", + "publicKeyMultibase": "zQ3shYNo9wSChj" + } + ], + "service": [ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": "http://localhost:%1/response/account/account2" + } + ] + }, + "collections": [ + "app.bsky.actor.profile", + "app.bsky.feed.generator", + "app.bsky.feed.like", + "app.bsky.feed.post", + "app.bsky.feed.postgate", + "app.bsky.feed.repost", + "app.bsky.feed.threadgate", + "app.bsky.graph.block", + "app.bsky.graph.follow", + "app.bsky.graph.list", + "app.bsky.graph.listitem", + "app.bsky.graph.starterpack", + "blue.linkat.board", + "chat.bsky.actor.declaration", + "com.whtwnd.blog.entry" + ], + "handleIsCorrect": true +} diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 50db8956..224042b2 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -393,8 +393,6 @@ void hagoromo_test::test_AccountListModel() QVERIFY2(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool() == true, QString::number(model.item(0, AccountListModel::PostGateQuoteEnabledRole).toBool()) .toLocal8Bit()); - - // TODO: account: service_endpointの保存 } void hagoromo_test::test_AccountManager() @@ -458,11 +456,22 @@ void hagoromo_test::test_AccountManager() QVERIFY(manager->getUuids().isEmpty()); { - QSignalSpy spy(manager, SIGNAL(finished())); + QSignalSpy spy(manager, SIGNAL(updatedAccount(const QString &))); manager->load(); - spy.wait(); - QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + spy.wait(10 * 1000); + spy.wait(10 * 1000); + QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } + + account = manager->getAccount(uuids.at(0)); + QVERIFY(account.service == m_service + "/account/account1"); + QVERIFY(account.service_endpoint == "http://localhost:%1/response/account/account1"); + QVERIFY(account.did == "did:plc:account1_refresh"); + + account = manager->getAccount(uuids.at(1)); + QVERIFY(account.service == m_service + "/account/account2"); + QVERIFY(account.service_endpoint == "http://localhost:%1/response/account/account2"); + QVERIFY(account.did == "did:plc:account2_refresh"); } void hagoromo_test::test_ListsListModel() From 427852988afd6c94a6c10102b7aaa5c11cc04c68 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 21:29:14 +0900 Subject: [PATCH 103/127] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88?= =?UTF-8?q?=E4=B8=80=E8=A6=A7=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/view/ChatListView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/qml/view/ChatListView.qml b/app/qml/view/ChatListView.qml index 5617ce96..52718d04 100644 --- a/app/qml/view/ChatListView.qml +++ b/app/qml/view/ChatListView.qml @@ -39,7 +39,7 @@ Item { function setAccount(uuid) { searchProfileListModel.setAccount(uuid) rootListView.model.setAccount(uuid) - accountDid = rootListView.model.did + accountDid = searchProfileListModel.did } function getLatest() { rootListView.model.getLatest() From 3a7f9d429e80e42de4dbc396465251b9a61d19a7 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 21:38:20 +0900 Subject: [PATCH 104/127] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=B7?= =?UTF-8?q?=E3=82=B0=E3=83=8A=E3=83=AB=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/main.qml | 3 --- app/qtquick/account/accountlistmodel.cpp | 10 ---------- app/qtquick/account/accountlistmodel.h | 1 - lib/tools/accountmanager.cpp | 3 --- lib/tools/accountmanager.h | 1 - 5 files changed, 18 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index fbb71cc8..6a165a4a 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -457,9 +457,6 @@ ApplicationWindow { // アカウント管理で内容が変更されたときにカラムとインデックスの関係が崩れるのでuuidで確認する AccountListModel { id: accountListModel - onUpdatedSession: (uuid) => { - console.log("onUpdatedSession:" + uuid) - } onUpdatedAccount: (uuid) => { console.log("onUpdatedAccount:" + uuid) // カラムを更新しにいく diff --git a/app/qtquick/account/accountlistmodel.cpp b/app/qtquick/account/accountlistmodel.cpp index 955224a4..94084daf 100644 --- a/app/qtquick/account/accountlistmodel.cpp +++ b/app/qtquick/account/accountlistmodel.cpp @@ -56,21 +56,12 @@ AccountListModel::AccountListModel(QObject *parent) : QAbstractListModel { paren AccountManager *manager = AccountManager::getInstance(); connect(manager, &AccountManager::errorOccured, this, &AccountListModel::errorOccured); - connect(manager, &AccountManager::updatedSession, this, &AccountListModel::updatedSession); connect(manager, &AccountManager::updatedAccount, this, &AccountListModel::updatedAccount); connect(manager, &AccountManager::countChanged, this, &AccountListModel::countChanged); connect(manager, &AccountManager::finished, this, &AccountListModel::finished); connect(manager, &AccountManager::allAccountsReadyChanged, this, &AccountListModel::allAccountsReadyChanged); - // 各カラムがアカウント情報をAccountManagerから取得するようになれば↓の2つをQMLへ投げる必要はなくなる - // dataChangedは必要 - connect(this, &AccountListModel::updatedSession, this, [=](const QString &uuid) { - int row = manager->indexAt(uuid); - if (row >= 0) { - emit dataChanged(index(row), index(row)); - } - }); connect(this, &AccountListModel::updatedAccount, this, [=](const QString &uuid) { int row = manager->indexAt(uuid); if (row >= 0) { @@ -84,7 +75,6 @@ AccountListModel::~AccountListModel() AccountManager *manager = AccountManager::getInstance(); disconnect(manager, &AccountManager::errorOccured, this, &AccountListModel::errorOccured); - disconnect(manager, &AccountManager::updatedSession, this, &AccountListModel::updatedSession); disconnect(manager, &AccountManager::updatedAccount, this, &AccountListModel::updatedAccount); disconnect(manager, &AccountManager::countChanged, this, &AccountListModel::countChanged); disconnect(manager, &AccountManager::finished, this, &AccountListModel::finished); diff --git a/app/qtquick/account/accountlistmodel.h b/app/qtquick/account/accountlistmodel.h index b77bacc1..8093ee54 100644 --- a/app/qtquick/account/accountlistmodel.h +++ b/app/qtquick/account/accountlistmodel.h @@ -75,7 +75,6 @@ class AccountListModel : public QAbstractListModel signals: void errorOccured(const QString &code, const QString &message); - void updatedSession(const QString &uuid); void updatedAccount(const QString &uuid); void countChanged(); void finished(); diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index c964db76..acc298ad 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -222,8 +222,6 @@ void AccountManager::Private::createSession() m_account.refreshJwt = session->refreshJwt(); m_account.status = AccountStatus::Authorized; - emit q->updatedSession(m_account.uuid); - // 詳細を取得 getProfile(); } else { @@ -269,7 +267,6 @@ void AccountManager::Private::refreshSession(bool initial) emit q->errorOccured(session->errorCode(), session->errorMessage()); } } - emit q->updatedSession(m_account.uuid); q->checkAllAccountsReady(); if (q->allAccountTried()) { diff --git a/lib/tools/accountmanager.h b/lib/tools/accountmanager.h index d7460fe8..e68be5dd 100644 --- a/lib/tools/accountmanager.h +++ b/lib/tools/accountmanager.h @@ -73,7 +73,6 @@ class AccountManager : public QObject signals: void errorOccured(const QString &code, const QString &message); - void updatedSession(const QString &uuid); void updatedAccount(const QString &uuid); void countChanged(); void finished(); From c791b811df0f4872eedf696d99537a9d3368185a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 22:02:26 +0900 Subject: [PATCH 105/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=AE=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E5=AE=8C?= =?UTF-8?q?=E4=BA=86=E3=81=AE=E5=88=A4=E5=AE=9A=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index acc298ad..6cc7ed47 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -220,7 +220,6 @@ void AccountManager::Private::createSession() m_account.email = session->email(); m_account.accessJwt = session->accessJwt(); m_account.refreshJwt = session->refreshJwt(); - m_account.status = AccountStatus::Authorized; // 詳細を取得 getProfile(); @@ -228,10 +227,11 @@ void AccountManager::Private::createSession() qDebug() << "Fail createSession."; m_account.status = AccountStatus::Unauthorized; emit q->errorOccured(session->errorCode(), session->errorMessage()); - } - q->checkAllAccountsReady(); - if (q->allAccountTried()) { - emit q->finished(); + + q->checkAllAccountsReady(); + if (q->allAccountTried()) { + emit q->finished(); + } } session->deleteLater(); }); @@ -252,7 +252,6 @@ void AccountManager::Private::refreshSession(bool initial) m_account.email = session->email(); m_account.accessJwt = session->accessJwt(); m_account.refreshJwt = session->refreshJwt(); - m_account.status = AccountStatus::Authorized; // 詳細を取得 getProfile(); @@ -265,12 +264,12 @@ void AccountManager::Private::refreshSession(bool initial) } else { m_account.status = AccountStatus::Unauthorized; emit q->errorOccured(session->errorCode(), session->errorMessage()); - } - } - q->checkAllAccountsReady(); - if (q->allAccountTried()) { - emit q->finished(); + q->checkAllAccountsReady(); + if (q->allAccountTried()) { + emit q->finished(); + } + } } session->deleteLater(); }); @@ -296,6 +295,7 @@ void AccountManager::Private::getProfile() m_account.description = detail.description; m_account.avatar = detail.avatar; m_account.banner = detail.banner; + m_account.status = AccountStatus::Authorized; q->save(); @@ -306,6 +306,12 @@ void AccountManager::Private::getProfile() } else { emit q->errorOccured(profile->errorCode(), profile->errorMessage()); } + + q->checkAllAccountsReady(); + if (q->allAccountTried()) { + emit q->finished(); + } + profile->deleteLater(); }); profile->setAccount(m_account); From b90c00894e279b153459629838167a297e27eef9 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sat, 19 Oct 2024 23:47:57 +0900 Subject: [PATCH 106/127] Update tst_hagoromo_test2.cpp --- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 224042b2..22bdc875 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -305,7 +305,7 @@ void hagoromo_test::test_AccountListModel() { QSignalSpy spy(&model2, SIGNAL(finished())); model2.load(); - spy.wait(); + spy.wait(15 * 1000); QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } QVERIFY2(model2.rowCount() == 2, QString::number(model2.rowCount()).toLocal8Bit()); From 9c47ab779be3ddd7681009870dfa44b19b3e1ec4 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 00:19:12 +0900 Subject: [PATCH 107/127] update workflow --- .github/workflows/dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 10642d06..0d7087e3 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -29,7 +29,7 @@ jobs: - name: Setup run: | qmake --version - cp app/qtquick/encryption_seed_template.h app/qtquick/encryption_seed.h + cp lib/tools/encryption_seed_template.h lib/tools/encryption_seed.h python -m pip install --upgrade pip jinja2 - name: Check style uses: pre-commit/action@v3.0.1 From 5cb0dcca7b83444c1a9431b3305867a39fd85ad9 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 00:19:22 +0900 Subject: [PATCH 108/127] Update README --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ebd55536..0e0074f8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ REM checkout repo >cd Hagoromo >git submodule update -i REM copy and edit encryption seed ->copy app\qtquick\encryption_seed_template.h app\qtquick\encryption_seed.h +>copy lib\tools\encryption_seed_template.h lib\tools\encryption_seed.h REM build Hagoromo >.\scripts\build.bat path\to\Qt\5.15.2\msvc2019_64\bin REM Execute @@ -73,8 +73,8 @@ $ git submodule update -i # setup dependent modules $ sudo apt-get install zlib1g-dev # copy and edit encryption seed -$ cp app/qtquick/encryption_seed_template.h app/qtquick/encryption_seed.h -$ vi app/qtquick/encryption_seed.h +$ cp lib/tools/encryption_seed_template.h lib/tools/encryption_seed.h +$ vi lib/tools/encryption_seed.h # build Hagoromo $ ./scripts/build.sh linux path/to/Qt/5.15.2/gcc_64/bin # exec hagoromo @@ -91,8 +91,8 @@ $ git clone git@github.com:ioriayane/Hagoromo.git $ cd Hagoromo $ git submodule update -i # copy and edit encryption seed -$ cp app/qtquick/encryption_seed_template.h app/qtquick/encryption_seed.h -$ vi app/qtquick/encryption_seed.h +$ cp lib/tools/encryption_seed_template.h lib/tools/encryption_seed.h +$ vi lib/tools/encryption_seed.h # build Hagoromo $ ./scripts/build.sh mac path/to/Qt/5.15.2/gcc_64/bin # exec hagoromo @@ -208,7 +208,7 @@ REM checkout repo >cd Hangoromo >git submodule update -i REM copy and edit encryption seed ->copy app\qtquick\encryption_seed_template.h app\qtquick\encryption_seed.h +>copy lib\tools\encryption_seed_template.h lib\tools\encryption_seed.h REM build Hagoromo >.\scripts\build.bat path\to\Qt\5.15.2\msvc2019_64\bin REM Execute @@ -227,8 +227,8 @@ $ git submodule update -i # setup dependent modules $ sudo apt-get install zlib1g-dev # copy and edit encryption seed -$ cp app/qtquick/encryption_seed_template.h app/qtquick/encryption_seed.h -$ vi app/qtquick/encryption_seed.h +$ cp lib/tools/encryption_seed_template.h lib/tools/encryption_seed.h +$ vi lib/tools/encryption_seed.h # build Hagoromo $ ./scripts/build.sh linux path/to/Qt/5.15.2/gcc_64/bin # exec hagoromo @@ -245,8 +245,8 @@ $ git clone git@github.com:ioriayane/Hagoromo.git $ cd Hagoromo $ git submodule update -i # copy and edit encryption seed -$ cp app/qtquick/encryption_seed_template.h app/qtquick/encryption_seed.h -$ vi app/qtquick/encryption_seed.h +$ cp lib/tools/encryption_seed_template.h lib/tools/encryption_seed.h +$ vi lib/tools/encryption_seed.h # build Hagoromo $ ./scripts/build.sh mac path/to/Qt/5.15.2/gcc_64/bin # exec hagoromo From d21ee839f51c7ca07324fb53771411866b44e9a3 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 00:52:00 +0900 Subject: [PATCH 109/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=8C=E7=84=A1=E3=81=84=E3=81=A8=E3=81=8D=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/account/accountlistmodel.cpp | 26 ++++++++++++++++-------- app/qtquick/account/accountlistmodel.h | 1 - 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/qtquick/account/accountlistmodel.cpp b/app/qtquick/account/accountlistmodel.cpp index 94084daf..90374719 100644 --- a/app/qtquick/account/accountlistmodel.cpp +++ b/app/qtquick/account/accountlistmodel.cpp @@ -64,7 +64,7 @@ AccountListModel::AccountListModel(QObject *parent) : QAbstractListModel { paren connect(this, &AccountListModel::updatedAccount, this, [=](const QString &uuid) { int row = manager->indexAt(uuid); - if (row >= 0) { + if (row < 0 || row >= count()) { emit dataChanged(index(row), index(row)); } }); @@ -150,6 +150,9 @@ QVariant AccountListModel::item(int row, AccountListModelRoles role) const void AccountListModel::update(int row, AccountListModelRoles role, const QVariant &value) { + if (row < 0 || row >= count()) + return; + AccountManager::getInstance()->update( row, m_roleTo.value(role, AccountManager::AccountManagerRoles::UnknownRole), value); @@ -188,10 +191,11 @@ void AccountListModel::updateAccount(const QString &service, const QString &iden void AccountListModel::removeAccount(int row) { - AccountManager *manager = AccountManager::getInstance(); - if (row < 0 || row >= manager->count()) + if (row < 0 || row >= count()) return; + AccountManager *manager = AccountManager::getInstance(); + beginRemoveRows(QModelIndex(), row, row); manager->removeAccount(manager->getUuid(row)); endRemoveRows(); @@ -220,15 +224,17 @@ int AccountListModel::getMainAccountIndex() const void AccountListModel::setMainAccount(int row) { - AccountManager::getInstance()->setMainAccount(row); + if (row < 0 || row >= count()) + return; + AccountManager::getInstance()->setMainAccount(row); emit dataChanged(index(0), index(AccountManager::getInstance()->count() - 1)); } void AccountListModel::refreshAccountSession(const QString &uuid) { int row = indexAt(uuid); - if (row < 0) + if (row < 0 || row >= count()) return; refreshSession(row); } @@ -236,7 +242,7 @@ void AccountListModel::refreshAccountSession(const QString &uuid) void AccountListModel::refreshAccountProfile(const QString &uuid) { int row = indexAt(uuid); - if (row < 0) + if (row < 0 || row >= count()) return; getProfile(row); } @@ -251,14 +257,16 @@ void AccountListModel::load() AccountManager *manager = AccountManager::getInstance(); if (manager->count() > 0) { - beginRemoveRows(QModelIndex(), 0, count() - 1); + beginRemoveRows(QModelIndex(), 0, manager->count() - 1); manager->clear(); endRemoveRows(); } manager->load(); - beginInsertRows(QModelIndex(), 0, manager->count() - 1); - endInsertRows(); + if (manager->count() > 0) { + beginInsertRows(QModelIndex(), 0, manager->count() - 1); + endInsertRows(); + } } QVariant AccountListModel::account(int row) const diff --git a/app/qtquick/account/accountlistmodel.h b/app/qtquick/account/accountlistmodel.h index 8093ee54..9901498f 100644 --- a/app/qtquick/account/accountlistmodel.h +++ b/app/qtquick/account/accountlistmodel.h @@ -85,7 +85,6 @@ class AccountListModel : public QAbstractListModel QHash roleNames() const; private: - QList m_accountList; QVariant m_accountTemp; QTimer m_timer; Encryption m_encryption; From 06cf8636b4049f1b3a4fee16c76e62f0a9480faa Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 00:52:21 +0900 Subject: [PATCH 110/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E6=83=85=E5=A0=B1=E3=81=AE=E4=BF=9D=E5=AD=98=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=9F=E3=83=B3=E3=82=B0=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/accountmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tools/accountmanager.cpp b/lib/tools/accountmanager.cpp index 6cc7ed47..c464698b 100644 --- a/lib/tools/accountmanager.cpp +++ b/lib/tools/accountmanager.cpp @@ -485,6 +485,7 @@ void AccountManager::removeAccount(const QString &uuid) dIndex[dList.at(i)->getAccount().uuid] = i; } + save(); emit countChanged(); checkAllAccountsReady(); } From cba3dbdd9709b18d613c80ec72ccc674dfa5821a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 01:12:52 +0900 Subject: [PATCH 111/127] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=AE=E3=83=AA=E3=82=B9=E3=83=88=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E3=82=B7=E3=82=B0=E3=83=8A=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/account/accountlistmodel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/qtquick/account/accountlistmodel.cpp b/app/qtquick/account/accountlistmodel.cpp index 90374719..9bacb9dd 100644 --- a/app/qtquick/account/accountlistmodel.cpp +++ b/app/qtquick/account/accountlistmodel.cpp @@ -64,7 +64,8 @@ AccountListModel::AccountListModel(QObject *parent) : QAbstractListModel { paren connect(this, &AccountListModel::updatedAccount, this, [=](const QString &uuid) { int row = manager->indexAt(uuid); - if (row < 0 || row >= count()) { + qDebug().noquote() << "AccountListModel::updatedAccount:" << uuid << row; + if (row >= 0 && row < count()) { emit dataChanged(index(row), index(row)); } }); From 4381858b251c98795682c2756f0d63e89aaf001a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 01:23:54 +0900 Subject: [PATCH 112/127] =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/main.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 6a165a4a..48d52aed 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -464,7 +464,10 @@ ApplicationWindow { } onFinished: { console.log("onFinished:" + allAccountsReady + ", count=" + columnManageModel.rowCount()) - if(rowCount() === 0){ + globalProgressFrame.text = "" + if(accountDialog.visible === true){ + // ダイアログが開いているときはアカウント追加のたびに呼ばれるので何もしない + }else if(rowCount() === 0){ accountDialog.open() }else if(columnManageModel.rowCount() === 0){ if(allAccountsReady){ @@ -1070,7 +1073,7 @@ ApplicationWindow { BusyIndicator { Layout.preferredWidth: AdjustedValues.i24 Layout.preferredHeight: AdjustedValues.i24 - running: globalProgressFrame.visible + // running: globalProgressFrame.visible } Label { font.pointSize: AdjustedValues.f10 @@ -1086,7 +1089,7 @@ ApplicationWindow { } Component.onCompleted: { - if(accountListModel.count > 0){ + if(accountListModel.count === 0){ globalProgressFrame.text = qsTr("Loading account(s) ...") } accountListModel.load() From 50e39dc041a972f6faa441052659ba881b3513a8 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 02:15:34 +0900 Subject: [PATCH 113/127] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/hagoromo_test2/tst_hagoromo_test2.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 22bdc875..67e6aecf 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -456,20 +456,17 @@ void hagoromo_test::test_AccountManager() QVERIFY(manager->getUuids().isEmpty()); { - QSignalSpy spy(manager, SIGNAL(updatedAccount(const QString &))); + QSignalSpy spy(manager, SIGNAL(finished())); manager->load(); spy.wait(10 * 1000); - spy.wait(10 * 1000); - QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); + QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } - account = manager->getAccount(uuids.at(0)); - QVERIFY(account.service == m_service + "/account/account1"); - QVERIFY(account.service_endpoint == "http://localhost:%1/response/account/account1"); - QVERIFY(account.did == "did:plc:account1_refresh"); + uuids = manager->getUuids(); + QVERIFY(manager->count() == 1); - account = manager->getAccount(uuids.at(1)); - QVERIFY(account.service == m_service + "/account/account2"); + account = manager->getAccount(uuids.at(0)); + QVERIFY2(account.service == m_service + "/account/account2", account.service.toLocal8Bit()); QVERIFY(account.service_endpoint == "http://localhost:%1/response/account/account2"); QVERIFY(account.did == "did:plc:account2_refresh"); } From cfa2de530fcbd60ffeab269f9582aba2555ccc05 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 11:17:36 +0900 Subject: [PATCH 114/127] =?UTF-8?q?linkat=E3=81=AELexicon=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/atprotocol/lexicons.h | 9 +++++---- lib/atprotocol/lexicons_func.cpp | 7 ++++--- lib/atprotocol/lexicons_func.h | 2 +- scripts/lexicons/blue.linkat.board.json | 7 ++++--- scripts/lexicons/blue.linkat.defs.json | 16 +++++++++------- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index 2a837f89..afc82eb3 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -1983,10 +1983,11 @@ struct Member // blue.linkat.defs namespace BlueLinkatDefs { -struct LinkItem +struct Card { - QString url; - QString text; + QString url; // URL of the link + QString text; // Text of the card + QString emoji; // Emoji of the card }; } @@ -1994,7 +1995,7 @@ struct LinkItem namespace BlueLinkatBoard { struct Main { - QList cards; + QList cards; }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 7a694bc2..4f35dfa1 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -2801,11 +2801,12 @@ void copyMember(const QJsonObject &src, ToolsOzoneTeamDefs::Member &dest) } // blue.linkat.defs namespace BlueLinkatDefs { -void copyLinkItem(const QJsonObject &src, BlueLinkatDefs::LinkItem &dest) +void copyCard(const QJsonObject &src, BlueLinkatDefs::Card &dest) { if (!src.isEmpty()) { dest.url = src.value("url").toString(); dest.text = src.value("text").toString(); + dest.emoji = src.value("emoji").toString(); } } } @@ -2815,8 +2816,8 @@ void copyMain(const QJsonObject &src, BlueLinkatBoard::Main &dest) { if (!src.isEmpty()) { for (const auto &s : src.value("cards").toArray()) { - BlueLinkatDefs::LinkItem child; - BlueLinkatDefs::copyLinkItem(s.toObject(), child); + BlueLinkatDefs::Card child; + BlueLinkatDefs::copyCard(s.toObject(), child); dest.cards.append(child); } } diff --git a/lib/atprotocol/lexicons_func.h b/lib/atprotocol/lexicons_func.h index f849339d..0465cb28 100644 --- a/lib/atprotocol/lexicons_func.h +++ b/lib/atprotocol/lexicons_func.h @@ -397,7 +397,7 @@ void copyMember(const QJsonObject &src, ToolsOzoneTeamDefs::Member &dest); } // blue.linkat.defs namespace BlueLinkatDefs { -void copyLinkItem(const QJsonObject &src, BlueLinkatDefs::LinkItem &dest); +void copyCard(const QJsonObject &src, BlueLinkatDefs::Card &dest); } // blue.linkat.board namespace BlueLinkatBoard { diff --git a/scripts/lexicons/blue.linkat.board.json b/scripts/lexicons/blue.linkat.board.json index 6ff55114..39e6372d 100644 --- a/scripts/lexicons/blue.linkat.board.json +++ b/scripts/lexicons/blue.linkat.board.json @@ -4,8 +4,8 @@ "defs": { "main": { "type": "record", - "description": "A declaration of links.", - "key": "tid", + "description": "Record containing a cards of your profile.", + "key": "literal:self", "record": { "type": "object", "required": [ @@ -14,9 +14,10 @@ "properties": { "cards": { "type": "array", + "description": "List of cards in the board.", "items": { "type": "ref", - "ref": "blue.linkat.defs#linkItem" + "ref": "blue.linkat.defs#card" } } } diff --git a/scripts/lexicons/blue.linkat.defs.json b/scripts/lexicons/blue.linkat.defs.json index b3ce3e82..1ce6714b 100644 --- a/scripts/lexicons/blue.linkat.defs.json +++ b/scripts/lexicons/blue.linkat.defs.json @@ -2,18 +2,20 @@ "lexicon": 1, "id": "blue.linkat.defs", "defs": { - "linkItem": { + "card": { "type": "object", - "required": [ - "url", - "text" - ], "properties": { "url": { - "type": "string" + "type": "string", + "description": "URL of the link" }, "text": { - "type": "string" + "type": "string", + "description": "Text of the card" + }, + "emoji": { + "type": "string", + "description": "Emoji of the card" } } } From 2e422dab4ad929a2b703084a27d218c7e5253398 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 13:00:00 +0900 Subject: [PATCH 115/127] =?UTF-8?q?via=E3=81=AE=E5=9F=8B=E3=82=81=E8=BE=BC?= =?UTF-8?q?=E3=81=BF=E5=BD=A2=E5=BC=8F=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/atpabstractlistmodel.cpp | 7 ++- lib/atprotocol/lexicons.h | 3 +- lib/atprotocol/lexicons_func.cpp | 1 + .../repo/comatprotorepocreaterecordex.cpp | 2 +- scripts/defs2struct.py | 49 ++++++++++--------- scripts/lexicons/app.bsky.feed.post.json | 4 ++ web/content/docs/release-note.en.md | 2 + web/content/docs/release-note.ja.md | 2 + 8 files changed, 44 insertions(+), 26 deletions(-) diff --git a/app/qtquick/atpabstractlistmodel.cpp b/app/qtquick/atpabstractlistmodel.cpp index d15aa684..9000886b 100644 --- a/app/qtquick/atpabstractlistmodel.cpp +++ b/app/qtquick/atpabstractlistmodel.cpp @@ -414,7 +414,12 @@ QStringList AtpAbstractListModel::getLaunguages(const QVariant &record) const QString AtpAbstractListModel::getVia(const QVariant &record) const { - return LexiconsTypeUnknown::fromQVariant(record).via; + AppBskyFeedPost::Main post = LexiconsTypeUnknown::fromQVariant(record); + if (!post.space_aoisora_post_via.isEmpty()) { + return post.space_aoisora_post_via; + } else { + return post.via; + } } QVariant AtpAbstractListModel::getQuoteItem(const AtProtocolType::AppBskyFeedDefs::PostView &post, diff --git a/lib/atprotocol/lexicons.h b/lib/atprotocol/lexicons.h index afc82eb3..e85fe0d8 100644 --- a/lib/atprotocol/lexicons.h +++ b/lib/atprotocol/lexicons.h @@ -990,7 +990,8 @@ struct Main tags; // Additional hashtags, in addition to any included in post text and facets. QString createdAt; // datetime , Client-declared timestamp when this post was originally // created. - QString via; // client name(Unofficial field) + QString via; // client name(Unofficial field) old + QString space_aoisora_post_via; // client name(Unofficial field) }; } diff --git a/lib/atprotocol/lexicons_func.cpp b/lib/atprotocol/lexicons_func.cpp index 4f35dfa1..85825db7 100644 --- a/lib/atprotocol/lexicons_func.cpp +++ b/lib/atprotocol/lexicons_func.cpp @@ -1428,6 +1428,7 @@ void copyMain(const QJsonObject &src, AppBskyFeedPost::Main &dest) } dest.createdAt = src.value("createdAt").toString(); dest.via = src.value("via").toString(); + dest.space_aoisora_post_via = src.value("space.aoisora.post.via").toString(); } } } diff --git a/lib/extension/com/atproto/repo/comatprotorepocreaterecordex.cpp b/lib/extension/com/atproto/repo/comatprotorepocreaterecordex.cpp index fcc359c2..2d251a6d 100644 --- a/lib/extension/com/atproto/repo/comatprotorepocreaterecordex.cpp +++ b/lib/extension/com/atproto/repo/comatprotorepocreaterecordex.cpp @@ -28,7 +28,7 @@ void ComAtprotoRepoCreateRecordEx::post(const QString &text) QJsonObject json_record; json_record.insert("text", text); json_record.insert("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs)); - json_record.insert("via", "Hagoromo"); + json_record.insert("space.aoisora.post.via", "Hagoromo"); if (!m_postLanguages.isEmpty()) { QJsonArray json_langs; for (const auto &lang : qAsConst(m_postLanguages)) { diff --git a/scripts/defs2struct.py b/scripts/defs2struct.py index f727d046..54312f77 100644 --- a/scripts/defs2struct.py +++ b/scripts/defs2struct.py @@ -251,6 +251,9 @@ def to_namespace_style(self, name: str) -> str: # app.bsky.embed.recordWithMediaがあるのでcapitalize()は使えない return ''.join(dest) + def to_property_style(self, name: str) -> str: + return name.replace('.', '_') + def to_header_path(self, namespace: str) -> str: srcs = namespace.split('.') dest = ['atprotocol'] @@ -540,13 +543,13 @@ def output_type(self, namespace: str, type_name: str, obj: dict): (temp_pointer, temp_list_pointer, temp_enum) = self.output_union(namespace, type_name, property_name, properties[property_name].get('refs', []), p_comment) enum_text.extend(temp_enum) elif p_type == 'unknown': - self.output_text[namespace].append(' QVariant %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' QVariant %s;%s' % (self.to_property_style(property_name), p_comment, )) elif p_type == 'integer': - self.output_text[namespace].append(' int %s = 0;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' int %s = 0;%s' % (self.to_property_style(property_name), p_comment, )) elif p_type == 'boolean': - self.output_text[namespace].append(' bool %s = false;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' bool %s = false;%s' % (self.to_property_style(property_name), p_comment, )) elif p_type == 'string': - self.output_text[namespace].append(' QString %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' QString %s;%s' % (self.to_property_style(property_name), p_comment, )) elif p_type == 'array': items_type = properties[property_name].get('items', {}).get('type', '') if items_type == 'ref': @@ -555,13 +558,13 @@ def output_type(self, namespace: str, type_name: str, obj: dict): (temp_pointer, temp_list_pointer, temp_enum) = self.output_union(namespace, type_name, property_name, properties[property_name].get('items', {}).get('refs', []), p_comment, True) enum_text.extend(temp_enum) elif items_type == 'integer': - self.output_text[namespace].append(' QList %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' QList %s;%s' % (self.to_property_style(property_name), p_comment, )) elif items_type == 'boolean': - self.output_text[namespace].append(' QList %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' QList %s;%s' % (self.to_property_style(property_name), p_comment, )) elif items_type == 'string': - self.output_text[namespace].append(' QList %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' QList %s;%s' % (self.to_property_style(property_name), p_comment, )) elif p_type == 'blob': - self.output_text[namespace].append(' Blob %s;%s' % (property_name, p_comment, )) + self.output_text[namespace].append(' Blob %s;%s' % (self.to_property_style(property_name), p_comment, )) self.output_text[namespace].append('};') @@ -691,16 +694,16 @@ def output_function(self, namespace: str, type_name: str, obj: dict): extend_ns = '%s::' % (self.to_namespace_style(ref_namespace), ) forward_type = self.history_type[ref_namespace + '#' + ref_type_name] if self.check_pointer(namespace, type_name, property_name, ref_namespace, ref_type_name): - self.output_func_text[namespace].append(' if (dest.%s.isNull())' % (property_name, )) - self.output_func_text[namespace].append(' dest.%s = QSharedPointer<%s%s>(new %s%s());' % (property_name, extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), )) - self.output_func_text[namespace].append(' %scopy%s(src.value("%s").toObject(), *dest.%s);' % (extend_ns, self.to_struct_style(ref_type_name), property_name, property_name, )) + self.output_func_text[namespace].append(' if (dest.%s.isNull())' % (self.to_property_style(property_name), )) + self.output_func_text[namespace].append(' dest.%s = QSharedPointer<%s%s>(new %s%s());' % (self.to_property_style(property_name), extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), )) + self.output_func_text[namespace].append(' %scopy%s(src.value("%s").toObject(), *dest.%s);' % (extend_ns, self.to_struct_style(ref_type_name), property_name, self.to_property_style(property_name), )) else: if forward_type in ['integer', 'string', 'boolean']: convert_method = '' else: convert_method = '.toObject()' self.output_func_text[namespace].append(' %scopy%s(src.value("%s")%s, dest.%s);' % ( - extend_ns, self.to_struct_style(ref_type_name), property_name, convert_method, property_name,)) + extend_ns, self.to_struct_style(ref_type_name), property_name, convert_method, self.to_property_style(property_name),)) elif p_type == 'union': value_key: str = '$type' @@ -744,16 +747,16 @@ def output_function(self, namespace: str, type_name: str, obj: dict): self.output_func_text[namespace].append(' }') elif p_type == 'unknown': - self.output_func_text[namespace].append(' LexiconsTypeUnknown::copyUnknown(src.value("%s").toObject(), dest.%s);' % (property_name, property_name, )) + self.output_func_text[namespace].append(' LexiconsTypeUnknown::copyUnknown(src.value("%s").toObject(), dest.%s);' % (property_name, self.to_property_style(property_name), )) elif p_type == 'integer': - self.output_func_text[namespace].append(' dest.%s = src.value("%s").toInt();' % (property_name, property_name, )) + self.output_func_text[namespace].append(' dest.%s = src.value("%s").toInt();' % (self.to_property_style(property_name), property_name, )) elif p_type == 'boolean': - self.output_func_text[namespace].append(' dest.%s = src.value("%s").toBool();' % (property_name, property_name, )) + self.output_func_text[namespace].append(' dest.%s = src.value("%s").toBool();' % (self.to_property_style(property_name), property_name, )) elif p_type == 'string': - self.output_func_text[namespace].append(' dest.%s = src.value("%s").toString();' % (property_name, property_name, )) + self.output_func_text[namespace].append(' dest.%s = src.value("%s").toString();' % (self.to_property_style(property_name), property_name, )) elif p_type == 'array': items_type = properties[property_name].get('items', {}).get('type', '') @@ -770,14 +773,14 @@ def output_function(self, namespace: str, type_name: str, obj: dict): if self.check_pointer(namespace, type_name, property_name, ref_namespace, ref_type_name): self.output_func_text[namespace].append(' QSharedPointer<%s%s> child = QSharedPointer<%s%s>(new %s%s());' % (extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), )) self.output_func_text[namespace].append(' %s(s.toObject(), *child);' % (func_name, )) - self.output_func_text[namespace].append(' dest.%s.append(child);' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(child);' % (self.to_property_style(property_name), )) else: self.output_func_text[namespace].append(' %s%s child;' % (extend_ns, self.to_struct_style(ref_type_name), )) if self.check_object(ref_namespace, 'copy%s' % (self.to_struct_style(ref_type_name), )): self.output_func_text[namespace].append(' %s(s.toObject(), child);' % (func_name, )) else: self.output_func_text[namespace].append(' %s(s, child);' % (func_name, )) - self.output_func_text[namespace].append(' dest.%s.append(child);' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(child);' % (self.to_property_style(property_name), )) self.output_func_text[namespace].append(' }') elif items_type == 'union': @@ -817,20 +820,20 @@ def output_function(self, namespace: str, type_name: str, obj: dict): elif items_type == 'integer': self.output_func_text[namespace].append(' for (const auto &value : src.value("%s").toArray()) {' % (property_name, )) - self.output_func_text[namespace].append(' dest.%s.append(value.toInt());' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(value.toInt());' % (self.to_property_style(property_name), )) self.output_func_text[namespace].append(' }') elif items_type == 'boolean': self.output_func_text[namespace].append(' for (const auto &value : src.value("%s").toArray()) {' % (property_name, )) - self.output_func_text[namespace].append(' dest.%s.append(value.toBool());' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(value.toBool());' % (self.to_property_style(property_name), )) self.output_func_text[namespace].append(' }') elif items_type == 'string': self.output_func_text[namespace].append(' for (const auto &value : src.value("%s").toArray()) {' % (property_name, )) - self.output_func_text[namespace].append(' dest.%s.append(value.toString());' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(value.toString());' % (self.to_property_style(property_name), )) self.output_func_text[namespace].append(' }') elif p_type == 'blob': - self.output_func_text[namespace].append(' LexiconsTypeUnknown::copyBlob(src.value("%s").toObject(), dest.%s);' % (property_name, property_name, )) + self.output_func_text[namespace].append(' LexiconsTypeUnknown::copyBlob(src.value("%s").toObject(), dest.%s);' % (property_name, self.to_property_style(property_name), )) # self.output_text[namespace].append(' Blob %s;' % (property_name, )) self.output_func_text[namespace].append(' }') @@ -937,7 +940,7 @@ def output_function(self, namespace: str, type_name: str, obj: dict): if self.check_pointer(namespace, type_name, '', ref_namespace, ref_type_name): self.output_func_text[namespace].append(' QSharedPointer<%s%s> child = QSharedPointer<%s%s>(new %s%s());' % (extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), extend_ns, self.to_struct_style(ref_type_name), )) self.output_func_text[namespace].append(' %s(s.toObject(), *child);' % (func_name, )) - self.output_func_text[namespace].append(' dest.%s.append(child);' % (property_name, )) + self.output_func_text[namespace].append(' dest.%s.append(child);' % (self.to_property_style(property_name), )) else: self.output_func_text[namespace].append(' %s%s child;' % (extend_ns, self.to_struct_style(ref_type_name), )) if self.check_object(ref_namespace, 'copy%s' % (self.to_struct_style(ref_type_name), )): diff --git a/scripts/lexicons/app.bsky.feed.post.json b/scripts/lexicons/app.bsky.feed.post.json index 6416c5f6..c1c4c808 100644 --- a/scripts/lexicons/app.bsky.feed.post.json +++ b/scripts/lexicons/app.bsky.feed.post.json @@ -4,6 +4,10 @@ "record": { "properties": { "via": { + "type": "string", + "format": "client name(Unofficial field) old" + }, + "space.aoisora.post.via": { "type": "string", "format": "client name(Unofficial field)" } diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index 3f069237..8f7ae7ec 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -10,6 +10,8 @@ description: This is a multi-column Bluesky client. - Add - Add a link to user's profile if user is registered with Linkat +- Update + - Change the format of the embedded via in the post ### v0.39.0 - 2024/10/12 diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index d368662e..88796dba 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -10,6 +10,8 @@ description: マルチカラム対応Blueskyクライアント - 追加 - Linkatに登録があるときリンクをプロフィールに追加 +- 変更 + - ポストに埋め込むviaの形式を変更 ### v0.39.0 - 2024/10/12 From 949c3e54c7dd416273ee4e3325d40b409c2cb8fa Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 17:42:53 +0900 Subject: [PATCH 116/127] =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qml/main.qml | 17 ----------------- app/qml/parts/MentionSuggestionView.qml | 4 ++-- app/qml/view/ColumnView.qml | 10 ---------- app/qml/view/ListDetailView.qml | 4 +--- app/qtquick/operation/recordoperator.cpp | 7 ++++++- app/qtquick/operation/recordoperator.h | 5 +++-- 6 files changed, 12 insertions(+), 35 deletions(-) diff --git a/app/qml/main.qml b/app/qml/main.qml index 48d52aed..8c017d86 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -457,11 +457,6 @@ ApplicationWindow { // アカウント管理で内容が変更されたときにカラムとインデックスの関係が崩れるのでuuidで確認する AccountListModel { id: accountListModel - onUpdatedAccount: (uuid) => { - console.log("onUpdatedAccount:" + uuid) - // カラムを更新しにいく - repeater.updateAccount(uuid) - } onFinished: { console.log("onFinished:" + allAccountsReady + ", count=" + columnManageModel.rowCount()) globalProgressFrame.text = "" @@ -878,18 +873,6 @@ ApplicationWindow { scrollView.contentWidth = max_w } - function updateAccount(account_uuid){ - for(var i=0; i { if(success){ listDetailView.back() @@ -53,7 +52,6 @@ ColumnLayout { function setAccount(uuid) { listItemListModel.setAccount(uuid) recordOperator.setAccount(uuid) - recordOperator.accountHandle = handle } function getLatest() { listItemListModel.getLatest() @@ -232,7 +230,7 @@ ColumnLayout { id: listItemListModel autoLoading: false uri: listDetailView.listUri - property bool mine: (creatorHandle === recordOperator.accountHandle) && recordOperator.accountHandle.length > 0 + property bool mine: (creatorHandle === recordOperator.handle) && recordOperator.handle.length > 0 onErrorOccured: (code, message) => listDetailView.errorOccured(code, message) } diff --git a/app/qtquick/operation/recordoperator.cpp b/app/qtquick/operation/recordoperator.cpp index 79532263..4330b677 100644 --- a/app/qtquick/operation/recordoperator.cpp +++ b/app/qtquick/operation/recordoperator.cpp @@ -32,7 +32,7 @@ RecordOperator::RecordOperator(QObject *parent) { } -AtProtocolInterface::AccountData RecordOperator::account() +AtProtocolInterface::AccountData RecordOperator::account() const { return AccountManager::getInstance()->getAccount(m_account.uuid); } @@ -1285,3 +1285,8 @@ void RecordOperator::setProgressMessage(const QString &newProgressMessage) m_progressMessage = newProgressMessage; emit progressMessageChanged(); } + +QString RecordOperator::handle() const +{ + return account().handle; +} diff --git a/app/qtquick/operation/recordoperator.h b/app/qtquick/operation/recordoperator.h index c73d6d80..f6bf61e3 100644 --- a/app/qtquick/operation/recordoperator.h +++ b/app/qtquick/operation/recordoperator.h @@ -19,6 +19,7 @@ class RecordOperator : public QObject Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) Q_PROPERTY(QString progressMessage READ progressMessage WRITE setProgressMessage NOTIFY progressMessageChanged FINAL) + Q_PROPERTY(QString handle READ handle CONSTANT) public: explicit RecordOperator(QObject *parent = nullptr); @@ -29,7 +30,7 @@ class RecordOperator : public QObject }; Q_ENUM(ListPurpose); - AtProtocolInterface::AccountData account(); + AtProtocolInterface::AccountData account() const; Q_INVOKABLE void setAccount(const QString &uuid); Q_INVOKABLE void setText(const QString &text); Q_INVOKABLE void setReply(const QString &parent_cid, const QString &parent_uri, @@ -83,9 +84,9 @@ class RecordOperator : public QObject bool running() const; void setRunning(bool newRunning); - QString progressMessage() const; void setProgressMessage(const QString &newProgressMessage); + QString handle() const; signals: void errorOccured(const QString &code, const QString &message); From f152467b7f22b5ed635d0c54f7e25395320ce54c Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Sun, 20 Oct 2024 17:48:39 +0900 Subject: [PATCH 117/127] Update labelerlistmodel.cpp --- app/qtquick/moderation/labelerlistmodel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/qtquick/moderation/labelerlistmodel.cpp b/app/qtquick/moderation/labelerlistmodel.cpp index 9862db4f..8531e1ea 100644 --- a/app/qtquick/moderation/labelerlistmodel.cpp +++ b/app/qtquick/moderation/labelerlistmodel.cpp @@ -47,7 +47,6 @@ void LabelerListModel::load() AtProtocolInterface::AccountData account; - // TODO: account: 替えなくてもOK? account.service = service(); account.handle = handle(); account.accessJwt = accessJwt(); From 5408085005ec672401fef861553a66fa4d2f1811 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 22 Oct 2024 01:21:32 +0900 Subject: [PATCH 118/127] =?UTF-8?q?TID=E3=81=AE=E3=82=B8=E3=82=A7=E3=83=8D?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=BF=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lib.pro | 6 ++++-- lib/tools/base32.cpp | 12 ++++++++++++ lib/tools/base32.h | 1 + lib/tools/tid.cpp | 26 ++++++++++++++++++++++++++ lib/tools/tid.h | 12 ++++++++++++ tests/tools_test/tst_tools_test.cpp | 21 +++++++++++++++++++++ 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 lib/tools/tid.cpp create mode 100644 lib/tools/tid.h diff --git a/lib/lib.pro b/lib/lib.pro index af39c7ac..e4025e5f 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -137,7 +137,8 @@ SOURCES += \ $$PWD/tools/leb128.cpp \ $$PWD/tools/listitemscache.cpp \ $$PWD/tools/opengraphprotocol.cpp \ - $$PWD/tools/pinnedpostcache.cpp + $$PWD/tools/pinnedpostcache.cpp \ + tools/tid.cpp HEADERS += \ $$PWD/atprotocol/accessatprotocol.h \ @@ -259,7 +260,8 @@ HEADERS += \ $$PWD/tools/opengraphprotocol.h \ $$PWD/tools/pinnedpostcache.h \ $$PWD/tools/qstringex.h \ - common.h + common.h \ + tools/tid.h RESOURCES += \ $$PWD/lib.qrc diff --git a/lib/tools/base32.cpp b/lib/tools/base32.cpp index 14ab7d5d..4c4e7560 100644 --- a/lib/tools/base32.cpp +++ b/lib/tools/base32.cpp @@ -27,3 +27,15 @@ QString Base32::encode(const QByteArray &data, const bool with_padding) return result; } + +QString Base32::encode_s(qint64 num) +{ + static const char alphabet[] = "234567abcdefghijklmnopqrstuvwxyz"; + QString result; + + for (int i = 0; i < 13; i++) { + result.insert(0, QChar(alphabet[num & 0x1F])); + num >>= 5; + } + return result; +} diff --git a/lib/tools/base32.h b/lib/tools/base32.h index 10c7c708..dce0d972 100644 --- a/lib/tools/base32.h +++ b/lib/tools/base32.h @@ -8,6 +8,7 @@ class Base32 { public: static QString encode(const QByteArray &data, const bool with_padding = false); + static QString encode_s(qint64 num); }; #endif // BASE32_H diff --git a/lib/tools/tid.cpp b/lib/tools/tid.cpp new file mode 100644 index 00000000..04988545 --- /dev/null +++ b/lib/tools/tid.cpp @@ -0,0 +1,26 @@ +#include "tid.h" +#include "base32.h" +#include +#include +#include + +QString Tid::next() +{ + static qint64 prev_time = 0; + static qint64 prev = 0; + qint64 current_time = QDateTime::currentMSecsSinceEpoch(); + if (current_time <= prev_time) { + current_time = prev_time + 1; + } + prev_time = current_time; + + qint64 clock_id = QRandomGenerator::global()->bounded(0, 1024); + qint64 id = (((current_time * 1000) << 10) & 0x7FFFFFFFFFFFFC00) | (clock_id & 0x3FF); + + if (prev == id) { + prev_time = current_time++; + id = (((current_time * 1000) << 10) & 0x7FFFFFFFFFFFFC00) | (clock_id & 0x3FF); + } + prev = id; + return Base32::encode_s(id); +} diff --git a/lib/tools/tid.h b/lib/tools/tid.h new file mode 100644 index 00000000..511ad38b --- /dev/null +++ b/lib/tools/tid.h @@ -0,0 +1,12 @@ +#ifndef TID_H +#define TID_H + +#include + +class Tid +{ +public: + static QString next(); +}; + +#endif // TID_H diff --git a/tests/tools_test/tst_tools_test.cpp b/tests/tools_test/tst_tools_test.cpp index 1877901a..8d34b067 100644 --- a/tests/tools_test/tst_tools_test.cpp +++ b/tests/tools_test/tst_tools_test.cpp @@ -7,6 +7,7 @@ #include "tools/base32.h" #include "tools/leb128.h" #include "tools/cardecoder.h" +#include "tools/tid.h" class tools_test : public QObject { @@ -21,6 +22,8 @@ private slots: void cleanupTestCase(); void test_base32(); + void test_base32_s(); + void test_tid(); void test_Leb128(); void test_CarDecoder(); }; @@ -83,6 +86,24 @@ void tools_test::test_base32() } } +void tools_test::test_base32_s() +{ + QVERIFY2(Base32::encode_s(0) == "2222222222222", Base32::encode_s(0).toLocal8Bit()); +} + +void tools_test::test_tid() +{ + QString prev = ""; + QString current; + + for (int i = 0; i < 1000; i++) { + current = Tid::next(); + QVERIFY2(prev < current, + QString("prev, current = %1, %2").arg(prev, current).toLocal8Bit()); + prev = current; + } +} + void tools_test::test_Leb128() { quint8 buf[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; From c5cf7c8fac5ba257de418131362526d3dfd39a6a Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 23 Oct 2024 01:43:57 +0900 Subject: [PATCH 119/127] =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AE=E4=BF=9D=E5=AD=98=E5=85=88=E3=82=92v2=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feedgenerator/feedgeneratorlistmodel.cpp | 60 +++++++++++++------ app/qtquick/timeline/customfeedlistmodel.cpp | 6 +- .../remove/app.bsky.actor.putPreferences | 30 ++++++++++ .../save/app.bsky.actor.putPreferences | 38 +++++++++++- .../remove/xrpc/app.bsky.actor.getPreferences | 35 +++++++++++ .../save/xrpc/app.bsky.actor.getPreferences | 29 +++++++++ tests/hagoromo_test2/tst_hagoromo_test2.cpp | 38 ++++++++++-- 7 files changed, 209 insertions(+), 27 deletions(-) diff --git a/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp b/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp index 66c77272..4db29132 100644 --- a/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp +++ b/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp @@ -3,6 +3,7 @@ #include "atprotocol/app/bsky/actor/appbskyactorgetpreferences.h" #include "atprotocol/app/bsky/unspecced/appbskyunspeccedgetpopularfeedgenerators.h" #include "atprotocol/app/bsky/actor/appbskyactorputpreferences.h" +#include "tools/tid.h" #include #include @@ -229,13 +230,22 @@ void FeedGeneratorListModel::getSavedGenerators() AppBskyActorGetPreferences *pref = new AppBskyActorGetPreferences(this); connect(pref, &AppBskyActorGetPreferences::finished, [=](bool success) { if (success) { - for (const auto &feed : pref->preferences().savedFeedsPref) { - m_savedUriList.append(feed.saved); + for (const auto &prefs : pref->preferences().savedFeedsPrefV2) { + for (const auto &item : prefs.items) { + if (item.type == "feed") { + m_savedUriList.append(item.value); + } + } } - for (int i = 0; i < m_cidList.count(); i++) { - emit dataChanged(index(i), index(i), - QVector() << static_cast(SavingRole)); + for (const auto &feed : pref->preferences().savedFeedsPref) { + for (const auto &uri : feed.saved) { + if (!m_savedUriList.contains(uri)) { + m_savedUriList.append(uri); + } + } } + emit dataChanged(index(0), index(m_cidList.count() - 1), + QVector() << static_cast(SavingRole)); } else { emit errorOccured(pref->errorCode(), pref->errorMessage()); } @@ -281,14 +291,27 @@ QJsonArray FeedGeneratorListModel::appendGeneratorToPreference(const QString &sr if (!preferences.toArray().at(i).isObject()) continue; QJsonObject value = preferences.toArray().takeAt(i).toObject(); - if (value.value("$type") == QStringLiteral("app.bsky.actor.defs#savedFeedsPref") - && value.value("saved").isArray()) { - QJsonArray json_saved = value.value("saved").toArray(); - if (!json_saved.contains(QJsonValue(uri))) { + if (value.value("$type") == QStringLiteral("app.bsky.actor.defs#savedFeedsPrefV2") + && value.value("items").isArray()) { + QJsonArray json_items = value.value("items").toArray(); + QJsonArray json_items_dest; + bool exist = false; + for (const auto &v : qAsConst(json_items)) { + if (v.toObject().value("value").toString() == uri) { + exist = true; + } + json_items_dest.append(v); + } + if (!exist) { // 含まれていなければ追加 - json_saved.append(QJsonValue(uri)); - value.insert("saved", json_saved); + QJsonObject json_item; + json_item.insert("id", Tid::next()); + json_item.insert("pinned", false); + json_item.insert("type", "feed"); + json_item.insert("value", uri); + json_items_dest.append(json_item); } + value.insert("items", json_items_dest); } dest_preferences.append(value); } @@ -312,15 +335,16 @@ QJsonArray FeedGeneratorListModel::removeGeneratorToPreference(const QString &sr if (!preferences.toArray().at(i).isObject()) continue; QJsonObject value = preferences.toArray().takeAt(i).toObject(); - if (value.value("$type") == QStringLiteral("app.bsky.actor.defs#savedFeedsPref") - && value.value("saved").isArray()) { - QJsonArray json_saved; - for (const auto &value : value.value("saved").toArray()) { - if (value.toString() != uri) { - json_saved.append(value); + if (value.value("$type") == QStringLiteral("app.bsky.actor.defs#savedFeedsPrefV2") + && value.value("items").isArray()) { + QJsonArray json_items = value.value("items").toArray(); + QJsonArray json_items_dest; + for (const auto &v : qAsConst(json_items)) { + if (v.toObject().value("value").toString() != uri) { + json_items_dest.append(v); } } - value.insert("saved", json_saved); + value.insert("items", json_items_dest); } dest_preferences.append(value); } diff --git a/app/qtquick/timeline/customfeedlistmodel.cpp b/app/qtquick/timeline/customfeedlistmodel.cpp index 70eb1386..d1adf913 100644 --- a/app/qtquick/timeline/customfeedlistmodel.cpp +++ b/app/qtquick/timeline/customfeedlistmodel.cpp @@ -52,9 +52,9 @@ void CustomFeedListModel::updateFeedSaveStatus() connect(pref, &AppBskyActorGetPreferences::finished, [=](bool success) { if (success) { bool exist = false; - for (const auto &feed : pref->preferences().savedFeedsPref) { - for (const auto &saved : feed.saved) { - if (saved == uri()) { + for (const auto &prefs : pref->preferences().savedFeedsPrefV2) { + for (const auto &item : prefs.items) { + if (item.type == "feed" && item.value == uri()) { exist = true; break; } diff --git a/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences b/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences index b9e090bb..c873f2b0 100644 --- a/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences +++ b/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences @@ -10,6 +10,35 @@ "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/bsky-team" ] }, + { + "$type": "app.bsky.actor.defs#savedFeedsPrefV2", + "items": [ + { + "id": "3ksooxaaknk27", + "pinned": true, + "type": "timeline", + "value": "following" + }, + { + "id": "3ksooxaaknm27", + "pinned": true, + "type": "feed", + "value": "at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/cv:cat" + }, + { + "id": "3ksooxaaknn27", + "pinned": true, + "type": "list", + "value": "at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.graph.list/3kflf2r3lwg2x" + }, + { + "id": "3ksooaaaaaaaa", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/hot-classic" + } + ] + }, { "$type": "app.bsky.actor.defs#savedFeedsPref", "pinned": [ @@ -20,6 +49,7 @@ ], "saved": [ "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/bsky-team", + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends", "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" ] }, diff --git a/tests/hagoromo_test2/data/generator/save/app.bsky.actor.putPreferences b/tests/hagoromo_test2/data/generator/save/app.bsky.actor.putPreferences index 449944b8..075e0bba 100644 --- a/tests/hagoromo_test2/data/generator/save/app.bsky.actor.putPreferences +++ b/tests/hagoromo_test2/data/generator/save/app.bsky.actor.putPreferences @@ -10,6 +10,41 @@ "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/bsky-team" ] }, + { + "$type": "app.bsky.actor.defs#savedFeedsPrefV2", + "items": [ + { + "id": "3ksooxaaknk27", + "pinned": true, + "type": "timeline", + "value": "following" + }, + { + "id": "3ksooxaaknm27", + "pinned": true, + "type": "feed", + "value": "at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/cv:cat" + }, + { + "id": "3ksooxaaknu27", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" + }, + { + "id": "3ksooxaaknn27", + "pinned": true, + "type": "list", + "value": "at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.graph.list/3kflf2r3lwg2x" + }, + { + "id": "3ksooaaaaaaaa", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/hot-classic" + } + ] + }, { "$type": "app.bsky.actor.defs#savedFeedsPref", "pinned": [ @@ -21,8 +56,7 @@ "saved": [ "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/bsky-team", "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends", - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/hot-classic" + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" ] }, { diff --git a/tests/hagoromo_test2/response/generator/remove/xrpc/app.bsky.actor.getPreferences b/tests/hagoromo_test2/response/generator/remove/xrpc/app.bsky.actor.getPreferences index 6cb3efcb..ad5f3750 100644 --- a/tests/hagoromo_test2/response/generator/remove/xrpc/app.bsky.actor.getPreferences +++ b/tests/hagoromo_test2/response/generator/remove/xrpc/app.bsky.actor.getPreferences @@ -10,6 +10,41 @@ "at://did:plc:hiptcrt4k63szzz4ty3dhwcp/app.bsky.feed.generator/ja-images" ] }, + { + "$type": "app.bsky.actor.defs#savedFeedsPrefV2", + "items": [ + { + "id": "3ksooxaaknk27", + "pinned": true, + "type": "timeline", + "value": "following" + }, + { + "id": "3ksooxaaknm27", + "pinned": true, + "type": "feed", + "value": "at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/cv:cat" + }, + { + "id": "3ksooxaaknu27", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" + }, + { + "id": "3ksooxaaknn27", + "pinned": true, + "type": "list", + "value": "at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.graph.list/3kflf2r3lwg2x" + }, + { + "id": "3ksooaaaaaaaa", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/hot-classic" + } + ] + }, { "$type": "app.bsky.actor.defs#savedFeedsPref", "saved": [ diff --git a/tests/hagoromo_test2/response/generator/save/xrpc/app.bsky.actor.getPreferences b/tests/hagoromo_test2/response/generator/save/xrpc/app.bsky.actor.getPreferences index 6cb3efcb..48cf5064 100644 --- a/tests/hagoromo_test2/response/generator/save/xrpc/app.bsky.actor.getPreferences +++ b/tests/hagoromo_test2/response/generator/save/xrpc/app.bsky.actor.getPreferences @@ -10,6 +10,35 @@ "at://did:plc:hiptcrt4k63szzz4ty3dhwcp/app.bsky.feed.generator/ja-images" ] }, + { + "$type": "app.bsky.actor.defs#savedFeedsPrefV2", + "items": [ + { + "id": "3ksooxaaknk27", + "pinned": true, + "type": "timeline", + "value": "following" + }, + { + "id": "3ksooxaaknm27", + "pinned": true, + "type": "feed", + "value": "at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/cv:cat" + }, + { + "id": "3ksooxaaknu27", + "pinned": false, + "type": "feed", + "value": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" + }, + { + "id": "3ksooxaaknn27", + "pinned": true, + "type": "list", + "value": "at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.graph.list/3kflf2r3lwg2x" + } + ] + }, { "$type": "app.bsky.actor.defs#savedFeedsPref", "saved": [ diff --git a/tests/hagoromo_test2/tst_hagoromo_test2.cpp b/tests/hagoromo_test2/tst_hagoromo_test2.cpp index 67e6aecf..7f102d16 100644 --- a/tests/hagoromo_test2/tst_hagoromo_test2.cpp +++ b/tests/hagoromo_test2/tst_hagoromo_test2.cpp @@ -50,6 +50,7 @@ private slots: void test_putPreferences(const QString &path, const QByteArray &body); void test_putRecord(const QString &path, const QByteArray &body); void verifyStr(const QString &expect, const QString &actual); + QJsonObject copyObject(const QJsonObject &object, const QStringList &excludes); }; hagoromo_test::hagoromo_test() @@ -245,7 +246,7 @@ void hagoromo_test::test_FeedGeneratorListModel() model.setAccount(uuid); QSignalSpy spy(&model, SIGNAL(runningChanged())); model.removeGenerator( - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends"); + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"); spy.wait(10 * 1000); QVERIFY2(spy.count() == 2, QString("spy.count()=%1").arg(spy.count()).toUtf8()); } @@ -753,14 +754,17 @@ void hagoromo_test::test_putPreferences(const QString &path, const QByteArray &b json_doc_expect = UnitTestCommon::loadJson(":/data/generator/remove/app.bsky.actor.putPreferences"); } + QJsonDocument json_doc_expect2 = + QJsonDocument(copyObject(json_doc_expect.object(), QStringList() << "id")); QJsonDocument json_doc = QJsonDocument::fromJson(body); + QJsonDocument json_doc2 = QJsonDocument(copyObject(json_doc.object(), QStringList() << "id")); - if (json_doc_expect.object() != json_doc.object()) { + if (json_doc_expect2.object() != json_doc2.object()) { qDebug().noquote().nospace() << QString("\nexpect:%1\nactual:%2\n") - .arg(json_doc_expect.toJson(), json_doc.toJson()); + .arg(json_doc_expect2.toJson(), json_doc2.toJson()); } - QVERIFY(json_doc_expect.object() == json_doc.object()); + QVERIFY(json_doc_expect2.object() == json_doc2.object()); } void hagoromo_test::test_putRecord(const QString &path, const QByteArray &body) @@ -791,6 +795,32 @@ void hagoromo_test::verifyStr(const QString &expect, const QString &actual) QString("\nexpect:%1\nactual:%2\n").arg(expect, actual).toLocal8Bit()); } +QJsonObject hagoromo_test::copyObject(const QJsonObject &src, const QStringList &excludes) +{ + + QJsonObject dest; + for (const auto &key : src.keys()) { + if (excludes.contains(key)) + continue; + if (src.value(key).isObject()) { + dest.insert(key, copyObject(src.value(key).toObject(), excludes)); + } else if (src.value(key).isArray()) { + QJsonArray dest_array; + for (const auto value : src.value(key).toArray()) { + if (value.isObject()) { + dest_array.append(copyObject(value.toObject(), excludes)); + } else { + dest_array.append(value); + } + } + dest.insert(key, dest_array); + } else { + dest.insert(key, src.value(key)); + } + } + return dest; +} + QTEST_MAIN(hagoromo_test) #include "tst_hagoromo_test2.moc" From 4f063473d3d4b538eba31c91fd4ee6c506f7a233 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 23 Oct 2024 01:47:59 +0900 Subject: [PATCH 120/127] =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=AFv1=E3=81=A8v?= =?UTF-8?q?2=E3=81=AE=E4=B8=A1=E6=96=B9=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp | 11 +++++++++++ .../generator/remove/app.bsky.actor.putPreferences | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp b/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp index 4db29132..f84659d6 100644 --- a/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp +++ b/app/qtquick/feedgenerator/feedgeneratorlistmodel.cpp @@ -345,6 +345,17 @@ QJsonArray FeedGeneratorListModel::removeGeneratorToPreference(const QString &sr } } value.insert("items", json_items_dest); + + } else if (value.value("$type") == QStringLiteral("app.bsky.actor.defs#savedFeedsPref") + && value.value("saved").isArray()) { + // 互換性維持で読み込み側はv1とv2のORなので削除も両方から実施 + QJsonArray json_saved; + for (const auto &value : value.value("saved").toArray()) { + if (value.toString() != uri) { + json_saved.append(value); + } + } + value.insert("saved", json_saved); } dest_preferences.append(value); } diff --git a/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences b/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences index c873f2b0..d5562db6 100644 --- a/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences +++ b/tests/hagoromo_test2/data/generator/remove/app.bsky.actor.putPreferences @@ -49,8 +49,7 @@ ], "saved": [ "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/bsky-team", - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends", - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends" ] }, { From f87a5f2569e2716d6e4f0065fc068b806f8da68c Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Wed, 23 Oct 2024 01:55:40 +0900 Subject: [PATCH 121/127] =?UTF-8?q?=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/content/docs/release-note.en.md | 1 + web/content/docs/release-note.ja.md | 1 + 2 files changed, 2 insertions(+) diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index 8f7ae7ec..a63464a1 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -12,6 +12,7 @@ description: This is a multi-column Bluesky client. - Add a link to user's profile if user is registered with Linkat - Update - Change the format of the embedded via in the post + - Change the internal format of feed storage to V2 ### v0.39.0 - 2024/10/12 diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index 88796dba..7a4bdce0 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -12,6 +12,7 @@ description: マルチカラム対応Blueskyクライアント - Linkatに登録があるときリンクをプロフィールに追加 - 変更 - ポストに埋め込むviaの形式を変更 + - フィードの保存の内部形式をV2に変更 ### v0.39.0 - 2024/10/12 From eef9ec3348eed9862368abfc78df1708319b0200 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Tue, 29 Oct 2024 00:51:47 +0900 Subject: [PATCH 122/127] =?UTF-8?q?client=5Fid=E3=82=92=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=81=AB=E7=BD=AE=E3=81=84=E3=81=9F=E3=83=A1?= =?UTF-8?q?=E3=82=BF=E3=83=87=E3=83=BC=E3=82=BF=E3=81=AE=E3=82=A2=E3=83=89?= =?UTF-8?q?=E3=83=AC=E3=82=B9=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tools/authorization.cpp | 12 +++++++++--- lib/tools/oauth/client-metadata.json | 19 +++++++++++++++++++ tests/oauth_test/tst_oauth_test.cpp | 26 +++++++++++--------------- 3 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 lib/tools/oauth/client-metadata.json diff --git a/lib/tools/authorization.cpp b/lib/tools/authorization.cpp index 2b50609c..a4b316cf 100644 --- a/lib/tools/authorization.cpp +++ b/lib/tools/authorization.cpp @@ -137,11 +137,18 @@ void Authorization::requestOauthAuthorizationServer() if (success) { QString error_message; if (validateServerMetadata(server->serverMetadata(), error_message)) { + const QStringList scope_candidates = QStringList() << "atproto" + << "transition:generic" + << "transition:chat.bsky"; setPushedAuthorizationRequestEndpoint( server->serverMetadata().pushed_authorization_request_endpoint); setAuthorizationEndpoint(server->serverMetadata().authorization_endpoint); setTokenEndopoint(server->serverMetadata().token_endpoint); - m_scopesSupported = server->serverMetadata().scopes_supported; + for (const auto &scope : scope_candidates) { + if (server->serverMetadata().scopes_supported.contains(scope)) { + m_scopesSupported.append(scope); + } + } // next step par(); @@ -241,8 +248,7 @@ void Authorization::makeClientId() m_redirectUri.append("http://127.0.0.1"); m_redirectUri.append(port); m_redirectUri.append("/tech/relog/hagoromo/oauth-callback"); - m_clientId.append("http://localhost/tech/relog/hagoromo?redirect_uri="); - m_clientId.append(simplyEncode(m_redirectUri)); + m_clientId = "https://oauth.hagoromo.relog.tech/client-metadata.json"; } void Authorization::makeCodeChallenge() diff --git a/lib/tools/oauth/client-metadata.json b/lib/tools/oauth/client-metadata.json new file mode 100644 index 00000000..91dd5f4d --- /dev/null +++ b/lib/tools/oauth/client-metadata.json @@ -0,0 +1,19 @@ +{ + "client_id": "https://oauth.hagoromo.relog.tech/client-metadata.json", + "application_type": "native", + "client_name": "Hagoromo", + "client_uri": "https://oauth.hagoromo.relog.tech", + "dpop_bound_access_tokens": true, + "grant_types": [ + "authorization_code", + "refresh_token" + ], + "redirect_uris": [ + "http://127.0.0.1/tech/relog/hagoromo/oauth-callback" + ], + "response_types": [ + "code" + ], + "scope": "atproto transition:generic transition:chat.bsky", + "token_endpoint_auth_method": "none" +} diff --git a/tests/oauth_test/tst_oauth_test.cpp b/tests/oauth_test/tst_oauth_test.cpp index 87813e6f..864a2811 100644 --- a/tests/oauth_test/tst_oauth_test.cpp +++ b/tests/oauth_test/tst_oauth_test.cpp @@ -214,20 +214,15 @@ void oauth_test::test_oauth() QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8()); QList arguments = spy.takeFirst(); request_url = arguments.at(0).toString(); - QVERIFY2( - request_url - == (QStringLiteral("http://localhost:") + QString::number(m_listenPort) - + QStringLiteral( - "/response/2/oauth/" - "authorize?client_id=http%3A%2F%2Flocalhost%2Ftech%2Frelog%" - "2Fhagoromo%3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A") - + oauth.listenPort() - + QStringLiteral( - "%252Ftech%252Frelog%" - "252Fhagoromo%252Foauth-callback&request_uri=urn%3Aietf%" - "3Aparams%3Aoauth%3Arequest_uri%3Areq-" - "05650c01604941dc674f0af9cb032aca")), - request_url.toLocal8Bit()); + QVERIFY2(request_url + == (QStringLiteral("http://localhost:") + QString::number(m_listenPort) + + QStringLiteral( + "/response/2/oauth/" + "authorize?client_id=https%3A%2F%2Foauth.hagoromo.relog.tech%" + "2Fclient-metadata.json&request_uri=urn%3Aietf%" + "3Aparams%3Aoauth%3Arequest_uri%3Areq-" + "05650c01604941dc674f0af9cb032aca")), + request_url.toLocal8Bit()); } { @@ -246,7 +241,7 @@ void oauth_test::test_oauth() QUrlQuery request_query(request.query()); QUrl client_id(request_query.queryItemValue("client_id", QUrl::FullyDecoded)); QUrlQuery client_query(client_id.query()); - redirect_url = client_query.queryItemValue("redirect_uri", QUrl::FullyDecoded); + redirect_url = "http://127.0.0.1/tech/relog/hagoromo/oauth-callback"; QUrlQuery redirect_query; redirect_query.addQueryItem("iss", "iss-hogehoge"); redirect_query.addQueryItem("state", oauth.state()); @@ -320,6 +315,7 @@ void oauth_test::test_es256() void oauth_test::test_get(const QString &url, const QByteArray &except_data) { + qDebug() << "test_get url" << url; QNetworkRequest request((QUrl(url))); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); From 7ddd1b4d2535753d136657d9c897367434b2d5b6 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 31 Oct 2024 01:08:53 +0900 Subject: [PATCH 123/127] =?UTF-8?q?=E6=B7=BB=E4=BB=98=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=82=92=E3=83=89=E3=83=A9=E3=83=83=E3=82=B0=E3=82=A2=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=83=89=E3=83=AD=E3=83=83=E3=83=97=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E5=8F=97=E4=BB=98=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.pro | 1 + app/qml/controls/DragAndDropArea.qml | 45 + app/qml/dialogs/PostDialog.qml | 965 ++++++++++--------- app/qml/main.qml | 26 + app/qtquick/controls/embedimagelistmodel.cpp | 22 +- app/qtquick/controls/embedimagelistmodel.h | 2 +- web/content/docs/release-note.en.md | 1 + web/content/docs/release-note.ja.md | 1 + 8 files changed, 584 insertions(+), 479 deletions(-) create mode 100644 app/qml/controls/DragAndDropArea.qml diff --git a/app/app.pro b/app/app.pro index 41319d55..cfbf638a 100644 --- a/app/app.pro +++ b/app/app.pro @@ -21,6 +21,7 @@ QML_FILES = \ qml/controls/CalendarPicker.qml \ qml/controls/ClickableFrame.qml \ qml/controls/ComboBoxEx.qml \ + qml/controls/DragAndDropArea.qml \ qml/controls/IconButton.qml \ qml/controls/IconLabelFrame.qml \ qml/controls/ImageWithIndicator.qml \ diff --git a/app/qml/controls/DragAndDropArea.qml b/app/qml/controls/DragAndDropArea.qml new file mode 100644 index 00000000..6df22293 --- /dev/null +++ b/app/qml/controls/DragAndDropArea.qml @@ -0,0 +1,45 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls.Material 2.15 + +import tech.relog.hagoromo.singleton 1.0 + +//ドラッグ状態に反応してことを表す四角 +Rectangle { + id: dropRect + radius: 4 + color: Material.color(Material.Grey) + opacity: 0 + states: State { + //ドラッグ状態で領域内にいたら背景色と文字色を変更 + when: imageDropArea.containsDrag + PropertyChanges { target: dropRect; opacity: 0.5 } + PropertyChanges { target: dropRectMessage; opacity: 1 } + } + signal dropped(var urls) + + //ドラッグ状態で領域内にいるときの説明 + Text { + id: dropRectMessage + anchors.centerIn: parent + color: "black" + text: qsTr("Detecting...") + font.pointSize: AdjustedValues.f14 + } + //ドロップの受付 + DropArea { + id: imageDropArea + anchors.fill: parent + keys: ["text/uri-list"] + onDropped: (drop) => { + if(drop.hasUrls){ + var new_urls = [] + for(var i=0; i basisHeight) ? basisHeight : mainLayout.height ScrollBar.vertical.policy: (mainLayout.height > basisHeight) ? ScrollBar.AlwaysOn :ScrollBar.AlwaysOff clip: true - ColumnLayout { - id: mainLayout - Frame { - id: replyFrame - Layout.preferredWidth: postText.width - Layout.maximumHeight: 200 * AdjustedValues.ratio - visible: postType === "reply" - clip: true - ColumnLayout { - anchors.fill: parent - RowLayout { - AvatarImage { - id: replyAvatarImage - Layout.preferredWidth: AdjustedValues.i16 - Layout.preferredHeight: AdjustedValues.i16 - source: replyAvatar + Item { + implicitWidth: mainLayout.width + implicitHeight: mainLayout.height + ColumnLayout { + id: mainLayout + Frame { + id: replyFrame + Layout.preferredWidth: postText.width + Layout.maximumHeight: 200 * AdjustedValues.ratio + visible: postType === "reply" + clip: true + ColumnLayout { + anchors.fill: parent + RowLayout { + AvatarImage { + id: replyAvatarImage + Layout.preferredWidth: AdjustedValues.i16 + Layout.preferredHeight: AdjustedValues.i16 + source: replyAvatar + } + Author { + layoutWidth: replyFrame.width - replyFrame.padding * 2 - replyAvatarImage.width - parent.spacing + displayName: replyDisplayName + handle: replyHandle + indexedAt: replyIndexedAt + } } - Author { - layoutWidth: replyFrame.width - replyFrame.padding * 2 - replyAvatarImage.width - parent.spacing - displayName: replyDisplayName - handle: replyHandle - indexedAt: replyIndexedAt + Label { + Layout.preferredWidth: postText.width - replyFrame.padding * 2 + wrapMode: Text.WrapAnywhere + font.pointSize: AdjustedValues.f8 + text: replyText } } - Label { - Layout.preferredWidth: postText.width - replyFrame.padding * 2 - wrapMode: Text.WrapAnywhere - font.pointSize: AdjustedValues.f8 - text: replyText - } } - } - RowLayout { - ComboBox { - id: accountCombo - Layout.preferredWidth: 200 * AdjustedValues.ratio + AdjustedValues.i24 - Layout.preferredHeight: implicitHeight * AdjustedValues.ratio - enabled: !createRecord.running - font.pointSize: AdjustedValues.f10 - textRole: "handle" - valueRole: "did" - delegate: ItemDelegate { - width: parent.width - height: implicitHeight * AdjustedValues.ratio + RowLayout { + ComboBox { + id: accountCombo + Layout.preferredWidth: 200 * AdjustedValues.ratio + AdjustedValues.i24 + Layout.preferredHeight: implicitHeight * AdjustedValues.ratio + enabled: !createRecord.running font.pointSize: AdjustedValues.f10 - onClicked: accountCombo.currentIndex = model.index - AccountLayout { - anchors.fill: parent - anchors.margins: 10 - source: model.avatar - handle: model.handle + textRole: "handle" + valueRole: "did" + delegate: ItemDelegate { + width: parent.width + height: implicitHeight * AdjustedValues.ratio + font.pointSize: AdjustedValues.f10 + onClicked: accountCombo.currentIndex = model.index + AccountLayout { + anchors.fill: parent + anchors.margins: 10 + source: model.avatar + handle: model.handle + } + } + contentItem: AccountLayout { + id: accountAvatarLayout + width: parent.width + height: parent.height + leftMargin: 10 + handle: accountCombo.displayText } - } - contentItem: AccountLayout { - id: accountAvatarLayout - width: parent.width - height: parent.height - leftMargin: 10 - handle: accountCombo.displayText - } - onCurrentIndexChanged: { - var row = accountCombo.currentIndex - if(row >= 0){ - accountAvatarLayout.source = - postDialog.accountModel.item(row, AccountListModel.AvatarRole) - postLanguagesButton.setLanguageText( - postDialog.accountModel.item(row, AccountListModel.PostLanguagesRole) - ) - selectThreadGateDialog.initialQuoteEnabled = postDialog.accountModel.item(row, AccountListModel.PostGateQuoteEnabledRole) - selectThreadGateDialog.initialType = postDialog.accountModel.item(row, AccountListModel.ThreadGateTypeRole) - selectThreadGateDialog.initialOptions = postDialog.accountModel.item(row, AccountListModel.ThreadGateOptionsRole) - // リプライ制限のダイアログを開かずにポストするときのため選択済みにも設定する - selectThreadGateDialog.selectedQuoteEnabled = selectThreadGateDialog.initialQuoteEnabled - selectThreadGateDialog.selectedType = selectThreadGateDialog.initialType - selectThreadGateDialog.selectedOptions = selectThreadGateDialog.initialOptions - // 入力中にアカウントを切り替えるかもなので選んだ時に設定する - mentionSuggestionView.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + onCurrentIndexChanged: { + var row = accountCombo.currentIndex + if(row >= 0){ + accountAvatarLayout.source = + postDialog.accountModel.item(row, AccountListModel.AvatarRole) + postLanguagesButton.setLanguageText( + postDialog.accountModel.item(row, AccountListModel.PostLanguagesRole) + ) + selectThreadGateDialog.initialQuoteEnabled = postDialog.accountModel.item(row, AccountListModel.PostGateQuoteEnabledRole) + selectThreadGateDialog.initialType = postDialog.accountModel.item(row, AccountListModel.ThreadGateTypeRole) + selectThreadGateDialog.initialOptions = postDialog.accountModel.item(row, AccountListModel.ThreadGateOptionsRole) + // リプライ制限のダイアログを開かずにポストするときのため選択済みにも設定する + selectThreadGateDialog.selectedQuoteEnabled = selectThreadGateDialog.initialQuoteEnabled + selectThreadGateDialog.selectedType = selectThreadGateDialog.initialType + selectThreadGateDialog.selectedOptions = selectThreadGateDialog.initialOptions + // 入力中にアカウントを切り替えるかもなので選んだ時に設定する + mentionSuggestionView.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + } } } - } - Item { - Layout.fillWidth: true - Layout.preferredHeight: 1 - } - IconButton { - id: threadGateButton - enabled: !createRecord.running && (postType !== "reply") - iconSource: "../images/thread.png" - iconSize: AdjustedValues.i18 - flat: true - foreground: (selectThreadGateDialog.selectedType !== "everybody" || !selectThreadGateDialog.selectedQuoteEnabled) - ? Material.accent : Material.foreground - onClicked: { - var row = accountCombo.currentIndex; - selectThreadGateDialog.account.service = postDialog.accountModel.item(row, AccountListModel.ServiceRole) - selectThreadGateDialog.account.did = postDialog.accountModel.item(row, AccountListModel.DidRole) - selectThreadGateDialog.account.handle = postDialog.accountModel.item(row, AccountListModel.HandleRole) - selectThreadGateDialog.account.email = postDialog.accountModel.item(row, AccountListModel.EmailRole) - selectThreadGateDialog.account.accessJwt = postDialog.accountModel.item(row, AccountListModel.AccessJwtRole) - selectThreadGateDialog.account.refreshJwt = postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole) - selectThreadGateDialog.initialQuoteEnabled = selectThreadGateDialog.selectedQuoteEnabled - selectThreadGateDialog.initialType = selectThreadGateDialog.selectedType - selectThreadGateDialog.initialOptions = selectThreadGateDialog.selectedOptions - selectThreadGateDialog.open() + Item { + Layout.fillWidth: true + Layout.preferredHeight: 1 } - } - - IconButton { - id: postLanguagesButton - enabled: !createRecord.running - iconSource: "../images/language.png" - iconSize: AdjustedValues.i18 - flat: true - onClicked: { - languageSelectionDialog.setSelectedLanguages( - postDialog.accountModel.item(accountCombo.currentIndex, AccountListModel.PostLanguagesRole) - ) - languageSelectionDialog.open() + IconButton { + id: threadGateButton + enabled: !createRecord.running && (postType !== "reply") + iconSource: "../images/thread.png" + iconSize: AdjustedValues.i18 + flat: true + foreground: (selectThreadGateDialog.selectedType !== "everybody" || !selectThreadGateDialog.selectedQuoteEnabled) + ? Material.accent : Material.foreground + onClicked: { + var row = accountCombo.currentIndex; + selectThreadGateDialog.account.service = postDialog.accountModel.item(row, AccountListModel.ServiceRole) + selectThreadGateDialog.account.did = postDialog.accountModel.item(row, AccountListModel.DidRole) + selectThreadGateDialog.account.handle = postDialog.accountModel.item(row, AccountListModel.HandleRole) + selectThreadGateDialog.account.email = postDialog.accountModel.item(row, AccountListModel.EmailRole) + selectThreadGateDialog.account.accessJwt = postDialog.accountModel.item(row, AccountListModel.AccessJwtRole) + selectThreadGateDialog.account.refreshJwt = postDialog.accountModel.item(row, AccountListModel.RefreshJwtRole) + selectThreadGateDialog.initialQuoteEnabled = selectThreadGateDialog.selectedQuoteEnabled + selectThreadGateDialog.initialType = selectThreadGateDialog.selectedType + selectThreadGateDialog.initialOptions = selectThreadGateDialog.selectedOptions + selectThreadGateDialog.open() + } } - function setLanguageText(post_langs){ - var langs = languageListModel.convertLanguageNames(post_langs) - var lang_str = "" - for(var i=0;i 0){ - lang_str += ", " - } - lang_str += langs[i] - } - if(lang_str.length > 13){ - lang_str = lang_str.substring(0, 10) + "..." + IconButton { + id: postLanguagesButton + enabled: !createRecord.running + iconSource: "../images/language.png" + iconSize: AdjustedValues.i18 + flat: true + onClicked: { + languageSelectionDialog.setSelectedLanguages( + postDialog.accountModel.item(accountCombo.currentIndex, AccountListModel.PostLanguagesRole) + ) + languageSelectionDialog.open() } - iconText = lang_str - } - } - } - ScrollView { - z: 99 // MentionSuggetionViewを最前に表示するため - Layout.preferredWidth: 420 * AdjustedValues.ratio - Layout.preferredHeight: 120 * AdjustedValues.ratio - TextArea { - id: postText - verticalAlignment: TextInput.AlignTop - enabled: !createRecord.running - wrapMode: TextInput.WordWrap - selectByMouse: true - font.pointSize: AdjustedValues.f10 - property int realTextLength: systemTool.countText(text) - onTextChanged: mentionSuggestionView.reload(getText(0, cursorPosition)) - Keys.onPressed: (event) => { - if(mentionSuggestionView.visible){ - console.log("Key(v):" + event.key) - if(event.key === Qt.Key_Up){ - mentionSuggestionView.up() - event.accepted = true - }else if(event.key === Qt.Key_Down){ - mentionSuggestionView.down() - event.accepted = true - }else if(event.key === Qt.Key_Enter || - event.key === Qt.Key_Return){ - mentionSuggestionView.accept() - event.accepted = true - }else if(event.key === Qt.Key_Escape){ - mentionSuggestionView.clear() - } - }else{ - console.log("Key(n):" + event.key) - if(event.key === Qt.Key_Space && (event.modifiers & Qt.ControlModifier)){ - mentionSuggestionView.reload(getText(0, cursorPosition)) - event.accepted = true - } - } - } - MentionSuggestionView { - id: mentionSuggestionView - anchors.left: parent.left - anchors.right: parent.right - onVisibleChanged: { - var rect = postText.positionToRectangle(postText.cursorPosition) - y = rect.y + rect.height + 2 + function setLanguageText(post_langs){ + var langs = languageListModel.convertLanguageNames(post_langs) + var lang_str = "" + for(var i=0;i 0){ + lang_str += ", " + } + lang_str += langs[i] + } + if(lang_str.length > 13){ + lang_str = lang_str.substring(0, 10) + "..." + } + iconText = lang_str } - onSelected: (handle) => { - var after = replaceText(postText.text, postText.cursorPosition, handle) - if(after !== postText.text){ - postText.text = after - postText.cursorPosition = postText.text.length - } - } } } - } - RowLayout { - Layout.preferredWidth: postText.width - visible: embedImageListModel.count === 0 ScrollView { - Layout.fillWidth: true - clip: true + z: 99 // MentionSuggetionViewを最前に表示するため + Layout.preferredWidth: 420 * AdjustedValues.ratio + Layout.preferredHeight: 120 * AdjustedValues.ratio TextArea { - id: addingExternalLinkUrlText + id: postText + verticalAlignment: TextInput.AlignTop + enabled: !createRecord.running + wrapMode: TextInput.WordWrap selectByMouse: true font.pointSize: AdjustedValues.f10 - placeholderText: (quoteValid === false) ? - qsTr("Link card URL, Custom feed URL, List URL, Post URL") : - qsTr("Link card URL") + property int realTextLength: systemTool.countText(text) + onTextChanged: mentionSuggestionView.reload(getText(0, cursorPosition)) + Keys.onPressed: (event) => { + if(mentionSuggestionView.visible){ + console.log("Key(v):" + event.key) + if(event.key === Qt.Key_Up){ + mentionSuggestionView.up() + event.accepted = true + }else if(event.key === Qt.Key_Down){ + mentionSuggestionView.down() + event.accepted = true + }else if(event.key === Qt.Key_Enter || + event.key === Qt.Key_Return){ + mentionSuggestionView.accept() + event.accepted = true + }else if(event.key === Qt.Key_Escape){ + mentionSuggestionView.clear() + } + }else{ + console.log("Key(n):" + event.key) + if(event.key === Qt.Key_Space && (event.modifiers & Qt.ControlModifier)){ + mentionSuggestionView.reload(getText(0, cursorPosition)) + event.accepted = true + } + } + } + MentionSuggestionView { + id: mentionSuggestionView + anchors.left: parent.left + anchors.right: parent.right + onVisibleChanged: { + var rect = postText.positionToRectangle(postText.cursorPosition) + y = rect.y + rect.height + 2 + } + onSelected: (handle) => { + var after = replaceText(postText.text, postText.cursorPosition, handle) + if(after !== postText.text){ + postText.text = after + postText.cursorPosition = postText.text.length + } + } + } } } - IconButton { - id: externalLinkButton - iconSource: "../images/add.png" - enabled: addingExternalLinkUrlText.text.length > 0 && - !externalLink.running && - !feedGeneratorLink.running && - !listLink.running && - !createRecord.running - onClicked: { - var uri = addingExternalLinkUrlText.text - var row = accountCombo.currentIndex - if(feedGeneratorLink.checkUri(uri, "feed") && !quoteValid){ - feedGeneratorLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) - feedGeneratorLink.getFeedGenerator(uri) - }else if(listLink.checkUri(uri, "lists") && !quoteValid){ - listLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) - listLink.getList(uri) - }else if(postLink.checkUri(uri, "post") && !quoteValid && postType !== "quote"){ - postLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) - postLink.getPost(uri) - }else{ - externalLink.getExternalLink(uri) + RowLayout { + Layout.preferredWidth: postText.width + visible: embedImageListModel.count === 0 + ScrollView { + Layout.fillWidth: true + clip: true + TextArea { + id: addingExternalLinkUrlText + selectByMouse: true + font.pointSize: AdjustedValues.f10 + placeholderText: (quoteValid === false) ? + qsTr("Link card URL, Custom feed URL, List URL, Post URL") : + qsTr("Link card URL") } } - BusyIndicator { - anchors.fill: parent - anchors.margins: 3 - visible: externalLink.running || - feedGeneratorLink.running || - listLink.running - } - states: [ - State { - when: externalLink.valid || - feedGeneratorLink.valid || - listLink.valid || - (postLink.valid && postType !== "quote") - PropertyChanges { - target: externalLinkButton - iconSource: "../images/delete.png" - onClicked: { - externalLink.clear() - feedGeneratorLink.clear() - listLink.clear() - postLink.clear() - if(postType !== "quote"){ - quoteCid = "" - quoteUri = "" - quoteAvatar = "" - quoteDisplayName = "" - quoteHandle = "" - quoteIndexedAt = "" - quoteText = "" - } - } + IconButton { + id: externalLinkButton + iconSource: "../images/add.png" + enabled: addingExternalLinkUrlText.text.length > 0 && + !externalLink.running && + !feedGeneratorLink.running && + !listLink.running && + !createRecord.running + onClicked: { + var uri = addingExternalLinkUrlText.text + var row = accountCombo.currentIndex + if(feedGeneratorLink.checkUri(uri, "feed") && !quoteValid){ + feedGeneratorLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + feedGeneratorLink.getFeedGenerator(uri) + }else if(listLink.checkUri(uri, "lists") && !quoteValid){ + listLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + listLink.getList(uri) + }else if(postLink.checkUri(uri, "post") && !quoteValid && postType !== "quote"){ + postLink.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + postLink.getPost(uri) + }else{ + externalLink.getExternalLink(uri) + } } - ] - } - } - ExternalLinkCard { - Layout.preferredWidth: postText.width - Layout.maximumHeight: 280 * AdjustedValues.ratio - visible: externalLink.valid - - thumbImage.source: externalLink.thumbLocal - uriLabel.text: externalLink.uri - titleLabel.text: externalLink.title - descriptionLabel.text: externalLink.description - } - FeedGeneratorLinkCard { - Layout.preferredWidth: postText.width - visible: feedGeneratorLink.valid - - avatarImage.source: feedGeneratorLink.avatar - displayNameLabel.text: feedGeneratorLink.displayName - creatorHandleLabel.text: feedGeneratorLink.creatorHandle - likeCountLabel.text: feedGeneratorLink.likeCount - } - ListLinkCard { - Layout.preferredWidth: postText.width - visible: listLink.valid - avatarImage.source: listLink.avatar - displayNameLabel.text: listLink.displayName - creatorHandleLabel.text: listLink.creatorHandle - descriptionLabel.text: listLink.description - } - - ScrollView { - Layout.preferredWidth: postText.width - Layout.preferredHeight: 102 * AdjustedValues.ratio + ScrollBar.horizontal.height + 1 - ScrollBar.horizontal.policy: ScrollBar.AlwaysOn - visible: embedImageListModel.count > 0 - enabled: !createRecord.running - clip: true - RowLayout { - spacing: 4 * AdjustedValues.ratio - Repeater { - model: EmbedImageListModel { - id: embedImageListModel - property int adjustPostLength: count > 4 ? 5 + (Math.ceil(count / 4) + "").length : 0 + BusyIndicator { + anchors.fill: parent + anchors.margins: 3 + visible: externalLink.running || + feedGeneratorLink.running || + listLink.running } - delegate: ImageWithIndicator { - Layout.preferredWidth: 102 * AdjustedValues.ratio - Layout.preferredHeight: 102 * AdjustedValues.ratio - fillMode: Image.PreserveAspectCrop - source: model.uri - TagLabel { - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.margins: 3 - visible: model.alt.length > 0 - source: "" - fontPointSize: AdjustedValues.f8 - text: "Alt" - } - TagLabel { - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 3 - visible: model.number.length > 0 - source: "" - fontPointSize: AdjustedValues.f8 - text: model.number - } - MouseArea { - anchors.fill: parent - onClicked: { - altEditDialog.editingIndex = model.index - altEditDialog.embedImage = model.uri - altEditDialog.embedAlt = model.alt - altEditDialog.open() + states: [ + State { + when: externalLink.valid || + feedGeneratorLink.valid || + listLink.valid || + (postLink.valid && postType !== "quote") + PropertyChanges { + target: externalLinkButton + iconSource: "../images/delete.png" + onClicked: { + externalLink.clear() + feedGeneratorLink.clear() + listLink.clear() + postLink.clear() + if(postType !== "quote"){ + quoteCid = "" + quoteUri = "" + quoteAvatar = "" + quoteDisplayName = "" + quoteHandle = "" + quoteIndexedAt = "" + quoteText = "" + } + } } } - IconButton { - enabled: !createRecord.running - width: AdjustedValues.b24 - height: AdjustedValues.b24 - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: 5 - iconSource: "../images/delete.png" - onClicked: embedImageListModel.remove(model.index) - } - } + ] } } - } - - Frame { - id: quoteFrame - Layout.preferredWidth: postText.width - Layout.maximumHeight: 200 * AdjustedValues.ratio - visible: quoteValid - clip: true - ColumnLayout { + ExternalLinkCard { Layout.preferredWidth: postText.width - RowLayout { - AvatarImage { - id: quoteAvatarImage - Layout.preferredWidth: AdjustedValues.i16 - Layout.preferredHeight: AdjustedValues.i16 - source: quoteAvatar - } - Author { - layoutWidth: postText.width - quoteFrame.padding * 2 - quoteAvatarImage.width - parent.spacing - displayName: quoteDisplayName - handle: quoteHandle - indexedAt: quoteIndexedAt - } - } - Label { - Layout.preferredWidth: postText.width - quoteFrame.padding * 2 - wrapMode: Text.WrapAnywhere - font.pointSize: AdjustedValues.f8 - text: quoteText - } - } - } + Layout.maximumHeight: 280 * AdjustedValues.ratio + visible: externalLink.valid - Label { - Layout.preferredWidth: postText.width - Layout.leftMargin: 10 - Layout.topMargin: 2 - Layout.bottomMargin: 2 - font.pointSize: AdjustedValues.f8 - text: createRecord.progressMessage - visible: createRecord.running && createRecord.progressMessage.length > 0 - color: Material.theme === Material.Dark ? Material.foreground : "white" - Rectangle { - anchors.fill: parent - anchors.leftMargin: -10 - anchors.topMargin: -2 - anchors.bottomMargin: -2 - z: -1 - radius: height / 2 - color: Material.color(Material.Indigo) + thumbImage.source: externalLink.thumbLocal + uriLabel.text: externalLink.uri + titleLabel.text: externalLink.title + descriptionLabel.text: externalLink.description } - } + FeedGeneratorLinkCard { + Layout.preferredWidth: postText.width + visible: feedGeneratorLink.valid - RowLayout { - spacing: 0 - Button { - enabled: !createRecord.running - flat: true - font.pointSize: AdjustedValues.f10 - text: qsTr("Cancel") - onClicked: postDialog.close() + avatarImage.source: feedGeneratorLink.avatar + displayNameLabel.text: feedGeneratorLink.displayName + creatorHandleLabel.text: feedGeneratorLink.creatorHandle + likeCountLabel.text: feedGeneratorLink.likeCount } - Item { - Layout.fillWidth: true - Layout.preferredHeight: 1 + ListLinkCard { + Layout.preferredWidth: postText.width + visible: listLink.valid + avatarImage.source: listLink.avatar + displayNameLabel.text: listLink.displayName + creatorHandleLabel.text: listLink.creatorHandle + descriptionLabel.text: listLink.description } - IconButton { - id: selfLabelsButton + + ScrollView { + Layout.preferredWidth: postText.width + Layout.preferredHeight: 102 * AdjustedValues.ratio + ScrollBar.horizontal.height + 1 + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + visible: embedImageListModel.count > 0 enabled: !createRecord.running - iconSource: "../images/labeling.png" - iconSize: AdjustedValues.i18 - flat: true - foreground: value.length > 0 ? Material.accent : Material.foreground - onClicked: selfLabelPopup.popup() - property string value: "" - SelfLabelPopup { - id: selfLabelPopup - onTriggered: (value, text) => { - if(value.length > 0){ - selfLabelsButton.value = value - selfLabelsButton.iconText = text - }else{ - selfLabelsButton.value = "" - selfLabelsButton.iconText = "" - } - } - onClosed: postText.forceActiveFocus() + clip: true + RowLayout { + spacing: 4 * AdjustedValues.ratio + Repeater { + model: EmbedImageListModel { + id: embedImageListModel + property int adjustPostLength: count > 4 ? 5 + (Math.ceil(count / 4) + "").length : 0 + } + delegate: ImageWithIndicator { + Layout.preferredWidth: 102 * AdjustedValues.ratio + Layout.preferredHeight: 102 * AdjustedValues.ratio + fillMode: Image.PreserveAspectCrop + source: model.uri + TagLabel { + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 3 + visible: model.alt.length > 0 + source: "" + fontPointSize: AdjustedValues.f8 + text: "Alt" + } + TagLabel { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 3 + visible: model.number.length > 0 + source: "" + fontPointSize: AdjustedValues.f8 + text: model.number + } + MouseArea { + anchors.fill: parent + onClicked: { + altEditDialog.editingIndex = model.index + altEditDialog.embedImage = model.uri + altEditDialog.embedAlt = model.alt + altEditDialog.open() + } + } + IconButton { + enabled: !createRecord.running + width: AdjustedValues.b24 + height: AdjustedValues.b24 + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 5 + iconSource: "../images/delete.png" + onClicked: embedImageListModel.remove(model.index) + } + } + } } } - IconButton { - enabled: !createRecord.running && - !externalLink.valid && - !feedGeneratorLink.valid && - !listLink.valid && - !embedImageListModel.running - iconSource: "../images/add_image.png" - iconSize: AdjustedValues.i18 - flat: true - onClicked: { - if(fileDialog.prevFolder.length > 0){ - fileDialog.folder = fileDialog.prevFolder + + Frame { + id: quoteFrame + Layout.preferredWidth: postText.width + Layout.maximumHeight: 200 * AdjustedValues.ratio + visible: quoteValid + clip: true + ColumnLayout { + Layout.preferredWidth: postText.width + RowLayout { + AvatarImage { + id: quoteAvatarImage + Layout.preferredWidth: AdjustedValues.i16 + Layout.preferredHeight: AdjustedValues.i16 + source: quoteAvatar + } + Author { + layoutWidth: postText.width - quoteFrame.padding * 2 - quoteAvatarImage.width - parent.spacing + displayName: quoteDisplayName + handle: quoteHandle + indexedAt: quoteIndexedAt + } + } + Label { + Layout.preferredWidth: postText.width - quoteFrame.padding * 2 + wrapMode: Text.WrapAnywhere + font.pointSize: AdjustedValues.f8 + text: quoteText } - fileDialog.open() } } Label { - Layout.leftMargin: 5 - Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: postText.width + Layout.leftMargin: 10 + Layout.topMargin: 2 + Layout.bottomMargin: 2 font.pointSize: AdjustedValues.f8 - text: 300 - embedImageListModel.adjustPostLength - postText.realTextLength - } - ProgressCircle { - Layout.leftMargin: 5 - Layout.preferredWidth: AdjustedValues.i24 - Layout.preferredHeight: AdjustedValues.i24 - Layout.alignment: Qt.AlignVCenter - from: 0 - to: 300 - embedImageListModel.adjustPostLength - value: postText.realTextLength + text: createRecord.progressMessage + visible: createRecord.running && createRecord.progressMessage.length > 0 + color: Material.theme === Material.Dark ? Material.foreground : "white" + Rectangle { + anchors.fill: parent + anchors.leftMargin: -10 + anchors.topMargin: -2 + anchors.bottomMargin: -2 + z: -1 + radius: height / 2 + color: Material.color(Material.Indigo) + } } - Button { - id: postButton - Layout.alignment: Qt.AlignRight - enabled: postText.text.length > 0 && - postText.realTextLength <= (300 - embedImageListModel.adjustPostLength) && - !createRecord.running && - !externalLink.running && - !feedGeneratorLink.running && - !listLink.running - font.pointSize: AdjustedValues.f10 - text: qsTr("Post") - onClicked: { - var row = accountCombo.currentIndex; - createRecord.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) - createRecord.clear() - createRecord.setText(postText.text) - createRecord.setPostLanguages(postDialog.accountModel.item(row, AccountListModel.PostLanguagesRole)) - if(postType !== "reply"){ - // replyのときは制限の設定はできない - createRecord.setThreadGate(selectThreadGateDialog.selectedType, selectThreadGateDialog.selectedOptions) - createRecord.setPostGate(selectThreadGateDialog.selectedQuoteEnabled, []) - } - if(postType === "reply"){ - createRecord.setReply(replyCid, replyUri, replyRootCid, replyRootUri) + + RowLayout { + spacing: 0 + Button { + enabled: !createRecord.running + flat: true + font.pointSize: AdjustedValues.f10 + text: qsTr("Cancel") + onClicked: postDialog.close() + } + Item { + Layout.fillWidth: true + Layout.preferredHeight: 1 + } + IconButton { + id: selfLabelsButton + enabled: !createRecord.running + iconSource: "../images/labeling.png" + iconSize: AdjustedValues.i18 + flat: true + foreground: value.length > 0 ? Material.accent : Material.foreground + onClicked: selfLabelPopup.popup() + property string value: "" + SelfLabelPopup { + id: selfLabelPopup + onTriggered: (value, text) => { + if(value.length > 0){ + selfLabelsButton.value = value + selfLabelsButton.iconText = text + }else{ + selfLabelsButton.value = "" + selfLabelsButton.iconText = "" + } + } + onClosed: postText.forceActiveFocus() } - if(quoteValid){ - createRecord.setQuote(quoteCid, quoteUri) + } + IconButton { + enabled: !createRecord.running && + !externalLink.valid && + !feedGeneratorLink.valid && + !listLink.valid && + !embedImageListModel.running + iconSource: "../images/add_image.png" + iconSize: AdjustedValues.i18 + flat: true + onClicked: { + if(fileDialog.prevFolder.length > 0){ + fileDialog.folder = fileDialog.prevFolder + } + fileDialog.open() } - if(selfLabelsButton.value.length > 0){ - createRecord.setSelfLabels([selfLabelsButton.value]) + } + + Label { + Layout.leftMargin: 5 + Layout.alignment: Qt.AlignVCenter + font.pointSize: AdjustedValues.f8 + text: 300 - embedImageListModel.adjustPostLength - postText.realTextLength + } + ProgressCircle { + Layout.leftMargin: 5 + Layout.preferredWidth: AdjustedValues.i24 + Layout.preferredHeight: AdjustedValues.i24 + Layout.alignment: Qt.AlignVCenter + from: 0 + to: 300 - embedImageListModel.adjustPostLength + value: postText.realTextLength + } + Button { + id: postButton + Layout.alignment: Qt.AlignRight + enabled: postText.text.length > 0 && + postText.realTextLength <= (300 - embedImageListModel.adjustPostLength) && + !createRecord.running && + !externalLink.running && + !feedGeneratorLink.running && + !listLink.running + font.pointSize: AdjustedValues.f10 + text: qsTr("Post") + onClicked: { + var row = accountCombo.currentIndex; + createRecord.setAccount(postDialog.accountModel.item(row, AccountListModel.UuidRole)) + createRecord.clear() + createRecord.setText(postText.text) + createRecord.setPostLanguages(postDialog.accountModel.item(row, AccountListModel.PostLanguagesRole)) + if(postType !== "reply"){ + // replyのときは制限の設定はできない + createRecord.setThreadGate(selectThreadGateDialog.selectedType, selectThreadGateDialog.selectedOptions) + createRecord.setPostGate(selectThreadGateDialog.selectedQuoteEnabled, []) + } + if(postType === "reply"){ + createRecord.setReply(replyCid, replyUri, replyRootCid, replyRootUri) + } + if(quoteValid){ + createRecord.setQuote(quoteCid, quoteUri) + } + if(selfLabelsButton.value.length > 0){ + createRecord.setSelfLabels([selfLabelsButton.value]) + } + if(externalLink.valid){ + createRecord.setExternalLink(externalLink.uri, externalLink.title, externalLink.description, externalLink.thumbLocal) + createRecord.postWithImages() + }else if(feedGeneratorLink.valid){ + createRecord.setFeedGeneratorLink(feedGeneratorLink.uri, feedGeneratorLink.cid) + createRecord.post() + }else if(listLink.valid){ + createRecord.setFeedGeneratorLink(listLink.uri, listLink.cid) + createRecord.post() + }else if(embedImageListModel.count > 0){ + createRecord.setImages(embedImageListModel.uris(), embedImageListModel.alts()) + createRecord.postWithImages() + }else{ + createRecord.post() + } } - if(externalLink.valid){ - createRecord.setExternalLink(externalLink.uri, externalLink.title, externalLink.description, externalLink.thumbLocal) - createRecord.postWithImages() - }else if(feedGeneratorLink.valid){ - createRecord.setFeedGeneratorLink(feedGeneratorLink.uri, feedGeneratorLink.cid) - createRecord.post() - }else if(listLink.valid){ - createRecord.setFeedGeneratorLink(listLink.uri, listLink.cid) - createRecord.post() - }else if(embedImageListModel.count > 0){ - createRecord.setImages(embedImageListModel.uris(), embedImageListModel.alts()) - createRecord.postWithImages() - }else{ - createRecord.post() + BusyIndicator { + anchors.fill: parent + anchors.margins: 3 + visible: createRecord.running } } - BusyIndicator { - anchors.fill: parent - anchors.margins: 3 - visible: createRecord.running - } } } + DragAndDropArea { + anchors.fill: parent + anchors.margins: -5 + onDropped: (urls) => embedImageListModel.append(urls) + } } } diff --git a/app/qml/main.qml b/app/qml/main.qml index 8c017d86..24876328 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -1071,6 +1071,32 @@ ApplicationWindow { visible: false } + DragAndDropArea { + anchors.fill: parent + anchors.margins: 5 + enabled: accountListModel.count > 0 + && !imageFullView.visible + && !messageDialog.visible + && !logViewDialog.visible + && !selectThreadGateDialog.visible + && !addMutedWordDialog.visible + && !editProfileDialog.visible + && !addListDialog.visible + && !addToListDialog.visible + && !reportMessageDialog.visible + && !reportAccountDialog.visible + && !reportDialog.visible + && !columnsettingDialog.visible + && !discoverFeedsDialog.visible + && !accountDialog.visible + && !addColumnDialog.visible + && !searchDialog.visible + && !postDialog.visible + && !settingDialog.visible + onDropped: (urls) => postDialog.openWithFiles(urls) + } + + Component.onCompleted: { if(accountListModel.count === 0){ globalProgressFrame.text = qsTr("Loading account(s) ...") diff --git a/app/qtquick/controls/embedimagelistmodel.cpp b/app/qtquick/controls/embedimagelistmodel.cpp index 6a082728..28419c4f 100644 --- a/app/qtquick/controls/embedimagelistmodel.cpp +++ b/app/qtquick/controls/embedimagelistmodel.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include EmbedImageListModel::EmbedImageListModel(QObject *parent) : QAbstractListModel { parent }, m_count(0), m_running(false) @@ -80,15 +82,29 @@ void EmbedImageListModel::remove(const int row) } } -void EmbedImageListModel::append(const QStringList &uris) +bool EmbedImageListModel::append(const QStringList &uris) { if (uris.isEmpty() || running()) - return; + return false; setRunning(true); - m_uriCue = uris; + m_uriCue.clear(); + + QStringList exts; + exts << "jpg" + << "jpeg" + << "png" + << "gif"; + for (const auto &uri : uris) { + QFileInfo info(QUrl(uri).toLocalFile()); + if (exts.contains(info.suffix().toLower())) { + m_uriCue.append(uri); + } + } QTimer::singleShot(10, this, &EmbedImageListModel::appendItem); + + return !m_uriCue.isEmpty(); } void EmbedImageListModel::updateAlt(const int row, const QString &alt) diff --git a/app/qtquick/controls/embedimagelistmodel.h b/app/qtquick/controls/embedimagelistmodel.h index 1919224f..c4524875 100644 --- a/app/qtquick/controls/embedimagelistmodel.h +++ b/app/qtquick/controls/embedimagelistmodel.h @@ -36,7 +36,7 @@ class EmbedImageListModel : public QAbstractListModel Q_INVOKABLE void clear(); Q_INVOKABLE void remove(const int row); - Q_INVOKABLE void append(const QStringList &uris); + Q_INVOKABLE bool append(const QStringList &uris); Q_INVOKABLE void updateAlt(const int row, const QString &alt); Q_INVOKABLE QStringList uris() const; diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index a63464a1..7e83f148 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -10,6 +10,7 @@ description: This is a multi-column Bluesky client. - Add - Add a link to user's profile if user is registered with Linkat + - Support for accepting attached images by drag and drop - Update - Change the format of the embedded via in the post - Change the internal format of feed storage to V2 diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index 7a4bdce0..67e76649 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -10,6 +10,7 @@ description: マルチカラム対応Blueskyクライアント - 追加 - Linkatに登録があるときリンクをプロフィールに追加 + - 添付画像をドラッグアンドドロップでの受付に対応 - 変更 - ポストに埋め込むviaの形式を変更 - フィードの保存の内部形式をV2に変更 From a8838bb64975856e7080bbec45ee91455bb72a5e Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Thu, 31 Oct 2024 01:41:48 +0900 Subject: [PATCH 124/127] =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/i18n/app_ja.qm | Bin 42278 -> 42346 bytes app/i18n/app_ja.ts | 306 +++++++++++++++++++++++---------------------- 2 files changed, 157 insertions(+), 149 deletions(-) diff --git a/app/i18n/app_ja.qm b/app/i18n/app_ja.qm index 0ea89b691c770ea5bc7ce54f911361e484c97a76..01e66a6e614c46c25518dfe331b25db43017848f 100644 GIT binary patch delta 4709 zcmXY#cU%>5jK zPqAz4f=aPIEEu0HmS>AG*kgOY^}av&d}ns%-h28v!{$cmvqov9b+1Gs@+8Vr5%GhG z+Pg3YOk)h5&sdzlbhMq`hbV9tk)x7moSw+dl_=sSk&l76c?*baw-J}0OVpziacc^R zEPrI|@{F+91JHlb_8^ks3!@W0S;TGZ^7ZK&Q68G08 zqNRU!8R+Ond=EDwS4ZOQ+Y^~pgB!7CHDi#Jc&7(M+#KRvCla}LGYplOTaClA#9lp;x|LnEO+8}W)Y3KN_;&8^mQ`ql32uiBMWB_qJ&ub zA$BW~Vn1V;j;te&QW(em zpObhBsr(x@x22G(p^x1BL5y=dQE2jBqU<=Dcy<%wseQchf+*aECZ2=l?W-uP z5CV;eqOdwhKKulQy+g2$te|iX2WKCl@VrlO7f;~@pFvj&FGU=VNulr-yg#}(g}-nj zDyyZ)a6Hd`M3K|rD5n(^xynE^b^~LMmLlu)Frn`{iV|Tu-xP|fg=ze9DLOU;d`Hpm zU_$>slyndq<_w`sqvY`-WuM%Mz|5f^OXG3SLaMsI7EyJXsy<1HBzCl=zmCXR!?^Gu z9W?JuG|P<|LS7@Vm{xteajyl=4-w?-Mlk&XgRp>_*hxRdU*fC-P8Bo{o8m%$y;4x)&BLw2*xG z>PWPtjZ}67>uiFg;@xl}moHMiW;60)(tc^1y8{usvC^Q|gNZ^(8dqV9RQ6^p&yXfx zA4y~(m!|3v6I1(0Q(K|INR_m3NioT&Fv#{97|u4M+% zAdSp;j<}16xTT!kFdS(5nd`F>IcL^|aiKrw)SN*i z(=m>J%=tgqgfcjoi{F)mA`-==>W;%-m$~$-2oP~Q|Lbz@vitmbTy;}4yuXlJa~p}3yM^0g z9!xaqD7PgDiQ2D;QNO^0t7&?Q1d?%k2njMMpF90;I7*nxT~=;@-aWX>+FYXkd${Wk z2}JFlF-C_n#w2prn`?=zuP}D4;#y9^ZVo>(F0|oV>TsZaH^w4o?$)^{*k>5`A44u2 zIAFl{3o0t(9_Lu2I1=}~`W&k7A?`(a1^AMCv9B*O^9=XZCYDH=$T&Zj7rM=bz_q-I z)n5>}nKvs!{CD`sTSk^3(;xF4L`!7q1ini@WVXu^zDF4pas8QbktvVBKoM;-mG33g zp=ulWG4)yqkjneUT>;nee#QTC}rKs>+B2M!+j55FNbhsZ0QvGhK_*%j(}weZ`W5FryP_#J~Ep%<*tG}t7x-313{oe(;N!A{di=y@K^$xkadB*PzE zli*?vd&zeRu0Ai(%={QjG6lD1P&YP07=2O(zuaXEnIL$tyoiW$O!|I73(JI%E08Gm zXCa{h3qq?5x$AH+UnP`9grW9-6P6XAn~w4omVber z`@RuYdBQIiPlRbe-{nDCPp=l2yV^-A_31 zFX7CtU(js!3H9EHG~G3!VJ!sn>@S>e9{^XZW-L<(w~fUv-a&Zt-iat+z3_S4RTM>% zTR(z8X+Df|OXU6bKSZtE!8oT`?mkoh6(-y-AMtoOBpfOa?HflF_D~)g1V*&TQ(wR$ z8e93YlvbiX3G$yV&qj4VEMJjo22IlBHMI~_-z48Fps7uJ&N#b={EYl21pQrpZ4X?U zcAhaKi!n=--|CEzZd)dQtmoRJ==GI9Z^VM6T=}bQcQ9C7lDA%dMAStm|4;za>r3Px z?&CnKD)}c*4g#i#rhh|Y>08n6HUu9$T3|lBTRn&jKD0?L)#9brG?=B`}jYPNWC?@}>EPAFFcN!a9;tsLK{3|58DDE%Wk9rs;9$9-IQQTgvYe~Yw^Z zfwIK&+wgu*6Y*01SZs7!ymZeGv*GWIQ|5}z78$5g-^3e@_?$@1W}G>PF|$g%x1t$A z`;oCr*V*hMYPI;_ts6pSnnE%F<;(VtLfLZ$I)bI)go(L6u&cspJzP0XV2rj=xEtpX zyLiRW-E|l#k1@{mSNI*sMfLhY;h%u#d^}_4Q;Lb(@1T)HD`JZnX*;*}rv z>FcX_9R`Dk=PN$fx8Ock$vuJIUOJ^|Io5|xGqO8IlU~X`g>e{aW0XVoffF;7Lt4SW z)yh%7Z6K;lH@w$$)<0AxI9^2`%upumff>8=%ETb(JF1AW%u$*6W(QUUGfv*7ObRj{ zdr+D7?H~rm^~y}+DM9^}^9$t2glc7B=^CQ)1IlXG_OSO_<*IE^XV_xp0pH{3v!;xN zxyp+Yyw}^*l3jEQR^E7(g@a!!ZyD#k(NTXn0 zMCPt7u-MXr#6ivb4=E}I_zp4f0 z&?|DQs$e_JY4t@_^tuh&XM<{aRys2KkZSXZWkj9KRlDcDM8*oLBaJ-#>bXXB(%~~E z!tJVtE|Am5ma)9Es-YWR^a)qp+qMs>zF+nH4N5|6v+C7H+{gA}Oyg9aJSQNPm#O*t zFi_`rYO&lKbAXxJwCXTEB@)!_jBzm5T5YzNM=eWHcf9Ke#lER~^0BDC4j0sYB9HvFqM7NQ#;F&rw1c8&>PdzSV3DFPl)Ny&S$jBCT;;o&iIUm)Dts1n2 zR`u-7NbTYa>XOK7@Jx=nDh8>q%vP_|!&3=T_1c?o$h>6rFYf}7@we0)KkHw>R8hD9 zgH|(!ZDpL|q5jnwFD5Ke*CgLWc|WZ_B0*~Z(4@X(DuWBk)c3653aftVS1z@vS)nGp zF_ij0VJy6EGByzwaQxLI*0}%^`Aw4od?=Bhx5?U)bkrVOlihlkpHaM`P3r31kuuRH zpDpm9vXh3EV#DrPnt_{OGWS}I^RbTb?NiNY0}hs2Yy9L8Y^g>Q;Q~`nU8YI&+X>SL zYElPnMXLE~GS0yz$s0BEk7Iq?AjZ7a8hv^*CZHxw-YyTgC`h9(OzVtX+^AXf@F)ba z(o_t?N6}DA&FTdk&`+`$XC2i1Yx^u{FhKLK_u%BAnnT5K$$(p$bH=Z*QEJWAu1-kF z?V8)Vm$1}F&Aql71YN7<;}cILS9`5w&qq}EomwFe0?f10Di6S--I`6cK7+78{X!d& zHUeK)&kQ%(+Ii+_YksxE{}oPa_v-IpWb|TO)T-UPA_*nvwDwSa5z+K!#yO-t%Gu&$ zWgKIMgJEbp3&lXn5yL4~J5~D>(mWouCBw140k=&Lkl@?jEmb#^Vp_)rd$S{n- z#MpN;gi2!>%kZL zIYiyQVzjDdEIm%#_F|$H{lNo7178w%1Ok}%BJLQ6y{n14upiIfj5*1~wd4|I93<|q ztwiO|t%f*#B);!(BG-4sJ9Z{As|3HnnhlHr=ZJTHNW?8Bepo2c$Uh9DC0)7)VOfG5 zkw+o%i!nW781cmmi3(MQxe{CRg~V@5Ct3z&Yw>3yHm!$%KJN{?C0(P>P*)dsqIg^C z5%V39;s9f4D_Mk{#P8|kbmI!tdPW`t8(=F_@~WtVO<&W*B@p$CNsPU(F{aHWZ|7>* z-IFnBt-)4muMch@AD1;mQxpH=WAe$w^58c#NmWTSD4a3tHce_YK7VA)&L_W|uZbKB z$nW%0uqXK!A0rxU$C$f>{7cG-hImr|2l0orP(a%&qU8w`ScZL^t0?H%6w;IVISP(# zhFkklaM35C;e#mn<7=Y)LYf?b=P6ki>PeIHKsR%mQUZb9KGKv%xGm6uG59}5 zHc&{}-$bLIP)NmS*xrIdYEKg_sAF96fTktwBTDT@q35aU zOjMjt;X1re{f@%t!coqV6u#a-6co>xdX>WK^f2M1Sc(u~x=9WcaR#RG^`yv{5b!8P zzJUpS|Dwc0*f32(ON^4oHc{%2wFu17^i@$D4)URj2Q`SQ)l~68N+hwR?GE-h@E69s zG&v|ti`@7qaQd06RAUrR<(TL!Sb(G%t84^cdl-@jdlBjGDl=+Onz9xo? zyj&mkLN?9)7EzI-Y?|?HhicjMGN}9IP?_%CZ$zcJvhacHh-U1O&HnB>POFs7k-HPk z{71Ir^ZcJ>*}Kj{&AqaMq9aHRUs+Y40jWNpQF%$W{$2*8UMs7~-bkcsk{ymHAX@xC z+2LJI$iaDoWoP%`V6#S9Q!&ce@*}caW(esnu^e$1VehG&tp^S?ZRZA5Bj?Pl81p)C z&R0{2WcG}ccW{0Wx1tP=2+|lI>4I(AC_WYX}f=C*zmLxh2N^It=D=?sss5 z<8rwChOtD8W^x5fVW+-FxdLOY?$^eZ6yVvmnkzBp7%$^i#{G&wSiqGH%ffrLK`VFF z2XK|%#}RK5M!&a=Q*0QgY8k`b8Iv3t=e}oLG?;POf9uk=u;(lfuJS@8yzj$pyo>KP)7D~#4U?&goMn^Op5ZVPv_4hK5?!I<+8cl-Qf?Bl`R zF=W7jj%}X@lpDacq*)*W>bR$s=TUW+bI(dj!M)tG{R4^I3%O6dVu+*(jOm`dV6zkg z=kq4r|AN3*d9z%^zxjKgV#vQe9=7^kl1r>;S$rYram!*Uc|8Dsu$ zeApiRp7Dr}dXM#^WBG&{G=Gm{d{P!F#jJ&V(oarkGm-q9FL7(g4&oO(`oT0~`eOhV zB=RK_kPCV%z9b1I?S7F_XUdltTW}Y9M%@v8hE#VXfE~Zd2M!*0lHZb?Ml{}zv1lv5Z5Y%WznEEPKLhJ%-73+B^dr#W>(za})NNmm7@ zB>027Ah=q_-FrODt*Oakvq%izXs2gJ`c>O4YUz!<1x(VLZ7ZFiI2Ynt;UVspC z1ro)C3GoeB5TauYyCbCeE+mQ?CZsJzWOX~i7)nCMCLGLH2*qL3iE_>etFzHfCuoJL zkFfK=!@~M8@JrXn!luOc(7Z|5>=}s?^0TnzLW%6kQV{t#&E}s?%hOL(; zKZ8XyedMcWwGs^&AYXHN5vp^#d|k2`G?^{ma|VLyOXd3nG_|?A85jO3KP$h5NZlyE zz85Z?UB;L)ig9U^{I(@Rx?{1tMbC9c5qv3s+K2^-p7NJF?%|?gkhflbL}b}2f0qr@ z>;2^K9^k<474i>bI0)z5FM&(aWxARUDXXJmY$+}!9MKkCwlbi zL=?JB^xxY?l-Dlm>{p|kP7!q}eV|}}QU7^RykCruy^gm0Sd3pbj>zi1nBWb~TojCX z--xq@V59j5#aTgxm{%dr3U2~WiTa+f(8^3Pvr{CZ{3K(ETFfr^34-K`g{_|8ak0|) z-p)>}84rP7?uk1d{*ElF)r+;p2A42T+|%U~B>PG{PcEz8;H^Ij<;MVf_82qSCP|vb6A{qP`TtKkf@fgBv{6|CplT z(pgyWzM|2X6rP6}3$H1fjyxpF-Ke;F1_JwUSKKbLKtJ$T+?}OBmTDDGH-17aZ&JMU z#XbYIidWNNaNT%Cd;Lv3dn&od(0g2~(zFEYr#Ttfn`qT@<$#=6SfGz`)P6A3RXM5^ z42V`vIJ$+XY>45l#!|ms89($I`e2GOelN^u)1XWUguWBJ8H?U26JGDaDs#rDIm*O9 zd#tFm{(oZUaFa$j{o`&_B2S~drny?mzsD5;6 z$Bl5Vs=*3!dOu+-eyD1&!9*Vm)%_j&k?IFjPhX=X#H>}le2?cD&l!{JRUgJoK`Mu; z`3EqNQ*Byt1fLQE)SZlR5cr$gY$cCcHcS0Q^H3;uM%|B(LG^ViR}TnV zjcQl32fgcB6t7+;b@gsY znO-LCUGbvyfriSlVee6zAzNWGw|tGui7(*W?=@Zq94xia_{t$z*)N(fSD1Qcs3v*D zcStd8h=@H&Dt5MI9GGm z{sk;_LUX^P2Ch1&dH;9}4!EV2?0rv^wLmLmLV&b;TIE4l(q^sJX9VV}pNZPA*<カラムの追加 - + Account アカウント - + Column type カラムタイプ @@ -112,17 +112,17 @@ カスタムフィードの検索 - + Cancel キャンセル - + Logs ログ - + Add 追加 @@ -140,37 +140,37 @@ リストの編集 - + Avatar アイコン - + Name 名称 - + Description 説明 - + Cancel キャンセル - + Add 追加 - + Update 更新 - + Select contents コンテンツの選択 @@ -226,12 +226,12 @@ リストへ追加/削除 - + Add list リストの追加 - + Close 閉じる @@ -273,14 +273,14 @@ AtpAbstractListModel - - + + Blocked ブロック中 - - + + Detached by author 投稿者によって切り離し済み @@ -293,7 +293,7 @@ ブロック中のアカウント - + Close 閉じる @@ -306,7 +306,7 @@ ブロック中のリスト - + Close 閉じる @@ -324,27 +324,27 @@ Please recreate AppPassword in the official application. ChatListView - + Unmute conversation チャットのミュートを解除 - + Mute conversation チャットをミュート - + Leave conversation チャットを離脱 - + Start a new chat 新しいチャットを開始 - + Search 検索 @@ -352,37 +352,37 @@ Please recreate AppPassword in the official application. ChatMessageListView - + Quoted content warning 閲覧注意な引用 - + Blocked ブロック中 - + Copy message メッセージをコピー - + Delete for me 自分宛を削除 - + Report message メッセージを通報 - + Post url or at-uri ポストのURLまたはat-uri - + Write a message メッセージを書く @@ -593,94 +593,94 @@ Please recreate AppPassword in the official application. 引用ポスト - + Home ホーム - + Notifications 通知 - + Search posts 検索(ポスト) - + Search users 検索(ユーザー) - - + + Feed フィード - + User ユーザー - + List リスト - - + + Chat チャット - + Realtime リアルタイム - + Unknown 不明 - + Move to left 左へ移動 - + Move to right 右へ移動 - + Delete column 削除 - + Copy url URLをコピー - + Open in Official 公式で開く - + Drop 解除 - + Save 保存 - + Settings 設定 @@ -748,21 +748,29 @@ Please recreate AppPassword in the official application. カスタムフィードの検索 - + Search 検索 - + Cancel キャンセル - + Add 追加 + + DragAndDropArea + + + Detecting... + ドロップする + + EditProfileDialog @@ -771,27 +779,27 @@ Please recreate AppPassword in the official application. プロフィールの編集 - + Display Name 表示名 - + Description 説明 - + Cancel キャンセル - + Update 更新 - + Select contents コンテンツの選択 @@ -1858,67 +1866,67 @@ Please recreate AppPassword in the official application. ListDetailView - + List リスト - + Edit 編集 - + Muted ミュート中 - + Blocked ブロック中 - + Copy Official Url 公式のURLをコピー - + Open in new col 新しいカラムで開く - + Open in Official 公式で開く - + Delete list リストを削除 - + Unmute list リストのミュート解除 - + Mute list リストをミュート - + Unblock list リストのブロック解除 - + Block list リストのブロック - + Users ユーザー @@ -1926,32 +1934,32 @@ Please recreate AppPassword in the official application. ListsListView - + Unmute ミュート解除 - + Mute ミュートする - + Unblock ブロック解除 - + Block ブロックする - + Muted ミュート中 - + Blocked ブロック中 @@ -1989,7 +1997,7 @@ Please recreate AppPassword in the official application. 月毎 - + Close 閉じる @@ -2048,7 +2056,7 @@ Please recreate AppPassword in the official application. ミュート中のアカウント - + Close 閉じる @@ -2061,7 +2069,7 @@ Please recreate AppPassword in the official application. ミュート中のリスト - + Close 閉じる @@ -2080,12 +2088,12 @@ Please recreate AppPassword in the official application. NotificationDelegate - + Post from an account you muted. ミュートしているアカウントのポスト - + signed up with your starter pack あなたのスターターパックで登録しました @@ -2217,7 +2225,7 @@ Please recreate AppPassword in the official application. PostDelegate - + Post from an account you muted. ミュートしているアカウントのポスト @@ -2253,7 +2261,7 @@ Please recreate AppPassword in the official application. リンクカード - + Link card URL リンクカードのURL @@ -2266,22 +2274,22 @@ Please recreate AppPassword in the official application. リンクカードかフィードカードかリストカードのURL - + Link card URL, Custom feed URL, List URL, Post URL リンクカード/フィードカード/リストカード/ポストのURL - + Cancel キャンセル - + Post ポスト - + Select contents コンテンツの選択 @@ -2321,12 +2329,12 @@ Please recreate AppPassword in the official application. ミュート中 - + Follows you あなたをフォロー中 - + Muted user ミュート中 @@ -2444,12 +2452,12 @@ Please recreate AppPassword in the official application. 通報 - + Account blocked ブロックしたアカウント - + Account muted ミュートしたアカウント @@ -2458,7 +2466,7 @@ Please recreate AppPassword in the official application. このアカウントに設定されたラベル : - + This account has blocked you あなたをブロックしているアカウント @@ -2490,134 +2498,134 @@ Please recreate AppPassword in the official application. RecordOperator - - + + Posting ... %1 - + Posting ... - + Repost ... - + Like ... - + Follow ... - + Mute ... - + Block ... - + Block list ... - + Create list ... (%1) - + Add to list ... - + Delete post ... - + Delete like ... - + Delete repost ... - + Unfollow ... - + Unmute ... - + Unblock ... - + Unblock block list ... - + Delete list ... - + Delete list item ... - + Update profile ... (%1) - + Update post pinning ... (%1) - + Update list ... (%1) - + Update who can reply ... - - + + Update quote status ... - + Uploading images ... (%1/%2) - + Delete list item ... (%1) @@ -2907,57 +2915,57 @@ Why should this message be reviewed? 投稿への反応の設定 - + Quote settings 引用の設定 - + Quote posts enabled 引用を可能にする - + Reply settings 返信の設定 - + Everybody 誰でも - + Nobody 自分のみ - + Combine these options 以下の組み合わせ - + Mentioned users メンションするユーザー - + Followed users フォローしているユーザー - + Users in "%1" "%1"のユーザー - + Cancel キャンセル - + Apply 適用 @@ -3349,42 +3357,42 @@ Why should this message be reviewed? SuggestionProfileListView - + @%s can't be messaged. @%sにメッセージを送れません。 - + Chat:All チャット:全員 - + Chat:Following チャット:フォローしているユーザー - + Chat:None チャット:誰からも受け取らない - + Chat:Not set チャット:未設定 - + Following フォロー中 - + Follows you あなたをフォロー中 - + Muted user ミュート中 @@ -3400,7 +3408,7 @@ Why should this message be reviewed? TimelineView - + Quoted content warning 閲覧注意な引用 @@ -3413,7 +3421,7 @@ Why should this message be reviewed? - + Search posts 検索(ポスト) @@ -3437,23 +3445,23 @@ Why should this message be reviewed? いくつかのアカウントでログインが必要です。 - - + + Updating 'Edit interaction settings' ... 投稿への反応の設定を更新中 ... - + Loading lists リストの読み込み中 - + Chat チャット - + Loading account(s) ... アカウント情報の読み込み中 ... From 0b422bac2ba04650943da1c11419d92ff6207fe1 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 1 Nov 2024 22:57:03 +0900 Subject: [PATCH 125/127] =?UTF-8?q?jetstream=E3=81=AE=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=AB=E8=BF=BD=E5=BE=93=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atproto/sync/comatprotosyncsubscribereposex.cpp | 10 +++++----- web/content/docs/release-note.en.md | 1 + web/content/docs/release-note.ja.md | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp index 86ce548e..39b3c7c1 100644 --- a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp +++ b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp @@ -162,16 +162,16 @@ void ComAtprotoSyncSubscribeReposEx::messageReceivedFromJetStream(const QByteArr QJsonDocument doc = QJsonDocument::fromJson(message); QJsonObject json_src = doc.object(); QHash commit_type_to; - commit_type_to["c"] = "create"; - commit_type_to["u"] = "update"; - commit_type_to["d"] = "delete"; + commit_type_to["create"] = "create"; + commit_type_to["update"] = "update"; + commit_type_to["delete"] = "delete"; if (doc.isNull() || json_src.isEmpty()) { qDebug().noquote() << "Invalid data"; qDebug().noquote() << "message:" << message; emit errorOccured("InvalidData", "Unreadable JSON data."); m_webSocket.close(); - } else if (!json_src.contains("type") || !json_src.contains("did") + } else if (!json_src.contains("kind") || !json_src.contains("did") || !json_src.contains("commit")) { qDebug().noquote() << "Unsupport data:" << message; // emit errorOccured("InvalidData", "Unanticipated data structure."); @@ -194,7 +194,7 @@ void ComAtprotoSyncSubscribeReposEx::messageReceivedFromJetStream(const QByteArr json_dest.insert("commit", json_dest_commit); QJsonObject json_dest_op; - QString commit_type = commit_type_to.value(json_src_commit.value("type").toString()); + QString commit_type = commit_type_to.value(json_src_commit.value("operation").toString()); json_dest_op.insert("action", commit_type); json_dest_op.insert("path", QString("%1/%2").arg(json_src_commit.value("collection").toString(), diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index 7e83f148..1136e5b6 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -14,6 +14,7 @@ description: This is a multi-column Bluesky client. - Update - Change the format of the embedded via in the post - Change the internal format of feed storage to V2 + - Keeping up with jetstream changes ### v0.39.0 - 2024/10/12 diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index 67e76649..e4b9bb72 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -14,6 +14,7 @@ description: マルチカラム対応Blueskyクライアント - 変更 - ポストに埋め込むviaの形式を変更 - フィードの保存の内部形式をV2に変更 + - jetstreamの変更に追従する ### v0.39.0 - 2024/10/12 From 47d1e15a45f0b6d11b5f5aac4d3ffdc7d1f30255 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 1 Nov 2024 23:00:24 +0900 Subject: [PATCH 126/127] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sync/comatprotosyncsubscribereposex.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp index 39b3c7c1..8fdb008a 100644 --- a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp +++ b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp @@ -161,10 +161,10 @@ void ComAtprotoSyncSubscribeReposEx::messageReceivedFromJetStream(const QByteArr QJsonDocument doc = QJsonDocument::fromJson(message); QJsonObject json_src = doc.object(); - QHash commit_type_to; - commit_type_to["create"] = "create"; - commit_type_to["update"] = "update"; - commit_type_to["delete"] = "delete"; + QHash commit_kind_to; + commit_kind_to["create"] = "create"; + commit_kind_to["update"] = "update"; + commit_kind_to["delete"] = "delete"; if (doc.isNull() || json_src.isEmpty()) { qDebug().noquote() << "Invalid data"; @@ -194,12 +194,12 @@ void ComAtprotoSyncSubscribeReposEx::messageReceivedFromJetStream(const QByteArr json_dest.insert("commit", json_dest_commit); QJsonObject json_dest_op; - QString commit_type = commit_type_to.value(json_src_commit.value("operation").toString()); - json_dest_op.insert("action", commit_type); + QString commit_op = commit_kind_to.value(json_src_commit.value("operation").toString()); + json_dest_op.insert("action", commit_op); json_dest_op.insert("path", QString("%1/%2").arg(json_src_commit.value("collection").toString(), json_src_commit.value("rkey").toString())); - if (commit_type == "delete") { + if (commit_op == "delete") { json_dest_op.insert("cid", QJsonValue()); } else { json_dest_op.insert("cid", json_dest_commit); From 80432589cef28fc827c82312f115a23daf11ce00 Mon Sep 17 00:00:00 2001 From: Takayuki Orito Date: Fri, 1 Nov 2024 23:29:57 +0900 Subject: [PATCH 127/127] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E6=83=85=E5=A0=B1=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.cpp | 2 +- web/content/docs/release-note.en.md | 2 ++ web/content/docs/release-note.ja.md | 2 ++ web/layouts/shortcodes/download_link.html | 8 ++++---- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/main.cpp b/app/main.cpp index 086bfa68..6a79d606 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) app.setOrganizationName(QStringLiteral("relog")); app.setOrganizationDomain(QStringLiteral("hagoromo.relog.tech")); app.setApplicationName(QStringLiteral("Hagoromo")); - app.setApplicationVersion(QStringLiteral("0.39.0")); + app.setApplicationVersion(QStringLiteral("0.40.0")); #ifndef HAGOROMO_RELEASE_BUILD app.setApplicationVersion(app.applicationVersion() + "d"); #endif diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md index 1136e5b6..76d902d9 100644 --- a/web/content/docs/release-note.en.md +++ b/web/content/docs/release-note.en.md @@ -8,6 +8,8 @@ description: This is a multi-column Bluesky client. ## 2024 +### v0.40.0 - 2024/11/1 + - Add - Add a link to user's profile if user is registered with Linkat - Support for accepting attached images by drag and drop diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md index e4b9bb72..9c0bbb65 100644 --- a/web/content/docs/release-note.ja.md +++ b/web/content/docs/release-note.ja.md @@ -8,6 +8,8 @@ description: マルチカラム対応Blueskyクライアント ## 2024 +### v0.40.0 - 2024/11/1 + - 追加 - Linkatに登録があるときリンクをプロフィールに追加 - 添付画像をドラッグアンドドロップでの受付に対応 diff --git a/web/layouts/shortcodes/download_link.html b/web/layouts/shortcodes/download_link.html index 61421fac..b923c363 100644 --- a/web/layouts/shortcodes/download_link.html +++ b/web/layouts/shortcodes/download_link.html @@ -1,9 +1,9 @@