PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/plugins/authplain/auth.php

https://gitlab.com/michield/dokuwiki
PHP | 358 lines | 171 code | 51 blank | 136 comment | 42 complexity | 59e7d691c485cb294ce4f6d64764bcf4 MD5 | raw file
  1. <?php
  2. // must be run within Dokuwiki
  3. if(!defined('DOKU_INC')) die();
  4. /**
  5. * Plaintext authentication backend
  6. *
  7. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  8. * @author Andreas Gohr <andi@splitbrain.org>
  9. * @author Chris Smith <chris@jalakai.co.uk>
  10. * @author Jan Schumann <js@schumann-it.com>
  11. */
  12. class auth_plugin_authplain extends DokuWiki_Auth_Plugin {
  13. /** @var array user cache */
  14. protected $users = null;
  15. /** @var array filter pattern */
  16. protected $_pattern = array();
  17. /**
  18. * Constructor
  19. *
  20. * Carry out sanity checks to ensure the object is
  21. * able to operate. Set capabilities.
  22. *
  23. * @author Christopher Smith <chris@jalakai.co.uk>
  24. */
  25. public function __construct() {
  26. parent::__construct();
  27. global $config_cascade;
  28. if(!@is_readable($config_cascade['plainauth.users']['default'])) {
  29. $this->success = false;
  30. } else {
  31. if(@is_writable($config_cascade['plainauth.users']['default'])) {
  32. $this->cando['addUser'] = true;
  33. $this->cando['delUser'] = true;
  34. $this->cando['modLogin'] = true;
  35. $this->cando['modPass'] = true;
  36. $this->cando['modName'] = true;
  37. $this->cando['modMail'] = true;
  38. $this->cando['modGroups'] = true;
  39. }
  40. $this->cando['getUsers'] = true;
  41. $this->cando['getUserCount'] = true;
  42. }
  43. }
  44. /**
  45. * Check user+password
  46. *
  47. * Checks if the given user exists and the given
  48. * plaintext password is correct
  49. *
  50. * @author Andreas Gohr <andi@splitbrain.org>
  51. * @param string $user
  52. * @param string $pass
  53. * @return bool
  54. */
  55. public function checkPass($user, $pass) {
  56. $userinfo = $this->getUserData($user);
  57. if($userinfo === false) return false;
  58. return auth_verifyPassword($pass, $this->users[$user]['pass']);
  59. }
  60. /**
  61. * Return user info
  62. *
  63. * Returns info about the given user needs to contain
  64. * at least these fields:
  65. *
  66. * name string full name of the user
  67. * mail string email addres of the user
  68. * grps array list of groups the user is in
  69. *
  70. * @author Andreas Gohr <andi@splitbrain.org>
  71. * @param string $user
  72. * @return array|bool
  73. */
  74. public function getUserData($user) {
  75. if($this->users === null) $this->_loadUserData();
  76. return isset($this->users[$user]) ? $this->users[$user] : false;
  77. }
  78. /**
  79. * Create a new User
  80. *
  81. * Returns false if the user already exists, null when an error
  82. * occurred and true if everything went well.
  83. *
  84. * The new user will be added to the default group by this
  85. * function if grps are not specified (default behaviour).
  86. *
  87. * @author Andreas Gohr <andi@splitbrain.org>
  88. * @author Chris Smith <chris@jalakai.co.uk>
  89. *
  90. * @param string $user
  91. * @param string $pwd
  92. * @param string $name
  93. * @param string $mail
  94. * @param array $grps
  95. * @return bool|null|string
  96. */
  97. public function createUser($user, $pwd, $name, $mail, $grps = null) {
  98. global $conf;
  99. global $config_cascade;
  100. // user mustn't already exist
  101. if($this->getUserData($user) !== false) return false;
  102. $pass = auth_cryptPassword($pwd);
  103. // set default group if no groups specified
  104. if(!is_array($grps)) $grps = array($conf['defaultgroup']);
  105. // prepare user line
  106. $groups = join(',', $grps);
  107. $userline = join(':', array($user, $pass, $name, $mail, $groups))."\n";
  108. if(io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) {
  109. $this->users[$user] = compact('pass', 'name', 'mail', 'grps');
  110. return $pwd;
  111. }
  112. msg(
  113. 'The '.$config_cascade['plainauth.users']['default'].
  114. ' file is not writable. Please inform the Wiki-Admin', -1
  115. );
  116. return null;
  117. }
  118. /**
  119. * Modify user data
  120. *
  121. * @author Chris Smith <chris@jalakai.co.uk>
  122. * @param string $user nick of the user to be changed
  123. * @param array $changes array of field/value pairs to be changed (password will be clear text)
  124. * @return bool
  125. */
  126. public function modifyUser($user, $changes) {
  127. global $ACT;
  128. global $config_cascade;
  129. // sanity checks, user must already exist and there must be something to change
  130. if(($userinfo = $this->getUserData($user)) === false) return false;
  131. if(!is_array($changes) || !count($changes)) return true;
  132. // update userinfo with new data, remembering to encrypt any password
  133. $newuser = $user;
  134. foreach($changes as $field => $value) {
  135. if($field == 'user') {
  136. $newuser = $value;
  137. continue;
  138. }
  139. if($field == 'pass') $value = auth_cryptPassword($value);
  140. $userinfo[$field] = $value;
  141. }
  142. $groups = join(',', $userinfo['grps']);
  143. $userline = join(':', array($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $groups))."\n";
  144. if(!$this->deleteUsers(array($user))) {
  145. msg('Unable to modify user data. Please inform the Wiki-Admin', -1);
  146. return false;
  147. }
  148. if(!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) {
  149. msg('There was an error modifying your user data. You should register again.', -1);
  150. // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page
  151. $ACT = 'register';
  152. return false;
  153. }
  154. $this->users[$newuser] = $userinfo;
  155. return true;
  156. }
  157. /**
  158. * Remove one or more users from the list of registered users
  159. *
  160. * @author Christopher Smith <chris@jalakai.co.uk>
  161. * @param array $users array of users to be deleted
  162. * @return int the number of users deleted
  163. */
  164. public function deleteUsers($users) {
  165. global $config_cascade;
  166. if(!is_array($users) || empty($users)) return 0;
  167. if($this->users === null) $this->_loadUserData();
  168. $deleted = array();
  169. foreach($users as $user) {
  170. if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/');
  171. }
  172. if(empty($deleted)) return 0;
  173. $pattern = '/^('.join('|', $deleted).'):/';
  174. if(io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) {
  175. foreach($deleted as $user) unset($this->users[$user]);
  176. return count($deleted);
  177. }
  178. // problem deleting, reload the user list and count the difference
  179. $count = count($this->users);
  180. $this->_loadUserData();
  181. $count -= count($this->users);
  182. return $count;
  183. }
  184. /**
  185. * Return a count of the number of user which meet $filter criteria
  186. *
  187. * @author Chris Smith <chris@jalakai.co.uk>
  188. *
  189. * @param array $filter
  190. * @return int
  191. */
  192. public function getUserCount($filter = array()) {
  193. if($this->users === null) $this->_loadUserData();
  194. if(!count($filter)) return count($this->users);
  195. $count = 0;
  196. $this->_constructPattern($filter);
  197. foreach($this->users as $user => $info) {
  198. $count += $this->_filter($user, $info);
  199. }
  200. return $count;
  201. }
  202. /**
  203. * Bulk retrieval of user data
  204. *
  205. * @author Chris Smith <chris@jalakai.co.uk>
  206. *
  207. * @param int $start index of first user to be returned
  208. * @param int $limit max number of users to be returned
  209. * @param array $filter array of field/pattern pairs
  210. * @return array userinfo (refer getUserData for internal userinfo details)
  211. */
  212. public function retrieveUsers($start = 0, $limit = 0, $filter = array()) {
  213. if($this->users === null) $this->_loadUserData();
  214. ksort($this->users);
  215. $i = 0;
  216. $count = 0;
  217. $out = array();
  218. $this->_constructPattern($filter);
  219. foreach($this->users as $user => $info) {
  220. if($this->_filter($user, $info)) {
  221. if($i >= $start) {
  222. $out[$user] = $info;
  223. $count++;
  224. if(($limit > 0) && ($count >= $limit)) break;
  225. }
  226. $i++;
  227. }
  228. }
  229. return $out;
  230. }
  231. /**
  232. * Only valid pageid's (no namespaces) for usernames
  233. *
  234. * @param string $user
  235. * @return string
  236. */
  237. public function cleanUser($user) {
  238. global $conf;
  239. return cleanID(str_replace(':', $conf['sepchar'], $user));
  240. }
  241. /**
  242. * Only valid pageid's (no namespaces) for groupnames
  243. *
  244. * @param string $group
  245. * @return string
  246. */
  247. public function cleanGroup($group) {
  248. global $conf;
  249. return cleanID(str_replace(':', $conf['sepchar'], $group));
  250. }
  251. /**
  252. * Load all user data
  253. *
  254. * loads the user file into a datastructure
  255. *
  256. * @author Andreas Gohr <andi@splitbrain.org>
  257. */
  258. protected function _loadUserData() {
  259. global $config_cascade;
  260. $this->users = array();
  261. if(!@file_exists($config_cascade['plainauth.users']['default'])) return;
  262. $lines = file($config_cascade['plainauth.users']['default']);
  263. foreach($lines as $line) {
  264. $line = preg_replace('/#.*$/', '', $line); //ignore comments
  265. $line = trim($line);
  266. if(empty($line)) continue;
  267. $row = explode(":", $line, 5);
  268. $groups = array_values(array_filter(explode(",", $row[4])));
  269. $this->users[$row[0]]['pass'] = $row[1];
  270. $this->users[$row[0]]['name'] = urldecode($row[2]);
  271. $this->users[$row[0]]['mail'] = $row[3];
  272. $this->users[$row[0]]['grps'] = $groups;
  273. }
  274. }
  275. /**
  276. * return true if $user + $info match $filter criteria, false otherwise
  277. *
  278. * @author Chris Smith <chris@jalakai.co.uk>
  279. *
  280. * @param string $user User login
  281. * @param array $info User's userinfo array
  282. * @return bool
  283. */
  284. protected function _filter($user, $info) {
  285. foreach($this->_pattern as $item => $pattern) {
  286. if($item == 'user') {
  287. if(!preg_match($pattern, $user)) return false;
  288. } else if($item == 'grps') {
  289. if(!count(preg_grep($pattern, $info['grps']))) return false;
  290. } else {
  291. if(!preg_match($pattern, $info[$item])) return false;
  292. }
  293. }
  294. return true;
  295. }
  296. /**
  297. * construct a filter pattern
  298. *
  299. * @param array $filter
  300. */
  301. protected function _constructPattern($filter) {
  302. $this->_pattern = array();
  303. foreach($filter as $item => $pattern) {
  304. $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
  305. }
  306. }
  307. }