Skip to content

Commit

Permalink
Merge pull request #76 from mlibrary/http-authorization
Browse files Browse the repository at this point in the history
Authorization: Bearer Token
  • Loading branch information
botimer authored May 21, 2024
2 parents 380590c + 11b13a4 commit c3afac3
Show file tree
Hide file tree
Showing 26 changed files with 183 additions and 49 deletions.
4 changes: 3 additions & 1 deletion apache/client/include/lauth/api_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
namespace mlibrary::lauth {
class ApiClient {
public:
ApiClient(const std::string& url) : client(std::make_unique<HttpClient>(url)) {};
ApiClient(const std::string& url, const std::string& bearerToken) : client(std::make_unique<HttpClient>(url)), bearerToken(bearerToken) {};
ApiClient(std::unique_ptr<HttpClient>&& client) : client(std::move(client)) {};
ApiClient(std::unique_ptr<HttpClient>&& client, const std::string& bearerToken) : client(std::move(client)), bearerToken(bearerToken) {};
ApiClient(const ApiClient&) = delete;
ApiClient& operator=(const ApiClient&) = delete;
ApiClient(ApiClient&&) = delete;
Expand All @@ -22,6 +23,7 @@ namespace mlibrary::lauth {

protected:
std::unique_ptr<HttpClient> client;
const std::string bearerToken;
};
}

Expand Down
2 changes: 1 addition & 1 deletion apache/client/include/lauth/authorizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace mlibrary::lauth {
class Authorizer {
public:
Authorizer(const std::string& url) : client(std::make_unique<ApiClient>(url)) {};
Authorizer(const std::string& url, const std::string& bearerToken) : client(std::make_unique<ApiClient>(url, bearerToken)) {};
Authorizer(std::unique_ptr<ApiClient>&& client) : client(std::move(client)) {};
Authorizer(const Authorizer&) = delete;
Authorizer& operator=(const Authorizer&) = delete;
Expand Down
3 changes: 3 additions & 0 deletions apache/client/include/lauth/http_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define __LAUTH_HTTP_CLIENT_HPP__

#include "lauth/http_params.hpp"
#include "lauth/http_headers.hpp"

#include <optional>
#include <string>
Expand All @@ -14,6 +15,8 @@ namespace mlibrary::lauth {

virtual std::optional<std::string> get(const std::string &path);
virtual std::optional<std::string> get(const std::string &path, const HttpParams &params);
virtual std::optional<std::string> get(const std::string &path, const HttpHeaders &headers);
virtual std::optional<std::string> get(const std::string &path, const HttpParams &params, const HttpHeaders &headers);

protected:
const std::string baseUrl;
Expand Down
23 changes: 23 additions & 0 deletions apache/client/include/lauth/http_headers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef __LAUTH_HTTP_HEADERS_HPP__
#define __LAUTH_HTTP_HEADERS_HPP__

#include <map>
#include <string>

namespace mlibrary::lauth {
namespace detail {
// https://github.com/yhirose/cpp-httplib/blob/3b6597bba913d51161383657829b7e644e59c006/httplib.h#L315
struct ci {
bool operator()(const std::string &s1, const std::string &s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(),
s2.end(),
[](unsigned char c1, unsigned char c2) {
return ::tolower(c1) < ::tolower(c2);
});
}
};
}
using HttpHeaders = std::multimap<std::string, std::string, detail::ci>;
}

#endif // __LAUTH_HTTP_HEADERS_HPP__
2 changes: 0 additions & 2 deletions apache/client/include/lauth/http_params.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#ifndef __LAUTH_HTTP_PARAMS_HPP__
#define __LAUTH_HTTP_PARAMS_HPP__

#include <httplib.h>

#include <map>
#include <string>

Expand Down
1 change: 0 additions & 1 deletion apache/client/include/lauth/json_conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define __LAUTH_JSON_CONVERSIONS_HPP__

#include "lauth/json.hpp"

#include "lauth/authorization_result.hpp"

namespace mlibrary::lauth {
Expand Down
4 changes: 3 additions & 1 deletion apache/client/include/lauth/request.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#ifndef __LAUTH_REQUEST_HPP__
#define __LAUTH_REQUEST_HPP__

#include <string>

namespace mlibrary::lauth {
Expand All @@ -9,5 +10,6 @@ namespace mlibrary::lauth {
std::string user;
};
}
#endif

#endif // __LAUTH_REQUEST_HPP__

1 change: 1 addition & 0 deletions apache/client/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ install_headers(
'include/lauth/authorizer.hpp',
'include/lauth/http_client.hpp',
'include/lauth/http_params.hpp',
'include/lauth/http_headers.hpp',
'include/lauth/json_conversions.hpp',
'include/lauth/request.hpp',
subdir: 'lauth')
Expand Down
8 changes: 7 additions & 1 deletion apache/client/src/lauth/api_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ namespace mlibrary::lauth {
{"user", req.user}
};

auto result = client->get("/authorized", params);
std::string authorization = "Bearer " + bearerToken;

HttpHeaders headers {
{"Authorization", authorization}
};

auto result = client->get("/authorized", params, headers);

try
{
Expand Down
23 changes: 14 additions & 9 deletions apache/client/src/lauth/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@
#include <httplib.h>

#include "lauth/http_params.hpp"
#include "lauth/http_headers.hpp"

namespace mlibrary::lauth {
std::optional<std::string> HttpClient::get(const std::string& path) {
std::optional<std::string> HttpClient::get(const std::string& path, const HttpParams& params, const HttpHeaders& headers) {
httplib::Client client(baseUrl);

auto res = client.Get(path);
// using Headers = std::multimap<std::string, std::string, detail::ci>;
httplib::Headers marshal_headers ( headers.begin(), headers.end() );

auto res = client.Get(path, params, marshal_headers);
if (res)
return res->body;
else
return std::nullopt;
}

std::optional<std::string> HttpClient::get(const std::string& path, const HttpParams& params) {
httplib::Client client(baseUrl);
httplib::Headers headers;
return get(path, params, HttpHeaders{});
}

auto res = client.Get(path, params, headers);
if (res)
return res->body;
else
return std::nullopt;
std::optional<std::string> HttpClient::get(const std::string& path, const HttpHeaders& headers) {
return get(path, HttpParams{}, headers);
}

std::optional<std::string> HttpClient::get(const std::string& path) {
return get(path, HttpParams{}, HttpHeaders{});
}
}
18 changes: 11 additions & 7 deletions apache/client/test/lauth/api_client_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ TEST(ApiClient, HttpRequestByApiClientIsCorrect) {
{"user", req.user}
};

HttpHeaders headers {
{"Authorization", "Bearer dGVzdA=="}
};

auto body = R"({"determination":"allowed"})";

EXPECT_CALL(*client, get("/authorized", params)).WillOnce(Return(body));
ApiClient api_client(std::move(client));
EXPECT_CALL(*client, get("/authorized", params, headers)).WillOnce(Return(body));
ApiClient api_client(std::move(client), "dGVzdA==");

api_client.authorize(req);
}
Expand All @@ -40,7 +44,7 @@ TEST(ApiClient, DeterminationAllowedReturnsTrue) {
auto client = std::make_unique<MockHttpClient>();
auto body = R"({"determination":"allowed"})";

EXPECT_CALL(*client, get(_, _)).WillOnce(Return(body));
EXPECT_CALL(*client, get(_, _, _)).WillOnce(Return(body));
ApiClient api_client(std::move(client));

auto result = api_client.authorize(Request());
Expand All @@ -51,7 +55,7 @@ TEST(ApiClient, DeterminationDeniedReturnsFalse) {
auto client = std::make_unique<MockHttpClient>();
auto body = R"({"determination":"denied"})";

EXPECT_CALL(*client, get(_, _)).WillOnce(Return(body));
EXPECT_CALL(*client, get(_, _, _)).WillOnce(Return(body));
ApiClient api_client(std::move(client));

auto result = api_client.authorize(Request());
Expand All @@ -62,7 +66,7 @@ TEST(ApiClient, MismatchedJsonReturnsFalse) {
auto client = std::make_unique<MockHttpClient>();
auto body = R"({"should_ignore_this_key":"allowed"})";

EXPECT_CALL(*client, get(_, _)).WillOnce(Return(body));
EXPECT_CALL(*client, get(_, _, _)).WillOnce(Return(body));
ApiClient api_client(std::move(client));

auto result = api_client.authorize(Request());
Expand All @@ -73,7 +77,7 @@ TEST(ApiClient, MalformedJsonReturnsFalse) {
auto client = std::make_unique<MockHttpClient>();
auto body = R"({"should_ignore_this_key":"allowed",)";

EXPECT_CALL(*client, get(_, _)).WillOnce(Return(body));
EXPECT_CALL(*client, get(_, _, _)).WillOnce(Return(body));
ApiClient api_client(std::move(client));

auto result = api_client.authorize(Request());
Expand All @@ -84,7 +88,7 @@ TEST(ApiClient, EmptyBodyReturnsFalse) {
auto client = std::make_unique<MockHttpClient>();
auto body = "";

EXPECT_CALL(*client, get(_, _)).WillOnce(Return(body));
EXPECT_CALL(*client, get(_, _, _)).WillOnce(Return(body));
ApiClient api_client(std::move(client));

auto result = api_client.authorize(Request());
Expand Down
12 changes: 12 additions & 0 deletions apache/client/test/lauth/http_client_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ TEST(HttpClient, GetRequestWithMultipleParametersEncodesThem) {

EXPECT_THAT(*response, Eq(R"({"foo":"bar","something":"else"})"));
}

TEST(HttpClient, GetRequestWithAuthorizationHeaderEncodesIt) {
HttpClient client(MOCK_API_URL());

HttpParams params;
params.emplace("foo", "bar");
HttpHeaders headers;
headers.emplace("Authorization", "Bearer dGVzdA==");
auto response = client.get("/authorization", params, headers);

EXPECT_THAT(*response, Eq(R"({"Bearer":"dGVzdA=="})"));
}
3 changes: 3 additions & 0 deletions apache/client/test/lauth/mocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "lauth/authorization_result.hpp"
#include "lauth/http_client.hpp"
#include "lauth/http_params.hpp"
#include "lauth/http_headers.hpp"

#include <optional>

Expand All @@ -15,6 +16,8 @@ class MockHttpClient : public HttpClient {
MockHttpClient() : HttpClient("http://api.invalid") {};
MOCK_METHOD(std::optional<std::string>, get, (const std::string&), (override));
MOCK_METHOD(std::optional<std::string>, get, (const std::string&, const HttpParams&), (override));
MOCK_METHOD(std::optional<std::string>, get, (const std::string&, const HttpHeaders&), (override));
MOCK_METHOD(std::optional<std::string>, get, (const std::string&, const HttpParams&, const HttpHeaders&), (override));
};

class MockApiClient : public ApiClient {
Expand Down
16 changes: 16 additions & 0 deletions apache/client/test/mock_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ int main(int argc, char **argv) {
res.set_content(params.dump().c_str(), "application/json");
});

// Echo GET authorization as json object
server.Get("/authorization", [](const Request &req, Response &res) {
auto buffer = req.get_header_value("Authorization");

auto pos = buffer.find(" ");
auto key = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
auto value = buffer;

json auth;
auth[key] = value;

std::cout << "GET /authorization" << std::endl;
res.set_content(auth.dump().c_str(), "application/json");
});

server.Get("/users/authorized/is_allowed", [](const Request &, Response &res) {
std::cout << "GET /users/authorized/is_allowed" << std::endl;
res.set_content("yes", "text/plain");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@
# RemoteUserAnonymousUsername lauth-nobody

</IfModule>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<IfModule lauth_module>

LauthApiUrl http://app.lauth.local:2300
LauthApiToken TG9yZCBvZiB0aGUgUmluZ3MK

</IfModule>
62 changes: 51 additions & 11 deletions apache/module/mod_lauth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "http_log.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "apr_strings.h"

#include "mod_auth.h"

Expand All @@ -16,21 +17,62 @@
using namespace mlibrary::lauth;

extern "C" {

void *create_lauth_server_config(apr_pool_t *p, server_rec *_);
const char *set_url(cmd_parms *cmd, void *cfg, const char* arg);
const char *set_token(cmd_parms *cmd, void *cfg, const char* arg);

static const command_rec lauth_cmds[] = {
AP_INIT_TAKE1("LauthApiUrl", (cmd_func) set_url, NULL, RSRC_CONF|OR_AUTHCFG, "The URL to use for API."),
AP_INIT_TAKE1("LauthApiToken", (cmd_func) set_token, NULL, RSRC_CONF|OR_AUTHCFG, "The token to use for API."),
{NULL}};
void lauth_register_hooks(apr_pool_t *p);

APLOG_USE_MODULE(lauth);
module AP_MODULE_DECLARE_DATA lauth_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-dir config structures */
NULL, /* merge per-dir config structures */
NULL, /* create per-server config structures */
NULL, /* merge per-server config structures */
NULL, /* table of config file commands */
lauth_register_hooks /* register hooks */
NULL, /* create per-dir config structures */
NULL, /* merge per-dir config structures */
create_lauth_server_config, /* create per-server config structures */
NULL, /* merge per-server config structures */
lauth_cmds, /* table of config file commands */
lauth_register_hooks /* register hooks */
};
};

typedef struct lauth_config_struct {
const char *url; /* URL to API */
const char *token; /* token for API */
} lauth_config;

void *create_lauth_server_config(apr_pool_t *p, server_rec *_) {
lauth_config *config = (lauth_config *) apr_pcalloc(p, sizeof(*config));
config->url = NULL;
config->token = NULL;
return (void*) config;
}

const char *set_url(cmd_parms *cmd, void *cfg, const char* arg)
{
if(!*arg) {
return "Lauth API URL cannot be empty";
}

lauth_config *config = (lauth_config *) ap_get_module_config(cmd->server->module_config, &lauth_module);
config->url = apr_pstrdup(cmd->pool, arg);
return NULL;
}

const char *set_token(cmd_parms *cmd, void *cfg, const char* arg)
{
if(!*arg) {
return "Lauth API Token cannot be empty";
}

lauth_config *config = (lauth_config *) ap_get_module_config(cmd->server->module_config, &lauth_module);
config->token = apr_pstrdup(cmd->pool, arg);
return NULL;
}

static authz_status lauth_check_authorization(request_rec *r,
const char *require_line,
const void *parsed_require_line)
Expand All @@ -53,8 +95,8 @@ static authz_status lauth_check_authorization(request_rec *r,
};
}

std::map<std::string, std::string> result =
Authorizer("http://app.lauth.local:2300").authorize(req);
lauth_config *config = (lauth_config *) ap_get_module_config(r->server->module_config, &lauth_module);
std::map<std::string, std::string> result = Authorizer(config->url, config->token).authorize(req);

apr_table_set(r->subprocess_env, "PUBLIC_COLL", result["public_collections"].c_str());
apr_table_set(r->subprocess_env, "AUTHZD_COLL", result["authorized_collections"].c_str());
Expand All @@ -74,5 +116,3 @@ void lauth_register_hooks(apr_pool_t *p)
AUTHZ_PROVIDER_VERSION,
&authz_lauth_provider, AP_AUTH_INTERNAL_PER_CONF);
}


1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
UID: ${UID:-1000}
GID: ${GID:-1000}
environment:
- BEARER_TOKEN=TG9yZCBvZiB0aGUgUmluZ3MK
- DATABASE_URL=mysql2://lauth:lauth@db.lauth.local:3306/lauth_demo
hostname: app.lauth.local
networks:
Expand Down
Loading

0 comments on commit c3afac3

Please sign in to comment.