Boost beast

This commit is the beginings of attempting to transition away from
crow, and toward boost::beast.  Unit tests are passing, and
implementation appears to be slightly faster than crow.

Change-Id: Ic8d946dc7a04f514c67b1098f181eee1ced69171
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 07540d4..49649dd 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -5,10 +5,9 @@
 #include <webassets.hpp>
 #include <random>
 #include <crow/app.h>
-#include <crow/http_codes.h>
+#include <crow/common.h>
 #include <crow/http_request.h>
 #include <crow/http_response.h>
-#include <boost/bimap.hpp>
 #include <boost/container/flat_set.hpp>
 
 namespace crow {
@@ -18,7 +17,7 @@
 class Middleware {
  public:
   struct context {
-    const crow::PersistentData::UserSession* session;
+    std::shared_ptr<crow::PersistentData::UserSession> session;
   };
 
   void before_handle(crow::request& req, response& res, context& ctx) {
@@ -27,25 +26,27 @@
     }
 
     ctx.session = perform_xtoken_auth(req);
-
     if (ctx.session == nullptr) {
       ctx.session = perform_cookie_auth(req);
     }
-
-    const std::string& auth_header = req.get_header_value("Authorization");
-    // Reject any kind of auth other than basic or token
-    if (ctx.session == nullptr && boost::starts_with(auth_header, "Token ")) {
-      ctx.session = perform_token_auth(auth_header);
-    }
-
-    if (ctx.session == nullptr && boost::starts_with(auth_header, "Basic ")) {
-      ctx.session = perform_basic_auth(auth_header);
+    if (ctx.session == nullptr) {
+      boost::string_view auth_header = req.get_header_value("Authorization");
+      if (!auth_header.empty()) {
+        // Reject any kind of auth other than basic or token
+        if (boost::starts_with(auth_header, "Token ")) {
+          ctx.session = perform_token_auth(auth_header);
+        } else if (boost::starts_with(auth_header, "Basic ")) {
+          ctx.session = perform_basic_auth(auth_header);
+        }
+      }
     }
 
     if (ctx.session == nullptr) {
       CROW_LOG_WARNING << "[AuthMiddleware] authorization failed";
-      res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
+
+      res.result(boost::beast::http::status::unauthorized);
       res.add_header("WWW-Authenticate", "Basic");
+
       res.end();
       return;
     }
@@ -57,10 +58,10 @@
   template <typename AllContext>
   void after_handle(request& req, response& res, context& ctx,
                     AllContext& allctx) {
-    // TODO(ed) THis should really be handled by the persistent data middleware,
-    // but because it is upstream, it doesn't have access to the session
-    // information.  Should the data middleware persist the current user
-    // session?
+    // TODO(ed) THis should really be handled by the persistent data
+    // middleware, but because it is upstream, it doesn't have access to the
+    // session information.  Should the data middleware persist the current
+    // user session?
     if (ctx.session != nullptr &&
         ctx.session->persistence ==
             crow::PersistentData::PersistenceType::SINGLE_REQUEST) {
@@ -69,12 +70,12 @@
   }
 
  private:
-  const crow::PersistentData::UserSession* perform_basic_auth(
-      const std::string& auth_header) const {
+  const std::shared_ptr<crow::PersistentData::UserSession> perform_basic_auth(
+      boost::string_view auth_header) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
 
     std::string auth_data;
-    std::string param = auth_header.substr(strlen("Basic "));
+    boost::string_view param = auth_header.substr(strlen("Basic "));
     if (!crow::utility::base64_decode(param, auth_data)) {
       return nullptr;
     }
@@ -102,24 +103,24 @@
     // needed.
     // This whole flow needs to be revisited anyway, as we can't be
     // calling directly into pam for every request
-    return &(PersistentData::session_store->generate_user_session(
-        user, crow::PersistentData::PersistenceType::SINGLE_REQUEST));
+    return PersistentData::session_store->generate_user_session(
+        user, crow::PersistentData::PersistenceType::SINGLE_REQUEST);
   }
 
-  const crow::PersistentData::UserSession* perform_token_auth(
-      const std::string& auth_header) const {
+  const std::shared_ptr<crow::PersistentData::UserSession> perform_token_auth(
+      boost::string_view auth_header) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication";
 
-    std::string token = auth_header.substr(strlen("Token "));
+    boost::string_view token = auth_header.substr(strlen("Token "));
     auto session = PersistentData::session_store->login_session_by_token(token);
     return session;
   }
 
-  const crow::PersistentData::UserSession* perform_xtoken_auth(
+  const std::shared_ptr<crow::PersistentData::UserSession> perform_xtoken_auth(
       const crow::request& req) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
 
-    const std::string& token = req.get_header_value("X-Auth-Token");
+    boost::string_view token = req.get_header_value("X-Auth-Token");
     if (token.empty()) {
       return nullptr;
     }
@@ -127,11 +128,11 @@
     return session;
   }
 
-  const crow::PersistentData::UserSession* perform_cookie_auth(
+  const std::shared_ptr<crow::PersistentData::UserSession> perform_cookie_auth(
       const crow::request& req) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
 
-    auto& cookie_value = req.get_header_value("Cookie");
+    boost::string_view cookie_value = req.get_header_value("Cookie");
     if (cookie_value.empty()) {
       return nullptr;
     }
@@ -145,18 +146,18 @@
     if (end_index == std::string::npos) {
       end_index = cookie_value.size();
     }
-    std::string auth_key =
+    boost::string_view auth_key =
         cookie_value.substr(start_index, end_index - start_index);
 
-    const crow::PersistentData::UserSession* session =
+    const std::shared_ptr<crow::PersistentData::UserSession> session =
         PersistentData::session_store->login_session_by_token(auth_key);
     if (session == nullptr) {
       return nullptr;
     }
 
     // RFC7231 defines methods that need csrf protection
-    if (req.method != "GET"_method) {
-      const std::string& csrf = req.get_header_value("X-XSRF-TOKEN");
+    if (req.method() != "GET"_method) {
+      boost::string_view csrf = req.get_header_value("X-XSRF-TOKEN");
       // Make sure both tokens are filled
       if (csrf.empty() || session->csrf_token.empty()) {
         return nullptr;
@@ -171,21 +172,21 @@
 
   // checks if request can be forwarded without authentication
   bool is_on_whitelist(const crow::request& req) const {
-    // it's allowed to GET root node without authentication
-    if ("GET"_method == req.method) {
-      CROW_LOG_DEBUG << "TESTING ROUTE " << req.url;
+    // it's allowed to GET root node without authentica tion
+    if ("GET"_method == req.method()) {
       if (req.url == "/redfish/v1" || req.url == "/redfish/v1/") {
         return true;
-      } else if (crow::webassets::routes.find(req.url) !=
+      } else if (crow::webassets::routes.find(std::string(req.url)) !=
                  crow::webassets::routes.end()) {
         return true;
       }
     }
 
-    // it's allowed to POST on session collection & login without authentication
-    if ("POST"_method == req.method) {
-      if ((req.url == "/redfish/v1/SessionService/Sessions" ||
-           req.url == "/redfish/v1/SessionService/Sessions/") ||
+    // it's allowed to POST on session collection & login without
+    // authentication
+    if ("POST"_method == req.method()) {
+      if ((req.url == "/redfish/v1/SessionService/Sessions") ||
+          (req.url == "/redfish/v1/SessionService/Sessions/") ||
           (req.url == "/login") || (req.url == "/logout")) {
         return true;
       }
@@ -207,35 +208,38 @@
   CROW_ROUTE(app, "/login")
       .methods(
           "POST"_method)([&](const crow::request& req, crow::response& res) {
-        std::string content_type;
-        auto content_type_it = req.headers.find("content-type");
-        if (content_type_it != req.headers.end()) {
-          content_type = content_type_it->second;
-          boost::algorithm::to_lower(content_type);
-        }
-        const std::string* username;
-        const std::string* password;
+        boost::string_view content_type = req.get_header_value("content-type");
+        boost::string_view username;
+        boost::string_view password;
+
         bool looks_like_ibm = false;
 
-        // This object needs to be declared at this scope so the strings within
-        // it are not destroyed before we can use them
+        // This object needs to be declared at this scope so the strings
+        // within it are not destroyed before we can use them
         nlohmann::json login_credentials;
         // Check if auth was provided by a payload
         if (content_type == "application/json") {
           login_credentials = nlohmann::json::parse(req.body, nullptr, false);
           if (login_credentials.is_discarded()) {
-            res.code = 400;
+            res.result(boost::beast::http::status::bad_request);
             res.end();
             return;
           }
+
           // check for username/password in the root object
           // THis method is how intel APIs authenticate
-          auto user_it = login_credentials.find("username");
-          auto pass_it = login_credentials.find("password");
+          nlohmann::json::iterator user_it = login_credentials.find("username");
+          nlohmann::json::iterator pass_it = login_credentials.find("password");
           if (user_it != login_credentials.end() &&
               pass_it != login_credentials.end()) {
-            username = user_it->get_ptr<const std::string*>();
-            password = pass_it->get_ptr<const std::string*>();
+            const std::string* user_str =
+                user_it->get_ptr<const std::string*>();
+            const std::string* pass_str =
+                pass_it->get_ptr<const std::string*>();
+            if (user_str != nullptr && pass_str != nullptr) {
+              username = *user_str;
+              password = *pass_str;
+            }
           } else {
             // Openbmc appears to push a data object that contains the same
             // keys (username and password), attempt to use that
@@ -245,56 +249,70 @@
               // "password"]
               if (data_it->is_array()) {
                 if (data_it->size() == 2) {
-                  username = (*data_it)[0].get_ptr<const std::string*>();
-                  password = (*data_it)[1].get_ptr<const std::string*>();
+                  nlohmann::json::iterator user_it2 = data_it->begin();
+                  nlohmann::json::iterator pass_it2 = data_it->begin() + 1;
                   looks_like_ibm = true;
+                  if (user_it2 != data_it->end() &&
+                      pass_it2 != data_it->end()) {
+                    const std::string* user_str =
+                        user_it2->get_ptr<const std::string*>();
+                    const std::string* pass_str =
+                        pass_it2->get_ptr<const std::string*>();
+                    if (user_str != nullptr && pass_str != nullptr) {
+                      username = *user_str;
+                      password = *pass_str;
+                    }
+                  }
                 }
+
               } else if (data_it->is_object()) {
-                auto user_it = data_it->find("username");
-                auto pass_it = data_it->find("password");
-                if (user_it != data_it->end() && pass_it != data_it->end()) {
-                  username = user_it->get_ptr<const std::string*>();
-                  password = pass_it->get_ptr<const std::string*>();
+                nlohmann::json::iterator user_it2 = data_it->find("username");
+                nlohmann::json::iterator pass_it2 = data_it->find("password");
+                if (user_it2 != data_it->end() && pass_it2 != data_it->end()) {
+                  const std::string* user_str =
+                      user_it2->get_ptr<const std::string*>();
+                  const std::string* pass_str =
+                      pass_it2->get_ptr<const std::string*>();
+                  if (user_str != nullptr && pass_str != nullptr) {
+                    username = *user_str;
+                    password = *pass_str;
+                  }
                 }
               }
             }
           }
         } else {
-          // check if auth was provided as a query string
-          auto user_it = req.headers.find("username");
-          auto pass_it = req.headers.find("password");
-          if (user_it != req.headers.end() && pass_it != req.headers.end()) {
-            username = &user_it->second;
-            password = &pass_it->second;
-          }
+          // check if auth was provided as a headers
+          username = req.get_header_value("username");
+          password = req.get_header_value("password");
         }
 
-        if (username != nullptr && !username->empty() && password != nullptr &&
-            !password->empty()) {
-          if (!pam_authenticate_user(*username, *password)) {
-            res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
+        if (!username.empty() && !password.empty()) {
+          if (!pam_authenticate_user(username, password)) {
+            res.result(boost::beast::http::status::unauthorized);
           } else {
-            auto& session =
-                PersistentData::session_store->generate_user_session(*username);
+            auto session =
+                PersistentData::session_store->generate_user_session(username);
 
             if (looks_like_ibm) {
               // IBM requires a very specific login structure, and doesn't
               // actually look at the status code.  TODO(ed).... Fix that
               // upstream
-              res.json_value = {{"data", "User '" + *username + "' logged in"},
-                                {"message", "200 OK"},
-                                {"status", "ok"}};
-              res.add_header("Set-Cookie", "XSRF-TOKEN=" + session.csrf_token);
-              res.add_header("Set-Cookie", "SESSION=" + session.session_token +
+              res.json_value = {
+                  {"data", "User '" + std::string(username) + "' logged in"},
+                  {"message", "200 OK"},
+                  {"status", "ok"}};
+              res.add_header("Set-Cookie", "XSRF-TOKEN=" + session->csrf_token);
+              res.add_header("Set-Cookie", "SESSION=" + session->session_token +
                                                "; Secure; HttpOnly");
             } else {
               // if content type is json, assume json token
-              res.json_value = {{"token", session.session_token}};
+              res.json_value = {{"token", session->session_token}};
             }
           }
 
         } else {
-          res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
+          res.result(boost::beast::http::status::bad_request);
         }
         res.end();
       });
@@ -308,7 +326,6 @@
             if (session != nullptr) {
               PersistentData::session_store->remove_session(session);
             }
-            res.code = static_cast<int>(HttpRespCode::OK);
             res.end();
             return;