diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index b963efe801..513d4de3f5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -57,15 +57,15 @@ class RedirectController extends ContainerAware * In case the path is empty, the status code will be 404 when permanent is false * and 410 otherwise. * - * @param string $path The absolute path or URL to redirect to - * @param Boolean $permanent Whether the redirection is permanent - * @param Boolean $scheme The URL scheme (null to keep the current one) - * @param integer $httpPort The HTTP port - * @param integer $httpsPort The HTTPS port + * @param string $path The absolute path or URL to redirect to + * @param Boolean $permanent Whether the redirect is permanent or not + * @param string|null $scheme The URL scheme (null to keep the current one) + * @param integer|null $httpPort The HTTP port (null to keep the current one for the same scheme or the configured port in the container) + * @param integer|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the configured port in the container) * * @return Response A Response instance */ - public function urlRedirectAction($path, $permanent = false, $scheme = null, $httpPort = 80, $httpsPort = 443) + public function urlRedirectAction($path, $permanent = false, $scheme = null, $httpPort = null, $httpsPort = null) { if ('' == $path) { return new Response(null, $permanent ? 410 : 404); @@ -89,10 +89,30 @@ class RedirectController extends ContainerAware } $port = ''; - if ('http' === $scheme && 80 != $httpPort) { - $port = ':'.$httpPort; - } elseif ('https' === $scheme && 443 != $httpsPort) { - $port = ':'.$httpsPort; + if ('http' === $scheme) { + if (null === $httpPort) { + if ('http' === $request->getScheme()) { + $httpPort = $request->getPort(); + } elseif ($this->container->hasParameter('request_listener.http_port')) { + $httpPort = $this->container->getParameter('request_listener.http_port'); + } + } + + if (null !== $httpPort && 80 != $httpPort) { + $port = ":$httpPort"; + } + } elseif ('https' === $scheme) { + if (null === $httpsPort) { + if ('https' === $request->getScheme()) { + $httpsPort = $request->getPort(); + } elseif ($this->container->hasParameter('request_listener.https_port')) { + $httpsPort = $this->container->getParameter('request_listener.https_port'); + } + } + + if (null !== $httpsPort && 443 != $httpsPort) { + $port = ":$httpsPort"; + } } $url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$path.$qs; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index 04763e069f..171116585b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -87,9 +87,7 @@ class RedirectControllerTest extends TestCase $returnResponse = $controller->redirectAction($route, $permanent); - $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); - - $this->assertTrue($returnResponse->isRedirect($url)); + $this->assertRedirectUrl($returnResponse, $url); $this->assertEquals($expectedCode, $returnResponse->getStatusCode()); } @@ -119,9 +117,143 @@ class RedirectControllerTest extends TestCase $controller = new RedirectController(); $returnResponse = $controller->urlRedirectAction('http://foo.bar/'); - $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); - - $this->assertEquals('http://foo.bar/', $returnResponse->headers->get('Location')); + $this->assertRedirectUrl($returnResponse, 'http://foo.bar/'); $this->assertEquals(302, $returnResponse->getStatusCode()); } + + public function testUrlRedirectDefaultPortParameters() + { + $host = 'www.example.com'; + $baseUrl = '/base'; + $path = '/redirect-path'; + $httpPort = 1080; + $httpsPort = 1443; + + $expectedUrl = "https://$host:$httpsPort$baseUrl$path"; + $request = $this->createRequestObject('http', $host, $httpPort, $baseUrl); + $controller = $this->createRedirectController($request, null, $httpsPort); + $returnValue = $controller->urlRedirectAction($path, false, 'https'); + $this->assertRedirectUrl($returnValue, $expectedUrl); + + $expectedUrl = "http://$host:$httpPort$baseUrl$path"; + $request = $this->createRequestObject('https', $host, $httpPort, $baseUrl); + $controller = $this->createRedirectController($request, $httpPort); + $returnValue = $controller->urlRedirectAction($path, false, 'http'); + $this->assertRedirectUrl($returnValue, $expectedUrl); + } + + public function urlRedirectProvider() + { + return array( + // Standard ports + array('http', null, null, 'http', 80, ""), + array('http', 80, null, 'http', 80, ""), + array('https', null, null, 'http', 80, ""), + array('https', 80, null, 'http', 80, ""), + + array('http', null, null, 'https', 443, ""), + array('http', null, 443, 'https', 443, ""), + array('https', null, null, 'https', 443, ""), + array('https', null, 443, 'https', 443, ""), + + // Non-standard ports + array('http', null, null, 'http', 8080, ":8080"), + array('http', 4080, null, 'http', 8080, ":4080"), + array('http', 80, null, 'http', 8080, ""), + array('https', null, null, 'http', 8080, ""), + array('https', null, 8443, 'http', 8080, ":8443"), + array('https', null, 443, 'http', 8080, ""), + + array('https', null, null, 'https', 8443, ":8443"), + array('https', null, 4443, 'https', 8443, ":4443"), + array('https', null, 443, 'https', 8443, ""), + array('http', null, null, 'https', 8443, ""), + array('http', 8080, 4443, 'https', 8443, ":8080"), + array('http', 80, 4443, 'https', 8443, ""), + ); + } + + /** + * @dataProvider urlRedirectProvider + */ + public function testUrlRedirect($scheme, $httpPort, $httpsPort, $requestScheme, $requestPort, $expectedPort) + { + $host = 'www.example.com'; + $baseUrl = '/base'; + $path = '/redirect-path'; + $expectedUrl = "$scheme://$host$expectedPort$baseUrl$path"; + + $request = $this->createRequestObject($requestScheme, $host, $requestPort, $baseUrl); + $controller = $this->createRedirectController($request); + + $returnValue = $controller->urlRedirectAction($path, false, $scheme, $httpPort, $httpsPort); + $this->assertRedirectUrl($returnValue, $expectedUrl); + } + + private function createRequestObject($scheme, $host, $port, $baseUrl) + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request + ->expects($this->any()) + ->method('getScheme') + ->will($this->returnValue($scheme)); + $request + ->expects($this->any()) + ->method('getHost') + ->will($this->returnValue($host)); + $request + ->expects($this->any()) + ->method('getPort') + ->will($this->returnValue($port)); + $request + ->expects($this->any()) + ->method('getBaseUrl') + ->will($this->returnValue($baseUrl)); + + return $request; + } + + private function createRedirectController(Request $request, $httpPort = null, $httpsPort = null) + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->at(0)) + ->method('get') + ->with($this->equalTo('request')) + ->will($this->returnValue($request)); + if (null !== $httpPort) { + $container + ->expects($this->once()) + ->method('hasParameter') + ->with($this->equalTo('request_listener.http_port')) + ->will($this->returnValue(true)); + $container + ->expects($this->once()) + ->method('getParameter') + ->with($this->equalTo('request_listener.http_port')) + ->will($this->returnValue($httpPort)); + } + if (null !== $httpsPort) { + $container + ->expects($this->once()) + ->method('hasParameter') + ->with($this->equalTo('request_listener.https_port')) + ->will($this->returnValue(true)); + $container + ->expects($this->once()) + ->method('getParameter') + ->with($this->equalTo('request_listener.https_port')) + ->will($this->returnValue($httpsPort)); + } + + $controller = new RedirectController(); + $controller->setContainer($container); + + return $controller; + } + + public function assertRedirectUrl(Response $returnResponse, $expectedUrl) + { + $this->assertTrue($returnResponse->isRedirect($expectedUrl), "Expected: $expectedUrl\nGot: ".$returnResponse->headers->get('Location')); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index de1001aae6..21664dc5b1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -182,7 +182,7 @@ class SecurityExtension extends Extension $container, $access['path'], $access['host'], - count($access['methods']) === 0 ? null : $access['methods'], + $access['methods'], $access['ip'] ); @@ -574,7 +574,7 @@ class SecurityExtension extends Extension return $switchUserListenerId; } - private function createRequestMatcher($container, $path = null, $host = null, $methods = null, $ip = null, array $attributes = array()) + private function createRequestMatcher($container, $path = null, $host = null, $methods = array(), $ip = null, array $attributes = array()) { $serialized = serialize(array($path, $host, $methods, $ip, $attributes)); $id = 'security.request_matcher.'.md5($serialized).sha1($serialized); @@ -583,6 +583,10 @@ class SecurityExtension extends Extension return $this->requestMatchers[$id]; } + if ($methods) { + $methods = array_map('strtoupper', (array) $methods); + } + // only add arguments that are necessary $arguments = array($path, $host, $methods, $ip, $attributes); while (count($arguments) > 0 && !end($arguments)) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index 8652333b85..235d324d78 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -63,7 +63,7 @@ $container->loadFromExtension('security', array( ), 'access_control' => array( - array('path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https'), + array('path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https', 'methods' => array('get', 'POST')), array('path' => '/blog/.*', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), ), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index 20c376075a..ff8cfc0ad3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -57,7 +57,7 @@ ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH ROLE_USER,ROLE_ADMIN - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index ade174fe6f..4c3738f081 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -51,7 +51,7 @@ security: ROLE_REMOTE: ROLE_USER,ROLE_ADMIN access_control: - - { path: /blog/524, role: ROLE_USER, requires_channel: https } + - { path: /blog/524, role: ROLE_USER, requires_channel: https, methods: [get, POST]} - path: /blog/.* role: IS_AUTHENTICATED_ANONYMOUSLY diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 60e1778822..ae5fadbed6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -102,6 +102,7 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase $matcherIds = array(); foreach ($rules as $rule) { list($matcherId, $roles, $channel) = $rule; + $requestMatcher = $container->getDefinition($matcherId); $this->assertFalse(isset($matcherIds[$matcherId])); $matcherIds[$matcherId] = true; @@ -110,9 +111,17 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase if (1 === $i) { $this->assertEquals(array('ROLE_USER'), $roles); $this->assertEquals('https', $channel); + $this->assertEquals( + array('/blog/524', null, array('GET', 'POST')), + $requestMatcher->getArguments() + ); } elseif (2 === $i) { $this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'), $roles); $this->assertNull($channel); + $this->assertEquals( + array('/blog/.*'), + $requestMatcher->getArguments() + ); } } } diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index dbd9d21346..160bd44292 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -359,7 +359,7 @@ class Form extends Link implements \ArrayAccess $xpath = new \DOMXPath($document); foreach ($xpath->query('descendant::input | descendant::button | descendant::textarea | descendant::select', $root) as $node) { - if (!$node->hasAttribute('name')) { + if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { continue; } diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php index b53a8309ca..89a0d3c24c 100644 --- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php @@ -126,6 +126,12 @@ class FormTest extends \PHPUnit_Framework_TestCase ', array(), ), + array( + 'does not take into account input fields with an empty name attribute value', + ' + ', + array(), + ), array( 'takes into account disabled input fields', ' diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php index 282a5247f4..96ece87f30 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php @@ -96,8 +96,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase public function testDecimalSeparatorMayBeDotIfGroupingSeparatorIsNotDot() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } \Locale::setDefault('fr'); @@ -117,8 +117,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase */ public function testDecimalSeparatorMayNotBeDotIfGroupingSeparatorIsDot() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } $transformer = new NumberToLocalizedStringTransformer(null, true); @@ -131,8 +131,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase */ public function testDecimalSeparatorMayNotBeDotIfGroupingSeparatorIsDot_noGroupSep() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } $transformer = new NumberToLocalizedStringTransformer(null, true); @@ -151,8 +151,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase public function testDecimalSeparatorMayBeCommaIfGroupingSeparatorIsNotComma() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } \Locale::setDefault('ak'); @@ -172,8 +172,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase */ public function testDecimalSeparatorMayNotBeCommaIfGroupingSeparatorIsComma() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } \Locale::setDefault('en'); @@ -187,8 +187,8 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase */ public function testDecimalSeparatorMayNotBeCommaIfGroupingSeparatorIsComma_noGroupSep() { - if ($this->isLowerThanIcuVersion('4.5')) { - $this->markTestSkipped('Please upgrade ICU version to 4.5+'); + if ($this->isLowerThanIcuVersion('4.7')) { + $this->markTestSkipped('Please upgrade ICU version to 4.7+'); } \Locale::setDefault('en'); diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php index 5c529dab02..2bc4aa550f 100644 --- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php @@ -141,11 +141,12 @@ class DigestData public function __construct($header) { $this->header = $header; - $parts = preg_split('/, /', $header); + preg_match_all('/(\w+)=("((?:[^"\\\\]|\\\\.)+)"|([^\s,$]+))/', $header, $matches, PREG_SET_ORDER); $this->elements = array(); - foreach ($parts as $part) { - list($key, $value) = explode('=', $part); - $this->elements[$key] = '"' === $value[0] ? substr($value, 1, -1) : $value; + foreach ($matches as $match) { + if (isset($match[1]) && isset($match[3])) { + $this->elements[$match[1]] = isset($match[4]) ? $match[4] : $match[3]; + } } } @@ -156,7 +157,7 @@ class DigestData public function getUsername() { - return $this->elements['username']; + return strtr($this->elements['username'], array("\\\"" => "\"", "\\\\" => "\\")); } public function validateAndDecode($entryPointKey, $expectedRealm) @@ -188,7 +189,7 @@ class DigestData $this->nonceExpiryTime = $nonceTokens[0]; if (md5($this->nonceExpiryTime.':'.$entryPointKey) !== $nonceTokens[1]) { - new BadCredentialsException(sprintf('Nonce token compromised "%s".', $nonceAsPlainText)); + throw new BadCredentialsException(sprintf('Nonce token compromised "%s".', $nonceAsPlainText)); } } diff --git a/tests/Symfony/Tests/Component/Security/Http/Firewall/DigestDataTest.php b/tests/Symfony/Tests/Component/Security/Http/Firewall/DigestDataTest.php new file mode 100644 index 0000000000..cfb929cacc --- /dev/null +++ b/tests/Symfony/Tests/Component/Security/Http/Firewall/DigestDataTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Tests\Http\Firewall; + +use Symfony\Component\Security\Http\Firewall\DigestData; + +class DigestDataTest extends \PHPUnit_Framework_TestCase +{ + public function testGetResponse() + { + $digestAuth = new DigestData( + 'username="user", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('b52938fc9e6d7c01be7702ece9031b42', $digestAuth->getResponse()); + } + + public function testGetUsername() + { + $digestAuth = new DigestData( + 'username="user", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('user', $digestAuth->getUsername()); + } + + public function testGetUsernameWithQuote() + { + $digestAuth = new DigestData( + 'username="\"user\"", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('"user"', $digestAuth->getUsername()); + } + + public function testGetUsernameWithQuoteAndEscape() + { + $digestAuth = new DigestData( + 'username="\"u\\\\\"ser\"", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('"u\\"ser"', $digestAuth->getUsername()); + } + + public function testGetUsernameWithSingleQuote() + { + $digestAuth = new DigestData( + 'username="\"u\'ser\"", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('"u\'ser"', $digestAuth->getUsername()); + } + + public function testGetUsernameWithSingleQuoteAndEscape() + { + $digestAuth = new DigestData( + 'username="\"u\\\'ser\"", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('"u\\\'ser"', $digestAuth->getUsername()); + } + + public function testGetUsernameWithEscape() + { + $digestAuth = new DigestData( + 'username="\"u\\ser\"", realm="Welcome, robot!", ' . + 'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $this->assertEquals('"u\\ser"', $digestAuth->getUsername()); + } + + public function testValidateAndDecode() + { + $time = microtime(true); + $key = 'ThisIsAKey'; + $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + + $digestAuth = new DigestData( + 'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + try { + $digestAuth->validateAndDecode($key, 'Welcome, robot!'); + } catch (\Exception $e) { + $this->fail(sprintf('testValidateAndDecode fail with message: %s', $e->getMessage())); + } + } + + public function testCalculateServerDigest() + { + $this->calculateServerDigest('user', 'Welcome, robot!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5'); + } + + public function testCalculateServerDigestWithQuote() + { + $this->calculateServerDigest('\"user\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5'); + } + + public function testCalculateServerDigestWithQuoteAndEscape() + { + $this->calculateServerDigest('\"u\\\\\"ser\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5'); + } + + public function testCalculateServerDigestEscape() + { + $this->calculateServerDigest('\"u\\ser\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5'); + $this->calculateServerDigest('\"u\\ser\\\\\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5'); + } + + public function testIsNonceExpired() + { + $time = microtime(true) + 10; + $key = 'ThisIsAKey'; + $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + + $digestAuth = new DigestData( + 'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' . + 'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' . + 'response="b52938fc9e6d7c01be7702ece9031b42"' + ); + + $digestAuth->validateAndDecode($key, 'Welcome, robot!'); + + $this->assertFalse($digestAuth->isNonceExpired()); + } + + protected function setUp() + { + class_exists('Symfony\Component\Security\Http\Firewall\DigestAuthenticationListener', true); + } + + private function calculateServerDigest($username, $realm, $password, $key, $nc, $cnonce, $qop, $method, $uri) + { + $time = microtime(true); + $nonce = base64_encode($time . ':' . md5($time . ':' . $key)); + + $response = md5( + md5($username . ':' . $realm . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . md5($method . ':' . $uri) + ); + + $digest = sprintf('username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=%s, qop="%s", response="%s"', + $username, $realm, $nonce, $uri, $cnonce, $nc, $qop, $response + ); + + $digestAuth = new DigestData($digest); + + $this->assertEquals($digestAuth->getResponse(), $digestAuth->calculateServerDigest($password, $method)); + } +}