Zend ACL - convert permission to public - php

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');

Related

Zend ACl How to implement Custom Dynamic Assertion?

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.

How to access model data in SecurityPlugin (Acl) in Phalcon PHP?

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();
...

zf2 api response event catched by bjyauthorize

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.

Custom authentication issue in Yii2

I have added a custom authentication component for a Yii2 RESTful project and it is validating credentials OK but it is not returning the valid User object to \Yii::$app->user
The component looks like this:
public function authenticate($user, $request, $response) {
$bearerToken = \Yii::$app->getRequest()->getQueryParam('bearer_token');
$user = Account::findIdentityByAccessToken($bearerToken);
return $user;
}
And the Account model method looks like this:
public static function findIdentityByAccessToken($token, $userType = null) {
return static::findOne(['bearer_token' => $token]);
}
I can see $user is the expected record of Account when debugging in the authenticate() method but \Yii::app()->user seems to be a newly instatiated user. \Yii::app()->user->identity is equal to null.
Can anyone see what I'm doing wrong here?
To login user this is not enough:
Account::findIdentityByAccessToken($bearerToken);
You need to call $user->login($identity) inside authentificate(). See for example how it's implemented in yii\web\User loginByAccessToken():
public function loginByAccessToken($token, $type = null)
{
/* #var $class IdentityInterface */
$class = $this->identityClass;
$identity = $class::findIdentityByAccessToken($token, $type);
if ($identity && $this->login($identity)) {
return $identity;
} else {
return null;
}
}
So you can also call it in your custom auth method:
$identity = $user->loginByAccessToken($accessToken, get_class($this));
See for example how it's implemented in yii\filters\auth\QueryParamAuth.
And you also need to return $identity, not $user. Also handling failure is missing in your code. See how it's implemented in built-in auth methods:
HttpBasicAuth
HttpBearerAuth
QueryParamAuth
More from official docs:
yii\web\User login()
yii\filters\auth\AuthInterface
Update:
Nothing forces you to use loginByAccessToken(), I just mentioned it as an example.
Here is an example of custom auth method that I wrote quite a while ago, not sure if it's 100% safe and true, but I hope it can help you to understand these details:
Custom auth method:
<?php
namespace api\components;
use yii\filters\auth\AuthMethod;
class HttpPostAuth extends AuthMethod
{
/**
* #see yii\filters\auth\HttpBasicAuth
*/
public $auth;
/**
* #inheritdoc
*/
public function authenticate($user, $request, $response)
{
$username = $request->post('username');
$password = $request->post('password');
if ($username !== null && $password !== null) {
$identity = call_user_func($this->auth, $username, $password);
if ($identity !== null) {
$user->switchIdentity($identity);
} else {
$this->handleFailure($response);
}
return $identity;
}
return null;
}
}
Usage in REST controller:
/**
* #inheritdoc
*/
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpPostAuth::className(),
'auth' => function ($username, $password) {
$user = new User;
$user->domain_name = $username;
// This will validate password according with LDAP
if (!$user->validatePassword($password)) {
return null;
}
return User::find()->username($username)->one();
},
];
return $behaviors;
}
Specifying $auth callable is also can be found in HttpBasicAuth.

Silex framework : Create an UserProvider/PasswordEncoder/User

I use the framework Silex, especially SecurityServiceProvider.
I have to create my own User class (because my salt is the username => with the default class the salt is null) :
<?php
namespace Adh\Security;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
class User implements AdvancedUserInterface {
private $username;
private $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function getRoles()
{
return array();
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->username;
}
...
}
Until this, no problem. Now, I have to create a custom UserProvider to retrieve my user from MySQL :
<?php
namespace Adh\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Doctrine\DBAL\Connection;
class UserProvider implements UserProviderInterface
{
private $conn;
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
public function loadUserByUsername($username)
{
$stmt = $this->conn->executeQuery('SELECT * FROM account WHERE username like ?', array($username));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Le nom d\'utilisateur "%s" n\'existe pas', $username));
}
return new \Adh\Security\User($user['username'], $user['sha_pass_hash']);
}
...
}
And to register the security provider :
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'user' => array(
'pattern' => '^/user',
'form' => array('login_path' => '/connexion', 'check_path' => '/user'),
'users' => $app->share(function () use ($app) {
return new Adh\Security\UserProvider($app['db']);
})
)
)
));
$app['security.encoder_factory'] = $app->share(function ($app) {
return new EncoderFactory(
array('Adh\Security\User' => new Adh\Security\PasswordEncoder())
);
});
It works, except when the authentification is positive (the username and password match) I've this exception :
RuntimeException: There is no user provider for user
"Adh\Security\User".
How to set my UserProvider for my User class ?
Thank's
I found the solution. To create my provider I followed this example : http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider
In the refreshUser method:
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
This is correct for the default User class: I have my own User class so the exception is raised.
The condition becomes :
if (!$user instanceof \Adh\Security\User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
Your function
loadUserByUsername()
does not return any role. By default a Symfony\Component\Security\Core\User\User record is returned with the roles of the user as third parameter. At least any user must have one role.
Sample:
use Symfony\Component\Security\Core\User\User;
public function loadUserByUsername($username)
{
$frameworkUser = new FrameworkUser($this->app);
if (false === ($user = $frameworkUser->selectUser($username))) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return new User($user['username'], $user['password'], $user['roles'], true, true, true, true);
}

Categories