How do I use the Zend ACL to allow access to certain users to some of the actions within a controller? Right now, I only know how to allow a user to access the whole controller, but I want to limit the actions within the controller!
To allow/deny access to certain actions, specify them in the allow/deny methods of Zend_Acl.
The third argument in the Zend_Acl::allow() method will only allow you to set access controls to certain actions on a given controller/resource. For example:
<?php
$acl = new Zend_Acl();
// Roles
$guest = new Zend_Acl_Role('guest');
$user = new Zend_Acl_Role('user');
// Register the roles with the Zend_Acl
$acl->addRole($guest);
$acl->addRole($user, 'guest');
// Resources/Controllers
$indexController = new Zend_Acl_Resource('index');
$profileController = new Zend_Acl_Resource('profile');
// Add resources/controllers to the Zend_Acl
$acl->add($indexController);
$acl->add($profileController);
// Now set limits of access to the resources.
// Guests get access to all the actions in the index controller,
// but to only the login and logout actions in the profile controller.
$acl->allow('guest', 'index');
$acl->allow('guest', 'profile', array('login', 'logout'));
// Users get full access to the profile controller
$acl->allow('user', 'profile');
Related
In my project I have lot of endpoint views (APIViews, ViewSets). For all of them now I set permissions, some of them are default (e.g. AllowAny) and some are custom created:
permission_classes = (IsUserHaveSomePermission,)
Now I want to implement some flexible system, that will allow me to specify set of allowed endpoints for each user, for example:
On front-end I want to select some user and have a list of checkboxes that correspond to project's endpoints.
This is just an utopian solution, some details may be changed, but the main question is to how make something similar so that admins can basically dynamically change list of allowed endpoints/views for user?
thanks in advance
This solution can be implemented by storing if the user has permission to access the current request method and request path.
Create a new db model for storing the user, request method and request path. Lets say the name of the model is RequestPermission
Instead of the path you can store a constant representing the url so that you have the flexibility of editing the path later on. This constant can be the url name which is supported by django.
class RequestPermission(models.Model):
user = user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='request_permissions')
method = models.CharField(max_length=10)
path_name = models.CharField(max_length=200)
create a custom permission class:
class IsUserResuestAllowed(permissions.BasePermission):
def has_permission(self, request, view):
user = request.user
# you can choose how to get the path_name from the path
path_name = get_path_name(request.path)
return RequestPermission.objects.filter(user=user, method=request.method, path_name=path_name).exists()
Now you can use this class as the default permission class in rest framework settings or use it per view.
With the find and update methods i am able to restrict fetching of data restricted to the logged in user by adding a policy that sets req.options.where.owner = userID, therefore i dont need to create custom controllers or models for these methods.
With update and create i can also set req.options.values.owner = userID so that the user cant create or update an object that will belong to another user.
But the problem is that the blueprint findOne controller does not have any options for this kind of filtering, so any logged in user can request an object created and owned by another user.
Is there anyway i can restrict findOne without writing my own controller and query?
Found a solution to the problem, what you can do is to override the default blueprint action by creating a folder named blueprints in your api folder, there you can create a findone.js (lowercase) file,
copy the original blueprint action from /node_modules/sails/lib/hooks/blueprints/actions/findOne.js to /api/blueprints/findone.js
add .where( actionUtil.parseCriteria(req) ); to the query.
Dont forget to change the path of actionutil from require('../actionUtil'); to require('../../node_modules/sails/lib/hooks/blueprints/actionUtil');
Voila, now the findOne action will respect your req.options.where queries.
You can specify blueprint in your policies like this
module.exports = function (req, res, next) {
var blueprint = req.options.action;
if (blueprint === 'findOne') {
// do restriction here
return next();
}
res.forbidden('not allowed to do something');
};
I'm rather forget, is blueprint name findOne or findone.
I want to create some routes with restrictions by role. Something like this:
my $auth = $app->routes->under->to('auth#check');
$auth->get('/list')->to('some#list')->name('list');
$auth->get('/add')->to('some#add', roles => ['user', 'admin'])->name('add');
I don't have any problem with checking roles in after_dispatch hook. But I cannot access this data when I'm trying to create a links for this routes.
For example, I'm a guest on /list route and want to form menu with available links. So, I have to check roles from route /add to decide to show this link or not.
For this moment I found only one way to get default data from route with name:
app->routes->lookup('add')->pattern->defaults->{roles}
And it looks like a hack. How I can do this in a right way?
You are right. This method to get defaults of the route is documented here
my $defaults = $pattern->defaults;
$pattern = $pattern->defaults({foo => 'bar'});
I use dektrium user registration form.
registration link generates domain.com/user/register link
but it's not base user model, this module is located inside vendor/dektrium folder.
Now in base controllers folder I have UsersController with view action.
and after finishing registration I want to start view action of UsersController to view users page.
This is registration module code
public function actionRegister()
{
if (!$this->module->enableRegistration) {
throw new NotFoundHttpException;
}
$model = $this->module->manager->createRegistrationForm();
if ($model->load(\Yii::$app->request->post()) && $model->register()) {
return $this->redirect(array('users/'.$model->username));
}
return $this->render('register', [
'model' => $model
]);
}
As you can see I've put there this code
return $this->redirect(array('users/'.$model->username));
Which is supposed to take user to it's own page at domain.com/users/username.
But unfortunatelly url is forming in the following way
domain.com/user/users/username
How can I fix this problem and direct user to domain.com/users/username page ?
Add an extra / in front of the users redirect. it should be
return $this->redirect(array('/users/'.$model->username));
Or you should actually create the url the proper way, that would be the best way to do this, but I do not know the way you have your rules set up so I cannot help you there. I am just guessing here but it should be:
return $this->redirect(array('users/view, 'username' => $model->username));
In this way you are using your url manager, not just hardcoding the url. In the future if you decide to change the link it will be much easier (replace just the url line in your config) and not go in files to change it.
Isn't there acl order deny method for module. do i always have to add controller and index? i have an admin module and default module witch dozen controller in them and three dozen actions for them and it's really being wearisome
My code goes like this
class Management_Access extends Zend_Controller_Plugin_Abstract{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
// set up acl
$acl = new Zend_Acl();
// add the roles
$acl->addRole(new Zend_Acl_Role('guest'));
$acl->addRole(new Zend_Acl_Role('administrator'), 'guest');
// add the resources
$acl->add(new Zend_Acl_Resource('index'));
$acl->add(new Zend_Acl_Resource('error'));
$acl->add(new Zend_Acl_Resource('login'));
//admin resources
$acl->add(new Zend_Acl_Resource('destination'));
$acl->add(new Zend_Acl_Resource('home'));
$acl->add(new Zend_Acl_Resource('page'));
$acl->add(new Zend_Acl_Resource('tour'));
$acl->add(new Zend_Acl_Resource('hotel'));
isn't there a way to check if resource is registered in acl?
UPDATE::
i have eight controllers in my default module and nine controllers in 'admin' module.
i have index controller in admin module as well as in default module. if i add allow guest index, the guest is also able to access the index page in admin module. admin module is only set for administrator
Two possible solutions:
check current module in controller plugin ($request->getModuleName())
implement the logic in module bootstrap (only for module you need).
Edit after update:
You just need to treat modules+controllers as resources, and actions as privileges:
$acl->deny('guest', 'adminmodulename:controllername', array('tour', 'hotel'));
or for all:
$acl->deny('guest', 'adminmodulename:controllername');
isn't there a way to check if resource is registered in acl?
$acl->has($resource)
That's not a very specific question :)
Anyways... You will probably have to implement a user management yourself for ZF. But don't be afraid, there are tons of tutorials online! (e.g. here)
What do you mean by "do i always have to add controller and index?"?
I understand your question. I suggest you make your application modular. For the ACL just move it up as well (aka make your modules the resources)!
e.g.
// ROLES
$this->addRole(new Zend_Acl_Role('guest')); // default
$this->addRole(new Zend_Acl_Role('Marketing'), 'guest'); // 15
// RESOURCES (MY MODULES)
$this->add(new Zend_Acl_Resource('auth'));
$this->add(new Zend_Acl_Resource('takeon'));
// PRIVILEGES
//
// default
$this->deny();
//
// guest
$this->allow('guest', 'auth');
// 15 Marketing
$this->allow('Marketing', 'default');
$this->allow('Marketing', 'takeon', array('index', 'ben10cards'));
Then in your plugin use:
// OBTAIN CONTROL LIST
$acl = new Auth_Model_Acl();
// OBTAIN RESOURCE
$module = $request->getModuleName();
$controller = $request->getControllerName();
// VALIDATE
if ($acl->isAllowed($role, $module, $controller)) {
$allowed = true;
You might then not have resources available for actions, but works better for me :)