WebOrbiton
v3.0.0.0

FlatlyPage

378 lines · 11.4 KB
  1. <?php
  2. class LoginTracker
  3. {
  4. private $loginsFile;
  5. ​
  6. public function __construct()
  7. {
  8. $this->loginsFile = DATA_DIR . '/private/logins.xml';
  9. $this->ensureLoginsFileExists();
  10. }
  11. ​
  12. private function ensureLoginsFileExists()
  13. {
  14. $privateDir = DATA_DIR . '/private';
  15. if (!is_dir($privateDir)) {
  16. mkdir($privateDir, 0755, true);
  17. }
  18. ​
  19. if (!file_exists($this->loginsFile)) {
  20. $xml = new DOMDocument('1.0', 'UTF-8');
  21. $xml->formatOutput = true;
  22. $root = $xml->createElement('logins');
  23. $xml->appendChild($root);
  24. $xml->save($this->loginsFile);
  25. chmod($this->loginsFile, 0600);
  26. }
  27. }
  28. ​
  29. private function getClientInfo()
  30. {
  31. return [
  32. 'ip' => $this->getClientIP(),
  33. 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
  34. 'language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'Unknown',
  35. 'platform' => $this->getPlatform(),
  36. 'browser' => $this->getBrowser()
  37. ];
  38. }
  39. ​
  40. public function getClientIP()
  41. {
  42. $ip = 'Unknown';
  43. ​
  44. if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
  45. $ip = $_SERVER['HTTP_CLIENT_IP'];
  46. } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  47. $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  48. $ip = trim($ips[0]);
  49. } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
  50. $ip = $_SERVER['REMOTE_ADDR'];
  51. }
  52. ​
  53. if (filter_var($ip, FILTER_VALIDATE_IP)) {
  54. return $ip;
  55. }
  56. ​
  57. return $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
  58. }
  59. ​
  60. private function getPlatform()
  61. {
  62. $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
  63. ​
  64. if (preg_match('/windows/i', $ua)) return 'Windows';
  65. if (preg_match('/macintosh|mac os x/i', $ua)) return 'macOS';
  66. if (preg_match('/linux/i', $ua)) return 'Linux';
  67. if (preg_match('/android/i', $ua)) return 'Android';
  68. if (preg_match('/iphone|ipad|ipod/i', $ua)) return 'iOS';
  69. ​
  70. return 'Unknown';
  71. }
  72. ​
  73. private function getBrowser()
  74. {
  75. $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
  76. ​
  77. if (preg_match('/edg/i', $ua)) return 'Edge';
  78. if (preg_match('/chrome/i', $ua)) return 'Chrome';
  79. if (preg_match('/firefox/i', $ua)) return 'Firefox';
  80. if (preg_match('/safari/i', $ua) && !preg_match('/chrome/i', $ua)) return 'Safari';
  81. if (preg_match('/opera|opr/i', $ua)) return 'Opera';
  82. ​
  83. return 'Unknown';
  84. }
  85. ​
  86. private function generateSessionToken()
  87. {
  88. return bin2hex(random_bytes(32));
  89. }
  90. ​
  91. public function recordLogin($username)
  92. {
  93. $xml = new DOMDocument('1.0', 'UTF-8');
  94. $xml->formatOutput = true;
  95. $xml->preserveWhiteSpace = false;
  96. ​
  97. if (!$xml->load($this->loginsFile)) {
  98. return false;
  99. }
  100. ​
  101. $root = $xml->documentElement;
  102. $clientInfo = $this->getClientInfo();
  103. $sessionToken = $this->generateSessionToken();
  104. ​
  105. $login = $xml->createElement('login');
  106. $login->setAttribute('id', uniqid('login_', true));
  107. $login->setAttribute('session_token', $sessionToken);
  108. ​
  109. $usernameNode = $xml->createElement('username');
  110. $usernameNode->appendChild($xml->createTextNode($username));
  111. $login->appendChild($usernameNode);
  112. ​
  113. $ipNode = $xml->createElement('ip');
  114. $ipNode->appendChild($xml->createTextNode($clientInfo['ip']));
  115. $login->appendChild($ipNode);
  116. ​
  117. $uaNode = $xml->createElement('user_agent');
  118. $uaNode->appendChild($xml->createCDATASection($clientInfo['user_agent']));
  119. $login->appendChild($uaNode);
  120. ​
  121. $langNode = $xml->createElement('language');
  122. $langNode->appendChild($xml->createTextNode($clientInfo['language']));
  123. $login->appendChild($langNode);
  124. ​
  125. $platformNode = $xml->createElement('platform');
  126. $platformNode->appendChild($xml->createTextNode($clientInfo['platform']));
  127. $login->appendChild($platformNode);
  128. ​
  129. $browserNode = $xml->createElement('browser');
  130. $browserNode->appendChild($xml->createTextNode($clientInfo['browser']));
  131. $login->appendChild($browserNode);
  132. ​
  133. $loginTimeNode = $xml->createElement('login_time');
  134. $loginTimeNode->appendChild($xml->createTextNode(date('Y-m-d H:i:s')));
  135. $login->appendChild($loginTimeNode);
  136. ​
  137. $lastActivityNode = $xml->createElement('last_activity');
  138. $lastActivityNode->appendChild($xml->createTextNode(date('Y-m-d H:i:s')));
  139. $login->appendChild($lastActivityNode);
  140. ​
  141. $statusNode = $xml->createElement('status');
  142. $statusNode->appendChild($xml->createTextNode('active'));
  143. $login->appendChild($statusNode);
  144. ​
  145. $root->appendChild($login);
  146. ​
  147. if ($xml->save($this->loginsFile)) {
  148. $_SESSION['login_token'] = $sessionToken;
  149. return true;
  150. }
  151. ​
  152. return false;
  153. }
  154. ​
  155. public function updateActivity()
  156. {
  157. if (!isset($_SESSION['login_token'])) {
  158. return false;
  159. }
  160. ​
  161. $xml = new DOMDocument('1.0', 'UTF-8');
  162. $xml->formatOutput = true;
  163. $xml->preserveWhiteSpace = false;
  164. ​
  165. if (!$xml->load($this->loginsFile)) {
  166. return false;
  167. }
  168. ​
  169. $xpath = new DOMXPath($xml);
  170. $sessionToken = $_SESSION['login_token'];
  171. ​
  172. $nodes = $xpath->query("//login[@session_token='$sessionToken']");
  173. ​
  174. if ($nodes->length > 0) {
  175. $loginNode = $nodes->item(0);
  176. ​
  177. $lastActivityNodes = $xpath->query('last_activity', $loginNode);
  178. if ($lastActivityNodes->length > 0) {
  179. $lastActivityNodes->item(0)->nodeValue = date('Y-m-d H:i:s');
  180. return $xml->save($this->loginsFile);
  181. }
  182. }
  183. ​
  184. return false;
  185. }
  186. ​
  187. public function getActiveLogins()
  188. {
  189. $xml = new DOMDocument('1.0', 'UTF-8');
  190. ​
  191. if (!$xml->load($this->loginsFile)) {
  192. return [];
  193. }
  194. ​
  195. $xpath = new DOMXPath($xml);
  196. $logins = [];
  197. ​
  198. $nodes = $xpath->query("//login[status='active']");
  199. ​
  200. foreach ($nodes as $node) {
  201. $loginId = $node->getAttribute('id');
  202. $sessionToken = $node->getAttribute('session_token');
  203. ​
  204. $logins[] = [
  205. 'id' => $loginId,
  206. 'session_token' => $sessionToken,
  207. 'username' => $xpath->query('username', $node)->item(0)->nodeValue,
  208. 'ip' => $xpath->query('ip', $node)->item(0)->nodeValue,
  209. 'user_agent' => $xpath->query('user_agent', $node)->item(0)->nodeValue,
  210. 'language' => $xpath->query('language', $node)->item(0)->nodeValue,
  211. 'platform' => $xpath->query('platform', $node)->item(0)->nodeValue,
  212. 'browser' => $xpath->query('browser', $node)->item(0)->nodeValue,
  213. 'login_time' => $xpath->query('login_time', $node)->item(0)->nodeValue,
  214. 'last_activity' => $xpath->query('last_activity', $node)->item(0)->nodeValue,
  215. 'is_current' => ($sessionToken === ($_SESSION['login_token'] ?? ''))
  216. ];
  217. }
  218. ​
  219. usort($logins, function ($a, $b) {
  220. return strtotime($b['login_time']) - strtotime($a['login_time']);
  221. });
  222. ​
  223. return $logins;
  224. }
  225. ​
  226. public function logoutSession($loginId)
  227. {
  228. $xml = new DOMDocument('1.0', 'UTF-8');
  229. $xml->formatOutput = true;
  230. $xml->preserveWhiteSpace = false;
  231. ​
  232. if (!$xml->load($this->loginsFile)) {
  233. return false;
  234. }
  235. ​
  236. $xpath = new DOMXPath($xml);
  237. $nodes = $xpath->query("//login[@id='$loginId']");
  238. ​
  239. if ($nodes->length > 0) {
  240. $loginNode = $nodes->item(0);
  241. ​
  242. $statusNodes = $xpath->query('status', $loginNode);
  243. if ($statusNodes->length > 0) {
  244. $statusNodes->item(0)->nodeValue = 'logged_out';
  245. }
  246. ​
  247. $logoutTimeNode = $xml->createElement('logout_time');
  248. $logoutTimeNode->appendChild($xml->createTextNode(date('Y-m-d H:i:s')));
  249. $loginNode->appendChild($logoutTimeNode);
  250. ​
  251. return $xml->save($this->loginsFile);
  252. }
  253. ​
  254. return false;
  255. }
  256. ​
  257. public function banIP($ip)
  258. {
  259. $bannedFile = DATA_DIR . '/private/banned_ips.json';
  260. ​
  261. $banned = [];
  262. if (file_exists($bannedFile)) {
  263. $banned = json_decode(file_get_contents($bannedFile), true) ?: [];
  264. }
  265. ​
  266. if (!in_array($ip, $banned)) {
  267. $banned[] = $ip;
  268. file_put_contents($bannedFile, json_encode($banned, JSON_PRETTY_PRINT));
  269. chmod($bannedFile, 0600);
  270. ​
  271. $this->logoutByIP($ip);
  272. ​
  273. return true;
  274. }
  275. ​
  276. return false;
  277. }
  278. ​
  279. public function unbanIP($ip)
  280. {
  281. $bannedFile = DATA_DIR . '/private/banned_ips.json';
  282. ​
  283. if (file_exists($bannedFile)) {
  284. $banned = json_decode(file_get_contents($bannedFile), true) ?: [];
  285. $banned = array_filter($banned, function ($bannedIp) use ($ip) {
  286. return $bannedIp !== $ip;
  287. });
  288. ​
  289. file_put_contents($bannedFile, json_encode(array_values($banned), JSON_PRETTY_PRINT));
  290. return true;
  291. }
  292. ​
  293. return false;
  294. }
  295. ​
  296. public function isIPBanned($ip)
  297. {
  298. $bannedFile = DATA_DIR . '/private/banned_ips.json';
  299. ​
  300. if (file_exists($bannedFile)) {
  301. $banned = json_decode(file_get_contents($bannedFile), true) ?: [];
  302. return in_array($ip, $banned);
  303. }
  304. ​
  305. return false;
  306. }
  307. ​
  308. public function getBannedIPs()
  309. {
  310. $bannedFile = DATA_DIR . '/private/banned_ips.json';
  311. ​
  312. if (file_exists($bannedFile)) {
  313. return json_decode(file_get_contents($bannedFile), true) ?: [];
  314. }
  315. ​
  316. return [];
  317. }
  318. ​
  319. private function logoutByIP($ip)
  320. {
  321. $xml = new DOMDocument('1.0', 'UTF-8');
  322. $xml->formatOutput = true;
  323. $xml->preserveWhiteSpace = false;
  324. ​
  325. if (!$xml->load($this->loginsFile)) {
  326. return false;
  327. }
  328. ​
  329. $xpath = new DOMXPath($xml);
  330. $nodes = $xpath->query("//login[ip='$ip' and status='active']");
  331. ​
  332. foreach ($nodes as $loginNode) {
  333. $statusNodes = $xpath->query('status', $loginNode);
  334. if ($statusNodes->length > 0) {
  335. $statusNodes->item(0)->nodeValue = 'banned';
  336. }
  337. ​
  338. $logoutTimeNode = $xml->createElement('logout_time');
  339. $logoutTimeNode->appendChild($xml->createTextNode(date('Y-m-d H:i:s')));
  340. $loginNode->appendChild($logoutTimeNode);
  341. }
  342. ​
  343. return $xml->save($this->loginsFile);
  344. }
  345. ​
  346. public function cleanOldSessions($daysOld = 30)
  347. {
  348. $xml = new DOMDocument('1.0', 'UTF-8');
  349. $xml->formatOutput = true;
  350. $xml->preserveWhiteSpace = false;
  351. ​
  352. if (!$xml->load($this->loginsFile)) {
  353. return false;
  354. }
  355. ​
  356. $xpath = new DOMXPath($xml);
  357. $cutoffDate = date('Y-m-d H:i:s', strtotime("-$daysOld days"));
  358. ​
  359. $nodes = $xpath->query("//login[status!='active']");
  360. $removed = 0;
  361. ​
  362. foreach ($nodes as $node) {
  363. $lastActivity = $xpath->query('last_activity', $node)->item(0)->nodeValue;
  364. ​
  365. if ($lastActivity < $cutoffDate) {
  366. $node->parentNode->removeChild($node);
  367. $removed++;
  368. }
  369. }
  370. ​
  371. if ($removed > 0) {
  372. return $xml->save($this->loginsFile);
  373. }
  374. ​
  375. return true;
  376. }
  377. }
  378. ​