Mass refactor. Clang-format. Start adding tests. Use FetchContent.
This commit is contained in:
parent
c76ff553cc
commit
3309b19c2e
|
|
@ -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
|
||||||
|
...
|
||||||
|
|
@ -1,34 +1,39 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# https://github.com/Sarcasm/cmake-superbuild/
|
|
||||||
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)
|
project(j7s-mosquitto-plugin)
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(jwt-cpp)
|
include(external-deps.cmake)
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -15,16 +15,22 @@
|
||||||
// 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.h"
|
||||||
#include "mosquitto_broker.h"
|
#include "mosquitto_broker.h"
|
||||||
#include "mosquitto_plugin.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_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_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);
|
int mosquitto_plugin_cleanup(void * userdata, struct mosquitto_opt * options, int option_count);
|
||||||
}
|
}
|
||||||
// My functions
|
// My functions
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -11,13 +11,10 @@
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
|
|
@ -42,7 +39,8 @@ void AuthList::remove(const std::string& 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 =
|
||||||
|
std::find(std::begin(_allowedUsernames), std::end(_allowedUsernames), username);
|
||||||
if (found != std::end(_allowedUsernames))
|
if (found != std::end(_allowedUsernames))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -11,34 +11,17 @@
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// 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);
|
_unknownList.add(username);
|
||||||
|
|
@ -51,44 +34,7 @@ bool Authorizer::is_unknown(const std::string& 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);
|
||||||
|
|
||||||
// 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))
|
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;
|
||||||
|
|
@ -123,4 +69,3 @@ void Authorizer::logout(const std::string& username)
|
||||||
_readList.remove(username);
|
_readList.remove(username);
|
||||||
_unknownList.remove(username);
|
_unknownList.remove(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 <j7s-plugin/utils.h>
|
||||||
|
|
||||||
#include <argparse/argparse.hpp>
|
#include <argparse/argparse.hpp>
|
||||||
#include <jwt-cpp/jwt.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#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.");
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@
|
||||||
// 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;
|
||||||
|
|
@ -32,11 +33,14 @@ int mosquitto_plugin_version(int supported_version_count, const int *supported_v
|
||||||
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);
|
||||||
|
|
@ -72,9 +76,11 @@ 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(
|
||||||
|
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_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_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;
|
||||||
|
|
@ -84,9 +90,11 @@ int mosquitto_plugin_cleanup(void *userdata, struct mosquitto_opt *options, int
|
||||||
{
|
{
|
||||||
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_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_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_DISCONNECT, j7s_disconnect_callback, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MOSQ_ERR_SUCCESS;
|
return MOSQ_ERR_SUCCESS;
|
||||||
|
|
@ -100,7 +108,8 @@ int j7s_auth_basic_auth_callback(int event, void *event_data, void *userdata)
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
|
@ -113,7 +122,8 @@ int j7s_auth_basic_auth_callback(int event, void *event_data, void *userdata)
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
|
@ -132,7 +142,8 @@ int j7s_acl_check_callback(int event, void *event_data, void *userdata)
|
||||||
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));
|
||||||
|
|
||||||
|
|
@ -158,11 +169,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue