Skip to content

Commit

Permalink
Implemented JsonCPP Traits (#317)
Browse files Browse the repository at this point in the history
* Implemented JsonCPP Traits

* quick pass to add CI

* correct find_packge to use config and not be required

* typo in default version

* jsoncpp forces out of source builds

* disable tests and shared library

* add missing build step

* try a different target + build less 

"they" do not maintain the CMake scripts so this will be fun open-source-parsers/jsoncpp#455 (comment)

* fix name for traits (missed replaces)

* Added missing array_type constructor so that basic_claims can be formed from sets of values

* Linters and more tests + fetchcontent gtest

* linter

* support library names with many separators

* linters

* run clang format after rendering defaults

---------

Co-authored-by: Chris Mc <prince.chrismc@gmail.com>
  • Loading branch information
cjserio and prince-chrismc authored Dec 16, 2023
1 parent 8cb307a commit 65cda08
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 9 deletions.
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

0 comments on commit 65cda08

Please sign in to comment.