UserTrait.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /**
  3. * @package Grav\Common\User
  4. *
  5. * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\User\Traits;
  9. use Grav\Common\Filesystem\Folder;
  10. use Grav\Common\Grav;
  11. use Grav\Common\Page\Medium\ImageMedium;
  12. use Grav\Common\Page\Medium\Medium;
  13. use Grav\Common\Page\Medium\StaticImageMedium;
  14. use Grav\Common\User\Authentication;
  15. use Grav\Common\Utils;
  16. use Multiavatar;
  17. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  18. use function is_array;
  19. use function is_string;
  20. /**
  21. * Trait UserTrait
  22. * @package Grav\Common\User\Traits
  23. */
  24. trait UserTrait
  25. {
  26. /**
  27. * Authenticate user.
  28. *
  29. * If user password needs to be updated, new information will be saved.
  30. *
  31. * @param string $password Plaintext password.
  32. * @return bool
  33. */
  34. public function authenticate(string $password): bool
  35. {
  36. $hash = $this->get('hashed_password');
  37. $isHashed = null !== $hash;
  38. if (!$isHashed) {
  39. // If there is no hashed password, fake verify with default hash.
  40. $hash = Grav::instance()['config']->get('system.security.default_hash');
  41. }
  42. // Always execute verify() to protect us from timing attacks, but make the test to fail if hashed password wasn't set.
  43. $result = Authentication::verify($password, $hash) && $isHashed;
  44. $plaintext_password = $this->get('password');
  45. if (null !== $plaintext_password) {
  46. // Plain-text password is still stored, check if it matches.
  47. if ($password !== $plaintext_password) {
  48. return false;
  49. }
  50. // Force hash update to get rid of plaintext password.
  51. $result = 2;
  52. }
  53. if ($result === 2) {
  54. // Password needs to be updated, save the user.
  55. $this->set('password', $password);
  56. $this->undef('hashed_password');
  57. $this->save();
  58. }
  59. return (bool)$result;
  60. }
  61. /**
  62. * Checks user authorization to the action.
  63. *
  64. * @param string $action
  65. * @param string|null $scope
  66. * @return bool|null
  67. */
  68. public function authorize(string $action, string $scope = null): ?bool
  69. {
  70. // User needs to be enabled.
  71. if ($this->get('state', 'enabled') !== 'enabled') {
  72. return false;
  73. }
  74. // User needs to be logged in.
  75. if (!$this->get('authenticated')) {
  76. return false;
  77. }
  78. // User needs to be authorized (2FA).
  79. if (strpos($action, 'login') === false && !$this->get('authorized', true)) {
  80. return false;
  81. }
  82. if (null !== $scope) {
  83. $action = $scope . '.' . $action;
  84. }
  85. $config = Grav::instance()['config'];
  86. $authorized = false;
  87. //Check group access level
  88. $groups = (array)$this->get('groups');
  89. foreach ($groups as $group) {
  90. $permission = $config->get("groups.{$group}.access.{$action}");
  91. $authorized = Utils::isPositive($permission);
  92. if ($authorized === true) {
  93. break;
  94. }
  95. }
  96. //Check user access level
  97. $access = $this->get('access');
  98. if ($access && Utils::getDotNotation($access, $action) !== null) {
  99. $permission = $this->get("access.{$action}");
  100. $authorized = Utils::isPositive($permission);
  101. }
  102. return $authorized;
  103. }
  104. /**
  105. * Return media object for the User's avatar.
  106. *
  107. * Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL.
  108. *
  109. * @return ImageMedium|StaticImageMedium|null
  110. */
  111. public function getAvatarImage(): ?Medium
  112. {
  113. $avatars = $this->get('avatar');
  114. if (is_array($avatars) && $avatars) {
  115. $avatar = array_shift($avatars);
  116. $media = $this->getMedia();
  117. $name = $avatar['name'] ?? null;
  118. $image = $name ? $media[$name] : null;
  119. if ($image instanceof ImageMedium ||
  120. $image instanceof StaticImageMedium) {
  121. return $image;
  122. }
  123. }
  124. return null;
  125. }
  126. /**
  127. * Return the User's avatar URL
  128. *
  129. * @return string
  130. */
  131. public function getAvatarUrl(): string
  132. {
  133. // Try to locate avatar image.
  134. $avatar = $this->getAvatarImage();
  135. if ($avatar) {
  136. return $avatar->url();
  137. }
  138. // Try if avatar is a sting (URL).
  139. $avatar = $this->get('avatar');
  140. if (is_string($avatar)) {
  141. return $avatar;
  142. }
  143. // Try looking for provider.
  144. $provider = $this->get('provider');
  145. $provider_options = $this->get($provider);
  146. if (is_array($provider_options)) {
  147. if (isset($provider_options['avatar_url']) && is_string($provider_options['avatar_url'])) {
  148. return $provider_options['avatar_url'];
  149. }
  150. if (isset($provider_options['avatar']) && is_string($provider_options['avatar'])) {
  151. return $provider_options['avatar'];
  152. }
  153. }
  154. $email = $this->get('email');
  155. $avatar_generator = Grav::instance()['config']->get('system.accounts.avatar', 'multiavatar');
  156. if ($avatar_generator === 'gravatar') {
  157. if (!$email) {
  158. return '';
  159. }
  160. $hash = md5(strtolower(trim($email)));
  161. return 'https://www.gravatar.com/avatar/' . $hash;
  162. }
  163. $hash = $this->get('avatar_hash');
  164. if (!$hash) {
  165. $username = $this->get('username');
  166. $hash = md5(strtolower(trim($email ?? $username)));
  167. }
  168. return $this->generateMultiavatar($hash);
  169. }
  170. /**
  171. * @param string $hash
  172. * @return string
  173. */
  174. protected function generateMultiavatar(string $hash): string
  175. {
  176. /** @var UniformResourceLocator $locator */
  177. $locator = Grav::instance()['locator'];
  178. $storage = $locator->findResource('image://multiavatar', true, true);
  179. $avatar_file = "{$storage}/{$hash}.svg";
  180. if (!file_exists($storage)) {
  181. Folder::create($storage);
  182. }
  183. if (!file_exists($avatar_file)) {
  184. $mavatar = new Multiavatar();
  185. file_put_contents($avatar_file, $mavatar->generate($hash, null, null));
  186. }
  187. $avatar_url = $locator->findResource("image://multiavatar/{$hash}.svg", false, true);
  188. return Utils::url($avatar_url);
  189. }
  190. abstract public function get($name, $default = null, $separator = null);
  191. abstract public function set($name, $value, $separator = null);
  192. abstract public function undef($name, $separator = null);
  193. abstract public function save();
  194. }