Skip to content

Commit 65cda08

Browse files
Implemented JsonCPP Traits (#317)
* 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>
1 parent 8cb307a commit 65cda08

File tree

11 files changed

+467
-9
lines changed

11 files changed

+467
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Install open-source-parsers/jsoncpp
2+
description: Install open-source-parsers/jsoncpp for building test application
3+
inputs:
4+
version:
5+
description: The desired open-source-parsers/jsoncpp version to install
6+
required: false
7+
default: "1.9.5"
8+
runs:
9+
using: composite
10+
steps:
11+
- run: |
12+
cd /tmp
13+
wget https://github.com/open-source-parsers/jsoncpp/archive/${{ inputs.version }}.tar.gz
14+
tar -zxf /tmp/${{ inputs.version }}.tar.gz
15+
cd jsoncpp-${{ inputs.version }}
16+
# https://github.com/open-source-parsers/jsoncpp/blob/69098a18b9af0c47549d9a271c054d13ca92b006/include/PreventInSourceBuilds.cmake#L8
17+
mkdir build
18+
cd build
19+
cmake .. -DJSONCPP_WITH_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJECT_LIBS=OFF
20+
cmake --build .
21+
sudo cmake --install .
22+
shell: bash

Diff for: .github/actions/render/defaults/action.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ runs:
4747
library_url: LIBRARY_URL,
4848
disable_default_traits: disableDefault,
4949
})
50-
const outputDir = path.join('include', 'jwt-cpp', 'traits', TRAITS_NAME.replace('_', '-'))
50+
// https://dmitripavlutin.com/replace-all-string-occurrences-javascript/
51+
function replaceAll(string, search, replace) {
52+
return string.split(search).join(replace);
53+
}
54+
55+
const outputDir = path.join('include', 'jwt-cpp', 'traits', replaceAll(TRAITS_NAME, '_', '-'))
5156
fs.mkdirSync(outputDir, { recursive: true })
5257
fs.writeFileSync(path.join(outputDir, 'defaults.h'), content)

Diff for: .github/workflows/lint.yml

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ jobs:
7575
- { name: "danielaparker_jsoncons", library: "jsoncons", url: "https://github.com/danielaparker/jsoncons", disable_pico: true }
7676
- { name: "kazuho_picojson", library: "picojson", url: "https://github.com/kazuho/picojson", disable_pico: false }
7777
- { name: "nlohmann_json", library: "JSON for Modern C++", url: "https://github.com/nlohmann/json", disable_pico: true }
78+
- { name: "open_source_parsers_jsoncpp", library: "jsoncpp", url: "https://github.com/open-source-parsers/jsoncpp", disable_pico: true }
7879
name: render-defaults (${{ matrix.traits.name }})
7980
steps:
8081
- uses: actions/checkout@v3
@@ -84,6 +85,7 @@ jobs:
8485
library_name: ${{ matrix.traits.library }}
8586
library_url: ${{ matrix.traits.url }}
8687
disable_default_traits: ${{ matrix.traits.disable_pico }}
88+
- run: clang-format -i include/jwt-cpp/traits/**/*.h
8789
- run: git add include/jwt-cpp/traits/*
8890
- uses: ./.github/actions/process-linting-results
8991
with:
@@ -99,6 +101,7 @@ jobs:
99101
- { name: "danielaparker_jsoncons", suite: "JsonconsTest" }
100102
# - { name: "kazuho_picojson", suite: "PicoJsonTest" } # Currently the default everything tests against this!
101103
- { name: "nlohmann_json", suite: "NlohmannTest" }
104+
- { name: "open_source_parsers_jsoncpp", suite: "OspJsoncppTest" }
102105
name: render-tests (${{ matrix.traits.name }})
103106
steps:
104107
- uses: actions/checkout@v3

Diff for: .github/workflows/traits.yml

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
- { name: "boost-json", tag: "1.78.0", version: "v1.80.0" }
1818
- { name: "nlohmann-json", tag: "3.11.2", version: "v3.11.2" }
1919
- { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" }
20+
- { name: "open-source-parsers-jsoncpp", tag: "1.9.5", version: "v1.9.5" }
2021
steps:
2122
- uses: actions/checkout@v3
2223
- uses: lukka/get-cmake@latest
@@ -50,6 +51,11 @@ jobs:
5051
with:
5152
version: ${{matrix.target.tag}}
5253

54+
- if: matrix.target.name == 'open-source-parsers-jsoncpp'
55+
uses: ./.github/actions/install/open-source-parsers-jsoncpp
56+
with:
57+
version: ${{matrix.target.tag}}
58+
5359
- name: test
5460
working-directory: example/traits
5561
run: |

Diff for: CMakeLists.txt

+4-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ set(JWT_SSL_LIBRARY_OPTIONS OpenSSL LibreSSL wolfSSL)
2828
set(JWT_SSL_LIBRARY OpenSSL CACHE STRING "Determines which SSL library to build with")
2929
set_property(CACHE JWT_SSL_LIBRARY PROPERTY STRINGS ${JWT_SSL_LIBRARY_OPTIONS})
3030

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

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

@@ -71,11 +71,9 @@ find_package(nlohmann_json CONFIG)
7171
if(NOT nlohmann_json_FOUND)
7272
message(STATUS "jwt-cpp: using FetchContent for nlohmann json")
7373
include(FetchContent)
74-
FetchContent_Declare(nlohmann_json
75-
URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz
76-
URL_MD5 127794b2c82c0c5693805feaa2a703e2
77-
)
78-
FetchContent_MakeAvailable(nlohmann_json)
74+
fetchcontent_declare(nlohmann_json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz
75+
URL_MD5 127794b2c82c0c5693805feaa2a703e2)
76+
fetchcontent_makeavailable(nlohmann_json)
7977
endif()
8078

8179
set(JWT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)

Diff for: example/traits/CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ if(TARGET kazuho_picojson)
2828
add_executable(kazuho-picojson kazuho-picojson.cpp)
2929
target_link_libraries(kazuho-picojson jwt-cpp::jwt-cpp kazuho_picojson)
3030
endif()
31+
32+
find_package(jsoncpp CONFIG)
33+
if(TARGET jsoncpp_static)
34+
add_executable(open-source-parsers-jsoncpp open-source-parsers-jsoncpp.cpp)
35+
target_link_libraries(open-source-parsers-jsoncpp jsoncpp_static jwt-cpp::jwt-cpp)
36+
endif()

Diff for: example/traits/open-source-parsers-jsoncpp.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include "jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h"
2+
3+
#include <iostream>
4+
#include <sstream>
5+
6+
int main() {
7+
using sec = std::chrono::seconds;
8+
using min = std::chrono::minutes;
9+
using traits = jwt::traits::open_source_parsers_jsoncpp;
10+
using claim = jwt::basic_claim<traits>;
11+
12+
claim from_raw_json;
13+
std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"};
14+
iss >> from_raw_json;
15+
16+
claim::set_t list{"once", "twice"};
17+
std::vector<int64_t> big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL};
18+
19+
const auto time = jwt::date::clock::now();
20+
const auto token = jwt::create<traits>()
21+
.set_type("JWT")
22+
.set_issuer("auth.mydomain.io")
23+
.set_audience("mydomain.io")
24+
.set_issued_at(time)
25+
.set_not_before(time)
26+
.set_expires_at(time + min{2} + sec{15})
27+
.set_payload_claim("boolean", true)
28+
.set_payload_claim("integer", 12345)
29+
.set_payload_claim("precision", 12.3456789)
30+
.set_payload_claim("strings", claim(list))
31+
.set_payload_claim("array", {big_numbers.begin(), big_numbers.end()})
32+
.set_payload_claim("object", from_raw_json)
33+
.sign(jwt::algorithm::none{});
34+
const auto decoded = jwt::decode<traits>(token);
35+
36+
const auto array = traits::as_array(decoded.get_payload_claim("object").to_json()["api"]["array"]);
37+
std::cout << "payload /object/api/array = " << array << std::endl;
38+
39+
jwt::verify<traits>()
40+
.allow_algorithm(jwt::algorithm::none{})
41+
.with_issuer("auth.mydomain.io")
42+
.with_audience("mydomain.io")
43+
.with_claim("object", from_raw_json)
44+
.verify(decoded);
45+
46+
return 0;
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#ifndef JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H
2+
#define JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H
3+
4+
#ifndef JWT_DISABLE_PICOJSON
5+
#define JWT_DISABLE_PICOJSON
6+
#endif
7+
8+
#include "traits.h"
9+
10+
namespace jwt {
11+
/**
12+
* \brief a class to store a generic [jsoncpp](https://github.com/open-source-parsers/jsoncpp) value as claim
13+
*
14+
* This type is the specialization of the \ref basic_claim class which
15+
* uses the standard template types.
16+
*/
17+
using claim = basic_claim<traits::open_source_parsers_jsoncpp>;
18+
19+
/**
20+
* Create a verifier using the default clock
21+
* \return verifier instance
22+
*/
23+
inline verifier<default_clock, traits::open_source_parsers_jsoncpp> verify() {
24+
return verify<default_clock, traits::open_source_parsers_jsoncpp>(default_clock{});
25+
}
26+
27+
/**
28+
* Return a builder instance to create a new token
29+
*/
30+
inline builder<traits::open_source_parsers_jsoncpp> create() {
31+
return builder<traits::open_source_parsers_jsoncpp>();
32+
}
33+
34+
#ifndef JWT_DISABLE_BASE64
35+
/**
36+
* Decode a token
37+
* \param token Token to decode
38+
* \return Decoded token
39+
* \throw std::invalid_argument Token is not in correct format
40+
* \throw std::runtime_error Base64 decoding failed or invalid json
41+
*/
42+
inline decoded_jwt<traits::open_source_parsers_jsoncpp> decode(const std::string& token) {
43+
return decoded_jwt<traits::open_source_parsers_jsoncpp>(token);
44+
}
45+
#endif
46+
47+
/**
48+
* Decode a token
49+
* \tparam Decode is callabled, taking a string_type and returns a string_type.
50+
* It should ensure the padding of the input and then base64url decode and
51+
* return the results.
52+
* \param token Token to decode
53+
* \param decode The token to parse
54+
* \return Decoded token
55+
* \throw std::invalid_argument Token is not in correct format
56+
* \throw std::runtime_error Base64 decoding failed or invalid json
57+
*/
58+
template<typename Decode>
59+
decoded_jwt<traits::open_source_parsers_jsoncpp> decode(const std::string& token, Decode decode) {
60+
return decoded_jwt<traits::open_source_parsers_jsoncpp>(token, decode);
61+
}
62+
63+
/**
64+
* Parse a jwk
65+
* \param token JWK Token to parse
66+
* \return Parsed JWK
67+
* \throw std::runtime_error Token is not in correct format
68+
*/
69+
inline jwk<traits::open_source_parsers_jsoncpp>
70+
parse_jwk(const traits::open_source_parsers_jsoncpp::string_type& token) {
71+
return jwk<traits::open_source_parsers_jsoncpp>(token);
72+
}
73+
74+
/**
75+
* Parse a jwks
76+
* \param token JWKs Token to parse
77+
* \return Parsed JWKs
78+
* \throw std::runtime_error Token is not in correct format
79+
*/
80+
inline jwks<traits::open_source_parsers_jsoncpp>
81+
parse_jwks(const traits::open_source_parsers_jsoncpp::string_type& token) {
82+
return jwks<traits::open_source_parsers_jsoncpp>(token);
83+
}
84+
85+
/**
86+
* This type is the specialization of the \ref verify_ops::verify_context class which
87+
* uses the standard template types.
88+
*/
89+
using verify_context = verify_ops::verify_context<traits::open_source_parsers_jsoncpp>;
90+
} // namespace jwt
91+
92+
#endif // JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#ifndef JWT_CPP_JSONCPP_TRAITS_H
2+
#define JWT_CPP_JSONCPP_TRAITS_H
3+
4+
#include "jwt-cpp/jwt.h"
5+
#include "json/json.h"
6+
7+
namespace jwt {
8+
namespace traits {
9+
struct open_source_parsers_jsoncpp {
10+
using value_type = Json::Value;
11+
using string_type = std::string;
12+
class array_type : public Json::Value {
13+
public:
14+
using value_type = Json::Value;
15+
16+
array_type() = default;
17+
array_type(const array_type&) = default;
18+
explicit array_type(const Json::Value& o) : Json::Value(o) {}
19+
array_type(array_type&&) = default;
20+
explicit array_type(Json::Value&& o) : Json::Value(o) {}
21+
template<typename Iterator>
22+
array_type(Iterator begin, Iterator end) {
23+
for (Iterator it = begin; it != end; ++it) {
24+
Json::Value value;
25+
value = *it;
26+
this->append(value);
27+
}
28+
}
29+
~array_type() = default;
30+
array_type& operator=(const array_type& o) = default;
31+
array_type& operator=(array_type&& o) noexcept = default;
32+
};
33+
using number_type = double;
34+
using integer_type = Json::Value::Int;
35+
using boolean_type = bool;
36+
class object_type : public Json::Value {
37+
public:
38+
using key_type = std::string;
39+
using mapped_type = Json::Value;
40+
using size_type = size_t;
41+
42+
object_type() = default;
43+
object_type(const object_type&) = default;
44+
explicit object_type(const Json::Value& o) : Json::Value(o) {}
45+
object_type(object_type&&) = default;
46+
explicit object_type(Json::Value&& o) : Json::Value(o) {}
47+
~object_type() = default;
48+
object_type& operator=(const object_type& o) = default;
49+
object_type& operator=(object_type&& o) noexcept = default;
50+
51+
// Add missing C++11 element access
52+
const mapped_type& at(const key_type& key) const {
53+
Json::Value const* found = find(key.data(), key.data() + key.length());
54+
if (!found) throw std::out_of_range("invalid key");
55+
return *found;
56+
}
57+
58+
size_type count(const key_type& key) const { return this->isMember(key) ? 1 : 0; }
59+
};
60+
61+
// Translation between the implementation notion of type, to the jwt::json::type equivilant
62+
static jwt::json::type get_type(const value_type& val) {
63+
using jwt::json::type;
64+
65+
if (val.isArray())
66+
return type::array;
67+
else if (val.isString())
68+
return type::string;
69+
else if (val.isNumeric())
70+
return type::number;
71+
else if (val.isInt())
72+
return type::integer;
73+
else if (val.isBool())
74+
return type::boolean;
75+
else if (val.isObject())
76+
return type::object;
77+
78+
throw std::logic_error("invalid type");
79+
}
80+
81+
static integer_type as_integer(const value_type& val) {
82+
switch (val.type()) {
83+
case Json::intValue: return val.asInt64();
84+
case Json::uintValue: return static_cast<integer_type>(val.asUInt64());
85+
default: throw std::bad_cast();
86+
}
87+
}
88+
89+
static boolean_type as_boolean(const value_type& val) {
90+
if (!val.isBool()) throw std::bad_cast();
91+
return val.asBool();
92+
}
93+
94+
static number_type as_number(const value_type& val) {
95+
if (!val.isNumeric()) throw std::bad_cast();
96+
return val.asDouble();
97+
}
98+
99+
static string_type as_string(const value_type& val) {
100+
if (!val.isString()) throw std::bad_cast();
101+
return val.asString();
102+
}
103+
104+
static object_type as_object(const value_type& val) {
105+
if (!val.isObject()) throw std::bad_cast();
106+
return object_type(val);
107+
}
108+
109+
static array_type as_array(const value_type& val) {
110+
if (!val.isArray()) throw std::bad_cast();
111+
return array_type(val);
112+
}
113+
114+
static bool parse(value_type& val, string_type str) {
115+
Json::Reader reader;
116+
return reader.parse(str, val);
117+
}
118+
119+
static string_type serialize(const value_type& val) {
120+
Json::StreamWriterBuilder builder;
121+
builder["commentStyle"] = "None";
122+
builder["indentation"] = "";
123+
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
124+
return Json::writeString(builder, val);
125+
}
126+
};
127+
} // namespace traits
128+
} // namespace jwt
129+
130+
#endif

0 commit comments

Comments
 (0)