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));