Skip to content

Commit

Permalink
Updated HTTP authentication to be performed during Event visitation.
Browse files Browse the repository at this point in the history
Currently HTTP authentication is done in the global context of the
ProcessManager. This poses ordering issues that are absent if done
within the Process execution context. Also, users are unable to
customize authentication behavior when overriding HttpEvent
visitation.

Review: https://reviews.apache.org/r/41375/
  • Loading branch information
Alexander Rojas authored and bmahler committed Dec 29, 2015
1 parent 0c3061e commit 19dfc49
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 239 deletions.
4 changes: 2 additions & 2 deletions 3rdparty/libprocess/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ PICOJSON = 3rdparty/picojson-$(PICOJSON_VERSION)
noinst_LTLIBRARIES = libprocess.la

libprocess_la_SOURCES = \
src/authentication_router.hpp \
src/authentication_router.cpp \
src/authenticator_manager.hpp \
src/authenticator_manager.cpp \
src/clock.cpp \
src/config.hpp \
src/decoder.hpp \
Expand Down
9 changes: 2 additions & 7 deletions 3rdparty/libprocess/include/process/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,9 @@ struct HttpEvent : Event
{
HttpEvent(
http::Request* _request,
Promise<http::Response>* _response,
const Option<std::string>& _principal = None())
Promise<http::Response>* _response)
: request(_request),
response(_response),
principal(_principal) {}
response(_response) {}

virtual ~HttpEvent()
{
Expand All @@ -140,9 +138,6 @@ struct HttpEvent : Event
http::Request* const request;
Promise<http::Response>* response;

// This will be set for authenticated requests.
Option<std::string> principal;

private:
// Not copyable, not assignable.
HttpEvent(const HttpEvent&);
Expand Down
44 changes: 35 additions & 9 deletions 3rdparty/libprocess/include/process/process.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

namespace process {

// Forward declaration.
class Sequence;

namespace firewall {

/**
Expand All @@ -61,7 +64,6 @@ void install(std::vector<Owned<FirewallRule>>&& rules);

} // namespace firewall {


class ProcessBase : public EventVisitor
{
public:
Expand Down Expand Up @@ -237,10 +239,12 @@ class ProcessBase : public EventVisitor

/**
* Any function which takes a `process::http::Request` and an
* `Option<std::string>` and returns a `process::http::Response`.
* `Option<std::string>` principal and returns a
* `process::http::Response`.
*
* If the string is set, it represents the authenticated principal
* for the request.
* If the authentication principal string is set, the realm
* requires authentication and authentication succeeded. If
* it is not set, the realm does not require authentication.
*
* The default visit implementation for HTTP events invokes
* installed HTTP handlers.
Expand Down Expand Up @@ -355,15 +359,37 @@ class ProcessBase : public EventVisitor
// Delegates for messages.
std::map<std::string, UPID> delegates;

// Definition of an HTTP endpoint. The endpoint can be
// associated with an authentication realm, in which case:
//
// (1) `realm` and `authenticatedHandler` will be set.
// Libprocess will perform HTTP authentication for
// all requests to this endpoint (by default during
// HttpEvent visitation). The authentication principal
// will be passed to the `authenticatedHandler`.
//
// Otherwise, if the endpoint is not associated with an
// authentication realm:
//
// (2) Only `handler` will be set, and no authentication
// takes place.
struct HttpEndpoint
{
Option<HttpRequestHandler> handler;

Option<std::string> realm;
Option<AuthenticatedHttpRequestHandler> authenticatedHandler;
};

// Handlers for messages and HTTP requests.
struct {
std::map<std::string, MessageHandler> message;
std::map<std::string, HttpEndpoint> http;

// `HttpRequestHandlers` are equivalent to their authenticated
// counterparts where the principal is always `None`. Therefore
// we convert the regular handler to an authenticated one in
// order to store only a single map here.
std::map<std::string, AuthenticatedHttpRequestHandler> http;
// Used for delivering HTTP requests in the correct order.
// Initialized lazily to avoid ProcessBase requiring
// another Process!
Owned<Sequence> httpSequence;
} handlers;

// Definition of a static asset.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// See the License for the specific language governing permissions and
// limitations under the License

#include "authentication_router.hpp"
#include "authenticator_manager.hpp"

#include <string>

Expand All @@ -33,10 +33,10 @@ namespace http {
namespace authentication {


class AuthenticationRouterProcess : public Process<AuthenticationRouterProcess>
class AuthenticatorManagerProcess : public Process<AuthenticatorManagerProcess>
{
public:
AuthenticationRouterProcess();
AuthenticatorManagerProcess();

Future<Nothing> setAuthenticator(
const string& realm,
Expand All @@ -45,27 +45,20 @@ class AuthenticationRouterProcess : public Process<AuthenticationRouterProcess>
Future<Nothing> unsetAuthenticator(
const string& realm);

Future<Nothing> addEndpoint(
const string& endpoint,
const string& realm);

Future<Nothing> removeEndpoint(
const string& realm);

Future<Option<AuthenticationResult>> authenticate(
const Request& request);
const Request& request,
const string& realm);

private:
hashmap<string, Owned<Authenticator>> authenticators_;
hashmap<string, string> endpoints_;
};


AuthenticationRouterProcess::AuthenticationRouterProcess()
AuthenticatorManagerProcess::AuthenticatorManagerProcess()
: ProcessBase(ID::generate("AuthenticationRouter")) {}


Future<Nothing> AuthenticationRouterProcess::setAuthenticator(
Future<Nothing> AuthenticatorManagerProcess::setAuthenticator(
const string& realm,
Owned<Authenticator> authenticator)
{
Expand All @@ -75,59 +68,26 @@ Future<Nothing> AuthenticationRouterProcess::setAuthenticator(
}


Future<Nothing> AuthenticationRouterProcess::unsetAuthenticator(
Future<Nothing> AuthenticatorManagerProcess::unsetAuthenticator(
const string& realm)
{
authenticators_.erase(realm);
return Nothing();
}


Future<Nothing> AuthenticationRouterProcess::addEndpoint(
const string& endpoint,
Future<Option<AuthenticationResult>> AuthenticatorManagerProcess::authenticate(
const Request& request,
const string& realm)
{
endpoints_[endpoint] = realm;
return Nothing();
}


Future<Nothing> AuthenticationRouterProcess::removeEndpoint(
const string& endpoint)
{
endpoints_.erase(endpoint);
return Nothing();
}


Future<Option<AuthenticationResult>> AuthenticationRouterProcess::authenticate(
const Request& request)
{
// Finds the longest prefix path which matches a realm.
Option<string> realm;
string name = request.url.path;

while (Path(name).dirname() != name) {
if (endpoints_.contains(name)) {
realm = endpoints_[name];
break;
}

name = Path(name).dirname();
}

if (realm.isNone()) {
return None(); // Request doesn't need authentication.
}

if (!authenticators_.contains(realm.get())) {
if (!authenticators_.contains(realm)) {
VLOG(2) << "Request for '" << request.url.path << "' requires"
<< " authentication in realm '" << realm.get() << "'"
<< " authentication in realm '" << realm << "'"
<< " but no authenticator found";
return None();
}

return authenticators_[realm.get()]->authenticate(request)
return authenticators_[realm]->authenticate(request)
.then([](const AuthenticationResult& authentication)
-> Future<Option<AuthenticationResult>> {
// Validate that exactly 1 member is set!
Expand All @@ -146,73 +106,53 @@ Future<Option<AuthenticationResult>> AuthenticationRouterProcess::authenticate(
}


AuthenticationRouter::AuthenticationRouter()
: process(new AuthenticationRouterProcess())
AuthenticatorManager::AuthenticatorManager()
: process(new AuthenticatorManagerProcess())
{
spawn(process.get());
}


AuthenticationRouter::~AuthenticationRouter()
AuthenticatorManager::~AuthenticatorManager()
{
terminate(process.get());
wait(process.get());
}


Future<Nothing> AuthenticationRouter::setAuthenticator(
Future<Nothing> AuthenticatorManager::setAuthenticator(
const string& realm,
Owned<Authenticator> authenticator)
{
return dispatch(
process.get(),
&AuthenticationRouterProcess::setAuthenticator,
&AuthenticatorManagerProcess::setAuthenticator,
realm,
authenticator);
}


Future<Nothing> AuthenticationRouter::unsetAuthenticator(
Future<Nothing> AuthenticatorManager::unsetAuthenticator(
const string& realm)
{
return dispatch(
process.get(),
&AuthenticationRouterProcess::unsetAuthenticator,
&AuthenticatorManagerProcess::unsetAuthenticator,
realm);
}


Future<Nothing> AuthenticationRouter::addEndpoint(
const string& endpoint,
Future<Option<AuthenticationResult>> AuthenticatorManager::authenticate(
const Request& request,
const string& realm)
{
return dispatch(
process.get(),
&AuthenticationRouterProcess::addEndpoint,
endpoint,
&AuthenticatorManagerProcess::authenticate,
request,
realm);
}


Future<Nothing> AuthenticationRouter::removeEndpoint(
const string& realm)
{
return dispatch(
process.get(),
&AuthenticationRouterProcess::removeEndpoint,
realm);
}


Future<Option<AuthenticationResult>> AuthenticationRouter::authenticate(
const Request& request)
{
return dispatch(
process.get(),
&AuthenticationRouterProcess::authenticate,
request);
}

} // namespace authentication {
} // namespace http {
} // namespace process {
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,19 @@ namespace process {
namespace http {
namespace authentication {

class AuthenticationRouterProcess;
class AuthenticatorManagerProcess;


// Manages the authentication routing via authentication
// "realms". Endpoints may map to a realm. Each realm may
// have an authenticator, through which all requests to
// matching endpoints must be authenticated.
class AuthenticationRouter
// Manages the realm -> Authenticator mapping for HTTP
// authentication in libprocess. Endpoints may map to
// a realm. Each realm may have an authenticator set
// here, through which all requests to matching
// endpoints will be authenticated.
class AuthenticatorManager
{
public:
AuthenticationRouter();
~AuthenticationRouter();
AuthenticatorManager();
~AuthenticatorManager();

// Sets the authenticator for the realm; this will
// overwrite any previous authenticator for the realm.
Expand All @@ -49,23 +50,15 @@ class AuthenticationRouter
// Unsets the authenticator for the realm.
Future<Nothing> unsetAuthenticator(const std::string& realm);

// TODO(arojas): Consider making the realm a property
// of the endpoint handler in `ProcessBase` rather than
// having the router maintain the mapping.
Future<Nothing> addEndpoint(
const std::string& endpoint,
const std::string& realm);

Future<Nothing> removeEndpoint(const std::string& endpoint);

// Authenticates the request, will return None if no
// authentication is required. None occurs when either
// the request endpoint does not match a realm, or there
// is no authenticator for the realm.
Future<Option<AuthenticationResult>> authenticate(const Request& request);
// authentication is required because there is no
// authenticator for the realm.
Future<Option<AuthenticationResult>> authenticate(
const Request& request,
const std::string& realm);

private:
Owned<AuthenticationRouterProcess> process;
Owned<AuthenticatorManagerProcess> process;
};

} // namespace authentication {
Expand Down

0 comments on commit 19dfc49

Please sign in to comment.