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