Scenerio:
Using Yii-rights + Yii-user module in my project. In Rights, I generated operations based on my controller action, under update I added a child UpdateOwn.
For UpdateOwn, the bizrule is suppose to be a simple comparison that the logged in user's ID is equal to $model->user_id field.
Problem:
I understand yii checkaccess allow you to pass in variables as parameters and comparing with your defined bizrule. But how does it work for Yii-rights module? How or what are the data/params passed in to be used in bizrule? How can I define or pass my own data/params?
Yii-rights is a wrapper for standart yii-rbac. In rights module you have web-interface for your RBAC. When you creating AuthItem (Operation in rights web interface) you can define your own bizrule.
Here is code for creating AuthItem:
$item = $this->_authorizer->createAuthItem($formModel->name, $type, $formModel->description, $formModel->bizRule, $formModel->data);
$item = $this->_authorizer->attachAuthItemBehavior($item);
_authorizer here is an example of RAuthorizer class. Then we go to RDbAuthManager, which extends CDbAuthManager, where we createAuthItem function:
public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
{
$this->db->createCommand()
->insert($this->itemTable, array(
'name'=>$name,
'type'=>$type,
'description'=>$description,
'bizrule'=>$bizRule,
'data'=>serialize($data)
));
return new CAuthItem($this,$name,$type,$description,$bizRule,$data);
}
This is how created AuthItem, in rights. Personally i prefer to use web interface. It have alot of great fetures and much easier to handle then go to code each time.
Then when we perform checkAccess() on AuthItem we call execute bizRule:
public function executeBizRule($bizRule,$params,$data)
{
return $bizRule==='' || $bizRule===null || ($this->showErrors ? eval($bizRule)!=0 : #eval($bizRule)!=0);
}
This is how RBAC in yii work, and rights is just a cool wrapper for it. Rights doesn't change logic of how things must be done.
So in basic yii-rbac if you want to allow update only Own records you do:
$bizRule='return Yii::app()->user->id==$params["user"]->username;';
$task=$auth->createTask('updateOwnUser','update a your own account',$bizRule);
$task->addChild('updateUser');
Then you call it like this:
$user=$this->loadUser();
$params = array('user' => $user);
if(Yii::app()->user->checkAccess('updateOwnUser', $params){
..................
}
In rights it's already implemented with filters. Only thing what you need to do is add to your controller:
class MyController extends RController{
.............
public function filters()
{
return array(
'rights',
............
);
}
.............
}
So define your bizrule for item in web interface, change your controller code, and actually thats it. To know what variables to use in bizrule you can watch on RightsFilter.php code, where checkAccess() performed.
And on top of all of this i'll say about how checkAccess() does :
For each assigned auth item of the user, it first checks if the bizRule for the assignment returns true.
If true, it calls the item's checkAccess method. If the item's bizRule returns true,
2.1. If the item name is the same as the name passed in the original checkAccess() method, it returns true;
2.2. Otherwise, for every child item, it calls its checkAccess.
Hope this will clarify some aspects of RBAC and help in your task.
The yii-rights module has the following properties:
/**
* #property boolean whether to enable business rules.
*/
public $enableBizRule = true;
/**
* #property boolean whether to enable data for business rules.
*/
public $enableBizRuleData = false;
To set bizrule data via the web interface you have to set $enableBizRuleData = true in your application configuration.
Please note that the UI is limited and you can set data only for Auth-Items not for Auth-Assignments. Also the value for data has to be a serialized PHP variable.
As mentioned by #ineersa you can access $data in unserialized form in your bizRule.
It's also worth noting, that Yii checks first the bizRule for the Auth-Item and then additionally for the Auth-Assignment.
[edit] added example
Auth Item
bizRule
Check if the assignment has all the keys specified in the item data
return BizRule::compareKeys($params, $data, 'Editor');
data
a:1:{s:8:"language";b:1;}
Auth Assignment
Check if the application language matches the assignment data
bizRule
return BizRule::compareApplicationLanguage($params, $data);
data
a:1:{s:8:"language";s:5:"de_de";}
[edit] added code link
Here is the full Helper Code
Related
Since i'm new to CakePHP, I have simple problems I cannot figure out.
I use CakePHP 3.4. I try to write a simple logger functionality. Every change applied to a record, I want to be logged to the ChangeLog model.
Using afterSave() event, I have following code:
public function afterSave($event, $entity, $options) {
$logTable = TableRegistry::get('ChangeLogs');
foreach ($entity->getDirty() as $key) {
if($key != 'modified') {
$record = $logTable->newEntity();
$record->previous_value = $entity->getOriginal($key);
$record->new_value = $entity[$key];
$record->table_name = 'Stars';
$record->column_name = $key;
$record->row_id = $entity->id;
$record->user_id = [what should i put here?]
$record->user_id = $_SESSION['Auth']['user']['id'];
$logTable->save($record);
}
}
It works well, but I also want to know which user performed operation and I don't know how can I obtain current user in the Model.
I try to avoid passing argument in controller, because I want user to be detected automaticly, and as a developer I don't want to remember about it every time I try change/add new functionalities in controller.
Do not fiddle with superglobals directly in CakePHP, this will surely bite you at some point, especially in the test environment! Always use the abstracted methods (like the session object) to access such data!
That being said, you could use events to inject the current user into the model callback/event flow. For example register globally to Model.afterSave, and pass the current user into the options.
Here's a basic example to demonstrate the principle. Imagine somthing like this in your app controller:
use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\Event\EventManager;
// ...
public function initialize()
{
parent::initialize();
// ...
EventManager::instance()->on(
'Model.afterSave',
['priority' => -1],
function (Event $event, EntityInterface $entity, \ArrayObject $options) {
// retrieve the user id from the auth component
$options['user_id'] = $this->Auth->user('id');
}
);
}
Given the priority of -1 (the default priority is 10) it will be invoked before the model callback for that event, so that in your table class you'll have access to user_id via the $options argument.
$record->user_id = $options['user_id'];
For something more reusable you'd probably use a custom listener class. Also check out events like Auth.afterIdentify, Model.initialize, and Controller.intialize/startup, these could be leaveraged to register your model events listener and to retrieve the current user.
See also
Awesome CakePHP > Auditing / Logging
Cookbook > Events System
Cookbook > Events System > Registering Listeners
Cookbook > Events System > Establishing Priorities
Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks
Cookbook > Controllers > Request Life-cycle Callbacks
This solution seems to allow you to pass the logged in user into the model layer:
https://github.com/UseMuffin/Footprint
It is not hooked into the model layer through events like the solution above.
I am fetching list of results from database but the list doesn't validates the constraints until i call "validate()" method for each objects.
def temp = ConfigInfo.where { projectId == project }.findAll()
//at this stage domain class is not validated
temp.each {
println "validate" + it.validate()
println "has error" + it.hasErrors()
}
//in the above code block validate() is called, I don't want to do this.
// is it possible to configure it happen automatically and i get the validated objects.
I don't want to invoke validate method.
is there any way or configuration in grails such that domain class get validated automatically.
Please help me in this.
Grails domain class objects get validated before save.
Use the following in your domain model to set the validation rules:
static constraints = {
// whatever you want to validate
}
Whenever you save an object it will be validated by the constraints you set. When the object is persistent in the database it has always been validated.
All you need to do is that just define all your validation in constraints closure.
e.g.
static constraints = {
id maxLength: 5
//etc...
}
at the time of saving object you just need to check object is validate or not!, it will return true/false.
look this: click here
As as title clarifies what is main purpose of filters in Yii? I am newbie to Yii and little confuse about filters and validators? Any one can explain it for me?
A validator will be validating that an attribute in a model is as it should be: an, integer, a date, less than a given size, ...
Example:
public function rules()
{
return array(
//username and password are required
array('username, password', 'required'),
//myInt is a number between 0 and 255
array('myInt', 'numerical', 'min'=>0, 'max'=> 255),
);
}
The validation rules will be tested when calling $model->validate() or $model->save(). If one of the validator did not pass, then an error will be thrown to the user.
You can know the errors thrown by calling $model->getErrors()
Source: Model Rules Validation
The filter definition is :
A filter can be applied before and after an action is executed. It can
modify the context that the action is to run or decorate the result
that the action generates.
So basiclly it will execute some work before calling the controller method (so before rendering anything on the screen) or after the controller is done (so it could be after the datas are validated and added in the db).
As example we can say:
Check user authorizations
Implements HTTP caching
...
To apply filters to actions, we need to override the CController::filters() method. The method should return an array of filter configurations. For example,
public function filters()
{
return array(
'postOnly + edit, create',
array(
'application.filters.PerformanceFilter - edit, create',
'unit'=>'second',
),
);
}
Using the plus and the minus operators, we can specify which actions the filter should and should not be applied to. In the above, the postOnly filter will be applied to the edit and create actions, while PerformanceFilter filter will be applied to all actions EXCEPT edit and create. If neither plus nor minus appears in the filter configuration, the filter will be applied to all actions.
Source: Yii API about CFilter and Yii Guide
So if you want to validate some datas, then use the validators and if what you want to do is not depending on the model (ie check a user is logged in, ...) then you should implements a filter.
In general the difference between a filter and a validator is pretty obvious.
Validators are used for preventing inserting or updating wrong data in db. Filters could be used to make some preparation before or after validating
class LoginForm extends CFormModel
{
public $mail;
public $password;
public $rememberMe;
public function rules()
{
return array(
array('mail, password', 'filter'=>'trim'),
array('mail', 'filter'=>'mb_strtolower'),
array('mail, password', 'required'),
array('mail', 'email'),
array('rememberMe', 'boolean'),
array('password', 'authenticate'),
);
}
}
Filters is mainly used to filter your URL. Here also included accessControl. That's means when you set URL to access any action. Then those filter checked that action is permitted for those User or not. Also if you set that this controller delete action only can be accessible by POST method Then you will not be able to delete by using GET method. Those all type of access .... Controlled by FILTERS.
On the other hand, Validator is used to validate your any input filed AS your wishes. As like Minimum , Maximum, Integer or not, is it will be Unique or not, is this field required or not. is this field will be email type or not ...... and many more type of validation of INPUT FIELD.....
In Single Word it can be say " Validator is used for Input Validation and Filter is Used for Output Validation "
Has anyone tried or found an example of a class derived from CModel that replicates CActiveRecord functionality with WebServices instead of database connection???
If done with RESTFULL WebServices it would be great. If data is transmitted JSON encoded, wonderful!!...
I'd appretiate your help. Thanks.
I spend a lot of time looking for that as well, I came across this Yii extension on Github:
https://github.com/Haensel/ActiveResource
It allows you to have exactly what you are looking for, the readme isn't updated with the changes reflected in changes.md, so I recommend you read through this document as well.
EActiveResource for Yii
...is an extension for the Yii PHP framework allowing the user to create models that use RESTful services as persistent storage.
The implementation is inspired by Yii's CActiveRecord class and the Ruby on Rails implementation of ActiveResource (http://api.rubyonrails.org/classes/ActiveResource/Base.html).
HINT:
CAUTION: THIS IS STILL AN ALPHA RELEASE!
This project started as a draft and is still under development, so as long is there is no 1.0 release you may experience changes that could break your code. Look at the CHANGES.md file for further information
As there are thousands of different REST services out there that use a thousand different approaches it can be tricky to debug errors. Because of that I added extensive
tracing to all major functions, so you should always be able to see every request, which method it used and how the service responded. Just enable the tracing functionality of Yii
and look for the category "ext.EActiveResource"
INSTALL:
Add the extension to Yii by placing it in your application's extension folder (for example '/protected/extensions')
Edit your applications main.php config file and add 'application.extensions.EActiveResource.*' to your import definitions
Add the configuration for your resources to the main config
'activeresource'=>array(
'class'=>'EActiveResourceConnection',
'site'=>'http://api.aRESTservice.com',
'contentType'=>'application/json',
'acceptType'=>'application/json',
)),
'queryCacheId'=>'SomeCacheComponent')
4.) Now create a class extending EActiveResource like this (don't forget the model() function!):
QUICK OVERVIEW:
class Person extends EActiveResource
{
/* The id that uniquely identifies a person. This attribute is not defined as a property
* because we don't want to send it back to the service like a name, surname or gender etc.
*/
public $id;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function rest()
{
return CMap::mergeArray(
parent::rest(),
array(
'resource'=>'people',
)
);
}
/* Let's define some properties and their datatypes
public function properties()
{
return array(
'name'=>array('type'=>'string'),
'surname'=>array('type'=>'string'),
'gender'=>array('type'=>'string'),
'age'=>array('type'=>'integer'),
'married'=>array('type'=>'boolean'),
'salary'=>array('type'=>'double'),
);
}
/* Define rules as usual */
public function rules()
{
return array(
array('name,surname,gender,age,married,salary','safe'),
array('age','numerical','integerOnly'=>true),
array('married','boolean'),
array('salary','numerical')
);
}
/* Add some custom labels for forms etc. */
public function attributeLabels()
{
return array(
'name'=>'First name',
'surname'=>'Last name',
'salary'=>'Your monthly salary',
);
}
}
Usage:
/* sends GET to http://api.example.com/person/1 and populates a single Person model*/
$person=Person::model()->findById(1);
/* sends GET to http://api.example.com/person and populates Person models with the response */
$persons=Person::model()->findAll();
/* create a resource
$person=new Person;
$person->name='A name';
$person->age=21;
$person->save(); //New resource, send POST request. Returns false if the model doesn't validate
/* Updating a resource (sending a PUT request)
$person=Person::model()->findById(1);
$person->name='Another name';
$person->save(); //Not at new resource, update it. Returns false if the model doesn't validate
//or short version
Person::model()->updateById(1,array('name'=>'Another name'));
/* DELETE a resource
$person=Person::model()->findById(1);
$person->destroy(); //DELETE to http://api.example.com/person/1
//or short version
Person::model()->deleteById(1);
Hope this helps you
What would be the best way to approach setting up a shared password protected area in Yii?
I am looking to have a view of a Group model, that can be accessed by a shared password created by the owner of that group - group members shouldn't have to log in, purely enter this passcode.
Should this still be done with Yii's built in auth tools? - or is there a simpler solution, bearing in mind that someone might want to access several groups.
You can do this using standard session mechanism built into PHP. When someone tries to view password-protected area, check the session variable, if the user haven't entered password yet then redirect him to some page with a password form (you can do the check using controller filters for example).
After the form is submitted, check correctness of password and if everything is ok, write it into the session. You can differentiate session keys by group ids.
You can use Yii filter capabilities to fire code before executing a controller action, and prevent actions that you do not want to allow.
I would create a common controller for all your group pages, and inherit other controller from this one if need to.
In the filter I would setup code to check/prompt for the password, and keep that in session.
For example we have a filter setup to detect if the user has accepted our revised Terms and Conditions. The filter will detect and will prevent access to the controller until the user doesn't confirm it.
class TocConfirmFilter extends CFilter {
/**
* Initializes the filter.
* This method is invoked after the filter properties are initialized
* and before {#link preFilter} is called.
* You may override this method to include some initialization logic.
*/
public function init() {
}
/**
* Performs the pre-action filtering.
* #param CFilterChain the filter chain that the filter is on.
* #return boolean whether the filtering process should continue and the action
* should be executed.
*/
protected function preFilter($filterChain) {
// do not perform this filter on this action
if ($filterChain->action->controller->id . '/' . $filterChain->action->id == 'public/onePublicPage') {
return true;
}
if (isset(Yii::app()->user->id)) {
$user = user::model()->findbyPk(Yii::app()->user->id);
if ($user === null)
throw new CHttpException(404, 'The requested user does not exist.');
if ($user->tocconfirmed == 0) {
Yii::app()->getRequest()->redirect(Yii::app()->createAbsoluteUrl('authorize/confirm'));
return false;
}
}
return true;
}
/**
* Performs the post-action filtering.
* #param CFilterChain the filter chain that the filter is on.
*/
protected function postFilter($filterChain) {
}
}