Mass refactor. Clang-format. Start adding tests. Use FetchContent.

This commit is contained in:
jimmy committed 2022-03-24 00:20:21 +00:00
1 parent c76ff553cc
commit 3309b19c2e
19 files changed
+310 -223

No files matched your search

+21
View File
@@ -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
...
+21 -16
View File
@@ -1,34 +1,39 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(j7s-mosquitto-plugin)
# https://github.com/Sarcasm/cmake-superbuild/ include(external-deps.cmake)
option (USE_SUPERBUILD "Whether or not a superbuild should be invoked" ON)
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) find_package(OpenSSL)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
add_library(utils SHARED src/utils.cpp)
target_include_directories(utils PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(utils OpenSSL::Crypto jwt-cpp)
add_library(j7s-plugin SHARED src/j7s-plugin.cpp src/AuthList.cpp src/Authorizer.cpp) add_library(j7s-plugin SHARED src/j7s-plugin.cpp src/AuthList.cpp src/Authorizer.cpp)
target_include_directories(j7s-plugin PUBLIC target_include_directories(j7s-plugin PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
${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) add_executable(gen-token src/gen-token.cpp)
target_include_directories(gen-token PUBLIC target_include_directories(gen-token PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
${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()
+1 -1
View File
@@ -4,7 +4,7 @@ Authentication using JWTs for the mosquitto mqtt broker.
## Dependencies ## 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 ## Generating offline keys
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
+11 -28
View File
@@ -1,39 +1,22 @@
include (ExternalProject) include(FetchContent)
set (DEPENDENCIES) FetchContent_Declare(googletest
set (EXTRA_CMAKE_ARGS) 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) FetchContent_Declare(jwt-cpp
ExternalProject_Add(ep_jwt-cpp
PREFIX ep_jwt-cpp
GIT_REPOSITORY "https://github.com/Thalhammer/jwt-cpp.git" GIT_REPOSITORY "https://github.com/Thalhammer/jwt-cpp.git"
GIT_TAG "v0.5.2" GIT_TAG "v0.5.2"
GIT_SHALLOW "True" 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) FetchContent_Declare(argparse
ExternalProject_Add(ep_argparse
PREFIX ep_argparse
GIT_REPOSITORY "https://github.com/p-ranav/argparse.git" GIT_REPOSITORY "https://github.com/p-ranav/argparse.git"
GIT_TAG "v2.2" GIT_TAG "v2.2"
GIT_SHALLOW "True" 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)
+17 -11
View File
@@ -15,19 +15,25 @@
// Mosquitto authentication plugin that using Authorizer to authorize // Mosquitto authentication plugin that using Authorizer to authorize
// users using jwts. // users using jwts.
extern "C" { extern "C"
#include "mosquitto.h" {
#include "mosquitto_broker.h" #include "mosquitto.h"
#include "mosquitto_plugin.h" #include "mosquitto_broker.h"
#include "mosquitto_plugin.h"
} }
// Stuff we're "exporting" for the dynamic loading. // Stuff we're "exporting" for the dynamic loading.
extern "C" { 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_version(int supported_version_count, const int * supported_versions);
int mosquitto_plugin_cleanup(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);
int mosquitto_plugin_cleanup(void * userdata, struct mosquitto_opt * options, int option_count);
} }
// My functions // My functions
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);
int j7s_acl_check_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_disconnect_callback(int event, void * event_data, void * userdata);
+36
View File
@@ -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 <chrono>
#include <optional>
#include <string>
#include <tuple>
std::optional<std::string> read_key(const std::string & key_file);
std::tuple<bool, bool> 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<std::chrono::system_clock> & expr_time);
+10 -12
View File
@@ -11,39 +11,37 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <j7s-plugin/AuthList.hpp>
#include <algorithm> #include <algorithm>
#include <j7s-plugin/AuthList.hpp>
AuthList::AuthList(): AuthList::AuthList() : _allowedUsernames{} {}
_allowedUsernames{}
{
}
void AuthList::add(const std::string& username) void AuthList::add(const std::string & username)
{ {
// Is the username already in the list? // Is the username already in the list?
// If not add it. // If not add it.
if(not confirm(username)) if (not confirm(username))
{ {
_allowedUsernames.emplace_front(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 the user in the list?
// Is so, remove it, // Is so, remove it,
if(confirm(username)) if (confirm(username))
{ {
_allowedUsernames.remove(username); _allowedUsernames.remove(username);
} }
} }
bool AuthList::confirm(const std::string& username) bool AuthList::confirm(const std::string & username)
{ {
// Is the user in the list? // Is the user in the list?
const auto found = std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username); const auto found =
if(found != std::end(_allowedUsernames)) std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username);
if (found != std::end(_allowedUsernames))
{ {
return true; return true;
} }
+15 -70
View File
@@ -11,95 +11,41 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <j7s-plugin/Authorizer.hpp> #include <j7s-plugin/utils.h>
#include <j7s-plugin/AuthList.hpp>
#include <jwt-cpp/jwt.h>
#include <iostream> #include <iostream>
#include <fstream> #include <j7s-plugin/AuthList.hpp>
#include <sstream> #include <j7s-plugin/Authorizer.hpp>
Authorizer::Authorizer(const std::string& pub_key, const std::string& issuer): Authorizer::Authorizer(const std::string & pub_key, const std::string & issuer) :
_pub_key{pub_key}, _pub_key{pub_key}, _issuer{issuer}
_issuer{issuer}
{ {
} }
std::optional<std::string> Authorizer::read_key(const std::string& key_file) void Authorizer::add_unknown(const std::string & username)
{
// 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)
{ {
_unknownList.add(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)); 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); const auto [can_read, can_write] = validate(token, username, _issuer, _pub_key);
if (not(can_write or can_read))
// 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))
{ {
std::cerr << "Can't write or can't read." << std::endl; std::cerr << "Can't write or can't read." << std::endl;
return false; return false;
} }
if(can_write) if (can_write)
{ {
_writeList.add(username); _writeList.add(username);
} }
if(can_read) if (can_read)
{ {
_readList.add(username); _readList.add(username);
} }
@@ -107,20 +53,19 @@ bool Authorizer::add(const std::string& token, const std::string& username)
return true; return true;
} }
bool Authorizer::can_read(const std::string& username) bool Authorizer::can_read(const std::string & username)
{ {
return _readList.confirm(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); return _writeList.confirm(username);
} }
void Authorizer::logout(const std::string& username) void Authorizer::logout(const std::string & username)
{ {
_writeList.remove(username); _writeList.remove(username);
_readList.remove(username); _readList.remove(username);
_unknownList.remove(username); _unknownList.remove(username);
} }
+16 -44
View File
@@ -12,35 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <argparse/argparse.hpp> #include <j7s-plugin/utils.h>
#include <jwt-cpp/jwt.h>
#include <iostream> #include <argparse/argparse.hpp>
#include <chrono> #include <chrono>
#include <optional>
#include <string>
#include <fstream>
#include <sstream>
#include <filesystem> #include <filesystem>
std::optional<std::string> 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"); argparse::ArgumentParser program("gen-token", "0.0.0");
program.add_argument("--pub-key") program.add_argument("--pub-key").required().help("Pub key of signer.");
.required() program.add_argument("--priv-key").required().help("Private key of signer.");
.help("Pub key of signer."); program.add_argument("--issuer").required().help("Issuer to assign to signed key.");
program.add_argument("--priv-key") program.add_argument("--username").required().help("Username assigned to 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") program.add_argument("--valid-days")
.required() .required()
.help("Days from now until the token will be valid."); .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<std::string>("--pub-key"); const auto pub_key_file = program.get<std::string>("--pub-key");
const auto pub_key = read_key(std::filesystem::absolute(pub_key_file)); 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; std::cerr << "Could not open key!" << std::endl;
return -2; return -2;
@@ -80,29 +65,16 @@ int main(int argc, char *argv[])
const auto expr_time = std::chrono::system_clock::now() + const auto expr_time = std::chrono::system_clock::now() +
std::chrono::days(std::stoi(program.get<std::string>("--valid-days"))); std::chrono::days(std::stoi(program.get<std::string>("--valid-days")));
const auto token = jwt::create() const auto token = gen_token(
.set_type("JWT") program.get<std::string>("--issuer"),
.set_issuer(program.get<std::string>("--issuer")) program.get<std::string>("--username"),
.set_payload_claim("upn", jwt::claim(program.get<std::string>("--username"))) can_read,
.set_payload_claim("mqtt-write", jwt::claim(can_read)) can_write,
.set_payload_claim("mqtt-read", jwt::claim(can_write)) pub_key.value(),
.set_expires_at(expr_time) priv_key.value(),
.sign(jwt::algorithm::rs256(pub_key.value(), priv_key.value(), "", "")); expr_time);
std::cout << token; std::cout << token;
return 0; return 0;
} }
std::optional<std::string> 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();
}
+51 -41
View File
@@ -12,32 +12,36 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <j7s-plugin/j7s-plugin.h> #include <j7s-plugin/j7s-plugin.h>
#include <j7s-plugin/Authorizer.hpp> #include <j7s-plugin/Authorizer.hpp>
#include <string>
#include <memory> #include <memory>
#include <string>
// Mosquitto Globals // Mosquitto Globals
static mosquitto_plugin_id_t *plugin_id = nullptr; static mosquitto_plugin_id_t * plugin_id = nullptr;
static std::unique_ptr<Authorizer> authorizer = nullptr; static std::unique_ptr<Authorizer> 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 5;
} }
} }
return -1; 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; plugin_id = identifier;
if (option_count != 2)
if(option_count != 2)
{ {
mosquitto_log_printf(MOSQ_LOG_ERR, "Missing an option. Found: %d", option_count); mosquitto_log_printf(MOSQ_LOG_ERR, "Missing an option. Found: %d", option_count);
return MOSQ_ERR_INVAL; 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 public_key;
std::string issuer; 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); 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)); 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."); mosquitto_log_printf(MOSQ_LOG_ERR, "Could not read public key.");
return MOSQ_ERR_INVAL; return MOSQ_ERR_INVAL;
} }
public_key = *key; public_key = *key;
} }
else if(key == "issuer") else if (key == "issuer")
{ {
issuer = std::string(options[index].value); issuer = std::string(options[index].value);
if(issuer.empty()) if (issuer.empty())
{ {
mosquitto_log_printf(MOSQ_LOG_ERR, "issuer not set."); mosquitto_log_printf(MOSQ_LOG_ERR, "issuer not set.");
return MOSQ_ERR_INVAL; return MOSQ_ERR_INVAL;
@@ -72,50 +76,56 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, st
authorizer = std::make_unique<Authorizer>(public_key, issuer); authorizer = std::make_unique<Authorizer>(public_key, issuer);
// Register the callbacks. // Register the callbacks.
mosquitto_callback_register(plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL, NULL); mosquitto_callback_register(
mosquitto_callback_register(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL, NULL); plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL, NULL);
mosquitto_callback_register(plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_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. // May want MOSQ_EVT_RELOAD as well.
return MOSQ_ERR_SUCCESS; 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(
mosquitto_callback_unregister(plugin_id, MOSQ_EVT_ACL_CHECK, j7s_acl_check_callback, NULL); plugin_id, MOSQ_EVT_BASIC_AUTH, j7s_auth_basic_auth_callback, NULL);
mosquitto_callback_unregister(plugin_id, MOSQ_EVT_DISCONNECT, j7s_disconnect_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; 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. // Not sure this is possible.
return MOSQ_ERR_AUTH; return MOSQ_ERR_AUTH;
} }
struct mosquitto_evt_basic_auth *auth_data = static_cast<struct mosquitto_evt_basic_auth*>(event_data); struct mosquitto_evt_basic_auth * auth_data =
static_cast<struct mosquitto_evt_basic_auth *>(event_data);
if(!auth_data->username) if (!auth_data->username)
{ {
// We need a username to do anything. // We need a username to do anything.
return MOSQ_ERR_PLUGIN_DEFER; return MOSQ_ERR_PLUGIN_DEFER;
} }
if(!auth_data->password) if (!auth_data->password)
{ {
authorizer->add_unknown(std::string(auth_data->username)); authorizer->add_unknown(std::string(auth_data->username));
return MOSQ_ERR_PLUGIN_DEFER; 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; 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; return MOSQ_ERR_ACL_DENIED;
} }
struct mosquitto_evt_acl_check *acl_data = static_cast<struct mosquitto_evt_acl_check *>(event_data); struct mosquitto_evt_acl_check * acl_data =
static_cast<struct mosquitto_evt_acl_check *>(event_data);
const std::string username = std::string(mosquitto_client_username(acl_data->client)); 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; return MOSQ_ERR_PLUGIN_DEFER;
} }
switch(acl_data->access) switch (acl_data->access)
{ {
case MOSQ_ACL_SUBSCRIBE: case MOSQ_ACL_SUBSCRIBE:
return MOSQ_ERR_PLUGIN_DEFER; 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<struct mosquitto_evt_disconnect*>(event_data); struct mosquitto_evt_disconnect * disconnect_data =
static_cast<struct mosquitto_evt_disconnect *>(event_data);
const std::string username = std::string(mosquitto_client_username(disconnect_data->client)); const std::string username = std::string(mosquitto_client_username(disconnect_data->client));
authorizer->logout(username); authorizer->logout(username);
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
} }
+101
View File
@@ -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 <j7s-plugin/utils.h>
#include <jwt-cpp/jwt.h>
#include <fstream>
#include <iostream>
#include <sstream>
std::optional<std::string> 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<bool, bool> 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<std::chrono::system_clock> & 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;
}
+10
View File
@@ -0,0 +1,10 @@
#include <j7s-plugin/Authorizer.hpp>
#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);
}