Symfony/sfDoctrineGuard Условные разрешения
Я обдумываю лучший способ реализации условных разрешений, то есть пользователи и команды являются m-to-m. Но каждая команда также имеет отношение "один к одному" "Лидер" с таблицей пользователей.
Для простоты, скажем, у нас есть два уровня разрешений: "Пользователь" и "Администратор". Тогда допустим, что только определенная задача администрирования команды, то есть групповые электронные письма, может быть отправлена только лидером этой команды.
На данный момент каждое действие, характерное для руководителей групп, запрашивает базу данных, чтобы проверить, является ли текущий пользователь лидером группы (учитывая, что пользователь может руководить многими командами). Лично мне не нравится видеть везде слово "if($user->isLeader($team))".
Я рассмотрел настройку списка команд, возглавляемых пользователем в сеансе пользователя при входе в систему (ala phpBB) или использования фильтра Symfony для того же.
Однако при первом подходе данные могут устареть в случае, когда другой пользователь может сменить лидера команды. Второй подход требует дополнительного запроса к базе данных при каждой загрузке страницы.
Есть идеи получше? примечание: в одном проекте есть несколько приложений, которым необходимо использовать одну и ту же модель разрешений (например, backend и api)
2 ответа
Я вижу 2 варианта:
OverridesfDoctrineGuardUser::getGuardUser()
Переопределить метод getGuardUser sfDoctrineGuardUser
чтобы получить команды, когда он получит пользователя. Это требует дополнительного левого соединения, но сохраняет ваши последующие запросы.
/**
* Overridden getGuardUser to automatically fetch Teams with User
* @see plugins/sfDoctrineGuardPlugin/lib/user/sfGuardSecurityUser#getGuardUser()
*/
public function getGuardUser()
{
if (!$this->user && $id = $this->getAttribute('user_id', null, 'sfGuardSecurityUser'))
{
$this->user = sfGuardUserTable::findUserByIdWithTeams($id); //write this query to fetch a user with his teams
if (!$this->user)
{
// the user does not exist anymore in the database
$this->signOut();
throw new sfException('The user does not exist anymore in the database.');
}
}
return $this->user;
}
Затем вы можете добавить учетные данные пользователя на основе его команд с помощью фильтра или путем переопределения hasCredential
,
Неправильный статус кэшированных команд
Если вам не нужны дополнительные запросы, единственная дополнительная опция, которую я вижу, - это использование глобального кэша. Это будет включать в себя следующее:
- Кэшируйте команды пользователя или учетные данные на основе команды при входе в систему.
- Когда лидер команды удален или добавлен, сделайте что-то вроде
apc_add(team_invalid_[team_id], true, [length_of_session])
- Всякий раз, когда учетные данные пользователя добавляются (с помощью одного из методов, описанных в первом варианте), проверяйте кэш, чтобы убедиться, что они недействительны.
Я добился чего-то подобного, переписав метод hasCredential в myUser для выполнения пользовательских проверок, когда требуются определенные учетные данные.
например:
public function hasCredential($credential, $useAnd = true) {
// make sure the symfony core always passes the team leader permission, we handle it later ourselves
$this->addCredential('team_leader');
$ret = parent::hasCredential($credential, $useAnd);
// if other checks have failed, return false now, no point continuing
if (!$ret) return false;
if ($credential == 'team_leader' || (is_array($credential) && in_array('team_leader', $credential))) {
// do stuff here. in this example, we get the object from a route and check the user is a leader
$route = sfcontext::getinstance()->getRequest()->getAttribute('sf_route');
if (!$route instanceof sfObjectRoute) {
throw new sfConfigurationException(sprintf('team_leader credential cannot be used against routes of class %s - must be sfObjectRoute', get_class($route)));
}
return $this->isLeader($route->getObject());
}
return $ret;
}
Затем вы можете добавить учетные данные "team_leader" в security.yml, как и любой другой.
Очевидно, это зависит от того, используете ли вы sfObjectRoutes, поэтому может оказаться не совсем подходящим, если вы не можете этого сделать и не можете адаптировать то, что вы используете, но я думаю, что это хорошее решение, когда вы можете его использовать!
Если вы беспокоитесь о дополнительных запросах, вы можете посмотреть на обертывание некоторого кэширования вокруг вызовов isLeader.