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