среда, 15 октября 2008 г.

sfUser->addCredentials() портит CSRF-защиту

Однажды, долгим зимнем вечером столкнулся с такой проблемой: на формах появилась ошибка CSRF валидации. Недолгие копания в исходниках symfomy привели меня в функцию sfUser->addCredentials(), в которой вызывалась sfSessionStorage->regenerate():

public function regenerate($destroy = false)
{
// regenerate a new session id
session_regenerate_id($destroy);
}

Вот же блин, думаю, он идентификатор меняет, поэтому и не подходит CSRF ключь. А addCredentials я как раз начал использовать в одном из своих фильтров. Ну что ж, наследуем и дописываем класс:

class sfMySessionStorage extends sfSessionStorage
{
public function regenerate($destroy = false)
{
// regenerate a new session id
// session_regenerate_id($destroy);
}
}

Регистрируем в factories.yml:

all:
storage:
class: sfMySessionStorage
param:
session_name: sid

Теперь все ок!

вторник, 14 октября 2008 г.

хранения сесси без куков

При содании wap-версии сайта, мне пришлось отказаться от хранения сесси в куках. Казалось бы нет ничего проще - пишем в .htaccess: 'php_value session.use_trans_sid 1' и идентификатор сессии автоматичеки дописывается ко всем url. Но при редиректе в action он, к сожалению, теряется. Исправить это можно немного дописав функцию redirect класса sfFrontWebController. Как обычно в lib'ах создаем sfMyFrontWebController.class.php с таким содержанием:

class sfMyFrontWebController extends sfFrontWebController
{
public function redirect($url, $delay = 0, $statusCode = 302)
{
if( session_id() != '' && false === strpos($url, session_name()))
{
if( false !== strpos($url, '?') )
$url .= '&'.session_name().'='.session_id();
else
$url .= '?'.session_name().'='.session_id();
}
parent::redirect($url, $delay, $statusCode);
}
}

и регистрируем этот класс в файле factories.yml вместо sfFrontWebController:

all:
controller:
class: sfMyFrontWebController

Теперь сессия не потеряется!

воскресенье, 12 октября 2008 г.

symfony и XAMPP

По желанию, symfony можно подружить и с xampp-сервером. Порядок действия:
1. После установки xampp исправляем в httpd.conf - DocumentRoot "C:/xampp/htdocs"
2. Создаем папку C:\xampp\htdocs\localhost и копируем туда содержимое C:\xampp\htdocs
3. Устанавливаем symfony любым способом.
4. Проект создаем в C:\xampp\htdocs\proj1
5. Настраиваем виртуальный хост C:\xampp\apache\conf\extra\httpd-vhosts.conf

NameVirtualHost *:80

<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot C:/xampp/htdocs/localhost
ServerName localhost
ServerAlias www.localhost
ErrorLog C:/xampp/htdocs/localhost/error_log
CustomLog C:/xampp/htdocs/localhost/access_log common
</VirtualHost>

<VirtualHost *:80>
ServerAdmin webmaster@proj1
DocumentRoot C:/xampp/htdocs/proj1/web
ServerName proj1
ServerAlias www.proj1
ErrorLog C:/xampp/htdocs/proj1/log/error_log
CustomLog C:/xampp/htdocs/proj1/log/access_log common
</VirtualHost>

6. Не забываем добавить запись '127.0.0.1 prog1' в hosts.
Все готово - стартуем xampp!

symfony и Денвер

symfony прекрасно уживается с Денвером. Я устанавливаю его через PEAR. Проекты создаю в папке z:\usr\projects.
После инициализации делаю симлинк: z:\usr\projects\prog1\web -> z:\home\prog1\www
Еще один симлинк: PEAR\data\symfony\web\sf -> z:\home\prog1\www\sf

После инициализации приложений нужно исправить пути в index файлах (в папке web). А т.к. пути в Денвере вероятно отличаются от путей на рабочем сервере, использую такую проверку:

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
require_once('/usr/projects/prog1/config/ProjectConfiguration.class.php');
else
require_once('[реальный путь на сервере]/ProjectConfiguration.class.php');

Так же не забудьте уточнить пути в ProjectConfiguration.class.php для подключения файла sfCoreAutoload.class.php, вероятно что-то вроде require_once('/usr/share/pear/symfony/autoload/sfCoreAutoload.class.php');
И если надо - делайте такую же проверку на ОС.

пятница, 10 октября 2008 г.

Constrains и загрузка данных

Когда я достаточно усложнил fixtures для тестирования, то при загрузке fixtures:

$data = new sfPropelData();
$data->loadData(sfConfig::get('sf_test_dir').'/fixtures/');

появилась ошибка c constrains - Propel не мог просто так очистить БД.

Нашел выход в небольшом дописывании класса sfPropelData - создаем myPropelData.class.php и кладем его в lib'ы:


class myPropelData extends sfPropelData
{
public function loadData($directoryOrFile = null, $connectionName = 'propel')
{
$con = Propel::getConnection($connectionName);
$query = 'SET FOREIGN_KEY_CHECKS = 0;';
$statement = $con->prepareStatement($query)->executeQuery();

parent::loadData($directoryOrFile, $connectionName);

$query = 'SET FOREIGN_KEY_CHECKS = 1;';
$statement = $con->prepareStatement($query)->executeQuery();
}
}


и далее в тестах используем

$data = new myPropelData();
$data->loadData(sfConfig::get('sf_test_dir').'/fixtures/');

четверг, 9 октября 2008 г.

Запоминание пользователя при входе

В комплекте с sfGuardPlugin есть фильтр sfGuardBasicSecurityFilter для 'запоминания' пользователя при авторизации. Этот фильтр необходимо указать в filters.yml в качсестве security фильтра.
Все бы хорошо, но security фильтр запускается только при входе на защищенные страницы (is_secure: on). Учитывая что для первой (стартовой) страницы сайта защита обычно отключается - то фильтр не запускается и пользователь не опознается. Если продолжить путешествие по сайту и пройти в защищенную область - фильтр сработает и пользователь опознается.
Но как авторизировать его с первого раза?
Копируем sfGuardBasicSecurityFilter.class.php под именем myRememberFilter.class.php в папку lib своего application. Потом вносим небольшие изменения:

class myRememberFilter extends sfFilter
{
public function execute ($filterChain)
{
if ($this->isFirstCall() and !$this->getContext()->getUser()->isAuthenticated())
{
if ($cookie = $this->getContext()->getRequest()->getCookie(sfConfig::get('app_sf_guard_plugin_remember_cookie_name', 'sfRemember')))
{
$c = new Criteria();
$c->add(sfGuardRememberKeyPeer::REMEMBER_KEY, $cookie);
$rk = sfGuardRememberKeyPeer::doSelectOne($c);
if ($rk && $rk->getSfGuardUser())
{
$this->getContext()->getUser()->signIn($rk->getSfGuardUser());
}
}
}

$filterChain->execute();
}
}

Теперь остается в filters.yml прописать его как обычный фильр:

...
security: ~

# generally, you will want to insert your own filters here
remember:
class: myRememberFilter
...


Теперь пользователи опознаются сразу!