Jump to content

Talking to IPB with C++...here we go...


Recommended Posts

I am implementing the IPB REST API into a C++ program that runs on the customer's computer. This program runs without the use of a web browser. This is meant to provide a mechanism to check software licenses.

In the future I also want to add these features:

  • Allow users to upload screenshots directly from our application to the community gallery.
  • Allow users to browse, purchase, and download IP.Downloads files directly in our application. 

Basically I am using IPB to build some of the functionality of Steam, for our own applications.

First step:

I am creating an OATH client. None of the client types describe what I am doing, so I am choosing "Custom Confidential OATH client". Here is the description:

Quote

Custom Confidential OAuth Client
A server-side app such as a website where the code will be written in a server-side language and stored on a server that no end-user has access to. A client secret will be issued.

The secret code will be stored in the program's compiled source code, so I think this is what best fits my situation. (There are potential absolutely certain DLL security issues I am not going to go into here.)

For "Available Grant Types" I am choosing "Resource Owner Password Credentials" as this seems to describe exactly what I am trying to do:

Quote

The end-user will enter their username or email address and password which you will exchange for an Access Token.

I don't know what "Redirection URLs" means so I am just adding my community URL.

For "Authorization Prompt" I am choosing the default setting. Now I am confused because I am not using a web browser at all for this application.

Edited by Interferon
Link to comment
Share on other sites

Now let's try to connect our application. First we initialize curl:

curl_global_init(CURL_GLOBAL_DEFAULT);

Create a curl object:

curl = curl_easy_init();

Set the URL for the request:

curl_easy_setopt(curl, CURLOPT_URL, "https://www.xxxxxxxxxxxx.com/oauth/token/?key=" + publickey);

Set the timeout value:

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10000);

Construct the authorization header:

//Construct authentication token
std::string postdata;
char authHeader[1024];
unsigned char buff[32];
hmac((unsigned char*)privatekey.c_str(), privatekey.length(), (unsigned char*)postdata.c_str(), postdata.length(), buff);
String encoding = base64_encode((const unsigned char*)buff, 32);
int len = sprintf(authHeader, "Authorization: %s", encoding.c_str());

//Construct header
struct curl_slist* headers = curl_slist_append(headers, "Content-Type: text/xml");
headers = curl_slist_append(headers, authHeader);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

Here is the hmac function, which is using OpenSSL:

void hmac(unsigned char* key, int keylen, unsigned char* message, int messagelen, unsigned char* out)
{
	HMAC_CTX* ctx = HMAC_CTX_new();
	unsigned int len = 32;

	// Using sha1 hash engine here.
	// You may use other hash engines. e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
	HMAC_Init_ex(ctx, key, keylen, EVP_sha256(), NULL);
	HMAC_Update(ctx, message, messagelen);
	HMAC_Final(ctx, out, &len);
	HMAC_CTX_free(ctx);
}

And finally perform the request:

CURLcode res = curl_easy_perform(curl);

When I do this, the result is CURL_OK but there is an "invalid_request" error in the returned JSON object. The error description says "request must be a POST request".

Okay, so my postdata value is just an empty string. Is there some kind of "hello" message I should set this to? I am just trying to break through the network at this point and make sure I can actually communicate with the server.

Edited by Interferon
Link to comment
Share on other sites

Following example here:

https://invisioncommunity.com/developers/rest-api/index/

I changed the URL to "https://www.xxxxxxxxxxx.com/api/core/hello/" but it is saying the key is invalid:

curl_easy_setopt(curl, CURLOPT_USERPWD, publickey.c_str());

If I use an API key then it actually works, but I want to use the per-user OATH method since this is running on the user's computer.

Edited by Interferon
Link to comment
Share on other sites

Using PHP and an API key I can very easily view purchase status given a user ID and an item ID. Creating our own intermediate web API might be a good idea, because it would prevent mountains of data from going back to the user. Should I be looking at an approach like that instead?

Link to comment
Share on other sites

You will probably want to create a "Custom public OAuth client" which has the description

Quote

A browser-based app written in JavaScript or a mobile/native app which will be installed on a device an end-user does have access to. No client secret will be issued.

Note the "native app which will be installed on a device an end-user does have access to" bit.

From there, I agree you will want to use "Resource Owner Password Credentials" as the grant type. "Redirection URI" won't mean much in your case so you can set it to whatever you want.

Here's a generic description of how it works: https://auth0.com/docs/flows/resource-owner-password-flow

You'll send the details to the /oauth/token/ endpoint as a POST request with

  • client_id: The client ID for the OAuth client
  • username: The user's username
  • password: The user's password
  • scope: Space separated list of scopes to grant access to
  • grant_type: "password"

This should return the access token, which you can then use to make REST API calls authenticated as the end user.

Link to comment
Share on other sites

We have the authentication token working now. I can see the authentications in the IPB admin panel, along with the correct scopes.

scopes3.png.8319ce9b0d250a6ec74e6f6f187033e0.png

However, every API call we try to make results in a NO_PERMISSION error. The URL is set like this:

std::string url = "https://www.xxxxxx.com/forums/api" + endpoint;

Where endpoint is equal to "/core/me".

The token appears to be working, because if I set it to a random string I get an error saying it is invalid.

The URL appears to be correct because if I make up an endpoint that doesn't exist I get an INVALID_CONTROLLER error.

The error code is 2S291/3.

I also notice I don't see any of my calls using an OAUTH token as the credential appearing in the API log.

Edited by Interferon
Link to comment
Share on other sites

Well, unlike most areas with a unique error code I can see this error code is repeated a few times in our code base (an issue I'll address shortly). For the combination of this error code and the response NO_PERMISSION, it would indicate that either (1) you are using an OAuth access token but hitting an endpoint tagged as "apiclientonly" (meaning only an API key can access the endpoint), or (2) you are not using an OAuth access token and are hitting an endpoint tagged as "apimemberonly" (meaning an API key cannot be used to hit this endpoint).

The /core/me endpoint is tagged as "apimemberonly".

This makes me think you are making your request using an API key and not using the OAuth access token. 

Take a look at this page: https://invisioncommunity.com/developers/rest-api . If using an OAuth access token, you should be setting an authorization header like so:

Authorization: Bearer {$accessToken}

$accessToken in this case is the access token that was returned.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...