Symfony security, sessions not cleared when logging out

I’m not sure if this will be covered in 1.1 (maybe someone can shed some light on it?) but currently when you logout using sfGuardAuth standard functionality, the session is not cleared/destroyed.

This only came to light recently, when I was scratching my head over why a parameter I had set was still available in the $sf_params array even after logging out, and logging back in again as a different user. This threw up an interesting security issue, because I started to wonder if I’d set any admin specific parameters elsewhere which could be reused by another user on the same machine.

The fix is fairly straightforward, but can only be run when not in test mode, because sfBrowser does not like the session to be destroyed! Maybe this is why it has never been written into the core functionality?

In apps/yourapp/modules/sfGuardAuth/actions/actions.class.php

 public function executeSignout()
   {
     if (sfConfig::get('sf_environment') != 'test')
     {
        session_destroy();
        session_write_close();
        session_regenerate_id();
     }
     parent::executeSignout();
   }

Adding the parent::executeSignout() line means that you can let the sfGuard plugin do the remainder of the work for you, so rather than overriding the function, you are just adding a bit to the start of it.

Symfony generators – automatic wildcards on filters with plugins

I was just working with a user list using an admin generator, and received a suggestion that username searches should automatically include wildcards in the searches (front and back), so it would not be necessary to add the asterisk. The problem was that the list was based on the sfGuardUser plugin, and we all know that modifying the contents of a plugin can be bad news.

The way to do this without modifying the plugin, is to override the action that is being called, and then call the parent method. In this case, the action that was processing the form submission was sfGuardUserActions->executeList(). So in apps/yourapp/modules/sfGuardUser/actions/actions.class.php add the following:

  public function executeList()
  {
    //Add fields to auto-wildcard to this list
    $wildcardFields = array("username");
 
    $filters = $this->getRequestParameter("filters")
    $originalFilters = $filters;
 
    if ($filters)
    {
      foreach ($wildcardFields as $fieldName)
      {
        // Only add the wildcards if the user is not already using them
        if (strpos($filters[$fieldName], "*") === false)
        {
          $filters[$fieldName]  = "*".$filters[$fieldName]."*";
        }
      }
      $this->getRequest()->getParameterHolder()->set("filters", $filters);
    }
    parent::executeList();
 
    // Set the filters back to their original values before the template is rendered
    $this->filters = $originalFilters;
  }

Now the request parameters for the selected fields are automatically prepended and appended with an asterisk before calling the parent function of the same name to continue with the request. Once the parent has finished, we then set the filters back to their original value, otherwise the asterisks we added would now appear in the fields when the page is presented.

An enhancement to this would be to specify the auto-wildcards field in the .yml file, rather than specifying it here in the action.

Symfony sfGuardUser “remember me” checkbox/cookie does not work

It seems that the sfGuard plugin only checks the “remember me” cookie if the user attempts to access a secure module. This is fine if your site requires a login for any access, and thus is always is_secure: on, however if you have any “public” pages which logged in and non-logged in users can access, users that have selected the “remember me” box will not be logged in automatically.

Solution:

Of course this could be fixed in the plugin, but if you do not want to (and shouldn’t) modify the plugin code directly, you can add another filter which will check the cookie. Assuming you have already installed the sfGuardPlugin, and have a “remember me” checkbox implemented, this is all you will need to do to get the automatic logins on non-secure pages:

in apps/yourapp/config/filters.yml add the remember me filter

rendering: ~
web_debug: ~
security:
  class: sfGuardBasicSecurityFilter
 
# generally, you will want to insert your own filters here

remember:
  class: rememberMeFilter
 
cache:     ~
common:    ~
flash:     ~
execution: ~

Now create rememberMeFilter.class.php in apps/yourapp/lib
This example uses Propel

class rememberMeFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // execute this filter only once, and if the user is not already logged in, and has a cookie set
    if ($this->isFirstCall() && !$this->getContext()->getUser()->isAuthenticated()
        && $this->getContext()->getRequest()->getCookie(sfConfig::get('app_sf_guard_plugin_remember_cookie_name', 'sfRemember')))
    {
      // See if a user exists with this cookie in the remember database
      $c = new Criteria();
      $c->add(sfGuardRememberKeyPeer::REMEMBER_KEY, $this->getContext()->getRequest()->getCookie(sfConfig::get('app_sf_guard_plugin_remember_cookie_name', 'sfRemember')));
      $c->add(sfGuardRememberKeyPeer::IP_ADDRESS, $this->getContext()->getRequest()->getHttpHeader ('addr','remote'));
 
      if ($resultArray = sfGuardRememberKeyPeer::doSelectJoinsfGuardUser($c))
      {
        $resultRow = current($resultArray);
        $this->getContext()->getUser()->signIn($resultRow->getSfGuardUser());
      }
    }
    // execute next filter
    $filterChain->execute();
  }
}

That should do it, now your users will be logged in on any page if they have the cookie set. The sfGuardPlugin will still take care of setting the cookie and clearing it on logout.

Remember when using these cookies, it’s good practice to ask the user to re-enter their password when doing anything sensitive, like submitting an order or changing any personal details like their password.

A follow up by Shiny explains that you can also get this working by securing modules with empty permission sets like this:

all:
  is_secure: on
 
index:
  credentials: []

Be careful with setting that globally though if you’re trying to set a “secure it first, grant permissions later” style system, because with the above – everything is accessible until secured.

Yaml (yml) file gotchas – trying to set default culture to “no”

The yaml file parser will attempt to parse all the values, rather than taking them as they are – which in most cases is a good thing, however you must remember to use quotes when the intended output is a string, and the parser may interpret the value otherwise:

default_culture:   en
// No problem, en is treated as a string 
// and /en/ is automatically added to links
 
default_culture: no
// Oops, no is translated by Symfony 
// the same way as false, 0 or off, giving it a boolean value!
 
default_culture: 'no'
// That's better, now you will have the intended results.

The same applies to any yml files that you may have “keywords” in, so try to always use quotes round everything that is not boolean, integer, etc.

How to write complex queries using Propel

This is a monster subject, and I spent a long time scratching my head over a few things yesterday, until I came across this site:

http://propel.jondh.me.uk/

It’s developed by a Symfony user, and allows you to type in sql statements using nested braces for prioritising. The form will then return a nicely formatted block of Propel code.

It’s not perfect, and it doesn’t cover everything just yet, but it’s great for getting some ideas about the structure and how to use various criteria – it’s also spot on if you are trying to learn Propel and already know how to structure SQL statements.

Discuss this tool and/or contribute on the Symfony Forum

Using a combination of tools like this, and taking a look at the generated files in lib/model/om should give you a good push, and a bit of a shortcut to more complex queries than you may be used to – rather than having to trawl the Propel documentation.

« Previous PageNext Page »