13.7 - Aplicativo com uma área restrita/administrativa
Daqui a vinte anos, você não vai se arrepender das coisas que fez, mas das que deixou de fazer. Por isso, veleje longe do seu porto seguro. Pegue os ventos. Explore. Sonhe. Descubra. (Mark Twain)
Criar um aplicativo com uma área restrita/administrativa para o CakePHP 3
Adaptado do vídeo abaixo:
https://www.youtube.com/watch?v=eWu6r5aO1Jc&index=6&list=PL83b6tjXNFHe-OVRROawG3YdIN_-Kbc6j
Criar um aplicativo chamado admin_area.
Banco admin_area
CREATE TABLE `customers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(55) COLLATE utf8mb4_unicode_ci NOT NULL,
`birthday` date DEFAULT NULL,
`phone` varchar(14) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`observation` text COLLATE utf8mb4_unicode_ci,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `customers` (`id`, `name`, `birthday`, `phone`, `observation`, `created`, `modified`) VALUES
(1, 'Brennan G. Wilcox', '2016-04-15', '(851) 190-1314', 'ante. Maecenas mi felis, adipiscing', NULL, NULL),
(2, 'Chase Summers', '2016-08-27', '(846) 297-4733', 'Sed molestie. Sed id risus', NULL, NULL),
(3, 'Sonia L. Mckay', '2015-12-02', '(131) 453-1690', 'fermentum vel, mauris. Integer sem', NULL, NULL),
(4, 'Isadora L. Bowers', '2015-10-24', '(939) 798-4625', 'consequat, lectus sit amet luctus', NULL, NULL),
(5, 'Sophia Cochran', '2017-06-15', '(811) 687-0491', 'Aliquam tincidunt, nunc ac mattis', NULL, NULL),
(6, 'Maxwell T. Burton', '2016-01-12', '(147) 962-3265', 'at arcu. Vestibulum ante ipsum', NULL, NULL),
(7, 'Desiree Y. Henry', '2017-07-21', '(148) 711-3747', 'vitae dolor. Donec fringilla. Donec', NULL, NULL),
(8, 'Asher Key', '2015-11-07', '(355) 668-5871', 'a, aliquet vel, vulputate eu,', NULL, NULL),
(9, 'Tyler Castro', '2016-08-31', '(567) 793-5061', 'nec tempus mauris erat eget', NULL, NULL),
(10, 'Rudyard Weber', '2015-10-26', '(445) 457-4552', 'Morbi vehicula. Pellentesque tincidunt tempus', NULL, NULL),
(11, 'Allen Austin', '2016-04-15', '(758) 867-2179', 'ipsum. Phasellus vitae mauris sit', NULL, NULL),
(12, 'Octavius Cooper', '2015-10-13', '(101) 625-3985', 'ipsum non arcu. Vivamus sit', NULL, NULL),
(13, 'Dustin M. Oneill', '2016-04-24', '(276) 722-0976', 'magnis dis parturient montes, nascetur', NULL, NULL),
(14, 'Giacomo K. Horton', '2016-07-03', '(773) 532-7468', 'neque. Sed eget lacus. Mauris', NULL, NULL),
(15, 'Signe T. Weaver', '2016-06-17', '(210) 895-3664', 'dui nec urna suscipit nonummy.', NULL, NULL),
(16, 'Avram O. Delaney', '2016-08-05', '(609) 552-7572', 'Donec luctus aliquet odio. Etiam', NULL, NULL),
(17, 'Cara Parker', '2016-07-24', '(854) 169-4797', 'ornare lectus justo eu arcu.', NULL, NULL),
(18, 'Chelsea Mcclain', '2016-08-06', '(363) 636-1560', 'mollis lectus pede et risus.', NULL, NULL),
(19, 'Wesley Garner', '2016-06-11', '(578) 231-2389', 'Fusce feugiat. Lorem ipsum dolor', NULL, NULL),
(20, 'Irene P. Arnold', '2016-02-12', '(253) 631-9830', 'accumsan laoreet ipsum. Curabitur consequat,', NULL, NULL),
(21, 'Austin S. Wall', '2016-01-21', '(225) 694-9511', 'Sed eget lacus. Mauris non', NULL, NULL);
DROP TABLE IF EXISTS `groups`;
CREATE TABLE `groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `groups` (`id`, `name`, `created`, `modified`) VALUES
(1, 'Supers', '2016-08-30 21:15:01', '2016-08-30 21:15:01'),
(2, 'Admins', '2016-08-30 21:15:01', '2016-08-30 21:15:01'),
(3, 'Managers', '2016-08-30 21:15:01', '2016-08-30 21:15:01'),
(4, 'Users', '2016-08-30 21:15:01', '2016-08-30 21:15:01');
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(55) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`group_id` int(11) NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
KEY `group_id` (`group_id`),
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO `users` (`id`, `username`, `password`, `group_id`, `created`, `modified`) VALUES
(1, 'super', '$2y$10$zlIAsTubGNqvhn3pS76jN.QeF6msHGYGcTIJ7KgzS757vDsioA3xa', 1, '2016-09-15 15:57:16', '2019-06-12 19:03:49'),
(2, 'admin', '$2y$10$f26.qAgF5Jnl7b3zIdRlQuKTBkrxM2d1xtrLlIee0EKULfSKgejqm', 2, '2016-09-15 15:57:16', '2019-06-12 19:03:49'),
(3, 'manager', '$2y$10$fx0/o/XU3WPO5.nnP7cnCeSuFsFjxCMkk72DciLqABzHp50cOFnre', 3, '2016-09-15 15:57:16', '2019-06-12 19:03:49');
Três usuários usando o hash bcrypt:
Login - super
Senha - abc123S@
Login - admin
Senha - abc123A@
Login - manager
Senha - abc123M@
Configurar o banco em config/app.php
Configurar a rota default em config/routes.php para Customers/index
Gerar o código com o bake
bin/cake bake all customers
bin/cake bake model Users
bin/cake bake model Groups
bin/cake bake controller Users --prefix admin
bin/cake bake template Users --prefix admin
bin/cake bake controller Groups --prefix admin
bin/cake bake template Groups --prefix admin
Adicionar ao webroot/css/style.css
.emlinha{
display: inline;
padding: 10px;
text-align: center;
}
li a{
color: #fff;
}
Crie um arquivo src/Template/Element/menu.ctp contendo:
<div>
<ul>
<li class="emlinha"><?= $this->Html->link(__('Customers'), '/customers/index') ?></li>
<li class="emlinha"><?= $this->Html->link(__('Groups'), '/admin/groups/index') ?></li>
<li class="emlinha"><?= $this->Html->link(__('Users'), '/admin/users/index') ?></li>
<li class="emlinha"><?= $this->Html->link(__('Login'), '/admin/users/login') ?></li>
<li class="emlinha"><?= $this->Html->link(__('Sair'), '/admin/users/logout') ?></li>
</ul>
</div>
Edite o src/Template/Layout/default.ctp
E sobrescreva o código entre
<nav>
e
</nav>
Com este abaixo, logo abaixo de <body>:
<nav class="top-bar expanded" data-topbar role="navigation">
<?php
if($loguser){
?>
<h2 align="center"><?= $titulo ?><?php echo $this->element('menu') ?></h2>
<div align="center"><?= $this->fetch('title') ?></div>
<div class="right">Logado como: <strong><?=__($loguser) ?></strong> </div>
<?php
}else{
echo '<h3 class="titulo" align="center">Acesso ao Sistema</h3>';
}
?>
</nav>
Somente customers poderá ser acessado livremente, groups e users serão restritos
Criar a rota admin em config/routes.php
Router::prefix('admin', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
Obs.:
Veja que na pasta src/Controller/Admin os controllers tem o namespace para Admin
namespace App\Controller\Admin;
Adicionar os actions login e logout para src/Controller/Admin/UsersController.php:
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error(__('Usuário ou senha ínvalido, tente novamente'));
}
}
public function logout()
{
return $this->redirect($this->Auth->logout());
}
Adicionar o login.ctp para src/Template/Admin/Users:
<div class="users form">
<?= $this->Flash->render('auth') ?>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('Por favor informe seu usuário e senha') ?></legend>
<?= $this->Form->input('username', ['autofocus' => true]) ?>
<?= $this->Form->input('password') ?>
</fieldset>
<?= $this->Form->button(__('Login')); ?>
<?= $this->Form->end() ?>
</div>
Para acessar customers:
http://localhost/admin_area
Ainda não podemos acessar users e groups e o acesso integral de customers é permitido.
Implementar a Auenticação
Para controlar o acesso ao aplicativo
Adicionar suporte para bcrypt ao login:
Adicionar ao início do src/Model/Entity/User.php:
use Cake\Auth\DefaultPasswordHasher;
Ao final:
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
Implementar a autenticação
Adicione ao initialize() do AppController, logo abaixo do carregamento do componente Flash::
$this->loadComponent('Auth', [
'authorize' => ['Controller'],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login',
'home'
],
//'unauthorizedRedirect' => $this->referer()
'unauthorizedRedirect' => [
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authError' => 'Você não tem permissão para acessar esta área!',
'flash' => [
'element' => 'error'
]
]);
$this->set('titulo', 'Aplicativo para área Admin Restrita');
$user = $this->request->getSession()->read('Auth.User');
$loguser = $user['username'];
$this->set('loguser',$loguser);
Restringindo acesso com o Authorization através do callback isAuthorized().
Agora que os usuários podem se conectar, nós vamos querer limitar os bookmarks que podem ver para aqueles que fizeram. Nós vamos fazer isso usando um adaptador de ‘autorização’. Sendo os nossos requisitos bastante simples, podemos escrever um código em nossa BookmarksController. Mas antes de fazer isso, vamos querer dizer ao AuthComponent como nossa aplicação vai autorizar ações. Em seu AppController adicione o seguinte:
Ao final, abaixo do initialize():
public function isAuthorized($user = null)
{
// Any registered user can access public functions
if (!$this->request->getParam('prefix')) {
return true;
}
// Only admins can access admin functions
if ($this->request->getParam('prefix') === 'admin') {
return (bool)($user['group_id'] === 1 || $user['group_id'] === 2);
}
// Default deny
return false;
}
Observe que os usuários dos grupos 1 (super) e 2 (admin), têm direito a acessar tudo. Aqui podemos customizar mais este comportamento. Mas caso precise de algo mais flexível recomendo o uso de um dos plugins sugeridos ao final.
Abaixo do isAuthorized() adicione:
public function beforeFilter(Event $event)
{
$this->Auth->allow(['customers'=>'index', 'customers'=>'view']);
// Negar acesso para todos os actions do controller Customers para usuários comuns
//$this->Auth->deny('customers');
}
Também podemos negar acesso:
Obs.: Não se aplica para os usuários dos grupos 1 e 2, que podem tudo e já foram previamente autorizados.
As regras abaixo aplicam-se somente para os usuários dos grupos 3 em diante.
// Negar acesso a qualquer parte do aplicativo:
$this->Auth->deny();
// Negar apenas para o action add
$this->Auth->deny('add');
// Negar para um grupo de actions
$this->Auth->deny(['add', 'edit']);
// Negar para um controller
$this->Auth->deny('customers');
Observe que são públicos apenas os actions index e view do controller Customers.
Isto indica a que estamos garantindo acesso completo em todos os controllers:
$this->Auth->allow(['index', 'view']);
Podemos controlar o acesso por este método acima.
Para que um controller permita o acesso completo adicione ao seu início:
use Cake\Event\Event;
public function beforeFilter(Event $event)
{
$this->Auth->allow();
}
Testar o acesso para áreas restritas:
http://localhost/admin_area/admin/users/add
ou
http://localhost/admin_area/customers/add
Veja que ele pede o controller Users no src. Então lhe daremos:
E crie o src/Controller/UsersController.php com apenas:
<?php
namespace App\Controller;
use App\Controller\AppController;
class UsersController extends AppController
{
public function login()
{
return $this->redirect('/admin/users/login');
}
}
Agora experimente acessar
http://localhost/admin_area/customers/add
Será redirecionado para o login.
Acesse com os logins citados no início. Veja que super e admin têm acesso completo e manager tem acesso somente a customers.
Sugestão de Plugins
Para um maior controle e mais recursos use um dos plugins abaixo:
Acl da equipe do Cake, via terminal/prompt:
https://github.com/cakephp/acl
Um ótimo exemplo de uso
https://github.com/mattmemmesheimer/cakephp-3-acl-example
Plugim com administração via interface web
https://github.com/ribafs/admin-br
Mais detalhes em:
https://book.cakephp.org/3.0/en/controllers/components/authentication.html
https://book.cakephp.org/3.0/en/tutorials-and-examples/cms/authentication.html
https://book.cakephp.org/3.0/pt/tutorials-and-examples/blog-auth-example/auth.html
Comments fornecido por CComment