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