AbstractToken.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Core\Authentication\Token;
  11. use Symfony\Component\Security\Core\User\EquatableInterface;
  12. use Symfony\Component\Security\Core\User\UserInterface;
  13. /**
  14. * Base class for Token instances.
  15. *
  16. * @author Fabien Potencier <fabien@symfony.com>
  17. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  18. */
  19. abstract class AbstractToken implements TokenInterface
  20. {
  21. private $user;
  22. private $roleNames = [];
  23. private $authenticated = false;
  24. private $attributes = [];
  25. /**
  26. * @param string[] $roles An array of roles
  27. *
  28. * @throws \InvalidArgumentException
  29. */
  30. public function __construct(array $roles = [])
  31. {
  32. foreach ($roles as $role) {
  33. $this->roleNames[] = $role;
  34. }
  35. }
  36. /**
  37. * {@inheritdoc}
  38. */
  39. public function getRoleNames(): array
  40. {
  41. return $this->roleNames;
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function getUsername()
  47. {
  48. if ($this->user instanceof UserInterface) {
  49. return $this->user->getUsername();
  50. }
  51. return (string) $this->user;
  52. }
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function getUser()
  57. {
  58. return $this->user;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public function setUser($user)
  64. {
  65. if (!($user instanceof UserInterface || (\is_object($user) && method_exists($user, '__toString')) || \is_string($user))) {
  66. throw new \InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.');
  67. }
  68. if (null === $this->user) {
  69. $changed = false;
  70. } elseif ($this->user instanceof UserInterface) {
  71. if (!$user instanceof UserInterface) {
  72. $changed = true;
  73. } else {
  74. $changed = $this->hasUserChanged($user);
  75. }
  76. } elseif ($user instanceof UserInterface) {
  77. $changed = true;
  78. } else {
  79. $changed = (string) $this->user !== (string) $user;
  80. }
  81. if ($changed) {
  82. $this->setAuthenticated(false);
  83. }
  84. $this->user = $user;
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function isAuthenticated()
  90. {
  91. return $this->authenticated;
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function setAuthenticated(bool $authenticated)
  97. {
  98. $this->authenticated = $authenticated;
  99. }
  100. /**
  101. * {@inheritdoc}
  102. */
  103. public function eraseCredentials()
  104. {
  105. if ($this->getUser() instanceof UserInterface) {
  106. $this->getUser()->eraseCredentials();
  107. }
  108. }
  109. /**
  110. * Returns all the necessary state of the object for serialization purposes.
  111. *
  112. * There is no need to serialize any entry, they should be returned as-is.
  113. * If you extend this method, keep in mind you MUST guarantee parent data is present in the state.
  114. * Here is an example of how to extend this method:
  115. * <code>
  116. * public function __serialize(): array
  117. * {
  118. * return [$this->childAttribute, parent::__serialize()];
  119. * }
  120. * </code>
  121. *
  122. * @see __unserialize()
  123. */
  124. public function __serialize(): array
  125. {
  126. return [$this->user, $this->authenticated, null, $this->attributes, $this->roleNames];
  127. }
  128. /**
  129. * Restores the object state from an array given by __serialize().
  130. *
  131. * There is no need to unserialize any entry in $data, they are already ready-to-use.
  132. * If you extend this method, keep in mind you MUST pass the parent data to its respective class.
  133. * Here is an example of how to extend this method:
  134. * <code>
  135. * public function __unserialize(array $data): void
  136. * {
  137. * [$this->childAttribute, $parentData] = $data;
  138. * parent::__unserialize($parentData);
  139. * }
  140. * </code>
  141. *
  142. * @see __serialize()
  143. */
  144. public function __unserialize(array $data): void
  145. {
  146. [$this->user, $this->authenticated, , $this->attributes, $this->roleNames] = $data;
  147. }
  148. /**
  149. * Returns the token attributes.
  150. *
  151. * @return array The token attributes
  152. */
  153. public function getAttributes()
  154. {
  155. return $this->attributes;
  156. }
  157. /**
  158. * Sets the token attributes.
  159. *
  160. * @param array $attributes The token attributes
  161. */
  162. public function setAttributes(array $attributes)
  163. {
  164. $this->attributes = $attributes;
  165. }
  166. /**
  167. * Returns true if the attribute exists.
  168. *
  169. * @return bool true if the attribute exists, false otherwise
  170. */
  171. public function hasAttribute(string $name)
  172. {
  173. return \array_key_exists($name, $this->attributes);
  174. }
  175. /**
  176. * Returns an attribute value.
  177. *
  178. * @return mixed The attribute value
  179. *
  180. * @throws \InvalidArgumentException When attribute doesn't exist for this token
  181. */
  182. public function getAttribute(string $name)
  183. {
  184. if (!\array_key_exists($name, $this->attributes)) {
  185. throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name));
  186. }
  187. return $this->attributes[$name];
  188. }
  189. /**
  190. * Sets an attribute.
  191. *
  192. * @param mixed $value The attribute value
  193. */
  194. public function setAttribute(string $name, $value)
  195. {
  196. $this->attributes[$name] = $value;
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. public function __toString()
  202. {
  203. $class = static::class;
  204. $class = substr($class, strrpos($class, '\\') + 1);
  205. $roles = [];
  206. foreach ($this->roleNames as $role) {
  207. $roles[] = $role;
  208. }
  209. return sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUsername(), json_encode($this->authenticated), implode(', ', $roles));
  210. }
  211. /**
  212. * @internal
  213. */
  214. final public function serialize(): string
  215. {
  216. return serialize($this->__serialize());
  217. }
  218. /**
  219. * @internal
  220. */
  221. final public function unserialize($serialized)
  222. {
  223. $this->__unserialize(\is_array($serialized) ? $serialized : unserialize($serialized));
  224. }
  225. private function hasUserChanged(UserInterface $user): bool
  226. {
  227. if (!($this->user instanceof UserInterface)) {
  228. throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".');
  229. }
  230. if ($this->user instanceof EquatableInterface) {
  231. return !(bool) $this->user->isEqualTo($user);
  232. }
  233. if ($this->user->getPassword() !== $user->getPassword()) {
  234. return true;
  235. }
  236. if ($this->user->getSalt() !== $user->getSalt()) {
  237. return true;
  238. }
  239. $userRoles = array_map('strval', (array) $user->getRoles());
  240. if ($this instanceof SwitchUserToken) {
  241. $userRoles[] = 'ROLE_PREVIOUS_ADMIN';
  242. }
  243. if (\count($userRoles) !== \count($this->getRoleNames()) || \count($userRoles) !== \count(array_intersect($userRoles, $this->getRoleNames()))) {
  244. return true;
  245. }
  246. if ($this->user->getUsername() !== $user->getUsername()) {
  247. return true;
  248. }
  249. return false;
  250. }
  251. }