otsdaq  3.07.00
WebUsers.cc
1 #include "otsdaq/WebUsersUtilities/WebUsers.h"
2 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
3 
4 #include <openssl/sha.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <cassert>
8 #include <cstdio>
9 #include <cstdlib>
10 #include <iostream>
11 
12 #include <chrono> // std::chrono::seconds
13 #include <thread> // std::this_thread::sleep_for
14 
15 using namespace ots;
16 
17 // clang-format off
18 #define WEB_LOGIN_BKUP_DB_PATH "bkup/"
19 
20 #define SECURITY_FILE_NAME std::string(__ENV__("SERVICE_DATA_PATH")) + "/OtsWizardData/security.dat"
21 
22 #define USERS_ACTIVE_SESSIONS_FILE USERS_DB_PATH + "/activeSessions.sv"
23 
24 #define HASHES_DB_FILE HASHES_DB_PATH + "/hashes.xml"
25 #define USERS_DB_FILE USERS_DB_PATH + "/users.xml"
26 #define USERS_GLOBAL_HISTORY_FILE "__global"
27 #define USERS_LOGIN_HISTORY_FILETYPE "hist"
28 #define USERS_PREFERENCES_FILETYPE "pref"
29 #define SYSTEM_PREFERENCES_PREFIX "system.preset"
30 #define USER_WITH_LOCK_FILE WEB_LOGIN_DB_PATH + "/user_with_lock.dat"
31 #define IP_BLACKLIST_FILE WEB_LOGIN_DB_PATH + "/ip_generated_blacklist.dat"
32 #define IP_REJECT_FILE WEB_LOGIN_DB_PATH + "/ip_reject.dat"
33 #define IP_ACCEPT_FILE WEB_LOGIN_DB_PATH + "/ip_accept.dat"
34 #define USERS_LOGIN_FAILURE_FILE USERS_DB_PATH + "/loginFailureCounts.dat"
35 
36 #define SILENCE_ALL_TOOLTIPS_FILENAME "silenceTooltips"
37 
38 #define HASHES_DB_GLOBAL_STRING "hashData"
39 #define HASHES_DB_ENTRY_STRING "hashEntry"
40 #define USERS_DB_GLOBAL_STRING "userData"
41 #define USERS_DB_ENTRY_STRING "userEntry"
42 #define USERS_DB_NEXT_UID_STRING "nextUserId"
43 
45 #define PREF_XML_BGCOLOR_FIELD "pref_bgcolor" // -background color
46 #define PREF_XML_DBCOLOR_FIELD "pref_dbcolor" // -dashboard color
47 #define PREF_XML_WINCOLOR_FIELD "pref_wincolor" // -window color
48 #define PREF_XML_LAYOUT_FIELD "pref_layout" // -3 defaults window layouts(and current)
49 #define PREF_XML_SYSLAYOUT_FIELD "pref_syslayout" // -2 defaults window layouts
50 #define PREF_XML_ALIAS_LAYOUT_FIELD "pref_aliaslayout"
51 #define PREF_XML_SYSALIAS_LAYOUT_FIELD "pref_sysalias_layout"
52 #define PREF_XML_PERMISSIONS_FIELD "desktop_user_permissions" // 0-255 permissions value (255 is admin super user)
53 #define PREF_XML_USERLOCK_FIELD "username_with_lock" // user with lock (to lockout others)
54 #define PREF_XML_USERNAME_FIELD "pref_username" // user with lock (to lockout others)
55 #define PREF_XML_OTS_OWNER_FIELD "ots_owner" // e.g. the experiment name
56 
57 #define PREF_XML_BGCOLOR_DEFAULT "rgb(0,76,151)" // -background color
58 #define PREF_XML_DBCOLOR_DEFAULT "rgb(0,40,85)" // -dashboard color
59 #define PREF_XML_WINCOLOR_DEFAULT "rgba(196,229,255,0.9)" // -window color
60 #define PREF_XML_LAYOUT_DEFAULT "0;0;0;0" // 3 default window layouts(and current)
61 #define PREF_XML_SYSLAYOUT_DEFAULT "0;0" // 2 system default window layouts
62 
63 #define PREF_XML_ACCOUNTS_FIELD "users_accounts" // user accounts field for super users
64 #define PREF_XML_LOGIN_HISTORY_FIELD "login_entry" // login history field for user login history data
65 
66 const std::string WebUsers::OTS_OWNER = getenv("OTS_OWNER")?getenv("OTS_OWNER"):"";
67 const std::string WebUsers::DEFAULT_ADMIN_USERNAME = "admin";
68 const std::string WebUsers::DEFAULT_ADMIN_DISPLAY_NAME = "Administrator";
69 const std::string WebUsers::DEFAULT_ADMIN_EMAIL = "root@otsdaq.fnal.gov";
70 const std::string WebUsers::DEFAULT_ITERATOR_USERNAME = "iterator";
71 const std::string WebUsers::DEFAULT_STATECHANGER_USERNAME = "statechanger";
72 const std::string WebUsers::DEFAULT_USER_GROUP = "allUsers";
73 
74 const std::string WebUsers::REQ_NO_LOGIN_RESPONSE = "NoLogin";
75 const std::string WebUsers::REQ_NO_PERMISSION_RESPONSE = "NoPermission";
76 const std::string WebUsers::REQ_USER_LOCKOUT_RESPONSE = "UserLockout";
77 const std::string WebUsers::REQ_LOCK_REQUIRED_RESPONSE = "LockRequired";
78 const std::string WebUsers::REQ_ALLOW_NO_USER = "AllowNoUser";
79 
80 const std::string WebUsers::SECURITY_TYPE_NONE = "NoSecurity";
81 const std::string WebUsers::SECURITY_TYPE_DIGEST_ACCESS = "DigestAccessAuthentication";
82 const std::string WebUsers::SECURITY_TYPE_DEFAULT = WebUsers::SECURITY_TYPE_NONE; // default to NO SECURITY
83 
84 const std::vector<std::string> WebUsers::HashesDatabaseEntryFields_ = {"hash","lastAccessTime"};
85 const std::vector<std::string> WebUsers::UsersDatabaseEntryFields_ = {"username","displayName","salt",
86  "uid","permissions","lastLoginAttemptTime","accountCreatedTime",
87  "loginFailureCount","lastModifiedTime","lastModifierUsername","useremail"};
88 
89 #undef __MF_SUBJECT__
90 #define __MF_SUBJECT__ "WebUsers"
91 
92 std::atomic<bool> WebUsers::remoteLoginVerificationEnabled_ = false;
93 volatile bool WebUsers::CareAboutCookieCodes_ = true;
94 
95 // clang-format on
96 
97 WebUsers::WebUsers()
98 {
99  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
100 
101  // deleteUserData(); //leave for debugging to reset user data
102 
103  usersNextUserId_ = 0; // first UID, default to 0 but get from database
104  usersUsernameWithLock_ = ""; // init to no user with lock
105 
106  // define field labels
107  // HashesDatabaseEntryFields.push_back("hash");
108  // HashesDatabaseEntryFields.push_back("lastAccessTime"); // last login month resolution, blurred by 1/2 month
109  //
110  // WebUsers::UsersDatabaseEntryFields_.push_back("username");
111  // WebUsers::UsersDatabaseEntryFields_.push_back("displayName");
112  // WebUsers::UsersDatabaseEntryFields_.push_back("salt");
113  // WebUsers::UsersDatabaseEntryFields_.push_back("uid");
114  // WebUsers::UsersDatabaseEntryFields_.push_back("permissions");
115  // WebUsers::UsersDatabaseEntryFields_.push_back("lastLoginAttemptTime");
116  // WebUsers::UsersDatabaseEntryFields_.push_back("accountCreatedTime");
117  // WebUsers::UsersDatabaseEntryFields_.push_back("loginFailureCount");
118  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifiedTime");
119  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifierUsername");
120  // WebUsers::UsersDatabaseEntryFields_.push_back("useremail");
121 
122  // attempt to make directory structure (just in case)
123  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
124  mkdir(((std::string)WEB_LOGIN_DB_PATH + "bkup/" + USERS_DB_PATH).c_str(), 0755);
125  mkdir(((std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH).c_str(), 0755);
126  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
127  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH).c_str(), 0755);
128  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH).c_str(), 0755);
129 
130  if(!loadDatabases())
131  __COUT__ << "FATAL USER DATABASE ERROR - failed to load!!!" << __E__;
132 
133  loadSecuritySelection();
134 
135  // print out admin new user code for ease of use
136  uint64_t i;
137  std::string user = DEFAULT_ADMIN_USERNAME;
138  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
139  {
140  __SS__ << "user: " << user << " is not found. This should be impossible!"
141  << __E__;
142  __COUT_ERR__ << ss.str();
143  __SS_THROW__; // THIS CAN NOT HAPPEN?! There must be an admin user
144  }
145  else if(Users_[i].salt_ ==
146  "" && // admin password not setup, so print out NAC to help out
147  securityType_ == SECURITY_TYPE_DIGEST_ACCESS)
148  {
150  // start thread for notifying the user about the admin new account code
151  // notify for 10 seconds (e.g.)
152  std::thread(
153  [](const std::string& nac, const std::string& user) {
154  WebUsers::NACDisplayThread(nac, user);
155  },
156  Users_[i].getNewAccountCode(),
157  user)
158  .detach();
159  }
160 
161  // attempt to load persistent user sessions
163 
164  // load login failure counts from separate file (overrides any stale values in users.xml)
165  loadLoginFailureCounts();
166 
167  // default user with lock to admin and/or try to load last user with lock
168  // Note: this must happen after getting persistent active sessions
169  loadUserWithLock();
170 
171  srand(time(0)); // seed random for hash salt generation
172 
173  __COUT__ << "Done with Web Users initialization!" << __E__;
174 } // end constructor
175 
176 //==============================================================================
181 bool WebUsers::xmlRequestOnGateway(cgicc::Cgicc& cgi,
182  std::ostringstream* out,
183  HttpXmlDocument* xmldoc,
184  WebUsers::RequestUserInfo& userInfo)
185 {
186  std::lock_guard<std::mutex> lock(webUserMutex_);
187 
188  // initialize user info parameters to failed results
190 
191  uint64_t i;
192 
194  userInfo.cookieCode_,
195  &userInfo.groupPermissionLevelMap_,
196  &userInfo.uid_,
197  userInfo.ip_,
198  !userInfo.automatedCommand_ /*refresh cookie*/,
199  userInfo
200  .allowNoUser_ /* do not go to remote verify to avoid hammering remote verify */
201  ,
202  &userInfo.usernameWithLock_,
203  &userInfo.userSessionIndex_))
204  {
205  *out << userInfo.cookieCode_;
206  goto HANDLE_ACCESS_FAILURE; // return false, access failed
207  }
208 
209  // setup userInfo.permissionLevel_ based on userInfo.groupPermissionLevelMap_
210  userInfo.getGroupPermissionLevel();
211 
212  i = searchUsersDatabaseForUserId(userInfo.uid_);
213  if(i >= Users_.size())
214  {
215  __SS__ << "Illegal uid encountered in cookie codes!? " << i << __E__;
216  ss << "User size = " << Users_.size() << __E__;
217  __SS_THROW__;
218  }
219 
220  userInfo.username_ = Users_[i].username_;
221  userInfo.displayName_ = Users_[i].displayName_;
222 
223  // If request requires lock and no user currently has lock, auto-take lock
224  if(userInfo.requireLock_ && userInfo.usernameWithLock_ == "")
225  {
226  __COUT_INFO__ << "Auto-taking lock for user '" << userInfo.username_
227  << "' because no user has the lock and lock is required." << __E__;
228  if(setUserWithLock(userInfo.uid_, true /*lock*/, userInfo.username_))
229  userInfo.usernameWithLock_ = userInfo.username_;
230  }
231 
232  if(!WebUsers::checkRequestAccess(cgi, out, xmldoc, userInfo))
233  goto HANDLE_ACCESS_FAILURE; // return false, access failed
234 
235  return true; // access success!
236 
237 HANDLE_ACCESS_FAILURE:
238  // print out return string on failure
239  if(!userInfo.automatedCommand_)
240  __COUT_ERR__ << "Failed request (requestType = " << userInfo.requestType_
241  << "): " << out->str() << __E__;
242  return false; // access failed
243 
244 } // end xmlRequestOnGateway()
245 
246 //==============================================================================
249 void WebUsers::initializeRequestUserInfo(cgicc::Cgicc& cgi,
250  WebUsers::RequestUserInfo& userInfo)
251 {
252  userInfo.ip_ = cgi.getEnvironment().getRemoteAddr();
253 
254  // note if related bools are false, members below may not be set
255  userInfo.username_ = "";
256  userInfo.displayName_ = "";
257  userInfo.usernameWithLock_ = "";
258  userInfo.userSessionIndex_ = NOT_FOUND_IN_DATABASE;
259  userInfo.setGroupPermissionLevels(""); // always init to inactive
260 } //end initializeRequestUserInfo()
261 
262 //==============================================================================
270 bool WebUsers::checkRequestAccess(cgicc::Cgicc& /*cgi*/,
271  std::ostringstream* out,
272  HttpXmlDocument* xmldoc,
273  WebUsers::RequestUserInfo& userInfo,
274  bool isWizardMode /* = false */,
275  const std::string& wizardModeSequence /* = "" */)
276 {
277  // steps:
278  // - check access based on cookieCode and permission level
279  // - check user lock flags and status
280 
281  if(userInfo.requireSecurity_ && userInfo.permissionsThreshold_ > 1)
282  {
283  // In an attempt to force accountability,..
284  // only allow higher permission threshold requests
285  // if wiz mode with random code, or normal mode with security mode enabled
286 
287  if(isWizardMode && wizardModeSequence.size() < 8)
288  {
289  // force wiz mode sequence to be "random and large"
290  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
291  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '"
292  << userInfo.requestType_
293  << "' which requires sufficient security enabled. Please enable the "
294  "random wizard mode"
295  " sequence of at least 8 characters."
296  << __E__;
297  return false; // invalid cookie and present sequence, but not correct
298  // sequence
299  }
300  else if(!isWizardMode &&
301  (userInfo.username_ == WebUsers::DEFAULT_ADMIN_USERNAME ||
302  userInfo.username_ == WebUsers::DEFAULT_ITERATOR_USERNAME ||
303  userInfo.username_ == WebUsers::DEFAULT_STATECHANGER_USERNAME))
304  {
305  // force non-admin user, which implies sufficient security
306  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
307  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '"
308  << userInfo.requestType_
309  << "' which requires sufficient security enabled. Please enable "
310  "individual user "
311  " logins (Note: the user admin is disallowed in an attempt to "
312  "force personal accountability for edits)."
313  << __E__;
314  return false; // invalid cookie and present sequence, but not correct
315  // sequence
316  }
317 
318  } // end security required verification
319 
320  if(!userInfo.automatedCommand_)
321  {
322  __COUTT__ << "requestType ==========>>> " << userInfo.requestType_ << __E__;
323  __COUTTV__((unsigned int)userInfo.permissionLevel_);
324  __COUTTV__((unsigned int)userInfo.permissionsThreshold_);
325  }
326 
327  // second, start check access -------
328  if(!isWizardMode && !userInfo.allowNoUser_ &&
329  userInfo.cookieCode_.length() != WebUsers::COOKIE_CODE_LENGTH &&
330  !(!WebUsers::CareAboutCookieCodes_ && WebUsers::remoteLoginVerificationEnabled_ &&
331  userInfo.cookieCode_ ==
332  WebUsers::
333  REQ_ALLOW_NO_USER)) //ignore case when security disabled at remote subsystem (avoid propagating bad cookieCode to primary Gateway)
334  {
335  __COUT__ << "User (@" << userInfo.ip_
336  << ") has invalid cookie code: " << userInfo.cookieCode_ << std::endl;
337  *out << WebUsers::REQ_NO_LOGIN_RESPONSE;
338  return false; // invalid cookie and sequence present, but not correct sequence
339  }
340 
341  if(!userInfo.allowNoUser_ &&
342  (userInfo.permissionLevel_ == 0 || // reject inactive user permission level
343  userInfo.permissionsThreshold_ == 0 || // reject inactive requests
344  userInfo.permissionLevel_ < userInfo.permissionsThreshold_))
345 
346  {
347  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
348  __COUT_INFO__ << "User (@" << userInfo.ip_
349  << ") has insufficient permissions for requestType '"
350  << userInfo.requestType_ << "' : user level is "
351  << (unsigned int)userInfo.permissionLevel_ << ", "
352  << (unsigned int)userInfo.permissionsThreshold_ << " required."
353  << __E__;
354  return false; // invalid permissions
355  }
356  // end check access -------
357 
358  if(isWizardMode)
359  {
360  userInfo.username_ = WebUsers::DEFAULT_ADMIN_USERNAME;
361  userInfo.displayName_ = "Admin";
362  userInfo.usernameWithLock_ = userInfo.username_;
363  userInfo.userSessionIndex_ = 0;
364  return true; // done, wizard mode access granted
365  }
366  // else, normal gateway verify mode
367 
368  if(xmldoc) // fill with cookie code tag
369  {
370  if(userInfo.allowNoUser_)
371  xmldoc->setHeader(WebUsers::REQ_ALLOW_NO_USER);
372  else
373  xmldoc->setHeader(userInfo.cookieCode_);
374  }
375 
376  if(userInfo.allowNoUser_)
377  {
378  if(userInfo.automatedCommand_)
379  __COUTT__ << "Allowing anonymous access." << __E__;
380 
381  return true; // ignore lock for allow-no-user case
382  }
383 
384  // if(!userInfo.automatedCommand_)
385  // {
386  // __COUTV__(userInfo.username_);
387  // __COUTV__(userInfo.usernameWithLock_);
388  // }
389 
390  if((userInfo.checkLock_ || userInfo.requireLock_) &&
391  userInfo.usernameWithLock_ != "" &&
392  userInfo.usernameWithLock_ != userInfo.username_)
393  {
394  *out << WebUsers::REQ_USER_LOCKOUT_RESPONSE;
395  __COUT_INFO__ << "User '" << userInfo.username_ << "' is locked out. '"
396  << userInfo.usernameWithLock_ << "' has lock." << std::endl;
397  return false; // failed due to another user having lock
398  }
399 
400  if(userInfo.requireLock_ && userInfo.usernameWithLock_ != userInfo.username_)
401  {
402  *out << WebUsers::REQ_LOCK_REQUIRED_RESPONSE;
403  __COUT_INFO__ << "User '" << userInfo.username_
404  << "' must have lock to proceed. ('" << userInfo.usernameWithLock_
405  << "' has lock.)" << std::endl;
406  return false; // failed due to lock being required, and this user does not have it
407  }
408 
409  return true; // access success!
410 
411 } // end checkRequestAccess()
412 
413 //==============================================================================
417 {
418  std::string fn;
419 
420  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
421  __COUT__ << fn << __E__;
422 
423  FILE* fp = fopen(fn.c_str(), "w");
424  if(!fp)
425  {
426  __COUT_ERR__ << "Error! Persistent active sessions could not be saved to file: "
427  << fn << __E__;
428  return;
429  }
430 
431  int version = 0;
432  fprintf(fp, "%d\n", version);
433  for(unsigned int i = 0; i < ActiveSessions_.size(); ++i)
434  {
435  // __COUT__ << "SAVE " << ActiveSessionCookieCodeVector[i] << __E__;
436  // __COUT__ << "SAVE " << ActiveSessionIpVector[i] << __E__;
437  // __COUT__ << "SAVE " << ActiveSessionUserIdVector[i] << __E__;
438  // __COUT__ << "SAVE " << ActiveSessionIndex[i] << __E__;
439  // __COUT__ << "SAVE " << ActiveSessionStartTimeVector[i] << __E__;
440 
441  fprintf(fp, "%s\n", ActiveSessions_[i].cookieCode_.c_str());
442  fprintf(fp, "%s\n", ActiveSessions_[i].ip_.c_str());
443  fprintf(fp, "%lu\n", ActiveSessions_[i].userId_);
444  fprintf(fp, "%lu\n", ActiveSessions_[i].sessionIndex_);
445  fprintf(fp, "%ld\n", ActiveSessions_[i].startTime_);
446  }
447 
448  __COUT__ << "Active Sessions saved with size " << ActiveSessions_.size() << __E__;
449 
450  fclose(fp);
451 } // end saveActiveSessions()
452 
453 //====================================================================================================================
457 {
458  std::string fn;
459 
460  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
461  __COUT__ << fn << __E__;
462  FILE* fp = fopen(fn.c_str(), "r");
463  if(!fp)
464  {
465  __COUT_INFO__
466  << "Persistent active sessions were not found to be loaded at file: " << fn
467  << __E__;
468  return;
469  }
470 
471  int version;
472 
473  const int LINELEN = 1000;
474  char line[LINELEN];
475  fgets(line, LINELEN, fp);
476  sscanf(line, "%d", &version);
477  if(version == 0)
478  {
479  __COUT__ << "Extracting active sessions..." << __E__;
480  }
481  while(fgets(line, LINELEN, fp))
482  {
483  if(strlen(line))
484  line[strlen(line) - 1] = '\0'; // remove new line
485  if(strlen(line) != COOKIE_CODE_LENGTH)
486  {
487  __COUT__ << "Illegal cookie code found: " << line << __E__;
488 
489  fclose(fp);
490  return;
491  }
492  ActiveSessions_.push_back(ActiveSession());
493  ActiveSessions_.back().cookieCode_ = line;
494 
495  fgets(line, LINELEN, fp);
496  if(strlen(line))
497  line[strlen(line) - 1] = '\0'; // remove new line
498  ActiveSessions_.back().ip_ = line;
499 
500  fgets(line, LINELEN, fp);
501  sscanf(line, "%lu", &(ActiveSessions_.back().userId_));
502 
503  fgets(line, LINELEN, fp);
504  sscanf(line, "%lu", &(ActiveSessions_.back().sessionIndex_));
505 
506  fgets(line, LINELEN, fp);
507  sscanf(line, "%ld", &(ActiveSessions_.back().startTime_));
508  }
509 
510  __COUT__ << "Active Sessions loaded with size " << ActiveSessions_.size() << __E__;
511 
512  fclose(fp);
513  // clear file after loading
514  fp = fopen(fn.c_str(), "w");
515  if(fp)
516  fclose(fp);
517 } // end loadActiveSessions()
518 
519 //==============================================================================
523 bool WebUsers::loadDatabases()
524 {
525  std::string fn;
526 
527  FILE* fp;
528  const unsigned int LINE_LEN = 1000;
529  char line[LINE_LEN];
530  unsigned int i, si, c, len, f;
531  // uint64_t tmpInt64;
532 
533  // hashes
534  // File Organization:
535  // <hashData>
536  // <hashEntry><hash>hash0</hash><lastAccessTime>lastAccessTime0</lastAccessTime></hashEntry>
537  // <hashEntry><hash>hash1</hash><lastAccessTime>lastAccessTime1</lastAccessTime></hashEntry>
538  // ..
539  // </hashData>
540 
541  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_FILE;
542  __COUT__ << fn << __E__;
543  fp = fopen(fn.c_str(), "r");
544  if(!fp) // need to create file
545  {
546  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str(),
547  0755);
548  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str()
549  << __E__;
550  fp = fopen(fn.c_str(), "w");
551  if(!fp)
552  return false;
553  __COUT__ << "Hashes database created: " << fn << __E__;
554 
555  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
556  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
557  fclose(fp);
558  }
559  else // load structures if hashes exists
560  {
561  // for every HASHES_DB_ENTRY_STRING, extract to local vector
562  // trusting file construction, assuming fields based >'s and <'s
563  while(fgets(line, LINE_LEN, fp))
564  {
565  if(strlen(line) < SHA512_DIGEST_LENGTH)
566  continue;
567 
568  c = 0;
569  len =
570  strlen(line); // save len, strlen will change because of \0 manipulations
571  for(i = 0; i < len; ++i)
572  if(line[i] == '>')
573  {
574  ++c; // count >'s
575  if(c != 2 && c != 4)
576  continue; // only proceed for field data
577 
578  si = ++i; // save start index
579  while(i < len && line[i] != '<')
580  ++i;
581  if(i == len)
582  break;
583  line[i] = '\0'; // close std::string
584 
585  //__COUT__ << "Found Hashes field " << c/2 << " " << &line[si] <<
586  //__E__;
587 
588  f = c / 2 - 1;
589  if(f == 0) // hash
590  {
591  Hashes_.push_back(Hash());
592  Hashes_.back().hash_ = &line[si];
593  }
594  else if(f == 1) // lastAccessTime
595  sscanf(&line[si], "%ld", &Hashes_.back().accessTime_);
596  }
597  }
598  __COUT__ << Hashes_.size() << " Hashes found." << __E__;
599 
600  fclose(fp);
601  }
602 
603  // users
604  // File Organization:
605  // <userData>
606  // <nextUserId>...</nextUserId>
607  // <userEntry>...</userEntry>
608  // <userEntry>...</userEntry>
609  // ..
610  // </userData>
611 
612  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_FILE;
613  fp = fopen(fn.c_str(), "r");
614  if(!fp) // need to create file
615  {
616  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str(),
617  0755);
618  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str()
619  << __E__;
620  fp = fopen(fn.c_str(), "w");
621  if(!fp)
622  return false;
623  __COUT__ << "Users database created: " << fn << __E__;
624 
625  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
626  char nidStr[100];
627  sprintf(nidStr, "%lu", usersNextUserId_);
628  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, nidStr, DB_SAVE_OPEN_AND_CLOSE);
629  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
630  fclose(fp);
631 
632  createNewAccount(DEFAULT_ADMIN_USERNAME,
633  DEFAULT_ADMIN_DISPLAY_NAME,
634  DEFAULT_ADMIN_EMAIL); // account 0 is always admin
635  }
636  else // extract next user id and user entries if users exists
637  {
638  __COUT__ << "Users database: " << fn << __E__;
639  // for every USERS_DB_ENTRY_STRING, extract to local vector
640  // trusting file construction, assuming fields based >'s and <'s
641 
642  char salt[] = "nextUserId";
643  while(fgets(line, LINE_LEN, fp))
644  {
645  if(strlen(line) < strlen(salt) * 2)
646  continue; // line size should indicate xml tags on same line
647 
648  for(i = 0; i < strlen(salt); ++i) // check for opening tag
649  if(line[i + 1] != salt[i])
650  break;
651 
652  if(i == strlen(salt)) // all salt matched, so found correct line! increment
653  // to get line index
654  {
655  i += 2;
656  si = i;
657  while(i < LINE_LEN && line[i] != '\0' && line[i] != '<')
658  ++i; // find '<'
659  line[i] = '\0'; // close std::string
660  sscanf(&line[si], "%lu", &usersNextUserId_);
661  break; // done with next uid
662  }
663  }
664 
665  __COUT__ << "Found Users database next user Id: " << usersNextUserId_ << __E__;
666 
667  // trusting file construction, assuming fields based >'s and <'s and each entry on
668  // one line
669  while(fgets(line, LINE_LEN, fp))
670  {
671  if(strlen(line) < 30)
672  continue; // rule out header tags
673 
674  c = 0;
675  len =
676  strlen(line); // save len, strlen will change because of \0 manipulations
677  if(len >= LINE_LEN)
678  {
679  __COUT__ << "Line buffer too small: " << len << __E__;
680  break;
681  }
682 
683  // get fields from line
684  f = 0;
685  for(i = 0; i < len; ++i)
686  if(line[i] == '>')
687  {
688  ++c; // count >'s
689  if(c == 0 || c % 2 == 1)
690  continue; // only proceed for field data (even
691 
692  si = ++i; // save start index
693  while(i < len && line[i] != '<')
694  ++i;
695  if(i == len)
696  break;
697  line[i] = '\0'; // close std::string
698  f = c / 2 - 1;
699 
700  //__COUT__ << "Found Users[" <<
701  // Users_.size() << "] field " << f << " " << &line[si] << __E__;
702 
703  if(f == 0) // username
704  {
705  Users_.push_back(User());
706  Users_.back().username_ = &line[si];
707  }
708  else if(f == 1) // displayName
709  Users_.back().displayName_ = &line[si];
710  else if(f == 2) // salt
711  Users_.back().salt_ = &line[si];
712  else if(f == 3) // uid
713  sscanf(&line[si], "%lu", &Users_.back().userId_);
714  else if(f == 4) // permissions
715  {
716  std::map<std::string, permissionLevel_t>& lastPermissionsMap =
717  Users_.back().permissions_;
718  StringMacros::getMapFromString<permissionLevel_t>(
719  &line[si], lastPermissionsMap);
720 
721  //__COUT__ << "User permission levels:" <<
722  // StringMacros::mapToString(lastPermissionsMap) << __E__;
723 
724  // verify 'allUsers' is there
725  // if not, add it as a disabled user (i.e.
726  // WebUsers::PERMISSION_LEVEL_INACTIVE)
727  if(lastPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) ==
728  lastPermissionsMap.end())
729  {
730  __COUT_INFO__
731  << "User '" << Users_.back().username_
732  << "' is not a member of the default user group '"
733  << WebUsers::DEFAULT_USER_GROUP
734  << ".' Assuming user account is inactive (permission "
735  "level := "
736  << WebUsers::PERMISSION_LEVEL_INACTIVE << ")." << __E__;
737  lastPermissionsMap[WebUsers::DEFAULT_USER_GROUP] =
738  WebUsers::PERMISSION_LEVEL_INACTIVE; // mark inactive
739  }
740 
741  if(Users_.back().username_ == DEFAULT_ADMIN_USERNAME)
742  {
743  // overwrite admin with full permissions (irregardless of corrupt user db situation), never allow to be inactive for example
744 
745  std::map<std::string /*groupName*/,
746  WebUsers::permissionLevel_t>
747  initPermissions = {{WebUsers::DEFAULT_USER_GROUP,
749 
750  Users_.back().permissions_ = initPermissions;
751  }
752  }
753  else if(f == 5) // lastLoginAttemptTime
754  sscanf(&line[si], "%ld", &Users_.back().lastLoginAttempt_);
755  else if(f == 6) // accountCreatedTime
756  sscanf(&line[si], "%ld", &Users_.back().accountCreationTime_);
757  else if(f == 7) // loginFailureCount
758  sscanf(&line[si], "%hhu", &Users_.back().loginFailureCount_);
759  else if(f == 8) // lastModifierTime
760  sscanf(&line[si], "%ld", &Users_.back().accessModifierTime());
761  else if(f == 9) // lastModifierUsername
762  Users_.back().loadModifierUsername(&line[si]);
763  else if(f == 10) // user email
764  Users_.back().email_ = &line[si];
765  }
766 
767  } // end get line loop
768  fclose(fp);
769  }
770 
771  __COUT__ << Users_.size() << " Users found." << __E__;
772  for(size_t ii = 0; ii < Users_.size(); ++ii)
773  {
774  std::cout << // do not send to message facility
775  "User [" << Users_[ii].userId_ << "] \tName: " << std::left
776  << std::setfill(' ') << std::setw(20) << Users_[ii].username_
777  << "\tDisplay Name: " << std::left << std::setfill(' ') << std::setw(30)
778  << Users_[ii].displayName_ << "\tEmail: " << std::left
779  << std::setfill(' ') << std::setw(30) << Users_[ii].email_
780  << "\tNAC: " << std::left << std::setfill(' ') << std::setw(5)
781  << Users_[ii].getNewAccountCode()
782  << "\tFailedCount: " << (int)Users_[ii].loginFailureCount_
783  << "\tPermissions: "
784  << StringMacros::mapToString(Users_[ii].permissions_) <<
785  //"\tSalt: " << Users_[ii].salt_.size() << " " << Users_[ii].salt_ <<
786  __E__;
787  }
788  // __COUT__ << Hashes_.size() << " Hashes found." << __E__;
789  // for(size_t ii = 0; ii < Hashes_.size(); ++ii)
790  // {
791  // std::cout << //do not send to message facility
792  // "Hash [" << ii <<
793  // "]: " << Hashes_[ii].hash_ <<
794  // __E__;
795  // }
796  return true;
797 } // end loadDatabases()
798 
799 //==============================================================================
803 void WebUsers::saveLoginFailureCounts()
804 {
805  std::string fn =
806  (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_LOGIN_FAILURE_FILE;
807 
808  FILE* fp = fopen(fn.c_str(), "w");
809  if(!fp)
810  {
811  __COUT_ERR__ << "Failed to open login failure counts file for writing: " << fn
812  << __E__;
813  return;
814  }
815  for(uint64_t i = 0; i < Users_.size(); ++i)
816  {
817  if(Users_[i].loginFailureCount_ > 0)
818  fprintf(fp, "%lu %hhu\n", Users_[i].userId_, Users_[i].loginFailureCount_);
819  }
820  fclose(fp);
821 } // end saveLoginFailureCounts()
822 
823 //==============================================================================
827 void WebUsers::loadLoginFailureCounts()
828 {
829  std::string fn =
830  (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_LOGIN_FAILURE_FILE;
831 
832  FILE* fp = fopen(fn.c_str(), "r");
833  if(!fp)
834  {
835  __COUT__ << "No login failure counts file found (this is normal on first run): "
836  << fn << __E__;
837  return;
838  }
839 
840  // first, zero out all failure counts (file is authoritative)
841  for(auto& user : Users_)
842  user.loginFailureCount_ = 0;
843 
844  uint64_t uid;
845  unsigned int count;
846  while(fscanf(fp, "%lu %u", &uid, &count) == 2)
847  {
848  for(auto& user : Users_)
849  {
850  if(user.userId_ == uid)
851  {
852  user.loginFailureCount_ = (unsigned char)count;
853  break;
854  }
855  }
856  }
857  fclose(fp);
858  __COUT__ << "Loaded login failure counts from " << fn << __E__;
859 } // end loadLoginFailureCounts()
860 
861 //==============================================================================
863 void WebUsers::saveToDatabase(FILE* fp,
864  const std::string& field,
865  const std::string& value,
866  uint8_t type,
867  bool addNewLine)
868 {
869  if(!fp)
870  return;
871 
872  std::string newLine = addNewLine ? "\n" : "";
873 
874  if(type == DB_SAVE_OPEN_AND_CLOSE)
875  fprintf(fp,
876  "<%s>%s</%s>%s",
877  field.c_str(),
878  value.c_str(),
879  field.c_str(),
880  newLine.c_str());
881  else if(type == DB_SAVE_OPEN)
882  fprintf(fp, "<%s>%s%s", field.c_str(), value.c_str(), newLine.c_str());
883  else if(type == DB_SAVE_CLOSE)
884  fprintf(fp, "</%s>%s", field.c_str(), newLine.c_str());
885 } // end saveToDatabase()
886 
887 //==============================================================================
893 bool WebUsers::saveDatabaseToFile(uint8_t db)
894 {
895  //__COUT__ << "Save Database: " << (int)db << __E__;
896 
897  std::string fn =
898  (std::string)WEB_LOGIN_DB_PATH +
899  ((db == DB_USERS) ? (std::string)USERS_DB_FILE : (std::string)HASHES_DB_FILE);
900 
901  __COUT__ << "Save Database Filename: " << fn << __E__;
902 
903  // backup file organized by day
904  if(0)
905  {
906  char dayAppend[20];
907  sprintf(dayAppend, ".%lu.bkup", time(0) / (3600 * 24));
908  std::string bkup_fn = (std::string)WEB_LOGIN_DB_PATH +
909  (std::string)WEB_LOGIN_BKUP_DB_PATH +
910  ((db == DB_USERS) ? (std::string)USERS_DB_FILE
911  : (std::string)HASHES_DB_FILE) +
912  (std::string)dayAppend;
913 
914  __COUT__ << "Backup file: " << bkup_fn << __E__;
915 
916  std::string shell_command = "mv " + fn + " " + bkup_fn;
917  system(shell_command.c_str());
918  }
919 
920  FILE* fp = fopen(fn.c_str(), "wb"); // write in binary mode
921  if(!fp)
922  return false;
923 
924  char fldStr[100];
925 
926  if(db == DB_USERS) // USERS
927  {
928  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
929 
930  sprintf(fldStr, "%lu", usersNextUserId_);
931  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, fldStr, DB_SAVE_OPEN_AND_CLOSE);
932 
933  __COUT__ << "Saving " << Users_.size() << " Users." << __E__;
934 
935  for(uint64_t i = 0; i < Users_.size(); ++i)
936  {
937  //__COUT__ << "Saving User: " << UsersUsernameVector[i] << __E__;
938 
939  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
940 
941  for(unsigned int f = 0; f < WebUsers::UsersDatabaseEntryFields_.size(); ++f)
942  {
943  //__COUT__ << "Saving Field: " << f << __E__;
944  if(f == 0) // username
945  saveToDatabase(fp,
946  WebUsers::UsersDatabaseEntryFields_[f],
947  Users_[i].username_,
948  DB_SAVE_OPEN_AND_CLOSE,
949  false);
950  else if(f == 1) // displayName
951  saveToDatabase(fp,
952  WebUsers::UsersDatabaseEntryFields_[f],
953  Users_[i].displayName_,
954  DB_SAVE_OPEN_AND_CLOSE,
955  false);
956  else if(f == 2) // salt
957  saveToDatabase(fp,
958  WebUsers::UsersDatabaseEntryFields_[f],
959  Users_[i].salt_,
960  DB_SAVE_OPEN_AND_CLOSE,
961  false);
962  else if(f == 3) // uid
963  {
964  sprintf(fldStr, "%lu", Users_[i].userId_);
965  saveToDatabase(fp,
966  WebUsers::UsersDatabaseEntryFields_[f],
967  fldStr,
968  DB_SAVE_OPEN_AND_CLOSE,
969  false);
970  }
971  else if(f == 4) // permissions
972  saveToDatabase(fp,
973  WebUsers::UsersDatabaseEntryFields_[f],
974  StringMacros::mapToString(Users_[i].permissions_,
975  "," /*primary delimeter*/,
976  ":" /*secondary delimeter*/),
977  DB_SAVE_OPEN_AND_CLOSE,
978  false);
979  else if(f == 5) // lastLoginAttemptTime
980  {
981  sprintf(fldStr, "%lu", Users_[i].lastLoginAttempt_);
982  saveToDatabase(fp,
983  WebUsers::UsersDatabaseEntryFields_[f],
984  fldStr,
985  DB_SAVE_OPEN_AND_CLOSE,
986  false);
987  }
988  else if(f == 6) // accountCreatedTime
989  {
990  sprintf(fldStr, "%lu", Users_[i].accountCreationTime_);
991  saveToDatabase(fp,
992  WebUsers::UsersDatabaseEntryFields_[f],
993  fldStr,
994  DB_SAVE_OPEN_AND_CLOSE,
995  false);
996  }
997  else if(f == 7) // loginFailureCount
998  {
999  sprintf(fldStr, "%hhu", Users_[i].loginFailureCount_);
1000  saveToDatabase(fp,
1001  WebUsers::UsersDatabaseEntryFields_[f],
1002  fldStr,
1003  DB_SAVE_OPEN_AND_CLOSE,
1004  false);
1005  }
1006  else if(f == 8) // lastModifierTime
1007  {
1008  sprintf(fldStr, "%lu", Users_[i].getModifierTime());
1009  saveToDatabase(fp,
1010  WebUsers::UsersDatabaseEntryFields_[f],
1011  fldStr,
1012  DB_SAVE_OPEN_AND_CLOSE,
1013  false);
1014  }
1015  else if(f == 9) // lastModifierUsername
1016  saveToDatabase(fp,
1017  WebUsers::UsersDatabaseEntryFields_[f],
1018  Users_[i].getModifierUsername(),
1019  DB_SAVE_OPEN_AND_CLOSE,
1020  false);
1021  else if(f == 10) // useremail
1022  saveToDatabase(fp,
1023  WebUsers::UsersDatabaseEntryFields_[f],
1024  Users_[i].email_,
1025  DB_SAVE_OPEN_AND_CLOSE,
1026  false);
1027  }
1028 
1029  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
1030  }
1031 
1032  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
1033  }
1034  else // HASHES
1035  {
1036  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
1037 
1038  __COUT__ << "Saving " << Hashes_.size() << " Hashes." << __E__;
1039  for(uint64_t i = 0; i < Hashes_.size(); ++i)
1040  {
1041  __COUT__ << "Saving " << Hashes_[i].hash_ << " Hash." << __E__;
1042  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
1043  for(unsigned int f = 0; f < WebUsers::HashesDatabaseEntryFields_.size(); ++f)
1044  {
1045  if(f == 0) // hash
1046  saveToDatabase(fp,
1047  WebUsers::HashesDatabaseEntryFields_[f],
1048  Hashes_[i].hash_,
1049  DB_SAVE_OPEN_AND_CLOSE,
1050  false);
1051  else if(f == 1) // lastAccessTime
1052  {
1053  sprintf(fldStr, "%lu", Hashes_[i].accessTime_);
1054  saveToDatabase(fp,
1055  WebUsers::HashesDatabaseEntryFields_[f],
1056  fldStr,
1057  DB_SAVE_OPEN_AND_CLOSE,
1058  false);
1059  }
1060  }
1061  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
1062  }
1063 
1064  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
1065  }
1066 
1067  fclose(fp);
1068  return true;
1069 } // end saveDatabaseToFile()
1070 
1071 //==============================================================================
1079 void WebUsers::createNewAccount(const std::string& username,
1080  const std::string& displayName,
1081  const std::string& email)
1082 {
1083  __COUT__ << "Creating account: " << username << __E__;
1084  // check if username already exists
1085  uint64_t i;
1086  if((i = searchUsersDatabaseForUsername(username)) != NOT_FOUND_IN_DATABASE ||
1087  username == WebUsers::DEFAULT_ITERATOR_USERNAME ||
1088  username == WebUsers::DEFAULT_STATECHANGER_USERNAME) // prevent reserved usernames
1089  // from being created!
1090  {
1091  __SS__ << "Username '" << username
1092  << "' already exists! Please choose a unique username." << __E__;
1093  __SS_THROW__;
1094  }
1095 
1096  // enforce unique Display Name
1097  if((i = searchUsersDatabaseForDisplayName(displayName)) != NOT_FOUND_IN_DATABASE)
1098  // from being created!
1099  {
1100  __SS__ << "Display Name '" << displayName
1101  << "' already exists! Please choose a unique display name." << __E__;
1102  __SS_THROW__;
1103  }
1104 
1105  // create Users database entry
1106  Users_.push_back(User());
1107 
1108  Users_.back().username_ = username;
1109  Users_.back().displayName_ = displayName;
1110  Users_.back().email_ = email;
1111 
1112  // first user is admin always!
1113  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> initPermissions = {
1114  {WebUsers::DEFAULT_USER_GROUP,
1115  (Users_.size() ? WebUsers::PERMISSION_LEVEL_NOVICE
1117 
1118  Users_.back().permissions_ = initPermissions;
1119  Users_.back().userId_ = usersNextUserId_++;
1120  if(usersNextUserId_ >= ACCOUNT_ERROR_THRESHOLD) // error wrap around case
1121  {
1122  __SS__ << "usersNextUserId_ wrap around!! Too many users??? Notify Admins."
1123  << __E__;
1124  __SS_THROW__;
1125  usersNextUserId_ = 1; // for safety to avoid weird issues at -1 and 0 (if used
1126  // for error indication)
1127  }
1128 
1129  Users_.back().accountCreationTime_ = time(0);
1130 
1131  if(!saveDatabaseToFile(DB_USERS))
1132  {
1133  __SS__ << "Failed to save User DB!" << __E__;
1134  __SS_THROW__;
1135  }
1136 } // end createNewAccount()
1137 
1138 //==============================================================================
1144 bool WebUsers::deleteAccount(const std::string& username, const std::string& displayName)
1145 {
1146  uint64_t i = searchUsersDatabaseForUsername(username);
1147  if(i == NOT_FOUND_IN_DATABASE)
1148  return false;
1149  if(Users_[i].displayName_ != displayName)
1150  return false; // display name does not match
1151 
1152  // delete entry from user database vector
1153 
1154  Users_.erase(Users_.begin() + i);
1155 
1156  // save database
1157  return saveDatabaseToFile(DB_USERS);
1158 } // end deleteAccount()
1159 
1160 //==============================================================================
1161 unsigned int WebUsers::hexByteStrToInt(const char* h)
1162 {
1163  unsigned int rv;
1164  char hs[3] = {h[0], h[1], '\0'};
1165  sscanf(hs, "%X", &rv);
1166  return rv;
1167 } // end hexByteStrToInt()
1168 
1169 //==============================================================================
1170 void WebUsers::intToHexStr(unsigned char i, char* h) { sprintf(h, "%2.2X", i); }
1171 
1172 //==============================================================================
1182 uint64_t WebUsers::attemptActiveSession(const std::string& uuid,
1183  std::string& jumbledUser,
1184  const std::string& jumbledPw,
1185  std::string& newAccountCode,
1186  const std::string& ip)
1187 {
1188  //__COUTV__(ip);
1189  if(!checkIpAccess(ip))
1190  {
1191  __COUT_ERR__ << "rejected ip: " << ip << __E__;
1192  return ACCOUNT_BLACKLISTED;
1193  }
1194 
1195  cleanupExpiredEntries(); // remove expired active and login sessions
1196 
1197  if(!CareAboutCookieCodes_) // NO SECURITY
1198  {
1199  uint64_t uid = getAdminUserID();
1200  jumbledUser = getUsersDisplayName(uid);
1201  newAccountCode = genCookieCode(); // return "dummy" cookie code by reference
1202  return uid;
1203  }
1204 
1205  uint64_t i;
1206 
1207  // search login sessions for uuid
1208  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1209  {
1210  __COUT_ERR__ << "Login attempt failed. Session uuid '" << uuid
1211  << "' is not found or inactive." << __E__;
1212  newAccountCode = "1"; // to indicate uuid was not found
1213 
1214  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1215 
1216  return NOT_FOUND_IN_DATABASE;
1217  }
1218  ++LoginSessions_[i].loginAttempts_;
1219 
1220  std::string user = dejumble(jumbledUser, LoginSessions_[i].id_);
1221  __COUTV__(user);
1222  std::string pw = dejumble(jumbledPw, LoginSessions_[i].id_);
1223 
1224  // search users for username
1225  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
1226  {
1227  __COUT_ERR__ << "user: " << user << " is not found" << __E__;
1228 
1229  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1230 
1231  return NOT_FOUND_IN_DATABASE;
1232  }
1233  else
1234  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1235 
1236  Users_[i].lastLoginAttempt_ = time(0);
1237 
1238  if(isInactiveForGroup(Users_[i].permissions_))
1239  {
1240  __COUT_ERR__ << "User '" << user
1241  << "' account INACTIVE (could be due to failed logins)" << __E__;
1242  return ACCOUNT_INACTIVE;
1243  }
1244 
1245  if(Users_[i].salt_ == "") // first login
1246  {
1247  __COUT__ << "First login attempt for user: " << user << __E__;
1248 
1249  if(newAccountCode != Users_[i].getNewAccountCode())
1250  {
1251  __COUT__ << "New account code did not match: "
1252  << Users_[i].getNewAccountCode() << " != " << newAccountCode
1253  << __E__;
1254  // Note: lastLoginAttempt_ changed but is not critical to persist here
1255  //modified 03-Mar-2026, do not need to save login attempts/fails, let them be tracked in memory, snapshots of the database will be saved periodically, and on shutdown
1256  // saveDatabaseToFile(DB_USERS); // users db modified, so save
1257  return NOT_FOUND_IN_DATABASE;
1258  }
1259 
1260  // initial user account setup
1261 
1262  // add until no collision (should 'never' be a collision)
1263  while(!addToHashesDatabase(
1264  sha512(user, pw, Users_[i].salt_))) // sha256 modifies UsersSaltVector[i]
1265  {
1266  // this should never happen, it would mean the user+pw+saltcontext was the
1267  // same
1268  // but if it were to happen, try again...
1269  Users_[i].salt_ = "";
1270  }
1271 
1272  __COUT__ << "\tHash added: " << Hashes_.back().hash_ << __E__;
1273  }
1274  else
1275  {
1276  std::string salt = Users_[i].salt_; // don't want to modify saved salt
1277  //__COUT__ << salt.size() << " " << salt << " " << i << __E__;
1278  if(searchHashesDatabaseForHash(sha512(user, pw, salt)) == NOT_FOUND_IN_DATABASE)
1279  {
1280  __COUT__ << "Failed login for " << user << " with permissions "
1281  << StringMacros::mapToString(Users_[i].permissions_) << __E__;
1282 
1283  // do not allow wrap around
1284  if(++Users_[i].loginFailureCount_ != (unsigned char)-1)
1285  ++Users_[i].loginFailureCount_;
1286 
1287  if(Users_[i].loginFailureCount_ >= USERS_MAX_LOGIN_FAILURES)
1288  Users_[i].permissions_[WebUsers::DEFAULT_USER_GROUP] =
1289  WebUsers::PERMISSION_LEVEL_INACTIVE; // Lock account
1290 
1291  __COUT_INFO__ << "User/pw for user '" << user
1292  << "' was not correct (Failed Attempt #"
1293  << (int)Users_[i].loginFailureCount_ << " of "
1294  << (int)USERS_MAX_LOGIN_FAILURES << " allowed)." << __E__;
1295 
1296  __COUTV__(isInactiveForGroup(Users_[i].permissions_));
1297  if(isInactiveForGroup(Users_[i].permissions_))
1298  {
1299  __COUT_INFO__ << "Account '" << user
1300  << "' has been marked inactive due to too many failed "
1301  "login attempts (Failed Attempt #"
1302  << (int)Users_[i].loginFailureCount_
1303  << ")! Note only admins can reactivate accounts." << __E__;
1304  // Account lockout is a security-relevant state change that must
1305  // persist across restarts, so save immediately
1306  saveDatabaseToFile(DB_USERS);
1307  }
1308  // else: do not save on every failed attempt; failure counts are
1309  // tracked in memory and will be included in periodic/shutdown saves
1310  return NOT_FOUND_IN_DATABASE;
1311  }
1312  }
1313 
1314  __COUT_INFO__ << "Login successful for: " << user << __E__;
1315 
1316  // Only persist failure count reset if it was previously non-zero
1317  if(Users_[i].loginFailureCount_ != 0)
1318  {
1319  Users_[i].loginFailureCount_ = 0;
1320  saveLoginFailureCounts(); // persist reset to separate file
1321  }
1322 
1323  // record to login history for user (h==0) and on global server level (h==1)
1324  for(int h = 0; h < 2; ++h)
1325  {
1326  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
1327  (std::string)USERS_LOGIN_HISTORY_PATH +
1328  (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1329  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1330 
1331  HttpXmlDocument histXml;
1332 
1333  if(histXml.loadXmlDocument(fn)) // not found
1334  {
1335  while(histXml.getChildrenCount() + 1 >
1336  (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1337  histXml.removeDataElement();
1338  }
1339  else
1340  __COUT__ << "No previous login history found." << __E__;
1341 
1342  // add new entry to history
1343  char entryStr[500];
1344  if(h)
1345  sprintf(entryStr,
1346  "Time=%lu Username=%s Permissions=%s UID=%lu",
1347  time(0),
1348  Users_[i].username_.c_str(),
1349  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1350  Users_[i].userId_);
1351  else
1352  sprintf(entryStr,
1353  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1354  time(0),
1355  Users_[i].displayName_.c_str(),
1356  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1357  Users_[i].userId_);
1358  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1359 
1360  // save file
1361  histXml.saveXmlDocument(fn);
1362  }
1363 
1364  // SUCCESS!!
1365  // Note: users.xml is NOT saved here — only account add/delete/modify should touch it
1366 
1367  //modified 03-Mar-2026, do not need to save login attempts/fails, let them be tracked in memory, snapshots of the database will be saved periodically, and on shutdown
1368  // saveDatabaseToFile(DB_USERS); // users db modified, so save
1369 
1370  jumbledUser = Users_[i].displayName_; // pass by reference displayName
1371  newAccountCode = createNewActiveSession(Users_[i].userId_,
1372  ip); // return cookie code by reference
1373 
1374  __COUTTV__(ActiveSessions_.size());
1375  // if only one user, then attempt to take lock for user friendliness
1376  if(ActiveSessions_.size() == 1)
1377  {
1378  __COUT__ << "Attempting to auto-lock for first login user '"
1379  << Users_[i].username_ << "'... " << __E__;
1380  setUserWithLock(Users_[i].userId_, true /*lock*/, Users_[i].username_);
1381  }
1382 
1383  return Users_[i].userId_; // return user Id
1384 } // end attemptActiveSession()
1385 
1386 //==============================================================================
1392 uint64_t WebUsers::attemptActiveSessionWithCert(const std::string& uuid,
1393  std::string& email,
1394  std::string& cookieCode,
1395  std::string& user,
1396  const std::string& ip)
1397 {
1398  if(!checkIpAccess(ip))
1399  {
1400  __COUT_ERR__ << "rejected ip: " << ip << __E__;
1401  return NOT_FOUND_IN_DATABASE;
1402  }
1403 
1404  cleanupExpiredEntries(); // remove expired active and login sessions
1405 
1406  if(!CareAboutCookieCodes_) // NO SECURITY
1407  {
1408  uint64_t uid = getAdminUserID();
1409  email = getUsersDisplayName(uid);
1410  cookieCode = genCookieCode(); // return "dummy" cookie code by reference
1411  return uid;
1412  }
1413 
1414  if(email == "")
1415  {
1416  __COUT__ << "Rejecting cert logon with blank fingerprint" << __E__;
1417 
1418  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1419 
1420  return NOT_FOUND_IN_DATABASE;
1421  }
1422 
1423  uint64_t i;
1424 
1425  // search login sessions for uuid
1426  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1427  {
1428  __COUT__ << "uuid: " << uuid << " is not found" << __E__;
1429  cookieCode = "1"; // to indicate uuid was not found
1430 
1431  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1432 
1433  return NOT_FOUND_IN_DATABASE;
1434  }
1435  ++LoginSessions_[i].loginAttempts_;
1436 
1437  email = getUserEmailFromFingerprint(email);
1438  __COUT__ << "DejumbledEmail = " << email << __E__;
1439  if(email == "")
1440  {
1441  __COUT__ << "Rejecting logon with unknown fingerprint" << __E__;
1442 
1443  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1444 
1445  return NOT_FOUND_IN_DATABASE;
1446  }
1447 
1448  // search users for username
1449  if((i = searchUsersDatabaseForUserEmail(email)) == NOT_FOUND_IN_DATABASE)
1450  {
1451  __COUT__ << "email: " << email << " is not found" << __E__;
1452 
1453  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1454 
1455  return NOT_FOUND_IN_DATABASE;
1456  }
1457  else
1458  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1459 
1460  user = getUsersUsername(i);
1461 
1462  Users_[i].lastLoginAttempt_ = time(0);
1463  if(isInactiveForGroup(Users_[i].permissions_))
1464  {
1465  __COUT__ << "User '" << user
1466  << "' account INACTIVE (could be due to failed logins)." << __E__;
1467  return NOT_FOUND_IN_DATABASE;
1468  }
1469 
1470  if(Users_[i].salt_ == "") // Can't be first login
1471  {
1472  return NOT_FOUND_IN_DATABASE;
1473  }
1474 
1475  __COUT__ << "Login successful for: " << user << __E__;
1476 
1477  // Only persist failure count reset if it was previously non-zero
1478  if(Users_[i].loginFailureCount_ != 0)
1479  {
1480  Users_[i].loginFailureCount_ = 0;
1481  saveLoginFailureCounts(); // persist reset to separate file
1482  }
1483 
1484  // record to login history for user (h==0) and on global server level (h==1)
1485  for(int h = 0; h < 2; ++h)
1486  {
1487  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
1488  (std::string)USERS_LOGIN_HISTORY_PATH +
1489  (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1490  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1491 
1492  HttpXmlDocument histXml;
1493 
1494  if(histXml.loadXmlDocument(fn)) // not found
1495  {
1496  while(histXml.getChildrenCount() + 1 >
1497  (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1498  histXml.removeDataElement();
1499  }
1500  else
1501  __COUT__ << "No previous login history found." << __E__;
1502 
1503  // add new entry to history
1504  char entryStr[500];
1505  if(h)
1506  sprintf(entryStr,
1507  "Time=%lu Username=%s Permissions=%s UID=%lu",
1508  time(0),
1509  Users_[i].username_.c_str(),
1510  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1511  Users_[i].userId_);
1512  else
1513  sprintf(entryStr,
1514  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1515  time(0),
1516  Users_[i].displayName_.c_str(),
1517  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1518  Users_[i].userId_);
1519  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1520 
1521  // save file
1522  histXml.saveXmlDocument(fn);
1523  }
1524 
1525  // SUCCESS!!
1526  // Note: users.xml is NOT saved here — only account add/delete/modify should touch it
1527 
1528  //modified 03-Mar-2026, do not need to save login attempts/fails, let them be tracked in memory, snapshots of the database will be saved periodically, and on shutdown
1529  // saveDatabaseToFile(DB_USERS); // users db modified, so save
1530 
1531  email = Users_[i].displayName_; // pass by reference displayName
1532  cookieCode = createNewActiveSession(Users_[i].userId_,
1533  ip); // return cookie code by reference
1534  return Users_[i].userId_; // return user Id
1535 } // end attemptActiveSessionWithCert()
1536 
1537 //==============================================================================
1540 uint64_t WebUsers::searchActiveSessionDatabaseForCookie(
1541  const std::string& cookieCode) const
1542 {
1543  uint64_t i = 0;
1544  for(; i < ActiveSessions_.size(); ++i)
1545  if(ActiveSessions_[i].cookieCode_ == cookieCode)
1546  break;
1547  return (i == ActiveSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1548 } //end searchActiveSessionDatabaseForCookie()
1549 
1554 // {
1555 // for(const auto& remoteSession : RemoteSessions_)
1556 // if(remoteSession.second.second.username_ == username)
1557 // return remoteSession.first;
1558 // return NOT_FOUND_IN_DATABASE;
1559 // } //end searchRemoteSessionDatabaseForUsername()
1560 
1561 //==============================================================================
1565 uint64_t WebUsers::checkRemoteLoginVerification(std::string& cookieCode,
1566  bool refresh,
1567  bool doNotGoRemote,
1568  const std::string& ip)
1569 {
1570  __COUTVS__(2, cookieCode);
1571  remoteLoginVerificationEnabledBlackoutTime_ = 0;
1572  if(!remoteLoginVerificationSocket_) //instantiate socket first time needed
1573  {
1575  {
1576  __SS__
1577  << "Illegal remote login verification port found in remote destination "
1578  << remoteLoginVerificationIP_ << ":" << remoteLoginVerificationPort_
1579  << ". Please check remote settings." << __E__;
1580  __SS_THROW__;
1581  }
1582  __COUT_INFO__ << "Instantiating Remote Gateway login verification socket! "
1583  "Validation requests will go to "
1584  << remoteLoginVerificationIP_ << ":" << remoteLoginVerificationPort_
1585  << __E__;
1586 
1587  remoteLoginVerificationSocket_ =
1588  std::make_unique<TransceiverSocket>(remoteLoginVerificationIP_);
1589  remoteLoginVerificationSocket_->initialize();
1590 
1591  remoteLoginVerificationSocketTarget_ = std::make_unique<Socket>(
1592  remoteLoginVerificationIP_, remoteLoginVerificationPort_);
1593  }
1594 
1595  //============================
1597  auto lockHandling = [this, refresh](std::string username,
1598  uint64_t verifiedUserId) -> uint64_t {
1599  __COUTT__ << "lambda lockHandling()" << __E__;
1600  __COUTTV__(ActiveSessions_.size());
1601  __COUTTV__(RemoteSessions_.size());
1602 
1603  if((!CareAboutCookieCodes_) //if passwords not on for subsystem
1604  && refresh &&
1605  (usersUsernameWithLock_ == DEFAULT_ADMIN_USERNAME ||
1606  usersUsernameWithLock_ == "") &&
1607  usersUsernameWithLock_ != username)
1608  {
1609  __COUT_INFO__ << "Overriding local user-with-lock '" << usersUsernameWithLock_
1610  << "' with remote user-with-lock 'Remote:" << username << "'"
1611  << __E__;
1612  usersUsernameWithLock_ =
1613  username; //Note: not calling setUserWithLock() because taking lock was incidental (on ots restart, will revert lock to admin still)
1614  addSystemMessage( //broadcast change!
1615  "*",
1616  getUserWithLock() + " has locked REMOTE ots (overriding anonymous " +
1617  DEFAULT_ADMIN_USERNAME + " user).");
1618  }
1619  else if((ActiveSessions_.size() == 0 &&
1620  RemoteSessions_.size() == 1) // if first remote user
1621  && refresh && (usersUsernameWithLock_ == "") &&
1622  usersUsernameWithLock_ != username)
1623  {
1624  __COUT_INFO__ << "Overriding local user-with-lock '" << usersUsernameWithLock_
1625  << "' with remote user-with-lock 'Remote:" << username << "'"
1626  << __E__;
1627  usersUsernameWithLock_ =
1628  username; //Note: not calling setUserWithLock() because taking lock was incidental (on ots restart, will revert lock to admin still)
1629  addSystemMessage( //broadcast change!
1630  "*",
1631  getUserWithLock() + " has locked REMOTE ots (which was unlocked).");
1632  }
1633  return verifiedUserId;
1634  }; //end lambda function lockHandling()
1635 
1636  //check if cookie code is cached locally
1637  cleanupExpiredRemoteEntries(); // remove expired cookies
1638  __COUTTV__(cookieCode);
1639  __COUTTV__(RemoteSessions_.size());
1640  auto it = RemoteSessions_.find(cookieCode);
1641  if(it != RemoteSessions_.end()) //then found cached cookie code
1642  {
1643  __COUTT__ << "cookieCode still active locally!" << __E__;
1644  __COUTTV__(it->second.userId_);
1645  uint64_t j = searchUsersDatabaseForUserId(it->second.userId_);
1646  if(j == NOT_FOUND_IN_DATABASE)
1647  {
1648  __SS__ << "Could not find cache entry for remote user ID '"
1649  << it->second.userId_ << "' - notify admins." << __E__;
1650  __SS_THROW__;
1651  }
1652  __COUTTV__(Users_[j].username_);
1653 
1654  // now, need to check lock handling!
1655  return lockHandling(Users_[j].username_, it->second.userId_);
1656  // return it->second.userId_;
1657  }
1658  //else ask Remote server to verify login
1659 
1660  __COUTTV__(doNotGoRemote);
1661  if(doNotGoRemote)
1662  return NOT_FOUND_IN_DATABASE;
1663 
1664  // Send these parameters:
1665  // command = loginVerify
1666  // parameters.addParameter("CookieCode");
1667  // parameters.addParameter("RefreshOption");
1668  // parameters.addParameter("IPAddress");
1669  // -- Use name to lookup access level conversion for user
1670  // -- if Desktop Icon has a special permission type, then modify userGroupPermissionsMap's allUsers to match
1671  // parameters.addParameter("RemoteGatewaySelfName");
1672 
1673  std::string request = "loginVerify," + cookieCode + "," + (refresh ? "1" : "0") +
1674  "," + ip + "," + remoteGatewaySelfName_;
1675 
1676  __COUTV__(request);
1677  __COUTS__(40) << StringMacros::stackTrace() << __E__;
1678 
1679  std::string requestResponseString = remoteLoginVerificationSocket_->sendAndReceive(
1680  *remoteLoginVerificationSocketTarget_, request, 10 /*timeoutSeconds*/);
1681  __COUTV__(requestResponseString);
1682 
1683  //from response... extract refreshedCookieCode, permissions, userWithLock, username, and display name
1684  std::vector<std::string> rxParams =
1685  StringMacros::getVectorFromString(requestResponseString);
1686  __COUTV__(StringMacros::vectorToString(rxParams));
1687 
1688  if(rxParams.size() != 6)
1689  {
1690  __COUT__ << "Remote login response indicates rejected: " << rxParams.size()
1691  << __E__;
1692  return NOT_FOUND_IN_DATABASE;
1693  }
1694  //else valid remote login! so create active remote session object
1695 
1696  // Receive these parameters
1697  // 0: retParameters.addParameter("CookieCode", cookieCode);
1698  // 1: retParameters.addParameter("Permissions", StringMacros::mapToString(userGroupPermissionsMap).c_str());
1699  // 2: retParameters.addParameter("UserWithLock", userWithLock);
1700  // 3: retParameters.addParameter("Username", theWebUsers_.getUsersUsername(uid));
1701  // 4: retParameters.addParameter("DisplayName", theWebUsers_.getUsersDisplayName(uid));
1702  // 5: retParameters.addParameter("UserSessionIndex", td::to_string(userSessionIndex));
1703 
1704  __COUTTV__(rxParams[2]); //Primary Gateway user-with-lock
1705  __COUTTV__(usersUsernameWithLock_); //Local Gateway user-with-lock
1706 
1707  //search for an existing matching username, otherwise create
1708  std::string username = rxParams[3];
1709  __COUTTV__(username);
1710  uint64_t j = searchUsersDatabaseForUsername(username);
1711  if(j == NOT_FOUND_IN_DATABASE)
1712  {
1713  __COUT_INFO__ << "Creating User entry for remote user '" << username
1714  << "' in local user list to track user preferences." << __E__;
1715 
1716  //Note: createNewAccount will validate username and displayName
1717  createNewAccount(username, rxParams[4] /* displayName */, "" /* email */);
1718  j = Users_.size() - 1;
1719  }
1720 
1721  Users_[j].lastLoginAttempt_ = time(0);
1722  Users_[j].setModifier("REMOTE_GATEWAY");
1723 
1724  //take permissions from remote source always, it overrides existing local user settings (and will force changes to local user db)
1725  __COUTV__(StringMacros::decodeURIComponent(rxParams[1]));
1726  Users_[j]
1727  .permissions_.clear(); //otherwise collissions could occur in getMapFromString()
1729  Users_[j].permissions_);
1730  __COUTV__(StringMacros::mapToString(Users_[j].permissions_));
1731  __COUTV__(Users_[j].username_);
1732  __COUTV__(Users_[j].userId_);
1733 
1734  //fill in Remote Session and User info to cache for next login attempt
1735 
1736  cookieCode = rxParams[0]; //modify cookieCode for response
1737  __COUTTV__(cookieCode);
1738  ActiveSession& newRemoteSession =
1739  RemoteSessions_[cookieCode]; //construct remote ActiveSession
1740  newRemoteSession.cookieCode_ = cookieCode;
1741  newRemoteSession.ip_ = ip;
1742  newRemoteSession.userId_ = Users_[j].userId_;
1743  sscanf(rxParams[5].c_str(), "%lu", &newRemoteSession.sessionIndex_);
1744  newRemoteSession.startTime_ = time(0);
1745 
1746  // now, need to check lock handling!
1747  return lockHandling(Users_[j].username_, Users_[j].userId_);
1748 } //end checkRemoteLoginVerification()
1749 
1750 //==============================================================================
1753 bool WebUsers::isUsernameActive(const std::string& username) const
1754 {
1755  uint64_t u;
1756  if((u = searchUsersDatabaseForUsername(username)) == NOT_FOUND_IN_DATABASE)
1757  return false;
1758  return isUserIdActive(Users_[u].userId_);
1759 } //end isUsernameActive()
1760 
1761 //==============================================================================
1764 bool WebUsers::isUserIdActive(uint64_t uid) const
1765 {
1766  __COUTT__ << "isUserIdActive? " << uid << __E__;
1767  if(remoteLoginVerificationEnabled_) //first check remote sessions
1768  {
1769  for(const auto& remoteSession : RemoteSessions_)
1770  if(remoteSession.second.userId_ == uid)
1771  return true;
1772  } //end remote session checkion
1773 
1774  uint64_t i = 0;
1775  for(; i < ActiveSessions_.size(); ++i)
1776  if(ActiveSessions_[i].userId_ == uid)
1777  return true;
1778  return false;
1779 } // end isUserIdActive()
1780 
1781 //==============================================================================
1784 uint64_t WebUsers::searchUsersDatabaseForUsername(const std::string& username) const
1785 {
1786  uint64_t i = 0;
1787  for(; i < Users_.size(); ++i)
1788  if(Users_[i].username_ == username)
1789  break;
1790  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1791 } // end searchUsersDatabaseForUsername()
1792 
1793 //==============================================================================
1796 uint64_t WebUsers::searchUsersDatabaseForDisplayName(const std::string& displayName) const
1797 {
1798  uint64_t i = 0;
1799  for(; i < Users_.size(); ++i)
1800  if(Users_[i].displayName_ == displayName)
1801  break;
1802  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1803 } // end searchUsersDatabaseForUsername()
1804 
1805 //==============================================================================
1808 uint64_t WebUsers::searchUsersDatabaseForUserEmail(const std::string& useremail) const
1809 {
1810  uint64_t i = 0;
1811  for(; i < Users_.size(); ++i)
1812  if(Users_[i].email_ == useremail)
1813  break;
1814  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1815 } // end searchUsersDatabaseForUserEmail()
1816 
1817 //==============================================================================
1820 uint64_t WebUsers::searchUsersDatabaseForUserId(uint64_t uid) const
1821 {
1822  uint64_t i = 0;
1823  for(; i < Users_.size(); ++i)
1824  if(Users_[i].userId_ == uid)
1825  break;
1826  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1827 } // end searchUsersDatabaseForUserId();
1828 
1829 //==============================================================================
1832 uint64_t WebUsers::searchLoginSessionDatabaseForUUID(const std::string& uuid) const
1833 {
1834  uint64_t i = 0;
1835  for(; i < LoginSessions_.size(); ++i)
1836  if(LoginSessions_[i].uuid_ == uuid)
1837  break;
1838  return (i == LoginSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1839 } // end searchLoginSessionDatabaseForUUID()
1840 
1841 //==============================================================================
1844 uint64_t WebUsers::searchHashesDatabaseForHash(const std::string& hash)
1845 {
1846  uint64_t i = 0;
1847  //__COUT__ << i << " " << Hashes_.size() << " " << hash << __E__;
1848  for(; i < Hashes_.size(); ++i)
1849  if(Hashes_[i].hash_ == hash)
1850  break;
1851  // else
1852  // __COUT__ << HashesVector[i] << " ?????? " << __E__;
1853  //__COUT__ << i << __E__;
1854  if(i < Hashes_.size()) // if found, means login successful, so update access time
1855  Hashes_[i].accessTime_ =
1856  ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) &
1857  0x0FFFFFFFFFE000000);
1858  // else
1859  // __COUT__ << "No matching hash..." << __E__;
1860 
1861  //__COUT__ << i << __E__;
1862  return (i == Hashes_.size()) ? NOT_FOUND_IN_DATABASE : i;
1863 } // end searchHashesDatabaseForHash()
1864 
1865 //==============================================================================
1869 bool WebUsers::addToHashesDatabase(const std::string& hash)
1870 {
1871  if(searchHashesDatabaseForHash(hash) != NOT_FOUND_IN_DATABASE)
1872  {
1873  __COUT__ << "Hash collision: " << hash << __E__;
1874  return false;
1875  }
1876  Hashes_.push_back(Hash());
1877  Hashes_.back().hash_ = hash;
1878  Hashes_.back().accessTime_ =
1879  ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) &
1880  0x0FFFFFFFFFE000000);
1881  // in seconds, blur by month and mask out changes on year time frame: 0xFFFFFFFF
1882  // FE000000
1883  return saveDatabaseToFile(DB_HASHES);
1884 } // end addToHashesDatabase()
1885 
1886 //==============================================================================
1888 std::string WebUsers::genCookieCode()
1889 {
1890  char hexStr[3];
1891  std::string cc = "";
1892  for(uint32_t i = 0; i < COOKIE_CODE_LENGTH / 2; ++i)
1893  {
1894  intToHexStr(rand(), hexStr);
1895  cc.append(hexStr);
1896  }
1897  return cc;
1898 } // end genCookieCode()
1899 
1900 //==============================================================================
1904 std::string WebUsers::createNewActiveSession(uint64_t uid,
1905  const std::string& ip,
1906  uint64_t asIndex)
1907 {
1908  //__COUTV__(ip);
1909  ActiveSessions_.push_back(ActiveSession());
1910  ActiveSessions_.back().cookieCode_ = genCookieCode();
1911  ActiveSessions_.back().ip_ = ip;
1912  ActiveSessions_.back().userId_ = uid;
1913  ActiveSessions_.back().startTime_ = time(0);
1914 
1915  if(asIndex) // this is a refresh of current active session
1916  ActiveSessions_.back().sessionIndex_ = asIndex;
1917  else
1918  {
1919  // find max(ActiveSessionIndex)
1920  uint64_t max = 0;
1921  for(uint64_t j = 0; j < ActiveSessions_.size(); ++j)
1922  if(ActiveSessions_[j].userId_ == uid &&
1923  max < ActiveSessions_[j].sessionIndex_) // new max
1924  max = ActiveSessions_[j].sessionIndex_;
1925 
1926  ActiveSessions_.back().sessionIndex_ = (max ? max + 1 : 1); // 0 is illegal
1927  }
1928 
1929  return ActiveSessions_.back().cookieCode_;
1930 } // end createNewActiveSession()
1931 
1932 //==============================================================================
1956 std::string WebUsers::refreshCookieCode(unsigned int i, bool enableRefresh)
1957 {
1958  // find most recent cookie for ActiveSessionIndex (should be deepest in vector always)
1959  for(uint64_t j = ActiveSessions_.size() - 1; j != (uint64_t)-1;
1960  --j) // reverse iterate vector
1961  if(ActiveSessions_[j].userId_ == ActiveSessions_[i].userId_ &&
1962  ActiveSessions_[j].sessionIndex_ ==
1963  ActiveSessions_[i].sessionIndex_) // if uid and asIndex match, found match
1964  {
1965  // found!
1966 
1967  // If half of expiration time is up, a new cookie is generated as most recent
1968  if(enableRefresh && (time(0) - ActiveSessions_[j].startTime_ >
1969  ACTIVE_SESSION_EXPIRATION_TIME / 2))
1970  {
1971  // but previous is maintained and start time is changed to accommodate
1972  // overlap time.
1973  ActiveSessions_[j].startTime_ =
1974  time(0) - ACTIVE_SESSION_EXPIRATION_TIME +
1975  ACTIVE_SESSION_COOKIE_OVERLAP_TIME; // give time window for stale
1976  // cookie commands before
1977  // expiring
1978 
1979  // create new active cookieCode with same ActiveSessionIndex, will now be
1980  // found as most recent
1981  return createNewActiveSession(ActiveSessions_[i].userId_,
1982  ActiveSessions_[i].ip_,
1983  ActiveSessions_[i].sessionIndex_);
1984  }
1985 
1986  return ActiveSessions_[j].cookieCode_; // cookieCode is unchanged
1987  }
1988 
1989  return "0"; // failure, should be impossible since i is already validated
1990 } // end refreshCookieCode()
1991 
1992 //==============================================================================
1997 uint64_t WebUsers::isCookieCodeActiveForLogin(const std::string& uuid,
1998  std::string& cookieCode,
1999  std::string& username)
2000 {
2001  if(!CareAboutCookieCodes_)
2002  return getAdminUserID(); // always successful
2003 
2004  // else
2005  // __COUT__ << "I care about
2006  // cookies?!?!?!*************************************************" << __E__;
2007 
2008  if(!ActiveSessions_.size())
2009  return NOT_FOUND_IN_DATABASE; // no active sessions, so do nothing
2010 
2011  uint64_t i, j; // used to iterate and search
2012 
2013  // find uuid in login session database else return "0"
2014  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
2015  {
2016  __COUT__ << "uuid not found: " << uuid << __E__;
2017  return NOT_FOUND_IN_DATABASE;
2018  }
2019 
2020  username =
2021  dejumble(username, LoginSessions_[i].id_); // dejumble user for cookie check
2022 
2023  // search active users for cookie code
2024  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
2025  {
2026  __COUT__ << "Cookie code not found" << __E__;
2027  return NOT_FOUND_IN_DATABASE;
2028  }
2029 
2030  // search users for user id
2031  if((j = searchUsersDatabaseForUserId(ActiveSessions_[i].userId_)) ==
2032  NOT_FOUND_IN_DATABASE)
2033  {
2034  __COUT__ << "User ID not found" << __E__;
2035  return NOT_FOUND_IN_DATABASE;
2036  }
2037 
2038  // match username, with one found
2039  if(Users_[j].username_ != username)
2040  {
2041  __COUT__ << "cookieCode: " << cookieCode << " was.." << __E__;
2042  __COUT__ << "username: " << username << " is not found" << __E__;
2043  return NOT_FOUND_IN_DATABASE;
2044  }
2045 
2046  username = Users_[j].displayName_; // return display name by reference
2047  cookieCode = refreshCookieCode(i); // refresh cookie by reference
2048  return Users_[j].userId_; // return user ID
2049 }
2050 
2051 //==============================================================================
2055 {
2056  bool unique;
2057  std::vector<uint64_t> uniqueAsi; // maintain unique as indices for reference
2058 
2059  uint64_t i, j;
2060  for(i = 0; i < ActiveSessions_.size(); ++i)
2061  if(ActiveSessions_[i].userId_ == uid) // found active session for user
2062  {
2063  // check if ActiveSessionIndex is unique
2064  unique = true;
2065 
2066  for(j = 0; j < uniqueAsi.size(); ++j)
2067  if(uniqueAsi[j] == ActiveSessions_[i].sessionIndex_)
2068  {
2069  unique = false;
2070  break;
2071  }
2072 
2073  if(unique) // unique! so count and save
2074  uniqueAsi.push_back(ActiveSessions_[i].sessionIndex_);
2075  }
2076 
2077  __COUT__ << "Found " << uniqueAsi.size() << " active sessions for uid " << uid
2078  << __E__;
2079 
2080  return uniqueAsi.size();
2081 } // end getActiveSessionCountForUser()
2082 
2083 //==============================================================================
2089 bool WebUsers::checkIpAccess(const std::string& ip)
2090 {
2091  if(ip == "0")
2092  return true; // always accept dummy IP
2093 
2094  __COUTTV__(ip);
2095 
2096  if(time(0) > ipSecurityLastLoadTime_ +
2097  10 * 60 * 60) //every 10 minutes (to allow manual dynamic changes)
2098  {
2099  ipSecurityLastLoadTime_ = time(0);
2100  loadIPAddressSecurity();
2101  }
2102 
2103  for(const auto& acceptIp : ipAccessAccept_)
2104  if(StringMacros::wildCardMatch(ip, acceptIp))
2105  {
2106  __COUTV__(acceptIp);
2107  return true; // found in accept set, so accept
2108  }
2109  for(const auto& rejectIp : ipAccessReject_)
2110  if(StringMacros::wildCardMatch(ip, rejectIp))
2111  {
2112  __COUTV__(rejectIp);
2113  return false; // found in reject file, so reject
2114  }
2115  for(const auto& blacklistIp : ipAccessBlacklist_)
2116  if(StringMacros::wildCardMatch(ip, blacklistIp))
2117  {
2118  __COUTV__(blacklistIp);
2119  return false; // found in blacklist file, so reject
2120  }
2121 
2122  // default to accept if nothing triggered above
2123  return true;
2124 } // end checkIpAccess()
2125 
2126 //==============================================================================
2128 void WebUsers::incrementIpBlacklistCount(const std::string& ip)
2129 {
2130  if(ipAccessBlacklist_.find(ip) != ipAccessBlacklist_.end())
2131  return; //already in IP blacklist
2132 
2133  // increment ip blacklist counter
2134  auto it = ipBlacklistCounts_.find(ip);
2135  if(it == ipBlacklistCounts_.end())
2136  {
2137  __COUT__ << "First error for ip '" << ip << "'" << __E__;
2138  ipBlacklistCounts_[ip] = 1;
2139  }
2140  else
2141  {
2142  ++(it->second);
2143 
2144  if(it->second >= IP_BLACKLIST_COUNT_THRESHOLD)
2145  {
2146  __COUT_WARN__ << "Adding IP '" << ip << "' to blacklist!" << __E__;
2147 
2148  ipAccessBlacklist_.emplace(ip);
2149  __COUTV__(ipAccessBlacklist_.size());
2150 
2151  // append to blacklisted IP to generated IP reject file
2152  FILE* fp = fopen((IP_BLACKLIST_FILE).c_str(), "a");
2153  if(!fp)
2154  {
2155  __COUT_ERR__ << "IP black list file '" << IP_BLACKLIST_FILE
2156  << "' could not be opened." << __E__;
2157  return;
2158  }
2159  fprintf(fp, "%s\n", ip.c_str());
2160  fclose(fp);
2161  }
2162  }
2163 } // end incrementIpBlacklistCount()
2164 
2165 //==============================================================================
2167 std::string WebUsers::getUsersDisplayName(uint64_t uid)
2168 {
2169  uint64_t i;
2170  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
2171  return "";
2172  return Users_[i].displayName_;
2173 } // end getUsersDisplayName()
2174 
2175 //==============================================================================
2177 std::string WebUsers::getUsersUsername(uint64_t uid)
2178 {
2179  uint64_t i;
2180  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
2181  return "";
2182  return Users_[i].username_;
2183 } // end getUsersUsername()
2184 
2185 //==============================================================================
2196 uint64_t WebUsers::cookieCodeLogout(const std::string& cookieCode,
2197  bool logoutOtherUserSessions,
2198  uint64_t* userId,
2199  const std::string& ip)
2200 {
2201  uint64_t i;
2202 
2203  // search active users for cookie code
2204  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
2205  {
2206  __COUT__ << "Cookie code not found" << __E__;
2207 
2208  incrementIpBlacklistCount(ip); // increment ip blacklist counter
2209 
2210  return NOT_FOUND_IN_DATABASE;
2211  }
2212  else
2213  ipBlacklistCounts_[ip] = 0; // clear blacklist count
2214 
2215  // check ip
2216  if(ActiveSessions_[i].ip_ != ip)
2217  {
2218  __COUT__ << "IP does not match active session" << __E__;
2219  return NOT_FOUND_IN_DATABASE;
2220  }
2221 
2222  // found valid active session i
2223  // if logoutOtherUserSessions
2224  // remove active sessions that match ActiveSessionUserIdVector[i] and
2225  // ActiveSessionIndex[i] else remove active sessions that match
2226  // ActiveSessionUserIdVector[i] but not ActiveSessionIndex[i]
2227 
2228  uint64_t asi = ActiveSessions_[i].sessionIndex_;
2229  uint64_t uid = ActiveSessions_[i].userId_;
2230  if(userId)
2231  *userId = uid; // return uid if requested
2232  uint64_t logoutCount = 0;
2233 
2234  i = 0;
2235  while(i < ActiveSessions_.size())
2236  {
2237  if((logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid &&
2238  ActiveSessions_[i].sessionIndex_ != asi) ||
2239  (!logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid &&
2240  ActiveSessions_[i].sessionIndex_ == asi))
2241  {
2242  __COUT__ << "Logging out of active session " << ActiveSessions_[i].userId_
2243  << "-" << ActiveSessions_[i].sessionIndex_ << __E__;
2244  ActiveSessions_.erase(ActiveSessions_.begin() + i);
2245  ++logoutCount;
2246  }
2247  else // only increment if no delete, for effectively erase rewind
2248  ++i;
2249  } // end cleanup active sessioins loop
2250 
2251  __COUT__ << "Found and removed active session count = " << logoutCount << __E__;
2252 
2253  return logoutCount;
2254 } // end cookieCodeLogout()
2255 
2256 //==============================================================================
2270  std::string& cookieCode,
2271  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>* userPermissions,
2272  uint64_t* uid,
2273  const std::string& ip,
2274  bool refresh,
2275  bool doNotGoRemote,
2276  std::string* userWithLock,
2277  uint64_t* userSessionIndex)
2278 {
2279  __COUTS__(50) << StringMacros::stackTrace() << __E__;
2280  __COUTVS__(51, ip);
2281 
2282  // check ip black list and increment counter if cookie code not found
2283  if(!checkIpAccess(ip))
2284  {
2285  __COUT_ERR__ << "User IP rejected." << __E__;
2286  cookieCode = REQ_NO_LOGIN_RESPONSE;
2287  return false;
2288  }
2289 
2290  cleanupExpiredEntries(); // remove expired cookies
2291 
2292  uint64_t i, j, userId = NOT_FOUND_IN_DATABASE, userSession = NOT_FOUND_IN_DATABASE;
2293 
2294  __COUTTV__(CareAboutCookieCodes_);
2295  __COUTT__ << "refresh=" << refresh << ", doNotGoRemote=" << doNotGoRemote << __E__;
2296  __COUTVS__(2, cookieCode);
2297 
2298  bool localEnableRemoteLogin = WebUsers::
2299  remoteLoginVerificationEnabled_; //cache here so another process does not change mid-function
2300  __COUTTV__(localEnableRemoteLogin);
2301 
2302  //always go remote if enabled
2303  try
2304  {
2305  if(localEnableRemoteLogin &&
2306  time(0) > remoteLoginVerificationEnabledBlackoutTime_ &&
2307  (userId = checkRemoteLoginVerification(
2308  cookieCode, refresh, doNotGoRemote, ip)) != NOT_FOUND_IN_DATABASE)
2309  {
2310  // remote verify success!
2311  __COUTT__ << "Remote login session verified." << __E__;
2312  userSession = RemoteSessions_.at(cookieCode).sessionIndex_;
2313  }
2314  }
2315  catch(...)
2316  {
2317  std::string err = "";
2318  try
2319  {
2320  throw;
2321  }
2322  catch(const std::exception& e)
2323  {
2324  err = e.what();
2325  }
2326 
2327  __COUT_WARN__ << "Ignoring exception during remote login verification. " << err
2328  << __E__;
2329 
2330  //Disable remote login in the case that remote verifier is down
2331  if(!CareAboutCookieCodes_ && localEnableRemoteLogin &&
2332  remoteLoginVerificationEnabledBlackoutTime_ == 0)
2333  {
2334  remoteLoginVerificationEnabled_ = false; //set globally
2335  localEnableRemoteLogin = false; //set locally
2336  remoteLoginVerificationEnabledBlackoutTime_ = time(0) + 10;
2337  __COUT_INFO__ << "Disabled remote login until "
2339  remoteLoginVerificationEnabledBlackoutTime_)
2340  << __E__;
2341  }
2342  }
2343  __COUTTV__(localEnableRemoteLogin);
2344 
2345  if(localEnableRemoteLogin && userId == NOT_FOUND_IN_DATABASE)
2346  __COUTT__ << "Remote login verification failed." << __E__;
2347 
2348  if(!CareAboutCookieCodes_ &&
2349  userId == NOT_FOUND_IN_DATABASE) // No Security, so grant admin
2350  {
2351  if(userPermissions)
2352  *userPermissions =
2353  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>(
2354  {{WebUsers::DEFAULT_USER_GROUP, WebUsers::PERMISSION_LEVEL_ADMIN}});
2355  if(uid)
2356  *uid = getAdminUserID();
2357  if(userWithLock)
2358  *userWithLock = usersUsernameWithLock_;
2359  if(userSessionIndex)
2360  *userSessionIndex = 0;
2361 
2362  if(cookieCode.size() != COOKIE_CODE_LENGTH)
2363  cookieCode = genCookieCode(); // return "dummy" cookie code
2364 
2365  if(localEnableRemoteLogin) //want future login attempts to still go to remote
2366  {
2367  cookieCode = WebUsers::
2368  REQ_ALLOW_NO_USER; //allowNoUser will not overwrite other valid cookieCodes in parent Gateway Desktop
2369  }
2370 
2371  return true;
2372  }
2373  // else using security!
2374 
2375  if(userId == NOT_FOUND_IN_DATABASE) //handle standard active session verify
2376  {
2377  // search active users for cookie code
2378  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) ==
2379  NOT_FOUND_IN_DATABASE)
2380  {
2381  __COUT_ERR__ << "Cookie code not found" << __E__;
2382  cookieCode = REQ_NO_LOGIN_RESPONSE;
2383 
2384  incrementIpBlacklistCount(ip); // increment ip blacklist counter
2385 
2386  return false;
2387  }
2388  else
2389  ipBlacklistCounts_[ip] = 0; // clear blacklist count
2390 
2391  // check ip
2392  if(ip != "0" && ActiveSessions_[i].ip_ != ip)
2393  {
2394  __COUTV__(ActiveSessions_[i].ip_);
2395  __COUTV__(ip);
2396  __COUT_ERR__ << "IP does not match active session." << __E__;
2397  cookieCode = REQ_NO_LOGIN_RESPONSE;
2398  return false;
2399  }
2400 
2401  userId = ActiveSessions_[i].userId_;
2402  userSession = ActiveSessions_[i].sessionIndex_;
2403  cookieCode = refreshCookieCode(i, refresh); // refresh cookie by reference
2404  __COUTT__ << "Login session verified." << __E__;
2405  }
2406 
2407  //at this point userId has been confirmed remotely or locally
2408 
2409  // get Users record
2410  if((j = searchUsersDatabaseForUserId(userId)) == NOT_FOUND_IN_DATABASE)
2411  {
2412  __COUT_ERR__ << "After login verification, User ID not found! Notify admins."
2413  << __E__;
2414  cookieCode = REQ_NO_LOGIN_RESPONSE;
2415  return false;
2416  }
2417 
2418  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> tmpPerm =
2419  getPermissionsForUser(userId);
2420 
2421  if(isInactiveForGroup(tmpPerm)) // Check for inactive for all requests!
2422  {
2423  __COUTT__ << "Inactive user identified." << __E__;
2424  cookieCode = REQ_NO_PERMISSION_RESPONSE;
2425  return false;
2426  }
2427 
2428  // success!
2429  if(userPermissions)
2430  *userPermissions = tmpPerm;
2431  if(uid)
2432  *uid = userId;
2433  if(userWithLock)
2434  *userWithLock = usersUsernameWithLock_;
2435  if(userSessionIndex)
2436  *userSessionIndex = userSession;
2437 
2438  return true;
2439 } // end cookieCodeIsActiveForRequest()
2440 
2441 //==============================================================================
2448 void WebUsers::cleanupExpiredEntries(std::vector<std::string>* loggedOutUsernames)
2449 {
2450  uint64_t i; // used to iterate and search
2451  uint64_t tmpUid;
2452 
2453  if(loggedOutUsernames) // return logged out users this time and clear storage vector
2454  {
2455  for(i = 0; i < UsersLoggedOutUsernames_.size(); ++i)
2456  loggedOutUsernames->push_back(UsersLoggedOutUsernames_[i]);
2457  UsersLoggedOutUsernames_.clear();
2458  }
2459 
2460  // remove expired entries from Login Session
2461  for(i = 0; i < LoginSessions_.size(); ++i)
2462  if(LoginSessions_[i].startTime_ + LOGIN_SESSION_EXPIRATION_TIME <
2463  time(0) || // expired
2464  LoginSessions_[i].loginAttempts_ > LOGIN_SESSION_ATTEMPTS_MAX)
2465  {
2466  __COUT__ << "Found expired login sessions: #" << (i + 1) << " of "
2467  << LoginSessions_.size() << __E__;
2468  //" at time " << LoginSessionStartTimeVector[i] << " with attempts " <<
2469  // LoginSessionAttemptsVector[i] << __E__;
2470 
2471  LoginSessions_.erase(LoginSessions_.begin() + i);
2472  --i; // rewind loop
2473  }
2474 
2475  // declare structures for ascii time
2476  // struct tm * timeinfo;
2477  // time_t tmpt;
2478  // char tstr[200];
2479  // timeinfo = localtime ( &(tmpt=time(0)) );
2480  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
2481  //__COUT__ << "Current time is: " << time(0) << " " << tstr << __E__;
2482 
2483  // remove expired entries from Active Session
2484  for(i = 0; i < ActiveSessions_.size(); ++i)
2485  if(ActiveSessions_[i].startTime_ + ACTIVE_SESSION_EXPIRATION_TIME <=
2486  time(0)) // expired
2487  {
2488  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i]));
2489  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
2490  //__COUT__ << "Found expired user: " << ActiveSessionUserIdVector[i] <<
2491  // " start time " << tstr << " i: " << i << " size: " <<
2492  // ActiveSessionStartTimeVector.size()
2493  // << __E__;
2494 
2495  __COUT__ << "Found expired active sessions: #" << (i + 1) << " of "
2496  << ActiveSessions_.size() << __E__;
2497  __COUTTV__(ActiveSessions_[i].cookieCode_);
2498 
2499  tmpUid = ActiveSessions_[i].userId_;
2500  ActiveSessions_.erase(ActiveSessions_.begin() + i);
2501 
2502  if(!isUserIdActive(tmpUid)) // if uid no longer active, then user was
2503  // completely logged out
2504  {
2505  if(loggedOutUsernames) // return logged out users this time
2506  loggedOutUsernames->push_back(
2507  Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
2508  else // store for next time requested as parameter
2509  UsersLoggedOutUsernames_.push_back(
2510  Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
2511  }
2512 
2513  --i; // rewind loop
2514  }
2515  // else
2516  // {
2517  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i] +
2518  // ACTIVE_SESSION_EXPIRATION_TIME)); sprintf(tstr,"\"%s\"",asctime
2519  //(timeinfo)); tstr[strlen(tstr)-2] = '\"';
2520  //
2521  // //__COUT__ << "Found user: " << ActiveSessionUserIdVector[i] << "-" <<
2522  // ActiveSessionIndex[i] <<
2523  // // " expires " << tstr <<
2524  // // " sec left " << ActiveSessionStartTimeVector[i] +
2525  // ACTIVE_SESSION_EXPIRATION_TIME - time(0) << __E__;
2526  //
2527  // }
2528 
2529  //__COUT__ << "Found usersUsernameWithLock_: " << usersUsernameWithLock_ << __E__;
2530  // size_t posRemoteFlag = std::string::npos;
2531  if(CareAboutCookieCodes_ && usersUsernameWithLock_ != "" &&
2532  // ((remoteLoginVerificationEnabled_ && //if remote login enabled, check if userWithLock is remote
2533  // (posRemoteFlag = usersUsernameWithLock_.find(REMOTE_USERLOCK_PREFIX)) == 0 &&
2534  // searchRemoteSessionDatabaseForUsername(
2535  // usersUsernameWithLock_.substr(strlen(REMOTE_USERLOCK_PREFIX))) == NOT_FOUND_IN_DATABASE ) ||
2536  // (posRemoteFlag != 0 &&
2537  !isUsernameActive(usersUsernameWithLock_))
2538  //))) // unlock if user no longer logged in
2539  usersUsernameWithLock_ = "";
2540 } // end cleanupExpiredEntries()
2541 
2542 //==============================================================================
2547 {
2548  // remove expired entries from Remote Active Session
2549  std::vector<std::string> toErase;
2550  for(const auto& remoteSession : RemoteSessions_)
2551  if(remoteSession.second.startTime_ + ACTIVE_SESSION_EXPIRATION_TIME / 4 <=
2552  time(0)) // expired
2553  {
2554  __COUT__ << "Found expired remote active sessions: #" << remoteSession.first
2555  << " in " << RemoteSessions_.size() << __E__;
2556  toErase.push_back(remoteSession.first); //mark for erasing
2557  }
2558  for(const auto& eraseId : toErase)
2559  RemoteSessions_.erase(eraseId);
2560 } // end cleanupExpiredRemoteEntries()
2561 
2562 //==============================================================================
2569 std::string WebUsers::createNewLoginSession(const std::string& UUID,
2570  const std::string& ip)
2571 {
2572  __COUTV__(UUID);
2573  //__COUTV__(ip);
2574 
2575  uint64_t i = 0;
2576  for(; i < LoginSessions_.size(); ++i)
2577  if(LoginSessions_[i].uuid_ == UUID)
2578  break;
2579 
2580  if(i != LoginSessions_.size())
2581  {
2582  __COUT_ERR__ << "UUID: " << UUID << " is not unique" << __E__;
2583  return "";
2584  }
2585  // else UUID is unique
2586 
2587  LoginSessions_.push_back(LoginSession());
2588  LoginSessions_.back().uuid_ = UUID;
2589 
2590  // generate sessionId
2591  char hexStr[3];
2592  std::string sid = "";
2593  for(i = 0; i < SESSION_ID_LENGTH / 2; ++i)
2594  {
2595  intToHexStr(rand(), hexStr);
2596  sid.append(hexStr);
2597  }
2598  LoginSessions_.back().id_ = sid;
2599  LoginSessions_.back().ip_ = ip;
2600  LoginSessions_.back().startTime_ = time(0);
2601  LoginSessions_.back().loginAttempts_ = 0;
2602 
2603  return sid;
2604 } // end createNewLoginSession()
2605 
2606 //==============================================================================
2611 std::string WebUsers::sha512(const std::string& user,
2612  const std::string& password,
2613  std::string& salt)
2614 {
2615  SHA512_CTX sha512_context;
2616  char hexStr[3];
2617 
2618  if(salt == "") // generate context
2619  {
2620  SHA512_Init(&sha512_context);
2621 
2622  for(unsigned int i = 0; i < 8; ++i)
2623  sha512_context.h[i] += rand();
2624 
2625  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2626  {
2627  intToHexStr((uint8_t)(((uint8_t*)(&sha512_context))[i]), hexStr);
2628 
2629  salt.append(hexStr);
2630  }
2631  //__COUT__ << salt << __E__;
2632  }
2633  else // use existing context
2634  {
2635  //__COUT__ << salt << __E__;
2636 
2637  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2638  ((uint8_t*)(&sha512_context))[i] = hexByteStrToInt(&(salt.c_str()[i * 2]));
2639  }
2640 
2641  std::string strToHash = salt + user + password;
2642 
2643  //__COUT__ << salt << __E__;
2644  unsigned char hash[SHA512_DIGEST_LENGTH];
2645  //__COUT__ << salt << __E__;
2646  char retHash[SHA512_DIGEST_LENGTH * 2 + 1];
2647  //__COUT__ << strToHash.length() << " " << strToHash << __E__;
2648 
2649  //__COUT__ << "If crashing occurs here, may be an illegal salt context." << __E__;
2650  SHA512_Update(&sha512_context, strToHash.c_str(), strToHash.length());
2651 
2652  SHA512_Final(hash, &sha512_context);
2653 
2654  //__COUT__ << salt << __E__;
2655  int i = 0;
2656  for(i = 0; i < SHA512_DIGEST_LENGTH; i++)
2657  sprintf(retHash + (i * 2), "%02x", hash[i]);
2658 
2659  //__COUT__ << salt << __E__;
2660  retHash[SHA512_DIGEST_LENGTH * 2] = '\0';
2661 
2662  //__COUT__ << salt << __E__;
2663 
2664  return retHash;
2665 } // end sha512()
2666 
2667 //==============================================================================
2671 std::string WebUsers::dejumble(const std::string& u, const std::string& s)
2672 {
2673  if(s.length() != SESSION_ID_LENGTH)
2674  return ""; // session std::string must be even
2675 
2676  const int ss = s.length() / 2;
2677  int p = hexByteStrToInt(&(s.c_str()[0])) % ss;
2678  int n = hexByteStrToInt(&(s.c_str()[p * 2])) % ss;
2679  int len = (hexByteStrToInt(&(u.c_str()[p * 2])) - p - n + ss * 3) % ss;
2680 
2681  std::vector<bool> x(ss);
2682  for(int i = 0; i < ss; ++i)
2683  x[i] = 0;
2684  x[p] = 1;
2685 
2686  int c = hexByteStrToInt(&(u.c_str()[p * 2]));
2687 
2688  std::string user = "";
2689 
2690  for(int l = 0; l < len; ++l)
2691  {
2692  p = (p + hexByteStrToInt(&(s.c_str()[p * 2]))) % ss;
2693  while(x[p])
2694  p = (p + 1) % ss;
2695  x[p] = 1;
2696  n = hexByteStrToInt(&(s.c_str()[p * 2]));
2697  user.append(1, (hexByteStrToInt(&(u.c_str()[p * 2])) - c - n + ss * 4) % ss);
2698  c = hexByteStrToInt(&(u.c_str()[p * 2]));
2699  }
2700 
2701  return user;
2702 } // end dejumble()
2703 
2704 //==============================================================================
2707 std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>
2709 {
2710  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2711  if(userIndex < Users_.size())
2712  return Users_[userIndex].permissions_;
2713 
2714  // else return all user inactive map
2715  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> retErrorMap;
2716  retErrorMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE;
2717  return retErrorMap;
2718 } // end getPermissionsForUser()
2719 
2724 // {
2725 // if(uid == ACCOUNT_REMOTE)
2726 // {
2727 // auto it = RemoteSessions_.find(remoteSessionID);
2728 // if(it == RemoteSessions_.end())
2729 // {
2730 // // else return all user inactive map
2731 // std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> retErrorMap;
2732 // retErrorMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE;
2733 // return retErrorMap;
2734 // }
2735 // return it->second.second.permissions_;
2736 // }
2737 
2750 //==============================================================================
2753 WebUsers::permissionLevel_t WebUsers::getPermissionLevelForGroup(
2754  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2755  const std::string& groupName)
2756 {
2757  auto it = permissionMap.find(groupName);
2758  if(it == permissionMap.end())
2759  {
2760  __COUT__ << "Group name '" << groupName
2761  << "' not found - assuming inactive user in this group." << __E__;
2762  return WebUsers::PERMISSION_LEVEL_INACTIVE;
2763  }
2764  return it->second;
2765 } // end getPermissionLevelForGroup()
2766 
2767 //==============================================================================
2768 bool WebUsers::isInactiveForGroup(
2769  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2770  const std::string& groupName)
2771 {
2772  return getPermissionLevelForGroup(permissionMap, groupName) ==
2773  WebUsers::PERMISSION_LEVEL_INACTIVE;
2774 }
2775 
2776 //==============================================================================
2777 bool WebUsers::isAdminForGroup(
2778  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2779  const std::string& groupName)
2780 {
2781  return getPermissionLevelForGroup(permissionMap, groupName) ==
2783 }
2784 
2785 //==============================================================================
2788 std::string WebUsers::getTooltipFilename(const std::string& username,
2789  const std::string& srcFile,
2790  const std::string& srcFunc,
2791  const std::string& srcId)
2792 {
2793  std::string filename = (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/";
2794 
2795  // make tooltip directory if not there
2796  // note: this is static so WebUsers constructor has not necessarily been called
2797  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
2798  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
2799  mkdir(filename.c_str(), 0755);
2800 
2801  for(const char& c : username)
2802  if( // only keep alpha numeric
2803  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2804  filename += c;
2805  filename += "/";
2806 
2807  // make username tooltip directory if not there
2808  mkdir(filename.c_str(), 0755);
2809 
2810  for(const char& c : srcFile)
2811  if( // only keep alpha numeric
2812  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2813  filename += c;
2814  filename += "_";
2815  for(const char& c : srcFunc)
2816  if( // only keep alpha numeric
2817  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2818  filename += c;
2819  filename += "_";
2820  for(const char& c : srcId)
2821  if( // only keep alpha numeric
2822  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2823  filename += c;
2824  filename += ".tip";
2825  //__COUT__ << "filename " << filename << __E__;
2826  return filename;
2827 }
2828 
2829 std::string ots::WebUsers::getUserEmailFromFingerprint(const std::string& fingerprint)
2830 {
2831  __COUT__ << "Checking if user fingerprint " << fingerprint << " is in memory database"
2832  << __E__;
2833  if(certFingerprints_.count(fingerprint))
2834  {
2835  return certFingerprints_[fingerprint];
2836  }
2837 
2838  __COUT__ << "Going to read credential database " << WEB_LOGIN_CERTDATA_PATH << __E__;
2839  std::ifstream f(WEB_LOGIN_CERTDATA_PATH);
2840  bool open = false;
2841  while(f)
2842  {
2843  open = true;
2844  std::string email;
2845  std::string fp;
2846  f >> email >> fp;
2847  if(fp != "NOKEY" && fp != "")
2848  {
2849  __COUT__ << "Adding user " << email << " to list with fingerprint " << fp
2850  << __E__;
2851  certFingerprints_[fp] = email;
2852  }
2853  }
2854  if(open)
2855  {
2856  f.close();
2857  remove(WEB_LOGIN_CERTDATA_PATH.c_str());
2858  }
2859 
2860  __COUT__ << "Checking again if fingerprint is in memory database" << __E__;
2861  if(certFingerprints_.count(fingerprint))
2862  {
2863  return certFingerprints_[fingerprint];
2864  }
2865 
2866  __COUT__ << "Could not match fingerprint, returning null email" << __E__;
2867  return "";
2868 } // end getUserEmailFromFingerprint()
2869 
2870 //==============================================================================
2873 void WebUsers::tooltipSetNeverShowForUsername(const std::string& username,
2874  HttpXmlDocument* /*xmldoc*/,
2875  const std::string& srcFile,
2876  const std::string& srcFunc,
2877  const std::string& srcId,
2878  bool doNeverShow,
2879  bool temporarySilence)
2880 {
2881  std::string filename;
2882  bool isForAll = (srcFile == "ALL" && srcFunc == "ALL" && srcId == "ALL");
2883 
2884  if(isForAll)
2885  {
2886  __COUT__ << "Disabling ALL tooltips for user '" << username << "' is now set to "
2887  << doNeverShow << " (temporarySilence=" << temporarySilence << ")"
2888  << __E__;
2889  filename = getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", "");
2890  }
2891  else
2892  {
2893  filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2894  __COUT__ << "Setting tooltip never show for user '" << username << "' to "
2895  << doNeverShow << " (temporarySilence=" << temporarySilence << ")"
2896  << __E__;
2897  }
2898  if(TTEST(1))
2899  {
2900  __COUTTV__(doNeverShow);
2901  __COUTTV__(temporarySilence);
2902  __COUTTV__(srcId);
2903  __COUTTV__(srcFunc);
2904  __COUTTV__(srcFile);
2905  __COUTTV__(filename);
2906  }
2907 
2908  FILE* fp = fopen(filename.c_str(), "w");
2909  if(fp)
2910  { // file exists, so do NOT show tooltip
2911  if(temporarySilence)
2912  fprintf(fp,
2913  "%ld",
2914  time(0) + 7 /*days*/ * 24 /*hours*/ * 60 * 60); // mute for a week
2915  else if(!isForAll && doNeverShow && username == WebUsers::DEFAULT_ADMIN_USERNAME)
2916  {
2917  // admin could be shared account, so max out at 30 days
2918  fprintf(fp, "%ld", time(0) + 30 /*days*/ * 24 /*hours*/ * 60 * 60);
2919 
2920  __COUT__ << "User '" << username
2921  << "' may be a shared account, so max silence duration for tooltips "
2922  "is 30 days. Silencing now."
2923  << __E__;
2924  }
2925  else
2926  fputc(doNeverShow ? '1' : '0', fp);
2927  fclose(fp);
2928  }
2929  else // default to show tool tip
2930  __COUT_ERR__ << "Big problem with tooltips! File not accessible: " << filename
2931  << __E__;
2932 } // end tooltipSetNeverShowForUsername()
2933 
2934 //==============================================================================
2941 void WebUsers::tooltipCheckForUsername(const std::string& username,
2942  HttpXmlDocument* xmldoc,
2943  const std::string& srcFile,
2944  const std::string& srcFunc,
2945  const std::string& srcId)
2946 {
2947  if(srcId == "ALWAYS")
2948  {
2949  // ALWAYS shows tool tip
2950  xmldoc->addTextElementToData("ShowTooltip", "1");
2951  return;
2952  }
2953 
2954  // if the silence file exists, silence all tooltips
2955  std::string silencefilename =
2956  getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", "");
2957 
2958  if(TTEST(1))
2959  {
2960  __COUTTV__(username);
2961  __COUTTV__(srcId);
2962  __COUTTV__(srcFunc);
2963  __COUTTV__(srcFile);
2964  __COUTTV__(silencefilename);
2965  }
2966 
2967  FILE* silencefp = fopen(silencefilename.c_str(), "r");
2968  if(silencefp != NULL)
2969  {
2970  time_t val;
2971  char line[100];
2972  fgets(line, 100, silencefp);
2973  sscanf(line, "%ld", &val);
2974  fclose(silencefp);
2975  if(val == 1)
2976  {
2977  xmldoc->addTextElementToData("ShowTooltip", "0");
2978  // tooltipSetNeverShowForUsername(username, xmldoc, srcFile, srcFunc, srcId, true, true);
2979  return;
2980  }
2981  }
2982 
2983  std::string filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2984  FILE* fp = fopen(filename.c_str(), "r");
2985  if(fp)
2986  { // file exists, so do NOT show tooltip
2987  time_t val;
2988  char line[100];
2989  fgets(line, 100, fp);
2990  sscanf(line, "%ld", &val);
2991  fclose(fp);
2992 
2993  __COUT__ << "tooltip value read = " << val << " vs time(0)=" << time(0) << __E__;
2994 
2995  // if first line in file is a 1 then do not show
2996  // else show if current time is greater than value
2997  xmldoc->addTextElementToData("ShowTooltip",
2998  val == 1 ? "0" : (time(0) > val ? "1" : "0"));
2999  }
3000  else // default to show tool tip
3001  {
3002  xmldoc->addTextElementToData("ShowTooltip", "1");
3003  }
3004 
3005 } // end tooltipCheckForUsername();
3006 
3007 //==============================================================================
3009 void WebUsers::resetAllUserTooltips(const std::string& userNeedle)
3010 {
3011  std::system(
3012  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/" + userNeedle)
3013  .c_str());
3014  __COUT__ << "Successfully reset Tooltips for user " << userNeedle << __E__;
3015 } // end of resetAllUserTooltips()
3016 
3017 //==============================================================================
3020 void WebUsers::silenceAllUserTooltips(const std::string& username)
3021 {
3022  std::string silencefilename = getTooltipFilename(
3023  username, SILENCE_ALL_TOOLTIPS_FILENAME, "", ""); // srcFile, srcFunc, srcId);
3024 
3025  __COUTV__(silencefilename);
3026  FILE* silencefp = fopen(silencefilename.c_str(), "w");
3027  if(silencefp != NULL)
3028  {
3029  fputs("1", silencefp);
3030  fclose(silencefp);
3031  }
3032 
3033 } // end of silenceAllUserTooltips()
3034 
3035 //==============================================================================
3058  uint64_t uid,
3059  HttpXmlDocument* xmldoc,
3060  bool includeAccounts,
3061  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>
3062  permissionMap /* = {} */)
3063 {
3064  if(permissionMap.size() == 0)
3065  {
3066  __COUTT__ << "Getting local permissions for user " << uid << __E__;
3067  permissionMap = getPermissionsForUser(uid);
3068  }
3069 
3070  __COUTTV__(StringMacros::mapToString(permissionMap));
3071  if(isInactiveForGroup(permissionMap))
3072  return; // not an active user
3073 
3074  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3075  __COUT__ << "Gettings settings for user: " << Users_[userIndex].username_ << __E__;
3076 
3077  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
3078  (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ +
3079  "." + (std::string)USERS_PREFERENCES_FILETYPE;
3080 
3081  HttpXmlDocument prefXml;
3082 
3083  __COUT__ << "Preferences file: " << fn << __E__;
3084 
3085  if(!prefXml.loadXmlDocument(fn))
3086  {
3087  __COUT__ << "Preferences are defaults." << __E__;
3088  // insert defaults, no pref document found
3089  xmldoc->addTextElementToData(PREF_XML_BGCOLOR_FIELD, PREF_XML_BGCOLOR_DEFAULT);
3090  xmldoc->addTextElementToData(PREF_XML_DBCOLOR_FIELD, PREF_XML_DBCOLOR_DEFAULT);
3091  xmldoc->addTextElementToData(PREF_XML_WINCOLOR_FIELD, PREF_XML_WINCOLOR_DEFAULT);
3092  xmldoc->addTextElementToData(PREF_XML_LAYOUT_FIELD, PREF_XML_LAYOUT_DEFAULT);
3093  }
3094  else
3095  {
3096  __COUT__ << "Saved Preferences found." << __E__;
3097  xmldoc->copyDataChildren(prefXml);
3098  }
3099 
3100  // add settings if super user
3101  if(includeAccounts && isAdminForGroup(permissionMap))
3102  {
3103  __COUT__ << "Admin on our hands" << __E__;
3104 
3105  xmldoc->addTextElementToData(PREF_XML_ACCOUNTS_FIELD, "");
3106 
3107  if(Users_.size() == 0)
3108  {
3109  __COUT__ << "Missing users? Attempting to load database" << __E__;
3110  loadDatabases();
3111  }
3112 
3113  // get all accounts
3114  for(uint64_t i = 0; i < Users_.size(); ++i)
3115  {
3116  xmldoc->addTextElementToParent(
3117  "username", Users_[i].username_, PREF_XML_ACCOUNTS_FIELD);
3118  xmldoc->addTextElementToParent(
3119  "display_name", Users_[i].displayName_, PREF_XML_ACCOUNTS_FIELD);
3120 
3121  if(Users_[i].email_.size() > i)
3122  {
3123  xmldoc->addTextElementToParent(
3124  "useremail", Users_[i].email_, PREF_XML_ACCOUNTS_FIELD);
3125  }
3126  else
3127  {
3128  xmldoc->addTextElementToParent("useremail", "", PREF_XML_ACCOUNTS_FIELD);
3129  }
3130 
3131  xmldoc->addTextElementToParent(
3132  "permissions",
3133  StringMacros::mapToString(Users_[i].permissions_),
3134  PREF_XML_ACCOUNTS_FIELD);
3135 
3136  xmldoc->addTextElementToParent(
3137  "nac", Users_[i].getNewAccountCode().c_str(), PREF_XML_ACCOUNTS_FIELD);
3138  }
3139  }
3140 
3141  // get system layout defaults
3142  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH +
3143  (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
3144  (std::string)USERS_PREFERENCES_FILETYPE;
3145  if(!prefXml.loadXmlDocument(fn))
3146  {
3147  __COUT__ << "System Preferences are defaults." << __E__;
3148  // insert defaults, no pref document found
3149  xmldoc->addTextElementToData(PREF_XML_SYSLAYOUT_FIELD,
3150  PREF_XML_SYSLAYOUT_DEFAULT);
3151  }
3152  else
3153  {
3154  __COUT__ << "Saved System Preferences found." << __E__;
3155  xmldoc->copyDataChildren(prefXml);
3156  }
3157 
3158  __COUTV__(StringMacros::mapToString(permissionMap));
3159 
3160  // add permissions value
3161  xmldoc->addTextElementToData(PREF_XML_PERMISSIONS_FIELD,
3162  StringMacros::mapToString(permissionMap));
3163 
3164  // add user with lock
3165  xmldoc->addTextElementToData(PREF_XML_USERLOCK_FIELD, usersUsernameWithLock_);
3166 
3167  // add user name
3168  xmldoc->addTextElementToData(PREF_XML_USERNAME_FIELD, getUsersUsername(uid));
3169 
3170  // add ots owner name
3171  xmldoc->addTextElementToData(PREF_XML_OTS_OWNER_FIELD, WebUsers::OTS_OWNER);
3172 
3173  if(WebUsers::remoteLoginVerificationEnabled_) // add remote ots ip:port
3174  xmldoc->addTextElementToData("ots_remote_address",
3175  remoteLoginVerificationIP_ + ":" +
3176  std::to_string(remoteLoginVerificationPort_));
3177 
3178 } // end insertSettingsForUser()
3179 
3180 //==============================================================================
3184  const std::string& preferenceName,
3185  const std::string& preferenceValue)
3186 {
3187  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3188  //__COUT__ << "setGenericPreference for user: " << UsersUsernameVector[userIndex] <<
3189  //__E__;
3190 
3191  // force alpha-numeric with dash/underscore
3192  std::string safePreferenceName = "";
3193  for(const auto& c : preferenceName)
3194  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
3195  (c >= '-' || c <= '_'))
3196  safePreferenceName += c;
3197 
3198  std::string dir = (std::string)WEB_LOGIN_DB_PATH +
3199  (std::string)USERS_PREFERENCES_PATH + "generic_" +
3200  safePreferenceName + "/";
3201 
3202  // attempt to make directory (just in case)
3203  mkdir(dir.c_str(), 0755);
3204 
3205  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." +
3206  (std::string)USERS_PREFERENCES_FILETYPE;
3207 
3208  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
3209 
3210  FILE* fp = fopen((dir + fn).c_str(), "w");
3211  if(fp)
3212  {
3213  fprintf(fp, "%s", preferenceValue.c_str());
3214  fclose(fp);
3215  }
3216  else
3217  __COUT_ERR__ << "Preferences file could not be opened for writing!" << __E__;
3218 } // end setGenericPreference()
3219 
3220 //==============================================================================
3224 std::string WebUsers::getGenericPreference(uint64_t uid,
3225  const std::string& preferenceName,
3226  HttpXmlDocument* xmldoc) const
3227 {
3228  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3229  //__COUT__ << "getGenericPreference for user: " << UsersUsernameVector[userIndex] <<
3230  //__E__;
3231 
3232  // force alpha-numeric with dash/underscore
3233  std::string safePreferenceName = "";
3234  for(const auto& c : preferenceName)
3235  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
3236  (c >= '-' || c <= '_'))
3237  safePreferenceName += c;
3238 
3239  std::string dir = (std::string)WEB_LOGIN_DB_PATH +
3240  (std::string)USERS_PREFERENCES_PATH + "generic_" +
3241  safePreferenceName + "/";
3242 
3243  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." +
3244  (std::string)USERS_PREFERENCES_FILETYPE;
3245 
3246  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
3247 
3248  // read from preferences file
3249  FILE* fp = fopen((dir + fn).c_str(), "r");
3250  if(fp)
3251  {
3252  fseek(fp, 0, SEEK_END);
3253  const long size = ftell(fp);
3254  char* line = new char
3255  [size +
3256  1]; // std::string with line.reserve(size + 1) does not work for unknown reason
3257  rewind(fp);
3258  fread(line, 1, size, fp);
3259  line[size] = '\0';
3260  fclose(fp);
3261  std::string retVal(line, size);
3262  delete[] line;
3263 
3264  __COUT__ << "Read value (sz = " << retVal.size() << ") " << retVal << __E__;
3265  if(xmldoc)
3266  xmldoc->addTextElementToData(safePreferenceName, retVal);
3267  return retVal;
3268  }
3269  else
3270  __COUT__ << "Using default value." << __E__;
3271 
3272  // default preference is empty string
3273  if(xmldoc)
3274  xmldoc->addTextElementToData(safePreferenceName, "");
3275  return "";
3276 } // end getGenericPreference()
3277 
3278 //==============================================================================
3281  const std::string& bgcolor,
3282  const std::string& dbcolor,
3283  const std::string& wincolor,
3284  const std::string& layout,
3285  const std::string& syslayout,
3286  const std::string& aliaslayout,
3287  const std::string& sysaliaslayout)
3288 {
3289  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3290  getPermissionsForUser(uid);
3291  if(isInactiveForGroup(permissionMap))
3292  return; // not an active user
3293 
3294  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3295  __COUT__ << "Changing settings for user: " << Users_[userIndex].username_ << __E__;
3296 
3297  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
3298  (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ +
3299  "." + (std::string)USERS_PREFERENCES_FILETYPE;
3300 
3301  __COUT__ << "Preferences file: " << fn << __E__;
3302 
3303  HttpXmlDocument prefXml;
3304  prefXml.addTextElementToData(PREF_XML_BGCOLOR_FIELD, bgcolor);
3305  prefXml.addTextElementToData(PREF_XML_DBCOLOR_FIELD, dbcolor);
3306  prefXml.addTextElementToData(PREF_XML_WINCOLOR_FIELD, wincolor);
3307  prefXml.addTextElementToData(PREF_XML_LAYOUT_FIELD, layout);
3308  prefXml.addTextElementToData(PREF_XML_ALIAS_LAYOUT_FIELD, aliaslayout);
3309 
3310  prefXml.saveXmlDocument(fn);
3311 
3312  // if admin privilieges set system default layouts
3313  if(!isAdminForGroup(permissionMap))
3314  return; // not admin
3315 
3316  // set system layout defaults
3317  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH +
3318  (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
3319  (std::string)USERS_PREFERENCES_FILETYPE;
3320 
3321  HttpXmlDocument sysPrefXml;
3322  sysPrefXml.addTextElementToData(PREF_XML_SYSLAYOUT_FIELD, syslayout);
3323  sysPrefXml.addTextElementToData(PREF_XML_SYSALIAS_LAYOUT_FIELD, sysaliaslayout);
3324 
3325  sysPrefXml.saveXmlDocument(fn);
3326 } // end changeSettingsForUser()
3327 
3328 //==============================================================================
3333 bool WebUsers::setUserWithLock(uint64_t actingUid, bool lock, const std::string& username)
3334 {
3335  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3336  getPermissionsForUser(actingUid);
3337 
3338  std::string actingUser = getUsersUsername(actingUid);
3339  bool isUserActive = isUsernameActive(username);
3340 
3341  __COUTV__(actingUser);
3342  __COUT__ << "Permissions: " << StringMacros::mapToString(permissionMap) << __E__;
3343  __COUTV__(usersUsernameWithLock_);
3344  __COUTV__(lock);
3345  __COUTV__(username);
3346  __COUTV__(isUserActive);
3347 
3348  if(lock &&
3349  (isUserActive || !WebUsers::CareAboutCookieCodes_)) // lock and currently active
3350  {
3351  if(!WebUsers::CareAboutCookieCodes_ && !isUserActive &&
3352  username !=
3353  DEFAULT_ADMIN_USERNAME) // enforce wiz mode & no security only use admin account
3354  {
3355  __COUT_ERR__
3356  << "User '" << actingUser
3357  << "' tried to lock for a user other than admin in wiz mode. Not allowed."
3358  << __E__;
3359  return false;
3360  }
3361  else if(!isAdminForGroup(permissionMap) &&
3362  actingUser != username) // enforce normal mode admin privleges
3363  {
3364  __COUT_ERR__ << "A non-admin user '" << actingUser
3365  << "' tried to lock for a user other than self. Not allowed."
3366  << __E__;
3367  return false;
3368  }
3369  usersUsernameWithLock_ = username;
3370  }
3371  else if(!lock && usersUsernameWithLock_ == username) // unlock
3372  usersUsernameWithLock_ = "";
3373  else
3374  {
3375  if(!isUserActive)
3376  __COUT_INFO__ << "User '" << username << "' is inactive so not giving lock."
3377  << __E__;
3378  else
3379  __COUT_ERR__ << "Failed to lock for user '" << username << ".'" << __E__;
3380  return false;
3381  }
3382 
3383  __COUT_INFO__ << "User '" << username << "' has locked out the system!" << __E__;
3384 
3385  // save username with lock
3386  {
3387  std::string securityFileName = USER_WITH_LOCK_FILE;
3388  FILE* fp = fopen(securityFileName.c_str(), "w");
3389  if(!fp)
3390  {
3391  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE
3392  << " not found. Ignoring." << __E__;
3393  }
3394  else
3395  {
3396  fprintf(fp, "%s", usersUsernameWithLock_.c_str());
3397  fclose(fp);
3398  }
3399  }
3400  return true;
3401 } // end setUserWithLock()
3402 
3403 //==============================================================================
3405 void WebUsers::modifyAccountSettings(uint64_t actingUid,
3406  uint8_t cmd_type,
3407  const std::string& username,
3408  const std::string& displayname,
3409  const std::string& email,
3410  const std::string& permissions)
3411 {
3412  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3413  getPermissionsForUser(actingUid);
3414  if(!isAdminForGroup(permissionMap))
3415  {
3416  // not an admin
3417  __SS__ << "Only admins can modify user settings." << __E__;
3418  __SS_THROW__;
3419  }
3420 
3421  uint64_t i = searchUsersDatabaseForUserId(actingUid);
3422  uint64_t modi = searchUsersDatabaseForUsername(username);
3423  if(modi == 0)
3424  {
3425  if(i == 0)
3426  {
3427  __COUT_INFO__ << "Admin password reset." << __E__;
3428  Users_[modi].setModifier(Users_[i].username_);
3429  Users_[modi].salt_ = "";
3430  Users_[modi].loginFailureCount_ = 0;
3431  saveDatabaseToFile(DB_USERS);
3432  return;
3433  }
3434  __SS__ << "Cannot modify first user" << __E__;
3435  __SS_THROW__;
3436  }
3437 
3438  if(username.length() < USERNAME_LENGTH)
3439  {
3440  __SS__ << "Invalid Username, must be length " << USERNAME_LENGTH << __E__;
3441  __SS_THROW__;
3442  }
3443  if(displayname.length() < DISPLAY_NAME_LENGTH)
3444  {
3445  __SS__ << "Invalid Display Name; must be length " << DISPLAY_NAME_LENGTH << __E__;
3446  __SS_THROW__;
3447  }
3448 
3449  __COUT__ << "Input Permissions: " << permissions << __E__;
3450  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> newPermissionsMap;
3451 
3452  switch(cmd_type)
3453  {
3454  case MOD_TYPE_UPDATE:
3455 
3456  __COUT__ << "MOD_TYPE_UPDATE " << username << " := " << permissions << __E__;
3457 
3458  if(modi == NOT_FOUND_IN_DATABASE)
3459  {
3460  __SS__ << "User not found!? Should not happen." << __E__;
3461  __SS_THROW__;
3462  }
3463 
3464  // enforce unique Display Name
3465  {
3466  for(uint64_t i = 0; i < Users_.size(); ++i)
3467  if(i == modi)
3468  continue; // skip target user
3469  else if(Users_[i].displayName_ == displayname)
3470  {
3471  __SS__ << "Display Name '" << displayname
3472  << "' already exists! Please choose a unique display name."
3473  << __E__;
3474  __SS_THROW__;
3475  }
3476  }
3477 
3478  Users_[modi].displayName_ = displayname;
3479  Users_[modi].email_ = email;
3480 
3481  { // handle permissions
3482  StringMacros::getMapFromString(permissions, newPermissionsMap);
3483  bool wasInactive = isInactiveForGroup(Users_[modi].permissions_);
3484 
3485  // fix permissions_ if missing default user group
3486  if(newPermissionsMap.size() == 0) // default to inactive
3487  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] =
3488  std::atoi(permissions.c_str());
3489  else if(newPermissionsMap.size() == 1 &&
3490  newPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) ==
3491  newPermissionsMap.end())
3492  {
3493  if(newPermissionsMap.begin()->first == "")
3494  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] =
3495  newPermissionsMap.begin()->second;
3496  else // if a user group attempted, copy settings for default group
3497  {
3498  newPermissionsMap[WebUsers::DEFAULT_USER_GROUP] =
3499  newPermissionsMap.begin()->second;
3500  Users_[modi].permissions_ = newPermissionsMap;
3501  }
3502  }
3503  else
3504  Users_[modi].permissions_ = newPermissionsMap;
3505 
3506  // If account was inactive and re-activating, then reset fail count and
3507  // password. Note: this is the account unlock mechanism.
3508  if(wasInactive && // was inactive
3509  !isInactiveForGroup(Users_[modi].permissions_)) // and re-activating
3510  {
3511  __COUT__ << "Reactivating " << username << __E__;
3512  Users_[modi].loginFailureCount_ = 0;
3513  Users_[modi].salt_ = "";
3514  }
3515  } // end permissions handling
3516 
3517  // save information about modifier
3518  {
3519  if(i == NOT_FOUND_IN_DATABASE)
3520  {
3521  __SS__ << "Master User not found!? Should not happen." << __E__;
3522  __SS_THROW__;
3523  }
3524  Users_[modi].setModifier(Users_[i].username_);
3525  }
3526  break;
3527  case MOD_TYPE_ADD:
3528  // Note: username, userId, AND displayName must be unique!
3529 
3530  __COUT__ << "MOD_TYPE_ADD " << username << " - " << displayname << __E__;
3531 
3532  createNewAccount(username, displayname, email);
3533  // save information about modifier
3534  {
3535  if(i == NOT_FOUND_IN_DATABASE)
3536  {
3537  __SS__ << "Master User not found!? Should not happen." << __E__;
3538  __SS_THROW__;
3539  }
3540  Users_.back().setModifier(Users_[i].username_);
3541  }
3542 
3543  if(permissions.size()) // apply permissions
3544  {
3546  actingUid, MOD_TYPE_UPDATE, username, displayname, email, permissions);
3547  return;
3548  }
3549  break;
3550  case MOD_TYPE_DELETE:
3551  __COUT__ << "MOD_TYPE_DELETE " << username << " - " << displayname << __E__;
3552  deleteAccount(username, displayname);
3553  break;
3554  default:
3555  __SS__ << "Undefined command - do nothing " << username << __E__;
3556  __SS_THROW__;
3557  }
3558 
3559  saveDatabaseToFile(DB_USERS);
3560  loadSecuritySelection(); //give opportunity to dynamically modifiy IP access settings or security settings
3561 } // end modifyAccountSettings()
3562 
3563 //==============================================================================
3567 {
3568  std::set<unsigned int> activeUserIndices;
3569  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
3570  activeUserIndices.emplace(
3571  searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
3572  //also add remote session users
3573  for(const auto& sessionPair : RemoteSessions_)
3574  activeUserIndices.emplace(
3575  searchUsersDatabaseForUserId(sessionPair.second.userId_));
3576  return activeUserIndices.size();
3577 } // end getActiveUserCount()
3578 
3579 //==============================================================================
3583 {
3584  std::set<unsigned int> activeUserIndices;
3585  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
3586  activeUserIndices.emplace(
3587  searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
3588  //also add remote session users
3589  for(const auto& sessionPair : RemoteSessions_)
3590  activeUserIndices.emplace(
3591  searchUsersDatabaseForUserId(sessionPair.second.userId_));
3592 
3593  std::string activeUsersString = "";
3594  bool addComma = false;
3595  for(const auto& i : activeUserIndices)
3596  {
3597  if(i >= Users_.size())
3598  continue; // skip not found
3599 
3600  if(addComma)
3601  activeUsersString += ",";
3602  else
3603  addComma = true;
3604 
3605  activeUsersString += Users_[i].displayName_;
3606  }
3607  if(activeUserIndices.size() == 0 &&
3609  WebUsers::SECURITY_TYPE_NONE) // assume only admin is active
3610  activeUsersString += WebUsers::DEFAULT_ADMIN_DISPLAY_NAME;
3611 
3612  __COUTVS__(20, activeUsersString);
3613  return activeUsersString;
3614 } // end getActiveUserDisplayNamesString()
3615 
3616 //==============================================================================
3620 {
3621  std::set<unsigned int> activeUserIndices;
3622  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
3623  activeUserIndices.emplace(
3624  searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
3625  //also add remote session users
3626  for(const auto& sessionPair : RemoteSessions_)
3627  activeUserIndices.emplace(
3628  searchUsersDatabaseForUserId(sessionPair.second.userId_));
3629 
3630  std::string activeUsersString = "";
3631  bool addComma = false;
3632  for(const auto& i : activeUserIndices)
3633  {
3634  if(i >= Users_.size())
3635  continue; // skip not found
3636 
3637  if(addComma)
3638  activeUsersString += ",";
3639  else
3640  addComma = true;
3641 
3642  activeUsersString += Users_[i].username_;
3643  }
3644  if(activeUserIndices.size() == 0 &&
3646  WebUsers::SECURITY_TYPE_NONE) // assume only admin is active
3647  activeUsersString += WebUsers::DEFAULT_ADMIN_USERNAME;
3648 
3649  __COUTVS__(20, activeUsersString);
3650  return activeUsersString;
3651 } // end getActiveUsernamesString()
3652 
3653 //==============================================================================
3657 {
3658  uint64_t uid = searchUsersDatabaseForUsername(DEFAULT_ADMIN_USERNAME);
3659  return uid;
3660 }
3661 
3662 //==============================================================================
3665 void WebUsers::loadUserWithLock()
3666 {
3667  char username[300] = ""; // assume username is less than 300 chars
3668 
3669  std::string securityFileName = USER_WITH_LOCK_FILE;
3670  FILE* fp = fopen(securityFileName.c_str(), "r");
3671  if(!fp)
3672  {
3673  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE
3674  << " not found. Defaulting to admin lock." << __E__;
3675 
3676  // default to admin lock if no file exists
3677  sprintf(username, "%s", DEFAULT_ADMIN_USERNAME.c_str());
3678  }
3679  else
3680  {
3681  fgets(username, 300, fp);
3682  username[299] =
3683  '\0'; // likely does nothing, but make sure there is closure on string
3684  fclose(fp);
3685  }
3686 
3687  // attempt to set lock
3688  __COUT__ << "Attempting to load username with lock: " << username << __E__;
3689 
3690  if(strlen(username) == 0)
3691  {
3692  __COUT_INFO__ << "Loaded state for user-with-lock is unlocked." << __E__;
3693  return;
3694  }
3695 
3696  uint64_t i = searchUsersDatabaseForUsername(username);
3697  if(i == NOT_FOUND_IN_DATABASE)
3698  {
3699  __COUT_INFO__ << "username " << username << " not found in database. Ignoring."
3700  << __E__;
3701  return;
3702  }
3703  __COUT__ << "Setting lock" << __E__;
3704  setUserWithLock(Users_[i].userId_, true, username);
3705 } // end loadUserWithLock()
3706 
3707 //==============================================================================
3710 void WebUsers::addSystemMessage(const std::string& targetUsersCSV,
3711  const std::string& message)
3712 {
3713  addSystemMessage(targetUsersCSV, "" /*subject*/, message, false /*doEmail*/);
3714 } // end addSystemMessage()
3715 
3716 //==============================================================================
3719 void WebUsers::addSystemMessage(const std::string& targetUsersCSV,
3720  const std::string& subject,
3721  const std::string& message,
3722  bool doEmail)
3723 {
3724  std::vector<std::string> targetUsers;
3725  StringMacros::getVectorFromString(targetUsersCSV, targetUsers);
3726  addSystemMessage(targetUsers, subject, message, doEmail);
3727 } // end addSystemMessage()
3728 
3729 //==============================================================================
3733 void WebUsers::addSystemMessage(const std::vector<std::string>& targetUsers,
3734  const std::string& subject,
3735  const std::string& message,
3736  bool doEmail)
3737 {
3738  systemMessageCleanup();
3739 
3740  std::string fullMessage = StringMacros::encodeURIComponent(
3741  (subject == "" ? "" : (subject + ": ")) + message);
3742 
3743  // Note: do not printout message, because if it was a Console trigger, it will fire repeatedly
3744  std::cout << __COUT_HDR_FL__ << "addSystemMessage() fullMessage: " << fullMessage
3745  << __E__;
3746  __COUTV__(StringMacros::vectorToString(targetUsers));
3747 
3748  std::set<std::string> targetEmails;
3749 
3750  for(const auto& targetUser : targetUsers)
3751  {
3752  // reject if message is a repeat for user
3753 
3754  if(targetUser == "" || (targetUser != "*" && targetUser.size() < 3))
3755  {
3756  __COUT__ << "Illegal username '" << targetUser << "'" << __E__;
3757  continue;
3758  }
3759  __COUTTV__(targetUser);
3760  // target user might * or <group name>:<permission threshold> or just <username>
3761 
3762  // do special ALL email handling
3763  if(doEmail && targetUser == "*")
3764  {
3765  // for each user, look up email and append
3766  for(const auto& user : Users_)
3767  {
3768  if(user.email_.size() > 5 && // few simple valid email checks
3769  user.email_.find('@') != std::string::npos &&
3770  user.email_.find('.') != std::string::npos)
3771  {
3772  __COUT__ << "Adding " << user.displayName_
3773  << " email: " << user.email_ << __E__;
3774  targetEmails.emplace(user.email_);
3775  }
3776  } // end add every user loop
3777 
3778  } // end all email handling
3779  else if(targetUser.find(':') != std::string::npos)
3780  {
3781  // special group handling.. convert to individual users
3782  __COUT__ << "Treating as group email target: " << targetUser << __E__;
3783 
3784  std::map<std::string, WebUsers::permissionLevel_t> targetGroupMap;
3785  StringMacros::getMapFromString( // re-factor membership string to map
3786  targetUser,
3787  targetGroupMap);
3788 
3789  __COUTV__(StringMacros::mapToString(targetGroupMap));
3790 
3791  if(targetGroupMap.size() == 1)
3792  {
3793  // add users to targetUsers, so the loop will catch them at end
3794 
3795  // loop through all users, and add users that match group spec
3796  for(const auto& user : Users_)
3797  {
3798  WebUsers::permissionLevel_t userLevel =
3799  getPermissionLevelForGroup(getPermissionsForUser(user.userId_),
3800  targetGroupMap.begin()->first);
3801 
3802  __COUTV__(
3804  __COUTV__((int)userLevel);
3805  __COUTV__(targetGroupMap.begin()->first);
3806 
3807  if(userLevel != WebUsers::PERMISSION_LEVEL_INACTIVE &&
3808  userLevel >= targetGroupMap.begin()->second &&
3809  user.email_.size() > 5 && // few simple valid email checks
3810  user.email_.find('@') != std::string::npos &&
3811  user.email_.find('.') != std::string::npos)
3812  {
3813  if(doEmail)
3814  {
3815  targetEmails.emplace(user.email_);
3816  __COUT__ << "Adding " << user.displayName_
3817  << " email: " << user.email_ << __E__;
3818  }
3819  addSystemMessageToMap(user.displayName_, fullMessage);
3820  }
3821  }
3822  }
3823  else
3824  __COUT__ << "target Group Map from '" << targetUser << "' is empty."
3825  << __E__;
3826 
3827  continue; // proceed with user loop, do not add group target message
3828  }
3829 
3830  // at this point add to system message map (similar to group individual add, but might be '*')
3831 
3832  addSystemMessageToMap(targetUser, fullMessage);
3833 
3834  if(doEmail) // find user for email
3835  {
3836  for(const auto& user : Users_)
3837  {
3838  if(user.displayName_ == targetUser)
3839  {
3840  if(user.email_.size() > 5 && // few simple valid email checks
3841  user.email_.find('@') != std::string::npos &&
3842  user.email_.find('.') != std::string::npos)
3843  {
3844  targetEmails.emplace(user.email_);
3845  __COUT__ << "Adding " << user.displayName_
3846  << " email: " << user.email_ << __E__;
3847  }
3848  break; // user found, exit search loop
3849  }
3850  } // end user search loop
3851  }
3852 
3853  } // end target user message add loop
3854 
3855  __COUTV__(targetEmails.size());
3856 
3857  if(doEmail && targetEmails.size())
3858  {
3859  __COUTV__(StringMacros::setToString(targetEmails));
3860 
3861  std::string toList = "";
3862  bool addComma = false;
3863  for(const auto& email : targetEmails)
3864  {
3865  if(addComma)
3866  toList += ", ";
3867  else
3868  addComma = true;
3869  toList += email;
3870  }
3871 
3872  std::string filename = (std::string)WEB_LOGIN_DB_PATH +
3873  (std::string)USERS_DB_PATH + "/.tmp_email.txt";
3874  FILE* fp = fopen(filename.c_str(), "w");
3875  if(!fp)
3876  {
3877  __SS__ << "Could not open email file: " << filename << __E__;
3878  __SS_THROW__;
3879  }
3880 
3881  fprintf(fp,
3882  "From: %s\n",
3883  (WebUsers::OTS_OWNER == ""
3884  ? "ots"
3886  .c_str());
3887  fprintf(fp, "To: %s\n", toList.c_str());
3888  fprintf(fp, "Subject: %s\n", subject.c_str());
3889  fprintf(fp, "Content-Type: text/html\n");
3890  fprintf(fp, "\n<html><pre>%s</pre></html>", message.c_str());
3891  fclose(fp);
3892 
3893  StringMacros::exec(("sendmail \"" + toList + "\" < " + filename).c_str());
3894  }
3895  else if(doEmail)
3896  __COUT_WARN__ << "Do email was attempted, but no target users had email "
3897  "addresses specified!"
3898  << __E__;
3899 
3900 } // end addSystemMessage()
3901 
3902 //==============================================================================
3906 void WebUsers::addSystemMessageToMap(const std::string& targetUser,
3907  const std::string& fullMessage)
3908 {
3909  // lock for remainder of scope
3910  std::lock_guard<std::mutex> lock(systemMessageLock_);
3911 
3912  __COUTT__ << "Before number of users with system messages: " << systemMessages_.size()
3913  << ", first user has "
3914  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3915  << " messages." << __E__;
3916 
3917  auto it = systemMessages_.find(targetUser);
3918 
3919  // check for repeat messages
3920  if(it != systemMessages_.end() && it->second.size() &&
3921  it->second[it->second.size() - 1].message_ == fullMessage)
3922  return; // skip user add
3923 
3924  if(it == systemMessages_.end()) // create first message for target user
3925  {
3926  systemMessages_.emplace(
3927  std::pair<std::string /*toUser*/, std::vector<SystemMessage>>(
3928  targetUser, std::vector<SystemMessage>({SystemMessage(fullMessage)})));
3929  __COUTT__ << targetUser << " Current System Messages count = " << 1 << __E__;
3930  }
3931  else // add message
3932  {
3933  __COUTT__ << __E__;
3934  it->second.push_back(SystemMessage(fullMessage));
3935  __COUTT__ << it->first << " Current System Messages count = " << it->second.size()
3936  << __E__;
3937  }
3938 
3939  __COUTT__ << "After number of users with system messages: " << systemMessages_.size()
3940  << ", first user has "
3941  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3942  << " messages." << __E__;
3943 } // end addSystemMessageToMap
3944 
3945 //==============================================================================
3948 std::pair<std::string, time_t> WebUsers::getLastSystemMessage()
3949 {
3950  // lock for remainder of scope
3951  std::lock_guard<std::mutex> lock(systemMessageLock_);
3952 
3953  __COUTT__ << "GetLast number of users with system messages: "
3954  << systemMessages_.size() << ", first user has "
3955  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3956  << " messages." << __E__;
3957 
3958  auto it = systemMessages_.find("*");
3959  if(it == systemMessages_.end() || it->second.size() == 0)
3960  return std::make_pair("", 0);
3961 
3962  return std::make_pair(it->second.back().message_, it->second.back().creationTime_);
3963 } // end getLastSystemMessage()
3964 
3965 //==============================================================================
3970 {
3971  std::string retStr = "";
3972 
3973  // lock for remainder of scope
3974  std::lock_guard<std::mutex> lock(systemMessageLock_);
3975 
3976  for(auto& userSysMessages : systemMessages_)
3977  {
3978  for(auto& userSysMessage : userSysMessages.second)
3979  {
3980  if(userSysMessage.deliveredRemote_)
3981  continue; //skip messages already deivered remote
3982 
3983  if(retStr.size())
3984  retStr += '|';
3985  retStr += userSysMessages.first; //target display name
3986  retStr += "|" + std::to_string(userSysMessage.creationTime_);
3987  retStr += "|" + userSysMessage.message_;
3988  userSysMessage.deliveredRemote_ = true;
3989  }
3990  }
3991  return retStr;
3992 } //end getAllSystemMessages()
3993 
3994 //==============================================================================
4001 std::string WebUsers::getSystemMessage(const std::string& targetUser)
4002 {
4003  __COUTS__(20) << "Current System Messages: " << targetUser << __E__;
4004  std::string retStr = "";
4005  {
4006  int cnt = 0;
4007  char tmp[32];
4008 
4009  // lock for remainder of scope
4010  std::lock_guard<std::mutex> lock(systemMessageLock_);
4011 
4012  __COUTS__(20) << "Number of users with system messages: "
4013  << systemMessages_.size() << __E__;
4014 
4015  //do broadcast * messages 1st because the web client will hide all messages before a repeat, so make sure to show user messages
4016  auto it = systemMessages_.find("*");
4017  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
4018  {
4019  // deliver "*" system message
4020  if(cnt)
4021  retStr += "|";
4022  sprintf(tmp, "%lu", it->second[i].creationTime_);
4023  retStr += std::string(tmp) + "|" + it->second[i].message_;
4024 
4025  ++cnt;
4026  }
4027 
4028  //do user messages 2nd because the web client will hide all messages before a repeat, so make sure to show user messages
4029  __COUTVS__(20, targetUser);
4030  it = systemMessages_.find(targetUser);
4031  if(TTEST(20))
4032  {
4033  for(auto systemMessagePair : systemMessages_)
4034  __COUTS__(20) << systemMessagePair.first << " "
4035  << systemMessagePair.second.size() << " "
4036  << (systemMessagePair.second.size()
4037  ? systemMessagePair.second[0].message_
4038  : "")
4039  << __E__;
4040  }
4041  if(it != systemMessages_.end())
4042  {
4043  __COUTS__(20) << "Message count: " << it->second.size() << ", Last Message: "
4044  << (it->second.size() ? it->second.back().message_ : "")
4045  << __E__;
4046  }
4047 
4048  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
4049  {
4050  // deliver user specific system message
4051  if(cnt)
4052  retStr += "|";
4053  sprintf(tmp, "%lu", it->second[i].creationTime_);
4054  retStr += std::string(tmp) + "|" + it->second[i].message_;
4055 
4056  // Track first delivery time for time-based cleanup (allows multiple tabs/devices to receive)
4057  if(!it->second[i].delivered_)
4058  it->second[i].firstDeliveryTime_ = time(0);
4059  it->second[i].delivered_ = true;
4060  ++cnt;
4061  }
4062  } //end mutex scope
4063 
4064  __COUTS__(20) << "retStr: " << retStr << __E__;
4065 
4066  systemMessageCleanup(); //NOTE: also locks mutex within!
4067  return retStr;
4068 } // end getSystemMessage()
4069 
4070 //==============================================================================
4074 void WebUsers::systemMessageCleanup()
4075 {
4076  // lock for remainder of scope
4077  std::lock_guard<std::mutex> lock(systemMessageLock_);
4078 
4079  const time_t now = time(0);
4080 
4081  __COUTT__ << "Before cleanup number of users with system messages: "
4082  << systemMessages_.size() << ", first user has "
4083  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
4084  << " messages." << __E__;
4085  for(auto& userMessagesPair : systemMessages_)
4086  {
4087  for(uint64_t i = 0; i < userMessagesPair.second.size(); ++i)
4088  {
4089  bool shouldRemove = false;
4090 
4091  if(userMessagesPair.first == "*")
4092  {
4093  // Wildcard messages: remove after SYS_CLEANUP_WILDCARD_TIME (300 seconds)
4094  if(userMessagesPair.second[i].creationTime_ + SYS_CLEANUP_WILDCARD_TIME <
4095  now)
4096  shouldRemove = true;
4097  }
4098  else
4099  {
4100  // User-specific messages: remove after SYS_CLEANUP_USER_MESSAGE_TIME (15 seconds)
4101  // from first delivery to allow multiple browser tabs/devices to receive the same message.
4102  // The client side should suppress duplicate messages with the same text and timestamp.
4103  if(userMessagesPair.second[i].delivered_ &&
4104  userMessagesPair.second[i].firstDeliveryTime_ +
4106  now)
4107  shouldRemove = true;
4108  // Also remove if message is too old (fallback cleanup using wildcard time)
4109  else if(userMessagesPair.second[i].creationTime_ +
4111  now)
4112  shouldRemove = true;
4113  }
4114 
4115  if(shouldRemove)
4116  {
4117  __COUTT__ << userMessagesPair.first
4118  << " at time: " << userMessagesPair.second[i].creationTime_
4119  << " system messages: " << userMessagesPair.second.size()
4120  << __E__;
4121 
4122  // remove
4123  userMessagesPair.second.erase(userMessagesPair.second.begin() + i);
4124  --i; // rewind
4125  }
4126  } //end cleanup loop by message
4127 
4128  __COUTT__ << "User '" << userMessagesPair.first
4129  << "' remaining system messages: " << userMessagesPair.second.size()
4130  << __E__;
4131  } //end cleanup loop by user
4132  __COUTT__ << "After cleanup number of users with system messages: "
4133  << systemMessages_.size() << ", first user has "
4134  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
4135  << " messages." << __E__;
4136 } // end systemMessageCleanup()
4137 
4138 //==============================================================================
4140 const std::string& WebUsers::getSecurity() { return securityType_; }
4141 //==============================================================================
4143 void WebUsers::loadSecuritySelection()
4144 {
4145  std::string securityFileName = SECURITY_FILE_NAME;
4146  FILE* fp = fopen(securityFileName.c_str(), "r");
4147  char line[100] = "";
4148  if(fp)
4149  fgets(line, 100, fp);
4150  unsigned int i = 0;
4151 
4152  // find first character that is not alphabetic
4153  while(i < strlen(line) && line[i] >= 'A' && line[i] <= 'z')
4154  ++i;
4155  line[i] = '\0'; // end string at first illegal character
4156 
4157  if(strcmp(line, SECURITY_TYPE_NONE.c_str()) == 0 ||
4158  strcmp(line, SECURITY_TYPE_DIGEST_ACCESS.c_str()) == 0)
4159  securityType_ = line;
4160  else
4161  securityType_ = SECURITY_TYPE_DEFAULT;
4162 
4163  __COUT__ << "The current security type is " << securityType_ << __E__;
4164 
4165  if(fp)
4166  fclose(fp);
4167 
4168  if(securityType_ == SECURITY_TYPE_NONE)
4169  CareAboutCookieCodes_ = false;
4170  else
4171  CareAboutCookieCodes_ = true;
4172 
4173  __COUT__ << "CareAboutCookieCodes_: " << CareAboutCookieCodes_ << __E__;
4174 
4175  loadIPAddressSecurity();
4176 
4177 } // end loadSecuritySelection()
4178 
4179 //==============================================================================
4181 void WebUsers::loadIPAddressSecurity()
4182 {
4183  ipAccessAccept_.clear();
4184  ipAccessReject_.clear();
4185  ipAccessBlacklist_.clear();
4186 
4187  FILE* fp = fopen((IP_ACCEPT_FILE).c_str(), "r");
4188  char line[300];
4189  size_t len;
4190 
4191  if(fp)
4192  {
4193  while(fgets(line, 300, fp))
4194  {
4195  len = strlen(line);
4196  // remove new line
4197  if(len > 2 && line[len - 1] == '\n')
4198  line[len - 1] = '\0';
4199  ipAccessAccept_.emplace(line);
4200  // if(StringMacros::wildCardMatch(ip, line))
4201  // return true; // found in accept file, so accept
4202  }
4203 
4204  fclose(fp);
4205  }
4206  __COUTV__(ipAccessAccept_.size());
4207 
4208  fp = fopen((IP_REJECT_FILE).c_str(), "r");
4209  if(fp)
4210  {
4211  while(fgets(line, 300, fp))
4212  {
4213  len = strlen(line);
4214  // remove new line
4215  if(len > 2 && line[len - 1] == '\n')
4216  line[len - 1] = '\0';
4217  ipAccessReject_.emplace(line);
4218  // if(StringMacros::wildCardMatch(ip, line))
4219  // return false; // found in reject file, so reject
4220  }
4221 
4222  fclose(fp);
4223  }
4224  __COUTV__(ipAccessReject_.size());
4225 
4226  fp = fopen((IP_BLACKLIST_FILE).c_str(), "r");
4227  if(fp)
4228  {
4229  while(fgets(line, 300, fp))
4230  {
4231  len = strlen(line);
4232  // remove new line
4233  if(len > 2 && line[len - 1] == '\n')
4234  line[len - 1] = '\0';
4235  ipAccessBlacklist_.emplace(line);
4236  // if(StringMacros::wildCardMatch(ip, line))
4237  // return false; // found in blacklist file, so reject
4238  }
4239 
4240  fclose(fp);
4241  }
4242  __COUTV__(ipAccessBlacklist_.size());
4243 } // end loadIPAddressSecurity()
4244 
4245 //==============================================================================
4246 void WebUsers::NACDisplayThread(const std::string& nac, const std::string& user)
4247 {
4248  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
4250  // thread notifying the user about the admin new account code
4251  // notify for 10 seconds (e.g.)
4252 
4253  // child thread
4254  int i = 0;
4255  for(; i < 5; ++i)
4256  {
4257  std::this_thread::sleep_for(std::chrono::seconds(2));
4258  __COUT__
4259  << "\n******************************************************************** "
4260  << __E__;
4261  __COUT__
4262  << "\n******************************************************************** "
4263  << __E__;
4264  __COUT__ << "\n\nNew account code = " << nac << " for user: " << user << "\n"
4265  << __E__;
4266  __COUT__
4267  << "\n******************************************************************** "
4268  << __E__;
4269  __COUT__
4270  << "\n******************************************************************** "
4271  << __E__;
4272  }
4273 } // end NACDisplayThread()
4274 
4275 //==============================================================================
4276 void WebUsers::deleteUserData()
4277 {
4278  __COUT__ << "$$$$$$$$$$$$$$ Deleting ALL service user data... $$$$$$$$$$$$" << __E__;
4279 
4280  // delete Login data
4281  std::system(
4282  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH + "/*").c_str());
4283  std::system(
4284  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH + "/*").c_str());
4285  std::system(
4286  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH + "/*")
4287  .c_str());
4288  std::system(
4289  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH + "/*")
4290  .c_str());
4291  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH).c_str());
4292 
4293  std::string serviceDataPath = __ENV__("SERVICE_DATA_PATH");
4294  // delete macro maker folders
4295  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroData/").c_str());
4296  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroHistory/").c_str());
4297  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroExport/").c_str());
4298 
4299  // delete console folders
4300  std::system(
4301  ("rm -rf " + std::string(serviceDataPath) + "/ConsolePreferences/").c_str());
4302 
4303  // delete code editor folders
4304  std::system(("rm -rf " + std::string(serviceDataPath) + "/CodeEditorData/").c_str());
4305 
4306  // delete wizard folders
4307  std::system(("rm -rf " + std::string(serviceDataPath) + "/OtsWizardData/").c_str());
4308 
4309  // delete progress bar folders
4310  std::system(("rm -rf " + std::string(serviceDataPath) + "/ProgressBarData/").c_str());
4311 
4312  // delete The Supervisor run folders
4313  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunNumber/").c_str());
4314  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunControlData/").c_str());
4315 
4316  // delete Visualizer folders
4317  std::system(("rm -rf " + std::string(serviceDataPath) + "/VisualizerData/").c_str());
4318 
4319  // DO NOT delete active groups file (this messes with people's configuration world,
4320  // which is not expected when "resetting user info") std::system(("rm -rf " +
4321  // std::string(serviceDataPath) + "/ActiveTableGroups.cfg").c_str());
4322 
4323  // delete Logbook folders
4324  std::system(("rm -rf " + std::string(__ENV__("LOGBOOK_DATA_PATH")) + "/").c_str());
4325 
4326  __COUT__ << "$$$$$$$$$$$$$$ Successfully deleted ALL service user data $$$$$$$$$$$$"
4327  << __E__;
4328 } // end deleteUserData()
void copyDataChildren(HttpXmlDocument &document)
void removeDataElement(unsigned int dataChildIndex=0)
default to first child
bool loadXmlDocument(const std::string &filePath)
unsigned int getChildrenCount(xercesc::DOMElement *parent=0)
void addSystemMessage(const std::string &targetUsersCSV, const std::string &message)
Definition: WebUsers.cc:3710
const std::string & getSecurity(void)
WebUsers::getSecurity.
Definition: WebUsers.cc:4140
std::string getGenericPreference(uint64_t uid, const std::string &preferenceName, HttpXmlDocument *xmldoc=0) const
Definition: WebUsers.cc:3224
bool setUserWithLock(uint64_t actingUid, bool lock, const std::string &username)
Definition: WebUsers.cc:3333
static bool checkRequestAccess(cgicc::Cgicc &cgi, std::ostringstream *out, HttpXmlDocument *xmldoc, WebUsers::RequestUserInfo &userInfo, bool isWizardMode=false, const std::string &wizardModeSequence="")
Definition: WebUsers.cc:270
static void silenceAllUserTooltips(const std::string &username)
Definition: WebUsers.cc:3020
void insertSettingsForUser(uint64_t uid, HttpXmlDocument *xmldoc, bool includeAccounts=false, std::map< std::string, WebUsers::permissionLevel_t > permissionMap={})
if empty, fetches local permissions; if provided, overrides with given permissions (e....
Definition: WebUsers.cc:3057
size_t getActiveUserCount(void)
Definition: WebUsers.cc:3566
std::string getActiveUsernamesString(void)
All active usernames.
Definition: WebUsers.cc:3619
std::map< std::string, WebUsers::permissionLevel_t > getPermissionsForUser(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2708
uint64_t attemptActiveSession(const std::string &uuid, std::string &jumbledUser, const std::string &jumbledPw, std::string &newAccountCode, const std::string &ip)
Definition: WebUsers.cc:1182
void setGenericPreference(uint64_t uid, const std::string &preferenceName, const std::string &preferenceValue)
Definition: WebUsers.cc:3183
std::string getAllSystemMessages(void)
Definition: WebUsers.cc:3969
void cleanupExpiredEntries(std::vector< std::string > *loggedOutUsernames=0)
Definition: WebUsers.cc:2448
uint64_t isCookieCodeActiveForLogin(const std::string &uuid, std::string &cookieCode, std::string &username)
Definition: WebUsers.cc:1997
std::string createNewLoginSession(const std::string &uuid, const std::string &ip)
Definition: WebUsers.cc:2569
void createNewAccount(const std::string &username, const std::string &displayName, const std::string &email)
Definition: WebUsers.cc:1079
void modifyAccountSettings(uint64_t actingUid, uint8_t cmd_type, const std::string &username, const std::string &displayname, const std::string &email, const std::string &permissions)
WebUsers::modifyAccountSettings.
Definition: WebUsers.cc:3405
int remoteLoginVerificationPort_
Port of remote Gateway to be used for login verification.
Definition: WebUsers.h:662
bool isUsernameActive(const std::string &username) const
Definition: WebUsers.cc:1753
bool isUserIdActive(uint64_t uid) const
Definition: WebUsers.cc:1764
void saveActiveSessions(void)
Definition: WebUsers.cc:416
std::string getActiveUserDisplayNamesString(void)
All active display names.
Definition: WebUsers.cc:3582
static std::atomic< bool > remoteLoginVerificationEnabled_
true if this supervisor is under control of a remote supervisor
Definition: WebUsers.h:660
uint64_t getAdminUserID(void)
Definition: WebUsers.cc:3656
@ SYS_CLEANUP_WILDCARD_TIME
300 seconds
Definition: WebUsers.h:192
@ SYS_CLEANUP_USER_MESSAGE_TIME
15 seconds - allows multiple browser tabs/devices to receive the same message
Definition: WebUsers.h:193
std::string getUsersUsername(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2177
static void initializeRequestUserInfo(cgicc::Cgicc &cgi, WebUsers::RequestUserInfo &userInfo)
used by gateway and other supervisors to verify requests consistently
Definition: WebUsers.cc:249
bool checkIpAccess(const std::string &ip)
Definition: WebUsers.cc:2089
bool xmlRequestOnGateway(cgicc::Cgicc &cgi, std::ostringstream *out, HttpXmlDocument *xmldoc, WebUsers::RequestUserInfo &userInfo)
Definition: WebUsers.cc:181
uint64_t cookieCodeLogout(const std::string &cookieCode, bool logoutOtherUserSessions, uint64_t *uid=0, const std::string &ip="0")
Definition: WebUsers.cc:2196
std::string getSystemMessage(const std::string &targetUser)
Definition: WebUsers.cc:4001
uint64_t getActiveSessionCountForUser(uint64_t uid)
Definition: WebUsers.cc:2054
static void resetAllUserTooltips(const std::string &userNeedle="*")
WebUsers::resetAllUserTooltips.
Definition: WebUsers.cc:3009
static void tooltipSetNeverShowForUsername(const std::string &username, HttpXmlDocument *xmldoc, const std::string &srcFile, const std::string &srcFunc, const std::string &srcId, bool doNeverShow, bool temporarySilence)
Definition: WebUsers.cc:2873
void cleanupExpiredRemoteEntries(void)
Definition: WebUsers.cc:2546
std::string getUsersDisplayName(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2167
void loadActiveSessions(void)
Definition: WebUsers.cc:456
std::pair< std::string, time_t > getLastSystemMessage(void)
Definition: WebUsers.cc:3948
uint64_t attemptActiveSessionWithCert(const std::string &uuid, std::string &jumbledEmail, std::string &cookieCode, std::string &username, const std::string &ip)
Definition: WebUsers.cc:1392
static const std::string OTS_OWNER
defined by environment variable, e.g. experiment name
Definition: WebUsers.h:71
static void tooltipCheckForUsername(const std::string &username, HttpXmlDocument *xmldoc, const std::string &srcFile, const std::string &srcFunc, const std::string &srcId)
Definition: WebUsers.cc:2941
std::string remoteGatewaySelfName_
IP of remote Gateway to be used for login verification.
Definition: WebUsers.h:661
bool cookieCodeIsActiveForRequest(std::string &cookieCode, std::map< std::string, WebUsers::permissionLevel_t > *userPermissions=0, uint64_t *uid=0, const std::string &ip="0", bool refresh=true, bool doNotGoRemote=false, std::string *userWithLock=0, uint64_t *userSessionIndex=0)
Definition: WebUsers.cc:2269
void changeSettingsForUser(uint64_t uid, const std::string &bgcolor, const std::string &dbcolor, const std::string &wincolor, const std::string &layout, const std::string &syslayout, const std::string &aliaslayout, const std::string &sysaliaslayout)
WebUsers::changeSettingsForUser.
Definition: WebUsers.cc:3280
@ PERMISSION_LEVEL_ADMIN
max permission level!
Definition: WebUsers.h:64
xercesc::DOMElement * addTextElementToParent(const std::string &childName, const std::string &childText, xercesc::DOMElement *parent)
Definition: XmlDocument.cc:244
void saveXmlDocument(const std::string &filePath)
Definition: XmlDocument.cc:473
defines used also by OtsConfigurationWizardSupervisor
void INIT_MF(const char *name)
static std::string getTimestampString(const std::string &linuxTimeInSeconds)
static void getVectorFromString(const std::string &inputString, std::vector< std::string > &listToReturn, const std::set< char > &delimiter={',', '|', '&'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'}, std::vector< char > *listOfDelimiters=0, bool decodeURIComponents=false)
static std::string exec(const char *cmd)
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static std::string vectorToString(const std::vector< T > &setToReturn, const std::string &delimeter=", ")
vectorToString ~
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static void getMapFromString(const std::string &inputString, std::map< S, T > &mapToReturn, const std::set< char > &pairPairDelimiter={',', '|', '&'}, const std::set< char > &nameValueDelimiter={'=', ':'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
getMapFromString ~
static bool wildCardMatch(const std::string &needle, const std::string &haystack, unsigned int *priorityIndex=0)
Definition: StringMacros.cc:30
static std::string decodeURIComponent(const std::string &data)
static std::string stackTrace(void)
uint64_t userSessionIndex_
can use session index to track a user's session on multiple devices/browsers
Definition: WebUsers.h:368
const WebUsers::permissionLevel_t & getGroupPermissionLevel()
Definition: WebUsers.h:285
bool setGroupPermissionLevels(const std::string &groupPermissionLevelsString)
end setGroupPermissionLevels()
Definition: WebUsers.h:262