diff --git a/.github/actions/install/boost-json/action.yml b/.github/actions/install/boost-json/action.yml new file mode 100644 index 000000000..c51ce35c9 --- /dev/null +++ b/.github/actions/install/boost-json/action.yml @@ -0,0 +1,17 @@ +name: Install Boost.JSON +description: Install Boost.JSON for building test application +inputs: + version: + description: The desired Boost.JSON version to install + required: false + default: "1.77.0" +runs: + using: composite + steps: + - run: | + cd /tmp + wget https://github.com/boostorg/json/archive/boost-${{ inputs.version }}.tar.gz + tar -zxf /tmp/boost-${{ inputs.version }}.tar.gz + cd json-boost-${{ inputs.version }} + sudo cp -vR include/boost /usr/local/include + shell: bash diff --git a/.github/actions/install/jsoncons/action.yml b/.github/actions/install/danielaparker-jsoncons/action.yml similarity index 95% rename from .github/actions/install/jsoncons/action.yml rename to .github/actions/install/danielaparker-jsoncons/action.yml index 264f6a033..659388c38 100644 --- a/.github/actions/install/jsoncons/action.yml +++ b/.github/actions/install/danielaparker-jsoncons/action.yml @@ -4,7 +4,7 @@ inputs: version: description: The desired jsoncons version to install required: false - default: "0.159.0" + default: "0.167.1" runs: using: composite steps: diff --git a/.github/actions/install/kazuho-picojson/action.yml b/.github/actions/install/kazuho-picojson/action.yml new file mode 100644 index 000000000..caf887512 --- /dev/null +++ b/.github/actions/install/kazuho-picojson/action.yml @@ -0,0 +1,17 @@ +name: Install PicoJSON +description: Install PicoJSON for building test application +inputs: + version: + description: The desired PicoJSON version to install + required: false + default: "v1.3.0" +runs: + using: composite + steps: + - run: | + cd /tmp + wget https://github.com/kazuho/picojson/archive/${{ inputs.version }}.tar.gz + tar -zxf /tmp/${{ inputs.version }}.tar.gz + cd picojson-${{ inputs.version }} + sudo cp -v picojson.h /usr/local/include/picojson + shell: bash diff --git a/.github/actions/install/nlohmann-json/action.yml b/.github/actions/install/nlohmann-json/action.yml new file mode 100644 index 000000000..68d530387 --- /dev/null +++ b/.github/actions/install/nlohmann-json/action.yml @@ -0,0 +1,18 @@ +name: Install nlohmann-json +description: Install nlohmann-json for building test application +inputs: + version: + description: The desired nlohmann-json version to install + required: false + default: "3.10.4" +runs: + using: composite + steps: + - run: | + cd /tmp + wget https://github.com/nlohmann/json/archive/v${{ inputs.version }}.tar.gz + tar -zxf /tmp/v${{ inputs.version }}.tar.gz + cd json-${{ inputs.version }} + cmake . + sudo cmake --install . + shell: bash diff --git a/.github/actions/process-linting-results/action.yml b/.github/actions/process-linting-results/action.yml new file mode 100644 index 000000000..c31f3486d --- /dev/null +++ b/.github/actions/process-linting-results/action.yml @@ -0,0 +1,21 @@ +name: Process Linting Results +description: Add a comment to a pull request with when `git diff` present and save the changes as an artifact so they can be applied manually +inputs: + linter_name: + description: The name of the tool to credit in the comment + required: true +runs: + using: "composite" + steps: + - run: git add --update + shell: bash + - id: stage + uses: dtinth/patch-generator-action@v1 + - if: steps.stage.outputs.result == 'dirty' + uses: actions-ecosystem/action-create-comment@v1 + with: + github_token: ${{ github.token }} + body: | + Hello, @${{ github.actor }}! `${{ inputs.linter_name }}` had some concerns :scream: + - run: exit $(git status -uno -s | wc -l) + shell: bash diff --git a/.github/actions/render/defaults/action.yml b/.github/actions/render/defaults/action.yml new file mode 100644 index 000000000..f4e25bafb --- /dev/null +++ b/.github/actions/render/defaults/action.yml @@ -0,0 +1,52 @@ +name: "Render `defaults.h` Template" +description: "Generate the `defaults.h` header file for a JSON library" +inputs: + traits_name: + description: "Name of the traits structure to be used. Typically in the format `author_repository` or equivilant" + required: true + library_name: + description: "Name of the JSON library." + required: true + library_url: + description: "URL to the JSON library." + required: true + disable_default_traits: + description: "Set the macro to disable the default traits" + required: false + default: "true" +runs: + using: composite + steps: + - uses: actions/setup-node@v2 + with: + node-version: 14 + - run: npm install mustache + shell: bash + - uses: actions/github-script@v5 + env: + TRAITS_NAME: ${{ inputs.traits_name }} + LIBRARY_NAME: ${{ inputs.library_name }} + LIBRARY_URL: ${{ inputs.library_url }} + DISABLE_DEFAULT_TRAITS: ${{ inputs.disable_default_traits }} + with: + script: | + const mustache = require('mustache') + const path = require('path') + const fs = require('fs') + + const { TRAITS_NAME, LIBRARY_NAME, LIBRARY_URL, DISABLE_DEFAULT_TRAITS } = process.env + console.log(`Rendering ${TRAITS_NAME}!`) + + const disableDefault = DISABLE_DEFAULT_TRAITS === 'true' + + const template = fs.readFileSync(path.join('include', 'jwt-cpp', 'traits', 'defaults.h.mustache'), 'utf8') + const content = mustache.render(template, { + traits_name: TRAITS_NAME, + traits_name_upper: TRAITS_NAME.toUpperCase(), + library_name: LIBRARY_NAME, + library_url: LIBRARY_URL, + disable_default_traits: disableDefault, + }) + const outputDir = path.join('include', 'jwt-cpp', 'traits', TRAITS_NAME.replace('_', '-')) + fs.mkdirSync(outputDir, { recursive: true }) + fs.writeFileSync(path.join(outputDir, 'defaults.h'), content) diff --git a/.github/actions/render/tests/action.yml b/.github/actions/render/tests/action.yml new file mode 100644 index 000000000..548721b6a --- /dev/null +++ b/.github/actions/render/tests/action.yml @@ -0,0 +1,39 @@ +name: "Render `TraitsTests.cpp` Template" +description: "Generate the `TraitsTests.cpp` header file for a JSON library" +inputs: + traits_name: + description: "Name of the traits structure to be used. Typically in the format `author_repository` or equivilant" + required: true + test_suite_name: + description: "Name of the JSON library." + required: true +runs: + using: composite + steps: + - uses: actions/setup-node@v2 + with: + node-version: 14 + - run: npm install mustache + shell: bash + - uses: actions/github-script@v5 + env: + TRAITS_NAME: ${{ inputs.traits_name }} + SUITE_NAME: ${{ inputs.test_suite_name }} + with: + script: | + const mustache = require('mustache') + const path = require('path') + const fs = require('fs') + + const { TRAITS_NAME, SUITE_NAME } = process.env + console.log(`Rendering ${TRAITS_NAME}!`) + + const template = fs.readFileSync(path.join('tests', 'traits', 'TraitsTest.cpp.mustache'), 'utf8') + const content = mustache.render(template, { + traits_name: TRAITS_NAME, + traits_dir: TRAITS_NAME.replace('_', '-'), + test_suite_name: SUITE_NAME, + }) + const outputDir = path.join('tests', 'traits') + fs.mkdirSync(outputDir, { recursive: true }) + fs.writeFileSync(path.join(outputDir, `${SUITE_NAME}.cpp`), content) diff --git a/.github/workflows/jwt.yml b/.github/workflows/jwt.yml index 929d867b6..95335d531 100644 --- a/.github/workflows/jwt.yml +++ b/.github/workflows/jwt.yml @@ -13,6 +13,8 @@ jobs: - uses: actions/checkout@v2 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest + - uses: ./.github/actions/install/danielaparker-jsoncons + - uses: ./.github/actions/install/boost-json - name: configure run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 447d67331..5383e727a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,44 +10,105 @@ jobs: clang-format: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: - files: ["include/jwt-cpp/*.h", "tests/**.cpp"] + files: + - "include/jwt-cpp/*.h" + - "include/jwt-cpp/traits/**/*.h" + - "tests/*.cpp" + - "tests/**/*.cpp" + - "example/*.cpp" + - "example/**/*.cpp" steps: - - run: sudo apt-get install clang-format + - run: | + sudo apt-get install clang-format + shopt -s globstar - uses: actions/checkout@v2 - run: clang-format -i ${{ matrix.files }} - - run: exit $(git status -s | wc -l) + - uses: ./.github/actions/process-linting-results + with: + linter_name: clang-format cmake-format: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: - files: ["CMakeLists.txt", "**/CMakeLists.txt", "cmake/code-coverage.cmake"] + files: ["**/CMakeLists.txt", "cmake/code-coverage.cmake"] steps: - uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: "3.x" - run: pip install cmakelang + - run: shopt -s globstar - uses: actions/checkout@v2 - - run: ls ${{ matrix.files }} - - run: cmake-format --check $(ls ${{ matrix.files }}) + - run: cmake-format -i ${{ matrix.files }} + - uses: ./.github/actions/process-linting-results + with: + linter_name: cmake-format clang-tidy: runs-on: ubuntu-20.04 steps: - run: sudo apt-get install clang-tidy - - uses: actions/checkout@v2 - uses: lukka/get-cmake@latest - - uses: ./.github/actions/install/gtest - + - uses: actions/checkout@v2 - name: configure run: | mkdir build cd build cmake .. -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-fix" - - name: run - run: | - cd build - make - - run: exit $(git status -s | wc -l) + working-directory: build + run: make + - uses: ./.github/actions/process-linting-results + with: + linter_name: clang-tidy + + render-defaults: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + traits: + - { name: "boost_json", library: "Boost.JSON", url: "https://github.com/boostorg/json", disable_pico: true } + - { 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: render-defaults (${{ matrix.traits.name }}) + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/render/defaults + with: + traits_name: ${{ matrix.traits.name }} + library_name: ${{ matrix.traits.library }} + library_url: ${{ matrix.traits.url }} + disable_default_traits: ${{ matrix.traits.disable_pico }} + - run: git add include/jwt-cpp/traits/* + - uses: ./.github/actions/process-linting-results + with: + linter_name: render-defaults + + render-tests: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + traits: + # - { name: "boost_json", suite: "BoostJsonTest" } # Currently needs work arounds for API limitations + - { name: "danielaparker_jsoncons", suite: "JsonconsTest" } + # - { name: "kazuho_picojson", suite: "PicoJsonTest" } # Currently the default everything tests against this! + - { name: "nlohmann_json", suite: "NlohmannTest" } + name: render-tests (${{ matrix.traits.name }}) + steps: + - uses: actions/checkout@v2 + - run: shopt -s globstar + - uses: ./.github/actions/render/tests + with: + traits_name: ${{ matrix.traits.name }} + test_suite_name: ${{ matrix.traits.suite }} + - run: clang-format -i tests/**/*.cpp + - run: git add tests/traits/* + - uses: ./.github/actions/process-linting-results + with: + linter_name: render-tests diff --git a/.github/workflows/traits.yml b/.github/workflows/traits.yml index 94febe103..b97623db4 100644 --- a/.github/workflows/traits.yml +++ b/.github/workflows/traits.yml @@ -5,56 +5,77 @@ on: branches: [master] pull_request: branches: [master] - paths: - - "CMakeLists.txt" - - "include/jwt-cpp/**" - - "tests/jsoncons/**" - - "tests/boost/**" - - ".github/actions/**" - - ".github/workflows/traits.yml" jobs: - jsoncons: + traits: + name: Traits (${{ matrix.target.name }}) runs-on: ubuntu-latest + strategy: + matrix: + target: + - { name: "danielaparker-jsoncons", tag: "0.167.1", version: "v0.167.1" } + - { name: "boost-json", tag: "1.77.0", version: "v1.77.0" } + - { name: "nlohmann-json", tag: "3.10.4", version: "v3.10.4" } + - { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" } steps: - uses: actions/checkout@v2 - uses: lukka/get-cmake@latest - - uses: ./.github/actions/install/gtest - - uses: ./.github/actions/install/jsoncons - with: - version: 0.167.1 - - name: setup run: | mkdir build cd build - cmake .. -DJWT_BUILD_EXAMPLES=OFF -DCMAKE_FIND_DEBUG_MODE=1 + cmake .. -DJWT_BUILD_EXAMPLES=OFF sudo cmake --install . - - name: test - run: | - cd tests/jsoncons - cmake . -DCMAKE_FIND_DEBUG_MODE=1 - cmake --build . - ./jsoncons-traits-narrow + # Install the JSON library + - if: matrix.target.name == 'danielaparker-jsoncons' + uses: ./.github/actions/install/danielaparker-jsoncons + with: + version: ${{matrix.target.tag}} - boost-json: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: lukka/get-cmake@latest - - uses: ./.github/actions/install/gtest + - if: matrix.target.name == 'boost-json' + uses: ./.github/actions/install/boost-json + with: + version: ${{matrix.target.tag}} - - name: setup - run: | - mkdir build - cd build - cmake .. -DJWT_BUILD_EXAMPLES=OFF - sudo cmake --install . + - if: matrix.target.name == 'nlohmann-json' + run: rm -rf include/nlohmann + - if: matrix.target.name == 'nlohmann-json' + uses: ./.github/actions/install/nlohmann-json + with: + version: ${{matrix.target.tag}} + + - if: matrix.target.name == 'kazuho-picojson' + run: rm -rf include/picojson + - if: matrix.target.name == 'kazuho-picojson' + uses: ./.github/actions/install/kazuho-picojson + with: + version: ${{matrix.target.tag}} - name: test + working-directory: example/traits run: | - cd tests/boost - cmake . - cmake --build . - ./boostjson-traits + cmake . -DCMAKE_FIND_DEBUG_MODE=1 + cmake --build . --target ${{ matrix.target.name }} + ./${{ matrix.target.name }} + + - name: badge success + if: github.event_name == 'push' && success() + uses: ./.github/actions/badge + with: + category: traits + label: ${{ matrix.target.name }} + message: ${{ matrix.target.version }} + color: lightblue # turquoise + - if: github.event_name == 'push' && success() + uses: ./.github/actions/badge/publish + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: badge failure + if: github.event_name == 'push' && !success() + uses: ./.github/actions/badge/failure + with: + category: traits + label: ${{ matrix.target.name }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 4a2feba6b..929a39fee 100644 --- a/.gitignore +++ b/.gitignore @@ -311,3 +311,4 @@ test docs build/* +package-lock.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 715bb0cbd..1e86af5b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,8 @@ 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) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") if(NOT JWT_SSL_LIBRARY IN_LIST JWT_SSL_LIBRARY_OPTIONS) @@ -62,6 +64,11 @@ endif() set(JWT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include) set(JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/jwt.h) +foreach(traits ${JWT_JSON_TRAITS_OPTIONS}) + list(APPEND JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/traits/${traits}/defaults.h + ${JWT_INCLUDE_PATH}/jwt-cpp/traits/${traits}/traits.h) +endforeach() + if(NOT JWT_DISABLE_BASE64) list(APPEND JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/base.h) endif() @@ -113,7 +120,7 @@ write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config-vers install(TARGETS jwt-cpp EXPORT jwt-cpp-targets PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT jwt-cpp-targets NAMESPACE jwt-cpp:: FILE jwt-cpp-targets.cmake DESTINATION ${JWT_CMAKE_FILES_INSTALL_DIR}) -install(FILES ${JWT_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jwt-cpp) +install(DIRECTORY ${JWT_INCLUDE_PATH}/jwt-cpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if(NOT JWT_EXTERNAL_PICOJSON AND NOT JWT_DISABLE_PICOJSON) install(FILES ${JWT_INCLUDE_PATH}/picojson/picojson.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/picojson) endif() diff --git a/README.md b/README.md index 0a8784f6f..d1d32e21d 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,19 @@ jwt::basic_claim claim(json::object({{"json", t This allows for complete freedom when picking which libraries you want to use. For more information, [see below](#providing-your-own-json-traits-your-traits). -In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. +For your convience there are serval traits implementation which provide some popular JSON libraries. They are: + +[![picojson][picojson]](https://github.com/kazuho/picojson) +[![nlohmann][nlohmann]](https://github.com/nlohmann/json) +[![jsoncons][jsoncons]](https://github.com/danielaparker/jsoncons) +[![boostjson][boostjson]](https://github.com/boostorg/json) + +[picojson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/kazuho-picojson/shields.json +[nlohmann]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/nlohmann-json/shields.json +[jsoncons]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/danielaparker-jsoncons/shields.json +[boostjson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/boost-json/shields.json + +In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. It's possible to directly include the traits defaults for the other JSON libraries. See the [traits examples](example/traits) for details. As for the base64 requirements of JWTs, this libary provides `base.h` with all the required implentation; However base64 implementations are very common, with varying degrees of performance. When providing your own base64 implementation, you can define `JWT_DISABLE_BASE64` to remove the jwt-cpp implementation. diff --git a/cmake/private-find-boost-json.cmake b/cmake/private-find-boost-json.cmake new file mode 100644 index 000000000..70c7b6ccd --- /dev/null +++ b/cmake/private-find-boost-json.cmake @@ -0,0 +1,16 @@ +if(TARGET boost_json) + return() +endif() + +unset(BOOSTJSON_INCLUDE_DIR CACHE) +find_path(BOOSTJSON_INCLUDE_DIR "boost/json.hpp" "boost/json/src.hpp") +if(EXISTS "${BOOSTJSON_INCLUDE_DIR}/boost/json.hpp") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp.in" "#include ") + configure_file("${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp.in" private-boost-json.cpp COPYONLY) + add_library(boost_json "${BOOSTJSON_INCLUDE_DIR}/boost/json.hpp" + "${BOOSTJSON_INCLUDE_DIR}/boost/json/src.hpp" + "${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp") + target_include_directories(boost_json PUBLIC ${BOOSTJSON_INCLUDE_DIR}) + target_compile_definitions(boost_json PUBLIC BOOST_JSON_STANDALONE) + target_compile_features(boost_json PUBLIC cxx_std_17) +endif() diff --git a/cmake/private-find-kazuho-picojson.cmake b/cmake/private-find-kazuho-picojson.cmake new file mode 100644 index 000000000..ddb67030d --- /dev/null +++ b/cmake/private-find-kazuho-picojson.cmake @@ -0,0 +1,10 @@ +if(TARGET kazuho_picojson) + return() +endif() + +unset(PICOJSON_INCLUDE_DIR CACHE) +find_path(PICOJSON_INCLUDE_DIR "picojson/picojson.h") +if(EXISTS "${PICOJSON_INCLUDE_DIR}/picojson/picojson.h") + add_library(kazuho_picojson INTERFACE "${PICOJSON_INCLUDE_DIR}/picojson/picojson.h") + target_include_directories(kazuho_picojson INTERFACE ${PICOJSON_INCLUDE_DIR}) +endif() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c7de16ecd..afc150da9 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,6 +5,8 @@ if(NOT TARGET jwt-cpp) find_package(jwt-cpp CONFIG REQUIRED) endif() +add_subdirectory(traits) + if(JWT_DISABLE_PICOJSON) message(FATAL_ERROR "examples require picojson to be available!") endif() diff --git a/example/es256k.cpp b/example/es256k.cpp index c0231ee95..371c93a09 100644 --- a/example/es256k.cpp +++ b/example/es256k.cpp @@ -25,8 +25,9 @@ K9EDZi0mZ7VUeeNKq476CU5X940yusahgneePQrDMF2nWFEtBCOiXQ== std::cout << "token:\n" << token << std::endl; - auto verify = - jwt::verify().allow_algorithm(jwt::algorithm::es256k(es256k_pub_key, es256k_priv_key, "", "")).with_issuer("auth0"); + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::es256k(es256k_pub_key, es256k_priv_key, "", "")) + .with_issuer("auth0"); auto decoded = jwt::decode(token); diff --git a/example/jwks-verify.cpp b/example/jwks-verify.cpp index 5fd1d0b12..2464a5fe6 100644 --- a/example/jwks-verify.cpp +++ b/example/jwks-verify.cpp @@ -2,7 +2,8 @@ #include int main() { - std::string raw_jwks = R"({"keys": [{ + std::string raw_jwks = + R"({"keys": [{ "kid":"internal-gateway-jwt.api.sc.net", "alg": "RS256", "kty": "RSA", diff --git a/example/private-claims.cpp b/example/private-claims.cpp index 0a3c6ccdd..c54099e5e 100644 --- a/example/private-claims.cpp +++ b/example/private-claims.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -7,134 +6,41 @@ using sec = std::chrono::seconds; using min = std::chrono::minutes; -std::string make_pico_token() { +int main() { jwt::claim from_raw_json; std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; iss >> from_raw_json; jwt::claim::set_t list{"once", "twice"}; - std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; const auto time = jwt::date::clock::now(); - - return jwt::create() - .set_type("JWT") - .set_issuer("auth.mydomain.io") - .set_audience("mydomain.io") - .set_issued_at(time) - .set_not_before(time + sec{15}) - .set_expires_at(time + sec{15} + min{2}) - .set_payload_claim("boolean", picojson::value(true)) - .set_payload_claim("integer", picojson::value(int64_t{12345})) - .set_payload_claim("precision", picojson::value(12.345)) - .set_payload_claim("strings", jwt::claim(list)) - .set_payload_claim("array", jwt::claim(big_numbers.begin(), big_numbers.end())) - .set_payload_claim("object", from_raw_json) - .sign(jwt::algorithm::none{}); -} - -std::string make_nlohmann_token() { - struct nlohmann_traits { - using json = nlohmann::json; - using value_type = json; - using object_type = json::object_t; - using array_type = json::array_t; - using string_type = std::string; - using number_type = json::number_float_t; - using integer_type = json::number_integer_t; - using boolean_type = json::boolean_t; - - static jwt::json::type get_type(const json& val) { - using jwt::json::type; - - if (val.type() == json::value_t::boolean) return type::boolean; - if (val.type() == json::value_t::number_integer) return type::integer; - // nlohmann internally tracks two types of integers - if (val.type() == json::value_t::number_unsigned) return type::integer; - if (val.type() == json::value_t::number_float) return type::number; - if (val.type() == json::value_t::string) return type::string; - if (val.type() == json::value_t::array) return type::array; - if (val.type() == json::value_t::object) return type::object; - throw std::logic_error("invalid type"); - } - - static json::object_t as_object(const json& val) { - if (val.type() != json::value_t::object) throw std::bad_cast(); - return val.get(); - } - - static std::string as_string(const json& val) { - if (val.type() != json::value_t::string) throw std::bad_cast(); - return val.get(); - } - - static json::array_t as_array(const json& val) { - if (val.type() != json::value_t::array) throw std::bad_cast(); - return val.get(); - } - - static int64_t as_int(const json& val) { - switch (val.type()) { - case json::value_t::number_integer: - case json::value_t::number_unsigned: return val.get(); - default: throw std::bad_cast(); - } - } - - static bool as_bool(const json& val) { - if (val.type() != json::value_t::boolean) throw std::bad_cast(); - return val.get(); - } - - static double as_number(const json& val) { - if (val.type() != json::value_t::number_float) throw std::bad_cast(); - return val.get(); - } - - static bool parse(json& val, std::string str) { - val = json::parse(str.begin(), str.end()); - return true; - } - - static std::string serialize(const json& val) { return val.dump(); } - }; - - using claim = jwt::basic_claim; - - 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 big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; - - const auto time = jwt::date::clock::now(); - - return jwt::create() - .set_type("JWT") - .set_issuer("auth.mydomain.io") - .set_audience("mydomain.io") - .set_issued_at(time) - .set_not_before(time + sec{15}) - .set_expires_at(time + sec{15} + min{2}) - .set_payload_claim("boolean", true) - .set_payload_claim("integer", 12345) - .set_payload_claim("precision", 12.345) - .set_payload_claim("strings", list) - .set_payload_claim("array", {big_numbers.begin(), big_numbers.end()}) - .set_payload_claim("object", from_raw_json) - .sign(jwt::algorithm::none{}); -} - -int main() { - const auto token = make_pico_token(); - auto decoded = jwt::decode(token); - - for (auto& e : decoded.get_payload_claims()) - std::cout << e.first << " = " << e.second << std::endl; + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time + sec{15}) + .set_expires_at(time + sec{15} + min{2}) + .set_payload_claim("boolean", picojson::value(true)) + .set_payload_claim("integer", picojson::value(int64_t{12345})) + .set_payload_claim("precision", picojson::value(12.345)) + .set_payload_claim("strings", jwt::claim(list)) + .set_payload_claim("array", jwt::claim(big_numbers.begin(), big_numbers.end())) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + + const auto decoded = jwt::decode(token); const auto api_array = decoded.get_payload_claims()["object"].to_json().get("api").get("array"); std::cout << "api array = " << api_array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; } diff --git a/example/rsa-verify.cpp b/example/rsa-verify.cpp index e1bc086e8..3b4f1f2d9 100644 --- a/example/rsa-verify.cpp +++ b/example/rsa-verify.cpp @@ -21,8 +21,7 @@ YwIDAQAB "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-" "uc2z5c05CCXqVSpfCjWbh9gQ"; - auto verify = - jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")).with_issuer("auth0"); + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")).with_issuer("auth0"); auto decoded = jwt::decode(token); diff --git a/example/traits/CMakeLists.txt b/example/traits/CMakeLists.txt new file mode 100644 index 000000000..b03c0718f --- /dev/null +++ b/example/traits/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.8) +project(jwt-cpp-traits) + +if(NOT TARGET jwt-cpp) + find_package(jwt-cpp CONFIG REQUIRED) +endif() + +find_package(jsoncons CONFIG) +if(TARGET jsoncons) + add_executable(danielaparker-jsoncons danielaparker-jsoncons.cpp) + target_link_libraries(danielaparker-jsoncons jwt-cpp::jwt-cpp jsoncons) +endif() + +include("../../cmake/private-find-boost-json.cmake") +if(TARGET boost_json) + add_executable(boost-json boost-json.cpp) + target_link_libraries(boost-json jwt-cpp::jwt-cpp boost_json) +endif() + +find_package(nlohmann_json CONFIG) +if(TARGET nlohmann_json::nlohmann_json) +add_executable(nlohmann-json nlohmann-json.cpp) +target_link_libraries(nlohmann-json nlohmann_json::nlohmann_json jwt-cpp::jwt-cpp) +endif() + +include("../../cmake/private-find-kazuho-picojson.cmake") +if(TARGET kazuho_picojson) + add_executable(kazuho-picojson kazuho-picojson.cpp) + target_link_libraries(kazuho-picojson jwt-cpp::jwt-cpp kazuho_picojson) +endif() diff --git a/example/traits/README.md b/example/traits/README.md new file mode 100644 index 000000000..42ecceb0f --- /dev/null +++ b/example/traits/README.md @@ -0,0 +1,6 @@ +# Traits Examples + +These example require upstream CMake installation to work. There are expections: + +- For Boost.JSON headers must be located by custom CMake. +- For PicoJSON headers must be located by custom CMake. diff --git a/example/traits/boost-json.cpp b/example/traits/boost-json.cpp new file mode 100644 index 000000000..65921a622 --- /dev/null +++ b/example/traits/boost-json.cpp @@ -0,0 +1,49 @@ +#include "jwt-cpp/traits/boost-json/traits.h" + +// #include // You may require this if you are not building it elsewhere +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::boost_json; + using claim = jwt::basic_claim; + + traits::value_type raw_value; + traits::parse(raw_value, R"##({"api":{"array":[1,2,3],"null":null}})##"); + claim from_raw_json(raw_value); + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .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", claim{big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto array = + traits::as_array(decoded.get_payload_claim("object").to_json().as_object()["api"].as_object()["array"]); + std::cout << "payload /object/api/array = " << array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/example/traits/danielaparker-jsoncons.cpp b/example/traits/danielaparker-jsoncons.cpp new file mode 100644 index 000000000..5eac313ce --- /dev/null +++ b/example/traits/danielaparker-jsoncons.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/danielaparker-jsoncons/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::danielaparker_jsoncons; + using claim = jwt::basic_claim; + + 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 big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .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", 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(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() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/example/traits/kazuho-picojson.cpp b/example/traits/kazuho-picojson.cpp new file mode 100644 index 000000000..4b29aee8e --- /dev/null +++ b/example/traits/kazuho-picojson.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/kazuho-picojson/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::kazuho_picojson; + using claim = jwt::basic_claim; + + 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 big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .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", picojson::value(true)) + .set_payload_claim("integer", picojson::value(int64_t{12345})) + .set_payload_claim("precision", picojson::value(12.345)) + .set_payload_claim("strings", claim(list)) + .set_payload_claim("array", claim(big_numbers.begin(), big_numbers.end())) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto api_array = decoded.get_payload_claims()["object"].to_json().get("api").get("array"); + std::cout << "api array = " << api_array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/example/traits/nlohmann-json.cpp b/example/traits/nlohmann-json.cpp new file mode 100644 index 000000000..00fa2acd0 --- /dev/null +++ b/example/traits/nlohmann-json.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/nlohmann-json/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::nlohmann_json; + using claim = jwt::basic_claim; + + 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 big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .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", 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(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() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index b260e79c4..9084506f0 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -2100,8 +2100,6 @@ namespace jwt { /** * \brief a class to store a generic JSON value as claim * - * The default template parameters use [picojson](https://github.com/kazuho/picojson) - * * \tparam json_traits : JSON implementation traits * * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) @@ -2624,6 +2622,26 @@ namespace jwt { std::unordered_map get_header_claims() const { return this->header_claims.get_claims(); } + /** + * Get a payload claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return this->payload_claims.get_claim(name); + } + /** + * Get a header claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return this->header_claims.get_claim(name); + } }; /** @@ -3544,6 +3562,16 @@ namespace jwt { date now() const { return date::clock::now(); } }; + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(default_clock c = {}) { + return verifier(c); + } + /** * Return a builder instance to create a new token */ @@ -3586,128 +3614,6 @@ namespace jwt { jwks parse_jwks(const typename json_traits::string_type& token) { return jwks(token); } - -#ifndef JWT_DISABLE_PICOJSON - struct picojson_traits { - using value_type = picojson::value; - using object_type = picojson::object; - using array_type = picojson::array; - using string_type = std::string; - using number_type = double; - using integer_type = int64_t; - using boolean_type = bool; - - static json::type get_type(const picojson::value& val) { - using json::type; - if (val.is()) return type::boolean; - if (val.is()) return type::integer; - if (val.is()) return type::number; - if (val.is()) return type::string; - if (val.is()) return type::array; - if (val.is()) return type::object; - - throw std::logic_error("invalid type"); - } - - static picojson::object as_object(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static std::string as_string(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static picojson::array as_array(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static int64_t as_int(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static bool as_bool(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static double as_number(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static bool parse(picojson::value& val, const std::string& str) { return picojson::parse(val, str).empty(); } - - static std::string serialize(const picojson::value& val) { return val.serialize(); } - }; - - /** - * Default JSON claim - * - * This type is the default specialization of the \ref basic_claim class which - * uses the standard template types. - */ - using claim = basic_claim; - - /** - * Create a verifier using the default clock - * \return verifier instance - */ - inline verifier verify() { - return verify(default_clock{}); - } - /** - * Return a picojson builder instance to create a new token - */ - inline builder create() { return builder(); } -#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 decode(const std::string& token) { return decoded_jwt(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 - decoded_jwt decode(const std::string& token, Decode decode) { - return decoded_jwt(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 parse_jwk(const picojson_traits::string_type& token) { - return jwk(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 parse_jwks(const picojson_traits::string_type& token) { - return jwks(token); - } -#endif } // namespace jwt template @@ -3720,4 +3626,8 @@ std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& return os << c.to_json(); } +#ifndef JWT_DISABLE_PICOJSON +#include "traits/kazuho-picojson/defaults.h" +#endif + #endif diff --git a/include/jwt-cpp/traits/boost-json/defaults.h b/include/jwt-cpp/traits/boost-json/defaults.h new file mode 100644 index 000000000..030f067b0 --- /dev/null +++ b/include/jwt-cpp/traits/boost-json/defaults.h @@ -0,0 +1,82 @@ +#ifndef JWT_CPP_BOOST_JSON_DEFAULTS_H +#define JWT_CPP_BOOST_JSON_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [Boost.JSON](https://github.com/boostorg/json) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 decode(const std::string& token) { + return decoded_jwt(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 + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(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 parse_jwk(const traits::boost_json::string_type& token) { + return jwk(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 parse_jwks(const traits::boost_json::string_type& token) { + return jwks(token); + } +} // namespace jwt + +#endif // JWT_CPP_BOOST_JSON_DEFAULTS_H diff --git a/include/jwt-cpp/traits/boost-json/traits.h b/include/jwt-cpp/traits/boost-json/traits.h new file mode 100644 index 000000000..9e1160d39 --- /dev/null +++ b/include/jwt-cpp/traits/boost-json/traits.h @@ -0,0 +1,80 @@ +#ifndef JWT_CPP_BOOSTJSON_TRAITS_H +#define JWT_CPP_BOOSTJSON_TRAITS_H + +#define JWT_DISABLE_PICOJSON +#include "jwt-cpp/jwt.h" + +#include +// if not boost JSON standalone then error... + +namespace jwt { + namespace traits { + namespace json = boost::json; + struct boost_json { + using value_type = json::value; + using object_type = json::object; + using array_type = json::array; + using string_type = std::string; + using number_type = double; + using integer_type = std::int64_t; + using boolean_type = bool; + + static jwt::json::type get_type(const value_type& val) { + using jwt::json::type; + + if (val.kind() == json::kind::bool_) return type::boolean; + if (val.kind() == json::kind::int64) return type::integer; + if (val.kind() == json::kind::uint64) // boost internally tracks two types of integers + return type::integer; + if (val.kind() == json::kind::double_) return type::number; + if (val.kind() == json::kind::string) return type::string; + if (val.kind() == json::kind::array) return type::array; + if (val.kind() == json::kind::object) return type::object; + + throw std::logic_error("invalid type"); + } + + static object_type as_object(const value_type& val) { + if (val.kind() != json::kind::object) throw std::bad_cast(); + return val.get_object(); + } + + static array_type as_array(const value_type& val) { + if (val.kind() != json::kind::array) throw std::bad_cast(); + return val.get_array(); + } + + static string_type as_string(const value_type& val) { + if (val.kind() != json::kind::string) throw std::bad_cast(); + return string_type{val.get_string()}; + } + + static integer_type as_int(const value_type& val) { + switch (val.kind()) { + case json::kind::int64: return val.get_int64(); + case json::kind::uint64: return static_cast(val.get_uint64()); + default: throw std::bad_cast(); + } + } + + static boolean_type as_bool(const value_type& val) { + if (val.kind() != json::kind::bool_) throw std::bad_cast(); + return val.get_bool(); + } + + static number_type as_number(const value_type& val) { + if (val.kind() != json::kind::double_) throw std::bad_cast(); + return val.get_double(); + } + + static bool parse(value_type& val, string_type str) { + val = json::parse(str); + return true; + } + + static std::string serialize(const value_type& val) { return json::serialize(val); } + }; + } // namespace traits +} // namespace jwt + +#endif // JWT_CPP_BOOSTJSON_TRAITS_H diff --git a/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h b/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h new file mode 100644 index 000000000..2004446a6 --- /dev/null +++ b/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h @@ -0,0 +1,82 @@ +#ifndef JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H +#define JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [jsoncons](https://github.com/danielaparker/jsoncons) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 decode(const std::string& token) { + return decoded_jwt(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 + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(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 parse_jwk(const traits::danielaparker_jsoncons::string_type& token) { + return jwk(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 parse_jwks(const traits::danielaparker_jsoncons::string_type& token) { + return jwks(token); + } +} // namespace jwt + +#endif // JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H diff --git a/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h new file mode 100644 index 000000000..0aada2e41 --- /dev/null +++ b/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h @@ -0,0 +1,123 @@ +#define JWT_DISABLE_PICOJSON +#define JSONCONS_NO_DEPRECATED + +#include "jwt-cpp/jwt.h" + +#include "jsoncons/json.hpp" + +#include + +namespace jwt { + namespace traits { + struct danielaparker_jsoncons { + // Needs at least https://github.com/danielaparker/jsoncons/commit/28c56b90ec7337f98a5b8942574590111a5e5831 + static_assert(jsoncons::version().minor >= 167, "A higher version of jsoncons is required!"); + + using json = jsoncons::json; + using value_type = json; + struct object_type : json::object { + // Add missing C++11 member types + // https://github.com/danielaparker/jsoncons/commit/1b1ceeb572f9a2db6d37cff47ac78a4f14e072e2#commitcomment-45391411 + using value_type = key_value_type; // Enable optional jwt-cpp methods + using mapped_type = key_value_type::value_type; + using size_type = size_t; // for implementing count + + object_type() = default; + object_type(const object_type&) = default; + explicit object_type(const json::object& o) : json::object(o) {} + object_type(object_type&&) = default; + explicit object_type(json::object&& o) : json::object(o) {} + ~object_type() = default; + object_type& operator=(const object_type& o) = default; + object_type& operator=(object_type&& o) noexcept = default; + + // Add missing C++11 subscription operator + mapped_type& operator[](const key_type& key) { + // https://github.com/microsoft/STL/blob/2914b4301c59dc7ffc09d16ac6f7979fde2b7f2c/stl/inc/map#L325 + return try_emplace(key).first->value(); + } + + // Add missing C++11 element access + const mapped_type& at(const key_type& key) const { + auto target = find(key); + if (target != end()) return target->value(); + + throw std::out_of_range("invalid key"); + } + + // Add missing C++11 lookup method + size_type count(const key_type& key) const { + struct compare { + bool operator()(const value_type& val, const key_type& key) const { return val.key() < key; } + bool operator()(const key_type& key, const value_type& val) const { return key < val.key(); } + }; + + // https://en.cppreference.com/w/cpp/algorithm/binary_search#Complexity + if (std::binary_search(this->begin(), this->end(), key, compare{})) return 1; + return 0; + } + }; + using array_type = json::array; + using string_type = std::string; // current limitation of traits implementation + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static jwt::json::type get_type(const json& val) { + using jwt::json::type; + + if (val.type() == jsoncons::json_type::bool_value) return type::boolean; + if (val.type() == jsoncons::json_type::int64_value) return type::integer; + if (val.type() == jsoncons::json_type::uint64_value) return type::integer; + if (val.type() == jsoncons::json_type::half_value) return type::number; + if (val.type() == jsoncons::json_type::double_value) return type::number; + if (val.type() == jsoncons::json_type::string_value) return type::string; + if (val.type() == jsoncons::json_type::array_value) return type::array; + if (val.type() == jsoncons::json_type::object_value) return type::object; + + throw std::logic_error("invalid type"); + } + + static object_type as_object(const json& val) { + if (val.type() != jsoncons::json_type::object_value) throw std::bad_cast(); + return object_type(val.object_value()); + } + + static array_type as_array(const json& val) { + if (val.type() != jsoncons::json_type::array_value) throw std::bad_cast(); + return val.array_value(); + } + + static string_type as_string(const json& val) { + if (val.type() != jsoncons::json_type::string_value) throw std::bad_cast(); + return val.as_string(); + } + + static number_type as_number(const json& val) { + if (get_type(val) != jwt::json::type::number) throw std::bad_cast(); + return val.as_double(); + } + + static integer_type as_int(const json& val) { + if (get_type(val) != jwt::json::type::integer) throw std::bad_cast(); + return val.as(); + } + + static boolean_type as_bool(const json& val) { + if (val.type() != jsoncons::json_type::bool_value) throw std::bad_cast(); + return val.as_bool(); + } + + static bool parse(json& val, const std::string& str) { + val = json::parse(str); + return true; + } + + static std::string serialize(const json& val) { + std::ostringstream os; + os << jsoncons::print(val); + return os.str(); + } + }; + } // namespace traits +} // namespace jwt diff --git a/include/jwt-cpp/traits/defaults.h.mustache b/include/jwt-cpp/traits/defaults.h.mustache new file mode 100644 index 000000000..031cb4526 --- /dev/null +++ b/include/jwt-cpp/traits/defaults.h.mustache @@ -0,0 +1,84 @@ +#ifndef JWT_CPP_{{traits_name_upper}}_DEFAULTS_H +#define JWT_CPP_{{traits_name_upper}}_DEFAULTS_H +{{#disable_default_traits}} + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +{{/disable_default_traits}} + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [{{library_name}}]({{{library_url}}}) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 decode(const std::string& token) { + return decoded_jwt(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 + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(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 parse_jwk(const traits::{{traits_name}}::string_type& token) { + return jwk(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 parse_jwks(const traits::{{traits_name}}::string_type& token) { + return jwks(token); + } +} // namespace jwt + +#endif // JWT_CPP_{{traits_name_upper}}_DEFAULTS_H diff --git a/include/jwt-cpp/traits/kazuho-picojson/defaults.h b/include/jwt-cpp/traits/kazuho-picojson/defaults.h new file mode 100644 index 000000000..8b2f6e088 --- /dev/null +++ b/include/jwt-cpp/traits/kazuho-picojson/defaults.h @@ -0,0 +1,78 @@ +#ifndef JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H +#define JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [picojson](https://github.com/kazuho/picojson) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 decode(const std::string& token) { + return decoded_jwt(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 + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(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 parse_jwk(const traits::kazuho_picojson::string_type& token) { + return jwk(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 parse_jwks(const traits::kazuho_picojson::string_type& token) { + return jwks(token); + } +} // namespace jwt + +#endif // JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H diff --git a/include/jwt-cpp/traits/kazuho-picojson/traits.h b/include/jwt-cpp/traits/kazuho-picojson/traits.h new file mode 100644 index 000000000..179cfde6a --- /dev/null +++ b/include/jwt-cpp/traits/kazuho-picojson/traits.h @@ -0,0 +1,76 @@ +#ifndef JWT_CPP_PICOJSON_TRAITS_H +#define JWT_CPP_PICOJSON_TRAITS_H + +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +#include "jwt-cpp/jwt.h" + +namespace jwt { + namespace traits { + struct kazuho_picojson { + using value_type = picojson::value; + using object_type = picojson::object; + using array_type = picojson::array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static json::type get_type(const picojson::value& val) { + using json::type; + if (val.is()) return type::boolean; + if (val.is()) return type::integer; + if (val.is()) return type::number; + if (val.is()) return type::string; + if (val.is()) return type::array; + if (val.is()) return type::object; + + throw std::logic_error("invalid type"); + } + + static picojson::object as_object(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static picojson::array as_array(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_int(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool as_bool(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool parse(picojson::value& val, const std::string& str) { + return picojson::parse(val, str).empty(); + } + + static std::string serialize(const picojson::value& val) { return val.serialize(); } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/include/jwt-cpp/traits/nlohmann-json/defaults.h b/include/jwt-cpp/traits/nlohmann-json/defaults.h new file mode 100644 index 000000000..10b9a5af2 --- /dev/null +++ b/include/jwt-cpp/traits/nlohmann-json/defaults.h @@ -0,0 +1,82 @@ +#ifndef JWT_CPP_NLOHMANN_JSON_DEFAULTS_H +#define JWT_CPP_NLOHMANN_JSON_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [JSON for Modern C++](https://github.com/nlohmann/json) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 decode(const std::string& token) { + return decoded_jwt(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 + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(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 parse_jwk(const traits::nlohmann_json::string_type& token) { + return jwk(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 parse_jwks(const traits::nlohmann_json::string_type& token) { + return jwks(token); + } +} // namespace jwt + +#endif // JWT_CPP_NLOHMANN_JSON_DEFAULTS_H diff --git a/include/jwt-cpp/traits/nlohmann-json/traits.h b/include/jwt-cpp/traits/nlohmann-json/traits.h new file mode 100644 index 000000000..23c2d7d10 --- /dev/null +++ b/include/jwt-cpp/traits/nlohmann-json/traits.h @@ -0,0 +1,77 @@ +#ifndef JWT_CPP_NLOHMANN_JSON_TRAITS_H +#define JWT_CPP_NLOHMANN_JSON_TRAITS_H + +#include "jwt-cpp/jwt.h" +#include "nlohmann/json.hpp" + +namespace jwt { + namespace traits { + struct nlohmann_json { + using json = nlohmann::json; + using value_type = json; + using object_type = json::object_t; + using array_type = json::array_t; + using string_type = std::string; // current limitation of traits implementation + using number_type = json::number_float_t; + using integer_type = json::number_integer_t; + using boolean_type = json::boolean_t; + + static jwt::json::type get_type(const json& val) { + using jwt::json::type; + + if (val.type() == json::value_t::boolean) return type::boolean; + // nlohmann internally tracks two types of integers + if (val.type() == json::value_t::number_integer) return type::integer; + if (val.type() == json::value_t::number_unsigned) return type::integer; + if (val.type() == json::value_t::number_float) return type::number; + if (val.type() == json::value_t::string) return type::string; + if (val.type() == json::value_t::array) return type::array; + if (val.type() == json::value_t::object) return type::object; + + throw std::logic_error("invalid type"); + } + + static json::object_t as_object(const json& val) { + if (val.type() != json::value_t::object) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const json& val) { + if (val.type() != json::value_t::string) throw std::bad_cast(); + return val.get(); + } + + static json::array_t as_array(const json& val) { + if (val.type() != json::value_t::array) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_int(const json& val) { + switch (val.type()) { + case json::value_t::number_integer: + case json::value_t::number_unsigned: return val.get(); + default: throw std::bad_cast(); + } + } + + static bool as_bool(const json& val) { + if (val.type() != json::value_t::boolean) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const json& val) { + if (val.type() != json::value_t::number_float) throw std::bad_cast(); + return val.get(); + } + + static bool parse(json& val, std::string str) { + val = json::parse(str.begin(), str.end()); + return true; + } + + static std::string serialize(const json& val) { return val.dump(); } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 467bed48b..5599aa2b8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,8 +17,18 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/BaseTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ClaimTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Keys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HelperTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TokenFormatTest.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/TokenTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/NlohmannTest.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/JwksTest.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/TokenTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/JwksTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/traits/NlohmannTest.cpp) + +find_package(jsoncons CONFIG) +if(TARGET jsoncons) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/JsonconsTest.cpp) +endif() + +include("private-find-boost-json") +if(TARGET boost_json) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/BoostJsonTest.cpp) +endif() add_executable(jwt-cpp-test ${TEST_SOURCES}) @@ -32,9 +42,15 @@ target_compile_options( if(HUNTER_ENABLED) target_link_libraries(jwt-cpp-test PRIVATE GTest::gtest GTest::gtest_main) # Define a compile define to bypass openssl error tests - target_compile_options(jwt-cpp-test PRIVATE -DHUNTER_ENABLED=1) + target_compile_definitions(jwt-cpp-test PRIVATE HUNTER_ENABLED=1) else() target_link_libraries(jwt-cpp-test PRIVATE GTest::GTest GTest::Main) + if(TARGET jsoncons) + target_link_libraries(jwt-cpp-test PRIVATE jsoncons) + endif() + if(TARGET boost_json) + target_link_libraries(jwt-cpp-test PRIVATE boost_json) + endif() endif() target_link_libraries(jwt-cpp-test PRIVATE jwt-cpp $<$>:${CMAKE_DL_LIBS}>) @@ -43,6 +59,7 @@ gtest_add_tests(TARGET jwt-cpp-test) if(JWT_ENABLE_COVERAGE) include("code-coverage") setup_coverage(jwt-cpp-test) - set(COVERAGE_EXCLUDES "/usr/**" "/home/*/.conan/**" "*test*" "*build*" "*json*") + set(COVERAGE_EXCLUDES "/usr/**" "/home/*/.conan/**" "*test*" "*build*" "**/nlohmann/json.hpp" + "**/picojson/picojson.h" "*boost*" "*jsoncons*") setup_target_for_coverage_lcov(NAME coverage EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-test) endif() diff --git a/tests/ClaimTest.cpp b/tests/ClaimTest.cpp index a62633caa..9ad2cc5a1 100644 --- a/tests/ClaimTest.cpp +++ b/tests/ClaimTest.cpp @@ -99,30 +99,30 @@ TEST(ClaimTest, AsDate) { } TEST(ClaimTest, PicoJSONTraitsAccessorsThrow) { - jwt::picojson_traits::value_type val; - ASSERT_THROW(jwt::picojson_traits::as_array(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::as_bool(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::as_int(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::as_number(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::as_object(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::as_string(val), std::bad_cast); - ASSERT_THROW(jwt::picojson_traits::get_type(val), std::logic_error); + jwt::traits::kazuho_picojson::value_type val; + ASSERT_THROW(jwt::traits::kazuho_picojson::as_array(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_bool(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_int(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_number(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_object(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_string(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::get_type(val), std::logic_error); } TEST(ClaimTest, PicoJSONTraitsAsBool) { - jwt::picojson_traits::value_type val(true); - ASSERT_EQ(jwt::picojson_traits::as_bool(val), true); - ASSERT_EQ(jwt::picojson_traits::get_type(val), jwt::json::type::boolean); + jwt::traits::kazuho_picojson::value_type val(true); + ASSERT_EQ(jwt::traits::kazuho_picojson::as_bool(val), true); + ASSERT_EQ(jwt::traits::kazuho_picojson::get_type(val), jwt::json::type::boolean); } TEST(ClaimTest, PicoJSONTraitsAsDouble) { - jwt::picojson_traits::value_type val(10.0); - ASSERT_EQ(jwt::picojson_traits::as_number(val), (int)10); - ASSERT_EQ(jwt::picojson_traits::get_type(val), jwt::json::type::number); + jwt::traits::kazuho_picojson::value_type val(10.0); + ASSERT_EQ(jwt::traits::kazuho_picojson::as_number(val), (int)10); + ASSERT_EQ(jwt::traits::kazuho_picojson::get_type(val), jwt::json::type::number); } TEST(ClaimTest, MapOfClaim) { - using map = jwt::details::map_of_claims; + using map = jwt::details::map_of_claims; ASSERT_THROW(map::parse_claims(R"##(__ not json __)##"), jwt::error::invalid_json_exception); const map claims{ map::parse_claims(R"##({ "array": [1], "string" : "hello world", "number": 9.9, "bool": true})##")}; diff --git a/tests/NlohmannTest.cpp b/tests/NlohmannTest.cpp deleted file mode 100644 index 1a65f0029..000000000 --- a/tests/NlohmannTest.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#define JWT_DISABLE_PICOJSON // Make sure JWT compiles with this flag - -#include "jwt-cpp/jwt.h" -#include "nlohmann/json.hpp" -#include - -struct nlohmann_traits { - using json = nlohmann::json; - using value_type = json; - using object_type = json::object_t; - using array_type = json::array_t; - using string_type = std::string; // current limitation of traits implementation - using number_type = json::number_float_t; - using integer_type = json::number_integer_t; - using boolean_type = json::boolean_t; - - static jwt::json::type get_type(const json& val) { - using jwt::json::type; - - if (val.type() == json::value_t::boolean) return type::boolean; - if (val.type() == json::value_t::number_integer) return type::integer; - if (val.type() == json::value_t::number_unsigned) // nlohmann internally tracks two types of integers - return type::integer; - if (val.type() == json::value_t::number_float) return type::number; - if (val.type() == json::value_t::string) return type::string; - if (val.type() == json::value_t::array) return type::array; - if (val.type() == json::value_t::object) return type::object; - - throw std::logic_error("invalid type"); - } - - static json::object_t as_object(const json& val) { - if (val.type() != json::value_t::object) throw std::bad_cast(); - return val.get(); - } - - static std::string as_string(const json& val) { - if (val.type() != json::value_t::string) throw std::bad_cast(); - return val.get(); - } - - static json::array_t as_array(const json& val) { - if (val.type() != json::value_t::array) throw std::bad_cast(); - return val.get(); - } - - static int64_t as_int(const json& val) { - switch (val.type()) { - case json::value_t::number_integer: - case json::value_t::number_unsigned: return val.get(); - default: throw std::bad_cast(); - } - } - - static bool as_bool(const json& val) { - if (val.type() != json::value_t::boolean) throw std::bad_cast(); - return val.get(); - } - - static double as_number(const json& val) { - if (val.type() != json::value_t::number_float) throw std::bad_cast(); - return val.get(); - } - - static bool parse(json& val, std::string str) { - val = json::parse(str.begin(), str.end()); - return true; - } - - static std::string serialize(const json& val) { return val.dump(); } -}; - -TEST(NlohmannTest, BasicClaims) { - using nlohmann_claim = jwt::basic_claim; - - const auto string = nlohmann_claim(std::string("string")); - const auto array = nlohmann_claim(std::set{"string", "string"}); - const auto integer = nlohmann_claim(159816816); -} - -TEST(NlohmannTest, AudienceAsString) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0." - "WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_FALSE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_TRUE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - ASSERT_FALSE(decoded.get_payload_claims().empty()); - ASSERT_FALSE(decoded.get_header_claims().empty()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWT", decoded.get_type()); - auto aud = decoded.get_audience(); - ASSERT_EQ(1, aud.size()); - ASSERT_EQ("test", *aud.begin()); -} - -TEST(NlohmannTest, SetArray) { - std::vector vect = {100, 20, 10}; - auto token = jwt::create() - .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) - .sign(jwt::algorithm::none{}); - ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); -} - -TEST(NlohmannTest, SetObject) { - std::istringstream iss{"{\"api-x\": [1]}"}; - jwt::basic_claim object; - iss >> object; - ASSERT_EQ(object.get_type(), jwt::json::type::object); - - auto token = - jwt::create().set_payload_claim("namespace", object).sign(jwt::algorithm::hs256("test")); - ASSERT_EQ(token, - "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); -} - -TEST(NlohmannTest, VerifyTokenHS256) { - std::string token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(NlohmannTest, VerifyTokenExpirationValid) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now()) - .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(NlohmannTest, VerifyTokenExpired) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) - .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - std::error_code ec; - ASSERT_NO_THROW(verify.verify(decoded_token, ec)); - ASSERT_TRUE(!(!ec)); - ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); - ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); -} diff --git a/tests/TokenTest.cpp b/tests/TokenTest.cpp index 5be01bf5e..5fc28f010 100644 --- a/tests/TokenTest.cpp +++ b/tests/TokenTest.cpp @@ -639,7 +639,7 @@ TEST(TokenTest, VerifyTokenExpireFail) { auto token = jwt::create().set_expires_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); std::error_code ec; @@ -653,7 +653,7 @@ TEST(TokenTest, VerifyTokenExpire) { auto token = jwt::create().set_expires_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_NO_THROW(verify.verify(decoded_token)); std::error_code ec; @@ -666,7 +666,7 @@ TEST(TokenTest, VerifyTokenNBFFail) { auto token = jwt::create().set_not_before(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); std::error_code ec; @@ -680,7 +680,7 @@ TEST(TokenTest, VerifyTokenNBF) { auto token = jwt::create().set_not_before(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_NO_THROW(verify.verify(decoded_token)); std::error_code ec; @@ -693,7 +693,7 @@ TEST(TokenTest, VerifyTokenIATFail) { auto token = jwt::create().set_issued_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); std::error_code ec; @@ -707,7 +707,7 @@ TEST(TokenTest, VerifyTokenIAT) { auto token = jwt::create().set_issued_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); auto decoded_token = jwt::decode(token); - auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) .allow_algorithm(jwt::algorithm::none{}); ASSERT_NO_THROW(verify.verify(decoded_token)); std::error_code ec; diff --git a/tests/boost/.gitignore b/tests/boost/.gitignore deleted file mode 100644 index 567609b12..000000000 --- a/tests/boost/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/tests/boost/BoostJsonTest.cpp b/tests/boost/BoostJsonTest.cpp deleted file mode 100644 index dd0cd0873..000000000 --- a/tests/boost/BoostJsonTest.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#define JWT_DISABLE_PICOJSON -#include "jwt-cpp/jwt.h" - -#include -#include -#include - -#include -#include - -namespace json = boost::json; - -struct boostjson_traits { - using value_type = json::value; - using object_type = json::object; - using array_type = json::array; - using string_type = std::string; - using number_type = double; - using integer_type = std::int64_t; - using boolean_type = bool; - - static jwt::json::type get_type(const value_type& val) { - using jwt::json::type; - - if (val.kind() == json::kind::bool_) return type::boolean; - if (val.kind() == json::kind::int64) return type::integer; - if (val.kind() == json::kind::uint64) // boost internally tracks two types of integers - return type::integer; - if (val.kind() == json::kind::double_) return type::number; - if (val.kind() == json::kind::string) return type::string; - if (val.kind() == json::kind::array) return type::array; - if (val.kind() == json::kind::object) return type::object; - - throw std::logic_error("invalid type"); - } - - static object_type as_object(const value_type& val) { - if (val.kind() != json::kind::object) throw std::bad_cast(); - return val.get_object(); - } - - static array_type as_array(const value_type& val) { - if (val.kind() != json::kind::array) throw std::bad_cast(); - return val.get_array(); - } - - static string_type as_string(const value_type& val) { - if (val.kind() != json::kind::string) throw std::bad_cast(); - return string_type{val.get_string()}; - } - - static integer_type as_int(const value_type& val) { - switch (val.kind()) { - case json::kind::int64: return val.get_int64(); - case json::kind::uint64: return static_cast(val.get_uint64()); - default: throw std::bad_cast(); - } - } - - static boolean_type as_bool(const value_type& val) { - if (val.kind() != json::kind::bool_) throw std::bad_cast(); - return val.get_bool(); - } - - static number_type as_number(const value_type& val) { - if (val.kind() != json::kind::double_) throw std::bad_cast(); - return val.get_double(); - } - - static bool parse(value_type& val, string_type str) { - val = json::parse(str); - return true; - } - - static std::string serialize(const value_type& val) { return json::serialize(val); } -}; - -TEST(BoostJSONTest, BasicClaims) { - using boostjson_claim = jwt::basic_claim; - - const auto string = boostjson_claim(boostjson_traits::string_type("string")); - const auto array = boostjson_claim(std::set{"string", "string"}); - const auto integer = boostjson_claim(boostjson_traits::value_type{159816816}); -} - -TEST(BoostJSONTest, AudienceAsString) { - boostjson_traits::string_type token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0." - "WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_FALSE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_TRUE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWT", decoded.get_type()); - auto aud = decoded.get_audience(); - ASSERT_EQ(1, aud.size()); - ASSERT_EQ("test", *aud.begin()); -} - -TEST(BoostJSONTest, SetArray) { - std::vector vect = {100, 20, 10}; - auto token = jwt::create() - .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) - .sign(jwt::algorithm::none{}); - ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); -} - -TEST(BoostJSONTest, SetObject) { - jwt::basic_claim object{json::parse("{\"api-x\": [1]}")}; - ASSERT_EQ(object.get_type(), jwt::json::type::object); - - auto token = - jwt::create().set_payload_claim("namespace", object).sign(jwt::algorithm::hs256("test")); - ASSERT_EQ(token, - "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); -} - -TEST(BoostJSONTest, VerifyTokenHS256) { - boostjson_traits::string_type token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(BoostJSONTest, VerifyTokenExpirationValid) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now()) - .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(BoostJSONTest, VerifyTokenExpired) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) - .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - std::error_code ec; - ASSERT_NO_THROW(verify.verify(decoded_token, ec)); - ASSERT_TRUE(!(!ec)); - ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); - ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); -} diff --git a/tests/boost/CMakeLists.txt b/tests/boost/CMakeLists.txt deleted file mode 100644 index c1b4d8381..000000000 --- a/tests/boost/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.18) -project(jwt-cpp-boostjson-tests) - -find_package(OpenSSL REQUIRED) -find_package(GTest REQUIRED) -if(NOT TARGET jwt-cpp::jwt-cpp) - find_package(jwt-cpp CONFIG REQUIRED) -endif() - -file(DOWNLOAD https://github.com/boostorg/json/archive/refs/tags/boost-1.77.0.tar.gz - ${CMAKE_CURRENT_BINARY_DIR}/boost-1.77.0.tar.gz) -file(ARCHIVE_EXTRACT INPUT ${CMAKE_CURRENT_BINARY_DIR}/boost-1.77.0.tar.gz DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - -add_executable(boostjson-traits BoostJsonTest.cpp) -target_link_libraries(boostjson-traits jwt-cpp::jwt-cpp GTest::GTest GTest::Main) -target_compile_definitions(boostjson-traits PRIVATE BOOST_JSON_STANDALONE) -target_compile_features(boostjson-traits PRIVATE cxx_std_17) -target_include_directories(boostjson-traits PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/json-boost-1.77.0/include") diff --git a/tests/fuzz/BaseDecodeFuzz.cpp b/tests/fuzz/BaseDecodeFuzz.cpp index 743e5834c..5dc4f003b 100644 --- a/tests/fuzz/BaseDecodeFuzz.cpp +++ b/tests/fuzz/BaseDecodeFuzz.cpp @@ -2,13 +2,12 @@ extern "C" { -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - try { - const auto bin = jwt::base::decode( - std::string{(char *)Data, Size}); - } catch (const std::runtime_error &) { - // parse errors are ok, because input may be random bytes - } - return 0; // Non-zero return values are reserved for future use. +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + try { + const auto bin = jwt::base::decode(std::string{(char*)Data, Size}); + } catch (const std::runtime_error&) { + // parse errors are ok, because input may be random bytes + } + return 0; // Non-zero return values are reserved for future use. } } diff --git a/tests/fuzz/BaseEncodeFuzz.cpp b/tests/fuzz/BaseEncodeFuzz.cpp index afb4931d4..9ac80252d 100644 --- a/tests/fuzz/BaseEncodeFuzz.cpp +++ b/tests/fuzz/BaseEncodeFuzz.cpp @@ -2,8 +2,8 @@ extern "C" { -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - jwt::base::encode(std::string{(char *)Data, Size}); - return 0; // Non-zero return values are reserved for future use. +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + jwt::base::encode(std::string{(char*)Data, Size}); + return 0; // Non-zero return values are reserved for future use. } } diff --git a/tests/fuzz/TokenDecodeFuzz.cpp b/tests/fuzz/TokenDecodeFuzz.cpp index 1d44d9c2f..6a23c2217 100644 --- a/tests/fuzz/TokenDecodeFuzz.cpp +++ b/tests/fuzz/TokenDecodeFuzz.cpp @@ -2,27 +2,26 @@ extern "C" { -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - try { - // step 1: parse input - const auto jwt1 = jwt::decode(std::string{(char *)Data, Size}); +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + try { + // step 1: parse input + const auto jwt1 = jwt::decode(std::string{(char*)Data, Size}); - try { - // step 2: round trip - std::string s1 = jwt1.get_token(); - const auto jwt2 = jwt::decode(s1); + try { + // step 2: round trip + std::string s1 = jwt1.get_token(); + const auto jwt2 = jwt::decode(s1); - // tokens must match - if (s1 != jwt2.get_token()) - abort(); - } catch (...) { - // parsing raw data twice must not fail - abort(); - } - } catch (...) { - // parse errors are ok, because input may be random bytes - } + // tokens must match + if (s1 != jwt2.get_token()) abort(); + } catch (...) { + // parsing raw data twice must not fail + abort(); + } + } catch (...) { + // parse errors are ok, because input may be random bytes + } - return 0; // Non-zero return values are reserved for future use. + return 0; // Non-zero return values are reserved for future use. } } diff --git a/tests/jsoncons/CMakeLists.txt b/tests/jsoncons/CMakeLists.txt deleted file mode 100644 index 8c1e4db99..000000000 --- a/tests/jsoncons/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.8) -project(jwt-cpp-jsoncons-tests) - -if(NOT TARGET jwt-cpp) - find_package(jwt-cpp CONFIG REQUIRED) -endif() - -find_package(GTest REQUIRED) -find_package(jsoncons CONFIG REQUIRED) - -add_executable(jsoncons-traits-narrow JsonconsTest.cpp) -target_link_libraries(jsoncons-traits-narrow jwt-cpp::jwt-cpp GTest::GTest GTest::Main jsoncons) diff --git a/tests/jsoncons/JsonconsTest.cpp b/tests/jsoncons/JsonconsTest.cpp deleted file mode 100644 index 9627c7b42..000000000 --- a/tests/jsoncons/JsonconsTest.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#define JWT_DISABLE_PICOJSON -#define JSONCONS_NO_DEPRECATED - -#include "jwt-cpp/jwt.h" - -#include -#include - -#include - -struct jsoncons_traits { - // Needs at least https://github.com/danielaparker/jsoncons/commit/28c56b90ec7337f98a5b8942574590111a5e5831 - static_assert(jsoncons::version().minor >= 167); - - using json = jsoncons::json; - using value_type = json; - struct object_type : json::object { - // Add missing C++11 member types - // https://github.com/danielaparker/jsoncons/commit/1b1ceeb572f9a2db6d37cff47ac78a4f14e072e2#commitcomment-45391411 - using value_type = key_value_type; // Enable optional jwt-cpp methods - using mapped_type = key_value_type::value_type; - using size_type = size_t; // for implementing count - - object_type() = default; - object_type(const object_type&) = default; - explicit object_type(const json::object& o) : json::object(o) {} - object_type(object_type&&) = default; - explicit object_type(json::object&& o) : json::object(o) {} - ~object_type() = default; - object_type& operator=(const object_type& o) = default; - object_type& operator=(object_type&& o) noexcept = default; - - // Add missing C++11 subscription operator - mapped_type& operator[](const key_type& key) { - // https://github.com/microsoft/STL/blob/2914b4301c59dc7ffc09d16ac6f7979fde2b7f2c/stl/inc/map#L325 - return try_emplace(key).first->value(); - } - - // Add missing C++11 element access - const mapped_type& at(const key_type& key) const { - auto target = find(key); - if (target != end()) return target->value(); - - throw std::out_of_range("invalid key"); - } - - // Add missing C++11 lookup method - size_type count(const key_type& key) const { - struct compare { - bool operator()(const value_type& val, const key_type& key) const { return val.key() < key; } - bool operator()(const key_type& key, const value_type& val) const { return key < val.key(); } - }; - - // https://en.cppreference.com/w/cpp/algorithm/binary_search#Complexity - if (std::binary_search(this->begin(), this->end(), key, compare{})) return 1; - return 0; - } - }; - using array_type = json::array; - using string_type = std::string; // current limitation of traits implementation - using number_type = double; - using integer_type = int64_t; - using boolean_type = bool; - - static jwt::json::type get_type(const json& val) { - using jwt::json::type; - - if (val.type() == jsoncons::json_type::bool_value) return type::boolean; - if (val.type() == jsoncons::json_type::int64_value) return type::integer; - if (val.type() == jsoncons::json_type::uint64_value) return type::integer; - if (val.type() == jsoncons::json_type::half_value) return type::number; - if (val.type() == jsoncons::json_type::double_value) return type::number; - if (val.type() == jsoncons::json_type::string_value) return type::string; - if (val.type() == jsoncons::json_type::array_value) return type::array; - if (val.type() == jsoncons::json_type::object_value) return type::object; - - throw std::logic_error("invalid type"); - } - - static object_type as_object(const json& val) { - if (val.type() != jsoncons::json_type::object_value) throw std::bad_cast(); - return object_type(val.object_value()); - } - - static array_type as_array(const json& val) { - if (val.type() != jsoncons::json_type::array_value) throw std::bad_cast(); - return val.array_value(); - } - - static string_type as_string(const json& val) { - if (val.type() != jsoncons::json_type::string_value) throw std::bad_cast(); - return val.as_string(); - } - - static number_type as_number(const json& val) { - if (get_type(val) != jwt::json::type::number) throw std::bad_cast(); - return val.as_double(); - } - - static integer_type as_int(const json& val) { - if (get_type(val) != jwt::json::type::integer) throw std::bad_cast(); - return val.as(); - } - - static boolean_type as_bool(const json& val) { - if (val.type() != jsoncons::json_type::bool_value) throw std::bad_cast(); - return val.as_bool(); - } - - static bool parse(json& val, const std::string& str) { - val = json::parse(str); - return true; - } - - static std::string serialize(const json& val) { - std::ostringstream os; - os << jsoncons::print(val); - return os.str(); - } -}; - -TEST(JsonconsTest, BasicClaims) { - using jsoncons_claim = jwt::basic_claim; - - const auto string = jsoncons_claim(std::string("string")); - const auto array = jsoncons_claim(std::set{"string", "string"}); - const auto integer = jsoncons_claim(159816816); -} - -TEST(JsonconsTest, AudienceAsString) { - - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0." - "WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_FALSE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_TRUE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWT", decoded.get_type()); - auto aud = decoded.get_audience(); - ASSERT_EQ(1, aud.size()); - ASSERT_EQ("test", *aud.begin()); -} - -TEST(JsonconsTest, SetArray) { - std::vector vect = {100, 20, 10}; - auto token = jwt::create() - .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) - .sign(jwt::algorithm::none{}); - ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); -} - -TEST(JsonconsTest, SetObject) { - std::istringstream iss{"{\"api-x\": [1]}"}; - jwt::basic_claim object; - iss >> object; - ASSERT_EQ(object.get_type(), jwt::json::type::object); - - auto token = - jwt::create().set_payload_claim("namespace", object).sign(jwt::algorithm::hs256("test")); - ASSERT_EQ(token, - "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); -} - -TEST(JsonconsTest, VerifyTokenHS256) { - std::string token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(JsonconsTest, VerifyTokenExpirationValid) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now()) - .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(JsonconsTest, VerifyTokenExpired) { - const auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) - .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) - .sign(jwt::algorithm::hs256{"secret"}); - - auto verify = jwt::verify({}) - .allow_algorithm(jwt::algorithm::hs256{"secret"}) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - std::error_code ec; - ASSERT_NO_THROW(verify.verify(decoded_token, ec)); - ASSERT_TRUE(!(!ec)); - ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); - ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); -} diff --git a/tests/traits/BoostJsonTest.cpp b/tests/traits/BoostJsonTest.cpp new file mode 100644 index 000000000..6c25904f9 --- /dev/null +++ b/tests/traits/BoostJsonTest.cpp @@ -0,0 +1,128 @@ +#include "jwt-cpp/traits/boost-json/traits.h" + +#include + +TEST(BoostJsonTest, BasicClaims) { + const auto string = jwt::basic_claim(jwt::traits::boost_json::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = + jwt::basic_claim(std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + jwt::traits::boost_json::value_type jvi = 159816816; + const auto integer = jwt::basic_claim(jvi); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(BoostJsonTest, AudienceAsString) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(BoostJsonTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(BoostJsonTest, SetObject) { + jwt::traits::boost_json::value_type value; + ASSERT_TRUE(jwt::traits::boost_json::parse(value, "{\"api-x\": [1]}")); + jwt::basic_claim object(value); + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(BoostJsonTest, VerifyTokenHS256) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(BoostJsonTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(BoostJsonTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(BoostJsonTest, VerifyArray) { + jwt::traits::boost_json::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(BoostJsonTest, VerifyObject) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::traits::boost_json::value_type value; + ASSERT_TRUE(jwt::traits::boost_json::parse(value, "{\"api-x\": [1]}")); + jwt::basic_claim object_claim(value); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/tests/traits/JsonconsTest.cpp b/tests/traits/JsonconsTest.cpp new file mode 100644 index 000000000..005c26367 --- /dev/null +++ b/tests/traits/JsonconsTest.cpp @@ -0,0 +1,133 @@ +#include "jwt-cpp/traits/danielaparker-jsoncons/traits.h" + +#include + +TEST(JsonconsTest, BasicClaims) { + const auto string = jwt::basic_claim( + jwt::traits::danielaparker_jsoncons::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(JsonconsTest, AudienceAsString) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(JsonconsTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = + jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(JsonconsTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(JsonconsTest, VerifyTokenHS256) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(JsonconsTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(JsonconsTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(JsonconsTest, VerifyArray) { + jwt::traits::danielaparker_jsoncons::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(JsonconsTest, VerifyObject) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/tests/traits/NlohmannTest.cpp b/tests/traits/NlohmannTest.cpp new file mode 100644 index 000000000..aa613827b --- /dev/null +++ b/tests/traits/NlohmannTest.cpp @@ -0,0 +1,128 @@ +#include "jwt-cpp/traits/nlohmann-json/traits.h" + +#include + +TEST(NlohmannTest, BasicClaims) { + const auto string = jwt::basic_claim(jwt::traits::nlohmann_json::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(NlohmannTest, AudienceAsString) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(NlohmannTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(NlohmannTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(NlohmannTest, VerifyTokenHS256) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(NlohmannTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(NlohmannTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(NlohmannTest, VerifyArray) { + jwt::traits::nlohmann_json::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(NlohmannTest, VerifyObject) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/tests/traits/TraitsTest.cpp.mustache b/tests/traits/TraitsTest.cpp.mustache new file mode 100644 index 000000000..c690a6106 --- /dev/null +++ b/tests/traits/TraitsTest.cpp.mustache @@ -0,0 +1,133 @@ +#include "jwt-cpp/traits/{{traits_dir}}/traits.h" + +#include + +TEST({{test_suite_name}}, BasicClaims) { + const auto string = jwt::basic_claim( + jwt::traits::{{traits_name}}::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST({{test_suite_name}}, AudienceAsString) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST({{test_suite_name}}, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = + jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST({{test_suite_name}}, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST({{test_suite_name}}, VerifyTokenHS256) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST({{test_suite_name}}, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST({{test_suite_name}}, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST({{test_suite_name}}, VerifyArray) { + jwt::traits::{{traits_name}}::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST({{test_suite_name}}, VerifyObject) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +}