From 18c605ffce111832685854615c1adfcccb2d2bec Mon Sep 17 00:00:00 2001 From: Martin Onufrak Date: Tue, 29 Oct 2024 15:57:57 +0100 Subject: [PATCH] [INTERNET MODULE][JWT] Refactoring and wider test coverage --- include/faker-cxx/internet.h | 23 ++++++------ src/modules/internet.cpp | 62 ++++++++++++++++++++------------- tests/modules/internet_test.cpp | 34 ++++++++++++++++++ 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/include/faker-cxx/internet.h b/include/faker-cxx/internet.h index d3dbcdb34..a67c4983c 100644 --- a/include/faker-cxx/internet.h +++ b/include/faker-cxx/internet.h @@ -369,12 +369,13 @@ FAKER_CXX_EXPORT std::string anonymousUsername(unsigned maxLength); * @code * std::map header = {{"alg", "HS256"}, {"typ", "JWT"}}; * std::map 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> header = std::nullopt, - std::optional> payload = std::nullopt, - std::optional refDate = std::nullopt); +FAKER_CXX_EXPORT std::string +getJWTToken(const std::optional>& header = std::nullopt, + const std::optional>& payload = std::nullopt, + const std::optional& refDate = std::nullopt); /** * @brief Returns the algorithm used for signing the JWT. @@ -384,11 +385,14 @@ FAKER_CXX_EXPORT std::string getJWTToken(std::optional 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& data); - +std::string toJSON(std::map& data); } diff --git a/src/modules/internet.cpp b/src/modules/internet.cpp index ee7e40f26..3cba9d991 100644 --- a/src/modules/internet.cpp +++ b/src/modules/internet.cpp @@ -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; @@ -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(), '/', '_'); @@ -383,13 +388,15 @@ std::string toBase64UrlEncode(std::string& input) return encodedInput; } -std::string toJSON(std::map& data) +std::string utility::toJSON(std::map& data) { std::string json = "{"; for (auto it = data.begin(); it != data.end(); ++it) { if (it != data.begin()) + { json += ","; + } json += "\"" + it->first + "\":\"" + it->second + "\""; } json += "}"; @@ -401,40 +408,47 @@ std::string_view getJWTAlgorithm() return helper::randomElement(jwtAlgorithms); } -std::string getJWTToken(std::optional> header, - std::optional> payload, std::optional refDate) +std::string getJWTToken(const std::optional>& header, + const std::optional>& payload, + const std::optional& 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> localHeader = header; + std::optional> 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; } - + } diff --git a/tests/modules/internet_test.cpp b/tests/modules/internet_test.cpp index 283c80ed3..77d11d7de 100644 --- a/tests/modules/internet_test.cpp +++ b/tests/modules/internet_test.cpp @@ -22,6 +22,7 @@ using namespace ::testing; using namespace faker; using namespace faker::internet; +using namespace faker::internet::utility; namespace { @@ -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 header = {{"alg", "HS256"}, {"typ", "JWT"}}; + const std::map 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 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); }