1 #pragma once 2 3 #include "logging.hpp" 4 #include "ossl_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 15 namespace persistent_data 16 { 17 18 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 19 // entropy. OWASP recommends at least 64 20 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 21 constexpr std::size_t sessionTokenSize = 20; 22 23 enum class PersistenceType 24 { 25 TIMEOUT, // User session times out after a predetermined amount of time 26 SINGLE_REQUEST // User times out once this request is completed. 27 }; 28 29 struct UserSession 30 { 31 std::string uniqueId; 32 std::string sessionToken; 33 std::string username; 34 std::string csrfToken; 35 std::optional<std::string> clientId; 36 std::string clientIp; 37 std::chrono::time_point<std::chrono::steady_clock> lastUpdated; 38 PersistenceType persistence{PersistenceType::TIMEOUT}; 39 bool cookieAuth = false; 40 bool isConfigureSelfOnly = false; 41 std::string userRole; 42 std::vector<std::string> userGroups; 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( 73 "Error reading persistent store. Property {} was not of type string", 74 element.key()); 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>( 257 UserSession{uniqueId, 258 sessionToken, 259 std::string(username), 260 csrfToken, 261 clientId, 262 redfish::ip_util::toString(clientIp), 263 std::chrono::steady_clock::now(), 264 persistence, 265 false, 266 isConfigureSelfOnly, 267 "", 268 {}}); 269 auto it = authTokens.emplace(sessionToken, session); 270 // Only need to write to disk if session isn't about to be destroyed. 271 needWrite = persistence == PersistenceType::TIMEOUT; 272 return it.first->second; 273 } 274 275 std::shared_ptr<UserSession> loginSessionByToken(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(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 authTokens.erase(session->sessionToken); 311 needWrite = true; 312 } 313 314 std::vector<const std::string*> getUniqueIds( 315 bool getAll = true, 316 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 317 { 318 applySessionTimeouts(); 319 320 std::vector<const std::string*> ret; 321 ret.reserve(authTokens.size()); 322 for (auto& session : authTokens) 323 { 324 if (getAll || type == session.second->persistence) 325 { 326 ret.push_back(&session.second->uniqueId); 327 } 328 } 329 return ret; 330 } 331 332 void removeSessionsByUsername(std::string_view username) 333 { 334 std::erase_if(authTokens, [username](const auto& value) { 335 if (value.second == nullptr) 336 { 337 return false; 338 } 339 return value.second->username == username; 340 }); 341 } 342 343 void updateAuthMethodsConfig(const AuthConfigMethods& config) 344 { 345 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 346 authMethodsConfig = config; 347 needWrite = true; 348 if (isTLSchanged) 349 { 350 // recreate socket connections with new settings 351 std::raise(SIGHUP); 352 } 353 } 354 355 AuthConfigMethods& getAuthMethodsConfig() 356 { 357 return authMethodsConfig; 358 } 359 360 bool needsWrite() const 361 { 362 return needWrite; 363 } 364 int64_t getTimeoutInSeconds() const 365 { 366 return std::chrono::seconds(timeoutInSeconds).count(); 367 } 368 369 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 370 { 371 timeoutInSeconds = newTimeoutInSeconds; 372 needWrite = true; 373 } 374 375 static SessionStore& getInstance() 376 { 377 static SessionStore sessionStore; 378 return sessionStore; 379 } 380 381 void applySessionTimeouts() 382 { 383 auto timeNow = std::chrono::steady_clock::now(); 384 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1)) 385 { 386 lastTimeoutUpdate = timeNow; 387 auto authTokensIt = authTokens.begin(); 388 while (authTokensIt != authTokens.end()) 389 { 390 if (timeNow - authTokensIt->second->lastUpdated >= 391 timeoutInSeconds) 392 { 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