Add UserPasswordExpired for local users
Adds a new UserPasswordExpired property to local User.Attributes which
represents if the account's password is expired and must be changed.
The value corresponds to the `chage` command.
Note this is distinct from UserLockedForFailedAttempt which represents
a locked account due to unsuccessful authentication atttempts.
Tested: Via busctl
- Checked local and LDAP users.
- Expired password via `passwd --expire USER`.
- Aged password via `chage USER`.
- Changed password via REST API and via the `passwd USER` command.
Signed-off-by: Joseph Reynolds <joseph-reynolds@charter.net>
Change-Id: I44585559509a422bb91c83a2a853c1a033594350
diff --git a/docs/README.md b/docs/README.md
index 0481327..ea3b532 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,7 +14,7 @@
#### Configure LDAP
```
-curl -c cjar -b cjar -k -H "Content-Type: application/json" -X POST -d '{"data":[false,"ldap://<ldap://<LDAP server ip/hostname>/", "<bindDN>", "<baseDN>","<bindDNPassword>","<searchScope>","<serverType>"]}'' https://$BMC_IP//xyz/openbmc_project/user/ldap/action/CreateConfig
+curl -c cjar -b cjar -k -H "Content-Type: application/json" -X POST -d '{"data":[false,"ldap://<ldap://<LDAP server ip/hostname>/", "<bindDN>", "<baseDN>","<bindDNPassword>","<searchScope>","<serverType>"]}'' https://$BMC_IP/xyz/openbmc_project/user/ldap/action/CreateConfig
```
#### NOTE
diff --git a/test/mock_user_mgr.hpp b/test/mock_user_mgr.hpp
index 81f9065..ea7c5f6 100644
--- a/test/mock_user_mgr.hpp
+++ b/test/mock_user_mgr.hpp
@@ -19,6 +19,7 @@
MOCK_METHOD1(getLdapGroupName, std::string(const std::string& userName));
MOCK_METHOD0(getPrivilegeMapperObject, DbusUserObj());
MOCK_METHOD1(userLockedForFailedAttempt, bool(const std::string& userName));
+ MOCK_METHOD1(userPasswordExpired, bool(const std::string& userName));
friend class TestUserMgr;
};
diff --git a/test/user_mgr_test.cpp b/test/user_mgr_test.cpp
index 943f0a8..83ae651 100644
--- a/test/user_mgr_test.cpp
+++ b/test/user_mgr_test.cpp
@@ -103,6 +103,7 @@
std::get<std::vector<std::string>>(userInfo["UserGroups"]));
EXPECT_EQ(true, std::get<bool>(userInfo["UserEnabled"]));
EXPECT_EQ(false, std::get<bool>(userInfo["UserLockedForFailedAttempt"]));
+ EXPECT_EQ(false, std::get<bool>(userInfo["UserPasswordExpired"]));
EXPECT_EQ(false, std::get<bool>(userInfo["RemoteUser"]));
}
diff --git a/user_mgr.cpp b/user_mgr.cpp
index 2f22323..9694fd1 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -18,6 +18,7 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <time.h>
#include <fstream>
#include <grp.h>
#include <pwd.h>
@@ -726,6 +727,50 @@
return userLockedForFailedAttempt(userName);
}
+bool UserMgr::userPasswordExpired(const std::string &userName)
+{
+ // All user management lock has to be based on /etc/shadow
+ phosphor::user::shadow::Lock lock();
+
+ struct spwd spwd
+ {
+ };
+ struct spwd *spwdPtr = nullptr;
+ auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buflen < -1)
+ {
+ // Use a default size if there is no hard limit suggested by sysconf()
+ buflen = 1024;
+ }
+ std::vector<char> buffer(buflen);
+ auto status =
+ getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
+ // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
+ // If no matching password record was found, these functions return 0
+ // and store NULL in *spwdPtr
+ if ((status == 0) && (&spwd == spwdPtr))
+ {
+ // Determine password validity per "chage" docs, where:
+ // spwd.sp_lstchg == 0 means password is expired, and
+ // spwd.sp_max == -1 means the password does not expire.
+ constexpr long seconds_per_day = 60 * 60 * 24;
+ long today = static_cast<long>(time(NULL)) / seconds_per_day;
+ if ((spwd.sp_lstchg == 0) ||
+ ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ log<level::ERR>("User does not exist",
+ entry("USER_NAME=%s", userName.c_str()));
+ elog<UserNameDoesNotExist>();
+ }
+
+ return false;
+}
+
UserSSHLists UserMgr::getUserAndSshGrpList()
{
// All user management lock has to be based on /etc/shadow
@@ -952,6 +997,8 @@
userInfo.emplace("UserEnabled", user.get()->userEnabled());
userInfo.emplace("UserLockedForFailedAttempt",
user.get()->userLockedForFailedAttempt());
+ userInfo.emplace("UserPasswordExpired",
+ user.get()->userPasswordExpired());
userInfo.emplace("RemoteUser", false);
}
else
diff --git a/user_mgr.hpp b/user_mgr.hpp
index c008cea..e25ca87 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -168,6 +168,13 @@
bool userLockedForFailedAttempt(const std::string &userName,
const bool &value);
+ /** @brief shows if the user's password is expired
+ *
+ * @param[in]: user name
+ * @return - true / false indicating user password expired
+ **/
+ virtual bool userPasswordExpired(const std::string &userName);
+
/** @brief returns user info
* Checks if user is local user, then returns map of properties of user.
* like user privilege, list of user groups, user enabled state and user
diff --git a/users.cpp b/users.cpp
index dafc2dc..562a0ad 100644
--- a/users.cpp
+++ b/users.cpp
@@ -166,5 +166,13 @@
}
}
+/** @brief indicates if the user's password is expired
+ *
+ **/
+bool Users::userPasswordExpired(void) const
+{
+ return manager.userPasswordExpired(userName);
+}
+
} // namespace user
} // namespace phosphor
diff --git a/users.hpp b/users.hpp
index aa1726a..45d86cd 100644
--- a/users.hpp
+++ b/users.hpp
@@ -111,6 +111,11 @@
**/
bool userLockedForFailedAttempt(bool value) override;
+ /** @brief indicates if the user's password is expired
+ *
+ **/
+ bool userPasswordExpired(void) const;
+
private:
std::string userName;
UserMgr &manager;