Autorização

Autorização é o processo que verifica se um usuário tem permissão para fazer alguma coisa. O Yii fornece dois métodos de autorização: Filtro de Controle de Acesso (ACF) e Controle de Acesso Baseado em Role (RBAC).

Filtro de Controle de Acesso

O Filtro de Controle de Acesso (ACF) é um método simples de autorização implementado como yii\filters\AccessControl que é mais indicado para aplicações que só precisam de algum controle de acesso simples. Como o próprio nome indica, ACF é uma ação de filtro que pode ser usada em um controller (controlador) ou um módulo. Enquanto um usuário faz uma solicitação para executar uma ação, ACF verificará a lista de regras de acesso para determinar se o usuário tem permissão para acessar a ação solicitada.

O código a seguir mostra como usar ACF no controller (controlador) site:

use yii\web\Controller;
use yii\filters\AccessControl;

class SiteController extends Controller
{
   public function behaviors()
   {
       return [
           'access' => [
               'class' => AccessControl::className(),
               'only' => ['login', 'logout', 'signup'],
               'rules' => [
                   [
                       'allow' => true,
                       'actions' => ['login', 'signup'],
                       'roles' => ['?'],
                   ],
                   [
                       'allow' => true,
                       'actions' => ['logout'],
                       'roles' => ['@'],
                   ],
               ],
           ],
       ];
   }
   // ...
}

No código acima do ACF é anexado ao controller (controlador) site como um behavior (comportamento). Esta é a maneira habitual de utilizar uma ação filtro. A opção only determina que o ACF só deva ser aplicado nas ações login, logout e signup. Todas as outras ações no controller (controlador) não estão sujeitas ao controle de acesso. A opção rules lista as regras de acesso, onde se lê da seguinte forma:

  • Permite todos os usuários convidados (ainda não autorizados) acessar as ações login e signup. A opção roles contém um ponto de interrogação ? que é um token especial que representa "usuários convidados".
  • Permite que usuários autenticados acessem a ação logout. O caractere @ é outro token especial que representa "usuários autenticados".

O ACF executa a verificação de autorização examinando as regras de acesso, uma por uma de cima para baixo até encontrar uma regra que corresponda ao contexto de execução atual. O valor da opção allow corresponde a regra que irá dizer se o usuário está autorizado ou não. Se nenhuma das regras forem correspondidas, significa que o usuário não está autorizado, e o ACF vai parar a próxima execução.

Quando o ACF determina que um usuário não está autorizado a acessar a ação atual, ele toma a seguinte medida por padrão:

Você pode personalizar este behavior configurando a propriedade yii\filters\AccessControl::$denyCallback da seguinte forma:

[
   'class' => AccessControl::className(),
   ...
   'denyCallback' => function ($rule, $action) {
       throw new \Exception('Você não está autorizado a acessar esta página');
   }
]

As regras de acesso suporta muitas opções. A seguir, está um resumo das opções suportadas. Você também pode estender yii\filters\AccessRule para criar suas próprias classes personalizadas de regras de acesso.

  • allow: especifica se é uma regra para "permitir" ou "negar".

  • actions: especifica quais ações essa regra corresponde. Deve ser um array de IDs das ações. A comparação é case-sensitive. Se esta opção estiver vazia ou não definida, isso significa que a regra se aplica a todas as ações.

  • controllers: especifica que controllers (controlador) esta regra corresponde. Deve ser um array de IDs de controller. A comparação é case-sensitive. Se esta opção estiver vazia ou não definida, isso significa que a regra se aplica a todos controllers.

  • roles: especifica quais roles de usuários que esta regra corresponde. Dois caracteres especiais são reconhecidos, e eles são verificados através yii\web\User::isGuest:

    • ?: corresponde a um usuário convidado (ainda não autenticado)
    • @: corresponde a um usuário autenticado

    A utilização de outros nomes invocará o método yii\web\User::can(), que requer RBAC permitindo (a ser descrito na próxima subsecção). Se esta opção estiver vazia ou não definida, significa que esta regra se aplica a todas as roles.

  • ips: especifica quais yii\web\Request::userIP esta regra corresponde. Um endereço de ip pode conter o coringa * no final para que ele corresponda endereços IP com o mesmo prefixo. Por exemplo, '192.168.*' corresponde a todos os endereços IPs no seguimento '192.168.'. Se esta opção estiver vazia ou não definida, significa que esta regra se aplica a todos os endereços IPs.

  • verbs: especifica quais métodos de request (ex. GET, POST) esta regra corresponde. A comparação é case-insensitive.

  • matchCallback: especifica um PHP callable que deve ser chamado para determinar se esta regra deve ser aplicada.

  • denyCallback: especifica um PHP callable que deve ser chamado quando esta regra negar o acesso.

Abaixo está um exemplo que mostra como fazer uso da opção matchCallback, que lhe permite escrever uma lógica arbritária de validação de acesso:

use yii\filters\AccessControl;

class SiteController extends Controller
{
   public function behaviors()
   {
       return [
           'access' => [
               'class' => AccessControl::className(),
               'only' => ['special-callback'],
               'rules' => [
                   [
                       'actions' => ['special-callback'],
                       'allow' => true,
                       'matchCallback' => function ($rule, $action) {
                           return date('d-m') === '31-10';
                       }
                   ],
               ],
           ],
       ];
   }

   // Match callback chamada! Esta página pode ser acessado somente a cada 31 de outubro
   public function actionSpecialCallback()
   {
       return $this->render('happy-halloween');
   }
}

Controle de Acesso Baseado em Role (RBAC)

Controle de Acesso Baseado em Role (RBAC) fornece um simples porém poderoso controle de acesso centralizado. Por favor, consulte Wikipedia para obter detalhes sobre comparação de RBAC com outros sistemas de controle de acesso mais tradicionais.

Yii implementa um RBAC Hierárquico genérico, conforme NIST RBAC model. Ele fornece as funcionalidades RBAC através do componente de aplicação authManager.

O uso do RBAC divide-se em duas partes. A primeira parte é construir os dados de autorização RBAC, e a segunda parte é usar os dados de autorização para executar verificação de acesso em locais onde ela é necessária.

Para facilitar a próxima descrição, vamos primeiro introduzir alguns conceitos básicos do RBAC.

Conceitos Básicos

Uma role representa uma coleção de permissões (ex. criar posts, atualizar posts). Uma role pode ser atribuído a um ou vários usuários. Para verificar se um usuário tem uma permissão especifica, podemos verificar se o usuário está associado a uma role que contém esta permissão.

Associado com cada role ou permissão, pode haver uma regra. Uma regra representa uma parte do código que será executado durante verificação de acesso para determinar se a role ou permissão correspondentes se aplicam ao usuário corrente. Por exemplo, a permissão para "atualizar post" pode ter uma regra que verifica se o usuário corrente é quem criou o post. Durante a verificação de acesso, se o usuário NÃO for quem criou o post, ele não terá permissão para "atualizar o post".

Ambos roles e permissões podem ser organizadas numa hierarquia. Em particular, uma role pode constituída de outras roles ou permissões; e uma permissão pode consistir em outras permissões. Yii implementa uma hierarquia de ordem parcial que inclui a hierarquia de árvore mais especial. Enquanto uma role pode conter uma permissão, o inverso não é verdadeiro.

Configurando RBAC

Antes de partimos para definir dados de autorização e realizar a verificação de acesso, precisamos configurar o componente de aplicação yii\base\Application::authManager. Yii oferece dois tipos de gerenciadores de autorização: yii\rbac\PhpManager e yii\rbac\DbManager. O primeiro utiliza um script PHP para armazena os dados de autorização, enquanto o último armazena os dados de autorização no banco. Você pode considerar o uso do primeiro se a sua aplicação não requerer um gerenciamento muito dinâmico das role e permissões.

UsandoPhpManager

O código a seguir mostra como configurar o authManager na configuração da aplicação utilizando a classe yii\rbac\PhpManager:

return [
   // ...
   'components' => [
       'authManager' => [
           'class' => 'yii\rbac\PhpManager',
       ],
       // ...
   ],
];

O authManager agora pode ser acessado via \Yii::$app->authManager.

Por padrão, yii\rbac\PhpManager armazena os dados RBAC em arquivos sob o diretório @app/rbac . Verifique se o diretório e todos os arquivos estão com direito de escrita pelo processo do servidor da Web caso seja necessário realizar alteração on-line.

Usando DbManager

O código a seguir mostra como configurar o authManager na configuração da apilcação utilizando a classe yii\rbac\DbManager:

return [
   // ...
   'components' => [
       'authManager' => [
           'class' => 'yii\rbac\DbManager',
       ],
       // ...
   ],
];

DbManager usa quatro tabelas de banco de dados para armazenar seus dados:

  • itemTable: tabela para armazenar itens de autorização. O padrão é "auth_item".
  • itemChildTable: tabela para armazenar hierarquia de itens de autorização. O padrão é "auth_item_child".
  • assignmentTable: tabela para armazenar tarefas de itens de autorização. O padrão é "auth_assignment".
  • ruleTable: tabela para armazenar as regras. O padrão é "auth_rule". Antes de começar é preciso criar essas tabelas no banco de dados . Para fazer isto, você pode usar o migration armazenado em @yii/rbac/migrations:

yii migrate --migrationPath=@yii/rbac/migrations

O authManager já pode ser acessado via \Yii::$app->authManager.

Construindo Dados de Autorização

Para construir dados de autorização devem ser realizadas as seguintes tarefas:

  • definir roles e permissões;
  • estabelecer relações entre roles e permissões;
  • definir regras;
  • associar regras com roles e permissões;
  • atribuir roles a usuários.

Dependendo dos requisitos de flexibilidade de autorização das tarefas acima poderia ser feito de maneiras diferentes.

Se a sua hierarquia de permissões não se altera e você tem um número fixo de usuários pode-se criar um console command que irá iniciar os dados de autorização uma vez através das APIs oferecidas pelo authManager:

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
   public function actionInit()
   {
       $auth = Yii::$app->authManager;

       // adciona a permissão "createPost"
       $createPost = $auth->createPermission('createPost');
       $createPost->description = 'Create a post';
       $auth->add($createPost);

       // adciona a permissão  "updatePost"
       $updatePost = $auth->createPermission('updatePost');
       $updatePost->description = 'Update post';
       $auth->add($updatePost);

       // adciona a role "author" e da a esta role a permissão "createPost"
       $author = $auth->createRole('author');
       $auth->add($author);
       $auth->addChild($author, $createPost);

       // adciona a role "admin" e da a esta role a permissão "updatePost"
       // bem como as permissões da role "author"
       $admin = $auth->createRole('admin');
       $auth->add($admin);
       $auth->addChild($admin, $updatePost);
       $auth->addChild($admin, $author);

       // Atribui roles para usuários. 1 and 2 são IDs retornados por IdentityInterface::getId()
       // normalmente implementado no seu model User.
       $auth->assign($author, 2);
       $auth->assign($admin, 1);
   }
}

Depois de executar o comando com yii rbac/init nós vamos chegar a seguinte hierarquia:

Simple RBAC hierarchy

Author pode criar post, admin pode atualizar post e fazer tudo que author pode.

Se a sua aplicação permite inscrição de usuários, você precisa atribuir roles a esses novos usuários. Por exemplo, para que todos os usuários inscritos tornem-se authors, no seu template avançado de projeto você precisa modificar o frontend\models\SignupForm::signup() conforme abaixo:

public function signup()
{
   if ($this->validate()) {
       $user = new User();
       $user->username = $this->username;
       $user->email = $this->email;
       $user->setPassword($this->password);
       $user->generateAuthKey();
       $user->save(false);

       // foram adicionadas as seguintes três linhas:
       $auth = Yii::$app->authManager;
       $authorRole = $auth->getRole('author');
       $auth->assign($authorRole, $user->getId());

       return $user;
   }

   return null;
}

Para aplicações que requerem controle de acesso complexo com dados de autorização atualizados dinamicamente, interfaces de usuário especiais (Isto é: painel de administração) pode ser necessário desenvolver usando APIs oferecidas pelo authManager.

Usando Regras

Como já mencionado, regras coloca restrição adicional às roles e permissões. Uma regra é uma classe que se estende de yii\rbac\Rule. Ela deve implementar o método execute(). Na hierarquia que criamos anteriormente, author não pode editar seu próprio post. Vamos corrigir isto. Primeiro nós precisamos de uma regra para verificar se o usuário é o autor do post:

namespace app\rbac;

use yii\rbac\Rule;

/**
* Verifica se o authorID corresponde ao usuário passado via  parâmetro
*/
class AuthorRule extends Rule
{
   public $name = 'isAuthor';

   /**
    * @param string|int $user the user ID.
    * @param Item $item the role or permission that this rule is associated with
    * @param array $params parameters passed to ManagerInterface::checkAccess().
    * @return bool a value indicating whether the rule permits the role or permission it is associated with.
    */
   public function execute($user, $item, $params)
   {
       return isset($params['post']) ? $params['post']->createdBy == $user : false;
   }
}

A regra acima verifica se o post foi criado pelo $user. Nós vamos criar uma permissão especial updateOwnPost no comando que usamos previamente:

$auth = Yii::$app->authManager;

// adciona a regra
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// adciona a permissão "updateOwnPost" e associar a regra com ela.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" será usado no "updatePost"
$auth->addChild($updateOwnPost, $updatePost);

// autoriza "author"  a atualizar seus próprios posts
$auth->addChild($author, $updateOwnPost);

Agora temos a seguinte hierarquia:

RBAC hierarchy with a rule

Verificação de Acesso

Com os dados de autorização prontos, você pode verificar o acesso simplesmente chamando o método yii\rbac\ManagerInterface::checkAccess(). Como a maioria das verificações de acesso é sobre o usuário corrente, por conveniência, o Yii fornece um método de atalho yii\web\User::can(), que pode ser usado como a seguir:

if (\Yii::$app->user->can('createPost')) {
   // create post
}

Se o usuário corrente é Jane com ID=1 começaremos com createPost e tentaremos chegar à Jane:

Access check

A fim de verificar se o usuário pode atualizar um post, precisamos passar um parâmetro extra que é requerido por AuthorRule descrito abaixo:

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
   // update post
}

Aqui está o que acontece se o usuário atual é John:

Access check

Nós começamos com o updatePost e passamos por updateOwnPost. Para passar pela verificação de acesso, AuthorRule deve retornar true no seu método execute(). O método recebe $params da chamada do método can() de modo que o valor é ['post' => $post]. Se tudo estiver correto, vamos chegar a author que é atribuído a John.

No caso de Jane é um pouco mais simples, uma vez que ela é um administrador:

Access check

Usando Roles Padrões

Uma role padrão é uma role que é implicitamente atribuída a todos os usuários. A chamada a yii\rbac\ManagerInterface::assign() não é necessária, e os dados de autorização não contém informação de atribuição.

Uma role padrão é geralmente associada com uma regra que determina se a role aplica-se ao do usuário que está sendo verificado.

Roles padrões são muitas vezes utilizados em aplicações que já têm algum tipo de atribuição de role. Por exemplo, uma aplicação pode ter uma coluna de "grupo" em sua tabela de usuário para representar a que grupo de privilégio cada usuário pertence. Se cada grupo privilégio pode ser mapeado para uma RBAC role, você pode usar o recurso de role padrão para associar automaticamente cada usuário ao role de RBAC. Vamos usar um exemplo para mostrar como isso pode ser feito.

Suponha que na tabela user, você tem uma coluna group que usa 1 para representar o grupo administrator e 2 o grupo author. Você pretende ter duas roles RBAC admin and author para representar as permissões para estes dois grupos, respectivamente. Você pode configurar os dados da RBAC da seguinte forma,

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
* Verifica se o grupo de usuário corresponde
*/
class UserGroupRule extends Rule
{
   public $name = 'userGroup';

   public function execute($user, $item, $params)
   {
       if (!Yii::$app->user->isGuest) {
           $group = Yii::$app->user->identity->group;
           if ($item->name === 'admin') {
               return $group == 1;
           } elseif ($item->name === 'author') {
               return $group == 1 || $group == 2;
           }
       }
       return false;
   }
}

$auth = Yii::$app->authManager;

$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);

$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... adiciona permissões como filhas de  $author ...

$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... adiciona permissões como filhas de  $admin ...

Note que no exemplo acima, porque "author" é adicionado como filho de "admin", quando você implementar o método execute() da classe rule, você também precisa respeitar essa hierarquia. É por isso que quando o nome da role é "author", o método execute() retornará true se o grupo de usuário for 1 or 2 (significa que o usuário está no grupo "admin" ou "author").

Em seguida, configure authManager listando as duas roles yii\rbac\BaseManager::$defaultRoles:

return [
   // ...
   'components' => [
       'authManager' => [
           'class' => 'yii\rbac\PhpManager',
           'defaultRoles' => ['admin', 'author'],
       ],
       // ...
   ],
];

Agora, se você executar uma verificação de acesso, ambas as roles admin e author serão verificadas através da avaliação das regras associado com elas. se a regra retornar true, isso significa que a role se aplica ao usuário atual. A partir da implementação da regra acima, isto significa que se o valor do ‘grupo’ de um usuário for 1, a role admin seria aplicável ao usuário; e se o valor do grupo for 2, seria a role author.