Initial implementation, plus test programs.

This commit is contained in:
jimmy committed 2021-11-17 02:01:08 +00:00
1 parent e559e1dbdb
commit f09ff2c9f7
12 files changed
+371 -19

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
#include <algorithm>
#include <jwp-plugin/AuthList.hpp>
AuthList::AuthList():
_allowedUsernames{}
{
}
void AuthList::add(const std::string& username)
{
// Is the username already in the list?
// If not add it.
const auto found = std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username);
if(found == std::end(_allowedUsernames))
{
_allowedUsernames.emplace_front(username);
}
}
void AuthList::remove(const std::string& username)
{
const auto found = std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username);
if(found != std::end(_allowedUsernames))
{
_allowedUsernames.remove(username);
}
}
bool AuthList::confirm(const std::string& username)
{
const auto found = std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username);
if(found != std::end(_allowedUsernames))
{
return true;
}
return false;
}
+114
View File
@@ -0,0 +1,114 @@
#include <jwp-plugin/Authorizer.hpp>
#include <string>
#include <jwt-cpp/jwt.h>
#include <jwp-plugin/AuthList.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <optional>
Authorizer::Authorizer(const std::string& pub_key, const std::string& issuer):
_pub_key{pub_key},
_issuer{issuer}
{
}
std::optional<std::string> 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)
{
_unknownList.add(username);
}
bool Authorizer::is_unknown(const std::string& username)
{
return _unknownList.confirm(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::cout << exception.what() << std::endl;
return false;
}
auto claims = decoded_token.get_payload_claims();
// Check username matches.
if(not claims.contains("upn"))
{
std::cout << "Missing upn." << std::endl;
return false;
}
if(claims["upn"].as_string() != username)
{
std::cout << "Wrong username." << std::endl;
return false;
}
// Check for mqtt-write claim value.
if(not (claims.contains("mqtt-write") and claims.contains("mqtt-read")))
{
std::cout << "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::cout << "Can't write or can't read." << std::endl;
return false;
}
if(can_write)
{
_writeList.add(username);
}
if(can_read)
{
_readList.add(username);
}
return true;
}
bool Authorizer::can_read(const std::string& username)
{
return _readList.confirm(username);
}
bool Authorizer::can_write(const std::string& username)
{
return _writeList.confirm(username);
}
void Authorizer::logout(const std::string& username)
{
_writeList.remove(username);
_readList.remove(username);
_unknownList.remove(username);
}
+85 -17
View File
@@ -4,6 +4,8 @@ extern "C" {
#include "mosquitto_plugin.h"
}
#include <string>
#include <memory>
#include <jwp-plugin/Authorizer.hpp>
// Stuff we're "exporting" for the dynamic loading.
extern "C" {
@@ -14,10 +16,12 @@ extern "C" {
// My functions
int jwp_auth_basic_auth_callback(int event, void *event_data, void *userdata);
int jwp_acl_check_callback(int event, void *event_data, void *userdata);
int jwp_disconnect_callback(int event, void *event_data, void *userdata);
// Mosquitto Globals
static mosquitto_plugin_id_t *plugin_id = nullptr;
static std::unique_ptr<Authorizer> authorizer = nullptr;
int mosquitto_plugin_version(int supported_version_count, const int *supported_versions)
@@ -36,8 +40,47 @@ int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **userdata, st
{
plugin_id = identifier;
if(option_count != 2)
{
return MOSQ_ERR_INVAL;
}
std::string public_key;
std::string issuer;
for(int index = 0; index < option_count; index++)
{
const auto key = std::string(options[index].key);
if(key == "public_key")
{
const auto key = Authorizer::read_key(std::string(options[index].value));
if(key)
{
public_key = *key;
}
else
{
return MOSQ_ERR_INVAL;
}
}
else if(key == "issuer")
{
issuer = std::string(options[index].value);
}
}
if(public_key.empty() or issuer.empty())
{
mosquitto_log_printf(MOSQ_LOG_ERR, "public_key or issue not set.");
return MOSQ_ERR_INVAL;
}
authorizer = std::make_unique<Authorizer>(public_key, issuer);
// Register the callbacks.
mosquitto_callback_register(plugin_id, MOSQ_EVT_BASIC_AUTH, jwp_auth_basic_auth_callback, NULL, NULL);
mosquitto_callback_register(plugin_id, MOSQ_EVT_ACL_CHECK, jwp_acl_check_callback, NULL, NULL);
mosquitto_callback_register(plugin_id, MOSQ_EVT_DISCONNECT, jwp_disconnect_callback, NULL, NULL);
// May want MOSQ_EVT_RELOAD as well.
return MOSQ_ERR_SUCCESS;
@@ -49,6 +92,7 @@ int mosquitto_plugin_cleanup(void *userdata, struct mosquitto_opt *options, int
{
mosquitto_callback_unregister(plugin_id, MOSQ_EVT_BASIC_AUTH, jwp_auth_basic_auth_callback, NULL);
mosquitto_callback_unregister(plugin_id, MOSQ_EVT_ACL_CHECK, jwp_acl_check_callback, NULL);
mosquitto_callback_unregister(plugin_id, MOSQ_EVT_DISCONNECT, jwp_disconnect_callback, NULL);
}
return MOSQ_ERR_SUCCESS;
@@ -56,45 +100,69 @@ int mosquitto_plugin_cleanup(void *userdata, struct mosquitto_opt *options, int
int jwp_auth_basic_auth_callback(int event, void *event_data, void *userdata)
{
if(not authorizer)
{
// Not sure this is possible.
return MOSQ_ERR_AUTH;
}
struct mosquitto_evt_basic_auth *auth_data = static_cast<struct mosquitto_evt_basic_auth*>(event_data);
if(!auth_data->username or !auth_data->password)
{
mosquitto_log_printf(MOSQ_LOG_ERR, "No username or password.");
authorizer->add_unknown(std::string(auth_data->username));
return MOSQ_ERR_PLUGIN_DEFER;
}
mosquitto_log_printf(MOSQ_LOG_ERR, "Username: %s Password: %s",
auth_data->username, auth_data->password);
bool is_authed = authorizer->add(std::string(auth_data->password), std::string(auth_data->username));
return MOSQ_ERR_SUCCESS; // MOSQ_ERR_AUTH;
if(is_authed)
{
return MOSQ_ERR_SUCCESS;
}
else
{
return MOSQ_ERR_AUTH;
}
}
int jwp_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 = static_cast<struct mosquitto_evt_acl_check *>(event_data);
std::string event_name = "none";
const std::string username = std::string(mosquitto_client_username(acl_data->client));
if(authorizer->is_unknown(username))
{
return MOSQ_ERR_PLUGIN_DEFER;
}
switch(acl_data->access)
{
case MOSQ_ACL_SUBSCRIBE:
event_name = "subscribe";
break;
return MOSQ_ERR_PLUGIN_DEFER;
case MOSQ_ACL_UNSUBSCRIBE:
event_name = "unsubscribe";
break;
return MOSQ_ERR_PLUGIN_DEFER;
case MOSQ_ACL_WRITE:
event_name = "write";
break;
return (authorizer->can_write(username) ? MOSQ_ERR_SUCCESS : MOSQ_ERR_ACL_DENIED);
case MOSQ_ACL_READ:
event_name = "read";
break;
return (authorizer->can_read(username) ? MOSQ_ERR_SUCCESS : MOSQ_ERR_ACL_DENIED);
default:
return MOSQ_ERR_ACL_DENIED;
}
}
mosquitto_log_printf(MOSQ_LOG_ERR, "Topic: %s Event: %s",
acl_data->topic, event_name.c_str());
return MOSQ_ERR_SUCCESS; // MOSQ_ERR_ACL_DENIED;
int jwp_disconnect_callback(int event, void *event_data, void *userdata)
{
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));
authorizer->logout(username);
return MOSQ_ERR_SUCCESS;
}