Handle Redfish PasswordChangeRequired

This enhances BMCWeb authentication to recognize when the user's password is
correct but expired.  The Redfish SessionService is enhanced to comply with
the Redfish PasswordChangeRequired spec which allows the session to be
created, but limits that sesion to changing the password only, and includes
the PasswordChangeRequired message in the response body.

Specifically, when the account's password is expired, a successful
authentication via the following interfaces will have these results:
- POST /redfish/v1/SessionService/Sessions -- follows Redfish spec
- POST /login -- creates a session limited to changing the password,
                 similar to Redfish
- Basic authentication -- continues to treat the password
  change required condition as an authentication failure and gives no
  indication the password is expired.
- Cookie auth -- works as before
- Token auth -- works as before

This patchset is intended to allow web applications to use the presence
of the Redfish PasswordChangeRequired message or the extendedMessage
field to trigger the password change dialog.

This does not implement the PasswordChangeRequired property in the
ManagerAccount resource.

This implements the Redfish privilege overrides associated with the
ConfigureSelf privilege.  Specifically, this correctly implements the
Password property override, and the ManagerAccount Resource URI override.

When an API results in 403 Forbidden and the issuing session has the
PasswordChangeRequired condition, appropriate JSON is given.

Tested:
  Yes, see https://github.com/openbmc/bmcweb/issues/103
  No, did not run Redfish validator

Signed-off-by: Joseph Reynolds <joseph-reynolds@charter.net>
Change-Id: Ibbf5f6414ac55c0e7bea14c721f6db227b52fe40
diff --git a/crow/include/crow/routing.h b/crow/include/crow/routing.h
index 0be5dbd..7c27ff3 100644
--- a/crow/include/crow/routing.h
+++ b/crow/include/crow/routing.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "json_msg_utility.hpp"
 #include "privileges.hpp"
 #include "sessions.hpp"
 
@@ -9,6 +10,7 @@
 #include <cerrno>
 #include <cstdint>
 #include <cstdlib>
+#include <error_messages.hpp>
 #include <limits>
 #include <memory>
 #include <tuple>
@@ -1233,44 +1235,109 @@
         redfish::Privileges userPrivileges;
         if (req.session != nullptr)
         {
-            // Get the user role from the session.
-            const std::string& userRole =
-                persistent_data::UserRoleMap::getInstance().getUserRole(
-                    req.session->username);
+            if (!req.session->isConfigureSelfOnly)
+            {
+                // Get the user role from the session.
+                const std::string& userRole =
+                    persistent_data::UserRoleMap::getInstance().getUserRole(
+                        req.session->username);
 
-            BMCWEB_LOG_DEBUG << "USER ROLE=" << userRole;
+                BMCWEB_LOG_DEBUG << "USER ROLE=" << userRole;
 
-            // Get the user privileges from the role
-            userPrivileges = redfish::getUserPrivileges(userRole);
+                // Get the user's Redfish Privileges from the role.
+                userPrivileges = redfish::getUserPrivileges(userRole);
+            }
+            else
+            {
+                BMCWEB_LOG_DEBUG << "Session limited to ConfigureSelf";
+                userPrivileges = {"ConfigureSelf"};
+            }
         }
 
         if (!rules[ruleIndex]->checkPrivileges(userPrivileges))
         {
-            res.result(boost::beast::http::status::forbidden);
+            if (boost::starts_with(req.url, "/redfish/"))
+            {
+                if (req.session == nullptr)
+                {
+                    redfish::messages::noValidSession(res);
+                }
+                else
+                {
+                    redfish::messages::insufficientPrivilege(res);
+                    if (req.session->isConfigureSelfOnly)
+                    {
+                        redfish::messages::passwordChangeRequired(
+                            res, "/redfish/v1/AccountService/Accounts/" +
+                                     req.session->username);
+                    }
+                }
+            }
+#ifdef BMCWEB_ENABLE_DBUS_REST
+            else if (boost::starts_with(req.url, "/xyz/") ||
+                     boost::starts_with(req.url, "/bus/") ||
+                     boost::starts_with(req.url, "/list/") ||
+                     boost::starts_with(req.url, "/org/") ||
+                     boost::starts_with(req.url, "/action/") ||
+                     boost::starts_with(req.url, "/enumerate/") ||
+                     boost::starts_with(req.url, "/download/"))
+            {
+                if (req.session != nullptr)
+                {
+                    crow::setErrorResponse(
+                        res, boost::beast::http::status::forbidden,
+                        "The session is not authorized to access URI: " +
+                            std::string(req.url),
+                        crow::msg::forbiddenMsg);
+                    if (req.session->isConfigureSelfOnly)
+                    {
+                        crow::setPasswordChangeRequired(res,
+                                                        req.session->username);
+                    }
+                }
+                else
+                {
+                    // How can we get here?
+                    crow::setErrorResponse(
+                        res, boost::beast::http::status::unauthorized,
+                        crow::msg::unAuthMsg,
+                        "The request is not authenticated");
+                    if (req.getHeaderValue("User-Agent").empty())
+                    {
+                        res.addHeader("WWW-Authenticate", "Basic");
+                    }
+                }
+            }
+            else
+            {
+                res.result(boost::beast::http::status::forbidden);
+            }
             res.end();
-            return;
         }
-
-        // any uncaught exceptions become 500s
-        try
+#endif
+        else
         {
-            rules[ruleIndex]->handle(req, res, found.second);
-        }
-        catch (std::exception& e)
-        {
-            BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what();
-            res.result(boost::beast::http::status::internal_server_error);
-            res.end();
-            return;
-        }
-        catch (...)
-        {
-            BMCWEB_LOG_ERROR
-                << "An uncaught exception occurred. The type was unknown "
-                   "so no information was available.";
-            res.result(boost::beast::http::status::internal_server_error);
-            res.end();
-            return;
+            try
+            {
+                rules[ruleIndex]->handle(req, res, found.second);
+            }
+            catch (std::exception& e)
+            {
+                BMCWEB_LOG_ERROR << "An uncaught exception occurred: "
+                                 << e.what();
+                res.result(boost::beast::http::status::internal_server_error);
+                res.end();
+                return;
+            }
+            catch (...)
+            {
+                BMCWEB_LOG_ERROR
+                    << "An uncaught exception occurred. The type was unknown "
+                       "so no information was available.";
+                res.result(boost::beast::http::status::internal_server_error);
+                res.end();
+                return;
+            }
         }
     }
 
diff --git a/include/json_msg_utility.hpp b/include/json_msg_utility.hpp
new file mode 100644
index 0000000..e8c8b7f
--- /dev/null
+++ b/include/json_msg_utility.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "crow/http_response.h"
+
+namespace crow
+{
+namespace msg
+{
+const std::string notFoundMsg = "404 Not Found";
+const std::string badReqMsg = "400 Bad Request";
+const std::string methodNotAllowedMsg = "405 Method Not Allowed";
+const std::string forbiddenMsg = "403 Forbidden";
+const std::string methodFailedMsg = "500 Method Call Failed";
+const std::string methodOutputFailedMsg = "500 Method Output Error";
+const std::string unAuthMsg = "401 Unauthorized"; // Unauthenticated
+
+const std::string notFoundDesc =
+    "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
+const std::string propNotFoundDesc = "The specified property cannot be found";
+const std::string noJsonDesc = "No JSON object could be decoded";
+const std::string methodNotFoundDesc = "The specified method cannot be found";
+const std::string methodNotAllowedDesc = "Method not allowed";
+const std::string forbiddenPropDesc =
+    "The specified property cannot be created";
+const std::string forbiddenResDesc = "The specified resource cannot be created";
+const std::string unAuthDesc = "The authentication could not be applied";
+const std::string forbiddenURIDesc = "The session is not authorized to access URI: ";
+
+} // namespace msg
+
+inline void setErrorResponse(crow::Response &res,
+                             boost::beast::http::status result,
+                             const std::string &desc, const std::string &msg)
+{
+    res.result(result);
+    res.jsonValue = {{"data", {{"description", desc}}},
+                     {"message", msg},
+                     {"status", "error"}};
+}
+
+inline void setPasswordChangeRequired(crow::Response &res,
+                                      const std::string &username)
+{
+    res.jsonValue["extendedMessage"] =
+        "The password for this account must be changed.  PATCH the 'Password' "
+        "property for the account under URI: "
+        "/redfish/v1/AccountService/Accounts/" +
+        username;
+}
+
+} // namespace crow
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 7839e65..b063fa6 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -23,6 +23,7 @@
 #include <dbus_utility.hpp>
 #include <filesystem>
 #include <fstream>
+#include <json_msg_utility.hpp>
 #include <regex>
 #include <sdbusplus/message/types.hpp>
 
@@ -30,37 +31,10 @@
 {
 namespace openbmc_mapper
 {
-
 using GetSubTreeType = std::vector<
     std::pair<std::string,
               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
 
-const std::string notFoundMsg = "404 Not Found";
-const std::string badReqMsg = "400 Bad Request";
-const std::string methodNotAllowedMsg = "405 Method Not Allowed";
-const std::string forbiddenMsg = "403 Forbidden";
-const std::string methodFailedMsg = "500 Method Call Failed";
-const std::string methodOutputFailedMsg = "500 Method Output Error";
-
-const std::string notFoundDesc =
-    "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
-const std::string propNotFoundDesc = "The specified property cannot be found";
-const std::string noJsonDesc = "No JSON object could be decoded";
-const std::string methodNotFoundDesc = "The specified method cannot be found";
-const std::string methodNotAllowedDesc = "Method not allowed";
-const std::string forbiddenPropDesc =
-    "The specified property cannot be created";
-const std::string forbiddenResDesc = "The specified resource cannot be created";
-
-void setErrorResponse(crow::Response &res, boost::beast::http::status result,
-                      const std::string &desc, const std::string &msg)
-{
-    res.result(result);
-    res.jsonValue = {{"data", {{"description", desc}}},
-                     {"message", msg},
-                     {"status", "error"}};
-}
-
 void introspectObjects(const std::string &processName,
                        const std::string &objectPath,
                        std::shared_ptr<bmcweb::AsyncResp> transaction)
@@ -418,17 +392,19 @@
             {
                 if (!methodFailed)
                 {
-                    setErrorResponse(res, boost::beast::http::status::not_found,
-                                     methodNotFoundDesc, notFoundMsg);
+                    crow::setErrorResponse(
+                        res, boost::beast::http::status::not_found,
+                        crow::msg::methodNotFoundDesc, crow::msg::notFoundMsg);
                 }
             }
             else
             {
                 if (outputFailed)
                 {
-                    setErrorResponse(
+                    crow::setErrorResponse(
                         res, boost::beast::http::status::internal_server_error,
-                        "Method output failure", methodOutputFailedMsg);
+                        "Method output failure",
+                        crow::msg::methodOutputFailedMsg);
                 }
                 else
                 {
@@ -444,8 +420,8 @@
 
     void setErrorStatus(const std::string &desc)
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request, desc,
-                         badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               desc, crow::msg::badReqMsg);
     }
     crow::Response &res;
     std::string path;
@@ -1442,7 +1418,7 @@
 
                                         if (e)
                                         {
-                                            setErrorResponse(
+                                            crow::setErrorResponse(
                                                 transaction->res,
                                                 boost::beast::http::status::
                                                     bad_request,
@@ -1450,12 +1426,12 @@
                                         }
                                         else
                                         {
-                                            setErrorResponse(
+                                            crow::setErrorResponse(
                                                 transaction->res,
                                                 boost::beast::http::status::
                                                     bad_request,
                                                 "Method call failed",
-                                                methodFailedMsg);
+                                                crow::msg::methodFailedMsg);
                                         }
                                         return;
                                     }
@@ -1489,24 +1465,24 @@
 
     if (requestDbusData.is_discarded())
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request,
-                         noJsonDesc, badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               crow::msg::noJsonDesc, crow::msg::badReqMsg);
         res.end();
         return;
     }
     nlohmann::json::iterator data = requestDbusData.find("data");
     if (data == requestDbusData.end())
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request,
-                         noJsonDesc, badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               crow::msg::noJsonDesc, crow::msg::badReqMsg);
         res.end();
         return;
     }
 
     if (!data->is_array())
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request,
-                         noJsonDesc, badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               crow::msg::noJsonDesc, crow::msg::badReqMsg);
         res.end();
         return;
     }
@@ -1523,9 +1499,9 @@
             if (ec || interfaceNames.size() <= 0)
             {
                 BMCWEB_LOG_ERROR << "Can't find object";
-                setErrorResponse(transaction->res,
-                                 boost::beast::http::status::not_found,
-                                 notFoundDesc, notFoundMsg);
+                crow::setErrorResponse(
+                    transaction->res, boost::beast::http::status::not_found,
+                    crow::msg::notFoundDesc, crow::msg::notFoundMsg);
                 return;
             }
 
@@ -1557,9 +1533,10 @@
             if (ec || interfaceNames.size() <= 0)
             {
                 BMCWEB_LOG_ERROR << "Can't find object";
-                setErrorResponse(res,
-                                 boost::beast::http::status::method_not_allowed,
-                                 methodNotAllowedDesc, methodNotAllowedMsg);
+                crow::setErrorResponse(
+                    res, boost::beast::http::status::method_not_allowed,
+                    crow::msg::methodNotAllowedDesc,
+                    crow::msg::methodNotAllowedMsg);
                 res.end();
                 return;
             }
@@ -1589,8 +1566,9 @@
                std::vector<std::string> &objectPaths) {
             if (ec)
             {
-                setErrorResponse(res, boost::beast::http::status::not_found,
-                                 notFoundDesc, notFoundMsg);
+                crow::setErrorResponse(
+                    res, boost::beast::http::status::not_found,
+                    crow::msg::notFoundDesc, crow::msg::notFoundMsg);
             }
             else
             {
@@ -1628,9 +1606,10 @@
             {
                 BMCWEB_LOG_ERROR << "GetSubTree failed on "
                                  << transaction->objectPath;
-                setErrorResponse(transaction->asyncResp->res,
-                                 boost::beast::http::status::not_found,
-                                 notFoundDesc, notFoundMsg);
+                crow::setErrorResponse(transaction->asyncResp->res,
+                                       boost::beast::http::status::not_found,
+                                       crow::msg::notFoundDesc,
+                                       crow::msg::notFoundMsg);
                 return;
             }
 
@@ -1661,8 +1640,9 @@
                                    const GetObjectType &object_names) {
             if (ec || object_names.size() <= 0)
             {
-                setErrorResponse(res, boost::beast::http::status::not_found,
-                                 notFoundDesc, notFoundMsg);
+                crow::setErrorResponse(
+                    res, boost::beast::http::status::not_found,
+                    crow::msg::notFoundDesc, crow::msg::notFoundMsg);
                 res.end();
                 return;
             }
@@ -1678,8 +1658,9 @@
 
                 if (interfaceNames.size() <= 0)
                 {
-                    setErrorResponse(res, boost::beast::http::status::not_found,
-                                     notFoundDesc, notFoundMsg);
+                    crow::setErrorResponse(
+                        res, boost::beast::http::status::not_found,
+                        crow::msg::notFoundDesc, crow::msg::notFoundMsg);
                     res.end();
                     return;
                 }
@@ -1734,10 +1715,11 @@
                             {
                                 if (!propertyName->empty() && response->empty())
                                 {
-                                    setErrorResponse(
+                                    crow::setErrorResponse(
                                         res,
                                         boost::beast::http::status::not_found,
-                                        propNotFoundDesc, notFoundMsg);
+                                        crow::msg::propNotFoundDesc,
+                                        crow::msg::notFoundMsg);
                                 }
                                 else
                                 {
@@ -1766,8 +1748,9 @@
     {
         if (res.jsonValue.empty())
         {
-            setErrorResponse(res, boost::beast::http::status::forbidden,
-                             forbiddenMsg, forbiddenPropDesc);
+            crow::setErrorResponse(res, boost::beast::http::status::forbidden,
+                                   crow::msg::forbiddenMsg,
+                                   crow::msg::forbiddenPropDesc);
         }
 
         res.end();
@@ -1775,8 +1758,9 @@
 
     void setErrorStatus(const std::string &desc)
     {
-        setErrorResponse(res, boost::beast::http::status::internal_server_error,
-                         desc, badReqMsg);
+        crow::setErrorResponse(
+            res, boost::beast::http::status::internal_server_error, desc,
+            crow::msg::badReqMsg);
     }
 
     crow::Response &res;
@@ -1790,8 +1774,9 @@
 {
     if (destProperty.empty())
     {
-        setErrorResponse(res, boost::beast::http::status::forbidden,
-                         forbiddenResDesc, forbiddenMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::forbidden,
+                               crow::msg::forbiddenResDesc,
+                               crow::msg::forbiddenMsg);
         res.end();
         return;
     }
@@ -1801,8 +1786,8 @@
 
     if (requestDbusData.is_discarded())
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request,
-                         noJsonDesc, badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               crow::msg::noJsonDesc, crow::msg::badReqMsg);
         res.end();
         return;
     }
@@ -1810,8 +1795,8 @@
     nlohmann::json::const_iterator propertyIt = requestDbusData.find("data");
     if (propertyIt == requestDbusData.end())
     {
-        setErrorResponse(res, boost::beast::http::status::bad_request,
-                         noJsonDesc, badReqMsg);
+        crow::setErrorResponse(res, boost::beast::http::status::bad_request,
+                               crow::msg::noJsonDesc, crow::msg::badReqMsg);
         res.end();
         return;
     }
@@ -1829,9 +1814,9 @@
                       const GetObjectType &object_names) {
             if (!ec && object_names.size() <= 0)
             {
-                setErrorResponse(transaction->res,
-                                 boost::beast::http::status::not_found,
-                                 propNotFoundDesc, notFoundMsg);
+                crow::setErrorResponse(
+                    transaction->res, boost::beast::http::status::not_found,
+                    crow::msg::propNotFoundDesc, crow::msg::notFoundMsg);
                 return;
             }
 
@@ -1947,7 +1932,7 @@
                                                     {
                                                         const sd_bus_error *e =
                                                             m.get_error();
-                                                        setErrorResponse(
+                                                        crow::setErrorResponse(
                                                             transaction->res,
                                                             boost::beast::http::
                                                                 status::
@@ -2057,8 +2042,9 @@
         return;
     }
 
-    setErrorResponse(res, boost::beast::http::status::method_not_allowed,
-                     methodNotAllowedDesc, methodNotAllowedMsg);
+    crow::setErrorResponse(res, boost::beast::http::status::method_not_allowed,
+                           crow::msg::methodNotAllowedDesc,
+                           crow::msg::methodNotAllowedMsg);
     res.end();
 }
 
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
index f211a29..68ce64a 100644
--- a/include/pam_authenticate.hpp
+++ b/include/pam_authenticate.hpp
@@ -48,10 +48,12 @@
 }
 
 inline bool pamAuthenticateUser(const std::string_view username,
-                                const std::string_view password)
+                                const std::string_view password,
+                                bool& passwordChangeRequired)
 {
     std::string userStr(username);
     std::string passStr(password);
+    passwordChangeRequired = false;
     const struct pam_conv localConversation = {
         pamFunctionConversation, const_cast<char*>(passStr.c_str())};
     pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start
@@ -70,12 +72,18 @@
         return false;
     }
 
-    /* check that the account is healthy */
-    if (pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK) !=
-        PAM_SUCCESS)
+    /* check if the account is healthy */
+    switch (pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK))
     {
-        pam_end(localAuthHandle, PAM_SUCCESS);
-        return false;
+        case PAM_SUCCESS:
+            break;
+        case PAM_NEW_AUTHTOK_REQD:
+            passwordChangeRequired = true;
+            break;
+        default:
+            pam_end(localAuthHandle, PAM_SUCCESS);
+            return false;
+            break;
     }
 
     if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS)
diff --git a/include/sessions.hpp b/include/sessions.hpp
index 2900cd5..ebf6360 100644
--- a/include/sessions.hpp
+++ b/include/sessions.hpp
@@ -276,6 +276,7 @@
     std::string csrfToken;
     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
     PersistenceType persistence;
+    bool isConfigureSelfOnly;
 
     /**
      * @brief Fills object with data from UserSession's JSON representation
@@ -334,6 +335,7 @@
         // this is done temporarily
         userSession->lastUpdated = std::chrono::steady_clock::now();
         userSession->persistence = PersistenceType::TIMEOUT;
+        userSession->isConfigureSelfOnly = false;
 
         return userSession;
     }
@@ -345,7 +347,7 @@
 {
   public:
     std::shared_ptr<UserSession> generateUserSession(
-        const std::string_view username,
+        const std::string_view username, bool configureSelfOnly,
         PersistenceType persistence = PersistenceType::TIMEOUT)
     {
         // TODO(ed) find a secure way to not generate session identifiers if
@@ -385,6 +387,7 @@
         auto session = std::make_shared<UserSession>(UserSession{
             uniqueId, sessionToken, std::string(username), csrfToken,
             std::chrono::steady_clock::now(), persistence});
+        session->isConfigureSelfOnly = configureSelfOnly;
         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
         // Only need to write to disk if session isn't about to be destroyed.
         needWrite = persistence == PersistenceType::TIMEOUT;
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 2ff3879..5842ae5 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -6,6 +6,7 @@
 #include <crow/http_response.h>
 
 #include <boost/container/flat_set.hpp>
+#include <error_messages.hpp>
 #include <pam_authenticate.hpp>
 #include <persistent_data_middleware.hpp>
 #include <random>
@@ -129,7 +130,9 @@
 
         BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
 
-        if (!pamAuthenticateUser(user, pass))
+        bool passwordChangeRequired = false;
+        if (!pamAuthenticateUser(user, pass, passwordChangeRequired) ||
+            passwordChangeRequired)
         {
             return nullptr;
         }
@@ -141,7 +144,8 @@
         // This whole flow needs to be revisited anyway, as we can't be
         // calling directly into pam for every request
         return persistent_data::SessionStore::getInstance().generateUserSession(
-            user, crow::persistent_data::PersistenceType::SINGLE_REQUEST);
+            user, false,
+            crow::persistent_data::PersistenceType::SINGLE_REQUEST);
     }
 
     const std::shared_ptr<crow::persistent_data::UserSession>
@@ -380,14 +384,17 @@
 
             if (!username.empty() && !password.empty())
             {
-                if (!pamAuthenticateUser(username, password))
+                bool passwordChangeRequired = false;
+                if (!pamAuthenticateUser(username, password,
+                                         passwordChangeRequired))
                 {
                     res.result(boost::beast::http::status::unauthorized);
                 }
                 else
                 {
                     auto session = persistent_data::SessionStore::getInstance()
-                                       .generateUserSession(username);
+                                       .generateUserSession(
+                                           username, passwordChangeRequired);
 
                     if (looksLikeIbm)
                     {
@@ -421,6 +428,10 @@
                         // if content type is json, assume json token
                         res.jsonValue = {{"token", session->sessionToken}};
                     }
+                    if (passwordChangeRequired)
+                    {
+                        crow::setPasswordChangeRequired(res, session->username);
+                    }
                 }
             }
             else
diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp
index d92a2b4..f7c5487 100644
--- a/redfish-core/include/error_messages.hpp
+++ b/redfish-core/include/error_messages.hpp
@@ -32,7 +32,7 @@
 namespace messages
 {
 
-constexpr const char* messageVersionPrefix = "Base.1.2.0.";
+constexpr const char* messageVersionPrefix = "Base.1.5.0.";
 constexpr const char* messageAnnotation = "@Message.ExtendedInfo";
 
 /**
@@ -625,6 +625,24 @@
 void queryParameterOutOfRange(crow::Response& res, const std::string& arg1,
                               const std::string& arg2, const std::string& arg3);
 
+/**
+ * @brief Formats PasswordChangeRequired message into JSON
+ * Message body: The password provided for this account must be changed
+ * before access is granted.  PATCH the 'Password' property for this
+ * account located at the target URI '%1' to complete this process.
+ *
+ * @param[in] arg1 Parameter of message that will replace %1 in its body.
+ *
+ * @returns Message PasswordChangeRequired formatted to JSON */
+void passwordChangeRequired(crow::Response& res, const std::string& arg1);
+
+/**
+ * @brief Formats SubscriptionTerminated message into JSON
+ * Message body: The event subscription has been terminated.
+ *
+ * @returns Message SubscriptionTerminated formatted to JSON */
+void subscriptionTerminated(crow::Response& res);
+
 } // namespace messages
 
 } // namespace redfish
diff --git a/redfish-core/include/node.hpp b/redfish-core/include/node.hpp
index 5819527..9d383a9 100644
--- a/redfish-core/include/node.hpp
+++ b/redfish-core/include/node.hpp
@@ -20,6 +20,7 @@
 #include "webserver_common.hpp"
 
 #include <error_messages.hpp>
+#include <sessions.hpp>
 #include <vector>
 
 #include "crow/http_request.h"
@@ -168,6 +169,28 @@
         res.result(boost::beast::http::status::method_not_allowed);
         res.end();
     }
+
+    /* @brief Would the operation be allowed if the user did not have
+     * the ConfigureSelf Privilege?
+     *
+     * @param req      the request
+     * @param verb     the operation's verb
+     *
+     * @returns        True if allowed, false otherwise
+     */
+    inline bool isAllowedWithoutConfigureSelf(const crow::Request& req)
+    {
+        const std::string& userRole =
+            crow::persistent_data::UserRoleMap::getInstance().getUserRole(
+                req.session->username);
+        Privileges effectiveUserPrivileges =
+            redfish::getUserPrivileges(userRole);
+        effectiveUserPrivileges.resetSinglePrivilege("ConfigureSelf");
+        const auto& requiredPrivilegesIt = entityPrivileges.find(req.method());
+        return (requiredPrivilegesIt != entityPrivileges.end()) and
+               isOperationAllowedWithPrivileges(requiredPrivilegesIt->second,
+                                                effectiveUserPrivileges);
+    }
 };
 
 } // namespace redfish
diff --git a/redfish-core/include/privileges.hpp b/redfish-core/include/privileges.hpp
index ec6e6a5..a62235a 100644
--- a/redfish-core/include/privileges.hpp
+++ b/redfish-core/include/privileges.hpp
@@ -62,6 +62,11 @@
  *        A bit is set if the privilege is required (entity domain) or granted
  *        (user domain) and false otherwise.
  *
+ *        This does not implement any Redfish property overrides,
+ *        subordinate overrides, or resource URI overrides.  This does
+ *        not implement the limitation of the ConfigureSelf privilege
+ *        to operate only on your own account or session.
+ *
  */
 class Privileges
 {
@@ -91,6 +96,29 @@
     }
 
     /**
+     * @brief Resets the given privilege in the bitset
+     *
+     * @param[in] privilege  Privilege to be reset
+     *
+     * @return               None
+     *
+     */
+    bool resetSinglePrivilege(const char* privilege)
+    {
+        for (int searchIndex = 0; searchIndex < privilegeNames.size();
+             searchIndex++)
+        {
+            if (privilege == privilegeNames[searchIndex])
+            {
+                privilegeBitset.reset(searchIndex);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * @brief Sets given privilege in the bitset
      *
      * @param[in] privilege  Privilege to be set
@@ -173,6 +201,11 @@
         return (privilegeBitset & p.privilegeBitset) == p.privilegeBitset;
     }
 
+    std::string to_string() const
+    {
+        return privilegeBitset.to_string();
+    }
+
   private:
     std::bitset<maxPrivilegeCount> privilegeBitset = 0;
 };
@@ -192,6 +225,12 @@
         static Privileges op{"Login", "ConfigureSelf", "ConfigureComponents"};
         return op;
     }
+    else if (userRole == "special-priv-configure-self")
+    {
+        // Redfish privilege : N/A - internal within BMCWeb
+        static Privileges configSelf{"ConfigureSelf"};
+        return configSelf;
+    }
     else
     {
         // Redfish privilege : Readonly
@@ -203,6 +242,34 @@
 using OperationMap = boost::container::flat_map<boost::beast::http::verb,
                                                 std::vector<Privileges>>;
 
+/* @brief Checks if user is allowed to call an operation
+ *
+ * @param[in] operationPrivilegesRequired   Privileges required
+ * @param[in] userPrivileges                Privileges the user has
+ *
+ * @return                 True if operation is allowed, false otherwise
+ */
+inline bool isOperationAllowedWithPrivileges(
+    const std::vector<Privileges>& operationPrivilegesRequired,
+    const Privileges& userPrivileges)
+{
+    // If there are no privileges assigned, there are no privileges required
+    if (operationPrivilegesRequired.empty())
+    {
+        return true;
+    }
+    for (auto& requiredPrivileges : operationPrivilegesRequired)
+    {
+        BMCWEB_LOG_ERROR << "Checking operation privileges...";
+        if (userPrivileges.isSupersetOf(requiredPrivileges))
+        {
+            BMCWEB_LOG_ERROR << "...success";
+            return true;
+        }
+    }
+    return false;
+}
+
 /**
  * @brief Checks if given privileges allow to call an HTTP method
  *
@@ -222,20 +289,7 @@
         return false;
     }
 
-    // If there are no privileges assigned, assume no privileges required
-    if (it->second.empty())
-    {
-        return true;
-    }
-
-    for (auto& requiredPrivileges : it->second)
-    {
-        if (userPrivileges.isSupersetOf(requiredPrivileges))
-        {
-            return true;
-        }
-    }
-    return false;
+    return isOperationAllowedWithPrivileges(it->second, userPrivileges);
 }
 
 /**
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index 9dac7e4..e3442af 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -1349,7 +1349,8 @@
             {boost::beast::http::verb::get,
              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
             {boost::beast::http::verb::head, {{"Login"}}},
-            {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
+            {boost::beast::http::verb::patch,
+             {{"ConfigureUsers"}, {"ConfigureSelf"}}},
             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
@@ -1359,7 +1360,6 @@
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
-
         auto asyncResp = std::make_shared<AsyncResp>(res);
 
         if (params.size() != 1)
@@ -1368,6 +1368,21 @@
             return;
         }
 
+        // Perform a tighter authority check for the ConfigureSelf
+        // privilege.  If the user is operating on an account not
+        // their own, then their ConfigureSelf privilege does not
+        // apply, so remove the user's ConfigureSelf privilege and
+        // perform the authority check again.
+        if (req.session->username != params[0])
+        {
+            if (!isAllowedWithoutConfigureSelf(req))
+            {
+                BMCWEB_LOG_DEBUG << "GET Account denied access";
+                messages::accessDenied(asyncResp->res, std::string(req.url));
+                return;
+            }
+        }
+
         crow::connections::systemBus->async_method_call(
             [asyncResp, accountName{std::string(params[0])}](
                 const boost::system::error_code ec,
@@ -1505,6 +1520,29 @@
 
         const std::string& username = params[0];
 
+        // Perform a tighter authority check for how the ConfigureSelf
+        // privilege interacts with the Redfish Password property
+        // override.  (Meaning: the ConfigureSelf privilege only
+        // applies when PATCHing the Password property.)  If the user
+        // is PATCHing a resource other than Password, then the
+        // Password property override does not apply, so the user's
+        // ConfigureSelf privilege does not apply.  If the user is
+        // operating on an account not their own, then their
+        // ConfigureSelf privilege does not apply.  In either case,
+        // remove the user's ConfigureSelf privilege and perform the
+        // authority check again.
+        if ((username != req.session->username) or
+            (newUserName or enabled or roleId or locked))
+        {
+            if (!isAllowedWithoutConfigureSelf(req))
+            {
+                BMCWEB_LOG_WARNING << "PATCH Password denied access";
+                asyncResp->res.clear();
+                messages::accessDenied(asyncResp->res, std::string(req.url));
+                return;
+            }
+        }
+
         if (!newUserName)
         {
             // If the username isn't being updated, we can update the
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index d4085af..c4b0a4f 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -35,7 +35,8 @@
             {boost::beast::http::verb::head, {{"Login"}}},
             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
-            {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+            {boost::beast::http::verb::delete_,
+             {{"ConfigureManager"}, {"ConfigureSelf"}}},
             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
     }
 
@@ -43,6 +44,7 @@
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
+        // Note that control also reaches here via doPost and doDelete.
         auto session =
             crow::persistent_data::SessionStore::getInstance().getSessionByUid(
                 params[0]);
@@ -63,6 +65,12 @@
             "/redfish/v1/$metadata#Session.Session";
         res.jsonValue["Name"] = "User Session";
         res.jsonValue["Description"] = "Manager User Session";
+        if (session->isConfigureSelfOnly)
+        {
+            messages::passwordChangeRequired(
+                res,
+                "/redfish/v1/AccountService/Accounts/" + session->username);
+        }
 
         res.end();
     }
@@ -93,6 +101,24 @@
             return;
         }
 
+        // Perform a tighter authority check for how the ConfigureSelf
+        // privilege interacts with the Session resource URI override.
+        // (Meaning: the ConfigureSelf privilege only applies to that
+        // session's Session resource.)  If a session is DELETEing
+        // some other session, then the ConfigureSelf privilege does
+        // not apply, so remove the user's ConfigureSelf privilege and
+        // perform the authority check again.
+        if (session->uniqueId != req.session->uniqueId)
+        {
+            if (!isAllowedWithoutConfigureSelf(req))
+            {
+                BMCWEB_LOG_WARNING << "DELETE Session denied access";
+                messages::accessDenied(res, std::string(req.url));
+                res.end();
+                return;
+            }
+        }
+
         // DELETE should return representation of object that will be removed
         doGet(res, req, params);
 
@@ -178,7 +204,8 @@
             return;
         }
 
-        if (!pamAuthenticateUser(username, password))
+        bool passwordChangeRequired = false;
+        if (!pamAuthenticateUser(username, password, passwordChangeRequired))
         {
             messages::resourceAtUriUnauthorized(res, std::string(req.url),
                                                 "Invalid username or password");
@@ -190,7 +217,7 @@
         // User is authenticated - create session
         std::shared_ptr<crow::persistent_data::UserSession> session =
             crow::persistent_data::SessionStore::getInstance()
-                .generateUserSession(username);
+                .generateUserSession(username, passwordChangeRequired);
         res.addHeader("X-Auth-Token", session->sessionToken);
         res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" +
                                       session->uniqueId);
diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp
index ca99870..21483d5 100644
--- a/redfish-core/src/error_messages.cpp
+++ b/redfish-core/src/error_messages.cpp
@@ -1504,6 +1504,52 @@
              "is within the range of valid pages."}});
 }
 
+/**
+ * @internal
+ * @brief Formats PasswordChangeRequired message into JSON
+ *
+ * See header file for more information
+ * @endinternal
+ */
+void passwordChangeRequired(crow::Response& res, const std::string& arg1)
+{
+    messages::addMessageToJsonRoot(
+        res.jsonValue,
+        nlohmann::json{
+            {"@odata.type", "/redfish/v1/$metadata#Message.v1_5_0.Message"},
+            {"MessageId", "Base.1.5.0.PasswordChangeRequired"},
+            {"Message", "The password provided for this account must be "
+                        "changed before access is granted.  PATCH the "
+                        "'Password' property for this account located at "
+                        "the target URI '" +
+                            arg1 + "' to complete this process."},
+            {"MessageArgs", {arg1}},
+            {"Severity", "Critical"},
+            {"Resolution", "Change the password for this account using "
+                           "a PATCH to the 'Password' property at the URI "
+                           "provided."}});
+}
+
+/**
+ * @internal
+ * @brief Formats SubscriptionTerminated message into JSON
+ *
+ * See header file for more information
+ * @endinternal
+ */
+void subscriptionTerminated(crow::Response& res)
+{
+    messages::addMessageToJsonRoot(
+        res.jsonValue,
+        nlohmann::json{
+            {"@odata.type", "/redfish/v1/$metadata#Message.v1_5_0.Message"},
+            {"MessageId", "Base.1.5.0.SubscriptionTerminated"},
+            {"Message", "The event subscription has been terminated."},
+            {"MessageArgs", nlohmann::json::array()},
+            {"Severity", "OK"},
+            {"Resolution", "No resolution is required."}});
+}
+
 } // namespace messages
 
 } // namespace redfish