Enabled authorization of libprocess HTTP endpoints (libprocess).

This patch enables libprocess to store and execute
authorization callbacks provided by a client
application.

Review: https://reviews.apache.org/r/46866/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/25376d8e
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/25376d8e
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/25376d8e

Branch: refs/heads/master
Commit: 25376d8ee9227653a93f546e33be49500b6d9d5c
Parents: acde41a
Author: Greg Mann <[email protected]>
Authored: Wed May 11 22:45:30 2016 -0400
Committer: Kapil Arya <[email protected]>
Committed: Thu May 12 01:50:20 2016 -0400

----------------------------------------------------------------------
 3rdparty/libprocess/include/process/http.hpp |  28 +++++
 3rdparty/libprocess/src/process.cpp          | 129 +++++++++++++++++-----
 2 files changed, 129 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/25376d8e/3rdparty/libprocess/include/process/http.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/http.hpp 
b/3rdparty/libprocess/include/process/http.hpp
index 1732362..10f6fb9 100644
--- a/3rdparty/libprocess/include/process/http.hpp
+++ b/3rdparty/libprocess/include/process/http.hpp
@@ -81,6 +81,34 @@ Future<Nothing> unsetAuthenticator(const std::string& realm);
 
 } // namespace authentication {
 
+// Forward declaration.
+struct Request;
+
+namespace authorization {
+
+// The `AuthorizationCallbacks` type is used for a set of authorization
+// callbacks used by libprocess to authorize HTTP endpoints. The key of the map
+// contains the endpoint's path, while the value contains the callback.
+typedef hashmap<std::string,
+                lambda::function<process::Future<bool>(
+                    const Request,
+                    const Option<std::string> principal)>>
+  AuthorizationCallbacks;
+
+
+// Set authorization callbacks for HTTP endpoints. These can be used to call 
out
+// to an external, application-level authorizer. The callbacks should accept an
+// HTTP request and an optional principal, and they should return a
+// `Future<bool>` representing whether or not authorization was successful.
+void setCallbacks(const AuthorizationCallbacks&);
+
+
+// Remove any authorization callbacks which were previously installed in
+// libprocess.
+void unsetCallbacks();
+
+} // namespace authorization {
+
 // Status code reason strings, from the HTTP1.1 RFC:
 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
 extern hashmap<uint16_t, std::string>* statuses;

http://git-wip-us.apache.org/repos/asf/mesos/blob/25376d8e/3rdparty/libprocess/src/process.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/process.cpp 
b/3rdparty/libprocess/src/process.cpp
index becd263..988fb3b 100644
--- a/3rdparty/libprocess/src/process.cpp
+++ b/3rdparty/libprocess/src/process.cpp
@@ -131,6 +131,8 @@ using process::http::authentication::Authenticator;
 using process::http::authentication::AuthenticationResult;
 using process::http::authentication::AuthenticatorManager;
 
+using process::http::authorization::AuthorizationCallbacks;
+
 using process::network::Address;
 using process::network::Socket;
 
@@ -484,6 +486,9 @@ static Gate* gate = new Gate();
 // Used for authenticating HTTP requests.
 static AuthenticatorManager* authenticator_manager = NULL;
 
+// Authorization callbacks for HTTP endpoints.
+static AuthorizationCallbacks* authorization_callbacks = NULL;
+
 // Filter. Synchronized support for using the filterer needs to be
 // recursive in case a filterer wants to do anything fancy (which is
 // possible and likely given that filters will get used for testing).
@@ -504,6 +509,7 @@ THREAD_LOCAL Executor* _executor_ = NULL;
 
 
 namespace http {
+
 namespace authentication {
 
 Future<Nothing> setAuthenticator(
@@ -524,6 +530,22 @@ Future<Nothing> unsetAuthenticator(const string& realm)
 }
 
 } // namespace authentication {
+
+namespace authorization {
+
+void setCallbacks(const AuthorizationCallbacks& callbacks)
+{
+  authorization_callbacks = new AuthorizationCallbacks(callbacks);
+}
+
+
+void unsetCallbacks()
+{
+  authorization_callbacks = NULL;
+}
+
+} // namespace authorization {
+
 } // namespace http {
 
 
@@ -3226,7 +3248,7 @@ void ProcessBase::visit(const HttpEvent& event)
           << " with path: '" << event.request->url.path << "'";
 
   // Lazily initialize the Sequence needed for ordering requests
-  // across authentication.
+  // across authentication and authorization.
   if (handlers.httpSequence.get() == NULL) {
     handlers.httpSequence.reset(new Sequence());
   }
@@ -3236,17 +3258,22 @@ void ProcessBase::visit(const HttpEvent& event)
   // Split the path by '/'.
   vector<string> tokens = strings::tokenize(event.request->url.path, "/");
   CHECK(!tokens.empty());
-  CHECK_EQ(pid.id, http::decode(tokens[0]).get());
+
+  const string id = http::decode(tokens[0]).get();
+  CHECK_EQ(pid.id, id);
 
   // First look to see if there is an HTTP handler that can handle the
   // longest prefix of this path.
 
-  // Remove the 'id' prefix from the path.
+  // Remove the `id` prefix from the path.
   string name = strings::remove(
       event.request->url.path, "/" + tokens[0], strings::PREFIX);
-
   name = strings::trim(name, strings::PREFIX, "/");
 
+  // Look for an endpoint handler for this path. We begin with the full path,
+  // but if no handler is found and the path is nested, we shorten it and look
+  // again. For example: if the request is for '/a/b/c' and no handler is 
found,
+  // we will then check for '/a/b', and finally for '/a'.
   while (Path(name).dirname() != name) {
     if (handlers.http.count(name) == 0) {
       name = Path(name).dirname();
@@ -3271,7 +3298,7 @@ void ProcessBase::visit(const HttpEvent& event)
     event.response->associate(response->future());
 
     authentication
-      .onAny(defer(self(), [endpoint, request, response](
+      .onAny(defer(self(), [this, endpoint, request, response, name, id](
           const Future<Option<AuthenticationResult>>& authentication) {
         if (!authentication.isReady()) {
           response->set(InternalServerError());
@@ -3287,36 +3314,82 @@ void ProcessBase::visit(const HttpEvent& event)
           return;
         }
 
-        if (authentication->isNone()) {
-          // Request didn't need authentication or authentication
-          // is not applicable, just forward the request.
-          if (endpoint.realm.isNone()) {
-            response->associate(endpoint.handler.get()(request));
-          } else {
-            response->associate(endpoint.authenticatedHandler.get()(
-                request, None()));
+        Option<string> principal = None();
+
+        // If authentication failed, we do not continue with authorization.
+        if (authentication->isSome()) {
+          if (authentication.get()->unauthorized.isSome()) {
+            // Request was not authenticated, challenged issued.
+            response->set(authentication.get()->unauthorized.get());
+
+            delete response;
+            return;
+          } else if (authentication.get()->forbidden.isSome()) {
+            // Request was not authenticated, no challenge issued.
+            response->set(authentication.get()->forbidden.get());
+
+            delete response;
+            return;
           }
 
-          delete response;
-          return;
+          principal = authentication.get()->principal;
         }
 
-        if (authentication.get()->unauthorized.isSome()) {
-          // Request was not authenticated, challenged issued.
-          response->set(authentication.get()->unauthorized.get());
-        } else if (authentication.get()->forbidden.isSome()) {
-          // Request was not authenticated, no challenge issued.
-          response->set(authentication.get()->forbidden.get());
+        // The result of a call to an authorization callback.
+        Future<bool> authorization;
+
+        // Look for an authorization callback installed for this endpoint path.
+        // If none is found, use a trivial one.
+        const string callback_path = path::join("/" + id, name);
+        if (authorization_callbacks != NULL &&
+            authorization_callbacks->count(callback_path) > 0) {
+          authorization = authorization_callbacks->at(callback_path)(
+              request, principal);
+
+          // Sequence the authorization future to ensure the handlers
+          // are invoked in the same order that requests arrive.
+          authorization = handlers.httpSequence->add<bool>(
+              [authorization]() { return authorization; });
         } else {
-          Option<string> principal = authentication.get()->principal;
-
-          response->associate(endpoint.authenticatedHandler.get()(
-              request, principal));
+          authorization = handlers.httpSequence->add<bool>(
+              []() { return true; });
         }
 
-        delete response;
-        return;
-    }));
+        // Install a callback on the authorization result.
+        authorization
+          .onAny(defer(self(), [endpoint, request, response, principal](
+              const Future<bool>& authorization) {
+            if (!authorization.isReady()) {
+              response->set(InternalServerError());
+
+              VLOG(1) << "Returning '" << response->future()->status << "'"
+                      << " for '" << request.url.path << "'"
+                      << " (authorization failed: "
+                      << (authorization.isFailed()
+                          ? authorization.failure()
+                          : "discarded") << ")";
+
+              delete response;
+              return;
+            }
+
+            if (authorization.get() == true) {
+              // Authorization succeeded, so forward request to the handler.
+              if (endpoint.realm.isNone()) {
+                response->associate(endpoint.handler.get()(request));
+              } else {
+                response->associate(endpoint.authenticatedHandler.get()(
+                    request, principal));
+              }
+            } else {
+              // Authorization failed, so return a `Forbidden` response.
+              response->set(Forbidden());
+            }
+
+            delete response;
+            return;
+        }));
+      }));
 
     return;
   }

Reply via email to