Skip to content

Commit

Permalink
[INTERNET MODULE][JWT] Refactoring and wider test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Onufrak committed Oct 29, 2024
1 parent 3443138 commit 18c605f
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 34 deletions.
23 changes: 13 additions & 10 deletions include/faker-cxx/internet.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,12 +369,13 @@ FAKER_CXX_EXPORT std::string anonymousUsername(unsigned maxLength);
* @code
* std::map<std::string, std::string> header = {{"alg", "HS256"}, {"typ", "JWT"}};
* std::map<std::string, std::string> payload = {{"sub", "1234567890"}, {"name", "John Doe"}, {"admin", "true"}};
* std::string token = faker::internet::getJWTToken(header, payload); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
* faker::internet::getJWTToken(header, payload); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
* @endcode
*/
FAKER_CXX_EXPORT std::string getJWTToken(std::optional<std::map<std::string, std::string>> header = std::nullopt,
std::optional<std::map<std::string, std::string>> payload = std::nullopt,
std::optional<std::string> refDate = std::nullopt);
FAKER_CXX_EXPORT std::string
getJWTToken(const std::optional<std::map<std::string, std::string>>& header = std::nullopt,
const std::optional<std::map<std::string, std::string>>& payload = std::nullopt,
const std::optional<std::string>& refDate = std::nullopt);

/**
* @brief Returns the algorithm used for signing the JWT.
Expand All @@ -384,11 +385,14 @@ FAKER_CXX_EXPORT std::string getJWTToken(std::optional<std::map<std::string, std
* @returns A string view representing the JWT signing algorithm.
*
* @code
* std::string_view algorithm = faker::internet::getJWTAlgorithm(); // "HS256"
* faker::internet::getJWTAlgorithm(); // "HS256"
* @endcode
*/
FAKER_CXX_EXPORT std::string_view getJWTAlgorithm();
}

namespace faker::internet::utility
{
/**
* @brief Encodes a given string to Base64 URL format.
*
Expand All @@ -401,10 +405,10 @@ FAKER_CXX_EXPORT std::string_view getJWTAlgorithm();
*
* @code
* std::string input = "Hello, World!";
* std::string encoded = faker::internet::toBase64UrlEncode(input); // "SGVsbG8sIFdvcmxkIQ"
* faker::internet::toBase64UrlEncode(input); // "SGVsbG8sIFdvcmxkIQ"
* @endcode
*/
FAKER_CXX_EXPORT std::string toBase64UrlEncode(std::string& input);
std::string toBase64UrlEncode(const std::string& input);

/**
* @brief Converts a map of key-value pairs to a JSON string.
Expand All @@ -417,9 +421,8 @@ FAKER_CXX_EXPORT std::string toBase64UrlEncode(std::string& input);
*
* @code
* std::map<std::string, std::string> data = {{"name", "John"}, {"age", "30"}};
* std::string json = faker::internet::toJSON(data); // json is now "{\"name\":\"John\",\"age\":\"30\"}"
* faker::internet::toJSON(data); // json is now "{\"name\":\"John\",\"age\":\"30\"}"
* @endcode
*/
FAKER_CXX_EXPORT std::string toJSON(std::map<std::string, std::string>& data);

std::string toJSON(std::map<std::string, std::string>& data);
}
62 changes: 38 additions & 24 deletions src/modules/internet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ std::string anonymousUsername(unsigned maxLength)
return common::format("{}{}", word::adjective(adjectiveLength), word::noun(nounLength));
}

std::string toBase64UrlEncode(std::string& input)
std::string utility::toBase64UrlEncode(const std::string& input)
{
const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string encodedInput;
Expand All @@ -372,9 +372,14 @@ std::string toBase64UrlEncode(std::string& input)
}

if (validBits > -6)
{
encodedInput.push_back(base64Chars[((value << 8) >> validBits) & 0x3F]);
}

while (encodedInput.size() % 4)
{
encodedInput.push_back('=');
}

std::replace(encodedInput.begin(), encodedInput.end(), '+', '-');
std::replace(encodedInput.begin(), encodedInput.end(), '/', '_');
Expand All @@ -383,13 +388,15 @@ std::string toBase64UrlEncode(std::string& input)
return encodedInput;
}

std::string toJSON(std::map<std::string, std::string>& data)
std::string utility::toJSON(std::map<std::string, std::string>& data)
{
std::string json = "{";
for (auto it = data.begin(); it != data.end(); ++it)
{
if (it != data.begin())
{
json += ",";
}
json += "\"" + it->first + "\":\"" + it->second + "\"";
}
json += "}";
Expand All @@ -401,40 +408,47 @@ std::string_view getJWTAlgorithm()
return helper::randomElement(jwtAlgorithms);
}

std::string getJWTToken(std::optional<std::map<std::string, std::string>> header,
std::optional<std::map<std::string, std::string>> payload, std::optional<std::string> refDate)
std::string getJWTToken(const std::optional<std::map<std::string, std::string>>& header,
const std::optional<std::map<std::string, std::string>>& payload,
const std::optional<std::string>& refDate)
{
std::string refDateValue = refDate.value_or(faker::date::anytime());
const auto refDateValue = refDate.value_or(faker::date::anytime());

// maybe add option to set ref date to date functions then refactor this
std::string iatDefault = faker::date::recentDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
std::string expDefault = faker::date::soonDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
std::string nbfDefault = faker::date::anytime(faker::date::DateFormat::Timestamp);
const auto iatDefault = faker::date::recentDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
const auto expDefault = faker::date::soonDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
const auto nbfDefault = faker::date::anytime(faker::date::DateFormat::Timestamp);

std::optional<std::map<std::string, std::string>> localHeader = header;
std::optional<std::map<std::string, std::string>> localPayload = payload;

std::string algorithm(getJWTAlgorithm());

if (!header)
header = {{"alg", algorithm}, {"typ", "JWT"}};
if (!payload)
if (!localHeader)
{
localHeader = {{"alg", algorithm}, {"typ", "JWT"}};
}

if (!localPayload)
{
payload = {{"iat", std::to_string(std::round(std::stoll(iatDefault)))},
{"exp", std::to_string(std::round(std::stoll(expDefault)))},
{"nbf", std::to_string(std::round(std::stoll(nbfDefault)))},
{"iss", faker::company::companyName()},
{"sub", faker::string::uuid()},
{"aud", faker::string::uuid()},
{"jti", faker::string::uuid()}};
localPayload = {{"iat", std::to_string(std::round(std::stoll(iatDefault)))},
{"exp", std::to_string(std::round(std::stoll(expDefault)))},
{"nbf", std::to_string(std::round(std::stoll(nbfDefault)))},
{"iss", faker::company::companyName()},
{"sub", faker::string::uuid()},
{"aud", faker::string::uuid()},
{"jti", faker::string::uuid()}};
}

std::string headerToJSON = toJSON(header.value());
std::string encodedHeader = toBase64UrlEncode(headerToJSON);
const auto headerToJSON = utility::toJSON(localHeader.value());
const auto encodedHeader = utility::toBase64UrlEncode(headerToJSON);

std::string payloadToJSON = toJSON(payload.value());
std::string encodedPayload = toBase64UrlEncode(payloadToJSON);
const auto payloadToJSON = utility::toJSON(localPayload.value());
const auto encodedPayload = utility::toBase64UrlEncode(payloadToJSON);

std::string signature = faker::string::alphanumeric(64);
const auto signature = faker::string::alphanumeric(64);

return encodedHeader + "." + encodedPayload + "." + signature;
}

}
34 changes: 34 additions & 0 deletions tests/modules/internet_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using namespace ::testing;
using namespace faker;
using namespace faker::internet;
using namespace faker::internet::utility;

namespace
{
Expand Down Expand Up @@ -808,5 +809,38 @@ TEST_F(InternetTest, shouldGenerateAnonymousUsernameWithMaxLength)
TEST_F(InternetTest, shouldGenerateJwtToken)
{
std::regex pattern(R"([A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+)");
const std::map<std::string, std::string> header = {{"alg", "HS256"}, {"typ", "JWT"}};
const std::map<std::string, std::string> payload = {{"sub", "1234567890"}, {"name", "John Doe"}, {"admin", "true"}};
const auto refDate = std::to_string(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));

ASSERT_TRUE(std::regex_match(getJWTToken(), pattern));
ASSERT_TRUE(std::regex_match(getJWTToken(header), pattern));
ASSERT_TRUE(std::regex_match(getJWTToken(header, payload), pattern));
ASSERT_TRUE(std::regex_match(getJWTToken(header, payload, refDate), pattern));
}

TEST_F(InternetTest, shouldGenerateJWTAlgorithm)
{
const auto generatedJWTAlgorythm = getJWTAlgorithm();

ASSERT_TRUE(std::ranges::any_of(jwtAlgorithms, [generatedJWTAlgorythm](const std::string_view& JWTAlgorythm)
{ return generatedJWTAlgorythm == JWTAlgorythm; }));
}

TEST_F(InternetTest, shouldProduceJSONFromMap)
{
std::map<std::string, std::string> map = {{"key1", "value1"}, {"key2", "value2"}};
const auto json = toJSON(map);

ASSERT_EQ(json, "{\"key1\":\"value1\",\"key2\":\"value2\"}");
}

TEST_F(InternetTest, shouldProduceBase64UrlEncoding)
{
const std::string input = "Hello, World!";
const std::string expectedOutput = "SGVsbG8sIFdvcmxkIG";

const auto output = toBase64UrlEncode(input);

ASSERT_EQ(output, expectedOutput);
}

0 comments on commit 18c605f

Please sign in to comment.