Zend ACL Dynamic Assertion - zend-framework

I want to restrict my users to edit/delete only the comments which they added. I found an example on youtube by a guy named intergral30 and followed his instruction. And now my admin account has the possibility to edit/delete everything, but my user has no access to his own comment.
Here's the code:
Resource
class Application_Model_CommentResource implements Zend_Acl_Resource_Interface{
public $ownerId = null;
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
}
Role
class Application_Model_UserRole implements Zend_Acl_Role_Interface{
public $role = 'guest';
public $id = null;
public function __construct(){
$auth = Zend_Auth::getInstance();
$identity = $auth->getStorage()->read();
$this->id = $identity->id;
$this->role = $identity->role;
}
public function getRoleId(){
return $this->role;
}
}
Assertion
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
if ($user->id != null && $comment->ownerId == $user->id){
return true;
} else {
return false;
}
}
}
In my ACL I have a function named setDynemicPermissions, which is called in an access check plugin's preDispatch method.
public function setDynamicPermissions() {
$this->addResource('comment');
$this->addResource('post');
$this->allow('user', 'comment', 'modify', new Application_Model_CommentAssertion());
$this->allow('admin', 'post', 'modify', new Application_Model_PostAssertion());
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->_acl->setDynamicPermissions();
}
And I'm calling the ACL-s isAllowed method from my comment model where I return a list of comment objects.
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
if (Zend_Registry::get('acl')->isAllowed($userRole->getRoleId(), $commentResource->getResourceId(), 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
Can anyone tell me what have I done wrong?
Or what should I use if I want to give my admins the right to start a post and other users the right to comment on them. Each user should have the chance to edit or delete his own comment and an admin should have all rights.

You seem to be using the dynamic assertions in a wrong manner, as you are still passing the roleId to isAllowed().
What these dynamic assertions really do, is take a complete object and work with it. Zend will determine which rule has to be used by calling getResourceId() and getRoleId() on your objects.
So all you have to do is pass your objects instead of the strings to isAllowed():
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// This line includes the changes
if (Zend_Registry::get('acl')->isAllowed($userRole, $commentResource, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
But in can be done better
You would not have to implement a total new Application_Model_CommentResource, but instead you can use your actual Application_Model_Comment like this:
// we are using your normal Comment class here
class Application_Model_Comment implements Zend_Acl_Resource_Interface {
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
// all other methods you have implemented
// I think there is something like this among them
public function getOwnerId() {
return $this->ownerId;
}
}
Assertion would then use this object and retrieve the owner to compare it with the actually logged in person:
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface {
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
// using the method now instead of ->ownerId, but this totally depends
// on how one can get the owner in Application_Model_Comment
if ($user->id != null && $comment->getOwnerId() == $user->id){
return true;
} else {
return false;
}
}
And the usage is like this:
public function getComments($id) {
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$comments = array();
foreach ($res as $comment) {
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// no $commentResource anymore, just pure $comment
if (Zend_Registry::get('acl')->isAllowed($userRole, $comment, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}

Related

Prestashop development - how configure a Controller (Property Tab->name is empty)

I want to set up a Controller following this guide:
https://webkul.com/blog/create-modules-admin-controllers-without-creating-tab-prestashop/
So in my custom module I do this:
....
public function install() {
return (parent::install()
&& $this->registerHook('header')
&& $this->registerHook('footer')
&& $this->installTab()
);
}
public function installTab() {
$tab = new Tab();
$tab->active = 1;
$tab->class_name = 'abandonedCartsAdminModuleController';
$tab->name = "test";
//If you don't want to create a tab for your admin controller then Pass id_parent value as -1.
$tab->id_parent = -1;
$tab->module = $this->name;
return $tab->add();
}
This is the Controller: abandonedCartsAdminModuleController.php
<?php
class abandonedCartsAdminModuleController extends AdminModuleController {
public function __construct() {
parent::__construct();
$this->context = Context::getContext();
}
public function init() {
$this->retrieve();
}
public function retrieve() {
...
}
}
What happens when I try to install my module is I have the PrestaShopException: "Property Tab->name is empty
at line 887 in file classes/ObjectModel.php"
$tab->name must be an array, one name by language.
$tab->name = array();
foreach (Language::getLanguages(true) as $lang) {
$tab->name[$lang['id_lang']] = 'test';
}

how to config container with request variable

In the first place I had to configure parameters using the class "ParametersCompilerPass" to get data from database.Here si my class :
class ParametersCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$boutique = $em->getRepository('AcmeBundle:Boutique')->findOneByNom($container->getParameter('boutique.config'));
if(null !== $boutique){
$container->setParameter('url_site', $boutique->getUrl());
$container->setParameter('idboutique', $boutique->getId());
}else{
$container->setParameter('url_site', null);
$container->setParameter('idboutique', 0);
}
}
}
and when i set a parameter from request, it dont work, i tried in adding this code :
$request = $container->get('request_stack')->getCurrentRequest();
if($request->getMethod() == 'POST'){
if (null !== $choixbout = $request->get('choixbout')){
// $this->container->setParameter('idboutique',$choixbout);
}
}
the service request_stack return null.
I do not know how to configure a parameter from a POST variable.
Hope you can help me.
thanks
Is it solid requirement to have the parameter set?
It could be handy to create a service which has a request dependency that can act as a boutique parameter holder.
For example
# app/config/services.yml
app.boutique:
class: AppBundle\Boutique\Boutique
arguments: ['#request_stack']
app.boutique_info_dependant1:
class: AppBundle\Boutique\BoutiqueDependant1
arguments: ['#app.boutique']
app.boutique_info_dependant2:
class: AppBundle\Boutique\BoutiqueDependant2
arguments: ['#app.boutique']
This would be a parameter handler.
# AppBundle/Boutique/Boutique.php
class Boutique
{
/** #var RequestStack */
private $requestStack;
/**
* BoutiqueListener constructor.
* #param ContainerInterface $container
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getBoutique()
{
$request = $this->requestStack->getCurrentRequest();
/// here you can add an extra check if the request is master etc.
if ($request->getMethod() == Request::METHOD_POST) {
if (null !== $choixbout = $request->get('choixbout')) {
return $choixbout;
}
}
return null;
}
}
Then using the handler
class BoutiqueDependant1
{
public function __construct(Boutique $boutique)
{
$this->myBoutique = $boutique->getBoutique();
}
}
This does not look like the best solution but could work...
Other option would be to rethink the application architecture to handle boutique information somehow differently.

How to access Model in Controller in zend framework?

I am trying to call model methods from controller. but I am getting Fatal error: Class 'GuestModel' not found in. error
following is the code ::
Controller ::
class GuestController extends Zend_Controller_Action
{
public function indexAction(){
$guestbook = new GuestModel();
$this->view->entries = $guestbook->fetchAll();
}
}
Model::
class GuestModel extends Zend_Db_Table_Abstract
{
public function fetchAll()
{
$resultSet = $this->getDbTable()->fetchAll();
$entries = array();
foreach ($resultSet as $row) {
$entry = new Application_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created);
$entries[] = $entry;
}
return $entries;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
}
Zend Framework autoload depends on using the correct directory structure and file naming conventions to find the classes automagically, from the looks of your code my guess would be you're not following it.
I see 2 possible solutions for your problem:
If possible, rename your class to Application_Model_Guestbook, the file to Guestbook.php and make sure to move it to your application/models/ directory. Then you just need to call it in your controller as $guestbook = new Application_Model_Guestbook();. Check this documentation example;
Create your own additional autoloading rules. Check the official documentation regarding Resource Autoloading.

ZF using placeholders inside plugins

I'm trying to implement a plugin that handles responses to the user on success or failure in a persistance transaction. When the response is false I use a _forward to the action that performed the form's submit and get my placeholder message shown but when the response is true I use a _redirect to the browse with the new record shown.
My problem is that when I use _redirect the browser doesn't show the placeholder message. I'll show the code here:
/**
* Plugin
*/
class Application_Plugin_PostMessage extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$message = $request->getParam('message');
$error = $request->getParam('error');
if (null !== $message || null !== $error) {
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$placeHolder = $view->placeholder('message');
$placeHolder->setPostfix('</div>');
if (null !== $error) {
$placeHolder->setPrefix('<div class="errorMessage">')
->append($error);
}
elseif (null !== $message) {
$placeHolder->setPrefix('<div class="infoMessage">')
->append($message);
}
}
}
}
/**
* Controller
*/
class My_FooController extends Zend_Controller_Action
{
public function init()
{
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Application_Plugin_PostMessage());
}
...
public function browseAction()
{
...
// No message is shown here on redirect
...
}
public function newAction()
{
...
// This code shows the placeholder on _forward call
...
}
public function insertAction()
{
if(true) {
return $this->_redirect('/my/foo/browse?message='
. urlencode("success message"));
}
else {
return $this->_forward('new', null, null, array(
'error' => 'error messsage'
));
}
}
}
I can't use _forward on success because I don't want the use of [F5] key repeats the insert action
Thanks in advance
This is what Flash Messenger is for:
http://framework.zend.com/manual/en/zend.controller.actionhelpers.html#zend.controller.actionhelper.flashmessenger.basicusage
It stores messages in your session removing the need for passing messages as you are.

how to update main registration table and secondary multicheckbox populated table?

I have a registration form with different input fields one of them being a multi checkbox so that the user can decide what countries he wants to receive information from. This last one is created like this:
$pais = $this->createElement('multiCheckbox', 'pais');
$pais->setLabel('Pais\es: ');
$pais->addMultioption('1', 'Argentina');
$pais->addMultioption('2', 'Espa?a');
$pais->addMultioption('3', 'Brasil');
$pais->addMultioption('4', 'USA');
$pais->addMultioption('5', 'Italia');
$this->addElement($pais);
In my UserController I have the following action to update the table 'users':
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$userMode->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
return $this->_forward('list');
}
}
$userForm->setAction('/user/create');
$this->view->form = $userForm;
}
which of course, right now is not contemplating the multicheckbox populatedn$pais variable, nor here nor in the model:
class Model_User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
public function createUser($email, $password, $url, $responsable, $role)
{
// create a new row
$rowUser = $this->createRow();
if($rowUser) {
// update the row values
$rowUser->email = $email;
$rowUser->password = md5($password);
$rowUser->url = $url;
$rowUser->responsable = $responsable;
$rowUser->role = $role;
$rowUser->save();
//return the new user
return $rowUser;
} else {
throw new Zend_Exception("El usuario no se ha podido crear!");
}
}
}
I have also a 'pais' table, which contains the 5 different countries, and I'm working on a separate model for 'users_has_pais' which is the table I created in the workbench for this purpose...but I'm not getting any results with what I'm doing right now. Can someone point me in the right path to get to update 'users_has_pais' at the same time that I update the 'users' table?
Thanks a lot in advance to anyone with good advice on this.
EDIT: this is the db model in case anyone needs it to figure out what I'm saying
EDIT2:
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$user = $userModel->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
$paises = $this->getRequest()->getParam('pais');
$userId = intval($user['id']);
require_once APPLICATION_PATH . '/models/UserHasPais.php';
$paisesModel = new Model_UsersHasPais();
$paisesModel->updateUserPais($userId, $paises);
return $this->_forward('index');
}
}
and users_has_pais model:
class Model_UsersHasPais extends Zend_Db_Table_Abstract
{
protected $_name = 'users_has_pais';
public function updateUserPais($id, array $paises)
{
$row = ($r = $this->fetchRow(array('users_id = ?' => $id))) ? $r : $this->createRow();
foreach($paises as $pais){
$row->users_id = $id;
$row->pais_id = $pais;
$row->save();
}
}
}
One way would be to create user row first, and use it's ID when creating rows for 'user_has_pais'. A pseudo-code is below:
public function createAction()
{
$this->view->pageTitle = 'Create User';
require_once APPLICATION_PATH . '/models/Users.php';
$userForm = new Form_User();
if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
$userModel = new Model_User();
$newUserRow = $userMode->createUser(
$userForm->getValue('email'),
$userForm->getValue('password'),
$userForm->getValue('url'),
$userForm->getValue('responsable'),
$userForm->getValue('role')
);
$user_id = newUserRow->id;
$checkBoxValues = $userForm->getValue('pais');
// $checkBoxValues should be an array where keys are option names and
// values are values. If checkbox is not checked, than the value = 0;
// At this moment I'm not 100% sure of the real nature of the 'pais' value,
// but this is only an example.
// I also assume that the values of the checkboxfields correspond to IDs in
// 'pais'.
foreach ($checkBoxValues as $key => $pais_id) {
if (intval(pais_id) > 0) {
// if language was checked
// do insert into user_has_pais having $pais_id and $user_id.
}
}
return $this->_forward('list');
}
}
$userForm->setAction('/user/create');
$this->view->form = $userForm;
}
You could also put all of this in transaction if you want.
Hope this helps, or at least you point you in the right direction.