Single-sign-on and self-serve account management. https://netsyms.biz/apps/accounthub
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

login.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /**
  3. * Authentication and account functions
  4. */
  5. use Base32\Base32;
  6. use OTPHP\TOTP;
  7. use LdapTools\LdapManager;
  8. use LdapTools\Connection\ADResponseCodes;
  9. ////////////////////////////////////////////////////////////////////////////////
  10. // Account handling //
  11. ////////////////////////////////////////////////////////////////////////////////
  12. /**
  13. * Add a user to the system. /!\ Assumes input is OK /!\
  14. * @param string $username Username, saved in lowercase.
  15. * @param string $password Password, will be hashed before saving.
  16. * @param string $realname User's real legal name
  17. * @param string $email User's email address.
  18. * @param string $phone1 Phone number #1
  19. * @param string $phone2 Phone number #2
  20. * @param string $type Account type
  21. * @return int The new user's ID number in the database.
  22. */
  23. function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "", $type) {
  24. global $database;
  25. $database->insert('accounts', [
  26. 'username' => strtolower($username),
  27. 'password' => (is_null($password) ? null : encryptPassword($password)),
  28. 'realname' => $realname,
  29. 'email' => $email,
  30. 'phone1' => $phone1,
  31. 'phone2' => $phone2,
  32. 'acctstatus' => 1,
  33. 'accttype' => $type
  34. ]);
  35. var_dump($database->error());
  36. return $database->id();
  37. }
  38. /**
  39. * Get where a user's account actually is.
  40. * @param string $username
  41. * @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
  42. */
  43. function account_location($username, $password) {
  44. global $database;
  45. $username = strtolower($username);
  46. $user_exists = user_exists($username);
  47. if (!$user_exists && !LDAP_ENABLED) {
  48. return false;
  49. }
  50. if ($user_exists) {
  51. $userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
  52. // if password empty, it's an LDAP user
  53. if (is_empty($userinfo['password']) && LDAP_ENABLED) {
  54. return "LDAP";
  55. } else if (is_empty($userinfo['password']) && !LDAP_ENABLED) {
  56. return "NONE";
  57. } else {
  58. return "LOCAL";
  59. }
  60. } else {
  61. if (user_exists_ldap($username, $password)) {
  62. return "LDAP_ONLY";
  63. } else {
  64. return "NONE";
  65. }
  66. }
  67. }
  68. /**
  69. * Checks the given credentials against the database.
  70. * @param string $username
  71. * @param string $password
  72. * @return boolean True if OK, else false
  73. */
  74. function authenticate_user($username, $password) {
  75. global $database;
  76. global $ldap_config;
  77. $username = strtolower($username);
  78. if (is_empty($username) || is_empty($password)) {
  79. return false;
  80. }
  81. $loc = account_location($username, $password);
  82. if ($loc == "NONE") {
  83. return false;
  84. } else if ($loc == "LOCAL") {
  85. $hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
  86. return (comparePassword($password, $hash));
  87. } else if ($loc == "LDAP") {
  88. return authenticate_user_ldap($username, $password) === TRUE;
  89. } else if ($loc == "LDAP_ONLY") {
  90. try {
  91. if (authenticate_user_ldap($username, $password) === TRUE) {
  92. $user = (new LdapManager($ldap_config))->getRepository('user')->findOneByUsername($username);
  93. //var_dump($user);
  94. adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
  95. return true;
  96. } else {
  97. return false;
  98. }
  99. } catch (Exception $e) {
  100. sendError("LDAP error: " . $e->getMessage());
  101. }
  102. } else {
  103. return false;
  104. }
  105. }
  106. /**
  107. * Check if a username exists in the local database.
  108. * @param String $username
  109. */
  110. function user_exists($username) {
  111. global $database;
  112. $username = strtolower($username);
  113. return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
  114. }
  115. /**
  116. * Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
  117. * CHANGE_PASSWORD, or ALERT_ON_ACCESS
  118. * @global $database $database
  119. * @param string $username
  120. * @return string
  121. */
  122. function get_account_status($username) {
  123. global $database;
  124. $username = strtolower($username);
  125. $loc = account_location($username);
  126. if ($loc == "LOCAL") {
  127. $statuscode = $database->select('accounts', [
  128. '[>]acctstatus' => [
  129. 'acctstatus' => 'statusid'
  130. ]
  131. ], [
  132. 'accounts.acctstatus',
  133. 'acctstatus.statuscode'
  134. ], [
  135. 'username' => $username,
  136. "LIMIT" => 1
  137. ]
  138. )[0]['statuscode'];
  139. return $statuscode;
  140. } else if ($loc == "LDAP") {
  141. // TODO: Read actual account status from AD servers
  142. return "NORMAL";
  143. } else {
  144. // account isn't setup properly
  145. return "LOCKED_OR_DISABLED";
  146. }
  147. }
  148. ////////////////////////////////////////////////////////////////////////////////
  149. // Login handling //
  150. ////////////////////////////////////////////////////////////////////////////////
  151. /**
  152. * Setup $_SESSION values to log in a user
  153. * @param string $username
  154. */
  155. function doLoginUser($username, $password) {
  156. global $database;
  157. $username = strtolower($username);
  158. $userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
  159. $_SESSION['username'] = $username;
  160. $_SESSION['uid'] = $userinfo['uid'];
  161. $_SESSION['email'] = $userinfo['email'];
  162. $_SESSION['realname'] = $userinfo['realname'];
  163. $_SESSION['password'] = $password; // needed for things like EWS
  164. $_SESSION['loggedin'] = true;
  165. }
  166. /**
  167. * Send an alert email to the system admin
  168. *
  169. * Used when an account with the status ALERT_ON_ACCESS logs in
  170. * @param String $username the account username
  171. */
  172. function sendLoginAlertEmail($username) {
  173. // TODO: add email code
  174. }
  175. function insertAuthLog($type, $uid = null) {
  176. global $database;
  177. $ip = "";
  178. if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
  179. $ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
  180. } else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
  181. $ip = $_SERVER["HTTP_CLIENT_IP"];
  182. } else if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
  183. $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
  184. } else if (isset($_SERVER["HTTP_X_FORWARDED"])) {
  185. $ip = $_SERVER["HTTP_X_FORWARDED"];
  186. } else if (isset($_SERVER["HTTP_FORWARDED_FOR"])) {
  187. $ip = $_SERVER["HTTP_FORWARDED_FOR"];
  188. } else if (isset($_SERVER["HTTP_FORWARDED"])) {
  189. $ip = $_SERVER["HTTP_FORWARDED"];
  190. } else if (isset($_SERVER["REMOTE_ADDR"])) {
  191. $ip = $_SERVER["REMOTE_ADDR"];
  192. } else {
  193. $ip = "NOT FOUND";
  194. }
  195. $database->insert("authlog", ['#logtime' => 'NOW()', 'logtype' => $type, 'uid' => $uid, 'ip' => $ip]);
  196. }
  197. function verifyReCaptcha($response) {
  198. try {
  199. $client = new GuzzleHttp\Client();
  200. $response = $client
  201. ->request('POST', "https://www.google.com/recaptcha/api/siteverify", [
  202. 'form_params' => [
  203. 'secret' => RECAPTCHA_SECRET_KEY,
  204. 'response' => $response
  205. ]
  206. ]);
  207. if ($response->getStatusCode() != 200) {
  208. return false;
  209. }
  210. $resp = json_decode($response->getBody(), TRUE);
  211. if ($resp['success'] === true) {
  212. return true;
  213. } else {
  214. return false;
  215. }
  216. } catch (Exception $e) {
  217. return false;
  218. }
  219. }
  220. ////////////////////////////////////////////////////////////////////////////////
  221. // LDAP handling //
  222. ////////////////////////////////////////////////////////////////////////////////
  223. /**
  224. * Checks the given credentials against the LDAP server.
  225. * @param string $username
  226. * @param string $password
  227. * @return mixed True if OK, else false or the error code from the server
  228. */
  229. function authenticate_user_ldap($username, $password) {
  230. global $ldap_config;
  231. if (is_empty($username) || is_empty($password)) {
  232. return false;
  233. }
  234. $username = strtolower($username);
  235. try {
  236. $ldapManager = new LdapManager($ldap_config);
  237. $msg = "";
  238. $code = 0;
  239. if ($ldapManager->authenticate($username, $password, $msg, $code) === TRUE) {
  240. return true;
  241. } else {
  242. return $code;
  243. }
  244. } catch (Exception $e) {
  245. sendError("LDAP error: " . $e->getMessage());
  246. }
  247. }
  248. /**
  249. * Check if a username exists on the LDAP server.
  250. * @global type $ldap_config
  251. * @param type $username
  252. * @return boolean true if yes, else false
  253. */
  254. function user_exists_ldap($username, $password) {
  255. global $ldap_config;
  256. try {
  257. $ldap = new LdapManager($ldap_config);
  258. $username = strtolower($username);
  259. if (!$ldap->authenticate($username, $password, $message, $code)) {
  260. switch ($code) {
  261. case ADResponseCodes::ACCOUNT_INVALID:
  262. return false;
  263. case ADResponseCodes::ACCOUNT_CREDENTIALS_INVALID:
  264. return true;
  265. case ADResponseCodes::ACCOUNT_RESTRICTIONS:
  266. return true;
  267. case ADResponseCodes::ACCOUNT_RESTRICTIONS_TIME:
  268. return true;
  269. case ADResponseCodes::ACCOUNT_RESTRICTIONS_DEVICE:
  270. return true;
  271. case ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED:
  272. return true;
  273. case ADResponseCodes::ACCOUNT_DISABLED:
  274. return true;
  275. case ADResponseCodes::ACCOUNT_CONTEXT_IDS:
  276. return true;
  277. case ADResponseCodes::ACCOUNT_EXPIRED:
  278. return false;
  279. case ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE:
  280. return true;
  281. case ADResponseCodes::ACCOUNT_LOCKED:
  282. return true;
  283. default:
  284. return false;
  285. }
  286. }
  287. return true;
  288. } catch (Exception $e) {
  289. sendError("LDAP error: " . $e->getMessage());
  290. }
  291. }
  292. ////////////////////////////////////////////////////////////////////////////////
  293. // 2-factor authentication //
  294. ////////////////////////////////////////////////////////////////////////////////
  295. /**
  296. * Check if a user has TOTP setup
  297. * @global $database $database
  298. * @param string $username
  299. * @return boolean true if TOTP secret exists, else false
  300. */
  301. function userHasTOTP($username) {
  302. global $database;
  303. $username = strtolower($username);
  304. $secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
  305. if (is_empty($secret)) {
  306. return false;
  307. }
  308. return true;
  309. }
  310. /**
  311. * Generate a TOTP secret for the given user.
  312. * @param string $username
  313. * @return string OTP provisioning URI (for generating a QR code)
  314. */
  315. function newTOTP($username) {
  316. global $database;
  317. $username = strtolower($username);
  318. $secret = random_bytes(20);
  319. $encoded_secret = Base32::encode($secret);
  320. $userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
  321. $totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
  322. $totp->setIssuer(SYSTEM_NAME);
  323. return $totp->getProvisioningUri();
  324. }
  325. /**
  326. * Save a TOTP secret for the user.
  327. * @global $database $database
  328. * @param string $username
  329. * @param string $secret
  330. */
  331. function saveTOTP($username, $secret) {
  332. global $database;
  333. $username = strtolower($username);
  334. $database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
  335. }
  336. /**
  337. * Verify a TOTP multiauth code
  338. * @global $database
  339. * @param string $username
  340. * @param int $code
  341. * @return boolean true if it's legit, else false
  342. */
  343. function verifyTOTP($username, $code) {
  344. global $database;
  345. $username = strtolower($username);
  346. $userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
  347. if (is_empty($userdata['authsecret'])) {
  348. return false;
  349. }
  350. $totp = new TOTP(null, $userdata['authsecret']);
  351. return $totp->verify($code);
  352. }