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