Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions src/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <util/platform.h>
#include <nlohmann/json.hpp>
#include <QDir>
#include <sstream>

using json = nlohmann::json;

Expand Down Expand Up @@ -64,13 +65,18 @@ MarketplaceApi::MarketplaceApi()

void MarketplaceApi::logOut()
{
std::string url = _authUrl + "/auth/realms/mp/protocol/openid-connect/logout";
std::string url = getAuthUrl(logoutEndpointSegments, {});

auto ec = GetElgatoCloud();
auto refreshToken = ec->GetRefreshToken();
auto accessToken = ec->GetAccessToken();
if (refreshToken != "") {
std::string postBody = "{\"client_id\": \"elgatolink\", \"refresh_token\": \"" + refreshToken + "\" }";
auto resp = fetch_string_from_post(url, postBody, accessToken);
std::map<std::string, std::string> params = {
{ID_KEY, ID},
{REFRESH_KEY, refreshToken}
};
std::string postData = postBody(params);
auto resp = fetch_string_from_post(url, postData, accessToken);
}
_loggedIn = false;
_hasAvatar = false;
Expand All @@ -92,6 +98,43 @@ MarketplaceApi *MarketplaceApi::getInstance()
return _api;
}

std::string MarketplaceApi::getAuthUrl(
std::vector<std::string> const& segments,
std::map<std::string, std::string> const& queryParams
)
{
std::string endpoint = segments.size() > 0 ? std::accumulate(std::next(segments.begin()), segments.end(), segments[0],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is technically not "bad" code, but I prefer when algorithms like this are spun out into a well named helper function; especially when it's a pattern used in multiple places for a relatively well understood high level operation like this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noted this, and will revisit.

[&](std::string a, std::string b) { return a + "/" + b; }) : "";

auto qString = queryString(queryParams);

if (!qString.empty()) {
qString = "?" + qString;
}

std::string url = _authUrl + "/" + endpoint + qString;
return url;
}

std::string MarketplaceApi::getGatewayUrl(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there future logic that is planned which will make this function materially different from getAuthUrl?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially yes, as the calls to the gateway API have various standard parameters such as limits/offsets. My thought is to standardize this in the future for easier/more clear calls to the various API endpoints versus the calls done to the OAuth process

std::vector<std::string> const& segments,
std::map<std::string, std::string> const& queryParams
)
{
std::string endpoint = segments.size() > 0 ? std::accumulate(std::next(segments.begin()), segments.end(), segments[0],
[&](std::string a, std::string b) { return a + "/" + b; }) : "";

auto qString = queryString(queryParams);

if (!qString.empty()) {
qString = "?" + qString;
}

std::string url = _gatewayUrl + "/" + endpoint + qString;
return url;
}


void MarketplaceApi::setUserDetails(nlohmann::json &data)
{
_hasAvatar = false;
Expand Down
34 changes: 34 additions & 0 deletions src/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,42 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define DEFAULT_AUTH_URL "https://account.elgato.com"
#define API_URLS_FILE "api-urls.json"

#define USERAGENT "elgatolink"
#define ID_KEY "client_id"
#define ID "elgatolink"
#define CC_KEY "code_challenge_method"
#define CC_METHOD "S256"
#define REDIRECT_KEY "redirect_uri"
#define REDIRECT "https://oauth2-redirect.elgato.com/" ID "/auth"
#define RESPONSE_TYPE_KEY "response_type"
#define RESPONSE_TYPE_CODE "code"
#define REFRESH_KEY "refresh_token"
#define GRANT_KEY "grant_type"
#define GRANT_REFRESH "refresh_token"

#define CHALLENGE_KEY "code_challenge"
#define CODE_VERIFIER_KEY "code_verifier"

namespace elgatocloud {

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

const std::vector<std::string> authEndpointSegments = {
"auth", "realms", "mp", "protocol", "openid-connect", "auth"
};

const std::vector<std::string> tokenEndpointSegments = {
"auth", "realms", "mp", "protocol", "openid-connect", "token"
};

const std::vector<std::string> logoutEndpointSegments = {
"auth", "realms", "mp", "protocol", "openid-connect", "logout"
};


class MarketplaceApi : public QObject {
Q_OBJECT
public:
Expand All @@ -55,6 +84,11 @@ class MarketplaceApi : public QObject {
void setUserDetails(nlohmann::json &data);
void logOut();
void OpenStoreInBrowser() const;
std::string getAuthUrl(std::vector<std::string> const& segments, std::map<std::string, std::string> const& queryParams);
std::string getGatewayUrl(
std::vector<std::string> const& segments,
std::map<std::string, std::string> const& queryParams
);

signals:
void AvatarDownloaded();
Expand Down
67 changes: 40 additions & 27 deletions src/elgato-cloud-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,15 @@ void ElgatoCloud::_TokenRefresh(bool loadData, bool loadUserDetails)
}
obs_log(LOG_INFO, "Access Token has expired. Fetching a new token.");
auto api = MarketplaceApi::getInstance();
std::string encodeddata;
encodeddata += "grant_type=refresh_token";
encodeddata += "&refresh_token=" + _refreshToken;
encodeddata += "&client_id=elgatolink";
std::string url = api->authUrl();
url += "/auth/realms/mp/protocol/openid-connect/token?";
url += encodeddata;

std::map<std::string, std::string> queryParams = {
{GRANT_KEY, GRANT_REFRESH},
{REFRESH_KEY, _refreshToken},
{ID_KEY, ID}
};

std::string url = api->getAuthUrl(tokenEndpointSegments, queryParams);
std::string encodeddata = queryString(queryParams);
auto response = fetch_string_from_post(url, encodeddata);
try {
auto responseJson = nlohmann::json::parse(response);
Expand Down Expand Up @@ -202,20 +204,17 @@ void ElgatoCloud::_Listen()
code = d.substr(offset);
}

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

std::string encodeddata = queryString(queryParams);
auto api = MarketplaceApi::getInstance();
std::string url = api->authUrl();

url += "/auth/realms/mp/protocol/openid-connect/token?";
url += encodeddata;
std::string url = api->getAuthUrl(tokenEndpointSegments, queryParams);

auto response = fetch_string_from_post(
url, encodeddata);
Expand Down Expand Up @@ -293,10 +292,16 @@ void ElgatoCloud::StartLogin()
.toStdString();

auto api = MarketplaceApi::getInstance();
std::string url =
api->authUrl() +
"/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=" +
stringhash + "&code_challenge_method=S256";

std::map<std::string, std::string> queryParams = {
{RESPONSE_TYPE_KEY, RESPONSE_TYPE_CODE},
{ID_KEY, ID},
{REDIRECT_KEY, REDIRECT},
{CHALLENGE_KEY, stringhash},
{CC_KEY, CC_METHOD}
};

std::string url = api->getAuthUrl(authEndpointSegments, queryParams);

authorizing = true;
ShellExecuteA(NULL, NULL, url.c_str(), NULL, NULL, SW_SHOW);
Expand Down Expand Up @@ -374,16 +379,24 @@ void ElgatoCloud::LoadPurchasedProducts()
}

auto api = MarketplaceApi::getInstance();
std::string api_url = api->gatewayUrl();
api_url +=
"/my-products?extension=scene-collections&offset=0&limit=100";
//std::string api_url = api->gatewayUrl();
//api_url +=
// "/my-products?extension=scene-collections&offset=0&limit=100";
std::vector<std::string> segments = { "my-products" };
std::map<std::string, std::string> queryParams = {
{"extension", "scene-collections"},
{"offset", "0"},
{"limit", "100"}
};

std::string api_url = api->getGatewayUrl(segments, queryParams);

auto productsResponse = fetch_string_from_get(api_url, _accessToken);
products.clear();
try {
auto productsJson = nlohmann::json::parse(productsResponse);
if (productsJson["results"].is_array()) {
for (auto &pdat : productsJson["results"]) {
//auto ep = new ElgatoProduct(pdat);
products.emplace_back(
std::make_unique<ElgatoProduct>(pdat));
}
Expand Down
33 changes: 30 additions & 3 deletions src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/

#include <sstream>

#include <QDir>
#include <obs-module.h>
#include <util/config-file.h>
Expand All @@ -38,6 +40,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>

#include "platform.h"
#include "util.h"
#include "api.hpp"

#pragma comment(lib, "crypt32.lib")
#include <Windows.h>
Expand Down Expand Up @@ -160,7 +163,7 @@ std::string fetch_string_from_get(std::string url, std::string token)
write_data<std::string>);
curl_easy_setopt(curl_instance, CURLOPT_WRITEDATA,
static_cast<void *>(&result));
std::string useragent = "elgatolink ";
std::string useragent = USERAGENT " ";
useragent += PLUGIN_VERSION;
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
if (token != "") {
Expand All @@ -182,6 +185,30 @@ std::string fetch_string_from_get(std::string url, std::string token)
return "{\"error\": \"Unspecified Error\"}";
}


std::string queryString(std::map<std::string, std::string> params)
{
std::stringstream queryStrStream;
for (const auto& pair : params) {
queryStrStream << url_encode(pair.first) << "=" << url_encode(pair.second) << "&";
}

std::string queryString = queryStrStream.str();

if (!queryString.empty()) {
queryString.pop_back();
}

return queryString;
}

std::string postBody(std::map<std::string, std::string> params)
{
nlohmann::json jsonBody = nlohmann::json(params);
std::string jsonString = jsonBody.dump();
return jsonString;
}

std::string fetch_string_from_post(std::string url, std::string postdata, std::string token)
{
std::string result;
Expand All @@ -198,7 +225,7 @@ std::string fetch_string_from_post(std::string url, std::string postdata, std::s
CURLAUTH_BEARER);
}
curl_easy_setopt(curl_instance, CURLOPT_POSTFIELDS, postdata.c_str());
std::string useragent = "elgatolink ";
std::string useragent = USERAGENT " ";
useragent += PLUGIN_VERSION;
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
CURLcode res = curl_easy_perform(curl_instance);
Expand Down Expand Up @@ -227,7 +254,7 @@ std::vector<char> fetch_bytes_from_url(std::string url)
write_data<std::vector<char>>);
curl_easy_setopt(curl_instance, CURLOPT_WRITEDATA,
static_cast<void *>(&result));
std::string useragent = "elgatolink ";
std::string useragent = USERAGENT " ";
useragent += PLUGIN_VERSION;
curl_easy_setopt(curl_instance, CURLOPT_USERAGENT, useragent.c_str());
CURLcode res = curl_easy_perform(curl_instance);
Expand Down
2 changes: 2 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ int get_major_version();
bool filename_json(std::string &filename);
std::string get_current_scene_collection_filename();

std::string queryString(std::map<std::string, std::string> params);
std::string postBody(std::map<std::string, std::string> params);
std::string fetch_string_from_get(std::string url, std::string token);
std::string fetch_string_from_post(std::string url, std::string postdata, std::string token="");
std::vector<char> fetch_bytes_from_url(std::string url);
Expand Down