Privacy control using Zend - php

I am making a social website using Zend. The site allows users to become friends and access each other's profiles and blogs. I also want users to have control over their privacy, which can take parameters "Friends Only" and "Public". I looked at Zend_Acl but it seems to be only able to to handle single user's accessibility not users have relationship. Any ideas about the best way to do this?

For your purposes, if you use Zend_Acl, you should look at assertions.
Given the complex nature of the relationships between users in your applications, most of the access rules you will query seem very dynamic so they will largely rely on assertions that can use more complex logic to determine accessibility.
You should be able to accomplish what you want using Zend_Acl though.
You may set up an ACL rule like this:
$acl->allow('user', 'profile', 'view', new My_Acl_Assertion_UsersAreFriends());
The ACL assertion itself:
<?php
class My_Acl_Assertion_UsersAreFriends implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
return $this->_usersAreFriends();
}
protected function _usersAreFriends()
{
// get UserID of current logged in user
// assumes Zend_Auth has stored a User object of the logged in user
$user = Zend_Auth::getInstance()->getStorage();
$userId = $user->getId();
// get the ID of the user profile they are trying to view
// assume you can pull it from the URL
// or your controller or a plugin can set this value another way
$userToView = $this->getRequest()->getParam('id', null);
// call your function that checks the database for the friendship
$usersAreFriends = usersAreFriends($userId, $userToView);
return $usersAreFriends;
}
}
Now with this assertion in place, the access will be denied if the 2 user IDs are not friends.
Check it like:
if ($acl->isAllowed('user', 'profile', 'view')) {
// This will use the UsersAreFriends assertion
// they can view profile
} else {
// sorry, friend this person to view their profile
}
Hope that helps.

Related

In Bolt CMS, is it possible to check role membership in a controller?

I'm trying to add some functionality to the back end of a Bolt CMS installation that does the following:
Check if the user is a member of the "limited editor" group.
If so, only list content which they, personally, own.
This needs to be within the controller, not using Twig.
I've got the user object using
$user = $app['users']->getCurrentUser();
I guess I could use
in_array('limitededitor', $user["roles"]);
But I wondered if there was any existing function in Bolt that would streamline this, like "isAllowed" but for checking role membership?
This is what I've used in the past to determine whether I mount a controller (and thus give access to the new urls), the key part is the users service has a hasRole method but you need to check by user id.
public function checkAuth()
{
$currentUser = $this->app['users']->getCurrentUser();
$currentUserId = $currentUser['id'];
foreach (['admin', 'root', 'developer', 'editor'] as $role) {
if ($this->app['users']->hasRole($currentUserId, $role)) {
return true;
}
}
return false;
}

Cakephp 3 - How to retrieve current logged user in a 'Table' class during validation process?

I'am using CakePhp3 for my website and I have to inject some custom validation logic based on the current user Id when I'am creating or modifying an entity.
The basic case is "Is the user allow to change this field to this new value" ? If' not, I want to raise a validation error (or an unauthorized exception).
In cakephp, for what I'am understanding, most of the application and businness rules must be placed on Models or 'ModelsTable'of the ORM. But, in this classes, the AuthComponent or the current session is not available.
I don't want to call manually a method on the entity from the controller each time I need to check. I would like to use a validator, something like :
$validator->add('protected_data', 'valid', [
'rule' => 'canChangeProtectedData',
'message' => __('You're not able to change this data !'),
'provider' => 'table',
]);
Method on ModelTable :
public function canChangeProtectedData($value, array $context)
{
\Cake\Log\Log::debug("canChangeProtectedData");
// Find logged user, look at the new value, check if he is authorized to do that, return true/false
return false;
}
I cakephp < 3, the AuthComponent have a static method 'AuthComponent::user()' that is not available anymore. So, how Can I do that in CakePhp 3 ?
Thank you for any response.
EDIT - Adding more details
So here are more details. In case of an REST API. I have an edit function of an entity. The "Article" Entity.
This Article has an owner with a foreign key on the column named "user_id" (nothing special here). My users are organized in groups with a leader on the group. Leaders of groups can change article's owner but "basics" users can't do it (but they can edit their own articles). Admin users can edit everything.
So the edit method must be available for any authenticated user, but changing the "user_id" of the entity must be allowed and checked depending the case (if I'am admin yes, if I'am leader yes only if the new Id is one of my group and if I'am basic user no).
I can do this check on the controller but if I want this rule to be checked everywhere in my code where an Article is modified (in another method than the "Edit" of ArticlesController). So for me the Model seems the good place to put it no?
Authentication vs Authorisation
Authentication means identifying an user by credentials, which most of the time boils down to "Is a user logged in".
Authorisation means to check if an user is allowed to do a specific action
So don't mix these two.
You don't want validation you want application rules
Taken from the book:
Validation vs. Application Rules
The CakePHP ORM is unique in that it uses a two-layered approach to
validation.
The first layer is validation. Validation rules are intended to
operate in a stateless way. They are best leveraged to ensure that the
shape, data types and format of data is correct.
The second layer is application rules. Application rules are best
leveraged to check stateful properties of your entities. For example,
validation rules could ensure that an email address is valid, while an
application rule could ensure that the email address is unique.
What you want to implement is complex application logic and more than just a simple validation, so the best way to implement this is as an application rule.
I'm taking a code snippet from one of my articles that explains a similar case. I had to check for a limitation of languages (translations) that can be associated to a model. You can read the whole article here http://florian-kraemer.net/2016/08/complex-application-rules-in-cakephp3/
<?php
namespace App\Model\Rule;
use Cake\Datasource\EntityInterface;
use Cake\ORM\TableRegistry;
use RuntimeException;
class ProfileLanguageLimitRule {
/**
* Performs the check
*
* #link http://php.net/manual/en/language.oop5.magic.php
* #param \Cake\Datasource\EntityInterface $entity Entity.
* #param array $options Options.
* #return bool
*/
public function __invoke(EntityInterface $entity, array $options) {
if (!isset($entity->profile_constraint->amount_of_languages)) {
if (!isset($entity->profile_constraint_id)) {
throw new RuntimeException('Profile Constraint ID is missing!');
}
$languageLimit = $this->_getConstraintFromDB($entity);
} else {
$languageLimit = $entity->profile_constraint->amount_of_languages;
}
// Unlimited languages are represented by -1
if ($languageLimit === -1) {
return true;
}
// -1 Here because the language_id of the profiles table already counts as one language
// So it's always -1 of the constraint value
$count = count($entity->languages);
return $count <= ($languageLimit - 1);
}
/**
* Gets the limitation from the ProfileConstraints Table object.
*
* #param \Cake\Datasource\EntityInterface $entity Entity.
* #return int
*/
protected function _getConstraintFromDB(EntityInterface $entity) {
$constraintsTable = TableRegistry::get('ProfileConstraints');
$constraint = $constraintsTable->find()
->where([
'id' => $entity['profile_constraint_id']
])
->select([
'amount_of_languages'
])
->firstOrFail();
return $constraint->amount_of_languages;
}
}
I think it is pretty self-explaining. Make sure your entities user_id field is not accessible for the "public". Before saving the data, just after the patching add it:
$entity->set('user_id', $this->Auth->user('id'));
If you alter the above snippet and change the profile_constraint_id to user_id or whatever else you have there this should do the job for you.
What you really want is row / field level based authorisation
Guess you can use ACL for that, but I've never ever had the need for field based ACL yet. So I can't give you much input on that, but it was (Cake2) and still is (Cake3) possible. For Cake3 the ACL stuff was moved to a plugin. Technically it is possible to check against anything, DB fields, rows, anything.
You could write a behavior that uses the Model.beforeMarshal event and checks if user_id (or role, or whatever) is present and not empty and then run a check on all fields you want for the given user id or user role using ACL.
You could probably use this method PermissionsTable::check() or you can write a more dedicated method does checks on multiple objects (fields) at the same time. Like I said, you'll spend some time to figure the best way out using ACL if you go for it.
UX and yet another cheap solution
First I would not show fields at all an user is not allowed to change or enter as inputs. If you need to show them, fine, disable the form input or just show it as text. Then use a regular set of validation rules that requires the field to be empty (or not present) or empty a list of fields based on your users role. If you don't show the fields the user would have to temper the form and then fail the CSRF check as well (if used).
I don't think you need to validate in the table. I just thought of a way to do it in the controller.
In my Users/Add method in the controller for instance:
public function add()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data);
//check if user is logged in and is a certain user
if ($this->request->session()->read('Auth.User.id') === 1) {
//allow adding/editing role or whatever
$user->role = $this->request->data('role');
} else {
$user->role = 4;//or whatever the correct data is for your problem.
}
if ($this->Users->save($user)) {
$this->Flash->success(__('You have been added.'));
} else {
$this->Flash->error(__('You could not be added. Please, try again.'));
}
}
$this->set(compact('user'));
$this->set('_serialize', ['user']);
}

Dynamic custom ACL in zend framework?

I need a solution where authenticated users are allowed access to certain Controllers/Actions based not on their user type :ie. admin or normal user (although I may add this using standard ACL later) but according to the current status of their user.
For example :
Have they been a member of the site for more than 1 week?
Have they filled in their profile fully?
Actually, now that I think about it, kind of like they have on this site with their priviledges and badges.
For dynamic condition-based tests like you are describing, you can use dynamic assertions in your Zend_Acl rules.
For example:
class My_Acl_IsProfileComplete implements Zend_Acl_Assert_Interface
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
// check the user's profile
if (null === $this->user){
return false;
}
return $this->user->isProfileComplete(); // for example
}
}
Then when defining your Acl object:
$user = Zend_Auth::getInstance()->getIdentity();
$assertion = new My_Acl_Assertion_IsProfileComplete($user);
$acl->allow($role, $resource, $privilege, $assertion);
Of course, some of the details depend upon the specifics of what you need to check and what you can use in your depend upon what you store in your Zend_Auth::setIdentity() call - only a user Id, a full user object, etc. And the roles, resources, and privileges are completely app-specific. But hopefully this gives the idea.
Also, since the assertion object requires a user object at instantiation, this dynamic rule cannot be added at Bootstrap. But, you can create the core Acl instance with static rules during bootstrap and then register a front controller plugin (to run at preDispatch(), say) that adds the dynamic assertion. This way, the Acl is fully populated by the time you get to your controllers where presumably you would be checking them.
Just thinking out loud.

Assign multiple roles in Zend_Navigation using Zend_ACL in Zend Framework PHP?

I can't get my Zend_Navigation to work properly,
When logging in user with AUth/Doctrine, I am pulling out the roles assigned to the user (usually it's a few of them) from a Many-to-many table,
Then in the bootstrap.php on line:
$view->navigation($navContainer)->setAcl($this->_acl)->setRole($this->_role);
I get error:
'$role must be a string, null, or an instance of Zend_Acl_Role_Interface; array given'
However if I loop through the roles with foreach - the previous roles are being overwritten by the following ones and I get the nav only for last role,
Does anyone have any logical solution for this ?
Really appreciate,
Adam
I had the same problem but approached the solution from a slightly different angle. Instead of modifying the Zend_Navigation object to accept two or more roles, I extended Zend_Acl and modified the isAllowed() method to check against all those roles. The Zend_Navigation objects use the isAllowed() method, so overriding this solved the issue.
My_Acl.php
<pre><code>
class My_Acl extends Zend_Acl
{
public function isAllowed($role = null, $resource = null, $privilege = null)
{
// Get all the roles to check against
$userRoles = Zend_Registry::get('aclUserRoles');
$isAllowed = false;
// Loop through them one by one and check if they're allowed
foreach ($userRoles as $role)
{
// Using the actual ACL isAllowed method here
if (parent::isAllowed($role->code, $resource))
{
$isAllowed = true;
}
}
return $isAllowed;
}
}
</code></pre>
Then, instead of creating an instance of Zend_Acl, use My_Acl, pass that to your navigation object and it should work.
You should really never, ever override isAllowed(), and yes there is a solution. Create a class that implements Zend_Acl_Role_Interface and if memory serves it requires defining a single method getRole(), this could, in fact, be your model that you use to authenticate a user against and allow that class to handle determining the role. A user should only have a single role. If access to the resource should be granted to users of multiple roles but only under certain conditions, then you should use an assertion, thats why they are there.

Multiple Instances (2) of Zend_Auth

I have a CMS built on the Zend Framework. It uses Zend_Auth for "CMS User" authentication. CMS users have roles and permissions that are enforced with Zend_Acl. I am now trying to create "Site Users" for things like an online store. For simplicity sake I would like to use a separate instance of Zend_Auth for site users. Zend_Auth is written as a singleton, so I'm not sure how to accomplish this.
Reasons I don't want to accomplish this by roles:
Pollution of the CMS Users with Site Users (visitors)
A Site User could accidentally get elevated permissions
The users are more accurately defined as different types than different roles
The two user types are stored in separate databases/tables
One user of each type could be signed in simultaneously
Different types of information are needed for the two user types
Refactoring that would need to take place on existing code
In that case, you want to create your own 'Auth' class to extend and remove the 'singleton' design pattern that exists in Zend_Auth
This is by no means complete, but you can create an instance and pass it a 'namespace'. The rest of Zend_Auth's public methods should be fine for you.
<?php
class My_Auth extends Zend_Auth
{
public function __construct($namespace) {
$this->setStorage(new Zend_Auth_Storage_Session($namespace));
// do other stuff
}
static function getInstance() {
throw new Zend_Auth_Exception('I do not support getInstance');
}
}
Then where you want to use it, $auth = new My_Auth('CMSUser'); or $auth = new My_Auth('SiteUser');
class App_Auth
{
const DEFAULT_NS = 'default';
protected static $instance = array();
protected function __clone(){}
protected function __construct() {}
static function getInstance($namespace = self::DEFAULT_NS) {
if(!isset(self::$instance[$namespace]) || is_null(self::$instance[$namespace])) {
self::$instance[$namespace] = Zend_Auth::getInstance();
self::$instance[$namespace]->setStorage(new Zend_Auth_Storage_Session($namespace));
}
return self::$instance[$namespace];
}
}
Try this one , just will need to use App_Auth instead of Zend_Auth everywhere, or App_auth on admin's area, Zend_Auth on front
that is my suggestion :
i think you are in case that you should calculate ACL , recourses , roles dynamically ,
example {md5(siteuser or cmsuser + module + controller)= random number for each roles }
and a simple plugin would this role is allowed to this recourse
or you can build like unix permission style but i guess this idea need alot of testing
one day i will build one like it in ZF :)
i hope my idea helps you
You're mixing problems. (not that I didn't when I first faced id)
Zend_Auth answers the question "is that user who he claims to be"? What you can do is to add some more info to your persistence object. Easiest option is to add one more column into your DB and add it to result.

Categories