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 <csignal> 11 #include <optional> 12 #include <random> 13 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 14 #include "ibm/locks.hpp" 15 #endif 16 17 namespace persistent_data 18 { 19 20 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 21 // entropy. OWASP recommends at least 64 22 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 23 constexpr std::size_t sessionTokenSize = 20; 24 25 enum class PersistenceType 26 { 27 TIMEOUT, // User session times out after a predetermined amount of time 28 SINGLE_REQUEST // User times out once this request is completed. 29 }; 30 31 struct UserSession 32 { 33 std::string uniqueId; 34 std::string sessionToken; 35 std::string username; 36 std::string csrfToken; 37 std::optional<std::string> clientId; 38 std::string clientIp; 39 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 40 PersistenceType persistence{PersistenceType::TIMEOUT}; 41 bool cookieAuth = false; 42 bool isConfigureSelfOnly = false; 43 44 // There are two sources of truth for isConfigureSelfOnly: 45 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD. 46 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired. 47 // These should be in sync, but the underlying condition can change at any 48 // time. For example, a password can expire or be changed outside of 49 // bmcweb. The value stored here is updated at the start of each 50 // operation and used as the truth within bmcweb. 51 52 /** 53 * @brief Fills object with data from UserSession's JSON representation 54 * 55 * This replaces nlohmann's from_json to ensure no-throw approach 56 * 57 * @param[in] j JSON object from which data should be loaded 58 * 59 * @return a shared pointer if data has been loaded properly, nullptr 60 * otherwise 61 */ 62 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) 63 { 64 std::shared_ptr<UserSession> userSession = 65 std::make_shared<UserSession>(); 66 for (const auto& element : j.items()) 67 { 68 const std::string* thisValue = 69 element.value().get_ptr<const std::string*>(); 70 if (thisValue == nullptr) 71 { 72 BMCWEB_LOG_ERROR << "Error reading persistent store. Property " 73 << element.key() << " was not of type string"; 74 continue; 75 } 76 if (element.key() == "unique_id") 77 { 78 userSession->uniqueId = *thisValue; 79 } 80 else if (element.key() == "session_token") 81 { 82 userSession->sessionToken = *thisValue; 83 } 84 else if (element.key() == "csrf_token") 85 { 86 userSession->csrfToken = *thisValue; 87 } 88 else if (element.key() == "username") 89 { 90 userSession->username = *thisValue; 91 } 92 else if (element.key() == "client_id") 93 { 94 userSession->clientId = *thisValue; 95 } 96 else if (element.key() == "client_ip") 97 { 98 userSession->clientIp = *thisValue; 99 } 100 101 else 102 { 103 BMCWEB_LOG_ERROR 104 << "Got unexpected property reading persistent file: " 105 << element.key(); 106 continue; 107 } 108 } 109 // If any of these fields are missing, we can't restore the session, as 110 // we don't have enough information. These 4 fields have been present 111 // in every version of this file in bmcwebs history, so any file, even 112 // on upgrade, should have these present 113 if (userSession->uniqueId.empty() || userSession->username.empty() || 114 userSession->sessionToken.empty() || userSession->csrfToken.empty()) 115 { 116 BMCWEB_LOG_DEBUG << "Session missing required security " 117 "information, refusing to restore"; 118 return nullptr; 119 } 120 121 // For now, sessions that were persisted through a reboot get their idle 122 // timer reset. This could probably be overcome with a better 123 // understanding of wall clock time and steady timer time, possibly 124 // persisting values with wall clock time instead of steady timer, but 125 // the tradeoffs of all the corner cases involved are non-trivial, so 126 // this is done temporarily 127 userSession->lastUpdated = std::chrono::steady_clock::now(); 128 userSession->persistence = PersistenceType::TIMEOUT; 129 130 return userSession; 131 } 132 }; 133 134 struct AuthConfigMethods 135 { 136 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 137 bool basic = true; 138 #else 139 bool basic = false; 140 #endif 141 142 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 143 bool sessionToken = true; 144 #else 145 bool sessionToken = false; 146 #endif 147 148 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 149 bool xtoken = true; 150 #else 151 bool xtoken = false; 152 #endif 153 154 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 155 bool cookie = true; 156 #else 157 bool cookie = false; 158 #endif 159 160 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 161 bool tls = true; 162 #else 163 bool tls = false; 164 #endif 165 166 void fromJson(const nlohmann::json& j) 167 { 168 for (const auto& element : j.items()) 169 { 170 const bool* value = element.value().get_ptr<const bool*>(); 171 if (value == nullptr) 172 { 173 continue; 174 } 175 176 if (element.key() == "XToken") 177 { 178 xtoken = *value; 179 } 180 else if (element.key() == "Cookie") 181 { 182 cookie = *value; 183 } 184 else if (element.key() == "SessionToken") 185 { 186 sessionToken = *value; 187 } 188 else if (element.key() == "BasicAuth") 189 { 190 basic = *value; 191 } 192 else if (element.key() == "TLS") 193 { 194 tls = *value; 195 } 196 } 197 } 198 }; 199 200 class SessionStore 201 { 202 public: 203 std::shared_ptr<UserSession> generateUserSession( 204 const std::string_view username, 205 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> 268 loginSessionByToken(const 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(const 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 updateAuthMethodsConfig(const AuthConfigMethods& config) 329 { 330 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 331 authMethodsConfig = config; 332 needWrite = true; 333 if (isTLSchanged) 334 { 335 // recreate socket connections with new settings 336 std::raise(SIGHUP); 337 } 338 } 339 340 AuthConfigMethods& getAuthMethodsConfig() 341 { 342 return authMethodsConfig; 343 } 344 345 bool needsWrite() const 346 { 347 return needWrite; 348 } 349 int64_t getTimeoutInSeconds() const 350 { 351 return std::chrono::seconds(timeoutInSeconds).count(); 352 } 353 354 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 355 { 356 timeoutInSeconds = newTimeoutInSeconds; 357 needWrite = true; 358 } 359 360 static SessionStore& getInstance() 361 { 362 static SessionStore sessionStore; 363 return sessionStore; 364 } 365 366 void applySessionTimeouts() 367 { 368 auto timeNow = std::chrono::steady_clock::now(); 369 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 370 { 371 lastTimeoutUpdate = timeNow; 372 auto authTokensIt = authTokens.begin(); 373 while (authTokensIt != authTokens.end()) 374 { 375 if (timeNow - authTokensIt->second->lastUpdated >= 376 timeoutInSeconds) 377 { 378 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 379 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 380 authTokensIt->second->uniqueId); 381 #endif 382 authTokensIt = authTokens.erase(authTokensIt); 383 384 needWrite = true; 385 } 386 else 387 { 388 authTokensIt++; 389 } 390 } 391 } 392 } 393 394 SessionStore(const SessionStore&) = delete; 395 SessionStore& operator=(const SessionStore&) = delete; 396 SessionStore(SessionStore&&) = delete; 397 SessionStore& operator=(const SessionStore&&) = delete; 398 ~SessionStore() = default; 399 400 std::unordered_map<std::string, std::shared_ptr<UserSession>, 401 std::hash<std::string>, 402 crow::utility::ConstantTimeCompare> 403 authTokens; 404 405 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 406 bool needWrite{false}; 407 std::chrono::seconds timeoutInSeconds; 408 AuthConfigMethods authMethodsConfig; 409 410 private: 411 SessionStore() : timeoutInSeconds(1800) 412 {} 413 }; 414 415 } // namespace persistent_data 416