Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented JsonCPP Traits #317

Merged
merged 17 commits into from
Dec 16, 2023
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
22 changes: 22 additions & 0 deletions .github/actions/install/open-source-parsers-jsoncpp/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Install open-source-parsers/jsoncpp
description: Install open-source-parsers/jsoncpp for building test application
inputs:
version:
description: The desired open-source-parsers/jsoncpp version to install
required: false
default: "1.9.5"
runs:
using: composite
steps:
- run: |
cd /tmp
wget https://github.com/open-source-parsers/jsoncpp/archive/${{ inputs.version }}.tar.gz
tar -zxf /tmp/${{ inputs.version }}.tar.gz
cd jsoncpp-${{ inputs.version }}
# https://github.com/open-source-parsers/jsoncpp/blob/69098a18b9af0c47549d9a271c054d13ca92b006/include/PreventInSourceBuilds.cmake#L8
mkdir build
cd build
cmake .. -DJSONCPP_WITH_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJECT_LIBS=OFF
cmake --build .
sudo cmake --install .
shell: bash
7 changes: 6 additions & 1 deletion .github/actions/render/defaults/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ runs:
library_url: LIBRARY_URL,
disable_default_traits: disableDefault,
})
const outputDir = path.join('include', 'jwt-cpp', 'traits', TRAITS_NAME.replace('_', '-'))
// https://dmitripavlutin.com/replace-all-string-occurrences-javascript/
function replaceAll(string, search, replace) {
return string.split(search).join(replace);
}

const outputDir = path.join('include', 'jwt-cpp', 'traits', replaceAll(TRAITS_NAME, '_', '-'))
fs.mkdirSync(outputDir, { recursive: true })
fs.writeFileSync(path.join(outputDir, 'defaults.h'), content)
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
- { name: "danielaparker_jsoncons", library: "jsoncons", url: "https://github.com/danielaparker/jsoncons", disable_pico: true }
- { name: "kazuho_picojson", library: "picojson", url: "https://github.com/kazuho/picojson", disable_pico: false }
- { name: "nlohmann_json", library: "JSON for Modern C++", url: "https://github.com/nlohmann/json", disable_pico: true }
- { name: "open_source_parsers_jsoncpp", library: "jsoncpp", url: "https://github.com/open-source-parsers/jsoncpp", disable_pico: true }
name: render-defaults (${{ matrix.traits.name }})
steps:
- uses: actions/checkout@v3
Expand All @@ -84,6 +85,7 @@ jobs:
library_name: ${{ matrix.traits.library }}
library_url: ${{ matrix.traits.url }}
disable_default_traits: ${{ matrix.traits.disable_pico }}
- run: clang-format -i include/jwt-cpp/traits/**/*.h
- run: git add include/jwt-cpp/traits/*
- uses: ./.github/actions/process-linting-results
with:
Expand All @@ -99,6 +101,7 @@ jobs:
- { name: "danielaparker_jsoncons", suite: "JsonconsTest" }
# - { name: "kazuho_picojson", suite: "PicoJsonTest" } # Currently the default everything tests against this!
- { name: "nlohmann_json", suite: "NlohmannTest" }
- { name: "open_source_parsers_jsoncpp", suite: "OspJsoncppTest" }
name: render-tests (${{ matrix.traits.name }})
steps:
- uses: actions/checkout@v3
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/traits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- { name: "boost-json", tag: "1.78.0", version: "v1.80.0" }
- { name: "nlohmann-json", tag: "3.11.2", version: "v3.11.2" }
- { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" }
- { name: "open-source-parsers-jsoncpp", tag: "1.9.5", version: "v1.9.5" }
steps:
- uses: actions/checkout@v3
- uses: lukka/get-cmake@latest
Expand Down Expand Up @@ -50,6 +51,11 @@ jobs:
with:
version: ${{matrix.target.tag}}

- if: matrix.target.name == 'open-source-parsers-jsoncpp'
uses: ./.github/actions/install/open-source-parsers-jsoncpp
with:
version: ${{matrix.target.tag}}

- name: test
working-directory: example/traits
run: |
Expand Down
10 changes: 4 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ set(JWT_SSL_LIBRARY_OPTIONS OpenSSL LibreSSL wolfSSL)
set(JWT_SSL_LIBRARY OpenSSL CACHE STRING "Determines which SSL library to build with")
set_property(CACHE JWT_SSL_LIBRARY PROPERTY STRINGS ${JWT_SSL_LIBRARY_OPTIONS})

set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json)
set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json open-source-parsers-jsoncpp)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")

Expand Down Expand Up @@ -71,11 +71,9 @@ find_package(nlohmann_json CONFIG)
if(NOT nlohmann_json_FOUND)
message(STATUS "jwt-cpp: using FetchContent for nlohmann json")
include(FetchContent)
FetchContent_Declare(nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz
URL_MD5 127794b2c82c0c5693805feaa2a703e2
)
FetchContent_MakeAvailable(nlohmann_json)
fetchcontent_declare(nlohmann_json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz
URL_MD5 127794b2c82c0c5693805feaa2a703e2)
fetchcontent_makeavailable(nlohmann_json)
endif()

set(JWT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
Expand Down
6 changes: 6 additions & 0 deletions example/traits/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ if(TARGET kazuho_picojson)
add_executable(kazuho-picojson kazuho-picojson.cpp)
target_link_libraries(kazuho-picojson jwt-cpp::jwt-cpp kazuho_picojson)
endif()

find_package(jsoncpp CONFIG)
if(TARGET jsoncpp_static)
add_executable(open-source-parsers-jsoncpp open-source-parsers-jsoncpp.cpp)
target_link_libraries(open-source-parsers-jsoncpp jsoncpp_static jwt-cpp::jwt-cpp)
endif()
47 changes: 47 additions & 0 deletions example/traits/open-source-parsers-jsoncpp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h"

#include <iostream>
#include <sstream>

int main() {
using sec = std::chrono::seconds;
using min = std::chrono::minutes;
using traits = jwt::traits::open_source_parsers_jsoncpp;
using claim = jwt::basic_claim<traits>;

claim from_raw_json;
std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"};
iss >> from_raw_json;

claim::set_t list{"once", "twice"};
std::vector<int64_t> big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL};

const auto time = jwt::date::clock::now();
const auto token = jwt::create<traits>()
.set_type("JWT")
.set_issuer("auth.mydomain.io")
.set_audience("mydomain.io")
.set_issued_at(time)
.set_not_before(time)
.set_expires_at(time + min{2} + sec{15})
.set_payload_claim("boolean", true)
.set_payload_claim("integer", 12345)
.set_payload_claim("precision", 12.3456789)
.set_payload_claim("strings", claim(list))
.set_payload_claim("array", {big_numbers.begin(), big_numbers.end()})
.set_payload_claim("object", from_raw_json)
.sign(jwt::algorithm::none{});
const auto decoded = jwt::decode<traits>(token);

const auto array = traits::as_array(decoded.get_payload_claim("object").to_json()["api"]["array"]);
std::cout << "payload /object/api/array = " << array << std::endl;

jwt::verify<traits>()
.allow_algorithm(jwt::algorithm::none{})
.with_issuer("auth.mydomain.io")
.with_audience("mydomain.io")
.with_claim("object", from_raw_json)
.verify(decoded);

return 0;
}
92 changes: 92 additions & 0 deletions include/jwt-cpp/traits/open-source-parsers-jsoncpp/defaults.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H
#define JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H

#ifndef JWT_DISABLE_PICOJSON
#define JWT_DISABLE_PICOJSON
#endif

#include "traits.h"

namespace jwt {
/**
* \brief a class to store a generic [jsoncpp](https://github.com/open-source-parsers/jsoncpp) value as claim
*
* This type is the specialization of the \ref basic_claim class which
* uses the standard template types.
*/
using claim = basic_claim<traits::open_source_parsers_jsoncpp>;

/**
* Create a verifier using the default clock
* \return verifier instance
*/
inline verifier<default_clock, traits::open_source_parsers_jsoncpp> verify() {
return verify<default_clock, traits::open_source_parsers_jsoncpp>(default_clock{});
}

/**
* Return a builder instance to create a new token
*/
inline builder<traits::open_source_parsers_jsoncpp> create() {
return builder<traits::open_source_parsers_jsoncpp>();
}

#ifndef JWT_DISABLE_BASE64
/**
* Decode a token
* \param token Token to decode
* \return Decoded token
* \throw std::invalid_argument Token is not in correct format
* \throw std::runtime_error Base64 decoding failed or invalid json
*/
inline decoded_jwt<traits::open_source_parsers_jsoncpp> decode(const std::string& token) {
return decoded_jwt<traits::open_source_parsers_jsoncpp>(token);
}
#endif

/**
* Decode a token
* \tparam Decode is callabled, taking a string_type and returns a string_type.
* It should ensure the padding of the input and then base64url decode and
* return the results.
* \param token Token to decode
* \param decode The token to parse
* \return Decoded token
* \throw std::invalid_argument Token is not in correct format
* \throw std::runtime_error Base64 decoding failed or invalid json
*/
template<typename Decode>
decoded_jwt<traits::open_source_parsers_jsoncpp> decode(const std::string& token, Decode decode) {
return decoded_jwt<traits::open_source_parsers_jsoncpp>(token, decode);
}

/**
* Parse a jwk
* \param token JWK Token to parse
* \return Parsed JWK
* \throw std::runtime_error Token is not in correct format
*/
inline jwk<traits::open_source_parsers_jsoncpp>
parse_jwk(const traits::open_source_parsers_jsoncpp::string_type& token) {
return jwk<traits::open_source_parsers_jsoncpp>(token);
}

/**
* Parse a jwks
* \param token JWKs Token to parse
* \return Parsed JWKs
* \throw std::runtime_error Token is not in correct format
*/
inline jwks<traits::open_source_parsers_jsoncpp>
parse_jwks(const traits::open_source_parsers_jsoncpp::string_type& token) {
return jwks<traits::open_source_parsers_jsoncpp>(token);
}

/**
* This type is the specialization of the \ref verify_ops::verify_context class which
* uses the standard template types.
*/
using verify_context = verify_ops::verify_context<traits::open_source_parsers_jsoncpp>;
} // namespace jwt

#endif // JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H
130 changes: 130 additions & 0 deletions include/jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#ifndef JWT_CPP_JSONCPP_TRAITS_H
#define JWT_CPP_JSONCPP_TRAITS_H

#include "jwt-cpp/jwt.h"
#include "json/json.h"

namespace jwt {
namespace traits {
struct open_source_parsers_jsoncpp {
using value_type = Json::Value;
using string_type = std::string;
class array_type : public Json::Value {
public:
using value_type = Json::Value;

array_type() = default;
array_type(const array_type&) = default;
explicit array_type(const Json::Value& o) : Json::Value(o) {}
array_type(array_type&&) = default;
explicit array_type(Json::Value&& o) : Json::Value(o) {}
template<typename Iterator>
array_type(Iterator begin, Iterator end) {
for (Iterator it = begin; it != end; ++it) {
Json::Value value;
value = *it;
this->append(value);
}
}
~array_type() = default;
array_type& operator=(const array_type& o) = default;
array_type& operator=(array_type&& o) noexcept = default;
};
using number_type = double;
using integer_type = Json::Value::Int;
using boolean_type = bool;
class object_type : public Json::Value {
public:
using key_type = std::string;
using mapped_type = Json::Value;
using size_type = size_t;

object_type() = default;
object_type(const object_type&) = default;
explicit object_type(const Json::Value& o) : Json::Value(o) {}
object_type(object_type&&) = default;
explicit object_type(Json::Value&& o) : Json::Value(o) {}
~object_type() = default;
object_type& operator=(const object_type& o) = default;
object_type& operator=(object_type&& o) noexcept = default;

// Add missing C++11 element access
const mapped_type& at(const key_type& key) const {
Json::Value const* found = find(key.data(), key.data() + key.length());
if (!found) throw std::out_of_range("invalid key");
return *found;
}

size_type count(const key_type& key) const { return this->isMember(key) ? 1 : 0; }
};

// Translation between the implementation notion of type, to the jwt::json::type equivilant
static jwt::json::type get_type(const value_type& val) {
using jwt::json::type;

if (val.isArray())
return type::array;
else if (val.isString())
return type::string;
else if (val.isNumeric())
return type::number;
else if (val.isInt())
return type::integer;
else if (val.isBool())
return type::boolean;
else if (val.isObject())
return type::object;

throw std::logic_error("invalid type");
}

static integer_type as_integer(const value_type& val) {
switch (val.type()) {
case Json::intValue: return val.asInt64();
case Json::uintValue: return static_cast<integer_type>(val.asUInt64());
default: throw std::bad_cast();
}
}

static boolean_type as_boolean(const value_type& val) {
if (!val.isBool()) throw std::bad_cast();
return val.asBool();
}

static number_type as_number(const value_type& val) {
if (!val.isNumeric()) throw std::bad_cast();
return val.asDouble();
}

static string_type as_string(const value_type& val) {
if (!val.isString()) throw std::bad_cast();
return val.asString();
}

static object_type as_object(const value_type& val) {
if (!val.isObject()) throw std::bad_cast();
return object_type(val);
}

static array_type as_array(const value_type& val) {
if (!val.isArray()) throw std::bad_cast();
return array_type(val);
}

static bool parse(value_type& val, string_type str) {
Json::Reader reader;
return reader.parse(str, val);
}

static string_type serialize(const value_type& val) {
Json::StreamWriterBuilder builder;
builder["commentStyle"] = "None";
builder["indentation"] = "";
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
return Json::writeString(builder, val);
}
};
} // namespace traits
} // namespace jwt

#endif
Loading
Loading