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 bool xtoken = true; 146 bool cookie = true; 147 bool sessionToken = true; 148 bool basic = true; 149 bool tls = false; 150 151 void fromJson(const nlohmann::json& j) 152 { 153 for (const auto& element : j.items()) 154 { 155 const bool* value = element.value().get_ptr<const bool*>(); 156 if (value == nullptr) 157 { 158 continue; 159 } 160 161 if (element.key() == "XToken") 162 { 163 xtoken = *value; 164 } 165 else if (element.key() == "Cookie") 166 { 167 cookie = *value; 168 } 169 else if (element.key() == "SessionToken") 170 { 171 sessionToken = *value; 172 } 173 else if (element.key() == "BasicAuth") 174 { 175 basic = *value; 176 } 177 else if (element.key() == "TLS") 178 { 179 tls = *value; 180 } 181 } 182 } 183 }; 184 185 class SessionStore 186 { 187 public: 188 std::shared_ptr<UserSession> generateUserSession( 189 const std::string_view username, 190 PersistenceType persistence = PersistenceType::TIMEOUT, 191 bool isConfigureSelfOnly = false, const std::string_view clientId = "", 192 const std::string_view clientIp = "") 193 { 194 // TODO(ed) find a secure way to not generate session identifiers if 195 // persistence is set to SINGLE_REQUEST 196 static constexpr std::array<char, 62> alphanum = { 197 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 198 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 199 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 200 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 201 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 202 203 std::string sessionToken; 204 sessionToken.resize(sessionTokenSize, '0'); 205 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1); 206 207 bmcweb::OpenSSLGenerator gen; 208 209 for (char& sessionChar : sessionToken) 210 { 211 sessionChar = alphanum[dist(gen)]; 212 if (gen.error()) 213 { 214 return nullptr; 215 } 216 } 217 // Only need csrf tokens for cookie based auth, token doesn't matter 218 std::string csrfToken; 219 csrfToken.resize(sessionTokenSize, '0'); 220 for (char& csrfChar : csrfToken) 221 { 222 csrfChar = alphanum[dist(gen)]; 223 if (gen.error()) 224 { 225 return nullptr; 226 } 227 } 228 229 std::string uniqueId; 230 uniqueId.resize(10, '0'); 231 for (char& uidChar : uniqueId) 232 { 233 uidChar = alphanum[dist(gen)]; 234 if (gen.error()) 235 { 236 return nullptr; 237 } 238 } 239 auto session = std::make_shared<UserSession>( 240 UserSession{uniqueId, sessionToken, std::string(username), 241 csrfToken, std::string(clientId), std::string(clientIp), 242 std::chrono::steady_clock::now(), persistence, false, 243 isConfigureSelfOnly}); 244 auto it = authTokens.emplace(std::make_pair(sessionToken, session)); 245 // Only need to write to disk if session isn't about to be destroyed. 246 needWrite = persistence == PersistenceType::TIMEOUT; 247 return it.first->second; 248 } 249 250 std::shared_ptr<UserSession> 251 loginSessionByToken(const std::string_view token) 252 { 253 applySessionTimeouts(); 254 if (token.size() != sessionTokenSize) 255 { 256 return nullptr; 257 } 258 auto sessionIt = authTokens.find(std::string(token)); 259 if (sessionIt == authTokens.end()) 260 { 261 return nullptr; 262 } 263 std::shared_ptr<UserSession> userSession = sessionIt->second; 264 userSession->lastUpdated = std::chrono::steady_clock::now(); 265 return userSession; 266 } 267 268 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid) 269 { 270 applySessionTimeouts(); 271 // TODO(Ed) this is inefficient 272 auto sessionIt = authTokens.begin(); 273 while (sessionIt != authTokens.end()) 274 { 275 if (sessionIt->second->uniqueId == uid) 276 { 277 return sessionIt->second; 278 } 279 sessionIt++; 280 } 281 return nullptr; 282 } 283 284 void removeSession(const std::shared_ptr<UserSession>& session) 285 { 286 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 287 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 288 #endif 289 authTokens.erase(session->sessionToken); 290 needWrite = true; 291 } 292 293 std::vector<const std::string*> getUniqueIds( 294 bool getAll = true, 295 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 296 { 297 applySessionTimeouts(); 298 299 std::vector<const std::string*> ret; 300 ret.reserve(authTokens.size()); 301 for (auto& session : authTokens) 302 { 303 if (getAll || type == session.second->persistence) 304 { 305 ret.push_back(&session.second->uniqueId); 306 } 307 } 308 return ret; 309 } 310 311 void updateAuthMethodsConfig(const AuthConfigMethods& config) 312 { 313 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 314 authMethodsConfig = config; 315 needWrite = true; 316 if (isTLSchanged) 317 { 318 // recreate socket connections with new settings 319 std::raise(SIGHUP); 320 } 321 } 322 323 AuthConfigMethods& getAuthMethodsConfig() 324 { 325 return authMethodsConfig; 326 } 327 328 bool needsWrite() 329 { 330 return needWrite; 331 } 332 int64_t getTimeoutInSeconds() const 333 { 334 return std::chrono::seconds(timeoutInSeconds).count(); 335 } 336 337 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds) 338 { 339 timeoutInSeconds = newTimeoutInSeconds; 340 needWrite = true; 341 } 342 343 static SessionStore& getInstance() 344 { 345 static SessionStore sessionStore; 346 return sessionStore; 347 } 348 349 SessionStore(const SessionStore&) = delete; 350 SessionStore& operator=(const SessionStore&) = delete; 351 352 std::unordered_map<std::string, std::shared_ptr<UserSession>, 353 std::hash<std::string>, 354 crow::utility::ConstantTimeCompare> 355 authTokens; 356 357 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 358 bool needWrite{false}; 359 std::chrono::seconds timeoutInSeconds; 360 AuthConfigMethods authMethodsConfig; 361 362 private: 363 SessionStore() : timeoutInSeconds(3600) 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 395 } // namespace persistent_data 396