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