From 0b4a73e2b7001c5ee158ea44213d7e24c7c5f42c Mon Sep 17 00:00:00 2001 From: James Pace Date: Fri, 25 Mar 2022 02:23:28 +0000 Subject: [PATCH] Add tests. Cleanup formatting. --- .clang-format | 2 +- CMakeLists.txt | 10 +++-- include/j7s-plugin/j7s-plugin.h | 16 +++---- include/j7s-plugin/utils.h | 17 +++----- src/AuthList.cpp | 10 ++--- src/Authorizer.cpp | 45 +++++++++---------- src/gen-token.cpp | 12 ++--- src/j7s-plugin.cpp | 30 ++++++------- src/utils.cpp | 45 +++++++++++-------- test/token_test.cpp | 77 ++++++++++++++++++++++++++++----- 10 files changed, 159 insertions(+), 105 deletions(-) diff --git a/.clang-format b/.clang-format index 1e0103a..309c979 100644 --- a/.clang-format +++ b/.clang-format @@ -16,6 +16,6 @@ ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 DerivePointerAlignment: false IndentWidth: 4 -PointerAlignment: Middle +PointerAlignment: Right ReflowComments: false ... diff --git a/CMakeLists.txt b/CMakeLists.txt index d45edc2..cb436fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,11 @@ if(BUILD_TESTING) enable_testing() - add_executable(authorizer_test test/authorizer_test.cpp) - target_link_libraries(authorizer_test GTest::gtest_main) - gtest_discover_tests(authorizer_test) + add_executable(token_test test/token_test.cpp) + target_include_directories(token_test PUBLIC + $ + $ + ) + target_link_libraries(token_test utils GTest::gtest_main) + gtest_discover_tests(token_test) endif() diff --git a/include/j7s-plugin/j7s-plugin.h b/include/j7s-plugin/j7s-plugin.h index 4894a15..e65ff6b 100644 --- a/include/j7s-plugin/j7s-plugin.h +++ b/include/j7s-plugin/j7s-plugin.h @@ -25,15 +25,15 @@ extern "C" // 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_version(int supported_version_count, const int *supported_versions); int mosquitto_plugin_init( - mosquitto_plugin_id_t * identifier, - void ** userdata, - struct mosquitto_opt * options, + 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); + 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 index dfa2231..e245fe2 100644 --- a/include/j7s-plugin/utils.h +++ b/include/j7s-plugin/utils.h @@ -17,17 +17,14 @@ #include #include -std::optional read_key(const std::string & key_file); +std::optional read_key(const std::string &key_file); std::tuple> validate( - const std::string & token, - const std::string & username, - const std::string & pub_key); + const std::string &token, const std::string &username, const std::string &pub_key); std::string gen_token( - const std::string & issuer, - const std::string & username, - const std::string & pub_key, - const std::string & priv_key, - const std::chrono::time_point & issue_time, - const std::chrono::time_point & expr_time); + const std::string &username, + const std::string &pub_key, + const std::string &priv_key, + const std::chrono::time_point &issue_time, + const std::chrono::time_point &expr_time); diff --git a/src/AuthList.cpp b/src/AuthList.cpp index 42bc61e..8081748 100644 --- a/src/AuthList.cpp +++ b/src/AuthList.cpp @@ -16,25 +16,25 @@ AuthList::AuthList() : _map{} {} -void AuthList::add(const std::string & username, const time_T& expr_time) +void AuthList::add(const std::string &username, const time_T &expr_time) { // Add the user to the list or update it's expr time if // it's already there. _map[username] = expr_time; } -void AuthList::remove(const std::string & username) +void AuthList::remove(const std::string &username) { // Remove the user _map.erase(username); } -bool AuthList::confirm(const std::string & username) +bool AuthList::confirm(const std::string &username) { // Is the user in the map? const auto iter = _map.find(username); - if(iter == _map.end()) + if (iter == _map.end()) { return false; } @@ -42,7 +42,7 @@ bool AuthList::confirm(const std::string & username) // Has the token expired? const auto now = std::chrono::system_clock::now(); const auto expr_time = std::get<1>(*iter); - if(now < expr_time) + if (now < expr_time) { return true; } diff --git a/src/Authorizer.cpp b/src/Authorizer.cpp index 20998fe..d701814 100644 --- a/src/Authorizer.cpp +++ b/src/Authorizer.cpp @@ -13,30 +13,33 @@ // limitations under the License. #include +#include #include #include #include - #include -#include // Util. -std::tuple checkACL(const std::string& user, const YAML::Node& aclFile); -std::optional getKey(const std::string& user, const YAML::Node& keyFile); +std::tuple checkACL(const std::string &user, const YAML::Node &aclFile); +std::optional getKey(const std::string &user, const YAML::Node &keyFile); // Class implementation. -Authorizer::Authorizer( - const std::string & keyFilePath, const std::string & aclFilePath) : +Authorizer::Authorizer(const std::string &keyFilePath, const std::string &aclFilePath) : _keyFile{keyFilePath}, _aclFile{aclFilePath} { } - -bool Authorizer::add(const std::string & token, const std::string & username) +bool Authorizer::add(const std::string &token, const std::string &username) { + // We should protect from this already. + if (token.empty() or username.empty()) + { + return false; + } + const auto key = getKey(username, _keyFile); - if(not key) + if (not key) { std::cerr << "Could not read key for user." << std::endl; return false; @@ -64,41 +67,39 @@ 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); } -void Authorizer::add_unknown(const std::string & username) +void Authorizer::add_unknown(const std::string &username) { _unknownList.add(username, time_T::max()); } -bool Authorizer::is_unknown(const std::string & username) +bool Authorizer::is_unknown(const std::string &username) { return (username.empty() or _unknownList.confirm(username)); } - // Util. -std::tuple checkACL(const std::string& user, const YAML::Node& aclFile) +std::tuple checkACL(const std::string &user, const YAML::Node &aclFile) { // TODO: Make sure default exists. YAML::Node userDict; - if(aclFile[user]) + if (aclFile[user]) { userDict = aclFile[user]; } @@ -109,11 +110,11 @@ std::tuple checkACL(const std::string& user, const YAML::Node& aclFi bool can_read = false; bool can_write = false; - if(userDict["can_read"] and userDict["can_read"].as()) + if (userDict["can_read"] and userDict["can_read"].as()) { can_read = true; } - if(userDict["can_write"] and userDict["can_write"].as()) + if (userDict["can_write"] and userDict["can_write"].as()) { can_write = true; } @@ -121,11 +122,11 @@ std::tuple checkACL(const std::string& user, const YAML::Node& aclFi return std::make_tuple(can_read, can_write); } -std::optional getKey(const std::string& user, const YAML::Node& keyFile) +std::optional getKey(const std::string &user, const YAML::Node &keyFile) { // TODO: Make sure default exists. std::filesystem::path pathToKey; - if(keyFile[user]) + if (keyFile[user]) { pathToKey = std::filesystem::path(keyFile[user].as()); } diff --git a/src/gen-token.cpp b/src/gen-token.cpp index b6f0285..9e42514 100644 --- a/src/gen-token.cpp +++ b/src/gen-token.cpp @@ -18,13 +18,12 @@ #include #include -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("--valid-days") .required() @@ -41,7 +40,7 @@ int main(int argc, char * argv[]) { program.parse_args(argc, argv); } - catch (const std::runtime_error & err) + catch (const std::runtime_error &err) { std::cerr << err.what() << std::endl; std::cerr << program; @@ -67,12 +66,7 @@ int main(int argc, char * argv[]) now + std::chrono::days(std::stoi(program.get("--valid-days"))); const auto token = gen_token( - program.get("--issuer"), - program.get("--username"), - pub_key.value(), - priv_key.value(), - now, - expr_time); + program.get("--username"), pub_key.value(), priv_key.value(), now, expr_time); std::cout << token; diff --git a/src/j7s-plugin.cpp b/src/j7s-plugin.cpp index 5acd108..96976e5 100644 --- a/src/j7s-plugin.cpp +++ b/src/j7s-plugin.cpp @@ -12,20 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. #include - -#include - #include +#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++) { @@ -38,9 +36,9 @@ int mosquitto_plugin_version(int supported_version_count, const int * supported_ } int mosquitto_plugin_init( - mosquitto_plugin_id_t * identifier, - void ** userdata, - struct mosquitto_opt * options, + mosquitto_plugin_id_t *identifier, + void **userdata, + struct mosquitto_opt *options, int option_count) { plugin_id = identifier; @@ -93,7 +91,7 @@ int mosquitto_plugin_init( 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) { @@ -107,7 +105,7 @@ int mosquitto_plugin_cleanup(void * userdata, struct mosquitto_opt * options, in 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) { @@ -115,7 +113,7 @@ int j7s_auth_basic_auth_callback(int event, void * event_data, void * userdata) return MOSQ_ERR_AUTH; } - struct mosquitto_evt_basic_auth * auth_data = + struct mosquitto_evt_basic_auth *auth_data = static_cast(event_data); if (!auth_data->username) @@ -142,14 +140,14 @@ 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) { return MOSQ_ERR_ACL_DENIED; } - struct mosquitto_evt_acl_check * acl_data = + struct mosquitto_evt_acl_check *acl_data = static_cast(event_data); const std::string username = std::string(mosquitto_client_username(acl_data->client)); @@ -174,9 +172,9 @@ 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 = + struct mosquitto_evt_disconnect *disconnect_data = static_cast(event_data); const std::string username = std::string(mosquitto_client_username(disconnect_data->client)); diff --git a/src/utils.cpp b/src/utils.cpp index c63230c..0ffb8ed 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -18,8 +18,9 @@ #include #include #include +#include -std::optional read_key(const std::string & key_file) +std::optional read_key(const std::string &key_file) { // Read key from file. std::ifstream key_stream(key_file, std::ios::binary); @@ -29,26 +30,34 @@ std::optional read_key(const std::string & key_file) } std::stringstream ss; ss << key_stream.rdbuf(); - return ss.str(); + const std::string key(ss.str()); + if (key.empty()) + { + return std::nullopt; + } + + return key; } std::tuple> validate( - const std::string & token, - const std::string & username, - const std::string & pub_key) + const std::string &token, const std::string &username, const std::string &pub_key) { + if (token.empty() or username.empty() or pub_key.empty()) + { + return std::make_tuple(false, std::chrono::system_clock::now()); + } + const auto decoded_token = jwt::decode(token); - // Is the token valid? - const auto verifier = - jwt::verify().allow_algorithm(jwt::algorithm::rs256(pub_key)); try { + // Is the token valid? + const auto verifier = jwt::verify().allow_algorithm(jwt::algorithm::rs256(pub_key)); verifier.verify(decoded_token); } - catch (jwt::error::token_verification_exception & exception) + catch (std::system_error &exception) { - std::cerr << exception.what() << std::endl; + std::cerr << "Token Verification Failed: " << exception.what() << std::endl; return std::make_tuple(false, std::chrono::system_clock::now()); } auto claims = decoded_token.get_payload_claims(); @@ -71,14 +80,14 @@ std::tuple> validate( std::cerr << "Missing mqtt claim." << std::endl; return std::make_tuple(false, std::chrono::system_clock::now()); } - if(not claims["mqtt"].as_bool()) + if (not(claims["mqtt"].as_string() == "true")) { std::cerr << "Not claiming can do mqtt." << std::endl; return std::make_tuple(false, std::chrono::system_clock::now()); } // Do we have an expiration time? - if(not claims.contains("exp")) + if (not claims.contains("exp")) { std::cerr << "Missing expiration time claim." << std::endl; return std::make_tuple(false, std::chrono::system_clock::now()); @@ -88,16 +97,14 @@ std::tuple> validate( } std::string gen_token( - const std::string & issuer, - const std::string & username, - const std::string & pub_key, - const std::string & priv_key, - const std::chrono::time_point & issue_time, - const std::chrono::time_point & expr_time) + const std::string &username, + const std::string &pub_key, + const std::string &priv_key, + const std::chrono::time_point &issue_time, + 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", jwt::claim(std::string("true"))) .set_issued_at(issue_time) diff --git a/test/token_test.cpp b/test/token_test.cpp index 21911b1..7ec9516 100644 --- a/test/token_test.cpp +++ b/test/token_test.cpp @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. #include + +#include +#include + #include "gtest/gtest.h" -constexpr std::string priv_key_a = +const std::string priv_key_a = R"(-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+ouwDpYOWDEyM nJhwejOn+boDxw4ntiOR3kRzIANuJrbEPf3UJFL+SPPzzY7NU1A6XPz/NAccbvfn @@ -42,9 +46,8 @@ mnjcxB3ZtRoyFWvfYx9wD3/rV4sMtiIoorNgtJMCgYABDGH571InLE9HMO1+Czmp zyvcbTAq8GiN0G4Rok95+THfa726N6BcmkZUK1xWaleO6xNGrDsBghfmgw629Ujk UJ73ERYyATbA4GHM9f3dbje8pd2SFa4xF+0Xp09qY380aJrZSWsklBZPUmYiU6+W i2MlHfF+44rBO9igkUjQKA== ------END PRIVATE KEY-----)" - -constexpr std::string pub_key_a = +-----END PRIVATE KEY-----)"; +const std::string pub_key_a = R"(-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqLsA6WDlgxMjJyYcHoz p/m6A8cOJ7Yjkd5EcyADbia2xD391CRS/kjz882OzVNQOlz8/zQHHG7353O/HY9d @@ -54,7 +57,8 @@ m0MrwLnIGkwyp1O9r4jTsaoPL+WxhflrU4ysHs5mLckPGe3aRkGzC9bZExEME6uZ 9Hfn9zmE+Y53zD0qSiYmDZS6sQiTxozXEkcY880bf3EWM4QD364jv35GBrUHCzQe XQIDAQAB -----END PUBLIC KEY-----)"; -constexpr std::string priv_key_b = + +const std::string priv_key_b = R"(-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCYq8QNOXZRoAid R7cKE9byr+9WekPMNDNkaKTjRUoXj8lUgno3y5tIDEIqhcv4thTLAxzQD4N+bVA3 @@ -83,7 +87,7 @@ t4/SVFXBRLzse8AB3V6qSEwgCaUfeuj0Qq93nrkTIodHFWXuFoQTgQrA29VWbK6x PSwjdVNwYES+Hg+LbXP8Fo+u5sGhcWLzWdmFp3UdUm5Mv76Oo+MriZNnS4RQiX0+ Y8PiIt3YYCsowmchtEggaQ== -----END PRIVATE KEY-----)"; -constexpr std::string pub_key_b = +const std::string pub_key_b = R"(-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmKvEDTl2UaAInUe3ChPW 8q/vVnpDzDQzZGik40VKF4/JVIJ6N8ubSAxCKoXL+LYUywMc0A+Dfm1QN1xdWTH5 @@ -94,11 +98,60 @@ wmwOQRQS2h5xR1Z/o6HBIGnzllI67mHMK1HGbgq/DsvCi6s+sOV7Wvwe8dC8sd87 xwIDAQAB -----END PUBLIC KEY-----)"; +using time_T = std::chrono::time_point; -// Demonstrate some basic assertions. -TEST(TokenTest, TwoWay) { - constexpr std::string issuer = "james-keycloak"; - constexpr std::string username = "james"; - constexpr - const auto token = gen_token( +TEST(TokenTest, SimpleTwoWay) +{ + const std::string username = "james"; + const time_T now = std::chrono::system_clock::now(); + const time_T expire = now + std::chrono::seconds(1); + + const auto token = gen_token(username, pub_key_a, priv_key_a, now, expire); + + auto [valid, end] = validate(token, username, pub_key_a); + + std::time_t expire_time = std::chrono::system_clock::to_time_t(expire); + std::time_t end_time = std::chrono::system_clock::to_time_t(end); + + EXPECT_TRUE(valid); + EXPECT_EQ(end_time, expire_time); +} + +TEST(TokenTest, InvalidUsername) +{ + const std::string username = "james"; + const time_T now = std::chrono::system_clock::now(); + const time_T expire = now + std::chrono::seconds(1); + const auto token = gen_token(username, pub_key_a, priv_key_a, now, expire); + + const std::string notjames = "not_james"; + const auto [valid, end] = validate(token, notjames, pub_key_a); + + EXPECT_FALSE(valid); +} + +TEST(TokenTest, WrongKey) +{ + const std::string username = "james"; + const time_T now = std::chrono::system_clock::now(); + const time_T expire = now + std::chrono::seconds(1); + const auto token = gen_token(username, pub_key_a, priv_key_a, now, expire); + + const auto [valid, end] = validate(token, username, pub_key_b); + + EXPECT_FALSE(valid); +} + +TEST(TokenTest, NonsenseKey) +{ + const std::string username = "james"; + const time_T now = std::chrono::system_clock::now(); + const time_T expire = now + std::chrono::seconds(1); + const auto token = gen_token(username, pub_key_a, priv_key_a, now, expire); + + const std::string nonsenseKey = "lslslslsl"; + + const auto [valid, end] = validate(token, username, nonsenseKey); + + EXPECT_FALSE(valid); }