summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http/routing.h65
-rw-r--r--include/sessions.hpp19
-rw-r--r--include/token_authorization_middleware.hpp19
-rw-r--r--redfish-core/include/error_messages.hpp11
-rw-r--r--redfish-core/include/node.hpp19
-rw-r--r--redfish-core/include/privileges.hpp16
-rw-r--r--redfish-core/lib/account_service.hpp18
-rw-r--r--redfish-core/lib/redfish_sessions.hpp14
-rw-r--r--redfish-core/src/error_messages.cpp26
9 files changed, 190 insertions, 17 deletions
diff --git a/http/routing.h b/http/routing.h
index c2a7503f00..cc5c75fc8c 100644
--- a/http/routing.h
+++ b/http/routing.h
@@ -1,5 +1,6 @@
#pragma once
+#include "error_messages.hpp"
#include "privileges.hpp"
#include "sessions.hpp"
@@ -1287,13 +1288,77 @@ class Router
<< " userRole = " << *userRolePtr;
}
+ bool* remoteUserPtr = nullptr;
+ auto remoteUserIter = userInfo.find("RemoteUser");
+ if (remoteUserIter != userInfo.end())
+ {
+ remoteUserPtr = std::get_if<bool>(&remoteUserIter->second);
+ }
+ if (remoteUserPtr == nullptr)
+ {
+ BMCWEB_LOG_ERROR
+ << "RemoteUser property missing or wrong type";
+ res.result(
+ boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ bool remoteUser = *remoteUserPtr;
+
+ bool passwordExpired = false; // default for remote user
+ if (!remoteUser)
+ {
+ bool* passwordExpiredPtr = nullptr;
+ auto passwordExpiredIter =
+ userInfo.find("UserPasswordExpired");
+ if (passwordExpiredIter != userInfo.end())
+ {
+ passwordExpiredPtr =
+ std::get_if<bool>(&passwordExpiredIter->second);
+ }
+ if (passwordExpiredPtr != nullptr)
+ {
+ passwordExpired = *passwordExpiredPtr;
+ }
+ else
+ {
+ BMCWEB_LOG_ERROR
+ << "UserPasswordExpired property is expected for"
+ " local user but is missing or wrong type";
+ res.result(
+ boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ }
+
// Get the user privileges from the role
redfish::Privileges userPrivileges =
redfish::getUserPrivileges(userRole);
+ // Set isConfigureSelfOnly based on D-Bus results. This
+ // ignores the results from both pamAuthenticateUser and the
+ // value from any previous use of this session.
+ req.session->isConfigureSelfOnly = passwordExpired;
+
+ // Modify privileges if isConfigureSelfOnly.
+ if (req.session->isConfigureSelfOnly)
+ {
+ // Remove all privileges except ConfigureSelf
+ userPrivileges = userPrivileges.intersection(
+ redfish::Privileges{"ConfigureSelf"});
+ BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf";
+ }
+
if (!rules[ruleIndex]->checkPrivileges(userPrivileges))
{
res.result(boost::beast::http::status::forbidden);
+ if (req.session->isConfigureSelfOnly)
+ {
+ redfish::messages::passwordChangeRequired(
+ res, "/redfish/v1/AccountService/Accounts/" +
+ req.session->username);
+ }
res.end();
return;
}
diff --git a/include/sessions.hpp b/include/sessions.hpp
index 9d24327eab..a7ffe28921 100644
--- a/include/sessions.hpp
+++ b/include/sessions.hpp
@@ -45,6 +45,15 @@ struct UserSession
std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
PersistenceType persistence;
bool cookieAuth = false;
+ bool isConfigureSelfOnly = false;
+
+ // There are two sources of truth for isConfigureSelfOnly:
+ // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
+ // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
+ // These should be in sync, but the underlying condition can change at any
+ // time. For example, a password can expire or be changed outside of
+ // bmcweb. The value stored here is updated at the start of each
+ // operation and used as the truth within bmcweb.
/**
* @brief Fills object with data from UserSession's JSON representation
@@ -196,7 +205,8 @@ class SessionStore
public:
std::shared_ptr<UserSession> generateUserSession(
const std::string_view username,
- PersistenceType persistence = PersistenceType::TIMEOUT)
+ PersistenceType persistence = PersistenceType::TIMEOUT,
+ bool isConfigureSelfOnly = false)
{
// TODO(ed) find a secure way to not generate session identifiers if
// persistence is set to SINGLE_REQUEST
@@ -244,9 +254,10 @@ class SessionStore
}
}
- auto session = std::make_shared<UserSession>(UserSession{
- uniqueId, sessionToken, std::string(username), csrfToken,
- std::chrono::steady_clock::now(), persistence});
+ auto session = std::make_shared<UserSession>(
+ UserSession{uniqueId, sessionToken, std::string(username),
+ csrfToken, std::chrono::steady_clock::now(),
+ persistence, false, isConfigureSelfOnly});
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 aaa1325b7a..ccea929f6f 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -138,7 +138,9 @@ class Middleware
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
- if (pamAuthenticateUser(user, pass) != PAM_SUCCESS)
+ int pamrc = pamAuthenticateUser(user, pass);
+ bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
+ if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{
return nullptr;
}
@@ -150,7 +152,8 @@ class Middleware
// 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, crow::persistent_data::PersistenceType::SINGLE_REQUEST,
+ isConfigureSelfOnly);
}
const std::shared_ptr<crow::persistent_data::UserSession>
@@ -397,14 +400,20 @@ template <typename... Middlewares> void requestRoutes(Crow<Middlewares...>& app)
if (!username.empty() && !password.empty())
{
- if (pamAuthenticateUser(username, password) != PAM_SUCCESS)
+ int pamrc = pamAuthenticateUser(username, password);
+ bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
+ if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{
res.result(boost::beast::http::status::unauthorized);
}
else
{
- auto session = persistent_data::SessionStore::getInstance()
- .generateUserSession(username);
+ auto session =
+ persistent_data::SessionStore::getInstance()
+ .generateUserSession(
+ username,
+ crow::persistent_data::PersistenceType::TIMEOUT,
+ isConfigureSelfOnly);
if (looksLikePhosphorRest)
{
diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp
index 4d717450a7..6e280c04c6 100644
--- a/redfish-core/include/error_messages.hpp
+++ b/redfish-core/include/error_messages.hpp
@@ -764,6 +764,17 @@ nlohmann::json queryParameterOutOfRange(const std::string& arg1,
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);
+
} // namespace messages
} // namespace redfish
diff --git a/redfish-core/include/node.hpp b/redfish-core/include/node.hpp
index 9086f1e0ef..a6e1e27ed9 100644
--- a/redfish-core/include/node.hpp
+++ b/redfish-core/include/node.hpp
@@ -169,8 +169,8 @@ class Node
res.end();
}
- /* @brief Would the operation be allowed if the user did not have
- * the ConfigureSelf Privilege?
+ /* @brief Would the operation be allowed if the user did not have the
+ * ConfigureSelf Privilege? Also honors session.isConfigureSelfOnly.
*
* @param req the request
*
@@ -181,9 +181,18 @@ class Node
const std::string& userRole = req.userRole;
BMCWEB_LOG_DEBUG << "isAllowedWithoutConfigureSelf for the role "
<< req.userRole;
- Privileges effectiveUserPrivileges =
- redfish::getUserPrivileges(userRole);
- effectiveUserPrivileges.resetSinglePrivilege("ConfigureSelf");
+ Privileges effectiveUserPrivileges;
+ if (req.session && req.session->isConfigureSelfOnly)
+ {
+ // The session has no privileges because it is limited to
+ // configureSelfOnly and we are disregarding that privilege.
+ // Note that some operations do not require any privilege.
+ }
+ else
+ {
+ effectiveUserPrivileges = redfish::getUserPrivileges(userRole);
+ effectiveUserPrivileges.resetSinglePrivilege("ConfigureSelf");
+ }
const auto& requiredPrivilegesIt = entityPrivileges.find(req.method());
return (requiredPrivilegesIt != entityPrivileges.end()) &&
isOperationAllowedWithPrivileges(requiredPrivilegesIt->second,
diff --git a/redfish-core/include/privileges.hpp b/redfish-core/include/privileges.hpp
index 1ca57fad36..35f619b77a 100644
--- a/redfish-core/include/privileges.hpp
+++ b/redfish-core/include/privileges.hpp
@@ -196,7 +196,23 @@ class Privileges
return (privilegeBitset & p.privilegeBitset) == p.privilegeBitset;
}
+ /**
+ * @brief Returns the intersection of two Privilege sets.
+ *
+ * @param[in] privilege Privilege set to intersect with.
+ *
+ * @return The new Privilege set.
+ *
+ */
+ Privileges intersection(const Privileges& p) const
+ {
+ return Privileges{privilegeBitset & p.privilegeBitset};
+ }
+
private:
+ Privileges(const std::bitset<maxPrivilegeCount>& p) : privilegeBitset{p}
+ {
+ }
std::bitset<maxPrivilegeCount> privilegeBitset = 0;
};
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index b579994e0b..609c7dbd23 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -1618,7 +1618,7 @@ class ManagerAccount : public Node
*userLocked;
asyncResp->res.jsonValue
["Locked@Redfish.AllowableValues"] = {
- "false"};
+ "false"}; // can only unlock accounts
}
else if (property.first == "UserPrivilege")
{
@@ -1647,6 +1647,22 @@ class ManagerAccount : public Node
"Roles/" +
role}};
}
+ else if (property.first == "UserPasswordExpired")
+ {
+ const bool* userPasswordExpired =
+ std::get_if<bool>(&property.second);
+ if (userPasswordExpired == nullptr)
+ {
+ BMCWEB_LOG_ERROR << "UserPassword"
+ "Expired "
+ "wasn't a bool";
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ asyncResp->res
+ .jsonValue["PasswordChangeRequired"] =
+ *userPasswordExpired;
+ }
}
}
}
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index c3e11a3404..515f5bea63 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -192,7 +192,9 @@ class SessionCollection : public Node
return;
}
- if (pamAuthenticateUser(username, password) != PAM_SUCCESS)
+ int pamrc = pamAuthenticateUser(username, password);
+ bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
+ if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{
messages::resourceAtUriUnauthorized(res, std::string(req.url),
"Invalid username or password");
@@ -204,11 +206,19 @@ class SessionCollection : public Node
// User is authenticated - create session
std::shared_ptr<crow::persistent_data::UserSession> session =
crow::persistent_data::SessionStore::getInstance()
- .generateUserSession(username);
+ .generateUserSession(
+ username, crow::persistent_data::PersistenceType::TIMEOUT,
+ isConfigureSelfOnly);
res.addHeader("X-Auth-Token", session->sessionToken);
res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" +
session->uniqueId);
res.result(boost::beast::http::status::created);
+ if (session->isConfigureSelfOnly)
+ {
+ messages::passwordChangeRequired(
+ res,
+ "/redfish/v1/AccountService/Accounts/" + session->username);
+ }
memberSession.doGet(res, req, {session->uniqueId});
}
diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp
index bc5ba77062..4be2687535 100644
--- a/redfish-core/src/error_messages.cpp
+++ b/redfish-core/src/error_messages.cpp
@@ -1699,6 +1699,32 @@ void queryParameterOutOfRange(crow::Response& res, const std::string& arg1,
queryParameterOutOfRange(arg1, arg2, arg3));
}
+/**
+ * @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."}});
+}
+
} // namespace messages
} // namespace redfish