Thanks to all in advance for posting answers.
Actually I am learning the Zend framework so now I am working with Zend ACL for allowed/deny multiple user roles to access controller/action. so for this, I did create a helper in app/controllers/helpers/acl.php and a code in app/bootstrap.php. now I did use this helper in bootstrap.php so when the application will be load/initialize then it will be work. Now it's working but I am looking for advance I want to add custom assertion where is allowed only for the user which is related to him like I can only edit or delete post I did create it.
So if you can help me please do.
My code is posted below
file App/Controllers/Helpers/Acl.php
<?php
require_once 'Zend/Controller/Action/Helper/Abstract.php';
class Zend_Controller_Action_Helper_Acl extends Zend_Controller_Action_Helper_Abstract {
protected $acl;
protected $role;
function __construct() {
$this->sess = new Zend_Session_Namespace("session");
$this->logger = Zend_Registry::get('logger');
}
protected function getAcl(){
if (is_null($this->acl)){
$acl = new Zend_Acl();
$roles = array('owner', 'administrator', 'editor', 'readonly');
$controllers = array('index', 'projects', 'applications', 'checks', 'settings', 'ajax', 'error', 'languageswitch');
//Add Roles
foreach ($roles as $role) {
$acl->addRole(new Zend_Acl_Role($role));
}
//Add Resources
foreach ($controllers as $controller) {
$acl->add(new Zend_Acl_Resource($controller));
//Administrator, Editior, Readonly
if($controller == 'projects'){
$acl->allow('administrator', $controller, array('main', 'add', 'detail', 'edit'));
$acl->allow('editor', $controller, array('main', 'add', 'detail', 'edit'));
$acl->allow('readonly', $controller, array('main', 'add', 'detail'));
}else if($controller == 'applications'){
$acl->allow('administrator', $controller, array('main', 'add', 'detail', 'edit', 'auditview', 'delete'));
$acl->allow('editor', $controller, array('main', 'add', 'detail', 'edit', 'audit'));
$acl->allow('readonly', $controller, array('main', 'detail', 'audit'));
}else {
$acl->allow('administrator', $controller);
$acl->allow('editor', $controller);
$acl->allow('readonly', $controller);
}
}
//Owner
$acl->allow('owner'); // Owner Has access to everything.
$this->acl = $acl;
}
return $this->acl;
}
protected function getRole(){
if (is_null($this->role)){
$session = new Zend_Session_Namespace('session');
$role = (isset($session->currentrole)) ? $session->currentrole : 'guest';
$this->role = $role;
}
return $this->role;
}
public function direct($resource, $privilege = null){
$acl = $this->getAcl();
$role = $this->getRole();
$allowed = $acl->isAllowed($role, $resource, $privilege);
return $allowed;
}
}
file App/Bootstrap.php
//Set Role Permission
$acl = new Zend_Controller_Action_Helper_Acl();
Zend_Registry::set('acl', $acl);
$permission = Zend_Registry::get('acl');
$request = new Zend_Controller_Request_Http();
$resource = $request->getControllerName();
$privilege = $request->getActionName();
if (!$permission->direct($resource, $privilege)) {
$request->setControllerName('error');
$request->setActionName('error');
}
My advice is to use a different approach.
First, you should create a class where you define you full ACL definitions, for example "My_Acl" that extends Zend_Acl.
Register in My_Acl all your roles, resources and privileges.
Make "My_Acl" singleton in order to get your configured Acl ( Zend_Acl ) using My_Acl::getInstance().
If you like you can also register this instance in your Zend_Registry.
Since you want to check in the user has privileges to access any action of any controller, my advice is to create a Plugin and register it at predispatch, in order to check all access in a single point.
You can also create an action helper ( eg My_Controller_Action_Helper_Acl ) with a isAllowed method that proxies the $alc->isAllowed, in order to check if a specific part of yuor action is accessible to the current logged user.
Related
does anyone know a way of mapping the controller methods with permissions authorisation?
Let's say that I have 20 controllers, with index,store,show and delete methods and I don't wanna put in each method of this controller the correspondent permission, just for the sake of ... DRY.
What I wanna do instead is trying to map the permissions with controller actions.
An example would be:
https://laravel.com/docs/5.5/authorization#writing-gates
Gate::resource('posts', 'PostPolicy');
This is identical to manually defining the following Gate definitions:
Gate::define('posts.view', 'PostPolicy#view');
Gate::define('posts.create', 'PostPolicy#create');
Gate::define('posts.update', 'PostPolicy#update');
Gate::define('posts.delete', 'PostPolicy#delete');
for me something like this would fit:
Permission::map('route', 'permission');
Permission::map('users.store', 'create-user');
or even better
Permission::mapResource('users', '????');
I created a Trait for that, if you have a better suggestion please.
namespace App\Traits;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Pluralizer;
use Spatie\Permission\Exceptions\UnauthorizedException;
trait Authorisation
{
private $permissions = [
'index' => 'view',
'store' => 'create',
'show' => 'view',
'update' => 'edit',
'destroy' => 'delete'
];
private $action;
public function callAction($method, $parameters)
{
$permission = $this->getPermission($method);
if(($permission && Auth::user()->can($permission)) || !$permission)
return parent::callAction($method, $parameters);
if(Request::ajax()) {
return response()->json([
'response' => str_slug($permission.'_not_allowed', '_')
], 403);
}
throw UnauthorizedException::forPermissions([$permission]);
}
public function getPermission($method)
{
if(!$this->action = array_get($this->getPermissions(), $method)) return null;
return $this->routeName() ? $this->actionRoute() : $this->action;
}
public function registerActionPermission($action, $permission) {
$this->permissions[$action] = $permission;
}
private function actionRoute() {
return Pluralizer::singular($this->action . '-' . $this->routeName());
}
private function routeName() {
return explode('.', Request::route()->getName())[0];
}
private function getPermissions()
{
return $this->permissions;
}
}
And use it in controller like:
use Authorisation;
and if a want a custom permission for an action which does not exist in the $permissions:
$this->registerActionPermission('action_name', 'action-permission');
I would like to deny access to the private areas on my website. But I don't know what I am doing wrong.
I don't want to use Acl::DENY as the default rule.
Instead I am using Acl::ALLOW as the global rule and denying access to the private resources.
Here is my code:
<?php
use Phalcon\Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Resource;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Acl\Adapter\Memory as AclList;
class SecurityPlugin extends Plugin {
public function getAcl() {
if (!isset($this->persistent->acl)) {
$acl = new AclList();
$acl->setDefaultAction(Acl::ALLOW);
$roles = array(
'admin' => new Role('Administrators'),
'guests' => new Role('Guests')
);
foreach ($roles as $role) {
$acl->addRole($role);
}
//Private area resources
$privateResources = array(
'admin' => array('index'),
'products' => array('index', 'search', 'new');
foreach ($privateResources as $resource => $actions) {
$acl->addResource(new Resource($resource), $actions);
}
foreach ($privateResources as $resource => $actions) {
foreach ($actions as $action) {
$acl->deny('Guests', $resource, $action);
}
}
}
return $this->persistent->acl;
}
public function beforeDispatch(Event $event, Dispatcher $dispatcher) {
$auth = $this->session->get('auth');
if (!$auth) {
$role = 'Guests';
} else {
$role = 'Admin';
}
$controller = $dispatcher->getControllerName();
$action = $dispatcher->getActionName();
$acl = $this->getAcl();
$allowed = $acl->isAllowed($role, $controller, $action);
if ($allowed != Acl::ALLOW) {
$dispatcher->forward(array(
'controller' => 'errors',
'action' => 'show401'
));
$this->session->destroy();
return false;
}
}
}
Thank you, for trying to help me.
You forgot to actually assign your ACL definitions to $this->persistent->acl
public function getAcl() {
if (!isset($this->persistent->acl)) {
$acl = new AclList();
...
//The acl is stored in session
$this->persistent->acl = $acl;
}
return $this->persistent->acl;
}
By looking at your code, I am guessing you used the Phalcon INVO example for this SecurityPlugin?
If so, refer to line 88. If not, this is a nice and easy example that can help you.
I am working on a small project where I have to implement an Acl, so i tried to implement it like in the sample invo project and it worked well. However, I want to add to the functionality that it gets its roles from the existing model called Role. Whenever I add the line $roles = Role::find(); there is an error in the application. Is it not possible to access this data from a plugin? Or if possible how to access it?
Here is my SecurityPlugin file
<?php
use Phalcon\Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Resource;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Acl\Adapter\Memory as AclList;
/**
* SecurityPlugin
*
* This is the security plugin which controls that users only have access to the modules they're assigned to
*/
class SecurityPlugin extends Plugin
{
/**
* Returns an existing or new access control list
*
* #returns AclList
*/
public function getAcl()
{
if (!isset($this->persistent->acl)) {
$acl = new AclList();
$acl->setDefaultAction(Acl::DENY);
//Register roles **this is the line that causes the problem**
$roles = Role::find();
$acl->addRole(new Role('Guest'));
$acl->addRole(new Role('User'));
//Public area resources
$publicResources = array(
'index' => array('index'),
'User' => array('new'),
'Errors' => array('show401'),
'Session' => array('index', 'register', 'start', 'end')
);
foreach ($publicResources as $resource => $actions) {
$acl->addResource(new Resource($resource), $actions);
}
foreach ($publicResources as $resource => $actions) {
foreach ($actions as $action) {
$acl->allow('Guest', $resource, $action);
$acl->allow('User', $resource, $action);
}
}
//Grant access to public areas to both users and guests
//The acl is stored in session, APC would be useful here too
$this->persistent->acl = $acl;
}
return $this->persistent->acl;
}
/**
* This action is executed before execute any action in the application
*
* #param Event $event
* #param Dispatcher $dispatcher
*/
public function beforeDispatch(Event $event, Dispatcher $dispatcher)
{
$auth = $this->session->get('auth');
if (!$auth){
$role = 'Guest';
} else {
$role = 'User';
}
$controller = $dispatcher->getControllerName();
$action = $dispatcher->getActionName();
if ($auth['username'] == 'Admin') {
return;
}
$acl = $this->getAcl();
$allowed = $acl->isAllowed($role, $controller, $action);
if ($allowed != Acl::ALLOW) {
$dispatcher->forward(array(
'controller' => 'Errors',
'action' => 'show401'
));
$this->session->destroy();
return false;
}
}
}
and here is the related code in services.php
$di->set('dispatcher', function() use ($di) {
$eventsManager = new EventsManager;
/**
* Check if the user is allowed to access certain action using the SecurityPlugin
*/
$eventsManager->attach('dispatch:beforeDispatch', new SecurityPlugin);
$dispatcher = new Dispatcher;
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
It looks like your Role model and the Phalcon\Acl\Role are colliding.
I would alias your Role model to something like RoleModel.
<?php
use Phalcon\Acl\Role;
use \Role as RoleModel;
...
class SecurityPlugin extends Plugin {
public function getAcl() {
...
$roles = RoleModel::find();
...
Hi can someone help me to prevent bjyauthorize to catch my api event error raised?
bjyauthorize redirect non logged user to login form as added to config. But since my api are allowed for all roles even for guest i just want it to return Json error message catched by ApiProblemListener
ApplicationRest\Module.php
class Module implements
ConfigProviderInterface,
AutoloaderProviderInterface
{
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$events = $app->getEventManager();
$listener = $sm->get('ApplicationRest\ApiAuthenticationListener');
$events->getSharedManager()->attach('ApplicationRest\Controller', 'dispatch', $listener, 500);
$events->attach('render', array($this, 'onRender'), 100);
$events->attach($sm->get('ApplicationRest\ApiProblemListener'));
}
/**
* Listener for the render event
* Attaches a rendering/response strategy to the View.
*
* #param \Zend\Mvc\MvcEvent $e
*/
public function onRender($e)
{
$result = $e->getResult();
if (!$result instanceof RestfulJsonModel) {
return;
}
//var_dump(123);exit();
$app = $e->getTarget();
$services = $app->getServiceManager();
$view = $services->get('View');
$restfulJsonStrategy = $services->get('ApplicationRest\RestfulJsonStrategy');
$events = $view->getEventManager();
// register at high priority, to "beat" normal json strategy registered
// via view manager
$events->attach($restfulJsonStrategy, 500);
}
}
Have many modules and i am really thinking to move away my apiModule "ApplicationRest" to another project but don't really want to update model and service each time i make some updates on main project.
Any suggestions would welcome!
Thanks for your time!
EDIT: Provided more HeaderAuthentication class
class HeaderAuthentication implements AdapterInterface
{
const AUTHORIZATION_HEADER = 'Authorization';
const CRYPTO = 'sha256';
protected $request;
protected $repository;
public function __construct(RequestInterface $request, UserRepository $repository)
{
$this->request = $request;
$this->repository = $repository;
}
/**
* Authorization: Key={key} Timestamp={timestamp} Signature={signature}
* #return Result
*/
public function authenticate()
{
$request = $this->getRequest();
if (!$request instanceof Request) {
return;
}
$headers = $request->getHeaders();
// Check Authorization header presence
if (!$headers->has(static::AUTHORIZATION_HEADER)) {
return new Result(Result::FAILURE, null, array(
'Authorization header missing'
));
}
$authorization = $headers->get(static::AUTHORIZATION_HEADER)->getFieldValue();
// Validate public key
$publicKey = $this->extractPublicKey($authorization);
$user = $this->getUserRepository()
->findOneByApiSecret($publicKey);
if (null === $user) {
$code = Result::FAILURE_IDENTITY_NOT_FOUND;
return new Result($code, null, array(
'User not found based on public key'
));
}
// Validate signature
$signature = $this->extractSignature($authorization);
/*$hmac = $this->getHmac($request, $user);
if ($signature !== $hmac) {
$code = Result::FAILURE_CREDENTIAL_INVALID;
return new Result($code, null, array(
'Signature does not match'
));
}*/
return new Result(Result::SUCCESS, $user);
}
}
ApiAuthenticationListener
class ApiAuthenticationListener
{
protected $adapter;
public function __construct(HeaderAuthentication $adapter)
{
$this->adapter = $adapter;
}
public function __invoke(MvcEvent $event)
{
$result = $this->adapter->authenticate();
if (!$result->isValid()) {
$response = $event->getResponse();
// Set some response content
$response->setStatusCode(401);
return $response;
}
// All is OK
$event->setParam('user', $result->getIdentity());
}
}
I'm guessing you configured guards on your route. You need to tell BJYAuthorize, through your module config, that this controller or route shouldn't be protected.
'bjyauthorize' => [
'default_role' => 'guest',
...
'guards' => [
'BjyAuthorize\Guard\Controller' => [
// system tools
['controller' => 'Application\Controller\Api', 'roles' => [] ],
['controller' => 'error', 'roles' => []],
],
],
],
I cut out the nitty gritty that's app specific, but this type of thing is quickly solved. I had a similar need for CLI routes to be unprotected by what is otherwise, http auth.
I need an ACL for my project, I've watched Alexander Romanenko'video I've been looking into Zend ACL which seems to cover my needs. It's so amazing and I've implemented this :
models/LibraryAcl.php :
<?php
class Model_LibraryAcl extends Zend_Acl
{
public function __construct()
{
$this ->add (new Zend_Acl_Resource('index'));
$this ->add (new Zend_Acl_Resource('authentication','login'));
$this-> add (new Zend_Acl_Resource('list'),'books');
$this->addRole(new Zend_Acl_Role('user'));
$this->addRole(new Zend_Acl_Role('admin'),'user');
$this->allow ('user','user');
$this->allow ('user','index');
$this ->allow('admin','books', 'list' ));
}
}
plugins/AccessCheck.php:
<?php
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract{
private $_acl = null;
private $_auth = null;
public function __construct(Zend_Acl $acl , Zend_Auth $auth){
$this->_acl = $acl;
$this->_auth = $auth;
}
public function preDispatch(Zend_Controller_Request_Abstract $request){
$resource = $request->getControllerName();
$action = $request->getActionName();
$identity = $this->_auth->getStorage()->read();
$role = $identity->role;
if (!$this->_acl ->isAllowed($role,$resource,$action) ){
$request->setControllerName('authentication')
->setActionName('login');
}
}
}
All I want is allowing all people (admin, user and people who doesn't log in yet) to access to log in page (authentication/login -> controller name: authentication , action name : login)
UPDATE:
I find out I have to use guest as role and set permission for this role.
$this->addRole(new Zend_Acl_Role('guest'));
$this->addRole(new Zend_Acl_Role('user'), 'guest');
$this->addRole(new Zend_Acl_Role('admin'), 'user');
$this->allow('guest', 'authentication', 'login');
change AccessCheck.php:
<?php
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract{
const UNAUTHORIZED_ACCESS = 'UNAUTHORIZED_ACCESS';
public function preDispatch(Zend_Controller_Request_Abstract $request){
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()){
$role = $auth->getIdentity();
}else{
$role = 'guest';
}
$acl = new Model_LibraryAcl();
$resource = $request->getControllerName();
$action = $request->getActionName();
if ($acl->isAllowed($role,$resource,$action) ){
$request->setControllerName('authentication')
->setActionName('login');
}
}
}
And add it to LibraryAcl.php
$this->addRole(new Zend_Acl_Role('guest'));
$this->addRole(new Zend_Acl_Role('user'), 'guest');
$this->addRole(new Zend_Acl_Role('admin'), 'user');
$this->allow('guest', 'authentication', 'login');