Skip to content

Commit b54b190

Browse files
Feature/url builder (#31)
* Adds url builder for auth and gateway endpoints. * Adds url (%) encoding to all processed url query parameters, and json escaping for all json post bodies.
1 parent d79fe59 commit b54b190

File tree

5 files changed

+152
-33
lines changed

5 files changed

+152
-33
lines changed

src/api.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
2828
#include <util/platform.h>
2929
#include <nlohmann/json.hpp>
3030
#include <QDir>
31+
#include <sstream>
3132

3233
using json = nlohmann::json;
3334

@@ -64,13 +65,18 @@ MarketplaceApi::MarketplaceApi()
6465

6566
void MarketplaceApi::logOut()
6667
{
67-
std::string url = _authUrl + "/auth/realms/mp/protocol/openid-connect/logout";
68+
std::string url = getAuthUrl(logoutEndpointSegments, {});
69+
6870
auto ec = GetElgatoCloud();
6971
auto refreshToken = ec->GetRefreshToken();
7072
auto accessToken = ec->GetAccessToken();
7173
if (refreshToken != "") {
72-
std::string postBody = "{\"client_id\": \"elgatolink\", \"refresh_token\": \"" + refreshToken + "\" }";
73-
auto resp = fetch_string_from_post(url, postBody, accessToken);
74+
std::map<std::string, std::string> params = {
75+
{ID_KEY, ID},
76+
{REFRESH_KEY, refreshToken}
77+
};
78+
std::string postData = postBody(params);
79+
auto resp = fetch_string_from_post(url, postData, accessToken);
7480
}
7581
_loggedIn = false;
7682
_hasAvatar = false;
@@ -92,6 +98,43 @@ MarketplaceApi *MarketplaceApi::getInstance()
9298
return _api;
9399
}
94100

101+
std::string MarketplaceApi::getAuthUrl(
102+
std::vector<std::string> const& segments,
103+
std::map<std::string, std::string> const& queryParams
104+
)
105+
{
106+
std::string endpoint = segments.size() > 0 ? std::accumulate(std::next(segments.begin()), segments.end(), segments[0],
107+
[&](std::string a, std::string b) { return a + "/" + b; }) : "";
108+
109+
auto qString = queryString(queryParams);
110+
111+
if (!qString.empty()) {
112+
qString = "?" + qString;
113+
}
114+
115+
std::string url = _authUrl + "/" + endpoint + qString;
116+
return url;
117+
}
118+
119+
std::string MarketplaceApi::getGatewayUrl(
120+
std::vector<std::string> const& segments,
121+
std::map<std::string, std::string> const& queryParams
122+
)
123+
{
124+
std::string endpoint = segments.size() > 0 ? std::accumulate(std::next(segments.begin()), segments.end(), segments[0],
125+
[&](std::string a, std::string b) { return a + "/" + b; }) : "";
126+
127+
auto qString = queryString(queryParams);
128+
129+
if (!qString.empty()) {
130+
qString = "?" + qString;
131+
}
132+
133+
std::string url = _gatewayUrl + "/" + endpoint + qString;
134+
return url;
135+
}
136+
137+
95138
void MarketplaceApi::setUserDetails(nlohmann::json &data)
96139
{
97140
_hasAvatar = false;

src/api.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,42 @@ with this program. If not, see <https://www.gnu.org/licenses/>
2727
#define DEFAULT_AUTH_URL "https://account.elgato.com"
2828
#define API_URLS_FILE "api-urls.json"
2929

30+
#define USERAGENT "elgatolink"
31+
#define ID_KEY "client_id"
32+
#define ID "elgatolink"
33+
#define CC_KEY "code_challenge_method"
34+
#define CC_METHOD "S256"
35+
#define REDIRECT_KEY "redirect_uri"
36+
#define REDIRECT "https://oauth2-redirect.elgato.com/" ID "/auth"
37+
#define RESPONSE_TYPE_KEY "response_type"
38+
#define RESPONSE_TYPE_CODE "code"
39+
#define REFRESH_KEY "refresh_token"
40+
#define GRANT_KEY "grant_type"
41+
#define GRANT_REFRESH "refresh_token"
42+
43+
#define CHALLENGE_KEY "code_challenge"
44+
#define CODE_VERIFIER_KEY "code_verifier"
45+
3046
namespace elgatocloud {
3147

3248
const std::map<std::string, std::string> avatarColors = {
3349
{"orange", "#BE5900"}, {"magenta", "#C93BA1"}, {"green", "#2A863E"},
3450
{"teal", "#22837D"}, {"cyan", "#0F7EAD"}, {"purple", "#A638FE"},
3551
{"gray", "#767676"}};
3652

53+
const std::vector<std::string> authEndpointSegments = {
54+
"auth", "realms", "mp", "protocol", "openid-connect", "auth"
55+
};
56+
57+
const std::vector<std::string> tokenEndpointSegments = {
58+
"auth", "realms", "mp", "protocol", "openid-connect", "token"
59+
};
60+
61+
const std::vector<std::string> logoutEndpointSegments = {
62+
"auth", "realms", "mp", "protocol", "openid-connect", "logout"
63+
};
64+
65+
3766
class MarketplaceApi : public QObject {
3867
Q_OBJECT
3968
public:
@@ -55,6 +84,11 @@ class MarketplaceApi : public QObject {
5584
void setUserDetails(nlohmann::json &data);
5685
void logOut();
5786
void OpenStoreInBrowser() const;
87+
std::string getAuthUrl(std::vector<std::string> const& segments, std::map<std::string, std::string> const& queryParams);
88+
std::string getGatewayUrl(
89+
std::vector<std::string> const& segments,
90+
std::map<std::string, std::string> const& queryParams
91+
);
5892

5993
signals:
6094
void AvatarDownloaded();

src/elgato-cloud-data.cpp

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,15 @@ void ElgatoCloud::_TokenRefresh(bool loadData, bool loadUserDetails)
138138
}
139139
obs_log(LOG_INFO, "Access Token has expired. Fetching a new token.");
140140
auto api = MarketplaceApi::getInstance();
141-
std::string encodeddata;
142-
encodeddata += "grant_type=refresh_token";
143-
encodeddata += "&refresh_token=" + _refreshToken;
144-
encodeddata += "&client_id=elgatolink";
145-
std::string url = api->authUrl();
146-
url += "/auth/realms/mp/protocol/openid-connect/token?";
147-
url += encodeddata;
141+
142+
std::map<std::string, std::string> queryParams = {
143+
{GRANT_KEY, GRANT_REFRESH},
144+
{REFRESH_KEY, _refreshToken},
145+
{ID_KEY, ID}
146+
};
147+
148+
std::string url = api->getAuthUrl(tokenEndpointSegments, queryParams);
149+
std::string encodeddata = queryString(queryParams);
148150
auto response = fetch_string_from_post(url, encodeddata);
149151
try {
150152
auto responseJson = nlohmann::json::parse(response);
@@ -202,20 +204,17 @@ void ElgatoCloud::_Listen()
202204
code = d.substr(offset);
203205
}
204206

205-
std::string encodeddata;
206-
encodeddata += "grant_type=authorization_code";
207-
encodeddata += "&code=" + code;
208-
encodeddata +=
209-
"&redirect_uri=https%3A%2F%2Foauth2-redirect.elgato.com%2Felgatolink%2Fauth";
210-
encodeddata +=
211-
"&code_verifier=" + _last_code_verifier;
212-
encodeddata += "&client_id=elgatolink";
207+
std::map<std::string, std::string> queryParams = {
208+
{"grant_type", "authorization_code"},
209+
{"code", code},
210+
{REDIRECT_KEY, REDIRECT},
211+
{CODE_VERIFIER_KEY, _last_code_verifier},
212+
{ID_KEY, ID}
213+
};
213214

215+
std::string encodeddata = queryString(queryParams);
214216
auto api = MarketplaceApi::getInstance();
215-
std::string url = api->authUrl();
216-
217-
url += "/auth/realms/mp/protocol/openid-connect/token?";
218-
url += encodeddata;
217+
std::string url = api->getAuthUrl(tokenEndpointSegments, queryParams);
219218

220219
auto response = fetch_string_from_post(
221220
url, encodeddata);
@@ -293,10 +292,16 @@ void ElgatoCloud::StartLogin()
293292
.toStdString();
294293

295294
auto api = MarketplaceApi::getInstance();
296-
std::string url =
297-
api->authUrl() +
298-
"/auth/realms/mp/protocol/openid-connect/auth?response_type=code&client_id=elgatolink&redirect_uri=https%3A%2F%2Foauth2-redirect.elgato.com%2Felgatolink%2Fauth&code_challenge=" +
299-
stringhash + "&code_challenge_method=S256";
295+
296+
std::map<std::string, std::string> queryParams = {
297+
{RESPONSE_TYPE_KEY, RESPONSE_TYPE_CODE},
298+
{ID_KEY, ID},
299+
{REDIRECT_KEY, REDIRECT},
300+
{CHALLENGE_KEY, stringhash},
301+
{CC_KEY, CC_METHOD}
302+
};
303+
304+
std::string url = api->getAuthUrl(authEndpointSegments, queryParams);
300305

301306
authorizing = true;
302307
ShellExecuteA(NULL, NULL, url.c_str(), NULL, NULL, SW_SHOW);
@@ -374,16 +379,24 @@ void ElgatoCloud::LoadPurchasedProducts()
374379
}
375380

376381
auto api = MarketplaceApi::getInstance();
377-
std::string api_url = api->gatewayUrl();
378-
api_url +=
379-
"/my-products?extension=scene-collections&offset=0&limit=100";
382+
//std::string api_url = api->gatewayUrl();
383+
//api_url +=
384+
// "/my-products?extension=scene-collections&offset=0&limit=100";
385+
std::vector<std::string> segments = { "my-products" };
386+
std::map<std::string, std::string> queryParams = {
387+
{"extension", "scene-collections"},
388+
{"offset", "0"},
389+
{"limit", "100"}
390+
};
391+
392+
std::string api_url = api->getGatewayUrl(segments, queryParams);
393+
380394
auto productsResponse = fetch_string_from_get(api_url, _accessToken);
381395
products.clear();
382396
try {
383397
auto productsJson = nlohmann::json::parse(productsResponse);
384398
if (productsJson["results"].is_array()) {
385399
for (auto &pdat : productsJson["results"]) {
386-
//auto ep = new ElgatoProduct(pdat);
387400
products.emplace_back(
388401
std::make_unique<ElgatoProduct>(pdat));
389402
}

src/util.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ You should have received a copy of the GNU General Public License along
1616
with this program. If not, see <https://www.gnu.org/licenses/>
1717
*/
1818

19+
#include <sstream>
20+
1921
#include <QDir>
2022
#include <obs-module.h>
2123
#include <util/config-file.h>
@@ -38,6 +40,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
3840

3941
#include "platform.h"
4042
#include "util.h"
43+
#include "api.hpp"
4144

4245
#pragma comment(lib, "crypt32.lib")
4346
#include <Windows.h>
@@ -160,7 +163,7 @@ std::string fetch_string_from_get(std::string url, std::string token)
160163
write_data<std::string>);
161164
curl_easy_setopt(curl_instance, CURLOPT_WRITEDATA,
162165
static_cast<void *>(&result));
163-
std::string useragent = "elgatolink ";
166+
std::string useragent = USERAGENT " ";
164167
useragent += PLUGIN_VERSION;
165168
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
166169
if (token != "") {
@@ -182,6 +185,30 @@ std::string fetch_string_from_get(std::string url, std::string token)
182185
return "{\"error\": \"Unspecified Error\"}";
183186
}
184187

188+
189+
std::string queryString(std::map<std::string, std::string> params)
190+
{
191+
std::stringstream queryStrStream;
192+
for (const auto& pair : params) {
193+
queryStrStream << url_encode(pair.first) << "=" << url_encode(pair.second) << "&";
194+
}
195+
196+
std::string queryString = queryStrStream.str();
197+
198+
if (!queryString.empty()) {
199+
queryString.pop_back();
200+
}
201+
202+
return queryString;
203+
}
204+
205+
std::string postBody(std::map<std::string, std::string> params)
206+
{
207+
nlohmann::json jsonBody = nlohmann::json(params);
208+
std::string jsonString = jsonBody.dump();
209+
return jsonString;
210+
}
211+
185212
std::string fetch_string_from_post(std::string url, std::string postdata, std::string token)
186213
{
187214
std::string result;
@@ -198,7 +225,7 @@ std::string fetch_string_from_post(std::string url, std::string postdata, std::s
198225
CURLAUTH_BEARER);
199226
}
200227
curl_easy_setopt(curl_instance, CURLOPT_POSTFIELDS, postdata.c_str());
201-
std::string useragent = "elgatolink ";
228+
std::string useragent = USERAGENT " ";
202229
useragent += PLUGIN_VERSION;
203230
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
204231
CURLcode res = curl_easy_perform(curl_instance);
@@ -227,7 +254,7 @@ std::vector<char> fetch_bytes_from_url(std::string url)
227254
write_data<std::vector<char>>);
228255
curl_easy_setopt(curl_instance, CURLOPT_WRITEDATA,
229256
static_cast<void *>(&result));
230-
std::string useragent = "elgatolink ";
257+
std::string useragent = USERAGENT " ";
231258
useragent += PLUGIN_VERSION;
232259
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
233260
CURLcode res = curl_easy_perform(curl_instance);

src/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ int get_major_version();
4242
bool filename_json(std::string &filename);
4343
std::string get_current_scene_collection_filename();
4444

45+
std::string queryString(std::map<std::string, std::string> params);
46+
std::string postBody(std::map<std::string, std::string> params);
4547
std::string fetch_string_from_get(std::string url, std::string token);
4648
std::string fetch_string_from_post(std::string url, std::string postdata, std::string token="");
4749
std::vector<char> fetch_bytes_from_url(std::string url);

0 commit comments

Comments
 (0)