PageRenderTime 28ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/user.lib.php

https://gitlab.com/paulmfoster/grotworx
PHP | 492 lines | 345 code | 70 blank | 77 comment | 39 complexity | b41c1a50d48cd2d1f66ce7e4bf6361e4 MD5 | raw file
  1. <?php
  2. if (!class_exists('database')) {
  3. include LIBDIR . 'database.lib.php';
  4. }
  5. class user
  6. {
  7. private const EVERYONE = 255;
  8. function __construct()
  9. {
  10. $filename = DATADIR . 'users.sq3';
  11. $already = file_exists($filename);
  12. $dsn = 'sqlite:' . $filename;
  13. $this->db = new database($dsn);
  14. if (!$already) {
  15. $this->make_tables();
  16. $this->sample_admin();
  17. }
  18. }
  19. function make_tables()
  20. {
  21. $sql = "CREATE TABLE user (id integer primary key autoincrement, login varchar(30), password varchar(255), name varchar(50) not null, email varchar(255) not null, nonce varchar(255) not null, level integer default 255)";
  22. $this->db->query($sql);
  23. $sql = "CREATE TABLE confirm (id integer primary key autoincrement, login varchar(30), password varchar(255), name varchar(50), email varchar(255), nonce varchar(255), level integer, ip varchar(15), link varchar(255), timestamp integer)";
  24. $this->db->query($sql);
  25. }
  26. function sample_admin()
  27. {
  28. $user = [
  29. 'login' => 'admin',
  30. 'password' => password_hash('password', PASSWORD_BCRYPT),
  31. 'name' => 'Administrator',
  32. 'email' => 'me@example.com',
  33. 'nonce' => md5('admin'),
  34. 'level' => 0
  35. ];
  36. $this->db->insert('user', $user);
  37. }
  38. /**
  39. * get_ip_address()
  40. *
  41. * Should obtain and return IP address under almost all
  42. * circumstances. Code is taken from stackoverflow.com, and tries to
  43. * compensate for proxies, etc.
  44. *
  45. */
  46. private function get_ip_address()
  47. {
  48. $server_parms = [
  49. 'HTTP_CLIENT_IP',
  50. 'HTTP_X_FORWARDED_FOR',
  51. 'HTTP_X_FORWARDED',
  52. 'HTTP_X_CLUSTER_CLIENT_IP',
  53. 'HTTP_FORWARDED_FOR',
  54. 'HTTP_FORWARDED',
  55. 'REMOTE_ADDR'
  56. ];
  57. foreach ($server_parms as $key) {
  58. if (array_key_exists($key, $_SERVER) === true) {
  59. foreach (explode(',', $_SERVER[$key]) as $ip) {
  60. $ip = trim($ip); // just to be safe
  61. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
  62. return $ip;
  63. }
  64. }
  65. }
  66. }
  67. }
  68. private function random_string($length = 32) {
  69. $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  70. $len = strlen($characters);
  71. $rs = '';
  72. for ($i = 0; $i < $length; $i++) {
  73. $rs .= $characters[rand(0, $len - 1)];
  74. }
  75. return $rs;
  76. }
  77. function register($post)
  78. {
  79. global $form;
  80. // did the user omit a required field?
  81. if (!$form->check_requireds($post)) {
  82. emsg('F', 'One or more required fields not provided');
  83. return FALSE;
  84. }
  85. // password and confirm must match
  86. if ($post['password'] !== $post['confirm']) {
  87. emsg('F', 'Password and confirmation do not match');
  88. return FALSE;
  89. }
  90. // hacker login?
  91. if ($post['login'] != preg_replace('%[^0-9A-Za-z]%', '', $content)) {
  92. emsg('F', 'Not-allowed characters in login');
  93. return FALSE;
  94. }
  95. // hacker name?
  96. if ($post['name'] != preg_replace('%[^\. ,\-0-9A-Za-z]%', '', $content)) {
  97. emsg('F', 'Not-allowed characters in user name');
  98. return FALSE;
  99. }
  100. // hacker email?
  101. if ($post['email'] != filter_var($content, FILTER_SANITIZE_EMAIL)) {
  102. emsg('F', 'Invalid email address');
  103. return FALSE;
  104. }
  105. // check for pre-existing identical login
  106. $sql = "SELECT * FROM user WHERE login = '{$post['login']}'";
  107. $pre_exist = $this->db->query($sql)->fetch();
  108. if ($pre_exist) {
  109. // user login already exists
  110. emsg('F', 'A user with this login already exists');
  111. return FALSE;
  112. }
  113. $save = array(
  114. 'login' => $post['login'],
  115. 'password' => password_hash($post['password'], PASSWORD_BCRYPT),
  116. 'name' => $post['name'],
  117. 'email' => $post['email'],
  118. 'nonce' => md5($post['login']),
  119. 'level' => 255,
  120. 'ip' => $this->get_ip_address(),
  121. 'link' => $this->random_string(32),
  122. 'timestamp' => time()
  123. );
  124. $save_prepped = $this->db->prepare('confirm', $save);
  125. $this->db->insert('confirm', $save_prepped);
  126. $message = <<<EOD
  127. In order to confirm your registration with our site you must copy
  128. the "token" below on the web page you came from.
  129. Token: {$save['link']}
  130. Thanks!
  131. EOD;
  132. $subject = 'Please CONFIRM your registration with our site';
  133. mail($post['email'], $subject, $message);
  134. return TRUE;
  135. }
  136. /**
  137. * confirm_registration()
  138. *
  139. * Confirms (or not) that a user is allowed to register, and does
  140. * so.
  141. *
  142. * @param string $get confirmation link
  143. *
  144. * @return boolean Either you're okay or you're not
  145. *
  146. */
  147. function confirm_registration($token)
  148. {
  149. // XSS
  150. $token = preg_replace('/[^A-Za-z0-9]/', '', $token);
  151. $now = time();
  152. $limit = $now - 3600;
  153. // best time to delete all the expired records
  154. $sql = "DELETE FROM confirm WHERE timestamp < $limit";
  155. $this->db->query($sql);
  156. // grab confirmation record
  157. $sql = "SELECT * FROM confirm WHERE link = '$token'";
  158. $rec = $this->db->query($sql)->fetch();
  159. // can't find the confirmation key
  160. if ($rec === FALSE) {
  161. emsg('F', 'No such confirmation token, or token expired');
  162. return FALSE;
  163. }
  164. $store = array(
  165. 'login' => $rec['login'],
  166. 'password' => $rec['password'],
  167. 'name' => $rec['name'],
  168. 'email' => $rec['email'],
  169. 'nonce' => $rec['nonce'],
  170. 'level' => $rec['level']
  171. );
  172. $save = $this->db->prepare('user', $store);
  173. $this->db->insert('user', $save);
  174. $sql = "DELETE FROM confirm WHERE link = '$token'";
  175. $this->db->query($sql);
  176. return TRUE;
  177. }
  178. function get_user_list()
  179. {
  180. $sql = "SELECT * FROM user ORDER BY name";
  181. return $this->db->query($sql)->fetch_all();
  182. }
  183. function get_user_by_nonce($nonce)
  184. {
  185. // XSS
  186. $nonce = preg_replace('/[^A-Za-z0-9]/', '', $nonce);
  187. $sql = "SELECT * FROM user WHERE nonce = '$nonce'";
  188. return $this->db->query($sql)->fetch();
  189. }
  190. function get_current_user()
  191. {
  192. if (isset($_SESSION['user'])) {
  193. $u = $this->get_user_by_nonce($_SESSION['user']);
  194. return $u;
  195. }
  196. return FALSE;
  197. }
  198. function get_user($id)
  199. {
  200. $id = filter_var($id, FILTER_SANITIZE_NUMBER_INT);
  201. $sql = "SELECT * FROM user WHERE id = $id";
  202. return $this->db->query($sql)->fetch();
  203. }
  204. function get_user_by_login($login)
  205. {
  206. $sql = "SELECT * FROM user WHERE login = '$login'";
  207. return $this->db->query($sql)->fetch();
  208. }
  209. function get_admin_users()
  210. {
  211. $sql = "SELECT * FROM user WHERE level = 0";
  212. return $this->db->query($sql)->fetch_all();
  213. }
  214. function get_non_admin_users()
  215. {
  216. $sql = "SELECT * FROM user WHERE level != 0";
  217. return $this->db->query($sql)->fetch_all();
  218. }
  219. /**
  220. * create_user()
  221. *
  222. * Does the heavy lifting of creating a user,
  223. * after we figure out if it's okay for him
  224. * to create that user.
  225. *
  226. * @param array $post The $_POST array
  227. *
  228. * @return boolean TRUE
  229. *
  230. */
  231. private function create_user($post)
  232. {
  233. // encrypt the password
  234. $post['password'] = password_hash($post['password'], PASSWORD_BCRYPT);
  235. $post['nonce'] = md5($post['login']);
  236. $add_array = $this->db->prepare('user', $post);
  237. $this->db->insert('user', $add_array);
  238. return TRUE;
  239. }
  240. function add_user($post)
  241. {
  242. // check for pre-existing identical login
  243. $sql = "SELECT * FROM user WHERE login = '{$post['login']}'";
  244. $pre_exist = $this->db->query($sql)->fetch();
  245. if ($pre_exist) {
  246. // user login already exists
  247. emsg('F', 'User login already exists');
  248. return FALSE;
  249. }
  250. // logged in?/determine level of user
  251. if (isset($_SESSION['user'])) {
  252. $sql = "SELECT level FROM user WHERE nonce = '{$_SESSION['user']}'";
  253. $level_rec = $this->db->query($sql)->fetch();
  254. $level = $level_rec['level'];
  255. }
  256. else {
  257. // signal value for "not logged in"
  258. $level = 1024;
  259. }
  260. if ($level == 0) {
  261. // admin, create any users
  262. $this->create_user($post);
  263. return TRUE;
  264. }
  265. elseif ($level == 255) {
  266. // regular user, can't create other users
  267. emsg('F', 'You are not authorized to create a new user.');
  268. return FALSE;
  269. }
  270. elseif ($level == 1024) {
  271. // not logged in
  272. if ($post['level'] != 255) {
  273. // can't create an admin user
  274. emsg('F', 'You are not authorized to create an admin user.');
  275. return FALSE;
  276. }
  277. // NOTE Theoretically, a not-logged-in user could
  278. // create numerous low level users
  279. $this->create_user($post);
  280. return TRUE;
  281. }
  282. }
  283. function update_user($post)
  284. {
  285. $rec = [
  286. 'name' => $post['name'],
  287. 'email' => $post['email']
  288. ];
  289. // check for password change
  290. if (!empty($post['password'])) {
  291. // does password === confirm?
  292. if ($post['password'] === $post['confirm']) {
  293. // encrypt password for storage
  294. $post['password'] = password_hash($post['password'], PASSWORD_BCRYPT);
  295. $rec['password'] = $post['password'];
  296. }
  297. else {
  298. emsg('F', "Confirm password doesn't match original");
  299. return FALSE;
  300. }
  301. }
  302. $user = $this->db->prepare('user', $post);
  303. $this->db->update('user', $user, "id = {$user['id']}");
  304. return TRUE;
  305. }
  306. function delete_user($userid)
  307. {
  308. $userid = filter_var($userid, FILTER_SANITIZE_NUMBER_INT);
  309. // id == 0 not a real user ID
  310. if ($userid == 0) {
  311. emsg('F', 'Cannot delete a non-existent user');
  312. return FALSE;
  313. }
  314. // all okay; delete user
  315. $sql = "DELETE FROM user WHERE id = $userid";
  316. $this->db->query($sql);
  317. return TRUE;
  318. }
  319. /**
  320. * Log in.
  321. *
  322. * Checks the login and password from the login screen.
  323. * Ensures the user in question exists, and that the
  324. * password entered matches the one on file.
  325. *
  326. * @param array $post The $_POST array
  327. *
  328. * @return boolean TRUE if user found, else FALSE
  329. *
  330. */
  331. function login($post)
  332. {
  333. $sql = "SELECT * FROM user WHERE login = '{$post['login']}'";
  334. $user = $this->db->query($sql)->fetch();
  335. if ($user) {
  336. $verified = password_verify($post['password'], $user['password']);
  337. if ($verified) {
  338. // this tells the system who's logged in
  339. $_SESSION['user'] = $user['nonce'];
  340. return TRUE;
  341. }
  342. else {
  343. // password didn't match
  344. return FALSE;
  345. }
  346. }
  347. else {
  348. // no such user
  349. return FALSE;
  350. }
  351. }
  352. /**
  353. * Limit access in a script.
  354. *
  355. * This function should appear in every controller which demands
  356. * some privilege in order to access that page.
  357. *
  358. * @param int $level The level of user needed to access the page.
  359. *
  360. * @return boolean TRUE if the user is qualified, else FALSE
  361. *
  362. */
  363. function access($level)
  364. {
  365. $level = filter_var($level, FILTER_SANITIZE_NUMBER_INT);
  366. if (isset($_SESSION['user'])) {
  367. // user is logged in
  368. $sql = "SELECT level FROM user WHERE nonce = '{$_SESSION['user']}'";
  369. $user = $this->db->query($sql)->fetch();
  370. if ($user === FALSE) {
  371. // nonce is not in the system,
  372. // so user was deleted,
  373. // or someone's attempting a hack
  374. emsg('F', 'User is anonymous or not in the system.');
  375. return FALSE;
  376. }
  377. if ($user['level'] > $level) {
  378. // user isn't qualified
  379. emsg('F', 'User not authorized.');
  380. return FALSE;
  381. }
  382. }
  383. elseif ($level < EVERYONE) {
  384. // user is not logged in or no user
  385. // access level is "below" the "everyone" threshold
  386. emsg('F', 'User not logged in or no user');
  387. return FALSE;
  388. }
  389. return TRUE;
  390. }
  391. function fetch_user_info()
  392. {
  393. if (isset($_SESSION['user'])) {
  394. $sql = "SELECT * FROM user WHERE nonce = '{$_SESSION['user']}'";
  395. $details = $this->db->query($sql)->fetch();
  396. return $details;
  397. }
  398. else {
  399. return FALSE;
  400. }
  401. }
  402. function is_admin()
  403. {
  404. if (isset($_SESSION['user'])) {
  405. $sql = "SELECT level FROM user WHERE nonce = '{$_SESSION['user']}'";
  406. $level_rec = $this->db->query($sql)->fetch();
  407. if ($level_rec['level'] == 0) {
  408. return TRUE;
  409. }
  410. }
  411. return FALSE;
  412. }
  413. };