PageRenderTime 26ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php

https://gitlab.com/Isaki/le331.fr
PHP | 222 lines | 159 code | 43 blank | 20 comment | 27 complexity | 0d19bddcd2cd151d6ef81cfc5073e18e MD5 | raw file
  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\Http\Firewall;
  11. use Symfony\Component\Security\Core\User\UserProviderInterface;
  12. use Symfony\Component\Security\Core\Util\StringUtils;
  13. use Symfony\Component\Security\Http\EntryPoint\DigestAuthenticationEntryPoint;
  14. use Psr\Log\LoggerInterface;
  15. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  16. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  17. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  18. use Symfony\Component\Security\Core\Exception\BadCredentialsException;
  19. use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
  20. use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  21. use Symfony\Component\Security\Core\Exception\NonceExpiredException;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  24. /**
  25. * DigestAuthenticationListener implements Digest HTTP authentication.
  26. *
  27. * @author Fabien Potencier <fabien@symfony.com>
  28. */
  29. class DigestAuthenticationListener implements ListenerInterface
  30. {
  31. private $tokenStorage;
  32. private $provider;
  33. private $providerKey;
  34. private $authenticationEntryPoint;
  35. private $logger;
  36. public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, $providerKey, DigestAuthenticationEntryPoint $authenticationEntryPoint, LoggerInterface $logger = null)
  37. {
  38. if (empty($providerKey)) {
  39. throw new \InvalidArgumentException('$providerKey must not be empty.');
  40. }
  41. $this->tokenStorage = $tokenStorage;
  42. $this->provider = $provider;
  43. $this->providerKey = $providerKey;
  44. $this->authenticationEntryPoint = $authenticationEntryPoint;
  45. $this->logger = $logger;
  46. }
  47. /**
  48. * Handles digest authentication.
  49. *
  50. * @param GetResponseEvent $event A GetResponseEvent instance
  51. *
  52. * @throws AuthenticationServiceException
  53. */
  54. public function handle(GetResponseEvent $event)
  55. {
  56. $request = $event->getRequest();
  57. if (!$header = $request->server->get('PHP_AUTH_DIGEST')) {
  58. return;
  59. }
  60. $digestAuth = new DigestData($header);
  61. if (null !== $token = $this->tokenStorage->getToken()) {
  62. if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $digestAuth->getUsername()) {
  63. return;
  64. }
  65. }
  66. if (null !== $this->logger) {
  67. $this->logger->debug('Digest Authorization header received from user agent.', array('header' => $header));
  68. }
  69. try {
  70. $digestAuth->validateAndDecode($this->authenticationEntryPoint->getKey(), $this->authenticationEntryPoint->getRealmName());
  71. } catch (BadCredentialsException $e) {
  72. $this->fail($event, $request, $e);
  73. return;
  74. }
  75. try {
  76. $user = $this->provider->loadUserByUsername($digestAuth->getUsername());
  77. if (null === $user) {
  78. throw new AuthenticationServiceException('Digest User provider returned null, which is an interface contract violation');
  79. }
  80. $serverDigestMd5 = $digestAuth->calculateServerDigest($user->getPassword(), $request->getMethod());
  81. } catch (UsernameNotFoundException $e) {
  82. $this->fail($event, $request, new BadCredentialsException(sprintf('Username %s not found.', $digestAuth->getUsername())));
  83. return;
  84. }
  85. if (!StringUtils::equals($serverDigestMd5, $digestAuth->getResponse())) {
  86. if (null !== $this->logger) {
  87. $this->logger->debug('Unexpected response from the DigestAuth received; is the header returning a clear text passwords?', array('expected' => $serverDigestMd5, 'received' => $digestAuth->getResponse()));
  88. }
  89. $this->fail($event, $request, new BadCredentialsException('Incorrect response'));
  90. return;
  91. }
  92. if ($digestAuth->isNonceExpired()) {
  93. $this->fail($event, $request, new NonceExpiredException('Nonce has expired/timed out.'));
  94. return;
  95. }
  96. if (null !== $this->logger) {
  97. $this->logger->info('Digest authentication successful.', array('username' => $digestAuth->getUsername(), 'received' => $digestAuth->getResponse()));
  98. }
  99. $this->tokenStorage->setToken(new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey));
  100. }
  101. private function fail(GetResponseEvent $event, Request $request, AuthenticationException $authException)
  102. {
  103. $token = $this->tokenStorage->getToken();
  104. if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey()) {
  105. $this->tokenStorage->setToken(null);
  106. }
  107. if (null !== $this->logger) {
  108. $this->logger->info('Digest authentication failed.', array('exception' => $authException));
  109. }
  110. $event->setResponse($this->authenticationEntryPoint->start($request, $authException));
  111. }
  112. }
  113. class DigestData
  114. {
  115. private $elements = array();
  116. private $header;
  117. private $nonceExpiryTime;
  118. public function __construct($header)
  119. {
  120. $this->header = $header;
  121. preg_match_all('/(\w+)=("((?:[^"\\\\]|\\\\.)+)"|([^\s,$]+))/', $header, $matches, PREG_SET_ORDER);
  122. foreach ($matches as $match) {
  123. if (isset($match[1]) && isset($match[3])) {
  124. $this->elements[$match[1]] = isset($match[4]) ? $match[4] : $match[3];
  125. }
  126. }
  127. }
  128. public function getResponse()
  129. {
  130. return $this->elements['response'];
  131. }
  132. public function getUsername()
  133. {
  134. return strtr($this->elements['username'], array('\\"' => '"', '\\\\' => '\\'));
  135. }
  136. public function validateAndDecode($entryPointKey, $expectedRealm)
  137. {
  138. if ($keys = array_diff(array('username', 'realm', 'nonce', 'uri', 'response'), array_keys($this->elements))) {
  139. throw new BadCredentialsException(sprintf('Missing mandatory digest value; received header "%s" (%s)', $this->header, implode(', ', $keys)));
  140. }
  141. if ('auth' === $this->elements['qop']) {
  142. if (!isset($this->elements['nc']) || !isset($this->elements['cnonce'])) {
  143. throw new BadCredentialsException(sprintf('Missing mandatory digest value; received header "%s"', $this->header));
  144. }
  145. }
  146. if ($expectedRealm !== $this->elements['realm']) {
  147. throw new BadCredentialsException(sprintf('Response realm name "%s" does not match system realm name of "%s".', $this->elements['realm'], $expectedRealm));
  148. }
  149. if (false === $nonceAsPlainText = base64_decode($this->elements['nonce'])) {
  150. throw new BadCredentialsException(sprintf('Nonce is not encoded in Base64; received nonce "%s".', $this->elements['nonce']));
  151. }
  152. $nonceTokens = explode(':', $nonceAsPlainText);
  153. if (2 !== count($nonceTokens)) {
  154. throw new BadCredentialsException(sprintf('Nonce should have yielded two tokens but was "%s".', $nonceAsPlainText));
  155. }
  156. $this->nonceExpiryTime = $nonceTokens[0];
  157. if (md5($this->nonceExpiryTime.':'.$entryPointKey) !== $nonceTokens[1]) {
  158. throw new BadCredentialsException(sprintf('Nonce token compromised "%s".', $nonceAsPlainText));
  159. }
  160. }
  161. public function calculateServerDigest($password, $httpMethod)
  162. {
  163. $a2Md5 = md5(strtoupper($httpMethod).':'.$this->elements['uri']);
  164. $a1Md5 = md5($this->elements['username'].':'.$this->elements['realm'].':'.$password);
  165. $digest = $a1Md5.':'.$this->elements['nonce'];
  166. if (!isset($this->elements['qop'])) {
  167. } elseif ('auth' === $this->elements['qop']) {
  168. $digest .= ':'.$this->elements['nc'].':'.$this->elements['cnonce'].':'.$this->elements['qop'];
  169. } else {
  170. throw new \InvalidArgumentException('This method does not support a qop: "%s".', $this->elements['qop']);
  171. }
  172. $digest .= ':'.$a2Md5;
  173. return md5($digest);
  174. }
  175. public function isNonceExpired()
  176. {
  177. return $this->nonceExpiryTime < microtime(true);
  178. }
  179. }