summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2019-11-09 14:50:31 -0500
committerGravatar GitHub2019-11-09 14:50:31 -0500
commit8714d40a776f655c644392bc2454ace98064144e (patch)
treed272c2bb1d28ea072d40a9c89b7b510b56229525 /src
parentMerge pull request #3082 from ReinUsesLisp/fix-lockers (diff)
parentweb-service: Port citra's updated web_backend code. (diff)
downloadyuzu-8714d40a776f655c644392bc2454ace98064144e.tar.gz
yuzu-8714d40a776f655c644392bc2454ace98064144e.tar.xz
yuzu-8714d40a776f655c644392bc2454ace98064144e.zip
Merge pull request #3085 from bunnei/web-token-b64
yuzu: configure_web: Use Base64 encoded token
Diffstat (limited to 'src')
-rw-r--r--src/web_service/web_backend.cpp52
-rw-r--r--src/web_service/web_backend.h23
-rw-r--r--src/yuzu/configuration/configure_web.cpp73
-rw-r--r--src/yuzu/configuration/configure_web.ui12
4 files changed, 110 insertions, 50 deletions
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index dc149d2ed..6683f459f 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -2,10 +2,12 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array>
5#include <cstdlib> 6#include <cstdlib>
6#include <mutex> 7#include <mutex>
7#include <string> 8#include <string>
8#include <LUrlParser.h> 9#include <LUrlParser.h>
10#include <fmt/format.h>
9#include <httplib.h> 11#include <httplib.h>
10#include "common/common_types.h" 12#include "common/common_types.h"
11#include "common/logging/log.h" 13#include "common/logging/log.h"
@@ -16,10 +18,10 @@ namespace WebService {
16 18
17constexpr std::array<const char, 1> API_VERSION{'1'}; 19constexpr std::array<const char, 1> API_VERSION{'1'};
18 20
19constexpr u32 HTTP_PORT = 80; 21constexpr int HTTP_PORT = 80;
20constexpr u32 HTTPS_PORT = 443; 22constexpr int HTTPS_PORT = 443;
21 23
22constexpr u32 TIMEOUT_SECONDS = 30; 24constexpr std::size_t TIMEOUT_SECONDS = 30;
23 25
24struct Client::Impl { 26struct Client::Impl {
25 Impl(std::string host, std::string username, std::string token) 27 Impl(std::string host, std::string username, std::string token)
@@ -31,8 +33,9 @@ struct Client::Impl {
31 } 33 }
32 34
33 /// A generic function handles POST, GET and DELETE request together 35 /// A generic function handles POST, GET and DELETE request together
34 Common::WebResult GenericJson(const std::string& method, const std::string& path, 36 Common::WebResult GenericRequest(const std::string& method, const std::string& path,
35 const std::string& data, bool allow_anonymous) { 37 const std::string& data, bool allow_anonymous,
38 const std::string& accept) {
36 if (jwt.empty()) { 39 if (jwt.empty()) {
37 UpdateJWT(); 40 UpdateJWT();
38 } 41 }
@@ -43,11 +46,11 @@ struct Client::Impl {
43 "Credentials needed"}; 46 "Credentials needed"};
44 } 47 }
45 48
46 auto result = GenericJson(method, path, data, jwt); 49 auto result = GenericRequest(method, path, data, accept, jwt);
47 if (result.result_string == "401") { 50 if (result.result_string == "401") {
48 // Try again with new JWT 51 // Try again with new JWT
49 UpdateJWT(); 52 UpdateJWT();
50 result = GenericJson(method, path, data, jwt); 53 result = GenericRequest(method, path, data, accept, jwt);
51 } 54 }
52 55
53 return result; 56 return result;
@@ -56,12 +59,13 @@ struct Client::Impl {
56 /** 59 /**
57 * A generic function with explicit authentication method specified 60 * A generic function with explicit authentication method specified
58 * JWT is used if the jwt parameter is not empty 61 * JWT is used if the jwt parameter is not empty
59 * username + token is used if jwt is empty but username and token are not empty 62 * username + token is used if jwt is empty but username and token are
60 * anonymous if all of jwt, username and token are empty 63 * not empty anonymous if all of jwt, username and token are empty
61 */ 64 */
62 Common::WebResult GenericJson(const std::string& method, const std::string& path, 65 Common::WebResult GenericRequest(const std::string& method, const std::string& path,
63 const std::string& data, const std::string& jwt = "", 66 const std::string& data, const std::string& accept,
64 const std::string& username = "", const std::string& token = "") { 67 const std::string& jwt = "", const std::string& username = "",
68 const std::string& token = "") {
65 if (cli == nullptr) { 69 if (cli == nullptr) {
66 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); 70 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
67 int port; 71 int port;
@@ -132,8 +136,7 @@ struct Client::Impl {
132 return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; 136 return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
133 } 137 }
134 138
135 if (content_type->second.find("application/json") == std::string::npos && 139 if (content_type->second.find(accept) == std::string::npos) {
136 content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
137 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, 140 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
138 content_type->second); 141 content_type->second);
139 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; 142 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
@@ -147,7 +150,7 @@ struct Client::Impl {
147 return; 150 return;
148 } 151 }
149 152
150 auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); 153 auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
151 if (result.result_code != Common::WebResult::Code::Success) { 154 if (result.result_code != Common::WebResult::Code::Success) {
152 LOG_ERROR(WebService, "UpdateJWT failed"); 155 LOG_ERROR(WebService, "UpdateJWT failed");
153 } else { 156 } else {
@@ -180,16 +183,29 @@ Client::~Client() = default;
180 183
181Common::WebResult Client::PostJson(const std::string& path, const std::string& data, 184Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
182 bool allow_anonymous) { 185 bool allow_anonymous) {
183 return impl->GenericJson("POST", path, data, allow_anonymous); 186 return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
184} 187}
185 188
186Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { 189Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
187 return impl->GenericJson("GET", path, "", allow_anonymous); 190 return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
188} 191}
189 192
190Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data, 193Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
191 bool allow_anonymous) { 194 bool allow_anonymous) {
192 return impl->GenericJson("DELETE", path, data, allow_anonymous); 195 return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
196}
197
198Common::WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
199 return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
200}
201
202Common::WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
203 return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
204}
205
206Common::WebResult Client::GetExternalJWT(const std::string& audience) {
207 return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
208 "text/html");
193} 209}
194 210
195} // namespace WebService 211} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index c637e09df..04121f17e 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -46,6 +46,29 @@ public:
46 Common::WebResult DeleteJson(const std::string& path, const std::string& data, 46 Common::WebResult DeleteJson(const std::string& path, const std::string& data,
47 bool allow_anonymous); 47 bool allow_anonymous);
48 48
49 /**
50 * Gets a plain string from the specified path.
51 * @param path the URL segment after the host address.
52 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
53 * @return the result of the request.
54 */
55 Common::WebResult GetPlain(const std::string& path, bool allow_anonymous);
56
57 /**
58 * Gets an PNG image from the specified path.
59 * @param path the URL segment after the host address.
60 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
61 * @return the result of the request.
62 */
63 Common::WebResult GetImage(const std::string& path, bool allow_anonymous);
64
65 /**
66 * Requests an external JWT for the specific audience provided.
67 * @param audience the audience of the JWT requested.
68 * @return the result of the request.
69 */
70 Common::WebResult GetExternalJWT(const std::string& audience);
71
49private: 72private:
50 struct Impl; 73 struct Impl;
51 std::unique_ptr<Impl> impl; 74 std::unique_ptr<Impl> impl;
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 336b062b3..8637f5b3c 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -11,6 +11,31 @@
11#include "yuzu/configuration/configure_web.h" 11#include "yuzu/configuration/configure_web.h"
12#include "yuzu/uisettings.h" 12#include "yuzu/uisettings.h"
13 13
14static constexpr char token_delimiter{':'};
15
16static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
17 if (username.empty() || token.empty()) {
18 return {};
19 }
20
21 const std::string unencoded_display_token{username + token_delimiter + token};
22 QByteArray b{unencoded_display_token.c_str()};
23 QByteArray b64 = b.toBase64();
24 return b64.toStdString();
25}
26
27static std::string UsernameFromDisplayToken(const std::string& display_token) {
28 const std::string unencoded_display_token{
29 QByteArray::fromBase64(display_token.c_str()).toStdString()};
30 return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
31}
32
33static std::string TokenFromDisplayToken(const std::string& display_token) {
34 const std::string unencoded_display_token{
35 QByteArray::fromBase64(display_token.c_str()).toStdString()};
36 return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
37}
38
14ConfigureWeb::ConfigureWeb(QWidget* parent) 39ConfigureWeb::ConfigureWeb(QWidget* parent)
15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { 40 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
16 ui->setupUi(this); 41 ui->setupUi(this);
@@ -63,13 +88,18 @@ void ConfigureWeb::SetConfiguration() {
63 ui->web_signup_link->setOpenExternalLinks(true); 88 ui->web_signup_link->setOpenExternalLinks(true);
64 ui->web_token_info_link->setOpenExternalLinks(true); 89 ui->web_token_info_link->setOpenExternalLinks(true);
65 90
91 if (Settings::values.yuzu_username.empty()) {
92 ui->username->setText(tr("Unspecified"));
93 } else {
94 ui->username->setText(QString::fromStdString(Settings::values.yuzu_username));
95 }
96
66 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); 97 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
67 ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username)); 98 ui->edit_token->setText(QString::fromStdString(
68 ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token)); 99 GenerateDisplayToken(Settings::values.yuzu_username, Settings::values.yuzu_token)));
69 100
70 // Connect after setting the values, to avoid calling OnLoginChanged now 101 // Connect after setting the values, to avoid calling OnLoginChanged now
71 connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); 102 connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
72 connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
73 103
74 user_verified = true; 104 user_verified = true;
75 105
@@ -80,12 +110,13 @@ void ConfigureWeb::ApplyConfiguration() {
80 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked(); 110 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
81 UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); 111 UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
82 if (user_verified) { 112 if (user_verified) {
83 Settings::values.yuzu_username = ui->edit_username->text().toStdString(); 113 Settings::values.yuzu_username =
84 Settings::values.yuzu_token = ui->edit_token->text().toStdString(); 114 UsernameFromDisplayToken(ui->edit_token->text().toStdString());
115 Settings::values.yuzu_token = TokenFromDisplayToken(ui->edit_token->text().toStdString());
85 } else { 116 } else {
86 QMessageBox::warning(this, tr("Username and token not verified"), 117 QMessageBox::warning(
87 tr("Username and token were not verified. The changes to your " 118 this, tr("Token not verified"),
88 "username and/or token have not been saved.")); 119 tr("Token was not verified. The change to your token has not been saved."));
89 } 120 }
90} 121}
91 122
@@ -96,17 +127,15 @@ void ConfigureWeb::RefreshTelemetryID() {
96} 127}
97 128
98void ConfigureWeb::OnLoginChanged() { 129void ConfigureWeb::OnLoginChanged() {
99 if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) { 130 if (ui->edit_token->text().isEmpty()) {
100 user_verified = true; 131 user_verified = true;
101 132
102 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); 133 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
103 ui->label_username_verified->setPixmap(pixmap);
104 ui->label_token_verified->setPixmap(pixmap); 134 ui->label_token_verified->setPixmap(pixmap);
105 } else { 135 } else {
106 user_verified = false; 136 user_verified = false;
107 137
108 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); 138 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
109 ui->label_username_verified->setPixmap(pixmap);
110 ui->label_token_verified->setPixmap(pixmap); 139 ui->label_token_verified->setPixmap(pixmap);
111 } 140 }
112} 141}
@@ -114,10 +143,11 @@ void ConfigureWeb::OnLoginChanged() {
114void ConfigureWeb::VerifyLogin() { 143void ConfigureWeb::VerifyLogin() {
115 ui->button_verify_login->setDisabled(true); 144 ui->button_verify_login->setDisabled(true);
116 ui->button_verify_login->setText(tr("Verifying...")); 145 ui->button_verify_login->setText(tr("Verifying..."));
117 verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(), 146 verify_watcher.setFuture(QtConcurrent::run(
118 token = ui->edit_token->text().toStdString()] { 147 [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
119 return Core::VerifyLogin(username, token); 148 token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
120 })); 149 return Core::VerifyLogin(username, token);
150 }));
121} 151}
122 152
123void ConfigureWeb::OnLoginVerified() { 153void ConfigureWeb::OnLoginVerified() {
@@ -127,16 +157,15 @@ void ConfigureWeb::OnLoginVerified() {
127 user_verified = true; 157 user_verified = true;
128 158
129 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); 159 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
130 ui->label_username_verified->setPixmap(pixmap);
131 ui->label_token_verified->setPixmap(pixmap); 160 ui->label_token_verified->setPixmap(pixmap);
161 ui->username->setText(
162 QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
132 } else { 163 } else {
133 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); 164 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
134 ui->label_username_verified->setPixmap(pixmap);
135 ui->label_token_verified->setPixmap(pixmap); 165 ui->label_token_verified->setPixmap(pixmap);
136 166 ui->username->setText(tr("Unspecified"));
137 QMessageBox::critical( 167 QMessageBox::critical(this, tr("Verification failed"),
138 this, tr("Verification failed"), 168 tr("Verification failed. Check that you have entered your token "
139 tr("Verification failed. Check that you have entered your username and token " 169 "correctly, and that your internet connection is working."));
140 "correctly, and that your internet connection is working."));
141 } 170 }
142} 171}
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
index 2f4b9dd73..8c07d1165 100644
--- a/src/yuzu/configuration/configure_web.ui
+++ b/src/yuzu/configuration/configure_web.ui
@@ -55,11 +55,7 @@
55 </widget> 55 </widget>
56 </item> 56 </item>
57 <item row="0" column="1" colspan="3"> 57 <item row="0" column="1" colspan="3">
58 <widget class="QLineEdit" name="edit_username"> 58 <widget class="QLabel" name="username" />
59 <property name="maxLength">
60 <number>36</number>
61 </property>
62 </widget>
63 </item> 59 </item>
64 <item row="1" column="0"> 60 <item row="1" column="0">
65 <widget class="QLabel" name="label_token"> 61 <widget class="QLabel" name="label_token">
@@ -79,14 +75,10 @@
79 </property> 75 </property>
80 </widget> 76 </widget>
81 </item> 77 </item>
82 <item row="0" column="4">
83 <widget class="QLabel" name="label_username_verified">
84 </widget>
85 </item>
86 <item row="1" column="1" colspan="3"> 78 <item row="1" column="1" colspan="3">
87 <widget class="QLineEdit" name="edit_token"> 79 <widget class="QLineEdit" name="edit_token">
88 <property name="maxLength"> 80 <property name="maxLength">
89 <number>36</number> 81 <number>80</number>
90 </property> 82 </property>
91 <property name="echoMode"> 83 <property name="echoMode">
92 <enum>QLineEdit::Password</enum> 84 <enum>QLineEdit::Password</enum>