Code snippets for symfony 1.x

Navigation

Simulate a 'owner' security credential

The Problem

Let's say your site manages users who can create posts. You want to restrict the edition of a post to the administrator or the owner of the post. You would therefore like to have a security.yml file that looks like:

edit:
  is_secure:on
  credentials: [[administrator owner]]

but the problem is that the credentials are given to the user statically upon login.

The solution

The solution is therefore to give or take back credentials dynamically. As of now, the only way to achieve that is to “hijack” the getCredential action of the sfAction class.

Write the following in the action file of your post module:

// 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();
  }

Of course you will have to write the sfUser::isOwnerOf function, which depends on your data structure. You will also have to write the _retrievePost function that returns null if no post have been found. Watch out for the isOwnerOf function: it has to return true if the post is new, otherwise users will never be able to create new posts.

Now the owner of a post can edit his post but not the others ones, except if he has administrator credentials. Everything is set up in the security.yml file. For example it is now trivial to add the same security check for the delete method.

by Olivier Verdier on 2006-05-25, tagged credentials  dynamiccredentials  owner  security 

Comments on this snippet

gravatar icon
#1 Erik Elmore on 2006-08-11 at 03:43

This snippet will open your application up to a very serious security hole because it would have you use a request parameter before it is validated. An attacker could exploit this and easily deliver an SQL injection into variables assumed to have been validated. To illustrate this, consider the following:

You have an action that is supposed to be secure and requires the owner credential and overrides getCredential like this:

public function getCredential() {
    $this->getLogger()->info("getting credential");
    $param = $this->getRequestParameter('id');
    $this->getLogger()->info("param: {$param}");
 
    $post = PostPeer::retrieveByPk($param);    // <---  This is you putting unverified data through your database system
 
    // .... and so on and so forth ....
 
    return parent::getCredential();
  }

If you try sending this module a request with a URI that you KNOW will trigger a validation error and you'll see the log entries still show up--which means no validation error was raised and you still used the request parameters as is.

gravatar icon
#2 Erik Elmore on 2006-08-11 at 04:06

I forgot to mention that you CAN force the parameters to be validated by calling sfActions::validate() before doing ANYTHING with the parameters.

for instance:

public function getCredential() {
    $this->validate();  // <--- This will cause a validation error, when appropriate
    $this->getLogger()->info("getting credential");
    $param = $this->getRequestParameter('id');
    $this->getLogger()->info("param: {$param}");
 
    $post = PostPeer::retrieveByPk($param);
 
    // .... and so on and so forth ....
 
    return parent::getCredential();
  }
gravatar icon
#3 tobias stanzel on 2006-09-04 at 03:03

I don't think that a SQL Injection is possible, cause in my case a prepared statement is used. Am i right?

gravatar icon
#4 scott meves on 2007-02-07 at 06:12

You're right tobias, Propel / Creole does a great job preventing injection attacks... at least better than a strip slashes or basic quote escaping. More here: http://creole.phpdb.org/trac/wiki/Documentation/FAQ

gravatar icon
#5 Stephen Riesenberg on 2007-02-21 at 03:50

Why not implement a validation method for the edit/delete actions of said module? You would simply pull the requested id from the request, retrieve your record, and return isOwnerOf($post).

gravatar icon
#6 sfxpt on 2007-03-15 at 06:43

1st @Stephen Riesenberg: No, that'd interfere with the existing symfony form validation mechanism. I.e., if you do that, all of you existing validation rules won't work any more. I find it the hard way.

2nd, we can use PHP's conditional statement short-circuiting feature, as:

{{{ // to put in the actions.class.php file function getCredential() { // retrieving the object based on the request parameters if ($this->post = $this->_retrievePost() AND $this->post AND $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();

} }}}

So that the _retrievePost function returns null if no post have been found, and the isOwnerOf function does not have to return true if the post is new.

gravatar icon
#7 sfxpt on 2007-03-15 at 06:58
// to put in the actions.class.php file
function getCredential()
  {
    // retrieving the object based on the request parameters
    if ($this->post = $this->_retrievePost() AND $this->post AND
      $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();
  }
gravatar icon
#8 Tom Hazel on 2008-04-04 at 05:33

I don't think that getCredential() is the function you want to overload. getCredential() is the function used by the security filters to see what credentials are required by a particular action, not to assign credentials to a user. I believe that preExecute() is to proper place to put that code. There're more details here: http://techstumbler.blogspot.com/2008/04/dynamic-credentials-in-symfony.html

gravatar icon
#9 Hari Gangadharan on 2008-04-29 at 11:00

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

gravatar icon
#10 Ɓukasz Herok on 2010-03-31 at 09:24

It would be better to have:

$post->isOwner($usr);

than

$usr->isOwner($post);