Zend Framework: How to display multiple actions, each requiring different authorizations levels, on a single page - zend-framework

Imagine I have 4 database tables, and an interface that presents forms for the management of the data in each of these tables on a single webpage (using the accordion design pattern to show only one form at a time). Each form is displayed with a list of rows in the table, allowing the user to insert a new row or select a row to edit or delete. AJAX is then used to send the request to the server.
A different set of forms must be displayed to different users, based on the application ACL.
My question is: In terms of controllers, actions, views, and layouts, what is the best architecture for this interface?
For example, so far I have a controller with add, edit and delete actions for each table. There is an indexAction for each, but it's an empty function. I've also extended Zend_Form for each table. To display the forms, I then in the IndexController pass the Forms to it's view, and echo each form. Javascript then takes care of populating the form and sending requests to the appropraite add/edit/delete action of the appropriate controller. This however doesn't allow for ACL to control the display or not of Forms to different users.
Would it be better to have the indexAction instantiate the form, and then use something like $this->render(); to render each view within the view of the indexAction of the IndexController? Would ACL then prevent certain views from being rendered?
Cheers.

There are a couple of places you could run your checks against your ACL:
Where you have your loop (or hardcoded block) to load each form.
In the constructor of each of the Form Objects, perhaps throwing a custom exception, which can be caught and appropriately handled.
From the constructor of an extension of Zend_Form from which all your custom Form objects are extended (probably the best method, as it helps reduce code duplication).
Keep in mind, that if you are using ZF to perform an AJAXy solution for your updating, your controller needs to run the ACL check in it's init() method as well, preventing unauthorized changes to your DB.
Hope that helps.

Have you solved this one yet?
I'm building a big database app with lots of nested sub-controllers as panels on a dashboard shown on the parent controller.
Simplified source code is below: comes from my parentController->indexAction()
$dashboardControllers = $this->_helper->model( 'User' )->getVisibleControllers();
foreach (array_reverse($dashboardControllers) as $controllerName) // lifo stack so put them on last first
{
if ($controllerName == 'header') continue; // always added last
// if you are wondering why a panel doesn't appear here even though the indexAction is called: it is probably because the panel is redirecting (eg if access denied). The view doesn't render on a redirect / forward
$this->_helper->actionStack( 'index', $this->parentControllerName . '_' . $controllerName );
}
$this->_helper->actionStack( 'index', $this->parentControllerName . '_header' );
If you have a better solution I'd be keen to hear it.
For my next trick I need to figure out how to display these in one, two or three columns depending on a user preference setting

I use a modified version of what's in the "Zend Framework in Action" book from Manning Press (available as PDF download if you need it now). I think you can just download the accompanying code from the book's site. You want to look at the Chapter 7 code.
Overview:
The controller is the resource, and the action is the privilege.
Put your allows & denys in the controller's init method.
I'm also using a customized version of their Controller_Action_Helper_Acl.
Every controller has a public static getAcls method:
public static function getAcls($actionName)
{
$acls = array();
$acls['roles'] = array('guest');
$acls['privileges'] = array('index','list','view');
return $acls;
}
This lets other controllers ask about this controller's permissions.
Every controller init method calls $this->_initAcls(), which is defined in my own base controller:
public function init()
{
parent::init(); // sets up ACLs
}
The parent looks like this:
public function init()
{
$this->_initAcls(); // init access control lists.
}
protected function _initAcls()
{
$to_call = array(get_class($this), 'getAcls');
$acls = call_user_func($to_call, $this->getRequest()->getActionName());
// i.e. PageController::getAcls($this->getRequest()->getActionName());
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$this->_helper->acl->allow($acls['roles'], $acls['privileges']);
}
}
Then I just have a function called:
aclink($link_text, $link_url, $module, $resource, $privilege);
It calls {$resource}Controller::getAcls() and does permission checks against them.
If they have permission, it returns the link, otherwise it returns ''.
function aclink($link_text, $link_url, $module, $resource, $privilege)
{
$auth = Zend_Auth::getInstance();
$acl = new Acl(); //wrapper for Zend_Acl
if(!$acl->has($resource))
{
$acl->add(new Zend_Acl_Resource($resource));
}
require_once ROOT.'/application/'.$module.'/controllers/'.ucwords($resource).'Controller.php';
$to_call = array(ucwords($resource).'Controller', 'getAcls');
$acls = call_user_func($to_call, $privilege);
if(isset($acls['roles']) && is_array($acls['roles']))
{
if(count($acls['roles'])==0) { $acls['roles'] = null; }
if(count($acls['privileges'])==0){ $acls['privileges'] = null; }
$acl->allow($acls['roles'], $resource, $acls['privileges']);
}
$result = $acl->isAllowed($auth, $resource, $privilege);
if($result)
{
return ''.$link_text.'';
}
else
{
return '';
}
}

Related

Create CSS classes based on user role

Working with Moodle 3.9 and a fork of the current Academi theme.
I am trying to create a css class hook on the <body> element which is based on the user's role(s). I understand that a user's role(s) are context-driven, (maybe a student in one course, and something else in another), so I would output all the roles as classes for a given context.
Based on some extensive digging on the moodle forum I have the following:
// User role based CSS classes
global $USER, $PAGE;
$context = context_system::instance();
$roles = get_user_roles($context, $USER->id, true);
if(is_array($roles)) {
foreach($roles as $role) {
$PAGE->add_body_class('role-'.$role->shortname);
}
} else {
$PAGE->add_body_class('role-none');
}
Preferably I'd like this to run on every page. From within the theme, I've tried just placing this in just about every location/function I thought could be executed early enough to modify the body element. Either I get no output at all or a warning indicating that it is too late to run add_body_class().
I've had a skim of the Page and Output APIs and I still don't have a sense of how or when to execute this code. Should this be a custom plugin instead?
Override the header() function on the core renderer of your current theme template, for instance, on theme\YOUR_THEME\classes\output\core_renderer.php:
namespace theme_YOUR_THEME\output;
defined('MOODLE_INTERNAL') || die;
use context_system;
class core_renderer extends \theme_boost\output\core_renderer {
public function header() {
global $USER;
$roles = get_user_roles(context_system::instance(),$USER->id);
if (is_array($roles) && !empty($roles)){
foreach($roles as $role){
$this->page->add_body_class('role-'.$role->shortname);
}
}else{
$this->page->add_body_class('role-none');
}
return parent::header();
}
}
This way, every page would call this function and have the desired css classes added to the body tag.

How to give a fixed Uid to my Action

Hy,
I'm trying to call my action with allways a fixed Uid (configured by TS) so I could put a plugin on my page to register for a specific Event. And don't have to go over a Event List click the Event click register.
I tried the following which did not work out:
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = 'DD8B2164290B40DA240D843095A29904'
)
The next didn't one work either!
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = Null
) {
$myinstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
'XYZ\\xyz\\Domain\\Model\\Event'
);
$event = $myinstance->findByUid('DD8B2164290B40DA240D843095A29904');
.......
}
So I was woundering is there a way to give my fixed Uid to the action?
In TYPO3 calling Extbase actions is done in the routing and dispatching components - to pass anything from the outside that is different from a numeric uid value a custom property TypeConverter would have to be implemented that transforms a particular string pattern into a value domain object of type Event.
However, there's a simpler approach by using configuration:
1) Provide configuration in TypoScript
Extbase uses a strong naming convention based on the extension name and optionally the plugin name. Thus, either tx_myextension or tx_myextension_someplugin can be used - latter is more specific for for according somePlugin. Besides that settings are automatically forwarded and provided in an Extbase controller context - accessible by $this->settings.
plugin.tx_xyz {
settings {
newActionEventIdentifier = DD8B2164290B40DA240D843095A29904
}
}
2) Retrieve data via repository
\XYZ\xyz\Domain\Repository\EventRepository
Use a dedicated EventRepository::findByIdentifier(string) method to retrieve the data. The property names are just assumptions since there are no explicit mentions how exactly the event data is persisted and whether it is persisted in a relational DBMS at all.
<?php
namespace XYZ\xyz\Domain\Repository;
class EventRepository
{
public function findByIdentifier($identifier)
{
$query = $this->createQuery();
$query->matching(
$query->equals('event_id', $identifier)
);
return $query->execute();
}
}
3) Putting all together in the according controller
The $event property was removed from the action since that entity is pre-defined and cannot be submitted from the outside (and to support the string to Event entity transformation a custom TypeConverter would be required as mentioned earlier).
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = null
) {
$event = $this->eventRepository->findByIdentifier(
$this->settings['newActionEventIdentifier']
);
if ($event === null) {
throw new \RuntimeException('No event found', 1522070079);
}
// the regular controller tasks
$this->view->assign(...);
}

REST Api with QueryParamAuth authenticator - Yii2

I'm trying to create rest api for my application to get the data in my android app. This is my controller
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\filters\auth\QueryParamAuth;
/**
* Tk103 Controller API
*/
class Tk103Controller extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Tk103CurrentLocation';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => QueryParamAuth::className(),
];
return $behaviors;
}
}
I added access_token column in my user table, implemented findIdentityByAccessToken() in User Model and calling this URL
http://localhost:7872/api/v1/tk103s?access-token=abcd
This is working great and returning data if and only if access_token matches with any single user in the table.
I checked QueryParamAuth class and found that QueryParamAuth::authenticate() returns $identity after successful authentication.
Currently this url is returning whole data of my table.
What I want is(after authentication):
Get user id/username of the requester.
Based on that id/username, the data related to him as per relations of tables in db. (currently whole rows are being returned but I want only few that are matching with the current requester/user)
I tried but didn't getting any clue to catch returned $identity of user after authentication.
And I know it is possible too to make this work. Help me out folks to create magic.
Get user id/username of the requester.
That user instance you did return within the findIdentityByAccessToken method should be accessible any where inside your app within Yii::$app->user->identity. And should hold all the attributes retreived from DB. here is a quick example of using it to check access within the checkAccess method of the ActiveController class:
public function checkAccess($action, $model = null, $params = [])
{
// only an image owner can request the related 'delete' or 'update' actions
if ($action === 'update' or $action === 'delete') {
if ($model->user_id !== \Yii::$app->user->identity->id)
throw new \yii\web\ForbiddenHttpException('You can only '.$action.' images that you\'ve added.');
}
}
Note that the checkAccess is by default an empty method that is manually called inside all the built-in actions in ActiveController. the Idea is to pass the action ID and the model instance to it just after retrieving it from DB and before modifying it so we can do extra checks. If you just need to perform checks by actions ID then yii\filters\AccessControl may be enough but inside checkAccess you are expecting to also get the model instance itself so it is important to note that when building your own actions or overriding existing onces. be sure to manually invoke it the same way it is done in UpdateAction.php or DeleteAction.php.
whole rows are being returned but I want only few .. matching with .. current requester/user
It depends on how your data is structured. You can override ActiveController's actions to filter results before outputting them, it can be handled in the related SearchModel class if you are using one or it can be handled in model. A quick tip may be by simply overriding the find method inside your model:
public static function find()
{
return parent::find()->where(['user_id' => Yii::$app->user->getId()]); // or Yii::$app->user->identity->id
}
Note that this works only when using ActiveRecord. Which means when using this:
$images = Image::find()->all();
The find method we just overriden will be filtered by default by always including that where condition before generating the DB query. Also note the default built-in actions in ActiveController are using ActiveRecords but if you are using actions where you are constructing the SQL queries using the Query Builder then you should manually do the filtering.
The same can be done if using ActiveQuery (maybe better explained here) by doing this:
public static function find()
{
$query = new \app\models\Image(get_called_class());
return $query->andWhere(['user_id' => Yii::$app->user->getId()]);
}

best practice on rendering several views based on same dynamic data

I would like to have some input on the following:
I would like some views to be systematically rendered (I can call $this->render from
my layout for these views) regardless of which controller/action is executed.
However the content used in these views is based on the same dynamically generated data and the code behind it is quite complex so I can't put the logic inside the views for obvious optimization/performance issues.
I could use $this->_helper->actionStack in each controller to call another controller in which data for the views would be prepared however I would like to do without modifying the existing controllers
I would be tempted to put something in the bootstrap since what I want is common to my application however I just don't know what to do.
That's what View Helpers are for.
In the View Helper you can fetch your data (through models or service layer) and prepare it for output.
<?php
class View_Helper_Foobar extends Zend_View_Helper_Abstract
{
protected $_data;
public function foobar()
{
if (null !== $this->_data) {
$this->_data = some_expensive_data_getter();
}
return $this;
}
public function __toString()
{
return $this->view->partial('foobar.phtml', array('data' => $this->_data));
}
}

Zend_Form using subforms getValues() problem

I am building a form in Zend Framework 1.9 using subforms as well as Zend_JQuery being enabled on those forms. The form itself is fine and all the error checking etc is working as normal. But the issue I am having is that when I'm trying to retrieve the values in my controller, I'm receiving just the form entry for the last subform e.g.
My master form class (abbreviated for speed):
Master_Form extends Zend_Form
{
public function init()
{
ZendX_JQuery::enableForm($this);
$this->setAction('actioninhere')
...
->setAttrib('id', 'mainForm')
$sub_one = new Form_One();
$sub_one->setDecorators(... in here I add the jQuery as per the docs);
$this->addSubForm($sub_one, 'form-one');
$sub_two = new Form_Two();
$sub_two->setDecorators(... in here I add the jQuery as per the docs);
$this->addSubForm($sub_two, 'form-two');
}
}
So that all works as it should in the display and when I submit without filling in the required values, the correct errors are returned. However, in my controller I have this:
class My_Controller extends Zend_Controller_Action
{
public function createAction()
{
$request = $this->getRequest();
$form = new Master_Form();
if ($request->isPost()) {
if ($form->isValid($request->getPost()) {
// This is where I am having the problems
print_r($form->getValues());
}
}
}
}
When I submit this and it gets past isValid(), the $form->getValues() is only returning the elements from the second subform, not the entire form.
I recently ran into this problem. It seems to me that getValues is using array_merge, instead of array_merge_recursive, which does render to correct results. I submitted a bug report, but have not gotten any feedback on it yet.
I submitted a bug report (http://framework.zend.com/issues/browse/ZF-8078). Perhaps you want to vote on it?
I think that perhaps I must have been misunderstanding the way that the subforms work in Zend, and the code below helps me achieve what I wanted. None of my elements share names across subforms, but I guess this is why Zend_Form works this way.
In my controller I now have:
if($request->isPost()) {
if ($form->isValid($request->getPost()) {
$all_form_details = array();
foreach ($form->getSubForms() as $subform) {
$all_form_details = array_merge($all_form_details, $subform->getValues());
}
// Now I have one nice and tidy array to pass to my model. I know this
// could also be seen as model logic for a skinnier controller, but
// this is just to demonstrate it working.
print_r($all_form_details);
}
}
I have a same problem to get value from subforms I solve it with this but not my desire one
code:
in controller i get value with this code that 'rolesSubform' is my subform name
$this->_request->getParam ( 'rolesSubform' );
Encountered the same problem. Used post instead of getValues.
$post = $this->getRequest()->getPost();
There are times when getValues does not return the same values returned by $post.
Must be a getValues() bug.