Redfish Session : Support ClientOriginIPAddress

This commit implements the ClientOriginIPAddress property on
the session resource. The IP address is persisted across the reboot

Tested by:
  1. Create session
     POST https://${bmc}/redfish/v1/SessionService/Sessions -d '{"UserName":<>, "Password":<>}'
  2. Check the session gets updated with the ClientOriginIPAddress
     GET https://${bmc}/redfish/v1/SessionService/Sessions/<id>
  3. Redfish validator passed
  4. Create session and reboot the BMC to ensure the IP address is persisted
  5. Tested the basic auth populates the clientIp at req

Signed-off-by: Sunitha Harish <sunharis@in.ibm.com>
Change-Id: Iaa60d0657c991bde4bcf6c86819055c71c92e421
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index ec8e9d5..6610bee 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -241,7 +241,8 @@
             session =
                 persistent_data::SessionStore::getInstance()
                     .generateUserSession(
-                        sslUser, persistent_data::PersistenceType::TIMEOUT);
+                        sslUser, persistent_data::PersistenceType::TIMEOUT,
+                        false, req->ipAddress.to_string());
             if (auto sp = session.lock())
             {
                 BMCWEB_LOG_DEBUG << this
@@ -278,6 +279,10 @@
     {
 
         startDeadline(0);
+
+        // Fetch the client IP address
+        readClientIp();
+
         // TODO(ed) Abstract this to a more clever class with the idea of an
         // asynchronous "start"
         if constexpr (std::is_same_v<Adaptor,
@@ -319,7 +324,7 @@
         BMCWEB_LOG_INFO << "Request: "
                         << " " << this << " HTTP/" << req->version() / 10 << "."
                         << req->version() % 10 << ' ' << req->methodString()
-                        << " " << req->target();
+                        << " " << req->target() << " " << req->ipAddress;
 
         needToCallAfterHandlers = false;
 
@@ -461,6 +466,26 @@
         res.completeRequestHandler = nullptr;
     }
 
+    void readClientIp()
+    {
+        boost::system::error_code ec;
+        BMCWEB_LOG_DEBUG << "Fetch the client IP address";
+        boost::asio::ip::tcp::endpoint endpoint =
+            boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
+
+        if (ec)
+        {
+            // If remote endpoint fails keep going. "ClientOriginIPAddress"
+            // will be empty.
+            BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : "
+                             << ec;
+        }
+        else
+        {
+            req->ipAddress = endpoint.address();
+        }
+    }
+
   private:
     void doReadHeaders()
     {
diff --git a/http/http_request.hpp b/http/http_request.hpp
index e5c0c9e..8bcce06 100644
--- a/http/http_request.hpp
+++ b/http/http_request.hpp
@@ -4,6 +4,7 @@
 #include "sessions.hpp"
 
 #include <boost/asio/io_context.hpp>
+#include <boost/asio/ip/address.hpp>
 #include <boost/beast/http/message.hpp>
 #include <boost/beast/http/string_body.hpp>
 #include <boost/beast/websocket.hpp>
@@ -24,6 +25,7 @@
     const std::string& body;
 
     boost::asio::io_context* ioService{};
+    boost::asio::ip::address ipAddress{};
 
     std::shared_ptr<persistent_data::UserSession> session;
 
diff --git a/include/authorization.hpp b/include/authorization.hpp
index e965508..0f73e96 100644
--- a/include/authorization.hpp
+++ b/include/authorization.hpp
@@ -35,7 +35,8 @@
 }
 
 static std::shared_ptr<persistent_data::UserSession>
-    performBasicAuth(std::string_view auth_header)
+    performBasicAuth(const boost::asio::ip::address& clientIp,
+                     std::string_view auth_header)
 {
     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
 
@@ -60,6 +61,8 @@
     std::string pass = authData.substr(separator);
 
     BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
+    BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: "
+                     << clientIp.to_string();
 
     int pamrc = pamAuthenticateUser(user, pass);
     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
@@ -76,7 +79,7 @@
     // calling directly into pam for every request
     return persistent_data::SessionStore::getInstance().generateUserSession(
         user, persistent_data::PersistenceType::SINGLE_REQUEST,
-        isConfigureSelfOnly);
+        isConfigureSelfOnly, clientIp.to_string());
 }
 
 static std::shared_ptr<persistent_data::UserSession>
@@ -269,7 +272,7 @@
             else if (boost::starts_with(authHeader, "Basic ") &&
                      authMethodsConfig.basic)
             {
-                req.session = performBasicAuth(authHeader);
+                req.session = performBasicAuth(req.ipAddress, authHeader);
             }
         }
     }
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index 1f7b35a..6879de0 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -141,7 +141,7 @@
                             .generateUserSession(
                                 username,
                                 persistent_data::PersistenceType::TIMEOUT,
-                                isConfigureSelfOnly);
+                                isConfigureSelfOnly, req.ipAddress.to_string());
 
                     if (looksLikePhosphorRest)
                     {
diff --git a/include/persistent_data.hpp b/include/persistent_data.hpp
index 19c424a..0ff0c7e 100644
--- a/include/persistent_data.hpp
+++ b/include/persistent_data.hpp
@@ -193,6 +193,7 @@
                     {"session_token", p.second->sessionToken},
                     {"username", p.second->username},
                     {"csrf_token", p.second->csrfToken},
+                    {"client_ip", p.second->clientIp},
 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
                     {"client_id", p.second->clientId},
 #endif
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index e325989..c38f102 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -60,15 +60,15 @@
         res.jsonValue["UserName"] = session->username;
         res.jsonValue["@odata.id"] =
             "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
-        res.jsonValue["@odata.type"] = "#Session.v1_0_2.Session";
+        res.jsonValue["@odata.type"] = "#Session.v1_3_0.Session";
         res.jsonValue["Name"] = "User Session";
         res.jsonValue["Description"] = "Manager User Session";
+        res.jsonValue["ClientOriginIPAddress"] = session->clientIp;
+#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
         res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] =
             "#OemSession.v1_0_0.Session";
-#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
         res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session->clientId;
 #endif
-        res.jsonValue["Oem"]["OpenBMC"]["ClientOriginIP"] = session->clientIp;
         res.end();
     }
 
@@ -174,7 +174,6 @@
         std::string password;
         std::optional<nlohmann::json> oemObject;
         std::string clientId;
-        std::string clientIp;
         if (!json_util::readJson(req, res, "UserName", username, "Password",
                                  password, "Oem", oemObject))
         {
@@ -231,7 +230,7 @@
         std::shared_ptr<persistent_data::UserSession> session =
             persistent_data::SessionStore::getInstance().generateUserSession(
                 username, persistent_data::PersistenceType::TIMEOUT,
-                isConfigureSelfOnly, clientId, clientIp);
+                isConfigureSelfOnly, clientId, req.ipAddress.to_string());
         res.addHeader("X-Auth-Token", session->sessionToken);
         res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" +
                                       session->uniqueId);
diff --git a/static/redfish/v1/JsonSchemas/OemSession/index.json b/static/redfish/v1/JsonSchemas/OemSession/index.json
index c80e340..8eefd95 100644
--- a/static/redfish/v1/JsonSchemas/OemSession/index.json
+++ b/static/redfish/v1/JsonSchemas/OemSession/index.json
@@ -28,12 +28,6 @@
                     "readonly": true,
                     "type" : "string"
                 },
-                "ClientOriginIP" : {
-                     "description": "The IP address where the Session was created from.",
-                     "longDescription": "This property shall contain the IP address where the client created the session from.",
-                     "readonly": true,
-                     "type": "string"
-                },
             "type": "object"
             }
         }
diff --git a/static/redfish/v1/schema/OemSession_v1.xml b/static/redfish/v1/schema/OemSession_v1.xml
index cc74848..f02f861 100644
--- a/static/redfish/v1/schema/OemSession_v1.xml
+++ b/static/redfish/v1/schema/OemSession_v1.xml
@@ -32,10 +32,6 @@
               <Annotation Term="OData.Description" String="The Id of the client creating this session."/>
               <Annotation Term="OData.LongDescription" String="This will be the unique identifier set by the client."/>
             </Property>
-            <Property Name="ClientOriginIP" Type="Edm.String">
-              <Annotation Term="OData.Description" String="The IP address where the Session was created from."/>
-              <Annotation Term="OData.LongDescription" String="This property shall contain the IP address where the client created the session from."/>
-            </Property>
 
       </EntityType>
     </Schema>