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/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);
}
/**