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