User.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <?php
  2. /**
  3. * @package Grav.Common.User
  4. *
  5. * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\User;
  9. use Grav\Common\Data\Blueprints;
  10. use Grav\Common\Data\Data;
  11. use Grav\Common\File\CompiledYamlFile;
  12. use Grav\Common\Grav;
  13. use Grav\Common\Utils;
  14. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  15. class User extends Data
  16. {
  17. /**
  18. * Load user account.
  19. *
  20. * Always creates user object. To check if user exists, use $this->exists().
  21. *
  22. * @param string $username
  23. * @param bool $setConfig
  24. *
  25. * @return User
  26. */
  27. public static function load($username)
  28. {
  29. $grav = Grav::instance();
  30. /** @var UniformResourceLocator $locator */
  31. $locator = $grav['locator'];
  32. // force lowercase of username
  33. $username = strtolower($username);
  34. $blueprints = new Blueprints;
  35. $blueprint = $blueprints->get('user/account');
  36. $file_path = $locator->findResource('account://' . $username . YAML_EXT);
  37. $file = CompiledYamlFile::instance($file_path);
  38. $content = (array)$file->content() + ['username' => $username, 'state' => 'enabled'];
  39. $user = new User($content, $blueprint);
  40. $user->file($file);
  41. return $user;
  42. }
  43. /**
  44. * Find a user by username, email, etc
  45. *
  46. * @param string $query the query to search for
  47. * @param array $fields the fields to search
  48. * @return User
  49. */
  50. public static function find($query, $fields = ['username', 'email'])
  51. {
  52. $account_dir = Grav::instance()['locator']->findResource('account://');
  53. $files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
  54. // Try with username first, you never know!
  55. if (in_array('username', $fields, true)) {
  56. $user = User::load($query);
  57. unset($fields[array_search('username', $fields, true)]);
  58. } else {
  59. $user = User::load('');
  60. }
  61. // If not found, try the fields
  62. if (!$user->exists()) {
  63. foreach ($files as $file) {
  64. if (Utils::endsWith($file, YAML_EXT)) {
  65. $find_user = User::load(trim(pathinfo($file, PATHINFO_FILENAME)));
  66. foreach ($fields as $field) {
  67. if ($find_user[$field] === $query) {
  68. return $find_user;
  69. }
  70. }
  71. }
  72. }
  73. }
  74. return $user;
  75. }
  76. /**
  77. * Remove user account.
  78. *
  79. * @param string $username
  80. *
  81. * @return bool True if the action was performed
  82. */
  83. public static function remove($username)
  84. {
  85. $file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
  86. return $file_path && unlink($file_path);
  87. }
  88. /**
  89. * @param string $offset
  90. * @return bool
  91. */
  92. public function offsetExists($offset)
  93. {
  94. $value = parent::offsetExists($offset);
  95. // Handle special case where user was logged in before 'authorized' was added to the user object.
  96. if (false === $value && $offset === 'authorized') {
  97. $value = $this->offsetExists('authenticated');
  98. }
  99. return $value;
  100. }
  101. /**
  102. * @param string $offset
  103. * @return mixed
  104. */
  105. public function offsetGet($offset)
  106. {
  107. $value = parent::offsetGet($offset);
  108. // Handle special case where user was logged in before 'authorized' was added to the user object.
  109. if (null === $value && $offset === 'authorized') {
  110. $value = $this->offsetGet('authenticated');
  111. $this->offsetSet($offset, $value);
  112. }
  113. return $value;
  114. }
  115. /**
  116. * Authenticate user.
  117. *
  118. * If user password needs to be updated, new information will be saved.
  119. *
  120. * @param string $password Plaintext password.
  121. *
  122. * @return bool
  123. */
  124. public function authenticate($password)
  125. {
  126. $save = false;
  127. // Plain-text is still stored
  128. if ($this->password) {
  129. if ($password !== $this->password) {
  130. // Plain-text passwords do not match, we know we should fail but execute
  131. // verify to protect us from timing attacks and return false regardless of
  132. // the result
  133. Authentication::verify(
  134. $password,
  135. Grav::instance()['config']->get('system.security.default_hash')
  136. );
  137. return false;
  138. }
  139. // Plain-text does match, we can update the hash and proceed
  140. $save = true;
  141. $this->hashed_password = Authentication::create($this->password);
  142. unset($this->password);
  143. }
  144. $result = Authentication::verify($password, $this->hashed_password);
  145. // Password needs to be updated, save the file.
  146. if ($result === 2) {
  147. $save = true;
  148. $this->hashed_password = Authentication::create($password);
  149. }
  150. if ($save) {
  151. $this->save();
  152. }
  153. return (bool)$result;
  154. }
  155. /**
  156. * Save user without the username
  157. */
  158. public function save()
  159. {
  160. $file = $this->file();
  161. if ($file) {
  162. $username = $this->get('username');
  163. if (!$file->filename()) {
  164. $locator = Grav::instance()['locator'];
  165. $file->filename($locator->findResource('account://') . DS . strtolower($username) . YAML_EXT);
  166. }
  167. // if plain text password, hash it and remove plain text
  168. if ($this->password) {
  169. $this->hashed_password = Authentication::create($this->password);
  170. unset($this->password);
  171. }
  172. unset($this->username);
  173. $file->save($this->items);
  174. $this->set('username', $username);
  175. }
  176. }
  177. /**
  178. * Checks user authorization to the action.
  179. *
  180. * @param string $action
  181. *
  182. * @return bool
  183. */
  184. public function authorize($action)
  185. {
  186. if (empty($this->items)) {
  187. return false;
  188. }
  189. if (!$this->authenticated) {
  190. return false;
  191. }
  192. if (isset($this->state) && $this->state !== 'enabled') {
  193. return false;
  194. }
  195. $return = false;
  196. //Check group access level
  197. $groups = $this->get('groups');
  198. if ($groups) {
  199. foreach ((array)$groups as $group) {
  200. $permission = Grav::instance()['config']->get("groups.{$group}.access.{$action}");
  201. $return = Utils::isPositive($permission);
  202. if ($return === true) {
  203. break;
  204. }
  205. }
  206. }
  207. //Check user access level
  208. if ($this->get('access')) {
  209. if (Utils::getDotNotation($this->get('access'), $action) !== null) {
  210. $permission = $this->get("access.{$action}");
  211. $return = Utils::isPositive($permission);
  212. }
  213. }
  214. return $return;
  215. }
  216. /**
  217. * Checks user authorization to the action.
  218. * Ensures backwards compatibility
  219. *
  220. * @param string $action
  221. *
  222. * @deprecated use authorize()
  223. * @return bool
  224. */
  225. public function authorise($action)
  226. {
  227. user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED);
  228. return $this->authorize($action);
  229. }
  230. /**
  231. * Return the User's avatar URL
  232. *
  233. * @return string
  234. */
  235. public function avatarUrl()
  236. {
  237. if ($this->avatar) {
  238. $avatar = $this->avatar;
  239. $avatar = array_shift($avatar);
  240. return Grav::instance()['base_url'] . '/' . $avatar['path'];
  241. }
  242. return 'https://www.gravatar.com/avatar/' . md5($this->email);
  243. }
  244. /**
  245. * Serialize user.
  246. */
  247. public function __sleep()
  248. {
  249. return [
  250. 'items',
  251. 'storage'
  252. ];
  253. }
  254. /**
  255. * Unserialize user.
  256. */
  257. public function __wakeup()
  258. {
  259. $this->gettersVariable = 'items';
  260. $this->nestedSeparator = '.';
  261. if (null === $this->items) {
  262. $this->items = [];
  263. }
  264. if (null === $this->blueprints) {
  265. $blueprints = new Blueprints;
  266. $this->blueprints = $blueprints->get('user/account');
  267. }
  268. }
  269. }