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