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 crow 24 { 25 26 namespace persistent_data 27 { 28 29 // entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of 30 // entropy. OWASP recommends at least 64 31 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy 32 constexpr std::size_t sessionTokenSize = 20; 33 34 enum class PersistenceType 35 { 36 TIMEOUT, // User session times out after a predetermined amount of time 37 SINGLE_REQUEST // User times out once this request is completed. 38 }; 39 40 struct UserSession 41 { 42 std::string uniqueId; 43 std::string sessionToken; 44 std::string username; 45 std::string csrfToken; 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 return nullptr; 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 else 100 { 101 BMCWEB_LOG_ERROR 102 << "Got unexpected property reading persistent file: " 103 << element.key(); 104 return nullptr; 105 } 106 } 107 108 // For now, sessions that were persisted through a reboot get their idle 109 // timer reset. This could probably be overcome with a better 110 // understanding of wall clock time and steady timer time, possibly 111 // persisting values with wall clock time instead of steady timer, but 112 // the tradeoffs of all the corner cases involved are non-trivial, so 113 // this is done temporarily 114 userSession->lastUpdated = std::chrono::steady_clock::now(); 115 userSession->persistence = PersistenceType::TIMEOUT; 116 117 return userSession; 118 } 119 }; 120 121 struct AuthConfigMethods 122 { 123 bool xtoken = true; 124 bool cookie = true; 125 bool sessionToken = true; 126 bool basic = true; 127 bool tls = false; 128 129 void fromJson(const nlohmann::json& j) 130 { 131 for (const auto& element : j.items()) 132 { 133 const bool* value = element.value().get_ptr<const bool*>(); 134 if (value == nullptr) 135 { 136 continue; 137 } 138 139 if (element.key() == "XToken") 140 { 141 xtoken = *value; 142 } 143 else if (element.key() == "Cookie") 144 { 145 cookie = *value; 146 } 147 else if (element.key() == "SessionToken") 148 { 149 sessionToken = *value; 150 } 151 else if (element.key() == "BasicAuth") 152 { 153 basic = *value; 154 } 155 else if (element.key() == "TLS") 156 { 157 tls = *value; 158 } 159 } 160 } 161 }; 162 163 class Middleware; 164 165 struct OpenSSLGenerator 166 { 167 168 uint8_t operator()(void) 169 { 170 uint8_t index = 0; 171 int rc = RAND_bytes(&index, sizeof(index)); 172 if (rc != opensslSuccess) 173 { 174 std::cerr << "Cannot get random number\n"; 175 err = true; 176 } 177 178 return index; 179 }; 180 181 uint8_t max() 182 { 183 return std::numeric_limits<uint8_t>::max(); 184 } 185 uint8_t min() 186 { 187 return std::numeric_limits<uint8_t>::min(); 188 } 189 190 bool error() 191 { 192 return err; 193 } 194 195 // all generators require this variable 196 using result_type = uint8_t; 197 198 private: 199 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function 200 static constexpr int opensslSuccess = 1; 201 bool err = false; 202 }; 203 204 class SessionStore 205 { 206 public: 207 std::shared_ptr<UserSession> generateUserSession( 208 const std::string_view username, 209 PersistenceType persistence = PersistenceType::TIMEOUT, 210 bool isConfigureSelfOnly = false) 211 { 212 // TODO(ed) find a secure way to not generate session identifiers if 213 // persistence is set to SINGLE_REQUEST 214 static constexpr std::array<char, 62> alphanum = { 215 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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', 'a', 'b', 'c', 218 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 219 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 220 221 std::string sessionToken; 222 sessionToken.resize(sessionTokenSize, '0'); 223 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1); 224 225 OpenSSLGenerator gen; 226 227 for (size_t i = 0; i < sessionToken.size(); ++i) 228 { 229 sessionToken[i] = alphanum[dist(gen)]; 230 if (gen.error()) 231 { 232 return nullptr; 233 } 234 } 235 // Only need csrf tokens for cookie based auth, token doesn't matter 236 std::string csrfToken; 237 csrfToken.resize(sessionTokenSize, '0'); 238 for (size_t i = 0; i < csrfToken.size(); ++i) 239 { 240 csrfToken[i] = alphanum[dist(gen)]; 241 if (gen.error()) 242 { 243 return nullptr; 244 } 245 } 246 247 std::string uniqueId; 248 uniqueId.resize(10, '0'); 249 for (size_t i = 0; i < uniqueId.size(); ++i) 250 { 251 uniqueId[i] = alphanum[dist(gen)]; 252 if (gen.error()) 253 { 254 return nullptr; 255 } 256 } 257 258 auto session = std::make_shared<UserSession>( 259 UserSession{uniqueId, sessionToken, std::string(username), 260 csrfToken, std::chrono::steady_clock::now(), 261 persistence, false, isConfigureSelfOnly}); 262 auto it = authTokens.emplace(std::make_pair(sessionToken, session)); 263 // Only need to write to disk if session isn't about to be destroyed. 264 needWrite = persistence == PersistenceType::TIMEOUT; 265 return it.first->second; 266 } 267 268 std::shared_ptr<UserSession> 269 loginSessionByToken(const std::string_view token) 270 { 271 applySessionTimeouts(); 272 if (token.size() != sessionTokenSize) 273 { 274 return nullptr; 275 } 276 auto sessionIt = authTokens.find(std::string(token)); 277 if (sessionIt == authTokens.end()) 278 { 279 return nullptr; 280 } 281 std::shared_ptr<UserSession> userSession = sessionIt->second; 282 userSession->lastUpdated = std::chrono::steady_clock::now(); 283 return userSession; 284 } 285 286 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid) 287 { 288 applySessionTimeouts(); 289 // TODO(Ed) this is inefficient 290 auto sessionIt = authTokens.begin(); 291 while (sessionIt != authTokens.end()) 292 { 293 if (sessionIt->second->uniqueId == uid) 294 { 295 return sessionIt->second; 296 } 297 sessionIt++; 298 } 299 return nullptr; 300 } 301 302 void removeSession(std::shared_ptr<UserSession> session) 303 { 304 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 305 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId); 306 #endif 307 authTokens.erase(session->sessionToken); 308 needWrite = true; 309 } 310 311 std::vector<const std::string*> getUniqueIds( 312 bool getAll = true, 313 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) 314 { 315 applySessionTimeouts(); 316 317 std::vector<const std::string*> ret; 318 ret.reserve(authTokens.size()); 319 for (auto& session : authTokens) 320 { 321 if (getAll || type == session.second->persistence) 322 { 323 ret.push_back(&session.second->uniqueId); 324 } 325 } 326 return ret; 327 } 328 329 void updateAuthMethodsConfig(const AuthConfigMethods& config) 330 { 331 bool isTLSchanged = (authMethodsConfig.tls != config.tls); 332 authMethodsConfig = config; 333 needWrite = true; 334 if (isTLSchanged) 335 { 336 // recreate socket connections with new settings 337 std::raise(SIGHUP); 338 } 339 } 340 341 AuthConfigMethods& getAuthMethodsConfig() 342 { 343 return authMethodsConfig; 344 } 345 346 bool needsWrite() 347 { 348 return needWrite; 349 } 350 int64_t getTimeoutInSeconds() const 351 { 352 return std::chrono::seconds(timeoutInMinutes).count(); 353 }; 354 355 // Persistent data middleware needs to be able to serialize our authTokens 356 // structure, which is private 357 friend Middleware; 358 359 static SessionStore& getInstance() 360 { 361 static SessionStore sessionStore; 362 return sessionStore; 363 } 364 365 SessionStore(const SessionStore&) = delete; 366 SessionStore& operator=(const SessionStore&) = delete; 367 368 private: 369 SessionStore() : timeoutInMinutes(60) 370 {} 371 372 void applySessionTimeouts() 373 { 374 auto timeNow = std::chrono::steady_clock::now(); 375 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1)) 376 { 377 lastTimeoutUpdate = timeNow; 378 auto authTokensIt = authTokens.begin(); 379 while (authTokensIt != authTokens.end()) 380 { 381 if (timeNow - authTokensIt->second->lastUpdated >= 382 timeoutInMinutes) 383 { 384 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 385 crow::ibm_mc_lock::Lock::getInstance().releaseLock( 386 authTokensIt->second->uniqueId); 387 #endif 388 authTokensIt = authTokens.erase(authTokensIt); 389 390 needWrite = true; 391 } 392 else 393 { 394 authTokensIt++; 395 } 396 } 397 } 398 } 399 400 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate; 401 std::unordered_map<std::string, std::shared_ptr<UserSession>, 402 std::hash<std::string>, 403 crow::utility::ConstantTimeCompare> 404 authTokens; 405 bool needWrite{false}; 406 std::chrono::minutes timeoutInMinutes; 407 AuthConfigMethods authMethodsConfig; 408 }; 409 410 } // namespace persistent_data 411 } // namespace crow 412 413 // to_json(...) definition for objects of UserSession type 414 namespace nlohmann 415 { 416 template <> 417 struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>> 418 { 419 static void 420 to_json(nlohmann::json& j, 421 const std::shared_ptr<crow::persistent_data::UserSession>& p) 422 { 423 if (p->persistence != 424 crow::persistent_data::PersistenceType::SINGLE_REQUEST) 425 { 426 j = nlohmann::json{{"unique_id", p->uniqueId}, 427 {"session_token", p->sessionToken}, 428 {"username", p->username}, 429 {"csrf_token", p->csrfToken}}; 430 } 431 } 432 }; 433 434 template <> 435 struct adl_serializer<crow::persistent_data::AuthConfigMethods> 436 { 437 static void to_json(nlohmann::json& j, 438 const crow::persistent_data::AuthConfigMethods& c) 439 { 440 j = nlohmann::json{{"XToken", c.xtoken}, 441 {"Cookie", c.cookie}, 442 {"SessionToken", c.sessionToken}, 443 {"BasicAuth", c.basic}, 444 {"TLS", c.tls}}; 445 } 446 }; 447 } // namespace nlohmann 448