Friday, April 4, 2008

Dynamic Credentials in Symfony

For a long time now I've been using dynamic credentials in Symfony as laid out in this code snippit: http://www.symfony-project.org/snippets/snippet/18.


// to put in the actions.class.php file
function getCredential()
{
$this->post = $this->_retrievePost(); // retrieving the object based on the request parameters
if ($this->getUser()->isOwnerOf($this->post))
$this->getUser()->addCredential('owner');
else
$this->getUser()->removeCredential('owner');

// the hijack is over, let the normal flow continue:
return parent::getCredential();
}


However, it seems like this isn't really the proper way to add dynamic credentials. The getCredential() method is called by the Symfony FilterChain to discover what credentials that module requires, not to add credentials to the user.

For me, the problem cam to a head when I was trying to show a link if a user had a particular credential. The problem was that the page with the link was not a secure page. Since the page was not secute (is_secure: off in the config/security.yml file) the getCretential() method was never called.

The solution is to use the preExecute() method instead of getCredential()


// to put in the actions.class.php file
function preExecute()
{
$this->post = $this->_retrievePost(); // retrieving the object based on the request parameters
if ($this->getUser()->isOwnerOf($this->post))
$this->getUser()->addCredential('owner');
else
$this->getUser()->removeCredential('owner');

// the hijack is over, let the normal flow continue:
return parent::getCredential();
}

6 comments:

Anonymous said...

I am not sure whether you can override the preExecute() to get the desired effect. For me it is not working. My guess is the credentials are checked before the preExecute() is invoked. Hence adding credentials dynamically within the preExecute() may not work. Did this work for anyone else? Or am I doing something stupid?

-- Hari Gangadharan
http://www.jroller.com/HariG

Unknown said...

You are absolutly right, the Solution with preExecute() seems to work, but in fact it does not work for credentials set in yml-files.
If you check it out carefuly you will find out, that you always have the owner credential if you had it on your last page-request.
So e.g. i can get to a form for editing another user's profile like /user/anotheruser/edit if i visited my profile like /user/me/edit before.
Refreshing the /user/anotheruser/edit page will lead to a credential-required-page, but anyway that proofs that it's not working.
Any other suggestions?
Maybe it's necessary to create and enable an own filter.

one said...

I think both of these comments are correct. Using the preExecute() method will not work for .yml based credentials. However for "owner" credentials this seems to be an OK option. I forgot to mention that you must also use the postExecute() method to remove the permission after the user navigates away from the page.

helloman said...

Is it not possible to use isSecure: on with credential 'all', which you set for everyone in myUser plus removing credential 'isOwner' if set? And then you add the 'isOwner' credential in the action on a certain condition?

Another way is to use sfGuard and define permissions for objects. When a new object is created i also create a corresponding sfGuardPermission. By this way you can even assign the permission to other users or usergroups

gunzip said...

why don't just return MERGE(parent::getCredential(), 'owner') in the overridden getCredential() ? (without using add/remove credential)

Unknown said...

I only found this after working something out myself.

As my first symfony project I hadn't grasped all of the credentials stuff but implemented an ownership plugin that checks an 'ownership' table that references peername, id and owner_id, I also have it refence owner_peer_name because I want ownership via group membership aswell.

Thus in the actions I just check isOwned() on the object and redirect with a flash if it isn't. Now I understand the credentials system a lot better but I still I feel this is a better way, ownership and module/action access are really different things and it seems better to me not to try to crow bar it into the symfony credentials system.