Redfish privileges

Redfish privilege authorization subsystem controlled by the
privilege_registy.json configuration file.

PropertyOverrides, SubordinateOverrides and ResourceURIOverrides
are not yet implemented.

Change-Id: I4d5670d557f4da172460ada3512e015830dab667
Signed-off-by: Borawski.Lukasz <lukasz.borawski@intel.com>
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/redfish-core/include/node.hpp b/redfish-core/include/node.hpp
index 70b85222..6a58cb2 100644
--- a/redfish-core/include/node.hpp
+++ b/redfish-core/include/node.hpp
@@ -28,13 +28,12 @@
 class Node {
  public:
   template <typename CrowApp, typename... Params>
-  Node(CrowApp& app, PrivilegeProvider& provider, std::string odataType,
-       std::string odataId, Params... params)
-      : odataType(odataType), odataId(odataId) {
-    // privileges for the node as defined in the privileges_registry.json
-    entityPrivileges = provider.getPrivileges(odataId, odataType);
-
-    app.route_dynamic(std::move(odataId))
+  Node(CrowApp& app, const PrivilegeProvider& privilegeProvider,
+       const std::string& entityType, const std::string& entityUrl,
+       Params... params)
+      : entityPrivileges(privilegeProvider.getPrivilegesRequiredByEntity(
+            entityUrl, entityType)) {
+    app.route_dynamic(entityUrl.c_str())
         .methods("GET"_method, "PATCH"_method, "POST"_method,
                  "DELETE"_method)([&](const crow::request& req,
                                       crow::response& res, Params... params) {
@@ -43,15 +42,41 @@
         });
   }
 
+  virtual ~Node() = default;
+
+ protected:
+  // Node is designed to be an abstract class, so doGet is pure virtual
+  virtual void doGet(crow::response& res, const crow::request& req,
+                     const std::vector<std::string>& params) = 0;
+
+  virtual void doPatch(crow::response& res, const crow::request& req,
+                       const std::vector<std::string>& params) {
+    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
+    res.end();
+  }
+
+  virtual void doPost(crow::response& res, const crow::request& req,
+                      const std::vector<std::string>& params) {
+    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
+    res.end();
+  }
+
+  virtual void doDelete(crow::response& res, const crow::request& req,
+                        const std::vector<std::string>& params) {
+    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
+    res.end();
+  }
+
+ private:
   template <typename CrowApp>
   void dispatchRequest(CrowApp& app, const crow::request& req,
                        crow::response& res,
                        const std::vector<std::string>& params) {
-    // drop requests without required privileges
     auto ctx =
         app.template get_context<crow::TokenAuthorization::Middleware>(req);
 
-    if (!entityPrivileges.isMethodAllowed(req.method, ctx.session->username)) {
+    if (!entityPrivileges.isMethodAllowedForUser(req.method,
+                                                 ctx.session->username)) {
       res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
       res.end();
       return;
@@ -81,33 +106,7 @@
     return;
   }
 
- protected:
-  const std::string odataType;
-  const std::string odataId;
-
-  // Node is designed to be an abstract class, so doGet is pure virutal
-  virtual void doGet(crow::response& res, const crow::request& req,
-                     const std::vector<std::string>& params) = 0;
-
-  virtual void doPatch(crow::response& res, const crow::request& req,
-                       const std::vector<std::string>& params) {
-    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
-    res.end();
-  }
-
-  virtual void doPost(crow::response& res, const crow::request& req,
-                      const std::vector<std::string>& params) {
-    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
-    res.end();
-  }
-
-  virtual void doDelete(crow::response& res, const crow::request& req,
-                        const std::vector<std::string>& params) {
-    res.code = static_cast<int>(HttpRespCode::METHOD_NOT_ALLOWED);
-    res.end();
-  }
-
-  EntityPrivileges entityPrivileges;
+  const EntityPrivileges entityPrivileges;
 };
 
 template <typename CrowApp>
diff --git a/redfish-core/include/privileges.hpp b/redfish-core/include/privileges.hpp
index 290c0eb..2441104 100644
--- a/redfish-core/include/privileges.hpp
+++ b/redfish-core/include/privileges.hpp
@@ -15,51 +15,228 @@
 */
 #pragma once
 
+#include <bitset>
+#include <cstdint>
+#include "crow.h"
+#include <boost/container/flat_map.hpp>
+#include <boost/optional.hpp>
+
 namespace redfish {
 
+class PrivilegeProvider;
+
+enum class PrivilegeType { BASE, OEM };
+
+/** @brief Max number of privileges per type  */
+constexpr const size_t MAX_PRIVILEGE_COUNT = 32;
+using privilegeBitset = std::bitset<MAX_PRIVILEGE_COUNT>;
+
 /**
- * @brief  Class used to store privileges for a given user.
+ * @brief Redfish privileges
+ *
+ *        Entity privileges and user privileges are represented by this class.
+ *
+ *        Each incoming connection requires a comparison between privileges held
+ *        by the user issuing a request and the target entity's privileges.
+ *
+ *        To ensure best runtime performance of this comparison, privileges
+ *        are represented as bitsets. Each bit in the bitset corresponds to a
+ *        unique privilege name.
+ *
+ *        Privilege names are read from the privilege_registry.json file and
+ *        stored in flat maps.
+ *
+ *        A bit is set if the privilege is required (entity domain) or granted
+ *        (user domain) and false otherwise.
+ *
+ *        Bitset index to privilege name mapping depends on the order in which
+ *        privileges are defined in PrivilegesUsed and OEMPrivilegesUsed arrays
+ *        in the privilege_registry.json.
  */
-class UserPrivileges {
-  // TODO: Temporary stub, implementation will come with next patch-sets
+class Privileges {
+ public:
+  /**
+   * @brief Retrieves the base privileges bitset
+   *
+   * @return          Bitset representation of base Redfish privileges
+   */
+  privilegeBitset getBasePrivilegeBitset() const { return basePrivilegeBitset; }
+
+  /**
+   * @brief Retrieves the OEM privileges bitset
+   *
+   * @return          Bitset representation of OEM Redfish privileges
+   */
+  privilegeBitset getOEMPrivilegeBitset() const { return oemPrivilegeBitset; }
+
+  /**
+   * @brief Sets given privilege in the bitset
+   *
+   * @param[in] privilege  Privilege to be set
+   *
+   * @return               None
+   */
+  void setSinglePrivilege(const std::string& privilege) {
+    auto index = getBitsetIndexForPrivilege(privilege, PrivilegeType::BASE);
+    if (index) {
+      basePrivilegeBitset.set(*index);
+      return;
+    }
+
+    index = getBitsetIndexForPrivilege(privilege, PrivilegeType::OEM);
+    if (index) {
+      oemPrivilegeBitset.set(*index);
+    }
+  }
+
+  /**
+   * @brief Retrieves names of all active privileges for a given type
+   *
+   * @param[in] type    Base or OEM
+   *
+   * @return            Vector of active privileges
+   */
+  std::vector<std::string> getActivePrivilegeNames(
+      const PrivilegeType type) const {
+    std::vector<std::string> activePrivileges;
+
+    if (type == PrivilegeType::BASE) {
+      for (const auto& pair : basePrivNameToIndexMap) {
+        if (basePrivilegeBitset.test(pair.second)) {
+          activePrivileges.emplace_back(pair.first);
+        }
+      }
+    } else {
+      for (const auto& pair : oemPrivNameToIndexMap) {
+        if (oemPrivilegeBitset.test(pair.second)) {
+          activePrivileges.emplace_back(pair.first);
+        }
+      }
+    }
+
+    return activePrivileges;
+  }
+
  private:
-  uint32_t redfishPrivileges;
-  uint32_t oemPrivileges;
+  boost::optional<size_t> getBitsetIndexForPrivilege(
+      const std::string& privilege, const PrivilegeType type) const {
+    if (type == PrivilegeType::BASE) {
+      const auto pair = basePrivNameToIndexMap.find(privilege);
+      if (pair != basePrivNameToIndexMap.end()) {
+        return pair->second;
+      }
+    } else {
+      const auto pair = oemPrivNameToIndexMap.find(privilege);
+      if (pair != oemPrivNameToIndexMap.end()) {
+        return pair->second;
+      }
+    }
+
+    return boost::none;
+  }
+
+  privilegeBitset basePrivilegeBitset;
+  privilegeBitset oemPrivilegeBitset;
+
+  static boost::container::flat_map<std::string, size_t> basePrivNameToIndexMap;
+  static boost::container::flat_map<std::string, size_t> oemPrivNameToIndexMap;
+
+  friend class PrivilegeProvider;
 };
 
 /**
- * @brief  Class used to store privileges for a given Redfish entity.
+ * @brief  Class used to store privileges for Redfish entities
  */
 class EntityPrivileges {
-  // TODO: Temporary stub, implementation will come with next patch-sets
  public:
-  bool isMethodAllowed(const crow::HTTPMethod& method,
-                       const std::string& username) const {
-    return true;
+  /**
+   * @brief Checks if a user is allowed to call an HTTP method
+   *
+   * @param[in] method       HTTP method
+   * @param[in] user         Username
+   *
+   * @return                 True if method allowed, false otherwise
+   */
+  bool isMethodAllowedForUser(const crow::HTTPMethod method,
+                              const std::string& user) const;
+
+  /**
+   * @brief Checks if given privileges allow to call an HTTP method
+   *
+   * @param[in] method       HTTP method
+   * @param[in] user         Privileges
+   *
+   * @return                 True if method allowed, false otherwise
+   */
+  bool isMethodAllowedWithPrivileges(const crow::HTTPMethod method,
+                                     const Privileges& userPrivileges) const;
+
+  /**
+   * @brief Sets required privileges for a method on a given entity
+   *
+   * @param[in] method       HTTP method
+   * @param[in] privileges   Required privileges
+   *
+   * @return                 None
+   */
+  void addPrivilegesRequiredByMethod(const crow::HTTPMethod method,
+                                     const Privileges& privileges) {
+    methodToPrivilegeMap[method].push_back(privileges);
   }
+
+ private:
+  bool verifyPrivileges(const privilegeBitset userPrivilegeBitset,
+                        const privilegeBitset requiredPrivilegeBitset) const;
+
+  boost::container::flat_map<crow::HTTPMethod, std::vector<Privileges>>
+      methodToPrivilegeMap;
 };
 
 /**
  * @brief  Class used to:
- *         -  read the PrivilegeRegistry file,
- *         -  provide EntityPrivileges objects to callers.
+ *         -  read the privilege_registry.json file
+ *         -  provide EntityPrivileges objects to callers
  *
- *         To save runtime memory object of this class should
- *         exist only for the time required to install all Nodes.
+ *         To save runtime memory, object of this class should
+ *         exist only for the time required to install all Nodes
  */
 class PrivilegeProvider {
-  // TODO: Temporary stub, implementation will come with next patch-sets
  public:
-  PrivilegeProvider() {
-    // load privilege_registry.json to memory
+  PrivilegeProvider(const std::string& privilegeRegistryPath) {
+    // TODO: read this path from the configuration once its available
+    std::ifstream privilegeRegistryFile{privilegeRegistryPath};
+
+    if (privilegeRegistryFile.is_open()) {
+      if (!loadPrivilegesFromFile(privilegeRegistryFile)) {
+        privilegeRegistryJson.clear();
+        CROW_LOG_ERROR << "Couldn't parse privilege_registry.json";
+      }
+    } else {
+      CROW_LOG_ERROR << "Couldn't open privilege_registry.json";
+    }
   }
 
-  EntityPrivileges getPrivileges(const std::string& entity_url,
-                                 const std::string& entity_type) const {
-    // return an entity privilege object based on the privilege_registry.json,
-    // currently returning default constructed object
-    return EntityPrivileges();
-  }
+  /**
+   * @brief Gets required privileges for a certain entity type
+   *
+   * @param[in] entityUrl    Entity url
+   * @param[in] entityType   Entity type
+   *
+   * @return                 EntityPrivilege object
+   */
+  EntityPrivileges getPrivilegesRequiredByEntity(
+      const std::string& entityUrl, const std::string& entityType) const;
+
+ private:
+  bool loadPrivilegesFromFile(std::ifstream& privilegeRegistryFile);
+  bool privilegeRegistryHasRequiredFields() const;
+  bool parseOperationMap(const nlohmann::json& operationMap,
+                         EntityPrivileges& entityPrivileges) const;
+  bool fillPrivilegeMap(const nlohmann::json& privilegesUsed,
+                        boost::container::flat_map<std::string, size_t>&
+                            privilegeToIndexMap) const;
+
+  nlohmann::json privilegeRegistryJson;
 };
 
 }  // namespace redfish
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 0598636..b67c857 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -34,7 +34,8 @@
    */
   template <typename CrowApp>
   RedfishService(CrowApp& app) {
-    auto privilegeProvider = PrivilegeProvider();
+    auto privilegeProvider =
+        PrivilegeProvider("/etc/redfish.conf.d/privilege_registry.json");
 
     nodes.emplace_back(
         std::make_unique<SessionCollection>(app, privilegeProvider));