1 #pragma once 2 3 #include "logging.hpp" 4 #include "random.hpp" 5 #include "utility.hpp" 6 7 #include <nlohmann/json.hpp> 8 #include <utils/ip_utils.hpp> 9 10 #include <csignal> 11 #include <random> 12 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 13 #include <ibm/locks.hpp> 14 #endif 15 16 namespace persistent_data 17 { 18 19 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 20 // entropy. OWASP recommends at least 64 21 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 22 constexpr std::size_t sessionTokenSize = 20; 23 24 enum class PersistenceType 25 { 26 TIMEOUT, // User session times out after a predetermined amount of time 27 SINGLE_REQUEST // User times out once this request is completed. 28 }; 29 30 struct UserSession 31 { 32 std::string uniqueId; 33 std::string sessionToken; 34 std::string username; 35 std::string csrfToken; 36 std::string clientId; 37 std::string clientIp; 38 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 39 PersistenceType persistence{PersistenceType::TIMEOUT}; 40 bool cookieAuth = false; 41 bool isConfigureSelfOnly = false; 42 43 // There are two sources of truth for isConfigureSelfOnly: 44 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD. 45 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired. 46 // These should be in sync, but the underlying condition can change at any 47 // time. For example, a password can expire or be changed outside of 48 // bmcweb. The value stored here is updated at the start of each 49 // operation and used as the truth within bmcweb. 50 51 /** 52 * @brief Fills object with data from UserSession's JSON representation 53 * 54 * This replaces nlohmann's from_json to ensure no-throw approach 55 * 56 * @param[in] j JSON object from which data should be loaded 57 * 58 * @return a shared pointer if data has been loaded properly, nullptr 59 * otherwise 60 */ 61 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) 62 { 63 std::shared_ptr<UserSession> userSession = 64 std::make_shared<UserSession>(); 65 for (const auto& element : j.items()) 66 { 67 const std::string* thisValue = 68 element.value().get_ptr<const std::string*>(); 69 if (thisValue == nullptr) 70 { 71 BMCWEB_LOG_ERROR << "Error reading persistent store. Property " 72 << element.key() << " was not of type string"; 73 continue; 74 } 75 if (element.key() == "unique_id") 76 { 77 userSession->uniqueId = *thisValue; 78 } 79 else if (element.key() == "session_token") 80 { 81 userSession->sessionToken = *thisValue; 82 } 83 else if (element.key() == "csrf_token") 84 { 85 userSession->csrfToken = *thisValue; 86 } 87 else if (element.key() == "username") 88 { 89 userSession->username = *thisValue; 90 } 91 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 92 else if (element.key() == "client_id") 93 { 94 userSession->clientId = *thisValue; 95 } 96 #endif 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 const std::string_view username, 206 const boost::asio::ip::address& clientIp, 207 const std::string_view 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, 259 std::string(clientId), redfish::ip_util::toString(clientIp), 260 std::chrono::steady_clock::now(), persistence, false, 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> 269 loginSessionByToken(const std::string_view token) 270 { 271 applySessionTimeouts(); 272 if (token.size() != sessionTokenSize) 273 { 274 return nullptr; 275 } 276 auto sessionIt = authTokens.find(std::string(token)); 277 if (sessionIt == authTokens.end()) 278 { 279 return nullptr; 280 } 281 std::shared_ptr<UserSession> userSession = sessionIt->second; 282 userSession->lastUpdated = std::chrono::steady_clock::now(); 283 return userSession; 284 } 285 286 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid) 287 { 288 applySessionTimeouts(); 289 // TODO(Ed) this is inefficient 290 auto sessionIt = authTokens.begin(); 291 while (sessionIt != authTokens.end()) 292 { 293 if (sessionIt->second->uniqueId == uid) 294 { 295 return sessionIt->second; 296 } 297 sessionIt++; 298 } 299 return nullptr; 300 } 301 302 void removeSession(const std::shared_ptr<UserSession>& session) 303 { 304 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 305 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 306 #endif 307 authTokens.erase(session->sessionToken); 308 needWrite = true; 309 } 310 311 std::vector<const std::string*> getUniqueIds( 312 bool getAll = true, 313 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 314 { 315 applySessionTimeouts(); 316 317 std::vector<const std::string*> ret; 318 ret.reserve(authTokens.size()); 319 for (auto& session : authTokens) 320 { 321 if (getAll || type == session.second->persistence) 322 { 323 ret.push_back(&session.second->uniqueId); 324 } 325 } 326 return ret; 327 } 328 329 void updateAuthMethodsConfig(const AuthConfigMethods& config) 330 { 331 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 332 authMethodsConfig = config; 333 needWrite = true; 334 if (isTLSchanged) 335 { 336 // recreate socket connections with new settings 337 std::raise(SIGHUP); 338 } 339 } 340 341 AuthConfigMethods& getAuthMethodsConfig() 342 { 343 return authMethodsConfig; 344 } 345 346 bool needsWrite() const 347 { 348 return needWrite; 349 } 350 int64_t getTimeoutInSeconds() const 351 { 352 return std::chrono::seconds(timeoutInSeconds).count(); 353 } 354 355 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 356 { 357 timeoutInSeconds = newTimeoutInSeconds; 358 needWrite = true; 359 } 360 361 static SessionStore& getInstance() 362 { 363 static SessionStore sessionStore; 364 return sessionStore; 365 } 366 367 void applySessionTimeouts() 368 { 369 auto timeNow = std::chrono::steady_clock::now(); 370 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 371 { 372 lastTimeoutUpdate = timeNow; 373 auto authTokensIt = authTokens.begin(); 374 while (authTokensIt != authTokens.end()) 375 { 376 if (timeNow - authTokensIt->second->lastUpdated >= 377 timeoutInSeconds) 378 { 379 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 380 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 381 authTokensIt->second->uniqueId); 382 #endif 383 authTokensIt = authTokens.erase(authTokensIt); 384 385 needWrite = true; 386 } 387 else 388 { 389 authTokensIt++; 390 } 391 } 392 } 393 } 394 395 SessionStore(const SessionStore&) = delete; 396 SessionStore& operator=(const SessionStore&) = delete; 397 SessionStore(SessionStore&&) = delete; 398 SessionStore& operator=(const SessionStore&&) = delete; 399 ~SessionStore() = default; 400 401 std::unordered_map<std::string, std::shared_ptr<UserSession>, 402 std::hash<std::string>, 403 crow::utility::ConstantTimeCompare> 404 authTokens; 405 406 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 407 bool needWrite{false}; 408 std::chrono::seconds timeoutInSeconds; 409 AuthConfigMethods authMethodsConfig; 410 411 private: 412 SessionStore() : timeoutInSeconds(1800) 413 {} 414 }; 415 416 } // namespace persistent_data 417