Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "sfguard"

sfGuard Logging with Brute Force Login Prevention

For those of us who just need a bit more security out of this already awesome authentication plugin. This has been tested to work on symfony 1.2 using sfGuardPlugin version 3.1.3 stable.

The default behavior of this snippet is a log that tracks each login attempt, the IP, the username entered, and whether it was successful or not. The brute force protection's default settings will check to see if more than 5 attempts for the same username in the past 2 minutes were failed attempts. It also includes a cooldown period of 15 minutes from the last failed login attempt. All of these variables can be changed in the arguments for checkBruteForce().

The first thing we need to do is create a log for sfGuard. Your schema should now contain the following:

plugins/sfGuardPlugin/config/schema.yml:

sf_guard_log:
    _attributes:    { phpName: sfGuardLog }
    id:             
    created_at:     
    ip:             varchar(30)
    username:       varchar(20)
    valid_login:    boolean
 

Once the changes to your schema have been made, rebuild your models, forms, and filters. Next, we need to extend the peer class for this new sfGuardLog model to handle brute force protection.

plugins/sfGuardPlugin/lib/model/sfGuardLogPeer.php:

/*
    Auther: Christopher Lewis (chris@bluehousegroup.com)
 
    Check for brute force login attempts
 
    Case I
     1. Get all logins for this username in the past N minutes
     2. If P or more of them are fails, this is likely a brute force attempt, return true
 
    Case II
     1. Get last P logins
     2. Are they all fails? AND Are they all within the same window of N minutes? AND Was the last attempt within the past Q minutes?
        If all are true, return true
 
    Where:
     N = $attempt_window
     P = $allowed_fail_count
     Q = $cooldown_window
  */
  public static function checkBruteForce($username, $attempt_window = 2, $cooldown_window = 15, $allowed_fail_count = 5)
  {
    //Case I...
 
    $N_minutes_ago_timestamp = strtotime("$attempt_window minutes ago");
    $N_minutes_ago_sqltime = date('Y-m-d H:i:s', $N_minutes_ago_timestamp);
 
    $c = new Criteria();
    $c->add(sfGuardLogPeer::USERNAME, $username);
    $c->add(sfGuardLogPeer::CREATED_AT, $N_minutes_ago_sqltime, Criteria::GREATER_THAN);
    $entries = sfGuardLogPeer::doSelect($c);
 
    $failed_count = 0;
    foreach($entries as $log) { if($log->getValidLogin() == false) { $failed_count++; } }
 
    if($failed_count >= $allowed_fail_count) { return true; }
 
    //Case II...
 
    $all_fails = false; $all_in_window = false; $last_attempt_recent = false;
 
    //Are they all fails?
    $c = new Criteria();
    $c->add(sfGuardLogPeer::USERNAME, $username);
    $c->addDescendingOrderByColumn(sfGuardLogPeer::ID);
    $c->setLimit($allowed_fail_count);
 
    $failed_count = 0; $timestamps = array();
    $entries = sfGuardLogPeer::doSelect($c);
    foreach($entries as $pos => $log)
    {
      if($log->getValidLogin() == false) { $failed_count++; }
      array_push($timestamps, strtotime($log->getCreatedAt()));
    }
 
    if($failed_count >= $allowed_fail_count) { $all_fails = true; }
    else { return false; }
 
    //Are they all within the same window of N minutes?
    sort($timestamps);
    $time_a = $timestamps[0];
    $time_b = $timestamps[(count($timestamps) - 1)];
    $window_in_secs = $time_b - $time_a;
    $window_in_mins = $window_in_secs / 60;
 
    if($window_in_mins <= $attempt_window) { $all_in_window = true; }
    else { return false; }
 
    //Was the last attempt less than Q minutes ago?
    $Q_minutes_ago_timestamp = strtotime("$allowed_fail_count minutes ago");
    $last_attempt_timestamp = strtotime($entries[0]->getCreatedAt());
    if($Q_minutes_ago_timestamp <= $last_attempt_timestamp) { $last_attempt_recent = true; }
    else { return false; }
 
    //If all are true, return true
    if($all_fails == true && $all_in_window == true && $last_attempt_recent == true) { return true; }
 
    return false;  
  }
 

Last, we'll need to overload the executeSignin action in the sfGuardAuth module to include our new logging and security features:

plugins/sfGuardPlugin/modules/sfGuardAuth/actions/actions.class.php:

public function executeSignin($request)
  {
    $user = $this->getUser();
    if($user->isAuthenticated())
    {
      return $this->redirect('@homepage');
    }
 
    $class = sfConfig::get('app_sf_guard_plugin_signin_form', 'sfGuardFormSignin');
    $this->form = new $class();
 
    if($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter('signin'));
      $values = $this->form->getValues();
 
      //Log attempts
      $log = new sfGuardLog();
      $log->setIp($_SERVER['REMOTE_ADDR']);
      $log->setUsername($request->getParameter('signin[username]'));
 
      //Check for brute force attempt
      $is_brute_force = sfGuardLogPeer::checkBruteForce($request->getParameter('signin[username]'));
 
      if($this->form->isValid() && !($is_brute_force))
      { 
        $this->getUser()->signin($values['user'], array_key_exists('remember', $values) ? $values['remember'] : false);
 
        // always redirect to a URL set in app.yml
        // or to the referer
        // or to the homepage
        $signinUrl = sfConfig::get('app_sf_guard_plugin_success_signin_url', $user->getReferer('@homepage'));
 
        $log->setValidLogin(true);
        $log->save();
 
        return $this->redirect($signinUrl);
      }
      else
      { 
        $log->setValidLogin(false);
        $log->save();
      }
    }
  }
 
by Christopher Lewis on 2009-06-03, tagged authentication  brute  login  plugins  security  sfguard 

myPakeTransformSchemaSfguard.php

or apply transformation on sfGuard tables into your main schema

Problem

When you generate a schema with sf_guard tables from your main database with the following command, the schema contains all the tables included the sf_guard tables (it's normal).

symfony propel-build-schema xml
 

Then after, when you build the model classes, the sfGuardPlugin classes have a wrong name and are in a wrong place (lib/). There is a post on this problem, how to correctly use propel-build-schema after sfGuardPlugin installation?

symfony propel-build-model
 

Solution

To correct the schema, apply the transformation on the XML schema after the schema generation :

symfony transform-schema-sfguard xml
 

The script add in the schema for all sf_guard_* tables, the package and the phpName with the camelcase used by the plugin.

Also you have to switch off the schema in the plugin to don't have twice declaration :

mv ./plugins/sfGuardPlugin/config/schema.yml ./plugins/sfGuardPlugin/config/schema.yml.off
 

The script: myPakeTransformSchemaSfguard.php

pake_desc( 'apply transformation on sfGuard tables into your main schema' );
pake_task( 'transform-schema-sfguard', 'project_exists' );
 
function run_transform_schema_sfguard($task, $args) 
{
  // Check params
  // -- missing params ?
  if ( !count($args) > 1 ) {
    throw new Exception("You must provide a transformation to apply.\nsymfony transform-schema-sfguard\nsymfony transform-schema-sfguard xml");
  }
 
  // -- schema exists ?
  if ($args[0] == 'xml')
  {
    $schema_filename = sprintf( '%s/schema.xml', sfConfig::get('sf_config_dir') );
    if ( !file_exists($schema_filename) ) {
        throw new Exception( "Missing schema.xml" );
    }
  }
  else
  {
    $schema_filename = sprintf( '%s/schema.yml', sfConfig::get('sf_config_dir') );
    if ( !file_exists($schema_filename) ) {
        throw new Exception( "Missing schema.yml (not yet implemented)" );
    } else {
        throw new Exception( "schema.yml not yet implemented" );
    }
  }
 
  // Backup schema
  //pake_copy( $schema_filename, $schema_filename . '.previous', array('override' => true) );
 
 
  if ($args[0] == 'xml')
  {
    $handle = fopen($schema_filename, "r");
 
    // get the entire file in $contents
    $contents = '';
    while (!feof($handle))
    {
      $contents.= fread($handle, 8192);
    }
    fclose($handle);
 
    $num = preg_match_all('/<table(.*)>/i', $contents, $matches);
 
    // each table definition found
    foreach ($matches[0] as $val)
    {
      if (!preg_match('/package|phpName/i', $val) && preg_match('/name="(sf_guard_.+?)"/i', $val, $val_matches)) {
 
        $table_name = $val_matches[1];
 
        //$php_name   = sfInflector::camelize($val_matches[1]);
        $php_name = sfToolkit::pregtr($val_matches[1], array('#/(.?)#e'   => "'::'.strtoupper('\\1')",
                                                             '/(_)(.)/e'  => "strtoupper('\\2')",
                                                             '/(^)(.)/e'  => "strtolower('\\2')"));
 
        $pattern    = '/(<table.*)(name="'.$table_name.'")(.*>)/i';
        $replace    = '$1$2 package="plugins.sfGuardPlugin.lib.model" phpName="'.$php_name.'" $3';
        $contents   = preg_replace($pattern, $replace, $contents, 1, $count);
 
        pake_echo($table_name);
      } 
    }
 
    // write the result
    $handle = fopen($schema_filename, "w+");
    fwrite($handle, $contents);
    fclose($handle);
  }
}
 

Installation

Put the myPakeTransformSchemaSfguard.php script into data/tasks directory

Use

mv ./plugins/sfGuardPlugin/config/schema.yml ./plugins/sfGuardPlugin/config/schema.yml.off
 
symfony propel-build-schema xml
 
symfony transform-schema-sfguard xml
 
symfony propel-build-model
 
by Olivier LOYNET on 2008-04-09, tagged cli  pake  propel  schema  sfguard 

Using HTTP authentification with sfGuardPlugin

Here is how I did this... Create a sfGuardAuth module in your application and edit the actions.class.php file as follow.

The trick is to not try to overwrite the sfGuardAuth/signin action, as it use validation. As well it allow you to use the "normal" signin way (form and etc).

require_once(sfConfig::get('sf_plugins_dir').'/sfGuardPlugin/modules/sfGuardAuth/lib/BasesfGuardAuthActions.class.php');
 
class sfGuardAuthActions extends BasesfGuardAuthActions
{
  public function executeHTTPSignin()
  {
    // get somme interesting stuff!
    $request = $this->getRequest();
    $response = $this->getResponse();
    $user = $this->getUser();
 
    // An HTTP authenticated user cannot logout (browser always send authentification datas)
    // So we must be sure that the user has seen the HTTP authentification box before
    if ( $user->getAttribute('request_authentification') )
    {
      // If authentification datas has been sent
      if ( isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) )
      {
        // If correct username given
        $guarduser = sfGuardUserPeer::retrieveByUserName( $_SERVER['PHP_AUTH_USER'] );
        if ( $guarduser instanceof sfGuardUser )
        {
          // If correct Password given
          if ( ($guarduser instanceof sfGuardUser) and ($guarduser->checkpassword( $_SERVER['PHP_AUTH_PW'] )) )
          {
            // we can signin the user and redirect it
            $user->signin( $guarduser );
            $user->setAttribute('request_authentification',false);
            $this->redirect( sfConfig::get('app_sf_guard_plugin_success_signin_url','@homepage') );
            throw new sfStopException;
          }
        }
      }
    }
 
    // else, popup the authentification box
    $user->setAttribute('request_authentification',true);
    $response->setHttpHeader( 'WWW-Authenticate', 'Basic realm="Identification"' );
    $response->setHttpHeader( 'HTTP/1.0', '401 Unauthorized' );
 
    // This will be displayed if the user cancel the authentification process
    $this->forward( 'sfGuardAuth', 'password' );
    throw new sfStopException;
  }
 
  public function executePasswowd()
  {
    # Implement this action as usual...
  }
}
 

Enjoy... (I hope)

by jugjug on 2008-03-03, tagged authenticate  http  sfguard 

PG4sfGuard(1): Partial for sfGuardUserProfile and sfGuardUser

If you want add a column to sfGuardUserProfile's list view for show a link to sfGuardUser's edit view:

Create a partial : site.com/apps/backend/modules/sf_Guard_User_Profile/templates/_user.php

And put this:

<?php echo link_to($sf_guard_user_profile->getSfGuardUser()->getUsername(), 'sfGuardUser/edit?id='.$sf_guard_user_profile->getSfGuardUser()->getId()) ?>

PD(It's not very much, but it is my first snippet)

by Roberto G Puentes Diaz on 2008-01-22, tagged generator  partial  sfguard 

Create profile after adding a new user, the simple way.

Sometimes if you use sfGuardUser and sfGuardUserProfile you need to create an empty profile just after adding the new user (automating is such a great thing). This snippet will help out.

UPDATE: Will Killian pointed me to the solution of using this without touching the plugin files, this way it will still work after upgrading the plugin. Thanks!

Edit apps/backend/modules/sfGuardUser/actions/actions.class.php

(create directories and files as needed, just for the record I use this with admin generator on "backend" app)

Mine looks like this.

<?php
 
require_once(sfConfig::get('sf_plugins_dir').'/sfGuardPlugin/modules/sfGuardUser/lib/BasesfGuardUserActions.class.php');
 
class sfGuardUserActions extends BasesfGuardUserActions
{
  protected function savesfGuardUser($sf_guard_user)
  {
    parent::savesfGuardUser($sf_guard_user);
    $user_id = $sf_guard_user->getId();
    $c = new Criteria();
    $c->add(sfGuardUserProfilePeer::USER_ID,$user_id);
    $profile = sfGuardUserProfilePeer::doSelectOne($c);
    if(!$profile) {
      $profile = new sfGuardUserProfile();
      $profile->setUserId($user_id);
      $profile->save();
    }
  }
}
 
by Roberto Carvajal on 2007-10-30, tagged profile  sfguard  user 
(5 comments)