blob: f58c6761616bd157940f1a8b72b3d45f1d785074 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01003#include <boost/container/flat_map.hpp>
4#include <boost/uuid/uuid.hpp>
5#include <boost/uuid/uuid_generators.hpp>
6#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05307#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07008#include <nlohmann/json.hpp>
9#include <pam_authenticate.hpp>
10#include <random>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050011#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053012
Ed Tanousc94ad492019-10-10 15:39:33 -070013#include "logging.h"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace crow
16{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010017
Ed Tanous1abe55e2018-09-05 08:30:59 -070018namespace persistent_data
19{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010020
Ed Tanous1abe55e2018-09-05 08:30:59 -070021enum class PersistenceType
22{
23 TIMEOUT, // User session times out after a predetermined amount of time
24 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010025};
26
Ratan Gupta12c04ef2019-04-03 10:08:11 +053027constexpr char const* userService = "xyz.openbmc_project.User.Manager";
28constexpr char const* userObjPath = "/xyz/openbmc_project/user";
29constexpr char const* userAttrIface = "xyz.openbmc_project.User.Attributes";
30constexpr char const* dbusPropertiesIface = "org.freedesktop.DBus.Properties";
31
Ratan Gupta12c04ef2019-04-03 10:08:11 +053032struct UserRoleMap
33{
34 using GetManagedPropertyType =
35 boost::container::flat_map<std::string,
36 std::variant<std::string, bool>>;
37
38 using InterfacesPropertiesType =
39 boost::container::flat_map<std::string, GetManagedPropertyType>;
40
41 using GetManagedObjectsType = std::vector<
42 std::pair<sdbusplus::message::object_path, InterfacesPropertiesType>>;
43
44 static UserRoleMap& getInstance()
45 {
46 static UserRoleMap userRoleMap;
47 return userRoleMap;
48 }
49
50 UserRoleMap(const UserRoleMap&) = delete;
51 UserRoleMap& operator=(const UserRoleMap&) = delete;
52
53 std::string getUserRole(std::string_view name)
54 {
55 auto it = roleMap.find(std::string(name));
56 if (it == roleMap.end())
57 {
58 BMCWEB_LOG_ERROR << "User name " << name
59 << " is not found in the UserRoleMap.";
60 return "";
61 }
62 return it->second;
63 }
64
65 std::string
66 extractUserRole(const InterfacesPropertiesType& interfacesProperties)
67 {
68 auto iface = interfacesProperties.find(userAttrIface);
69 if (iface == interfacesProperties.end())
70 {
71 return {};
72 }
73
74 auto& properties = iface->second;
75 auto property = properties.find("UserPrivilege");
76 if (property == properties.end())
77 {
78 return {};
79 }
80
81 const std::string* role = std::get_if<std::string>(&property->second);
82 if (role == nullptr)
83 {
84 BMCWEB_LOG_ERROR << "UserPrivilege property value is null";
85 return {};
86 }
87
88 return *role;
89 }
90
91 private:
92 void userAdded(sdbusplus::message::message& m)
93 {
94 sdbusplus::message::object_path objPath;
95 InterfacesPropertiesType interfacesProperties;
96
97 try
98 {
99 m.read(objPath, interfacesProperties);
100 }
101 catch (const sdbusplus::exception::SdBusError& e)
102 {
103 BMCWEB_LOG_ERROR << "Failed to parse user add signal."
104 << "ERROR=" << e.what()
105 << "REPLY_SIG=" << m.get_signature();
106 return;
107 }
108 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
109
110 std::size_t lastPos = objPath.str.rfind("/");
111 if (lastPos == std::string::npos)
112 {
113 return;
114 };
115
116 std::string name = objPath.str.substr(lastPos + 1);
117 std::string role = this->extractUserRole(interfacesProperties);
118
119 // Insert the newly added user name and the role
120 auto res = roleMap.emplace(name, role);
121 if (res.second == false)
122 {
123 BMCWEB_LOG_ERROR << "Insertion of the user=\"" << name
124 << "\" in the roleMap failed.";
125 return;
126 }
127 }
128
129 void userRemoved(sdbusplus::message::message& m)
130 {
131 sdbusplus::message::object_path objPath;
132
133 try
134 {
135 m.read(objPath);
136 }
137 catch (const sdbusplus::exception::SdBusError& e)
138 {
139 BMCWEB_LOG_ERROR << "Failed to parse user delete signal.";
140 BMCWEB_LOG_ERROR << "ERROR=" << e.what()
141 << "REPLY_SIG=" << m.get_signature();
142 return;
143 }
144
145 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
146
147 std::size_t lastPos = objPath.str.rfind("/");
148 if (lastPos == std::string::npos)
149 {
150 return;
151 };
152
153 // User name must be atleast 1 char in length.
154 if ((lastPos + 1) >= objPath.str.length())
155 {
156 return;
157 }
158
159 std::string name = objPath.str.substr(lastPos + 1);
160
161 roleMap.erase(name);
162 }
163
164 void userPropertiesChanged(sdbusplus::message::message& m)
165 {
166 std::string interface;
167 GetManagedPropertyType changedProperties;
168 m.read(interface, changedProperties);
169 const std::string path = m.get_path();
170
171 BMCWEB_LOG_DEBUG << "Object Path = \"" << path << "\"";
172
173 std::size_t lastPos = path.rfind("/");
174 if (lastPos == std::string::npos)
175 {
176 return;
177 };
178
179 // User name must be at least 1 char in length.
180 if ((lastPos + 1) == path.length())
181 {
182 return;
183 }
184
185 std::string user = path.substr(lastPos + 1);
186
187 BMCWEB_LOG_DEBUG << "User Name = \"" << user << "\"";
188
189 auto index = changedProperties.find("UserPrivilege");
190 if (index == changedProperties.end())
191 {
192 return;
193 }
194
195 const std::string* role = std::get_if<std::string>(&index->second);
196 if (role == nullptr)
197 {
198 return;
199 }
200 BMCWEB_LOG_DEBUG << "Role = \"" << *role << "\"";
201
202 auto it = roleMap.find(user);
203 if (it == roleMap.end())
204 {
205 BMCWEB_LOG_ERROR << "User Name = \"" << user
206 << "\" is not found. But, received "
207 "propertiesChanged signal";
208 return;
209 }
210 it->second = *role;
211 }
212
213 UserRoleMap() :
214 userAddedSignal(
215 *crow::connections::systemBus,
216 sdbusplus::bus::match::rules::interfacesAdded(userObjPath),
217 [this](sdbusplus::message::message& m) {
218 BMCWEB_LOG_DEBUG << "User Added";
219 this->userAdded(m);
220 }),
221 userRemovedSignal(
222 *crow::connections::systemBus,
223 sdbusplus::bus::match::rules::interfacesRemoved(userObjPath),
224 [this](sdbusplus::message::message& m) {
225 BMCWEB_LOG_DEBUG << "User Removed";
226 this->userRemoved(m);
227 }),
228 userPropertiesChangedSignal(
229 *crow::connections::systemBus,
230 sdbusplus::bus::match::rules::path_namespace(userObjPath) +
231 sdbusplus::bus::match::rules::type::signal() +
232 sdbusplus::bus::match::rules::member("PropertiesChanged") +
233 sdbusplus::bus::match::rules::interface(dbusPropertiesIface) +
234 sdbusplus::bus::match::rules::argN(0, userAttrIface),
235 [this](sdbusplus::message::message& m) {
236 BMCWEB_LOG_DEBUG << "Properties Changed";
237 this->userPropertiesChanged(m);
238 })
239 {
240 crow::connections::systemBus->async_method_call(
241 [this](boost::system::error_code ec,
242 GetManagedObjectsType& managedObjects) {
243 if (ec)
244 {
245 BMCWEB_LOG_DEBUG << "User manager call failed, ignoring";
246 return;
247 }
248
249 for (auto& managedObj : managedObjects)
250 {
251 std::size_t lastPos = managedObj.first.str.rfind("/");
252 if (lastPos == std::string::npos)
253 {
254 continue;
255 };
256 std::string name = managedObj.first.str.substr(lastPos + 1);
257 std::string role = extractUserRole(managedObj.second);
258 roleMap.emplace(name, role);
259 }
260 },
261 userService, userObjPath, "org.freedesktop.DBus.ObjectManager",
262 "GetManagedObjects");
263 }
264
265 boost::container::flat_map<std::string, std::string> roleMap;
266 sdbusplus::bus::match_t userAddedSignal;
267 sdbusplus::bus::match_t userRemovedSignal;
268 sdbusplus::bus::match_t userPropertiesChangedSignal;
269};
270
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271struct UserSession
272{
273 std::string uniqueId;
274 std::string sessionToken;
275 std::string username;
276 std::string csrfToken;
277 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
278 PersistenceType persistence;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100279
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 /**
281 * @brief Fills object with data from UserSession's JSON representation
282 *
283 * This replaces nlohmann's from_json to ensure no-throw approach
284 *
285 * @param[in] j JSON object from which data should be loaded
286 *
287 * @return a shared pointer if data has been loaded properly, nullptr
288 * otherwise
289 */
290 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
291 {
292 std::shared_ptr<UserSession> userSession =
293 std::make_shared<UserSession>();
294 for (const auto& element : j.items())
295 {
296 const std::string* thisValue =
297 element.value().get_ptr<const std::string*>();
298 if (thisValue == nullptr)
299 {
300 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
301 << element.key() << " was not of type string";
302 return nullptr;
303 }
304 if (element.key() == "unique_id")
305 {
306 userSession->uniqueId = *thisValue;
307 }
308 else if (element.key() == "session_token")
309 {
310 userSession->sessionToken = *thisValue;
311 }
312 else if (element.key() == "csrf_token")
313 {
314 userSession->csrfToken = *thisValue;
315 }
316 else if (element.key() == "username")
317 {
318 userSession->username = *thisValue;
319 }
320 else
321 {
322 BMCWEB_LOG_ERROR
323 << "Got unexpected property reading persistent file: "
324 << element.key();
325 return nullptr;
326 }
327 }
328
329 // For now, sessions that were persisted through a reboot get their idle
330 // timer reset. This could probably be overcome with a better
331 // understanding of wall clock time and steady timer time, possibly
332 // persisting values with wall clock time instead of steady timer, but
333 // the tradeoffs of all the corner cases involved are non-trivial, so
334 // this is done temporarily
335 userSession->lastUpdated = std::chrono::steady_clock::now();
336 userSession->persistence = PersistenceType::TIMEOUT;
337
338 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100339 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100340};
341
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100342struct AuthConfigMethods
343{
344 bool xtoken = true;
345 bool cookie = true;
346 bool sessionToken = true;
347 bool basic = true;
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200348 bool tls = true;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100349
350 void fromJson(const nlohmann::json& j)
351 {
352 for (const auto& element : j.items())
353 {
354 const bool* value = element.value().get_ptr<const bool*>();
355 if (value == nullptr)
356 {
357 continue;
358 }
359
360 if (element.key() == "XToken")
361 {
362 xtoken = *value;
363 }
364 else if (element.key() == "Cookie")
365 {
366 cookie = *value;
367 }
368 else if (element.key() == "SessionToken")
369 {
370 sessionToken = *value;
371 }
372 else if (element.key() == "BasicAuth")
373 {
374 basic = *value;
375 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200376 else if (element.key() == "TLS")
377 {
378 tls = *value;
379 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100380 }
381 }
382};
383
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100384class Middleware;
385
Ed Tanous1abe55e2018-09-05 08:30:59 -0700386class SessionStore
387{
388 public:
389 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800390 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700391 PersistenceType persistence = PersistenceType::TIMEOUT)
392 {
393 // TODO(ed) find a secure way to not generate session identifiers if
394 // persistence is set to SINGLE_REQUEST
395 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500396 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
397 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
398 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700399 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
400 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100401
Ed Tanous1abe55e2018-09-05 08:30:59 -0700402 // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
403 // entropy. OWASP recommends at least 60
404 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
405 std::string sessionToken;
406 sessionToken.resize(20, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700407 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
408 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700409 {
410 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100411 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700412 // Only need csrf tokens for cookie based auth, token doesn't matter
413 std::string csrfToken;
414 csrfToken.resize(20, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700415 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700416 {
417 csrfToken[i] = alphanum[dist(rd)];
418 }
419
420 std::string uniqueId;
421 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700422 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700423 {
424 uniqueId[i] = alphanum[dist(rd)];
425 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530426
Ed Tanous1abe55e2018-09-05 08:30:59 -0700427 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700428 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700429 std::chrono::steady_clock::now(), persistence});
430 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
431 // Only need to write to disk if session isn't about to be destroyed.
432 needWrite = persistence == PersistenceType::TIMEOUT;
433 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100434 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700435
436 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800437 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700438 {
439 applySessionTimeouts();
440 auto sessionIt = authTokens.find(std::string(token));
441 if (sessionIt == authTokens.end())
442 {
443 return nullptr;
444 }
445 std::shared_ptr<UserSession> userSession = sessionIt->second;
446 userSession->lastUpdated = std::chrono::steady_clock::now();
447 return userSession;
448 }
449
Ed Tanous39e77502019-03-04 17:35:53 -0800450 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700451 {
452 applySessionTimeouts();
453 // TODO(Ed) this is inefficient
454 auto sessionIt = authTokens.begin();
455 while (sessionIt != authTokens.end())
456 {
457 if (sessionIt->second->uniqueId == uid)
458 {
459 return sessionIt->second;
460 }
461 sessionIt++;
462 }
463 return nullptr;
464 }
465
466 void removeSession(std::shared_ptr<UserSession> session)
467 {
468 authTokens.erase(session->sessionToken);
469 needWrite = true;
470 }
471
472 std::vector<const std::string*> getUniqueIds(
473 bool getAll = true,
474 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
475 {
476 applySessionTimeouts();
477
478 std::vector<const std::string*> ret;
479 ret.reserve(authTokens.size());
480 for (auto& session : authTokens)
481 {
482 if (getAll || type == session.second->persistence)
483 {
484 ret.push_back(&session.second->uniqueId);
485 }
486 }
487 return ret;
488 }
489
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100490 void updateAuthMethodsConfig(const AuthConfigMethods& config)
491 {
492 authMethodsConfig = config;
493 needWrite = true;
494 }
495
496 AuthConfigMethods& getAuthMethodsConfig()
497 {
498 return authMethodsConfig;
499 }
500
Ed Tanous1abe55e2018-09-05 08:30:59 -0700501 bool needsWrite()
502 {
503 return needWrite;
504 }
Ed Tanous271584a2019-07-09 16:24:22 -0700505 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700506 {
507 return std::chrono::seconds(timeoutInMinutes).count();
508 };
509
510 // Persistent data middleware needs to be able to serialize our authTokens
511 // structure, which is private
512 friend Middleware;
513
514 static SessionStore& getInstance()
515 {
516 static SessionStore sessionStore;
517 return sessionStore;
518 }
519
520 SessionStore(const SessionStore&) = delete;
521 SessionStore& operator=(const SessionStore&) = delete;
522
523 private:
524 SessionStore() : timeoutInMinutes(60)
525 {
526 }
527
528 void applySessionTimeouts()
529 {
530 auto timeNow = std::chrono::steady_clock::now();
531 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
532 {
533 lastTimeoutUpdate = timeNow;
534 auto authTokensIt = authTokens.begin();
535 while (authTokensIt != authTokens.end())
536 {
537 if (timeNow - authTokensIt->second->lastUpdated >=
538 timeoutInMinutes)
539 {
540 authTokensIt = authTokens.erase(authTokensIt);
541 needWrite = true;
542 }
543 else
544 {
545 authTokensIt++;
546 }
547 }
548 }
549 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530550
Ed Tanous1abe55e2018-09-05 08:30:59 -0700551 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
552 boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
553 authTokens;
554 std::random_device rd;
555 bool needWrite{false};
556 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100557 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100558};
559
Ed Tanous1abe55e2018-09-05 08:30:59 -0700560} // namespace persistent_data
561} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200562
563// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700564namespace nlohmann
565{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200566template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700567struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
568{
569 static void
570 to_json(nlohmann::json& j,
571 const std::shared_ptr<crow::persistent_data::UserSession>& p)
572 {
573 if (p->persistence !=
574 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
575 {
576 j = nlohmann::json{{"unique_id", p->uniqueId},
577 {"session_token", p->sessionToken},
578 {"username", p->username},
579 {"csrf_token", p->csrfToken}};
580 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200581 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200582};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100583
584template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
585{
586 static void to_json(nlohmann::json& j,
587 const crow::persistent_data::AuthConfigMethods& c)
588 {
589 j = nlohmann::json{{"XToken", c.xtoken},
590 {"Cookie", c.cookie},
591 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200592 {"BasicAuth", c.basic},
593 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100594 }
595};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700596} // namespace nlohmann