PageRenderTime 26ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

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