Session and SessionCollection

New Redfish-Core nodes added (removed from redfish_v1.hpp) - Session
and SessionCollection. Tested manually on x86 VM and Wolfpass Platform.
Behavior almost identical to what was before - differences:
- SessionCollection - now only returns TIMEOUT presistence sessions, not SINGLE
- Aquiring sessions from session storage now applies timeouts

Change-Id: I68bf4fa7fa1c8371216a7d4daa30bbfb653cfa72
Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
diff --git a/include/persistent_data_middleware.hpp b/include/persistent_data_middleware.hpp
index b52e225..9d6195c 100644
--- a/include/persistent_data_middleware.hpp
+++ b/include/persistent_data_middleware.hpp
@@ -4,6 +4,7 @@
 #include <pam_authenticate.hpp>
 #include <webassets.hpp>
 #include <random>
+#include "session_storage_singleton.hpp"
 #include <crow/app.h>
 #include <crow/http_request.h>
 #include <crow/http_response.h>
@@ -16,182 +17,23 @@
 
 namespace PersistentData {
 
-enum class PersistenceType {
-  TIMEOUT,        // User session times out after a predetermined amount of time
-  SINGLE_REQUEST  // User times out once this request is completed.
-};
-
-struct UserSession {
-  std::string unique_id;
-  std::string session_token;
-  std::string username;
-  std::string csrf_token;
-  std::chrono::time_point<std::chrono::steady_clock> last_updated;
-  PersistenceType persistence;
-};
-
-void to_json(nlohmann::json& j, const UserSession& p) {
-  if (p.persistence != PersistenceType::SINGLE_REQUEST) {
-    j = nlohmann::json{{"unique_id", p.unique_id},
-                       {"session_token", p.session_token},
-                       {"username", p.username},
-                       {"csrf_token", p.csrf_token}};
-  }
-}
-
-void from_json(const nlohmann::json& j, UserSession& p) {
-  try {
-    p.unique_id = j.at("unique_id").get<std::string>();
-    p.session_token = j.at("session_token").get<std::string>();
-    p.username = j.at("username").get<std::string>();
-    p.csrf_token = j.at("csrf_token").get<std::string>();
-    // For now, sessions that were persisted through a reboot get their timer
-    // reset.  This could probably be overcome with a better understanding of
-    // wall clock time and steady timer time, possibly persisting values with
-    // wall clock time instead of steady timer, but the tradeoffs of all the
-    // corner cases involved are non-trivial, so this is done temporarily
-    p.last_updated = std::chrono::steady_clock::now();
-  } catch (std::out_of_range) {
-    // do nothing.  Session API incompatibility, leave sessions empty
-  }
-}
-
-class Middleware;
-
-class SessionStore {
- public:
-  const UserSession& generate_user_session(
-      const std::string& username,
-      PersistenceType persistence = PersistenceType::TIMEOUT) {
-    // TODO(ed) find a secure way to not generate session identifiers if
-    // persistence is set to SINGLE_REQUEST
-    static constexpr std::array<char, 62> alphanum = {
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
-        'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
-        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
-        'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
-        'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
-
-    // entropy: 30 characters, 62 possibilities.  log2(62^30) = 178 bits of
-    // entropy.  OWASP recommends at least 60
-    // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
-    std::string session_token;
-    session_token.resize(20, '0');
-    std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
-    for (int i = 0; i < session_token.size(); ++i) {
-      session_token[i] = alphanum[dist(rd)];
-    }
-    // Only need csrf tokens for cookie based auth, token doesn't matter
-    std::string csrf_token;
-    csrf_token.resize(20, '0');
-    for (int i = 0; i < csrf_token.size(); ++i) {
-      csrf_token[i] = alphanum[dist(rd)];
-    }
-
-    std::string unique_id;
-    unique_id.resize(10, '0');
-    for (int i = 0; i < unique_id.size(); ++i) {
-      unique_id[i] = alphanum[dist(rd)];
-    }
-
-    const auto session_it = auth_tokens.emplace(
-        session_token,
-        std::move(UserSession{unique_id, session_token, username, csrf_token,
-                              std::chrono::steady_clock::now(), persistence}));
-    const UserSession& user = (session_it).first->second;
-    // Only need to write to disk if session isn't about to be destroyed.
-    need_write_ = persistence == PersistenceType::TIMEOUT;
-    return user;
-  }
-
-  const UserSession* login_session_by_token(const std::string& token) {
-    apply_session_timeouts();
-    auto session_it = auth_tokens.find(token);
-    if (session_it == auth_tokens.end()) {
-      return nullptr;
-    }
-    UserSession& foo = session_it->second;
-    foo.last_updated = std::chrono::steady_clock::now();
-    return &foo;
-  }
-
-  const UserSession* get_session_by_uid(const std::string& uid) {
-    apply_session_timeouts();
-    // TODO(Ed) this is inefficient
-    auto session_it = auth_tokens.begin();
-    while (session_it != auth_tokens.end()) {
-      if (session_it->second.unique_id == uid) {
-        return &session_it->second;
-      }
-      session_it++;
-    }
-    return nullptr;
-  }
-
-  void remove_session(const UserSession* session) {
-    auth_tokens.erase(session->session_token);
-    need_write_ = true;
-  }
-
-  std::vector<const std::string*> get_unique_ids() {
-    std::vector<const std::string*> ret;
-    ret.reserve(auth_tokens.size());
-    for (auto& session : auth_tokens) {
-      ret.push_back(&session.second.unique_id);
-    }
-    return ret;
-  }
-
-  bool needs_write() { return need_write_; }
-
-  // Persistent data middleware needs to be able to serialize our auth_tokens
-  // structure, which is private
-  friend Middleware;
-
- private:
-  void apply_session_timeouts() {
-    std::chrono::minutes timeout(60);
-    auto time_now = std::chrono::steady_clock::now();
-    if (time_now - last_timeout_update > std::chrono::minutes(1)) {
-      last_timeout_update = time_now;
-      auto auth_tokens_it = auth_tokens.begin();
-      while (auth_tokens_it != auth_tokens.end()) {
-        if (time_now - auth_tokens_it->second.last_updated >= timeout) {
-          auth_tokens_it = auth_tokens.erase(auth_tokens_it);
-          need_write_ = true;
-        } else {
-          auth_tokens_it++;
-        }
-      }
-    }
-  }
-  std::chrono::time_point<std::chrono::steady_clock> last_timeout_update;
-  boost::container::flat_map<std::string, UserSession> auth_tokens;
-  std::random_device rd;
-  bool need_write_{false};
-};
-
 class Middleware {
   // todo(ed) should read this from a fixed location somewhere, not CWD
   static constexpr const char* filename = "bmcweb_persistent_data.json";
   int json_revision = 1;
 
  public:
-  struct context {
-    SessionStore* sessions;
-  };
+  struct context {};
 
   Middleware() { read_data(); }
 
   ~Middleware() {
-    if (sessions.needs_write()) {
+    if (PersistentData::session_store->needs_write()) {
       write_data();
     }
   }
 
-  void before_handle(crow::request& req, response& res, context& ctx) {
-    ctx.sessions = &sessions;
-  }
+  void before_handle(crow::request& req, response& res, context& ctx) {}
 
   void after_handle(request& req, response& res, context& ctx) {}
 
@@ -206,8 +48,8 @@
       auto data = nlohmann::json::parse(persistent_file, nullptr, false);
       if (!data.is_discarded()) {
         file_revision = data.value("revision", 0);
-        sessions.auth_tokens =
-            data.value("sessions", decltype(sessions.auth_tokens)());
+        PersistentData::session_store->auth_tokens =
+            data.value("sessions", decltype(session_store->auth_tokens)());
         system_uuid = data.value("system_uuid", "");
       }
     }
@@ -229,13 +71,12 @@
   void write_data() {
     std::ofstream persistent_file(filename);
     nlohmann::json data;
-    data["sessions"] = sessions.auth_tokens;
+    data["sessions"] = PersistentData::session_store->auth_tokens;
     data["system_uuid"] = system_uuid;
     data["revision"] = json_revision;
     persistent_file << data;
   }
 
-  SessionStore sessions;
   std::string system_uuid;
 };
 
diff --git a/include/redfish_v1.hpp b/include/redfish_v1.hpp
index 1a5ee74..3aecae0 100644
--- a/include/redfish_v1.hpp
+++ b/include/redfish_v1.hpp
@@ -168,9 +168,8 @@
                 for (int user_index = 0; user_index < users.size();
                      user_index++) {
                   member_array.push_back(
-                      {{"@odata.id",
-                        "/redfish/v1/AccountService/Accounts/" +
-                            std::to_string(user_index)}});
+                      {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
+                                         std::to_string(user_index)}});
                 }
                 res.json_value["Members"] = member_array;
               }
@@ -222,110 +221,6 @@
         res.end();
       });
 
-  CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
-      .methods("POST"_method, "GET"_method)([&](const crow::request& req,
-                                                crow::response& res) {
-        auto& session_store =
-            app.template get_middleware<PersistentData::Middleware>().sessions;
-        if (req.method == "POST"_method) {
-          // call with exceptions disabled
-          auto login_credentials =
-              nlohmann::json::parse(req.body, nullptr, false);
-          if (login_credentials.is_discarded()) {
-            res.code = 400;
-            res.end();
-            return;
-          }
-          // check for username/password in the root object
-          auto user_it = login_credentials.find("UserName");
-          auto pass_it = login_credentials.find("Password");
-          if (user_it == login_credentials.end() ||
-              pass_it == login_credentials.end()) {
-            res.code = 400;
-            res.end();
-            return;
-          }
-
-          std::string username = user_it->get<const std::string>();
-          std::string password = pass_it->get<const std::string>();
-          if (username.empty() || password.empty()) {
-            res.code = 400;
-            res.end();
-            return;
-          }
-
-          if (!pam_authenticate_user(username, password)) {
-            res.code = 401;
-            res.end();
-            return;
-          }
-          auto session = session_store.generate_user_session(username);
-          res.code = 200;
-          res.add_header("X-Auth-Token", session.session_token);
-          res.json_value = {
-              {"@odata.context", "/redfish/v1/$metadata#Session"},
-              {"@odata.id",
-               "/redfish/v1/SessionService/Sessions/" + session.unique_id},
-              {"@odata.type", "#Session.v1_0_3.Session"},
-              {"Id", session.unique_id},
-              {"Name", "User Session"},
-              {"Description", "Manager User Session"},
-              {"UserName", username}};
-        } else {  // assume get
-          std::vector<const std::string*> session_ids =
-              session_store.get_unique_ids();
-          res.json_value = {
-              {"@odata.context",
-               "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
-              {"@odata.id", "/redfish/v1/SessionService/Sessions"},
-              {"@odata.type", "#SessionCollection.SessionCollection"},
-              {"Name", "Session Collection"},
-              {"Description", "Session Collection"},
-              {"Members@odata.count", session_ids.size()}
-
-          };
-          nlohmann::json member_array = nlohmann::json::array();
-          for (auto session_uid : session_ids) {
-            member_array.push_back(
-                {{"@odata.id",
-                  "/redfish/v1/SessionService/Sessions/" + *session_uid}});
-          }
-          res.json_value["Members"] = member_array;
-        }
-        res.end();
-      });
-
-  CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
-      .methods("GET"_method, "DELETE"_method)([&](
-          const crow::request& req, crow::response& res,
-          const std::string& session_id) {
-        auto& session_store =
-            app.template get_middleware<PersistentData::Middleware>().sessions;
-        // TODO(Ed) this is inefficient
-        auto session = session_store.get_session_by_uid(session_id);
-
-        if (session == nullptr) {
-          res.code = 404;
-          res.end();
-          return;
-        }
-        if (req.method == "DELETE"_method) {
-          session_store.remove_session(session);
-          res.code = 200;
-        } else {  // assume get
-          res.json_value = {
-              {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
-              {"@odata.id",
-               "/redfish/v1/SessionService/Sessions/" + session->unique_id},
-              {"@odata.type", "#Session.v1_0_3.Session"},
-              {"Id", session->unique_id},
-              {"Name", "User Session"},
-              {"Description", "Manager User Session"},
-              {"UserName", session->username}};
-        }
-        res.end();
-      });
-
   CROW_ROUTE(app, "/redfish/v1/Managers/")
       .methods("GET"_method)(
           [&](const crow::request& req, crow::response& res) {
@@ -365,9 +260,8 @@
             {"Id", "openbmc"},
             {"Name", "OpenBmc Manager"},
             {"Description", "Baseboard Management Controller"},
-            {"UUID",
-             app.template get_middleware<PersistentData::Middleware>()
-                 .system_uuid},
+            {"UUID", app.template get_middleware<PersistentData::Middleware>()
+                         .system_uuid},
             {"Model", "OpenBmc"},  // TODO(ed), get model
             {"DateTime", time_buffer.data()},
             {"Status",
diff --git a/include/session_storage_singleton.hpp b/include/session_storage_singleton.hpp
new file mode 100644
index 0000000..6ff8a0e
--- /dev/null
+++ b/include/session_storage_singleton.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "sessions.hpp"
+
+namespace crow {
+namespace PersistentData {
+
+static std::shared_ptr<SessionStore> session_store;
+
+}  // namespace PersistentData
+}  // namespace crow
diff --git a/include/sessions.hpp b/include/sessions.hpp
new file mode 100644
index 0000000..6d4ab4d
--- /dev/null
+++ b/include/sessions.hpp
@@ -0,0 +1,181 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <pam_authenticate.hpp>
+#include <webassets.hpp>
+#include <random>
+#include <crow/app.h>
+#include <crow/http_request.h>
+#include <crow/http_response.h>
+#include <boost/container/flat_map.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/uuid/uuid_io.hpp>
+
+namespace crow {
+
+namespace PersistentData {
+
+enum class PersistenceType {
+  TIMEOUT,        // User session times out after a predetermined amount of time
+  SINGLE_REQUEST  // User times out once this request is completed.
+};
+
+struct UserSession {
+  std::string unique_id;
+  std::string session_token;
+  std::string username;
+  std::string csrf_token;
+  std::chrono::time_point<std::chrono::steady_clock> last_updated;
+  PersistenceType persistence;
+};
+
+void to_json(nlohmann::json& j, const UserSession& p) {
+  if (p.persistence != PersistenceType::SINGLE_REQUEST) {
+    j = nlohmann::json{{"unique_id", p.unique_id},
+                       {"session_token", p.session_token},
+                       {"username", p.username},
+                       {"csrf_token", p.csrf_token}};
+  }
+}
+
+void from_json(const nlohmann::json& j, UserSession& p) {
+  try {
+    p.unique_id = j.at("unique_id").get<std::string>();
+    p.session_token = j.at("session_token").get<std::string>();
+    p.username = j.at("username").get<std::string>();
+    p.csrf_token = j.at("csrf_token").get<std::string>();
+    // For now, sessions that were persisted through a reboot get their timer
+    // reset.  This could probably be overcome with a better understanding of
+    // wall clock time and steady timer time, possibly persisting values with
+    // wall clock time instead of steady timer, but the tradeoffs of all the
+    // corner cases involved are non-trivial, so this is done temporarily
+    p.last_updated = std::chrono::steady_clock::now();
+  } catch (std::out_of_range) {
+    // do nothing.  Session API incompatibility, leave sessions empty
+  }
+}
+
+class Middleware;
+
+class SessionStore {
+ public:
+  const UserSession& generate_user_session(
+      const std::string& username,
+      PersistenceType persistence = PersistenceType::TIMEOUT) {
+    // TODO(ed) find a secure way to not generate session identifiers if
+    // persistence is set to SINGLE_REQUEST
+    static constexpr std::array<char, 62> alphanum = {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
+        'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
+        'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+        'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
+
+    // entropy: 30 characters, 62 possibilities.  log2(62^30) = 178 bits of
+    // entropy.  OWASP recommends at least 60
+    // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
+    std::string session_token;
+    session_token.resize(20, '0');
+    std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
+    for (int i = 0; i < session_token.size(); ++i) {
+      session_token[i] = alphanum[dist(rd)];
+    }
+    // Only need csrf tokens for cookie based auth, token doesn't matter
+    std::string csrf_token;
+    csrf_token.resize(20, '0');
+    for (int i = 0; i < csrf_token.size(); ++i) {
+      csrf_token[i] = alphanum[dist(rd)];
+    }
+
+    std::string unique_id;
+    unique_id.resize(10, '0');
+    for (int i = 0; i < unique_id.size(); ++i) {
+      unique_id[i] = alphanum[dist(rd)];
+    }
+
+    const auto session_it = auth_tokens.emplace(
+        session_token,
+        std::move(UserSession{unique_id, session_token, username, csrf_token,
+                              std::chrono::steady_clock::now(), persistence}));
+    const UserSession& user = (session_it).first->second;
+    // Only need to write to disk if session isn't about to be destroyed.
+    need_write_ = persistence == PersistenceType::TIMEOUT;
+    return user;
+  }
+
+  const UserSession* login_session_by_token(const std::string& token) {
+    apply_session_timeouts();
+    auto session_it = auth_tokens.find(token);
+    if (session_it == auth_tokens.end()) {
+      return nullptr;
+    }
+    UserSession& foo = session_it->second;
+    foo.last_updated = std::chrono::steady_clock::now();
+    return &foo;
+  }
+
+  const UserSession* get_session_by_uid(const std::string& uid) {
+    apply_session_timeouts();
+    // TODO(Ed) this is inefficient
+    auto session_it = auth_tokens.begin();
+    while (session_it != auth_tokens.end()) {
+      if (session_it->second.unique_id == uid) {
+        return &session_it->second;
+      }
+      session_it++;
+    }
+    return nullptr;
+  }
+
+  void remove_session(const UserSession* session) {
+    auth_tokens.erase(session->session_token);
+    need_write_ = true;
+  }
+
+  std::vector<const std::string*> get_unique_ids(
+      bool getAll = true,
+      const PersistenceType& type = PersistenceType::SINGLE_REQUEST) {
+    apply_session_timeouts();
+
+    std::vector<const std::string*> ret;
+    ret.reserve(auth_tokens.size());
+    for (auto& session : auth_tokens) {
+      if (getAll || type == session.second.persistence) {
+        ret.push_back(&session.second.unique_id);
+      }
+    }
+    return ret;
+  }
+
+  bool needs_write() { return need_write_; }
+
+  // Persistent data middleware needs to be able to serialize our auth_tokens
+  // structure, which is private
+  friend Middleware;
+
+ private:
+  void apply_session_timeouts() {
+    std::chrono::minutes timeout(60);
+    auto time_now = std::chrono::steady_clock::now();
+    if (time_now - last_timeout_update > std::chrono::minutes(1)) {
+      last_timeout_update = time_now;
+      auto auth_tokens_it = auth_tokens.begin();
+      while (auth_tokens_it != auth_tokens.end()) {
+        if (time_now - auth_tokens_it->second.last_updated >= timeout) {
+          auth_tokens_it = auth_tokens.erase(auth_tokens_it);
+          need_write_ = true;
+        } else {
+          auth_tokens_it++;
+        }
+      }
+    }
+  }
+  std::chrono::time_point<std::chrono::steady_clock> last_timeout_update;
+  boost::container::flat_map<std::string, UserSession> auth_tokens;
+  std::random_device rd;
+  bool need_write_{false};
+};
+
+}  // namespaec PersistentData
+}  // namespace crow
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 8099020..7497b43 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -20,23 +20,20 @@
   struct context {
     const crow::PersistentData::UserSession* session;
   };
-  template <typename AllContext>
-  void before_handle(crow::request& req, response& res, context& ctx,
-                     AllContext& allctx) {
-    auto& sessions =
-        allctx.template get<crow::PersistentData::Middleware>().sessions;
+
+  void before_handle(crow::request& req, response& res, context& ctx) {
     std::string auth_header = req.get_header_value("Authorization");
     if (auth_header != "") {
       // Reject any kind of auth other than basic or token
       if (boost::starts_with(auth_header, "Basic ")) {
-        ctx.session = perform_basic_auth(auth_header, sessions);
+        ctx.session = perform_basic_auth(auth_header);
       } else if (boost::starts_with(auth_header, "Token ")) {
-        ctx.session = perform_token_auth(auth_header, sessions);
+        ctx.session = perform_token_auth(auth_header);
       }
     } else if (req.headers.count("X-Auth-Token") == 1) {
-      ctx.session = perform_xtoken_auth(req, sessions);
+      ctx.session = perform_xtoken_auth(req);
     } else if (req.headers.count("Cookie") == 1) {
-      ctx.session = perform_cookie_auth(req, sessions);
+      ctx.session = perform_cookie_auth(req);
     }
 
     if (ctx.session == nullptr && !is_on_whitelist(req)) {
@@ -61,17 +58,13 @@
     if (ctx.session != nullptr &&
         ctx.session->persistence ==
             crow::PersistentData::PersistenceType::SINGLE_REQUEST) {
-      auto& session_store =
-          allctx.template get<crow::PersistentData::Middleware>().sessions;
-
-      session_store->remove_session(ctx.session);
+      PersistentData::session_store->remove_session(ctx.session);
     }
   }
 
  private:
   const crow::PersistentData::UserSession* perform_basic_auth(
-      const std::string& auth_header,
-      crow::PersistentData::SessionStore* sessions) const {
+      const std::string& auth_header) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
 
     std::string auth_data;
@@ -103,33 +96,30 @@
     // needed.
     // This whole flow needs to be revisited anyway, as we can't be
     // calling directly into pam for every request
-    return &(sessions->generate_user_session(
+    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,
-      crow::PersistentData::SessionStore* sessions) const {
+      const std::string& auth_header) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication";
 
     std::string token = auth_header.substr(strlen("Token "));
-    auto session = sessions->login_session_by_token(token);
+    auto session = PersistentData::session_store->login_session_by_token(token);
     return session;
   }
 
   const crow::PersistentData::UserSession* perform_xtoken_auth(
-      const crow::request& req,
-      crow::PersistentData::SessionStore* sessions) const {
+      const crow::request& req) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
 
     auto& token = req.get_header_value("X-Auth-Token");
-    auto session = sessions->login_session_by_token(token);
+    auto session = PersistentData::session_store->login_session_by_token(token);
     return session;
   }
 
   const crow::PersistentData::UserSession* perform_cookie_auth(
-      const crow::request& req,
-      crow::PersistentData::SessionStore* sessions) const {
+      const crow::request& req) const {
     CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
 
     auto& cookie_value = req.get_header_value("Cookie");
@@ -147,7 +137,7 @@
         cookie_value.substr(start_index, end_index - start_index);
 
     const crow::PersistentData::UserSession* session =
-        sessions->login_session_by_token(auth_key);
+        PersistentData::session_store->login_session_by_token(auth_key);
     if (session == nullptr) {
       return nullptr;
     }
@@ -266,10 +256,8 @@
           if (!pam_authenticate_user(username, password)) {
             res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
           } else {
-            auto& context =
-                app.template get_context<PersistentData::Middleware>(req);
-            auto& session_store = context.sessions;
-            auto& session = 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
@@ -279,9 +267,8 @@
                                  {"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");
+              res.add_header("Set-Cookie", "SESSION=" + session.session_token +
+                                               "; Secure; HttpOnly");
 
               res.write(ret.dump());
             } else {
@@ -300,21 +287,19 @@
       });
 
   CROW_ROUTE(app, "/logout")
-      .methods(
-          "POST"_method)([&](const crow::request& req, crow::response& res) {
-        auto& session_store =
-            app.template get_context<PersistentData::Middleware>(req).sessions;
-        auto& session =
-            app.template get_context<TokenAuthorization::Middleware>(req)
-                .session;
-        if (session != nullptr) {
-          session_store->remove_session(session);
-        }
-        res.code = static_cast<int>(HttpRespCode::OK);
-        res.end();
-        return;
+      .methods("POST"_method)(
+          [&](const crow::request& req, crow::response& res) {
+            auto& session =
+                app.template get_context<TokenAuthorization::Middleware>(req)
+                    .session;
+            if (session != nullptr) {
+              PersistentData::session_store->remove_session(session);
+            }
+            res.code = static_cast<int>(HttpRespCode::OK);
+            res.end();
+            return;
 
-      });
+          });
 }
 }  // namespaec TokenAuthorization
 }  // namespace crow
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index c4b765c..c26d90b 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -15,6 +15,7 @@
 */
 #pragma once
 
+#include "../lib/redfish_sessions.hpp"
 #include "../lib/service_root.hpp"
 
 namespace redfish {
@@ -34,10 +35,13 @@
   RedfishService(CrowApp& app) {
     auto privilegeProvider = PrivilegeProvider();
     serviceRootPtr = std::make_unique<ServiceRoot>(app, privilegeProvider);
+    sessionsCollectionPtr =
+        std::make_unique<SessionCollection>(app, privilegeProvider);
   }
 
  private:
   std::unique_ptr<ServiceRoot> serviceRootPtr;
+  std::unique_ptr<SessionCollection> sessionsCollectionPtr;
 };
 
 }  // namespace redfish
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
new file mode 100644
index 0000000..0835fa1
--- /dev/null
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -0,0 +1,226 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <tuple>
+#include "node.hpp"
+#include "session_storage_singleton.hpp"
+
+namespace redfish {
+
+class SessionCollection;
+
+class Sessions : public Node {
+ public:
+  template <typename CrowApp, typename PrivilegeProvider>
+  Sessions(CrowApp& app, PrivilegeProvider& provider)
+      : Node(app, provider, "#Session.v1_0_2.Session",
+             "/redfish/v1/SessionService/Sessions/<str>", std::string()) {
+    nodeJson["@odata.type"] = Node::odataType;
+    nodeJson["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
+    nodeJson["Name"] = "User Session";
+    nodeJson["Description"] = "Manager User Session";
+  }
+
+ private:
+  void doGet(crow::response& res, const crow::request& req,
+             const std::vector<std::string>& params) override {
+    auto session =
+        crow::PersistentData::session_store->get_session_by_uid(params[0]);
+
+    if (session == nullptr) {
+      res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+      res.end();
+      return;
+    }
+
+    nodeJson["Id"] = session->unique_id;
+    nodeJson["UserName"] = session->username;
+    nodeJson["@odata.id"] =
+        "/redfish/v1/SessionService/Sessions/" + session->unique_id;
+
+    res.json_value = nodeJson;
+    res.end();
+  }
+
+  void doDelete(crow::response& res, const crow::request& req,
+                const std::vector<std::string>& params) override {
+    // Need only 1 param which should be id of session to be deleted
+    if (params.size() != 1) {
+      res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
+      res.end();
+      return;
+    }
+
+    auto session =
+        crow::PersistentData::session_store->get_session_by_uid(params[0]);
+
+    if (session == nullptr) {
+      res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+      res.end();
+      return;
+    }
+
+    crow::PersistentData::session_store->remove_session(session);
+    res.code = static_cast<int>(HttpRespCode::OK);
+    res.end();
+  }
+
+  /**
+   * This allows SessionCollection to reuse this class' doGet method, to
+   * maintain consistency of returned data, as Collection's doPost should return
+   * data for created member which should match member's doGet result in 100%
+   */
+  friend SessionCollection;
+
+  nlohmann::json nodeJson;
+};
+
+class SessionCollection : public Node {
+ public:
+  template <typename CrowApp, typename PrivilegeProvider>
+  SessionCollection(CrowApp& app, PrivilegeProvider& provider)
+      : Node(app, provider, "#SessionCollection.SessionCollection",
+             "/redfish/v1/SessionService/Sessions/"),
+        memberSession(app, provider) {
+    nodeJson["@odata.type"] = Node::odataType;
+    nodeJson["@odata.id"] = Node::odataId;
+    nodeJson["@odata.context"] =
+        "/redfish/v1/$metadata#SessionCollection.SessionCollection";
+    nodeJson["Name"] = "Session Collection";
+    nodeJson["Description"] = "Session Collection";
+    nodeJson["Members@odata.count"] = 0;
+    nodeJson["Members"] = nlohmann::json::array();
+  }
+
+ private:
+  void doGet(crow::response& res, const crow::request& req,
+             const std::vector<std::string>& params) override {
+    std::vector<const std::string*> session_ids =
+        crow::PersistentData::session_store->get_unique_ids(
+            false, crow::PersistentData::PersistenceType::TIMEOUT);
+
+    nodeJson["Members@odata.count"] = session_ids.size();
+    nodeJson["Members"] = nlohmann::json::array();
+    for (const auto& uid : session_ids) {
+      nodeJson["Members"].push_back(
+          {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
+    }
+
+    res.json_value = nodeJson;
+    res.end();
+  }
+
+  void doPost(crow::response& res, const crow::request& req,
+              const std::vector<std::string>& params) override {
+    std::string username;
+    bool userAuthSuccessful = authenticateUser(req, &res.code, &username);
+
+    if (!userAuthSuccessful) {
+      res.end();
+      return;
+    }
+
+    // User is authenticated - create session for him
+    auto session =
+        crow::PersistentData::session_store->generate_user_session(username);
+    res.add_header("X-Auth-Token", session.session_token);
+
+    // Return data for created session
+    memberSession.doGet(res, req, {session.unique_id});
+
+    // No need for res.end(), as it is called by doGet()
+  }
+
+  /**
+   * @brief Verifies data provided in request and tries to authenticate user
+   *
+   * @param[in]  req            Crow request containing authentication data
+   * @param[out] httpRespCode   HTTP Code that should be returned in response
+   * @param[out] user           Retrieved username - not filled on failure
+   *
+   * @return true if authentication was successful, false otherwise
+   */
+  bool authenticateUser(const crow::request& req, int* httpRespCode,
+                        std::string* user) {
+    // We need only UserName and Password - nothing more, nothing less
+    static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
+
+    // call with exceptions disabled
+    auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
+    if (login_credentials.is_discarded()) {
+      *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+      return false;
+    }
+
+    // Check that there are only as many fields as there should be
+    if (login_credentials.size() != numberOfRequiredFieldsInReq) {
+      *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+      return false;
+    }
+
+    // Find fields that we need - UserName and Password
+    auto user_it = login_credentials.find("UserName");
+    auto pass_it = login_credentials.find("Password");
+    if (user_it == login_credentials.end() ||
+        pass_it == login_credentials.end()) {
+      *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+      return false;
+    }
+
+    // Check that given data is of valid type (string)
+    if (!user_it->is_string() || !pass_it->is_string()) {
+      *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+      return false;
+    }
+
+    // Extract username and password
+    std::string username = user_it->get<const std::string>();
+    std::string password = pass_it->get<const std::string>();
+
+    // Verify that required fields are not empty
+    if (username.empty() || password.empty()) {
+      *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+      return false;
+    }
+
+    // Finally - try to authenticate user
+    if (!pam_authenticate_user(username, password)) {
+      *httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
+
+      return false;
+    }
+
+    // User authenticated successfully
+    *httpRespCode = static_cast<int>(HttpRespCode::OK);
+    *user = username;
+
+    return true;
+  }
+
+  /**
+   * Member session to ensure consistency between collection's doPost and
+   * member's doGet, as they should return 100% matching data
+   */
+  Sessions memberSession;
+  nlohmann::json nodeJson;
+};
+
+}  // namespace redfish
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 834a5df..f504cc7 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -12,10 +12,10 @@
 #include <webassets.hpp>
 #include <memory>
 #include <string>
+#include "redfish.hpp"
 #include <crow/app.h>
 #include <boost/asio.hpp>
 #include <systemd/sd-daemon.h>
-#include "redfish.hpp"
 
 constexpr int defaultPort = 18080;
 
@@ -41,6 +41,8 @@
 
 int main(int argc, char** argv) {
   auto io = std::make_shared<boost::asio::io_service>();
+  crow::PersistentData::session_store =
+      std::make_shared<crow::PersistentData::SessionStore>();
   crow::App<crow::PersistentData::Middleware,
             crow::TokenAuthorization::Middleware,
             crow::SecurityHeadersMiddleware>