Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "security"

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 

foreignkey to sf_guard_user

if you won´t to add a foreignkey to sf_guard_user put this on top off you schema.yml file ;)

propel:
  _attributes :          { package: "plugins.sfGuardPlugin.lib.model" }
  sf_guard_user:
    _attributes:         { phpName: sfGuardUser }
    id:

now you can simple add a foreignkey

propel:
  tbl_user_profile:
    user_id:             { type: integer, primaryKey: true, foreignTable: sf_guard_user, foreignReference: id, onDelete: cascade }

or

propel:
  tbl_user_profile:
    sf_guard_user_id:
by Gordon Franke on 2007-04-27, tagged database  foreignkey  model  plugin  propel  schema  security  user  yml 
(1 comment)

Access control to environment and applications and custom global config file for an environment ?

With this snippet we can implement the following functionalities from the global configuration file config/config.php:

I developed a mini-library (config/config.lib.php) to include and use in the global config file config/config.php.

<?php
/**
 * IP access control to an environment
 *
 * @param string $env environment name
 * @param array $ips IPs that has access
 * @param string $clientip client IP
 */
function envCorrecte ($env,$ips,$clientip)
{
    if (SF_ENVIRONMENT==$env)
    {
        $acces=0;
        foreach($ips as $ip)
        {
            $ipclient=substr($clientip,0,strlen($ip));
            if ($ipclient==$ip)
            {
                $acces=1;
            }
        }
        if ($acces==0)
        {
            echo "Keep away! this is not a public environment.";
            exit;
        }
    }
}
/**
 * IP access control to an application of the symfony project
 *
 * @param array $apps no public applications
 * @param array $ips IPs that has access
 * @param string $clientip client IP
 */
function appCorrecte ($apps,$ips,$clientip)
{
    // solve problem with key '0' in array
        $apps_aux[0]='';
    $apps = $apps_aux + $apps;
    //
    if (array_search(SF_APP,$apps)!=false)
    {
        $acces=0;
        foreach($ips as $ip)
        {
            $ADR=$HTTP_SERVER_VARS['REMOTE_ADDR'];
            $ipclient=substr($clientip,0,strlen($ip));
            if ($ipclient==$ip)
            {
                $acces=1;
            }
        }   
        if ($acces==0)
        {
            echo "Keep away! ".SF_APP." is not a public application.";
            exit;
        }
    }
 
}
?>

Finally config/config.php file can be like this:

<?php
include("config.lib.php");
 
/**
 * IP access control to 'env' and 'int' environments
 */
$ip_dev=array("10.138.0.","192.168.");
$ip_int=array("10.138.0.","192.168.");
envCorrecte('env',$ip_env,$HTTP_SERVER_VARS['REMOTE_ADDR']);
envCorrecte('int',$ip_int,$HTTP_SERVER_VARS['REMOTE_ADDR']);
 
/**
 * IP access control to backend applications
 */
$ip_apps=array("10.138.0.","192.168.","w.x.y.z");
$apps=array('myapp_1','myapp_2','myapp_n');
appCorrecte($apps,$ip_apps,$HTTP_SERVER_VARS['REMOTE_ADDR']);
 
/**
 * Custom config.php file for each environment
 */
include("config.".SF_ENVIRONMENT.".php");
 
/**
 * Common config.php file for all environments
 */
define("example","content1");
 
?>
by Oriol Rius on 2006-08-11, tagged access  administration  backend  config  environment  security 

Alternative security module

For some applications, the basic security layer offered by symfony is insufficient - often a database describes users organised into groups or hierarchical groups, and a way is needed to translate this into a symfony mechanism. Luckily this is quite easy: a filter can be implemented that redirects if the necessary auth criteria have not been met.

You will need to modify this to match your own objects, but it may be good to get you started. Place it in apps/frontend/lib/myDatabaseSecurityFilter.class.php:

<?php
 
/**
 * Trying something to implement a database security layer
 *
 * HISTORY
 * 24 May 2006 - initial version
 */
class myDatabaseSecurityFilter extends sfBasicSecurityFilter
{
  public function execute($filterChain)
  {
    // A call may be made up of several subcalls, so limit this to just the first one
    if ($this->isFirstCall()) {
      $context = $this->getContext();
      $user = $context->getUser();
      $strUser = $user->getUsername();
      $strModule = $context->getModuleName();
      $strRoute = $strModule . '/' . $context->getActionName();
 
      // Only do security check if the module is security-enabled (which is default, so
      // use 'none' to turn it off
      $strParam = "mod_${strModule}_db_security";
      $boolSec = sfConfig::get($strParam);
 
      if ($boolSec !== 'none') {
 
        // Get the UserMain object for this user
        $objUserMain = $user->getUserMain();
        $objFuncs = $objUserMain->getUserFunctions($strRoute);
 
        // If the above checks did not OK this op, then refuse access
        if (count($objFuncs) < 1)
        {
          $fCtrllr = '';
          if (!sfConfig::get('sf_no_script_name')) $fCtrllr = getenv('SCRIPT_NAME');
          $context->getController()->redirect("$fCtrllr/default/notauth");
        }
      }
    }
 
    $filterChain->execute();
  }
}
 
?>

Basically, this filter looks up a yaml config value on a per-module basis, called mod_<module>_db_security, and if it is set to 'none', then allows the filter to pass through. If it is not, it looks up the database user object (which for convenience I am keeping within my sfUser object) and performs a query called getUserFunctions(), which looks up the current module/action and determines whether this is allowed.

A redirect is called if no explicit authorisation is held, otherwise the module passes control to the next filter.

Then to install it, simply put:

myDbSecurityFilter:
  class: myDatabaseSecurityFilter

in your apps/frontend/config/filters.yml file.

by halfer on 2006-06-20, tagged credentials  owner  security 
(1 comment)

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 
(10 comments)