From 3309b19c2eaa14c6e896ea7816443b0d88683192 Mon Sep 17 00:00:00 2001 From: James Pace Date: Thu, 24 Mar 2022 00:10:12 +0000 Subject: [PATCH] Mass refactor. Clang-format. Start adding tests. Use FetchContent. --- .clang-format | 21 +++++ CMakeLists.txt | 37 ++++---- README.md | 2 +- {test => examples}/get-token-example.py | 0 {test => examples}/key.pem | 0 {test => examples}/mosquitto.conf | 0 {test => examples}/pub.sh | 0 {test => examples}/python-device-client.py | 0 {test => examples}/python-direct-client.py | 0 {test => examples}/run.sh | 0 external-deps.cmake | 39 +++----- include/j7s-plugin/j7s-plugin.h | 28 +++--- include/j7s-plugin/utils.h | 36 ++++++++ src/AuthList.cpp | 22 ++--- src/Authorizer.cpp | 85 +++-------------- src/gen-token.cpp | 60 ++++-------- src/j7s-plugin.cpp | 92 ++++++++++--------- src/utils.cpp | 101 +++++++++++++++++++++ test/authorizer_test.cpp | 10 ++ 19 files changed, 310 insertions(+), 223 deletions(-) create mode 100644 .clang-format rename {test => examples}/get-token-example.py (100%) rename {test => examples}/key.pem (100%) rename {test => examples}/mosquitto.conf (100%) rename {test => examples}/pub.sh (100%) rename {test => examples}/python-device-client.py (100%) rename {test => examples}/python-direct-client.py (100%) rename {test => examples}/run.sh (100%) create mode 100644 include/j7s-plugin/utils.h create mode 100644 src/utils.cpp create mode 100644 test/authorizer_test.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1e0103a --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +--- +Language: Cpp +BasedOnStyle: Google + +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Allman +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +ColumnLimit: 100 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Middle +ReflowComments: false +... diff --git a/CMakeLists.txt b/CMakeLists.txt index ceb8e3a..3e139c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,34 +1,39 @@ cmake_minimum_required(VERSION 3.16) +project(j7s-mosquitto-plugin) -# https://github.com/Sarcasm/cmake-superbuild/ -option (USE_SUPERBUILD "Whether or not a superbuild should be invoked" ON) +include(external-deps.cmake) -if (USE_SUPERBUILD) - project(j7s-mosquitto-plugin-super) - include(external-deps.cmake) - return() -else() - project(j7s-mosquitto-plugin) -endif() - -find_package(jwt-cpp) -find_package(argparse) find_package(OpenSSL) set(CMAKE_CXX_STANDARD 20) +add_library(utils SHARED src/utils.cpp) +target_include_directories(utils PUBLIC + $ + $ +) +target_link_libraries(utils OpenSSL::Crypto jwt-cpp) + add_library(j7s-plugin SHARED src/j7s-plugin.cpp src/AuthList.cpp src/Authorizer.cpp) target_include_directories(j7s-plugin PUBLIC $ $ - ${jwt-cpp_INCLUDE_DIR} ) -target_link_libraries(j7s-plugin OpenSSL::Crypto) +target_link_libraries(j7s-plugin utils) add_executable(gen-token src/gen-token.cpp) target_include_directories(gen-token PUBLIC $ $ - ${jwt-cpp_INCLUDE_DIR} ) -target_link_libraries(gen-token OpenSSL::Crypto argparse::argparse) +target_link_libraries(gen-token utils argparse::argparse) + +if(BUILD_TESTING) + include(GoogleTest) + + enable_testing() + + add_executable(authorizer_test test/authorizer_test.cpp) + target_link_libraries(authorizer_test GTest::gtest_main) + gtest_discover_tests(authorizer_test) +endif() diff --git a/README.md b/README.md index e9acb81..094b628 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Authentication using JWTs for the mosquitto mqtt broker. ## Dependencies ``` -sudo apt install mosquitto-dev g++ cmake libmosquitto-dev mosquitto-clients openssl libssl-dev +sudo apt install mosquitto-dev g++ cmake libmosquitto-dev mosquitto-clients openssl libssl-dev googletest ``` ## Generating offline keys diff --git a/test/get-token-example.py b/examples/get-token-example.py similarity index 100% rename from test/get-token-example.py rename to examples/get-token-example.py diff --git a/test/key.pem b/examples/key.pem similarity index 100% rename from test/key.pem rename to examples/key.pem diff --git a/test/mosquitto.conf b/examples/mosquitto.conf similarity index 100% rename from test/mosquitto.conf rename to examples/mosquitto.conf diff --git a/test/pub.sh b/examples/pub.sh similarity index 100% rename from test/pub.sh rename to examples/pub.sh diff --git a/test/python-device-client.py b/examples/python-device-client.py similarity index 100% rename from test/python-device-client.py rename to examples/python-device-client.py diff --git a/test/python-direct-client.py b/examples/python-direct-client.py similarity index 100% rename from test/python-direct-client.py rename to examples/python-direct-client.py diff --git a/test/run.sh b/examples/run.sh similarity index 100% rename from test/run.sh rename to examples/run.sh diff --git a/external-deps.cmake b/external-deps.cmake index 91a509a..7e2458b 100644 --- a/external-deps.cmake +++ b/external-deps.cmake @@ -1,39 +1,22 @@ -include (ExternalProject) +include(FetchContent) -set (DEPENDENCIES) -set (EXTRA_CMAKE_ARGS) +FetchContent_Declare(googletest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-1.11.0" + GIT_SHALLOW "True" +) +FetchContent_MakeAvailable(googletest) -list(APPEND DEPENDENCIES ep_jwt-cpp) -ExternalProject_Add(ep_jwt-cpp - PREFIX ep_jwt-cpp +FetchContent_Declare(jwt-cpp GIT_REPOSITORY "https://github.com/Thalhammer/jwt-cpp.git" GIT_TAG "v0.5.2" GIT_SHALLOW "True" - CMAKE_ARGS -DJWT_CMAKE_FILES_INSTALL_DIR=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp -) -list (APPEND EXTRA_CMAKE_ARGS - -Djwt-cpp_DIR=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp -Djwt-cpp_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp/include ) +FetchContent_MakeAvailable(jwt-cpp) -list(APPEND DEPENDENCIES ep_argparse) -ExternalProject_Add(ep_argparse - PREFIX ep_argparse +FetchContent_Declare(argparse GIT_REPOSITORY "https://github.com/p-ranav/argparse.git" GIT_TAG "v2.2" GIT_SHALLOW "True" - CMAKE_ARGS -DARGPARSE_CMAKE_FILES_INSTALL_DIR=${CMAKE_CURRENT_BINARY_DIR}/argparse -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/argparse - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/argparse -) -list (APPEND EXTRA_CMAKE_ARGS - -Dargparse_DIR=${CMAKE_CURRENT_BINARY_DIR}/argparse -) - -ExternalProject_Add (ep_j7s-mosquitto-plugin - PREFIX ep_j7s-mosquitto-plugin - DEPENDS ${DEPENDENCIES} - SOURCE_DIR "${PROJECT_SOURCE_DIR}" - CMAKE_ARGS -DUSE_SUPERBUILD=OFF ${EXTRA_CMAKE_ARGS} - INSTALL_COMMAND "" - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR} ) +FetchContent_MakeAvailable(argparse) \ No newline at end of file diff --git a/include/j7s-plugin/j7s-plugin.h b/include/j7s-plugin/j7s-plugin.h index f84a48f..4894a15 100644 --- a/include/j7s-plugin/j7s-plugin.h +++ b/include/j7s-plugin/j7s-plugin.h @@ -15,19 +15,25 @@ // Mosquitto authentication plugin that using Authorizer to authorize // users using jwts. -extern "C" { - #include "mosquitto.h" - #include "mosquitto_broker.h" - #include "mosquitto_plugin.h" +extern "C" +{ +#include "mosquitto.h" +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" } // Stuff we're "exporting" for the dynamic loading. -extern "C" { - int mosquitto_plugin_version(int supported_version_count, const int *supported_versions); - int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, struct mosquitto_opt *options, int option_count); - int mosquitto_plugin_cleanup(void *userdata, struct mosquitto_opt *options, int option_count); +extern "C" +{ + int mosquitto_plugin_version(int supported_version_count, const int * supported_versions); + int mosquitto_plugin_init( + mosquitto_plugin_id_t * identifier, + void ** userdata, + struct mosquitto_opt * options, + int option_count); + int mosquitto_plugin_cleanup(void * userdata, struct mosquitto_opt * options, int option_count); } // My functions -int j7s_auth_basic_auth_callback(int event, void *event_data, void *userdata); -int j7s_acl_check_callback(int event, void *event_data, void *userdata); -int j7s_disconnect_callback(int event, void *event_data, void *userdata); +int j7s_auth_basic_auth_callback(int event, void * event_data, void * userdata); +int j7s_acl_check_callback(int event, void * event_data, void * userdata); +int j7s_disconnect_callback(int event, void * event_data, void * userdata); diff --git a/include/j7s-plugin/utils.h b/include/j7s-plugin/utils.h new file mode 100644 index 0000000..5d0de24 --- /dev/null +++ b/include/j7s-plugin/utils.h @@ -0,0 +1,36 @@ + +// Copyright 2022 James Pace +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +std::optional read_key(const std::string & key_file); + +std::tuple validate( + const std::string & token, + const std::string & username, + const std::string & issuer, + const std::string & pub_key); + +std::string gen_token( + const std::string & issuer, + const std::string & username, + const std::string & can_read, + const std::string & can_write, + const std::string & pub_key, + const std::string & priv_key, + const std::chrono::time_point & expr_time); diff --git a/src/AuthList.cpp b/src/AuthList.cpp index bbdc935..e1030de 100644 --- a/src/AuthList.cpp +++ b/src/AuthList.cpp @@ -11,39 +11,37 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#include #include +#include -AuthList::AuthList(): - _allowedUsernames{} -{ -} +AuthList::AuthList() : _allowedUsernames{} {} -void AuthList::add(const std::string& username) +void AuthList::add(const std::string & username) { // Is the username already in the list? // If not add it. - if(not confirm(username)) + if (not confirm(username)) { _allowedUsernames.emplace_front(username); } } -void AuthList::remove(const std::string& username) +void AuthList::remove(const std::string & username) { // Is the user in the list? // Is so, remove it, - if(confirm(username)) + if (confirm(username)) { _allowedUsernames.remove(username); } } -bool AuthList::confirm(const std::string& username) +bool AuthList::confirm(const std::string & username) { // Is the user in the list? - const auto found = std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username); - if(found != std::end(_allowedUsernames)) + const auto found = + std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username); + if (found != std::end(_allowedUsernames)) { return true; } diff --git a/src/Authorizer.cpp b/src/Authorizer.cpp index 9fd2ee0..3b7373c 100644 --- a/src/Authorizer.cpp +++ b/src/Authorizer.cpp @@ -11,95 +11,41 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#include -#include - -#include +#include #include -#include -#include +#include +#include -Authorizer::Authorizer(const std::string& pub_key, const std::string& issuer): - _pub_key{pub_key}, - _issuer{issuer} +Authorizer::Authorizer(const std::string & pub_key, const std::string & issuer) : + _pub_key{pub_key}, _issuer{issuer} { } -std::optional Authorizer::read_key(const std::string& key_file) -{ - // Read key from file. - std::ifstream key_stream(key_file, std::ios::binary); - if(not key_stream) - { - return std::nullopt; - } - std::stringstream ss; - ss << key_stream.rdbuf(); - return ss.str(); -} - -void Authorizer::add_unknown(const std::string& username) +void Authorizer::add_unknown(const std::string & username) { _unknownList.add(username); } -bool Authorizer::is_unknown(const std::string& username) +bool Authorizer::is_unknown(const std::string & username) { return (username.empty() or _unknownList.confirm(username)); } -bool Authorizer::add(const std::string& token, const std::string& username) +bool Authorizer::add(const std::string & token, const std::string & username) { - const auto decoded_token = jwt::decode(token); - - // Is the token valid? - const auto verifier = jwt::verify() - .with_issuer(_issuer) - .allow_algorithm(jwt::algorithm::rs256(_pub_key)); - try - { - verifier.verify(decoded_token); - } - catch(jwt::error::token_verification_exception& exception) - { - std::cerr << exception.what() << std::endl; - return false; - } - auto claims = decoded_token.get_payload_claims(); - - // Check username matches. - if(not claims.contains("upn")) - { - std::cerr << "Missing upn." << std::endl; - return false; - } - if(claims["upn"].as_string() != username) - { - std::cerr << "Wrong username." << std::endl; - return false; - } - - // Check for mqtt-write claim value. - if(not (claims.contains("mqtt-write") and claims.contains("mqtt-read"))) - { - std::cerr << "Missing mqtt-write or mqtt-read." << std::endl; - return false; - } - - bool can_write = claims["mqtt-write"].as_bool(); - bool can_read = claims["mqtt-read"].as_bool(); - if(not (can_write or can_read)) + const auto [can_read, can_write] = validate(token, username, _issuer, _pub_key); + if (not(can_write or can_read)) { std::cerr << "Can't write or can't read." << std::endl; return false; } - if(can_write) + if (can_write) { _writeList.add(username); } - if(can_read) + if (can_read) { _readList.add(username); } @@ -107,20 +53,19 @@ bool Authorizer::add(const std::string& token, const std::string& username) return true; } -bool Authorizer::can_read(const std::string& username) +bool Authorizer::can_read(const std::string & username) { return _readList.confirm(username); } -bool Authorizer::can_write(const std::string& username) +bool Authorizer::can_write(const std::string & username) { return _writeList.confirm(username); } -void Authorizer::logout(const std::string& username) +void Authorizer::logout(const std::string & username) { _writeList.remove(username); _readList.remove(username); _unknownList.remove(username); } - diff --git a/src/gen-token.cpp b/src/gen-token.cpp index daaaf42..f19694e 100644 --- a/src/gen-token.cpp +++ b/src/gen-token.cpp @@ -12,35 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include -#include +#include #include -#include -#include -#include -#include #include -std::optional read_key(const std::string& key_file); - -int main(int argc, char *argv[]) +int main(int argc, char * argv[]) { argparse::ArgumentParser program("gen-token", "0.0.0"); - program.add_argument("--pub-key") - .required() - .help("Pub key of signer."); - program.add_argument("--priv-key") - .required() - .help("Private key of signer."); - program.add_argument("--issuer") - .required() - .help("Issuer to assign to signed key."); - program.add_argument("--username") - .required() - .help("Username assigned to key."); + program.add_argument("--pub-key").required().help("Pub key of signer."); + program.add_argument("--priv-key").required().help("Private key of signer."); + program.add_argument("--issuer").required().help("Issuer to assign to signed key."); + program.add_argument("--username").required().help("Username assigned to key."); program.add_argument("--valid-days") .required() .help("Days from now until the token will be valid."); @@ -68,7 +53,7 @@ int main(int argc, char *argv[]) const auto pub_key_file = program.get("--pub-key"); const auto pub_key = read_key(std::filesystem::absolute(pub_key_file)); - if(not pub_key or not priv_key) + if (not pub_key or not priv_key) { std::cerr << "Could not open key!" << std::endl; return -2; @@ -80,29 +65,16 @@ int main(int argc, char *argv[]) const auto expr_time = std::chrono::system_clock::now() + std::chrono::days(std::stoi(program.get("--valid-days"))); - const auto token = jwt::create() - .set_type("JWT") - .set_issuer(program.get("--issuer")) - .set_payload_claim("upn", jwt::claim(program.get("--username"))) - .set_payload_claim("mqtt-write", jwt::claim(can_read)) - .set_payload_claim("mqtt-read", jwt::claim(can_write)) - .set_expires_at(expr_time) - .sign(jwt::algorithm::rs256(pub_key.value(), priv_key.value(), "", "")); + const auto token = gen_token( + program.get("--issuer"), + program.get("--username"), + can_read, + can_write, + pub_key.value(), + priv_key.value(), + expr_time); std::cout << token; return 0; } - -std::optional read_key(const std::string& key_file) -{ - // Read key from file. - std::ifstream key_stream(key_file, std::ios::binary); - if(not key_stream) - { - return std::nullopt; - } - std::stringstream ss; - ss << key_stream.rdbuf(); - return ss.str(); -} diff --git a/src/j7s-plugin.cpp b/src/j7s-plugin.cpp index 43784fc..6fc3658 100644 --- a/src/j7s-plugin.cpp +++ b/src/j7s-plugin.cpp @@ -12,32 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. #include + #include -#include #include +#include // Mosquitto Globals -static mosquitto_plugin_id_t *plugin_id = nullptr; +static mosquitto_plugin_id_t * plugin_id = nullptr; static std::unique_ptr authorizer = nullptr; -int mosquitto_plugin_version(int supported_version_count, const int *supported_versions) +int mosquitto_plugin_version(int supported_version_count, const int * supported_versions) { - for(int index = 0; index < supported_version_count; index++) + for (int index = 0; index < supported_version_count; index++) { - if(supported_versions[index] == 5) + if (supported_versions[index] == 5) { - return 5; - } - } - return -1; + return 5; + } + } + return -1; } -int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, struct mosquitto_opt *options, int option_count) +int mosquitto_plugin_init( + mosquitto_plugin_id_t * identifier, + void ** userdata, + struct mosquitto_opt * options, + int option_count) { plugin_id = identifier; - - if(option_count != 2) + if (option_count != 2) { mosquitto_log_printf(MOSQ_LOG_ERR, "Missing an option. Found: %d", option_count); return MOSQ_ERR_INVAL; @@ -45,23 +49,23 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, st std::string public_key; std::string issuer; - for(int index = 0; index < option_count; index++) + for (int index = 0; index < option_count; index++) { const auto key = std::string(options[index].key); - if(key == "public_key") + if (key == "public_key") { const auto key = Authorizer::read_key(std::string(options[index].value)); - if(not key or key->empty()) + if (not key or key->empty()) { mosquitto_log_printf(MOSQ_LOG_ERR, "Could not read public key."); return MOSQ_ERR_INVAL; } public_key = *key; } - else if(key == "issuer") + else if (key == "issuer") { issuer = std::string(options[index].value); - if(issuer.empty()) + if (issuer.empty()) { mosquitto_log_printf(MOSQ_LOG_ERR, "issuer not set."); return MOSQ_ERR_INVAL; @@ -72,50 +76,56 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, st authorizer = std::make_unique(public_key, issuer); // Register the callbacks. - mosquitto_callback_register(plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL, NULL); - mosquitto_callback_register(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL, NULL); - mosquitto_callback_register(plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_callback, NULL, NULL); + mosquitto_callback_register( + plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL, NULL); + mosquitto_callback_register(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL, NULL); + mosquitto_callback_register( + plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_callback, NULL, NULL); // May want MOSQ_EVT_RELOAD as well. return MOSQ_ERR_SUCCESS; } -int mosquitto_plugin_cleanup(void *userdata, struct mosquitto_opt *options, int option_count) +int mosquitto_plugin_cleanup(void * userdata, struct mosquitto_opt * options, int option_count) { - if(plugin_id) + if (plugin_id) { - mosquitto_callback_unregister(plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL); - mosquitto_callback_unregister(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL); - mosquitto_callback_unregister(plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_callback, NULL); + mosquitto_callback_unregister( + plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL); + mosquitto_callback_unregister(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL); + mosquitto_callback_unregister( + plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_callback, NULL); } return MOSQ_ERR_SUCCESS; } -int j7s_auth_basic_auth_callback(int event, void *event_data, void *userdata) +int j7s_auth_basic_auth_callback(int event, void * event_data, void * userdata) { - if(not authorizer) + if (not authorizer) { // Not sure this is possible. return MOSQ_ERR_AUTH; } - struct mosquitto_evt_basic_auth *auth_data = static_cast(event_data); + struct mosquitto_evt_basic_auth * auth_data = + static_cast(event_data); - if(!auth_data->username) + if (!auth_data->username) { // We need a username to do anything. return MOSQ_ERR_PLUGIN_DEFER; } - if(!auth_data->password) + if (!auth_data->password) { authorizer->add_unknown(std::string(auth_data->username)); return MOSQ_ERR_PLUGIN_DEFER; } - bool is_authed = authorizer->add(std::string(auth_data->password), std::string(auth_data->username)); + bool is_authed = + authorizer->add(std::string(auth_data->password), std::string(auth_data->username)); - if(is_authed) + if (is_authed) { return MOSQ_ERR_SUCCESS; } @@ -125,23 +135,24 @@ int j7s_auth_basic_auth_callback(int event, void *event_data, void *userdata) } } -int j7s_acl_check_callback(int event, void *event_data, void *userdata) +int j7s_acl_check_callback(int event, void * event_data, void * userdata) { - if(not authorizer) + if (not authorizer) { return MOSQ_ERR_ACL_DENIED; } - struct mosquitto_evt_acl_check *acl_data = static_cast(event_data); + struct mosquitto_evt_acl_check * acl_data = + static_cast(event_data); const std::string username = std::string(mosquitto_client_username(acl_data->client)); - if(authorizer->is_unknown(username)) + if (authorizer->is_unknown(username)) { return MOSQ_ERR_PLUGIN_DEFER; } - switch(acl_data->access) + switch (acl_data->access) { case MOSQ_ACL_SUBSCRIBE: return MOSQ_ERR_PLUGIN_DEFER; @@ -156,13 +167,12 @@ int j7s_acl_check_callback(int event, void *event_data, void *userdata) } } -int j7s_disconnect_callback(int event, void *event_data, void *userdata) +int j7s_disconnect_callback(int event, void * event_data, void * userdata) { - struct mosquitto_evt_disconnect *disconnect_data = static_cast(event_data); + struct mosquitto_evt_disconnect * disconnect_data = + static_cast(event_data); const std::string username = std::string(mosquitto_client_username(disconnect_data->client)); authorizer->logout(username); return MOSQ_ERR_SUCCESS; } - - diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..ecffa7a --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,101 @@ +// Copyright 2022 James Pace +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include + +std::optional read_key(const std::string & key_file) +{ + // Read key from file. + std::ifstream key_stream(key_file, std::ios::binary); + if (not key_stream) + { + return std::nullopt; + } + std::stringstream ss; + ss << key_stream.rdbuf(); + return ss.str(); +} + +std::tuple validate( + const std::string & token, + const std::string & username, + const std::string & issuer, + const std::string & pub_key) +{ + const auto decoded_token = jwt::decode(token); + + // Is the token valid? + const auto verifier = + jwt::verify().with_issuer(issuer).allow_algorithm(jwt::algorithm::rs256(pub_key)); + try + { + verifier.verify(decoded_token); + } + catch (jwt::error::token_verification_exception & exception) + { + std::cerr << exception.what() << std::endl; + return std::make_tuple(false, false); + } + auto claims = decoded_token.get_payload_claims(); + + // Check username matches. + if (not claims.contains("upn")) + { + std::cerr << "Missing upn." << std::endl; + return std::make_tuple(false, false); + } + if (claims["upn"].as_string() != username) + { + std::cerr << "Wrong username." << std::endl; + return std::make_tuple(false, false); + } + + // Check for mqtt-write claim value. + if (not(claims.contains("mqtt-write") and claims.contains("mqtt-read"))) + { + std::cerr << "Missing mqtt-write or mqtt-read." << std::endl; + return std::make_tuple(false, false); + } + + bool can_read = claims["mqtt-read"].as_bool(); + bool can_write = claims["mqtt-write"].as_bool(); + + return std::make_tuple(can_read, can_write); +} + +std::string gen_token( + const std::string & issuer, + const std::string & username, + const std::string & can_read, + const std::string & can_write, + const std::string & pub_key, + const std::string & priv_key, + const std::chrono::time_point & expr_time) +{ + const auto token = jwt::create() + .set_type("JWT") + .set_issuer(issuer) + .set_payload_claim("upn", jwt::claim(username)) + .set_payload_claim("mqtt-read", jwt::claim(can_read)) + .set_payload_claim("mqtt-write", jwt::claim(can_write)) + .set_expires_at(expr_time) + .sign(jwt::algorithm::rs256(pub_key, priv_key, "", "")); + + return token; +} diff --git a/test/authorizer_test.cpp b/test/authorizer_test.cpp new file mode 100644 index 0000000..c6ef489 --- /dev/null +++ b/test/authorizer_test.cpp @@ -0,0 +1,10 @@ +#include +#include "gtest/gtest.h" + +// Demonstrate some basic assertions. +TEST(AuthorizerTest, BasicAssertions) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +}