[Security][ACL] Fixed ObjectIdentity::fromDomainObject and UserSecurityIdentity::from(Account|Token) when working with proxies

Backported ClassUtils class from Doctrine Common 2.2
Fixes #2611, #2056, #2048, #2035
This commit is contained in:
Jordan Alliot 2012-04-07 22:09:48 +02:00
parent b47cb35e7b
commit 6483d88f69
6 changed files with 200 additions and 68 deletions

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Security\Acl\Domain; namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Core\Util\ClassUtils;
use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException; use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException;
use Symfony\Component\Security\Acl\Model\DomainObjectInterface; use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
@ -59,9 +60,9 @@ final class ObjectIdentity implements ObjectIdentityInterface
try { try {
if ($domainObject instanceof DomainObjectInterface) { if ($domainObject instanceof DomainObjectInterface) {
return new self($domainObject->getObjectIdentifier(), get_class($domainObject)); return new self($domainObject->getObjectIdentifier(), ClassUtils::getRealClass($domainObject));
} elseif (method_exists($domainObject, 'getId')) { } elseif (method_exists($domainObject, 'getId')) {
return new self($domainObject->getId(), get_class($domainObject)); return new self($domainObject->getId(), ClassUtils::getRealClass($domainObject));
} }
} catch (\InvalidArgumentException $invalid) { } catch (\InvalidArgumentException $invalid) {
throw new InvalidDomainObjectException($invalid->getMessage(), 0, $invalid); throw new InvalidDomainObjectException($invalid->getMessage(), 0, $invalid);

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Util\ClassUtils;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/** /**
@ -52,7 +53,7 @@ final class UserSecurityIdentity implements SecurityIdentityInterface
*/ */
static public function fromAccount(UserInterface $user) static public function fromAccount(UserInterface $user)
{ {
return new self($user->getUsername(), get_class($user)); return new self($user->getUsername(), ClassUtils::getRealClass($user));
} }
/** /**
@ -69,7 +70,7 @@ final class UserSecurityIdentity implements SecurityIdentityInterface
return self::fromAccount($user); return self::fromAccount($user);
} }
return new self((string) $user, is_object($user)? get_class($user) : get_class($token)); return new self((string) $user, is_object($user) ? ClassUtils::getRealClass($user) : ClassUtils::getRealClass($token));
} }
/** /**

View File

@ -0,0 +1,55 @@
<?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\Core\Util;
/**
* Class related functionality for objects that
* might or might not be proxy objects at the moment.
*
* @see Doctrine\Common\Util\ClassUtils
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Johannes Schmitt <schmittjoh@gmail.com>
*/
class ClassUtils
{
/**
* Marker for Proxy class names.
*
* @var string
*/
const MARKER = '__CG__';
/**
* Length of the proxy marker
*
* @var int
*/
const MARKER_LENGTH = 6;
/**
* Gets the real class name of a class name that could be a proxy.
*
* @param string|object
* @return string
*/
public static function getRealClass($object)
{
$class = is_object($object) ? get_class($object) : $object;
if (false === $pos = strrpos($class, '\\'.self::MARKER.'\\')) {
return $class;
}
return substr($class, $pos + self::MARKER_LENGTH + 2);
}
}

View File

@ -9,83 +9,108 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Symfony\Component\Security\Tests\Acl\Domain; namespace Symfony\Component\Security\Tests\Acl\Domain
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
class ObjectIdentityTest extends \PHPUnit_Framework_TestCase
{ {
public function testConstructor() use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
class ObjectIdentityTest extends \PHPUnit_Framework_TestCase
{ {
$id = new ObjectIdentity('fooid', 'footype'); public function testConstructor()
{
$id = new ObjectIdentity('fooid', 'footype');
$this->assertEquals('fooid', $id->getIdentifier());
$this->assertEquals('footype', $id->getType());
}
$this->assertEquals('fooid', $id->getIdentifier()); // Test that constructor never changes passed type, even with proxies
$this->assertEquals('footype', $id->getType()); public function testConstructorWithProxy()
} {
$id = new ObjectIdentity('fooid', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject');
public function testFromDomainObjectPrefersInterfaceOverGetId() $this->assertEquals('fooid', $id->getIdentifier());
{ $this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject', $id->getType());
$domainObject = $this->getMock('Symfony\Component\Security\Acl\Model\DomainObjectInterface'); }
$domainObject
->expects($this->once()) public function testFromDomainObjectPrefersInterfaceOverGetId()
->method('getObjectIdentifier') {
->will($this->returnValue('getObjectIdentifier()')) $domainObject = $this->getMock('Symfony\Component\Security\Acl\Model\DomainObjectInterface');
; $domainObject
$domainObject ->expects($this->once())
->expects($this->never()) ->method('getObjectIdentifier')
->method('getId') ->will($this->returnValue('getObjectIdentifier()'))
->will($this->returnValue('getId()')) ;
; $domainObject
->expects($this->never())
->method('getId')
->will($this->returnValue('getId()'))
;
$id = ObjectIdentity::fromDomainObject($domainObject);
$this->assertEquals('getObjectIdentifier()', $id->getIdentifier());
}
public function testFromDomainObjectWithoutInterface()
{
$id = ObjectIdentity::fromDomainObject(new TestDomainObject());
$this->assertEquals('getId()', $id->getIdentifier());
$this->assertEquals('Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject', $id->getType());
}
$id = ObjectIdentity::fromDomainObject($domainObject); public function testFromDomainObjectWithProxy()
$this->assertEquals('getObjectIdentifier()', $id->getIdentifier()); {
} $id = ObjectIdentity::fromDomainObject(new \Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject());
$this->assertEquals('getId()', $id->getIdentifier());
public function testFromDomainObjectWithoutInterface() $this->assertEquals('Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject', $id->getType());
{ }
$id = ObjectIdentity::fromDomainObject(new TestDomainObject());
$this->assertEquals('getId()', $id->getIdentifier()); /**
} * @dataProvider getCompareData
*/
/** public function testEquals($oid1, $oid2, $equal)
* @dataProvider getCompareData {
*/ if ($equal) {
public function testEquals($oid1, $oid2, $equal) $this->assertTrue($oid1->equals($oid2));
{ } else {
if ($equal) { $this->assertFalse($oid1->equals($oid2));
$this->assertTrue($oid1->equals($oid2)); }
} else { }
$this->assertFalse($oid1->equals($oid2));
public function getCompareData()
{
return array(
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity('123', 'foo'), true),
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity(123, 'foo'), true),
array(new ObjectIdentity('1', 'foo'), new ObjectIdentity('2', 'foo'), false),
array(new ObjectIdentity('1', 'bla'), new ObjectIdentity('1', 'blub'), false),
);
}
protected function setUp()
{
if (!class_exists('Doctrine\DBAL\DriverManager')) {
$this->markTestSkipped('The Doctrine2 DBAL is required for this test');
}
} }
} }
public function getCompareData() class TestDomainObject
{ {
return array( public function getObjectIdentifier()
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity('123', 'foo'), true), {
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity(123, 'foo'), true), return 'getObjectIdentifier()';
array(new ObjectIdentity('1', 'foo'), new ObjectIdentity('2', 'foo'), false), }
array(new ObjectIdentity('1', 'bla'), new ObjectIdentity('1', 'blub'), false),
); public function getId()
} {
return 'getId()';
protected function setUp()
{
if (!class_exists('Doctrine\DBAL\DriverManager')) {
$this->markTestSkipped('The Doctrine2 DBAL is required for this test');
} }
} }
} }
class TestDomainObject namespace Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain
{ {
public function getObjectIdentifier() class TestDomainObject extends \Symfony\Component\Security\Tests\Acl\Domain\TestDomainObject
{ {
return 'getObjectIdentifier()';
}
public function getId()
{
return 'getId()';
} }
} }

View File

@ -24,6 +24,15 @@ class UserSecurityIdentityTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('Foo', $id->getClass()); $this->assertEquals('Foo', $id->getClass());
} }
// Test that constructor never changes the type, even for proxies
public function testContructorWithProxy()
{
$id = new UserSecurityIdentity('foo', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain\Foo');
$this->assertEquals('foo', $id->getUsername());
$this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Acl\Domain\Foo', $id->getClass());
}
/** /**
* @dataProvider getCompareData * @dataProvider getCompareData
*/ */

View File

@ -0,0 +1,41 @@
<?php
namespace Symfony\Component\Security\Tests\Core\Util
{
use Symfony\Component\Security\Core\Util\ClassUtils;
class ClassUtilsTest extends \PHPUnit_Framework_TestCase
{
static public function dataGetClass()
{
return array(
array('stdClass', 'stdClass'),
array('Symfony\Component\Security\Core\Util\ClassUtils', 'Symfony\Component\Security\Core\Util\ClassUtils'),
array('MyProject\Proxies\__CG__\stdClass', 'stdClass'),
array('MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__\stdClass', 'stdClass'),
array('MyProject\Proxies\__CG__\Symfony\Component\Security\Tests\Core\Util\ChildObject', 'Symfony\Component\Security\Tests\Core\Util\ChildObject'),
array(new TestObject(), 'Symfony\Component\Security\Tests\Core\Util\TestObject'),
array(new \Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Core\Util\TestObject(), 'Symfony\Component\Security\Tests\Core\Util\TestObject'),
);
}
/**
* @dataProvider dataGetClass
*/
public function testGetRealClass($object, $expectedClassName)
{
$this->assertEquals($expectedClassName, ClassUtils::getRealClass($object));
}
}
class TestObject
{
}
}
namespace Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Tests\Core\Util
{
class TestObject extends \Symfony\Component\Security\Tests\Core\Util\TestObject
{
}
}