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