diff options
32 files changed, 299 insertions, 98 deletions
diff --git a/.gitattributes b/.gitattributes index e742c9b..8048151 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -Tests/ export-ignore +/Tests export-ignore phpunit.xml.dist export-ignore @@ -1,2 +1,4 @@ vendor/ composer.lock +phpunit.xml + diff --git a/Acl/Domain/AclCollectionCache.php b/Acl/Domain/AclCollectionCache.php index 88c017c..d3a4b37 100644 --- a/Acl/Domain/AclCollectionCache.php +++ b/Acl/Domain/AclCollectionCache.php @@ -44,8 +44,8 @@ class AclCollectionCache * Batch loads ACLs for an entire collection; thus, it reduces the number * of required queries considerably. * - * @param mixed $collection anything that can be passed to foreach() - * @param array $tokens an array of TokenInterface implementations + * @param mixed $collection anything that can be passed to foreach() + * @param TokenInterface[] $tokens an array of TokenInterface implementations */ public function cache($collection, array $tokens = array()) { diff --git a/Acl/Domain/PermissionGrantingStrategy.php b/Acl/Domain/PermissionGrantingStrategy.php index 3b4e99a..e075861 100644 --- a/Acl/Domain/PermissionGrantingStrategy.php +++ b/Acl/Domain/PermissionGrantingStrategy.php @@ -16,6 +16,7 @@ use Symfony\Component\Security\Acl\Model\AclInterface; use Symfony\Component\Security\Acl\Model\AuditLoggerInterface; use Symfony\Component\Security\Acl\Model\EntryInterface; use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface; +use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; /** * The permission granting strategy to apply to the access control list. @@ -120,16 +121,15 @@ class PermissionGrantingStrategy implements PermissionGrantingStrategyInterface * permission/identity combination. * * This process is repeated until either a granting ACE is found, or no - * permission/identity combinations are left. In the latter case, we will - * call this method on the parent ACL if it exists, and isEntriesInheriting - * is true. Otherwise, we will either throw an NoAceFoundException, or deny - * access finally. + * permission/identity combinations are left. Finally, we will either throw + * an NoAceFoundException, or deny access. + * + * @param AclInterface $acl + * @param EntryInterface[] $aces An array of ACE to check against + * @param array $masks An array of permission masks + * @param SecurityIdentityInterface[] $sids An array of SecurityIdentityInterface implementations + * @param Boolean $administrativeMode True turns off audit logging * - * @param AclInterface $acl - * @param array $aces An array of ACE to check against - * @param array $masks An array of permission masks - * @param array $sids An array of SecurityIdentityInterface implementations - * @param Boolean $administrativeMode True turns off audit logging * @return Boolean true, or false; either granting, or denying access respectively. */ private function hasSufficientPermissions(AclInterface $acl, array $aces, array $masks, array $sids, $administrativeMode) @@ -188,7 +188,10 @@ class PermissionGrantingStrategy implements PermissionGrantingStrategyInterface * * @param integer $requiredMask * @param EntryInterface $ace + * * @return Boolean + * + * @throws \RuntimeException if the ACE strategy is not supported */ private function isAceApplicable($requiredMask, EntryInterface $ace) { diff --git a/Acl/Model/AclProviderInterface.php b/Acl/Model/AclProviderInterface.php index bdb40b7..4be49bf 100644 --- a/Acl/Model/AclProviderInterface.php +++ b/Acl/Model/AclProviderInterface.php @@ -23,6 +23,7 @@ interface AclProviderInterface * * @param ObjectIdentityInterface $parentOid * @param Boolean $directChildrenOnly + * * @return array returns an array of child 'ObjectIdentity's */ public function findChildren(ObjectIdentityInterface $parentOid, $directChildrenOnly = false); @@ -30,20 +31,24 @@ interface AclProviderInterface /** * Returns the ACL that belongs to the given object identity * - * @throws AclNotFoundException when there is no ACL - * @param ObjectIdentityInterface $oid - * @param array $sids + * @param ObjectIdentityInterface $oid + * @param SecurityIdentityInterface[] $sids + * * @return AclInterface + * + * @throws AclNotFoundException when there is no ACL */ public function findAcl(ObjectIdentityInterface $oid, array $sids = array()); /** * Returns the ACLs that belong to the given object identities * - * @throws AclNotFoundException when we cannot find an ACL for all identities - * @param array $oids an array of ObjectIdentityInterface implementations - * @param array $sids an array of SecurityIdentityInterface implementations + * @param ObjectIdentityInterface[] $oids an array of ObjectIdentityInterface implementations + * @param SecurityIdentityInterface[] $sids an array of SecurityIdentityInterface implementations + * * @return \SplObjectStorage mapping the passed object identities to ACLs + * + * @throws AclNotFoundException when we cannot find an ACL for all identities */ public function findAcls(array $oids, array $sids = array()); } diff --git a/Acl/Model/SecurityIdentityRetrievalStrategyInterface.php b/Acl/Model/SecurityIdentityRetrievalStrategyInterface.php index c20f04b..5bb7915 100644 --- a/Acl/Model/SecurityIdentityRetrievalStrategyInterface.php +++ b/Acl/Model/SecurityIdentityRetrievalStrategyInterface.php @@ -28,7 +28,8 @@ interface SecurityIdentityRetrievalStrategyInterface * least specific. * * @param TokenInterface $token - * @return array of SecurityIdentityInterface implementations + * + * @return SecurityIdentityInterface[] An array of SecurityIdentityInterface implementations */ public function getSecurityIdentities(TokenInterface $token); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 279c614..1305b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 2.2.0 ----- + * `Symfony\Component\Security\Http\Firewall` and + `Symfony\Component\Security\Http\RememberMe\ResponseListener` now + implements EventSubscriberInterface * added secure random number generator * added PBKDF2 Password encoder diff --git a/Core/Authentication/Token/AbstractToken.php b/Core/Authentication/Token/AbstractToken.php index ed6e8de..68cbb79 100644 --- a/Core/Authentication/Token/AbstractToken.php +++ b/Core/Authentication/Token/AbstractToken.php @@ -33,7 +33,7 @@ abstract class AbstractToken implements TokenInterface /** * Constructor. * - * @param Role[] $roles An array of roles + * @param RoleInterface[] $roles An array of roles */ public function __construct(array $roles = array()) { diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php index ecdd4cc..9b0a084 100644 --- a/Core/Authentication/Token/AnonymousToken.php +++ b/Core/Authentication/Token/AnonymousToken.php @@ -24,9 +24,9 @@ class AnonymousToken extends AbstractToken /** * Constructor. * - * @param string $key The key shared with the authentication provider - * @param string $user The user - * @param Role[] $roles An array of roles + * @param string $key The key shared with the authentication provider + * @param string $user The user + * @param RoleInterface[] $roles An array of roles */ public function __construct($key, $user, array $roles = array()) { @@ -66,9 +66,9 @@ class AnonymousToken extends AbstractToken /** * {@inheritDoc} */ - public function unserialize($str) + public function unserialize($serialized) { - list($this->key, $parentStr) = unserialize($str); + list($this->key, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authentication/Token/TokenInterface.php b/Core/Authentication/Token/TokenInterface.php index dec31d5..11f69da 100644 --- a/Core/Authentication/Token/TokenInterface.php +++ b/Core/Authentication/Token/TokenInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\Role\RoleInterface; + /** * TokenInterface is the interface for the user authentication information. * @@ -31,7 +33,7 @@ interface TokenInterface extends \Serializable /** * Returns the user roles. * - * @return Role[] An array of Role instances. + * @return RoleInterface[] An array of RoleInterface instances. */ public function getRoles(); diff --git a/Core/Authentication/Token/UsernamePasswordToken.php b/Core/Authentication/Token/UsernamePasswordToken.php index 95eec54..d6e3998 100644 --- a/Core/Authentication/Token/UsernamePasswordToken.php +++ b/Core/Authentication/Token/UsernamePasswordToken.php @@ -24,10 +24,10 @@ class UsernamePasswordToken extends AbstractToken /** * Constructor. * - * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. - * @param string $credentials This usually is the password of the user - * @param string $providerKey The provider key - * @param array $roles An array of roles + * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. + * @param string $credentials This usually is the password of the user + * @param string $providerKey The provider key + * @param RoleInterface[] $roles An array of roles * * @throws \InvalidArgumentException */ @@ -78,14 +78,20 @@ class UsernamePasswordToken extends AbstractToken $this->credentials = null; } + /** + * {@inheritdoc} + */ public function serialize() { return serialize(array($this->credentials, $this->providerKey, parent::serialize())); } - public function unserialize($str) + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { - list($this->credentials, $this->providerKey, $parentStr) = unserialize($str); + list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Role/RoleHierarchyInterface.php b/Core/Role/RoleHierarchyInterface.php index c495a7f..2ea6ca3 100644 --- a/Core/Role/RoleHierarchyInterface.php +++ b/Core/Role/RoleHierarchyInterface.php @@ -24,9 +24,9 @@ interface RoleHierarchyInterface * Reachable roles are the roles directly assigned but also all roles that * are transitively reachable from them in the role hierarchy. * - * @param array $roles An array of directly assigned roles + * @param RoleInterface[] $roles An array of directly assigned roles * - * @return array An array of all reachable roles + * @return RoleInterface[] An array of all reachable roles */ public function getReachableRoles(array $roles); } diff --git a/Core/Role/RoleInterface.php b/Core/Role/RoleInterface.php index a3cb266..3d4cbea 100644 --- a/Core/Role/RoleInterface.php +++ b/Core/Role/RoleInterface.php @@ -15,7 +15,7 @@ namespace Symfony\Component\Security\Core\Role; * RoleInterface represents a role granted to a user. * * A role must either have a string representation or it needs to be explicitly - * supported by an at least one AccessDecisionManager. + * supported by at least one AccessDecisionManager. * * @author Fabien Potencier <fabien@symfony.com> */ diff --git a/Core/Util/ClassUtils.php b/Core/Util/ClassUtils.php index 7b583a3..26bf1a1 100644 --- a/Core/Util/ClassUtils.php +++ b/Core/Util/ClassUtils.php @@ -37,6 +37,11 @@ class ClassUtils const MARKER_LENGTH = 6; /** + * This class should not be instantiated + */ + private function __construct() {} + + /** * Gets the real class name of a class name that could be a proxy. * * @param string|object diff --git a/Core/Util/SecureRandomInterface.php b/Core/Util/SecureRandomInterface.php index 64830a9..bb70a7e 100644 --- a/Core/Util/SecureRandomInterface.php +++ b/Core/Util/SecureRandomInterface.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Core\Util; -use Symfony\Component\HttpKernel\Log\LoggerInterface; /** * Interface that needs to be implemented by all secure random number generators. diff --git a/Core/Util/StringUtils.php b/Core/Util/StringUtils.php index a73b471..d21efd3 100644 --- a/Core/Util/StringUtils.php +++ b/Core/Util/StringUtils.php @@ -16,11 +16,12 @@ namespace Symfony\Component\Security\Core\Util; * * @author Fabien Potencier <fabien@symfony.com> */ -final class StringUtils +class StringUtils { - final private function __construct() - { - } + /** + * This class should not be instantiated + */ + private function __construct() {} /** * Compares two strings. diff --git a/Http/EntryPoint/BasicAuthenticationEntryPoint.php b/Http/EntryPoint/BasicAuthenticationEntryPoint.php index 6ba3872..44ece5e 100644 --- a/Http/EntryPoint/BasicAuthenticationEntryPoint.php +++ b/Http/EntryPoint/BasicAuthenticationEntryPoint.php @@ -34,7 +34,7 @@ class BasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface { $response = new Response(); $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName)); - $response->setStatusCode(401, $authException ? $authException->getMessage() : null); + $response->setStatusCode(401); return $response; } diff --git a/Http/EntryPoint/DigestAuthenticationEntryPoint.php b/Http/EntryPoint/DigestAuthenticationEntryPoint.php index ec92419..37fba85 100644 --- a/Http/EntryPoint/DigestAuthenticationEntryPoint.php +++ b/Http/EntryPoint/DigestAuthenticationEntryPoint.php @@ -57,7 +57,7 @@ class DigestAuthenticationEntryPoint implements AuthenticationEntryPointInterfac $response = new Response(); $response->headers->set('WWW-Authenticate', $authenticateHeader); - $response->setStatusCode(401, $authException ? $authException->getMessage() : null); + $response->setStatusCode(401); return $response; } diff --git a/Http/Firewall.php b/Http/Firewall.php index a590fd9..e083fdb 100644 --- a/Http/Firewall.php +++ b/Http/Firewall.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Security\Http; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Firewall uses a FirewallMap to register security listeners for the given @@ -25,7 +27,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; * * @author Fabien Potencier <fabien@symfony.com> */ -class Firewall +class Firewall implements EventSubscriberInterface { private $map; private $dispatcher; @@ -68,4 +70,9 @@ class Firewall } } } + + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('onKernelRequest', 8)); + } } diff --git a/Http/Firewall/DigestAuthenticationListener.php b/Http/Firewall/DigestAuthenticationListener.php index 5c529da..2bc4aa5 100644 --- a/Http/Firewall/DigestAuthenticationListener.php +++ b/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/Http/RememberMe/AbstractRememberMeServices.php b/Http/RememberMe/AbstractRememberMeServices.php index 4f7c5b9..1d6a109 100644 --- a/Http/RememberMe/AbstractRememberMeServices.php +++ b/Http/RememberMe/AbstractRememberMeServices.php @@ -172,6 +172,9 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface */ final public function loginSuccess(Request $request, Response $response, TokenInterface $token) { + // Make sure any old remember-me cookies are cancelled + $this->cancelCookie($request); + if (!$token->getUser() instanceof UserInterface) { if (null !== $this->logger) { $this->logger->debug('Remember-me ignores token since it does not contain a UserInterface implementation.'); @@ -192,6 +195,12 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface $this->logger->debug('Remember-me was requested; setting cookie.'); } + // Remove attribute from request that sets a NULL cookie. + // It was set by $this->cancelCookie() + // (cancelCookie does other things too for some RememberMeServices + // so we should still call it at the start of this method) + $request->attributes->remove(self::COOKIE_ATTR_NAME); + $this->onLoginSuccess($request, $response, $token); } diff --git a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index dbb6429..9f4013d 100644 --- a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -63,10 +63,12 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices /** * {@inheritDoc} */ - public function logout(Request $request, Response $response, TokenInterface $token) + protected function cancelCookie(Request $request) { - parent::logout($request, $response, $token); + // Delete cookie on the client + parent::cancelCookie($request); + // Delete cookie from the tokenProvider if (null !== ($cookie = $request->cookies->get($this->options['name'])) && count($parts = $this->decodeCookie($cookie)) === 2 ) { @@ -88,8 +90,6 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices $persistentToken = $this->tokenProvider->loadTokenBySeries($series); if ($persistentToken->getTokenValue() !== $tokenValue) { - $this->tokenProvider->deleteTokenBySeries($series); - throw new CookieTheftException('This token was already used. The account is possibly compromised.'); } diff --git a/Http/RememberMe/ResponseListener.php b/Http/RememberMe/ResponseListener.php index 6cbdcb3..03c71c7 100644 --- a/Http/RememberMe/ResponseListener.php +++ b/Http/RememberMe/ResponseListener.php @@ -12,13 +12,15 @@ namespace Symfony\Component\Security\Http\RememberMe; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Adds remember-me cookies to the Response. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ -class ResponseListener +class ResponseListener implements EventSubscriberInterface { public function onKernelResponse(FilterResponseEvent $event) { @@ -29,4 +31,9 @@ class ResponseListener $response->headers->setCookie($request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME)); } } + + public static function getSubscribedEvents() + { + return array(KernelEvents::RESPONSE => 'onKernelResponse'); + } } @@ -18,9 +18,6 @@ Resources You can run the unit tests with the following command: - phpunit - -If you also want to run the unit tests that depend on other Symfony -Components, install dev dependencies before running PHPUnit: - - php composer.phar install --dev + $ cd path/to/Symfony/Component/Security/ + $ composer.phar install --dev + $ phpunit diff --git a/Tests/Core/Util/SecureRandomTest.php b/Tests/Core/Util/SecureRandomTest.php index 230a26a..c7ed016 100755 --- a/Tests/Core/Util/SecureRandomTest.php +++ b/Tests/Core/Util/SecureRandomTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Tests\Core\Util; -use Symfony\Component\Security\Core\Util\NullSeedProvider; use Symfony\Component\Security\Core\Util\SecureRandom; class SecureRandomTest extends \PHPUnit_Framework_TestCase diff --git a/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php b/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php index b442309..b9e289d 100644 --- a/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php +++ b/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php @@ -34,7 +34,6 @@ class BasicAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $this->assertEquals('Basic realm="TheRealmName"', $response->headers->get('WWW-Authenticate')); $this->assertEquals(401, $response->getStatusCode()); - $this->assertAttributeEquals('The exception message', 'statusText', $response); } public function testStartWithoutAuthException() @@ -47,6 +46,5 @@ class BasicAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $this->assertEquals('Basic realm="TheRealmName"', $response->headers->get('WWW-Authenticate')); $this->assertEquals(401, $response->getStatusCode()); - $this->assertAttributeEquals('Unauthorized', 'statusText', $response); } } diff --git a/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php b/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php index ae0e3cc..8dfd618 100644 --- a/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php +++ b/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php @@ -34,7 +34,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $response = $entryPoint->start($request, $authenticationException); $this->assertEquals(401, $response->getStatusCode()); - $this->assertAttributeEquals('TheAuthenticationExceptionMessage', 'statusText', $response); $this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}"$/', $response->headers->get('WWW-Authenticate')); } @@ -46,7 +45,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $response = $entryPoint->start($request); $this->assertEquals(401, $response->getStatusCode()); - $this->assertAttributeEquals('Unauthorized', 'statusText', $response); $this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}"$/', $response->headers->get('WWW-Authenticate')); } @@ -60,7 +58,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase $response = $entryPoint->start($request, $nonceExpiredException); $this->assertEquals(401, $response->getStatusCode()); - $this->assertAttributeEquals('TheNonceExpiredExceptionMessage', 'statusText', $response); $this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}", stale="true"$/', $response->headers->get('WWW-Authenticate')); } } diff --git a/Tests/Http/Firewall/DigestDataTest.php b/Tests/Http/Firewall/DigestDataTest.php new file mode 100644 index 0000000..cfb929c --- /dev/null +++ b/Tests/Http/Firewall/DigestDataTest.php @@ -0,0 +1,181 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * 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)); + } +} diff --git a/Tests/Http/RememberMe/AbstractRememberMeServicesTest.php b/Tests/Http/RememberMe/AbstractRememberMeServicesTest.php index fc8dffb..8571686 100644 --- a/Tests/Http/RememberMe/AbstractRememberMeServicesTest.php +++ b/Tests/Http/RememberMe/AbstractRememberMeServicesTest.php @@ -39,7 +39,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testAutoLoginReturnsNullWhenNoCookie() { - $service = $this->getService(null, array('name' => 'foo')); + $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); $this->assertNull($service->autoLogin(new Request())); } @@ -49,7 +49,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase */ public function testAutoLoginThrowsExceptionWhenImplementationDoesNotReturnUserInterface() { - $service = $this->getService(null, array('name' => 'foo')); + $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); $request = new Request; $request->cookies->set('foo', 'foo'); @@ -64,7 +64,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testAutoLogin() { - $service = $this->getService(null, array('name' => 'foo')); + $service = $this->getService(null, array('name' => 'foo', 'path' => null, 'domain' => null)); $request = new Request(); $request->cookies->set('foo', 'foo'); @@ -112,7 +112,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testLoginSuccessIsNotProcessedWhenTokenDoesNotContainUserInterfaceImplementation() { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true)); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true, 'path' => null, 'domain' => null)); $request = new Request; $response = new Response; $account = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); @@ -135,7 +135,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testLoginSuccessIsNotProcessedWhenRememberMeIsNotRequested() { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo')); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo', 'path' => null, 'domain' => null)); $request = new Request; $response = new Response; $account = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); @@ -159,7 +159,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testLoginSuccessWhenRememberMeAlwaysIsTrue() { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true)); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true, 'path' => null, 'domain' => null)); $request = new Request; $response = new Response; $account = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); @@ -184,7 +184,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase */ public function testLoginSuccessWhenRememberMeParameterWithPathIsPositive($value) { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo[bar]')); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo[bar]', 'path' => null, 'domain' => null)); $request = new Request; $request->request->set('foo', array('bar' => $value)); @@ -211,7 +211,7 @@ class AbstractRememberMeServicesTest extends \PHPUnit_Framework_TestCase */ public function testLoginSuccessWhenRememberMeParameterIsPositive($value) { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo')); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => false, 'remember_me_parameter' => 'foo', 'path' => null, 'domain' => null)); $request = new Request; $request->request->set('foo', $value); diff --git a/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php b/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php index 407db02..6de69f1 100644 --- a/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php +++ b/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php @@ -179,7 +179,7 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase public function testLoginSuccessIgnoresTokensWhichDoNotContainAnUserInterfaceImplementation() { - $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true)); + $service = $this->getService(null, array('name' => 'foo', 'always_remember_me' => true, 'path' => null, 'domain' => null)); $request = new Request; $response = new Response; $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php deleted file mode 100644 index 84ae3a6..0000000 --- a/Tests/bootstrap.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -spl_autoload_register(function ($class) { - if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\Security')) { - if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\Security')).'.php')) { - require_once $file; - } - } -}); - -if (file_exists($loader = __DIR__.'/../vendor/autoload.php')) { - require_once $loader; -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0560cf5..f45a44e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ processIsolation="false" stopOnFailure="false" syntaxCheck="false" - bootstrap="Tests/bootstrap.php" + bootstrap="vendor/autoload.php" > <testsuites> <testsuite name="Symfony Security Component Test Suite"> |