Skip to content

Commit

Permalink
Merge pull request #67 from mlibrary/delegation
Browse files Browse the repository at this point in the history
Delegation
  • Loading branch information
malakai97 authored Jan 31, 2024
2 parents b3dec48 + 301e654 commit 081a8f7
Show file tree
Hide file tree
Showing 39 changed files with 965 additions and 287 deletions.
5 changes: 4 additions & 1 deletion apache/client/include/lauth/authorization_result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
#define __LAUTH_AUTHORIZATION_RESULT_HPP__

#include <string>
#include <vector>

namespace mlibrary::lauth {
struct AuthorizationResult {
std::string determination;
std::string determination = "denied";
std::vector<std::string> public_collections = std::vector<std::string>();
std::vector<std::string> authorized_collections = std::vector<std::string>();
};
}

Expand Down
3 changes: 2 additions & 1 deletion apache/client/include/lauth/authorizer.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef __LAUTH_AUTHORIZER_HPP__
#define __LAUTH_AUTHORIZER_HPP__

#include <map>
#include <memory>
#include <string>

Expand All @@ -18,7 +19,7 @@ namespace mlibrary::lauth {
Authorizer& operator=(const Authorizer&&) = delete;
virtual ~Authorizer() = default;

virtual bool isAllowed(Request req);
virtual std::map<std::string, std::string> authorize(Request req);

protected:
std::unique_ptr<ApiClient> client;
Expand Down
10 changes: 8 additions & 2 deletions apache/client/src/lauth/authorization_result.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ using json = nlohmann::json;
namespace mlibrary::lauth {
void to_json(json& j, const AuthorizationResult& authz) {
j = json {
{ "determination", authz.determination}
{ "determination", authz.determination},
{ "public_collections", authz.public_collections},
{ "authorized_collections", authz.authorized_collections}
};
}

void from_json(const json& j, AuthorizationResult& authz) {
j.at("determination").get_to(authz.determination);
// Note: value() only uses the default if the key is absent.
AuthorizationResult defaults;
authz.determination = j.value("determination", defaults.determination);
authz.public_collections = j.value("public_collections", defaults.public_collections);
authz.authorized_collections = j.value("authorized_collections", defaults.authorized_collections);
}
}
22 changes: 20 additions & 2 deletions apache/client/src/lauth/authorizer.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
#include "lauth/authorizer.hpp"

#include <map>
#include <string>
#include <numeric>

namespace mlibrary::lauth {
bool Authorizer::isAllowed(Request req) {
return client->authorize(req).determination == "allowed";
std::string join(std::vector<std::string> elements, const std::string &separator) {
return std::accumulate(
begin(elements),
end(elements),
separator,
[&separator](std::string result, std::string value) {
return result + value + separator;
}
);
}

std::map<std::string, std::string> Authorizer::authorize(Request req) {
AuthorizationResult result = client->authorize(req);
return std::map<std::string, std::string> {
{"determination", result.determination},
{"public_collections", join(result.public_collections, ":")},
{"authorized_collections", join(result.authorized_collections, ":")},
};
}
}
24 changes: 22 additions & 2 deletions apache/client/test/lauth/authorization_result_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "lauth/authorization_result.hpp"

#include <string>
#include <vector>

#include <gtest/gtest.h>
#include <gmock/gmock.h>
Expand All @@ -10,20 +11,39 @@

using namespace mlibrary::lauth;

TEST(AuthorizationResultTest, Defaults) {
AuthorizationResult result;

EXPECT_THAT(result.determination, "denied");
EXPECT_THAT(result.public_collections, testing::IsEmpty());
EXPECT_THAT(result.authorized_collections, testing::IsEmpty());
}

TEST(AuthorizationResultTest, FromJson) {
std::string stringBody = R"({"determination":"allowed"})";
std::string stringBody =
R"({"determination":"allowed",)"
R"("public_collections":["pub1","pub2"],)"
R"("authorized_collections":["auth1","auth2"]})";
json jsonBody = json::parse(stringBody);
AuthorizationResult result = jsonBody.template get<AuthorizationResult>();

EXPECT_THAT(result.determination, "allowed");
EXPECT_THAT(result.public_collections, testing::ElementsAre("pub1", "pub2"));
EXPECT_THAT(result.authorized_collections, testing::ElementsAre("auth1", "auth2"));
}


TEST(AuthorizationResultTest, ToJson) {
std::vector<std::string> public_collections = {"pub1", "pub2"};
std::vector<std::string> authorized_collections = {"auth1", "auth2"};
AuthorizationResult result {
.determination = "allowed"
.determination = "allowed",
.public_collections = public_collections,
.authorized_collections = authorized_collections
};
json j = result; // magic

EXPECT_THAT(j["determination"], "allowed");
EXPECT_THAT(j["public_collections"], testing::ElementsAre("pub1", "pub2"));
EXPECT_THAT(j["authorized_collections"], testing::ElementsAre("auth1", "auth2"));
}
54 changes: 39 additions & 15 deletions apache/client/test/lauth/authorizer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,9 @@ TEST(AuthorizerTest, AllowsAccessWhenApiSaysAuthorized) {
EXPECT_CALL(*client, authorize(_)).WillOnce(Return(result));
Authorizer authorizer(std::move(client));

Request req {
.ip = "127.0.0.1",
.uri = "/restricted-by-username/",
.user = "lauth-allowed",
};
auto allowed = authorizer.isAllowed(req);

EXPECT_THAT(allowed, true);
Request req;
auto actual = authorizer.authorize(req);
EXPECT_THAT(actual["determination"], "allowed");
}

TEST(AuthorizerTest, DeniesAccessWhenApiSaysUnauthorized) {
Expand All @@ -38,12 +33,41 @@ TEST(AuthorizerTest, DeniesAccessWhenApiSaysUnauthorized) {
EXPECT_CALL(*client, authorize(_)).WillOnce(Return(result));
Authorizer authorizer(std::move(client));

Request req {
.ip = "127.0.0.1",
.uri = "/restricted-by-username/",
.user = "lauth-denied",
};
auto allowed = authorizer.isAllowed(req);
Request req;
auto actual = authorizer.authorize(req);
EXPECT_THAT(actual["determination"], "denied");
}

TEST(AuthorizerTest, JoinsEmptyCollections) {
auto client = std::make_unique<MockApiClient>();
AuthorizationResult result;
EXPECT_CALL(*client, authorize(_)).WillOnce(Return(result));
Authorizer authorizer(std::move(client));

Request req;
auto actual = authorizer.authorize(req);
EXPECT_THAT(actual["public_collections"], ":");
EXPECT_THAT(actual["authorized_collections"], ":");
}

TEST(AuthorizerTest, JoinsPublicCollections) {
auto client = std::make_unique<MockApiClient>();
auto result = AuthorizationResult { .public_collections = {"pub1", "pub2", "pub3"} };
EXPECT_CALL(*client, authorize(_)).WillOnce(Return(result));
Authorizer authorizer(std::move(client));

Request req;
auto actual = authorizer.authorize(req);
EXPECT_THAT(actual["public_collections"], ":pub1:pub2:pub3:");
}

TEST(AuthorizerTest, JoinsAuthorizedCollections) {
auto client = std::make_unique<MockApiClient>();
auto result = AuthorizationResult { .authorized_collections = {"auth1", "auth2"} };
EXPECT_CALL(*client, authorize(_)).WillOnce(Return(result));
Authorizer authorizer(std::move(client));

EXPECT_THAT(allowed, false);
Request req;
auto actual = authorizer.authorize(req);
EXPECT_THAT(actual["authorized_collections"], ":auth1:auth2:");
}
4 changes: 2 additions & 2 deletions apache/conf/httpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule remoteip_module modules/mod_remoteip.so
#LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.so
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
#LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
Expand Down
19 changes: 19 additions & 0 deletions apache/conf/test-site.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

# LogLevel debug

<Location /hosted>
ScriptAlias /lauth/test-site/cgi/printenv
AuthType RemoteUser
<RequireAll>
Require valid-user
Require lauth
</RequireAll>
</Location>

<Location /debug>
AuthType RemoteUser
AuthzSendForbiddenOnFailure On
Expand Down Expand Up @@ -45,6 +54,16 @@
</RequireAll>
</Location>

<Location "/app/proxied">
AuthType RemoteUser
ProxyPass "http://proxied-test-app.lauth.local:8008"
ProxyPassReverse "http://proxied-test-app.lauth.local:8008"
<RequireAll>
Require valid-user
Require lauth
</RequireAll>
</Location>

</VirtualHost>

# <VirtualHost *:443>
Expand Down
9 changes: 8 additions & 1 deletion apache/module/mod_lauth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <lauth/authorizer.hpp>

#include <string>
#include <map>

using namespace mlibrary::lauth;

Expand Down Expand Up @@ -44,7 +45,13 @@ static authz_status lauth_check_authorization(request_rec *r,
.user = r->user ? std::string(r->user) : ""
};

return Authorizer("http://app.lauth.local:2300").isAllowed(req) ? AUTHZ_GRANTED : AUTHZ_DENIED;
std::map<std::string, std::string> result =
Authorizer("http://app.lauth.local:2300").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());

return result["determination"] == "allowed" ? AUTHZ_GRANTED : AUTHZ_DENIED;
}

static const authz_provider authz_lauth_provider =
Expand Down
120 changes: 120 additions & 0 deletions db/delegation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
-- target-cats is the collection that the user will request
-- the user is authorized for this collection
INSERT INTO aa_coll VALUES(
'target-cats', -- uniqueIdentifier
'target-cats', -- commonName
'auth system testing: delegation',
'catpics', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'd', -- dlpsAuthzType
'f', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

-- extra-cats is a private collection the user is authorized for
INSERT INTO aa_coll VALUES(
'extra-cats', -- uniqueIdentifier
'extra-cats', -- commonName
'auth system testing: delegation',
'catpics', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'd', -- dlpsAuthzType
'f', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

-- secret-cats is a private collection the user is not authorized for
INSERT INTO aa_coll VALUES(
'secret-cats', -- uniqueIdentifier
'secret-cats', -- commonName
'auth system testing: delegation',
'catpics', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'd', -- dlpsAuthzType
'f', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

-- public-cats is a public collection the user is not explicitly authorized for
INSERT INTO aa_coll VALUES(
'public-cats', -- uniqueIdentifier
'public-cats', -- commonName
'auth system testing: delegation',
'catpics', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'd', -- dlpsAuthzType
't', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

-- extra-public-cats is a public collection the user is explicitly authorized for
INSERT INTO aa_coll VALUES(
'extra-public-cats', -- uniqueIdentifier
'extra-public-cats', -- commonName
'auth system testing: delegation',
'catpics', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'd', -- dlpsAuthzType
't', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

-- we only need one location for these tests
INSERT INTO aa_coll_obj VALUES(
'www.lauth.local', -- server hostname, not vhost
'/lauth/test-site/cgi/printenv', -- dlpsPath
'target-cats', -- coll.uniqueIdentifier
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_may_access VALUES(
NULL, -- uniqueIdentifier
'lauth-allowed', -- userid
NULL, -- user_grp
NULL, -- inst
'target-cats', -- coll
CURRENT_TIMESTAMP,
'root',
NULL,
'f'
);

INSERT INTO aa_may_access VALUES(
NULL, -- uniqueIdentifier
'lauth-allowed', -- userid
NULL, -- user_grp
NULL, -- inst
'extra-cats', -- coll
CURRENT_TIMESTAMP,
'root',
NULL,
'f'
);

INSERT INTO aa_may_access VALUES(
NULL, -- uniqueIdentifier
'lauth-allowed', -- userid
NULL, -- user_grp
NULL, -- inst
'extra-public-cats', -- coll
CURRENT_TIMESTAMP,
'root',
NULL,
'f'
);
1 change: 1 addition & 0 deletions db/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ if [[ $all == "true" ]]; then
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/keys.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/test-fixture.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/network.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/delegation.sql"
fi
Loading

0 comments on commit 081a8f7

Please sign in to comment.