Redfish(Authorization): Add the privilege in the user session object.

This commit fetches the user privilege during creation of the
session by making D-bus call and add the privilege in the
user session object.

Change-Id: I0e9da8a52df00fc753b13101066ce6d0be9e2ce3
Signed-off-by: Ratan Gupta <ratagupt@linux.vnet.ibm.com>
diff --git a/include/sessions.hpp b/include/sessions.hpp
index 6bc1c99..f8f3e8e 100644
--- a/include/sessions.hpp
+++ b/include/sessions.hpp
@@ -1,17 +1,15 @@
 #pragma once
 
-#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>
+#include <dbus_singleton.hpp>
 #include <nlohmann/json.hpp>
 #include <pam_authenticate.hpp>
 #include <random>
-#include <webassets.hpp>
+
+#include "crow/logging.h"
 
 namespace crow
 {
@@ -25,11 +23,258 @@
     SINGLE_REQUEST // User times out once this request is completed.
 };
 
+constexpr char const* userService = "xyz.openbmc_project.User.Manager";
+constexpr char const* userObjPath = "/xyz/openbmc_project/user";
+constexpr char const* userAttrIface = "xyz.openbmc_project.User.Attributes";
+constexpr char const* dbusPropertiesIface = "org.freedesktop.DBus.Properties";
+
+class SessionStore;
+
+struct UserRoleMap
+{
+    using GetManagedPropertyType =
+        boost::container::flat_map<std::string,
+                                   std::variant<std::string, bool>>;
+
+    using InterfacesPropertiesType =
+        boost::container::flat_map<std::string, GetManagedPropertyType>;
+
+    using GetManagedObjectsType = std::vector<
+        std::pair<sdbusplus::message::object_path, InterfacesPropertiesType>>;
+
+    static UserRoleMap& getInstance()
+    {
+        static UserRoleMap userRoleMap;
+        return userRoleMap;
+    }
+
+    UserRoleMap(const UserRoleMap&) = delete;
+    UserRoleMap& operator=(const UserRoleMap&) = delete;
+
+    std::string getUserRole(std::string_view name)
+    {
+        auto it = roleMap.find(std::string(name));
+        if (it == roleMap.end())
+        {
+            BMCWEB_LOG_ERROR << "User name " << name
+                             << " is not found in the UserRoleMap.";
+            return "";
+        }
+        return it->second;
+    }
+
+    std::string
+        extractUserRole(const InterfacesPropertiesType& interfacesProperties)
+    {
+        auto iface = interfacesProperties.find(userAttrIface);
+        if (iface == interfacesProperties.end())
+        {
+            return {};
+        }
+
+        auto& properties = iface->second;
+        auto property = properties.find("UserPrivilege");
+        if (property == properties.end())
+        {
+            return {};
+        }
+
+        const std::string* role = std::get_if<std::string>(&property->second);
+        if (role == nullptr)
+        {
+            BMCWEB_LOG_ERROR << "UserPrivilege property value is null";
+            return {};
+        }
+
+        return *role;
+    }
+
+  private:
+    void userAdded(sdbusplus::message::message& m)
+    {
+        sdbusplus::message::object_path objPath;
+        InterfacesPropertiesType interfacesProperties;
+
+        try
+        {
+            m.read(objPath, interfacesProperties);
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            BMCWEB_LOG_ERROR << "Failed to parse user add signal."
+                             << "ERROR=" << e.what()
+                             << "REPLY_SIG=" << m.get_signature();
+            return;
+        }
+        BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
+
+        std::size_t lastPos = objPath.str.rfind("/");
+        if (lastPos == std::string::npos)
+        {
+            return;
+        };
+
+        std::string name = objPath.str.substr(lastPos + 1);
+        std::string role = this->extractUserRole(interfacesProperties);
+
+        // Insert the newly added user name and the role
+        auto res = roleMap.emplace(name, role);
+        if (res.second == false)
+        {
+            BMCWEB_LOG_ERROR << "Insertion of the user=\"" << name
+                             << "\" in the roleMap failed.";
+            return;
+        }
+    }
+
+    void userRemoved(sdbusplus::message::message& m)
+    {
+        sdbusplus::message::object_path objPath;
+
+        try
+        {
+            m.read(objPath);
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            BMCWEB_LOG_ERROR << "Failed to parse user delete signal.";
+            BMCWEB_LOG_ERROR << "ERROR=" << e.what()
+                             << "REPLY_SIG=" << m.get_signature();
+            return;
+        }
+
+        BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
+
+        std::size_t lastPos = objPath.str.rfind("/");
+        if (lastPos == std::string::npos)
+        {
+            return;
+        };
+
+        // User name must be atleast 1 char in length.
+        if ((lastPos + 1) >= objPath.str.length())
+        {
+            return;
+        }
+
+        std::string name = objPath.str.substr(lastPos + 1);
+
+        roleMap.erase(name);
+    }
+
+    void userPropertiesChanged(sdbusplus::message::message& m)
+    {
+        std::string interface;
+        GetManagedPropertyType changedProperties;
+        m.read(interface, changedProperties);
+        const std::string path = m.get_path();
+
+        BMCWEB_LOG_DEBUG << "Object Path = \"" << path << "\"";
+
+        std::size_t lastPos = path.rfind("/");
+        if (lastPos == std::string::npos)
+        {
+            return;
+        };
+
+        // User name must be at least 1 char in length.
+        if ((lastPos + 1) == path.length())
+        {
+            return;
+        }
+
+        std::string user = path.substr(lastPos + 1);
+
+        BMCWEB_LOG_DEBUG << "User Name = \"" << user << "\"";
+
+        auto index = changedProperties.find("UserPrivilege");
+        if (index == changedProperties.end())
+        {
+            return;
+        }
+
+        const std::string* role = std::get_if<std::string>(&index->second);
+        if (role == nullptr)
+        {
+            return;
+        }
+        BMCWEB_LOG_DEBUG << "Role = \"" << *role << "\"";
+
+        auto it = roleMap.find(user);
+        if (it == roleMap.end())
+        {
+            BMCWEB_LOG_ERROR << "User Name = \"" << user
+                             << "\" is not found. But, received "
+                                "propertiesChanged signal";
+            return;
+        }
+        it->second = *role;
+    }
+
+    UserRoleMap() :
+        userAddedSignal(
+            *crow::connections::systemBus,
+            sdbusplus::bus::match::rules::interfacesAdded(userObjPath),
+            [this](sdbusplus::message::message& m) {
+                BMCWEB_LOG_DEBUG << "User Added";
+                this->userAdded(m);
+            }),
+        userRemovedSignal(
+            *crow::connections::systemBus,
+            sdbusplus::bus::match::rules::interfacesRemoved(userObjPath),
+            [this](sdbusplus::message::message& m) {
+                BMCWEB_LOG_DEBUG << "User Removed";
+                this->userRemoved(m);
+            }),
+        userPropertiesChangedSignal(
+            *crow::connections::systemBus,
+            sdbusplus::bus::match::rules::path_namespace(userObjPath) +
+                sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::member("PropertiesChanged") +
+                sdbusplus::bus::match::rules::interface(dbusPropertiesIface) +
+                sdbusplus::bus::match::rules::argN(0, userAttrIface),
+            [this](sdbusplus::message::message& m) {
+                BMCWEB_LOG_DEBUG << "Properties Changed";
+                this->userPropertiesChanged(m);
+            })
+    {
+        crow::connections::systemBus->async_method_call(
+            [this](boost::system::error_code ec,
+                   GetManagedObjectsType& managedObjects) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG << "User manager call failed, ignoring";
+                    return;
+                }
+
+                for (auto& managedObj : managedObjects)
+                {
+                    std::size_t lastPos = managedObj.first.str.rfind("/");
+                    if (lastPos == std::string::npos)
+                    {
+                        continue;
+                    };
+                    std::string name = managedObj.first.str.substr(lastPos + 1);
+                    std::string role = extractUserRole(managedObj.second);
+                    roleMap.emplace(name, role);
+                }
+            },
+            userService, userObjPath, "org.freedesktop.DBus.ObjectManager",
+            "GetManagedObjects");
+    }
+
+    boost::container::flat_map<std::string, std::string> roleMap;
+    sdbusplus::bus::match_t userAddedSignal;
+    sdbusplus::bus::match_t userRemovedSignal;
+    sdbusplus::bus::match_t userPropertiesChangedSignal;
+};
+
 struct UserSession
 {
     std::string uniqueId;
     std::string sessionToken;
     std::string username;
+    std::string userRole;
     std::string csrfToken;
     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
     PersistenceType persistence;
@@ -138,8 +383,14 @@
         {
             uniqueId[i] = alphanum[dist(rd)];
         }
+
+        // Get the User Privilege
+        const std::string& role =
+            UserRoleMap::getInstance().getUserRole(username);
+
+        BMCWEB_LOG_DEBUG << "user name=\"" << username << "\" role = " << role;
         auto session = std::make_shared<UserSession>(UserSession{
-            uniqueId, sessionToken, std::string(username), csrfToken,
+            uniqueId, sessionToken, std::string(username), role, csrfToken,
             std::chrono::steady_clock::now(), persistence});
         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
         // Only need to write to disk if session isn't about to be destroyed.
@@ -250,6 +501,7 @@
             }
         }
     }
+
     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
     boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
         authTokens;
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 1c9c1f1..602c216 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -99,6 +99,10 @@
         std::make_shared<sdbusplus::asio::connection>(*io);
     redfish::RedfishService redfish(app);
 
+    // Keep the user role map hot in memory and
+    // track the changes using match object
+    crow::persistent_data::UserRoleMap::getInstance();
+
     app.run();
     io->run();