1 #pragma once 2 3 #include "logging.h" 4 #include "utility.h" 5 6 #include <openssl/rand.h> 7 8 #include <boost/container/flat_map.hpp> 9 #include <boost/uuid/uuid.hpp> 10 #include <boost/uuid/uuid_generators.hpp> 11 #include <boost/uuid/uuid_io.hpp> 12 #include <dbus_singleton.hpp> 13 #include <nlohmann/json.hpp> 14 #include <pam_authenticate.hpp> 15 #include <sdbusplus/bus/match.hpp> 16 17 #include <csignal> 18 #include <random> 19 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 20 #include <ibm/locks.hpp> 21 #endif 22 23 namespace crow 24 { 25 26 namespace persistent_data 27 { 28 29 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 30 // entropy. OWASP recommends at least 64 31 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 32 constexpr std::size_t sessionTokenSize = 20; 33 34 enum class PersistenceType 35 { 36 TIMEOUT, // User session times out after a predetermined amount of time 37 SINGLE_REQUEST // User times out once this request is completed. 38 }; 39 40 struct UserSession 41 { 42 std::string uniqueId; 43 std::string sessionToken; 44 std::string username; 45 std::string csrfToken; 46 std::string clientId; 47 std::string clientIp; 48 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 49 PersistenceType persistence; 50 bool cookieAuth = false; 51 bool isConfigureSelfOnly = false; 52 53 // There are two sources of truth for isConfigureSelfOnly: 54 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD. 55 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired. 56 // These should be in sync, but the underlying condition can change at any 57 // time. For example, a password can expire or be changed outside of 58 // bmcweb. The value stored here is updated at the start of each 59 // operation and used as the truth within bmcweb. 60 61 /** 62 * @brief Fills object with data from UserSession's JSON representation 63 * 64 * This replaces nlohmann's from_json to ensure no-throw approach 65 * 66 * @param[in] j JSON object from which data should be loaded 67 * 68 * @return a shared pointer if data has been loaded properly, nullptr 69 * otherwise 70 */ 71 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) 72 { 73 std::shared_ptr<UserSession> userSession = 74 std::make_shared<UserSession>(); 75 for (const auto& element : j.items()) 76 { 77 const std::string* thisValue = 78 element.value().get_ptr<const std::string*>(); 79 if (thisValue == nullptr) 80 { 81 BMCWEB_LOG_ERROR << "Error reading persistent store. Property " 82 << element.key() << " was not of type string"; 83 return nullptr; 84 } 85 if (element.key() == "unique_id") 86 { 87 userSession->uniqueId = *thisValue; 88 } 89 else if (element.key() == "session_token") 90 { 91 userSession->sessionToken = *thisValue; 92 } 93 else if (element.key() == "csrf_token") 94 { 95 userSession->csrfToken = *thisValue; 96 } 97 else if (element.key() == "username") 98 { 99 userSession->username = *thisValue; 100 } 101 else if (element.key() == "client_id") 102 { 103 userSession->clientId = *thisValue; 104 } 105 else if (element.key() == "client_ip") 106 { 107 userSession->clientIp = *thisValue; 108 } 109 110 else 111 { 112 BMCWEB_LOG_ERROR 113 << "Got unexpected property reading persistent file: " 114 << element.key(); 115 return nullptr; 116 } 117 } 118 119 // For now, sessions that were persisted through a reboot get their idle 120 // timer reset. This could probably be overcome with a better 121 // understanding of wall clock time and steady timer time, possibly 122 // persisting values with wall clock time instead of steady timer, but 123 // the tradeoffs of all the corner cases involved are non-trivial, so 124 // this is done temporarily 125 userSession->lastUpdated = std::chrono::steady_clock::now(); 126 userSession->persistence = PersistenceType::TIMEOUT; 127 128 return userSession; 129 } 130 }; 131 132 struct AuthConfigMethods 133 { 134 bool xtoken = true; 135 bool cookie = true; 136 bool sessionToken = true; 137 bool basic = true; 138 bool tls = false; 139 140 void fromJson(const nlohmann::json& j) 141 { 142 for (const auto& element : j.items()) 143 { 144 const bool* value = element.value().get_ptr<const bool*>(); 145 if (value == nullptr) 146 { 147 continue; 148 } 149 150 if (element.key() == "XToken") 151 { 152 xtoken = *value; 153 } 154 else if (element.key() == "Cookie") 155 { 156 cookie = *value; 157 } 158 else if (element.key() == "SessionToken") 159 { 160 sessionToken = *value; 161 } 162 else if (element.key() == "BasicAuth") 163 { 164 basic = *value; 165 } 166 else if (element.key() == "TLS") 167 { 168 tls = *value; 169 } 170 } 171 } 172 }; 173 174 class Middleware; 175 176 struct OpenSSLGenerator 177 { 178 179 uint8_t operator()(void) 180 { 181 uint8_t index = 0; 182 int rc = RAND_bytes(&index, sizeof(index)); 183 if (rc != opensslSuccess) 184 { 185 std::cerr << "Cannot get random number\n"; 186 err = true; 187 } 188 189 return index; 190 }; 191 192 uint8_t max() 193 { 194 return std::numeric_limits<uint8_t>::max(); 195 } 196 uint8_t min() 197 { 198 return std::numeric_limits<uint8_t>::min(); 199 } 200 201 bool error() 202 { 203 return err; 204 } 205 206 // all generators require this variable 207 using result_type = uint8_t; 208 209 private: 210 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function 211 static constexpr int opensslSuccess = 1; 212 bool err = false; 213 }; 214 215 class SessionStore 216 { 217 public: 218 std::shared_ptr<UserSession> generateUserSession( 219 const std::string_view username, 220 PersistenceType persistence = PersistenceType::TIMEOUT, 221 bool isConfigureSelfOnly = false, const std::string_view clientId = "", 222 const std::string_view clientIp = "") 223 { 224 // TODO(ed) find a secure way to not generate session identifiers if 225 // persistence is set to SINGLE_REQUEST 226 static constexpr std::array<char, 62> alphanum = { 227 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 228 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 229 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 230 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 231 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 232 233 std::string sessionToken; 234 sessionToken.resize(sessionTokenSize, '0'); 235 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1); 236 237 OpenSSLGenerator gen; 238 239 for (size_t i = 0; i < sessionToken.size(); ++i) 240 { 241 sessionToken[i] = alphanum[dist(gen)]; 242 if (gen.error()) 243 { 244 return nullptr; 245 } 246 } 247 // Only need csrf tokens for cookie based auth, token doesn't matter 248 std::string csrfToken; 249 csrfToken.resize(sessionTokenSize, '0'); 250 for (size_t i = 0; i < csrfToken.size(); ++i) 251 { 252 csrfToken[i] = alphanum[dist(gen)]; 253 if (gen.error()) 254 { 255 return nullptr; 256 } 257 } 258 259 std::string uniqueId; 260 uniqueId.resize(10, '0'); 261 for (size_t i = 0; i < uniqueId.size(); ++i) 262 { 263 uniqueId[i] = alphanum[dist(gen)]; 264 if (gen.error()) 265 { 266 return nullptr; 267 } 268 } 269 auto session = std::make_shared<UserSession>( 270 UserSession{uniqueId, sessionToken, std::string(username), 271 csrfToken, std::string(clientId), std::string(clientIp), 272 std::chrono::steady_clock::now(), persistence, false, 273 isConfigureSelfOnly}); 274 auto it = authTokens.emplace(std::make_pair(sessionToken, session)); 275 // Only need to write to disk if session isn't about to be destroyed. 276 needWrite = persistence == PersistenceType::TIMEOUT; 277 return it.first->second; 278 } 279 280 std::shared_ptr<UserSession> 281 loginSessionByToken(const std::string_view token) 282 { 283 applySessionTimeouts(); 284 if (token.size() != sessionTokenSize) 285 { 286 return nullptr; 287 } 288 auto sessionIt = authTokens.find(std::string(token)); 289 if (sessionIt == authTokens.end()) 290 { 291 return nullptr; 292 } 293 std::shared_ptr<UserSession> userSession = sessionIt->second; 294 userSession->lastUpdated = std::chrono::steady_clock::now(); 295 return userSession; 296 } 297 298 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid) 299 { 300 applySessionTimeouts(); 301 // TODO(Ed) this is inefficient 302 auto sessionIt = authTokens.begin(); 303 while (sessionIt != authTokens.end()) 304 { 305 if (sessionIt->second->uniqueId == uid) 306 { 307 return sessionIt->second; 308 } 309 sessionIt++; 310 } 311 return nullptr; 312 } 313 314 void removeSession(std::shared_ptr<UserSession> session) 315 { 316 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 317 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 318 #endif 319 authTokens.erase(session->sessionToken); 320 needWrite = true; 321 } 322 323 std::vector<const std::string*> getUniqueIds( 324 bool getAll = true, 325 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 326 { 327 applySessionTimeouts(); 328 329 std::vector<const std::string*> ret; 330 ret.reserve(authTokens.size()); 331 for (auto& session : authTokens) 332 { 333 if (getAll || type == session.second->persistence) 334 { 335 ret.push_back(&session.second->uniqueId); 336 } 337 } 338 return ret; 339 } 340 341 void updateAuthMethodsConfig(const AuthConfigMethods& config) 342 { 343 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 344 authMethodsConfig = config; 345 needWrite = true; 346 if (isTLSchanged) 347 { 348 // recreate socket connections with new settings 349 std::raise(SIGHUP); 350 } 351 } 352 353 AuthConfigMethods& getAuthMethodsConfig() 354 { 355 return authMethodsConfig; 356 } 357 358 bool needsWrite() 359 { 360 return needWrite; 361 } 362 int64_t getTimeoutInSeconds() const 363 { 364 return std::chrono::seconds(timeoutInMinutes).count(); 365 }; 366 367 // Persistent data middleware needs to be able to serialize our authTokens 368 // structure, which is private 369 friend Middleware; 370 371 static SessionStore& getInstance() 372 { 373 static SessionStore sessionStore; 374 return sessionStore; 375 } 376 377 SessionStore(const SessionStore&) = delete; 378 SessionStore& operator=(const SessionStore&) = delete; 379 380 private: 381 SessionStore() : timeoutInMinutes(60) 382 {} 383 384 void applySessionTimeouts() 385 { 386 auto timeNow = std::chrono::steady_clock::now(); 387 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1)) 388 { 389 lastTimeoutUpdate = timeNow; 390 auto authTokensIt = authTokens.begin(); 391 while (authTokensIt != authTokens.end()) 392 { 393 if (timeNow - authTokensIt->second->lastUpdated >= 394 timeoutInMinutes) 395 { 396 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 397 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 398 authTokensIt->second->uniqueId); 399 #endif 400 authTokensIt = authTokens.erase(authTokensIt); 401 402 needWrite = true; 403 } 404 else 405 { 406 authTokensIt++; 407 } 408 } 409 } 410 } 411 412 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 413 std::unordered_map<std::string, std::shared_ptr<UserSession>, 414 std::hash<std::string>, 415 crow::utility::ConstantTimeCompare> 416 authTokens; 417 bool needWrite{false}; 418 std::chrono::minutes timeoutInMinutes; 419 AuthConfigMethods authMethodsConfig; 420 }; 421 422 } // namespace persistent_data 423 } // namespace crow 424 425 // to_json(...) definition for objects of UserSession type 426 namespace nlohmann 427 { 428 template <> 429 struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>> 430 { 431 static void 432 to_json(nlohmann::json& j, 433 const std::shared_ptr<crow::persistent_data::UserSession>& p) 434 { 435 if (p->persistence != 436 crow::persistent_data::PersistenceType::SINGLE_REQUEST) 437 { 438 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 439 j = nlohmann::json{ 440 {"unique_id", p->uniqueId}, {"session_token", p->sessionToken}, 441 {"username", p->username}, {"csrf_token", p->csrfToken}, 442 {"client_id", p->clientId}, { "client_ip", p->clientIp }}; 443 #else 444 j = nlohmann::json{{"unique_id", p->uniqueId}, 445 {"session_token", p->sessionToken}, 446 {"username", p->username}, 447 {"csrf_token", p->csrfToken}, 448 {"client_ip", p->clientIp}}; 449 #endif 450 } 451 } 452 }; 453 454 template <> 455 struct adl_serializer<crow::persistent_data::AuthConfigMethods> 456 { 457 static void to_json(nlohmann::json& j, 458 const crow::persistent_data::AuthConfigMethods& c) 459 { 460 j = nlohmann::json{{"XToken", c.xtoken}, 461 {"Cookie", c.cookie}, 462 {"SessionToken", c.sessionToken}, 463 {"BasicAuth", c.basic}, 464 {"TLS", c.tls}}; 465 } 466 }; 467 } // namespace nlohmann 468