vendor/symfony/security-bundle/DataCollector/SecurityDataCollector.php line 52

Open in your IDE?
  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\Bundle\SecurityBundle\DataCollector;
  11. use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
  12. use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\DataCollector\DataCollector;
  16. use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
  17. use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
  18. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  19. use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
  20. use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
  21. use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
  22. use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
  23. use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
  24. use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
  25. use Symfony\Component\Security\Http\FirewallMapInterface;
  26. use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
  27. use Symfony\Component\VarDumper\Caster\ClassStub;
  28. use Symfony\Component\VarDumper\Cloner\Data;
  29. /**
  30. * @author Fabien Potencier <fabien@symfony.com>
  31. *
  32. * @final
  33. */
  34. class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface
  35. {
  36. private $tokenStorage;
  37. private $roleHierarchy;
  38. private $logoutUrlGenerator;
  39. private $accessDecisionManager;
  40. private $firewallMap;
  41. private $firewall;
  42. private $hasVarDumper;
  43. private $authenticatorManagerEnabled;
  44. public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null, bool $authenticatorManagerEnabled = false)
  45. {
  46. if (!$authenticatorManagerEnabled) {
  47. trigger_deprecation('symfony/security-bundle', '5.4', 'Setting the $authenticatorManagerEnabled argument of "%s" to "false" is deprecated, use the new authenticator system instead.', __METHOD__);
  48. }
  49. $this->tokenStorage = $tokenStorage;
  50. $this->roleHierarchy = $roleHierarchy;
  51. $this->logoutUrlGenerator = $logoutUrlGenerator;
  52. $this->accessDecisionManager = $accessDecisionManager;
  53. $this->firewallMap = $firewallMap;
  54. $this->firewall = $firewall;
  55. $this->hasVarDumper = class_exists(ClassStub::class);
  56. $this->authenticatorManagerEnabled = $authenticatorManagerEnabled;
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function collect(Request $request, Response $response, ?\Throwable $exception = null)
  62. {
  63. if (null === $this->tokenStorage) {
  64. $this->data = [
  65. 'enabled' => false,
  66. 'authenticated' => false,
  67. 'impersonated' => false,
  68. 'impersonator_user' => null,
  69. 'impersonation_exit_path' => null,
  70. 'token' => null,
  71. 'token_class' => null,
  72. 'logout_url' => null,
  73. 'user' => '',
  74. 'roles' => [],
  75. 'inherited_roles' => [],
  76. 'supports_role_hierarchy' => null !== $this->roleHierarchy,
  77. ];
  78. } elseif (null === $token = $this->tokenStorage->getToken()) {
  79. $this->data = [
  80. 'enabled' => true,
  81. 'authenticated' => false,
  82. 'impersonated' => false,
  83. 'impersonator_user' => null,
  84. 'impersonation_exit_path' => null,
  85. 'token' => null,
  86. 'token_class' => null,
  87. 'logout_url' => null,
  88. 'user' => '',
  89. 'roles' => [],
  90. 'inherited_roles' => [],
  91. 'supports_role_hierarchy' => null !== $this->roleHierarchy,
  92. ];
  93. } else {
  94. $inheritedRoles = [];
  95. $assignedRoles = $token->getRoleNames();
  96. $impersonatorUser = null;
  97. if ($token instanceof SwitchUserToken) {
  98. $originalToken = $token->getOriginalToken();
  99. // @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0
  100. $impersonatorUser = method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername();
  101. }
  102. if (null !== $this->roleHierarchy) {
  103. foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) {
  104. if (!\in_array($role, $assignedRoles, true)) {
  105. $inheritedRoles[] = $role;
  106. }
  107. }
  108. }
  109. $logoutUrl = null;
  110. try {
  111. if (null !== $this->logoutUrlGenerator && !$token instanceof AnonymousToken) {
  112. $logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
  113. }
  114. } catch (\Exception $e) {
  115. // fail silently when the logout URL cannot be generated
  116. }
  117. $this->data = [
  118. 'enabled' => true,
  119. 'authenticated' => method_exists($token, 'isAuthenticated') ? $token->isAuthenticated(false) : (bool) $token->getUser(),
  120. 'impersonated' => null !== $impersonatorUser,
  121. 'impersonator_user' => $impersonatorUser,
  122. 'impersonation_exit_path' => null,
  123. 'token' => $token,
  124. 'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token),
  125. 'logout_url' => $logoutUrl,
  126. // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
  127. 'user' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(),
  128. 'roles' => $assignedRoles,
  129. 'inherited_roles' => array_unique($inheritedRoles),
  130. 'supports_role_hierarchy' => null !== $this->roleHierarchy,
  131. ];
  132. }
  133. // collect voters and access decision manager information
  134. if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) {
  135. $this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
  136. $this->data['voters'] = [];
  137. foreach ($this->accessDecisionManager->getVoters() as $voter) {
  138. if ($voter instanceof TraceableVoter) {
  139. $voter = $voter->getDecoratedVoter();
  140. }
  141. $this->data['voters'][] = $this->hasVarDumper ? new ClassStub(\get_class($voter)) : \get_class($voter);
  142. }
  143. // collect voter details
  144. $decisionLog = $this->accessDecisionManager->getDecisionLog();
  145. foreach ($decisionLog as $key => $log) {
  146. $decisionLog[$key]['voter_details'] = [];
  147. foreach ($log['voterDetails'] as $voterDetail) {
  148. $voterClass = \get_class($voterDetail['voter']);
  149. $classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass;
  150. $decisionLog[$key]['voter_details'][] = [
  151. 'class' => $classData,
  152. 'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy
  153. 'vote' => $voterDetail['vote'],
  154. ];
  155. }
  156. unset($decisionLog[$key]['voterDetails']);
  157. }
  158. $this->data['access_decision_log'] = $decisionLog;
  159. } else {
  160. $this->data['access_decision_log'] = [];
  161. $this->data['voter_strategy'] = 'unknown';
  162. $this->data['voters'] = [];
  163. }
  164. // collect firewall context information
  165. $this->data['firewall'] = null;
  166. if ($this->firewallMap instanceof FirewallMap) {
  167. $firewallConfig = $this->firewallMap->getFirewallConfig($request);
  168. if (null !== $firewallConfig) {
  169. $this->data['firewall'] = [
  170. 'name' => $firewallConfig->getName(),
  171. 'allows_anonymous' => $this->authenticatorManagerEnabled ? false : $firewallConfig->allowsAnonymous(),
  172. 'request_matcher' => $firewallConfig->getRequestMatcher(),
  173. 'security_enabled' => $firewallConfig->isSecurityEnabled(),
  174. 'stateless' => $firewallConfig->isStateless(),
  175. 'provider' => $firewallConfig->getProvider(),
  176. 'context' => $firewallConfig->getContext(),
  177. 'entry_point' => $firewallConfig->getEntryPoint(),
  178. 'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(),
  179. 'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
  180. 'user_checker' => $firewallConfig->getUserChecker(),
  181. ];
  182. // in 6.0, always fill `$this->data['authenticators'] only
  183. if ($this->authenticatorManagerEnabled) {
  184. $this->data['firewall']['authenticators'] = $firewallConfig->getAuthenticators();
  185. } else {
  186. $this->data['firewall']['listeners'] = $firewallConfig->getAuthenticators();
  187. }
  188. // generate exit impersonation path from current request
  189. if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) {
  190. $exitPath = $request->getRequestUri();
  191. $exitPath .= null === $request->getQueryString() ? '?' : '&';
  192. $exitPath .= sprintf('%s=%s', urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE);
  193. $this->data['impersonation_exit_path'] = $exitPath;
  194. }
  195. }
  196. }
  197. // collect firewall listeners information
  198. $this->data['listeners'] = [];
  199. if ($this->firewall) {
  200. $this->data['listeners'] = $this->firewall->getWrappedListeners();
  201. }
  202. $this->data['authenticator_manager_enabled'] = $this->authenticatorManagerEnabled;
  203. $this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : [];
  204. }
  205. /**
  206. * {@inheritdoc}
  207. */
  208. public function reset()
  209. {
  210. $this->data = [];
  211. }
  212. public function lateCollect()
  213. {
  214. $this->data = $this->cloneVar($this->data);
  215. }
  216. /**
  217. * Checks if security is enabled.
  218. */
  219. public function isEnabled(): bool
  220. {
  221. return $this->data['enabled'];
  222. }
  223. /**
  224. * Gets the user.
  225. */
  226. public function getUser(): string
  227. {
  228. return $this->data['user'];
  229. }
  230. /**
  231. * Gets the roles of the user.
  232. *
  233. * @return array|Data
  234. */
  235. public function getRoles()
  236. {
  237. return $this->data['roles'];
  238. }
  239. /**
  240. * Gets the inherited roles of the user.
  241. *
  242. * @return array|Data
  243. */
  244. public function getInheritedRoles()
  245. {
  246. return $this->data['inherited_roles'];
  247. }
  248. /**
  249. * Checks if the data contains information about inherited roles. Still the inherited
  250. * roles can be an empty array.
  251. */
  252. public function supportsRoleHierarchy(): bool
  253. {
  254. return $this->data['supports_role_hierarchy'];
  255. }
  256. /**
  257. * Checks if the user is authenticated or not.
  258. */
  259. public function isAuthenticated(): bool
  260. {
  261. return $this->data['authenticated'];
  262. }
  263. public function isImpersonated(): bool
  264. {
  265. return $this->data['impersonated'];
  266. }
  267. public function getImpersonatorUser(): ?string
  268. {
  269. return $this->data['impersonator_user'];
  270. }
  271. public function getImpersonationExitPath(): ?string
  272. {
  273. return $this->data['impersonation_exit_path'];
  274. }
  275. /**
  276. * Get the class name of the security token.
  277. *
  278. * @return string|Data|null
  279. */
  280. public function getTokenClass()
  281. {
  282. return $this->data['token_class'];
  283. }
  284. /**
  285. * Get the full security token class as Data object.
  286. */
  287. public function getToken(): ?Data
  288. {
  289. return $this->data['token'];
  290. }
  291. /**
  292. * Get the logout URL.
  293. */
  294. public function getLogoutUrl(): ?string
  295. {
  296. return $this->data['logout_url'];
  297. }
  298. /**
  299. * Returns the FQCN of the security voters enabled in the application.
  300. *
  301. * @return string[]|Data
  302. */
  303. public function getVoters()
  304. {
  305. return $this->data['voters'];
  306. }
  307. /**
  308. * Returns the strategy configured for the security voters.
  309. */
  310. public function getVoterStrategy(): string
  311. {
  312. return $this->data['voter_strategy'];
  313. }
  314. /**
  315. * Returns the log of the security decisions made by the access decision manager.
  316. *
  317. * @return array|Data
  318. */
  319. public function getAccessDecisionLog()
  320. {
  321. return $this->data['access_decision_log'];
  322. }
  323. /**
  324. * Returns the configuration of the current firewall context.
  325. *
  326. * @return array|Data|null
  327. */
  328. public function getFirewall()
  329. {
  330. return $this->data['firewall'];
  331. }
  332. /**
  333. * @return array|Data
  334. */
  335. public function getListeners()
  336. {
  337. return $this->data['listeners'];
  338. }
  339. /**
  340. * @return array|Data
  341. */
  342. public function getAuthenticators()
  343. {
  344. return $this->data['authenticators'];
  345. }
  346. /**
  347. * {@inheritdoc}
  348. */
  349. public function getName(): string
  350. {
  351. return 'security';
  352. }
  353. public function isAuthenticatorManagerEnabled(): bool
  354. {
  355. return $this->data['authenticator_manager_enabled'];
  356. }
  357. }