cesium-native/CesiumITwinClient/test/MockITwinAssetAccessor.cpp

288 lines
9.9 KiB
C++

#include "MockITwinAssetAccessor.h"
#include <CesiumClientCommon/fillWithRandomBytes.h>
#include <CesiumNativeTests/SimpleAssetRequest.h>
#include <CesiumNativeTests/readFile.h>
#include <CesiumUtility/Uri.h>
#include <modp_b64.h>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <chrono>
#include <filesystem>
namespace {
void writeMap(
rapidjson::Writer<rapidjson::StringBuffer>& writer,
const std::unordered_map<std::string, std::string>& map) {
for (const auto& [k, v] : map) {
writer.Key(k.c_str());
writer.String(v.c_str());
}
}
std::string encodeBase64(const std::string& str) {
const size_t len = modp_b64_encode_len(str.size());
std::string output;
output.resize(len);
const size_t bytes = modp_b64_encode(output.data(), str.data(), str.size());
REQUIRE(bytes != -1);
output.resize(bytes);
return output;
}
const char* ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz0123456789";
} // namespace
std::string randomStringOfLen(size_t len) {
std::string str;
str.reserve(len);
std::vector<uint8_t> buffer(len, 0);
CesiumClientCommon::fillWithRandomBytes(buffer);
const size_t alphabetLen = strlen(ALPHABET);
for (size_t i = 0; i < len; i++) {
str += ALPHABET[buffer[i] % alphabetLen];
}
return str;
}
std::string generateAuthToken(bool isAccessToken) {
rapidjson::StringBuffer tokenBuffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(tokenBuffer);
const std::chrono::time_point exp =
std::chrono::system_clock::now() + std::chrono::hours(1);
writer.StartObject();
if (isAccessToken) {
writer.Key("scope");
writer.StartArray();
writer.String("itwin-platform");
writer.String("offline_access");
writer.EndArray();
writer.Key("name");
writer.String("Example.User@example.com");
writer.Key("preferred_username");
writer.String("Example.User@example.com");
const std::chrono::time_point nbf =
std::chrono::system_clock::now() - std::chrono::minutes(5);
writer.Key("nbf");
writer.Int64(
std::chrono::duration_cast<std::chrono::seconds>(nbf.time_since_epoch())
.count());
} else {
writer.Key("iTwinId");
writer.String("00000000-0000-0000-0000-000000000000");
}
writer.Key("exp");
writer.Int64(
std::chrono::duration_cast<std::chrono::seconds>(exp.time_since_epoch())
.count());
writer.EndObject();
std::string tokenJson(tokenBuffer.GetString(), tokenBuffer.GetSize());
return randomStringOfLen(74) + "." + encodeBase64(tokenJson) + "." +
randomStringOfLen(342);
}
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
MockITwinAssetAccessor::get(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& url,
const std::vector<THeader>& headers) {
return this
->request(asyncSystem, "GET", url, headers, std::span<const std::byte>());
}
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
MockITwinAssetAccessor::request(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& verb,
const std::string& url,
const std::vector<THeader>& headers,
const std::span<const std::byte>& body) {
std::vector<std::byte> bodyBuffer(body.data(), body.data() + body.size());
CesiumUtility::Uri uri(url);
if (uri.getHost() == "ims.bentley.com") {
return handleAuthServer(asyncSystem, verb, uri, headers, bodyBuffer);
} else if (uri.getHost() == "api.bentley.com") {
return handleApiServer(asyncSystem, verb, uri, headers, bodyBuffer);
}
FAIL("Cannot find request for url " << url);
return asyncSystem.createResolvedFuture(
std::shared_ptr<CesiumAsync::IAssetRequest>(nullptr));
}
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
MockITwinAssetAccessor::handleAuthServer(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& verb,
const CesiumUtility::Uri& uri,
const std::vector<THeader>& /*headers*/,
const std::vector<std::byte>& body) {
const std::string bodyStr(
reinterpret_cast<const char*>(body.data()),
body.size());
CesiumUtility::UriQuery bodyParams((std::string_view(bodyStr)));
rapidjson::StringBuffer outputBuffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(outputBuffer);
writer.StartObject();
if (uri.getPath() == "/connect/token") {
REQUIRE(bodyParams.getValue("grant_type"));
const std::string_view grantType = *bodyParams.getValue("grant_type");
const bool grantTypeCheck =
grantType == "authorization_code" || grantType == "refresh_token";
CHECK(grantTypeCheck);
if (grantType == "refresh_token") {
REQUIRE(this->refreshToken);
CHECK(bodyParams.getValue("refresh_token") == this->refreshToken);
}
this->authToken = generateAuthToken(true);
this->refreshToken = randomStringOfLen(42);
writer.Key("access_token");
writer.String(this->authToken.c_str());
writer.Key("refresh_token");
writer.String(this->refreshToken->c_str());
writer.Key("token_type");
writer.String("Bearer");
writer.Key("expires_in");
writer.Int(3599);
}
writer.EndObject();
return asyncSystem.createResolvedFuture<
std::shared_ptr<CesiumAsync::IAssetRequest>>(
std::make_shared<CesiumNativeTests::SimpleAssetRequest>(
verb,
std::string(uri.toString()),
CesiumAsync::HttpHeaders{},
std::make_unique<CesiumNativeTests::SimpleAssetResponse>(
200,
"application/json",
CesiumAsync::HttpHeaders{},
std::vector<std::byte>(
reinterpret_cast<const std::byte*>(outputBuffer.GetString()),
reinterpret_cast<const std::byte*>(
outputBuffer.GetString() + outputBuffer.GetSize())))));
}
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
MockITwinAssetAccessor::handleApiServer(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::string& verb,
const CesiumUtility::Uri& uri,
const std::vector<THeader>& headers,
const std::vector<std::byte>& body) {
const auto& authIt =
std::find_if(headers.begin(), headers.end(), [](const THeader& header) {
return header.first == "Authorization";
});
REQUIRE(authIt != headers.end());
if (authIt->second.starts_with("Bearer ")) {
const std::string& headerToken = authIt->second.substr(strlen("Bearer "));
CHECK(headerToken == this->authToken);
} else if (authIt->second.starts_with("Basic ")) {
const std::string& headerToken = authIt->second.substr(strlen("Basic "));
CHECK(headerToken == this->authToken);
} else {
FAIL("No auth token found");
}
const std::string bodyStr(
reinterpret_cast<const char*>(body.data()),
body.size());
CesiumUtility::UriQuery bodyParams((std::string_view(bodyStr)));
// handle JSON loaded from test data
if (uri.getPath() ==
"/geospatial-features/itwins/00000000-0000-0000-0000-000000000000/ogc/"
"collections") {
return asyncSystem
.createResolvedFuture<std::shared_ptr<CesiumAsync::IAssetRequest>>(
std::make_shared<CesiumNativeTests::SimpleAssetRequest>(
verb,
std::string(uri.toString()),
CesiumAsync::HttpHeaders{},
std::make_unique<CesiumNativeTests::SimpleAssetResponse>(
200,
"application/json",
CesiumAsync::HttpHeaders{},
readFile(
std::filesystem::path(CesiumITwinClient_TEST_DATA_DIR) /
"FeatureCollections.json"))));
} else if (
uri.getPath() ==
"/geospatial-features/itwins/00000000-0000-0000-0000-000000000000/ogc/"
"collections/00000000-0000-0000-0000-000000000000/items") {
return asyncSystem
.createResolvedFuture<std::shared_ptr<CesiumAsync::IAssetRequest>>(
std::make_shared<CesiumNativeTests::SimpleAssetRequest>(
verb,
std::string(uri.toString()),
CesiumAsync::HttpHeaders{},
std::make_unique<CesiumNativeTests::SimpleAssetResponse>(
200,
"application/json",
CesiumAsync::HttpHeaders{},
readFile(
std::filesystem::path(CesiumITwinClient_TEST_DATA_DIR) /
"FeatureService.json"))));
}
rapidjson::StringBuffer outputBuffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(outputBuffer);
writer.StartObject();
if (uri.getPath() == "/users/me") {
writer.Key("user");
writer.StartObject();
writeMap(
writer,
std::unordered_map<std::string, std::string>{
{"id", "00000000-0000-0000-0000-000000000000"},
{"displayName", "John.Smith@example.com"},
{"givenName", "John"},
{"surname", "Smith"},
{"email", "John.Smith@example.com"},
{"alternateEmail", "John.Smith@example.com"},
{"phone", "000-000-0000"},
{"organizationName", "Example Organization"},
{"city", "Anytown"},
{"country", "US"},
{"language", "EN"},
{"createdDateTime", "2020-03-25T04,36,40.4210000Z"}});
writer.EndObject();
}
writer.EndObject();
return asyncSystem.createResolvedFuture<
std::shared_ptr<CesiumAsync::IAssetRequest>>(
std::make_shared<CesiumNativeTests::SimpleAssetRequest>(
verb,
std::string(uri.toString()),
CesiumAsync::HttpHeaders{},
std::make_unique<CesiumNativeTests::SimpleAssetResponse>(
200,
"application/json",
CesiumAsync::HttpHeaders{},
std::vector<std::byte>(
reinterpret_cast<const std::byte*>(outputBuffer.GetString()),
reinterpret_cast<const std::byte*>(
outputBuffer.GetString() + outputBuffer.GetSize())))));
}