How can we use the auth_rule table in Yii2 RBAC? - yii2

In Yii 2 RBAC, there is a new table called auth_rule. Can anyone explain its usage with a small example
create table [auth_rule]
(
[name] varchar(64) not null,
[data] text,
[created_at] integer,
[updated_at] integer,
primary key ([name])
);

The basic parts of yiis RBAC-cconcept stayed exactly the same. In both Yii1 and Yii2 you have the following tables:
auth_item: holds the actual rights, groups, roles, etc.
auth_item_child: defines the graph / hierarchy of the items
auth_assignement: assigns an item to a user
In Yii2 you now have a fourth table:
auth_rule: holds reusable rules to check if a right is actually granted
Why is this?
Yii1
The concept behind the rule was already there in Yii1...kind of at least. In Yii1 you had the possibility to define a "bizrule" in auth_item and auth_assignement. "bizrule" and "data" were columns in both those tables.
The contents of the columns were the following:
bizrule: held php-code which had to return a boolean value. This code was executed during rights check with eval(). That way you could control if a right was granted or not even though the user had the item assigned. Example: it makes no sense, but you could give a user a right only on even hours with this bizrule: return date('h') % 2 == 0.
data: held params which could be passed to the bizrule while beeing executed. This data was then available in the scope of the bizrule.
Yii2
The above solution works perfectly, except that the code of a bizrule is not reusable. Therefore this functionality was extracted into its own table.
If you look at the migration-file creating the basic rbac-tables (yii\rbac\migrations\m140506_102106_rbac_init.php) you can see that the item table now has a relation to the rule-table instead of hosting the code in one of its own columns.
There is however no relationship between auth_assignement and auth_rule. In Yii1 this allowed you to disable groups of rights at once. Since you can reuse a rule and attach it to all relevant items this is no longer necessary and was therefore removed.
Example
If you look at the actual implementation of yii\rbac\DbManager and yii\rbac\BaseManager an example shouldn't be necessary. Interesting are the following mthods:
DbManager::addRule(): serializes and persists a rule-instance
DbManager::getRule(): here you can see how the rule is retrieved, unserialized and returned. This means the rule is saved in a serialized format within the data-column of auth_rule.
BaseManager::executeRule(): the rule loaded above is executed via Rule::execute()
If you want to add a rule simply create an instance of yii\rbac\Rule and call DbManager::addRule($rule) with it as its param. This will serialize and save your rule making it reusable elsewhere. Awesome!
Voilà...should be pretty clear now. If you have some open questions or want more details just write a comment.
Cheers and have a good one!

The rule attribute data is serialized.
What does this data look like? Is it like the array below as not yet unserialized?
[
'allow' => true,
'actions' => ['view'],
'roles' => ['viewPost'],
],

Related

yii2 via() vs viaTable()

I have this schema:
and this relation in model zwz:
public function getAuftrs() {
return $this->hasMany(\app\models\Auftr::className(), ['id' => 'auftr_id'])
->viaTable('znw', ['zwzx_id' => 'id'])
->viaTable('zwz_expl', ['zwz_id' => 'id'])
;}
in the view of zwz:
<?= count($model->getAuftrs()->asArray()->all())
I'm getting:
PHP Notice – yii\base\ErrorException
Undefined index: auftr_id
in C:...\vendor\yiisoft\yii2\db\ActiveRelationTrait.php
And now if I change the two viaTable()s to:
->via('znws')
and of course define this relation before:
public function getZnws() {
return $this->hasMany(\app\models\Znw::className(), ['zwzx_id' => 'id'])
->viaTable('zwz_expl', ['zwz_id' => 'id'])
;}
then it works.
The problem is, that this latter via() way is incompatible with yii2-giiant, so I would like to know what is the difference actually between the two, and how could I keep the original viaTable() way.
github.com/yiisoft/yii2/.../docs/guide/db-active-record.md#chaining-relation-definitions-via-multiple-tables
for me it seems quite clear that we always have to pick the last ID of the chain and define all other IDs backwards. (however in these docs there is via() and not viaTable() and maybe it makes also a difference)
Thanks in advance!
You can not use viaTable() twice on the same relation. The second call will overwrite the first one. If you want to go over more than a junction table you need via(). You can however define multiple relations, one of them using via() and the other using viaTable().
I have no idea how giiant works, but it may detect a Many-Many relation through the fact that viaTable() is used. viaTable() in contrast to via() skips one table so you do not need an ActiveRecord for the junction table. With via() you always define direct relations.
About the order of keys in relation definitions, please check the docs at
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#declaring-relations
[...] the link between the two types of data: specifies the column(s) through which the two types of data are related. The array values are the columns of the primary data (represented by the Active Record class that you are declaring relations), while the array keys are the columns of the related data.
An easy rule to remember this is, as you see in the example above, you write the column that belongs to the related Active Record directly next to it. You see there that customer_id is a property of Order and id is a property of Customer.

Yii2 is there a way to specify tablename in ActiveQuery conditions (like andWhere) in a nice and short way

I make a query (with \yii\db\ActiveQuery) with joins, and some fields in "where" clause become ambigous. Is there a nice and short way to specify the name of the current model`s (ActiveRecord) table (from which one the ActiveQuery was instantiated) before the column name? So I can use this all the time in all cases and to make it short.
Don't like doing smth like this all the time (especially in places where there're no joins, but just to be able to use those methods with joins if it will be needed):
// in the ActiveQuery method initialized from the model with tableName "company"
$this->andWhere(['{{%company}}.`company_id`' => $id]);
To make the "named scopes" to work for some cases with joins..
Also, what does the [[..]] mean in this case, like:
$this->andWhere(['[[company_id]]' => $id]);
Doesn't seem to work like to solve the problem described above.
Thx in advance!
P.S. sorry, don't have enough reputation to create tag yii2-active-query
to get real table name :
Class :
ModelName::getTableSchema()->fullName
Object :
$model::getTableSchema()->fullName
Your problem is a very common one and happens most often with fields liek description, notes and the like.
Solution
Instead of
$this->andWhere(['description'=>$desc]);
you simply write
$this->andWhere(['mytable.description'=>$desc]);
Done! Simply add the table name in front of the field. Both the table name and the field name will be automatically quoted when the raw SQL is created.
Pitfall
The above example solves your problem within query classes. One I struggled over and took me quite some time to solve was a models relations! If you join in other tables during your queries (more than just one) you could also run into this problem because your relation-methods within the model are not qualified.
Example: If you have three tables: student, class, and teacher. Student and teacher probably are in relation with class and both have a FK-field class_id. Now if you go from student via class to teacher ($student->class->teacher). You also get the ambigous-error. The problem here is that you should also qualify your relation definitions within the models!
public function getTeacher()
{
return $this->hasOne(Teacher::className(), ['teacher.id' => 'class.teacher_id']);
}
Proposal
When developing your models and query-classes always fully qualify the fields. You will never ever run into this problem again...that was my experience at least! I actually created my own model-gii-template. So this gets solved automatically now ;)
Hope it helped!

Cakephp 3.0 - Validating Input against data in another model

I'm a pretty new to Cakephp 3.0 and I'm quite stuck on data validation, or rather Application Rules.
I have a simple user registration form to create a new user in the table 'users'. In the user registration form, there is a field for "ticket_number" where the user has to enter a ticket number which must exist in the table tickets.ticket_number, and also tickets.registration_status must be false (that ticket had not had a user registered with it yet).
My tables look like this (simplified:)
users:
id | username | password
tickets:
id | ticket_number | user_id | registration_status
In my users model, I have defined (user can have many tickets):
$this->hasMany('Tickets', [
'foreignKey' => 'user_id'
]);
In my Tickets model, I have defined (a ticket belongs to a user):
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
In the users model, I added a rule to attempt to check whether the entered ticket_number exists in the tickets.ticket_number column:
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['username']));
$rules->add($rules->isUnique(['email']));
$rules->add($rules->existsIn
(['ticket_number'], 'Tickets.ticket_number'));
return $rules;
}
This gives me the error:
Error: Call to a member function primaryKey() on a non-object
I'm very stuck here. Can anyone advise on the right approach implementing this check? And then, how to also implement the check to see whether the associated registration_status is 0 (that ticket has not been registered yet).
Thanks in advance for any advice!
--D.
The existsIn rule will not solve your problem, as it will not take your additional condition into account (registration-status).
You will need to provide a custom rule to accomplish the problem you described.
Regarding your update: The rule will return true - so passing the rule, if your tables are not setup correctly or it cannot find the field you are referencing or your field is nullable, check the source here: http://api.cakephp.org/3.0/source-class-Cake.ORM.Rule.ExistsIn.html#74-91
OK, after considerable tinkering, I solved the first issue (On the user add form, the user must enter a ticket number which must exist in the table tickets.ticket_number.)
The only way I could get this to work using an existsIn rule in the UsersTable model, was to declare the ticket_number field as the primary key in the initialize method of the TicketsTable model:
$this->primaryKey('ticket_number');
Then the associations and existsIn rule in the UsersTable model worked. However, this isn't good as I don't want the primary key in tickets permanently set to ticket_number. It also doesn't solve my second problem (checking tickets.registration_status is false.)
So I took a different approach and instead used $this->loadModel('Tickets'); in the add method of my users controller. Then, I could easily perform the required checks:
//Look for this particular ticket
$ticketCount = $this->Tickets->find()->where(['Tickets.ticket_number' => $ticket_number])->count();
$this->set(compact('ticketCount'));
$this->set('_serialize', ['ticketCount']);
//If the ticket isn't found, then return with the Flash error
if (!$ticketCount > 0) {
$this->Flash->error(__('Sorry, but a Ticket with this number could not be found. Please, try again.'));
return;
} //otherwise move on...
I then similarly query the registration status of the ticket, and flash a different error back if it is already registered.
After the checks are passed, it goes on to the normal Add User checks and saving.
While this solution isn't as elegant as what I was hoping to do in the Users model with a custom association and rule, it works.
I'm also not convinced it can't be done in the model with a rule. But for now, this solution works.
Anyway thanks hmic for a response.
DBZ

Design best practice - model vs controller vs UI - CakePHP, MySQL

I have been struggling for a few days with this problem and finally seek the opinion of the experts and crowd at this website.
I have two tables - one is a template of workflow steps and the other is an instance of these workflow steps called events. The templates table contains information like step name, step type etc - very generic information. The event table contains a reference link back to the workflow step table and an additional column called notes - which stores data that the user logged as they logged a particular workflow step. Both Workflow Steps and Events are linked to a POST on the website
Workflow step templates can exist without events having yet occurred - that is the user may be still on Step 3 or Step 5 and not logged an event for Step 1, 2 , 4 - basically the order of steps is only suggestive but not binding. Workflow Steps have a sequence field that dictate the order in which they should appear on screen.
Events can also occur without a workflow step - in other words, a user can log a note outside the context of workflow steps. These are generic events and directly associated with the POST
I am able to successfully retrieve both of these values for a given POST - they are retrieved as two separate arrays. I am using CakePHP and MySQL
The UI needs to render a screen that shows all the workflow steps in order and corresponding events that have occured in correlation to these steps or outside of these steps. The ordering of the screen will be driven primarily by the sequence of workflow steps and secondarily by created_date for those events that are not associated with a particular workflow step
Problem statement -
1. Do I send two separate arrays (as noted in #4) to the UI and let the UI determine the complex logic of how to interweave the steps and events for display?
2. Do I process the interweaving of steps and events in the controller and then send to the UI a simple array that it can loop through and display?
3. I have tried moving this logic to the database but because of variations explained in #2 and #3 it becomes quite complicated
I am seeking advise on which would be a better option from a design practice as well as from a simplification point of view. I understand that I have given a limited picture here but am hoping that someone on this website may have run into a similar issue elsewhere.
Depending on how you are assigning events to users, I would make a hasOne relation in Event to Workflow. You would need another relationship for you users, hasOne or hasMany.
$hasOne = 'Workflow';
Obviously this would mean that your Event table would have a column called wordflow_id and would be associated with a single row in your workflow. In the controller I would call the Event with by the user.
$this->Event->findAllByUserId($user_id);
This should provide you with an array that might look something like this.
array(
[0] => array(
[Event] => array(
'id' => 1,
'name' => 'blah',
...
),
[User] => array(
'id' => 1,
'name' => 'Charles',
...
),
[Workflow] => array(
[0] => array(
'id' => 1,
'name' => 'more blagblagblag',
...
),
[1] => array(
'id' => 9,
'name' => 'sblagblsagblag',
...
),
[2] => array(
'id' => 42,
'name' => 'mordse d',
...
)
)
)
)
Call all your workflow templates
$this->Workflow->find('all');
Then I would user cake's built in SET:: functionality to print the workflow template in your view and use your Event call to fill in the data.
Please post more detail and your code, models, ect and I'm sure we can get you the exact query/logic you'll need to achieve this.
http://book.cakephp.org/2.0/en/core-utility-libraries/set.html
OK - I have solved this. I ended up moving the functionality to the Model.
I created two SQL queries - one that retrieves all the workflow steps along with any event information that maybe associated with each of them.
Then I created a second SQL that retrieves all those events that are stand-alone and not associated with any particular workflow step
I used UNION ALL to stack them on top of each other
I used a SORT on modified date and squence number so that all the steps and events appear in chronological order and sequence
I then passed this from the Model to the View (via the controller) and let the View iterate and display the elements. This approach actually simplified my View and Controller code immensely and even the Model code is quite simple since all it is a query statement with parameters.

Exposing table name and field names in request URL

I was tasked to create this Joomla component (yep, joomla; but its unrelated) and a professor told me that I should make my code as dynamic as possible (a code that needs less maintenance) and avoid hard coding. The approach we thought initially is take url parameters, turn them into objects, and pass them to query.
Let's say we want to read hotel with id # 1 in the table "hotels". lets say the table has the fields "hotel_id", "hotel_name" and some other fields.
Now, the approach we took in making the sql query string is to parse the url request that looked like this:
index.php?task=view&table=hotels&hotel_id=1&param1=something&param2=somethingelse
and turned it into a PHP object like this (shown in JSON equivalent, easier to understand):
obj = {
'table':'hotel',
'conditions':{
'hotel_id':'1',
'param1':'something',
'param2':'somethingelse'
}
and the SQL query will be something like this where conditions are looped and appended into the string where field and value of the WHERE clause are the key and value of the object (still in JSON form for ease):
SELECT * FROM obj.table WHERE hotel_id=1 AND param1=something and so on...
The problem that bugged me was the exposing of the table name and field names in the request url. I know it poses a security risk exposing items that should only be seen to the server side. The current solution I'm thinking is giving aliases to each and every table and field for the client side - but that would be hard coding, which is against his policy. and besides, if I did that, and had a thousand tables to alias, it would not be practical.
What is the proper method to do this without:
hard coding stuff
keep the code as dynamic and adaptable
EDIT:
Regarding the arbitrary queries (I forgot to include this), what currently stops them in the back end is a function, that takes a reference from a hard-coded object (more like a config file shown here), and parses the url by picking out parameters or matching them.
The config looks like:
// 'hotels' here is the table name. instead of parsing the url for a table name
// php will just find the table from this config. if no match, return error.
// reduces risk of arbitrary tables.
'hotels' => array(
// fields and their types, used to identify what filter to use
'structure' => array(
'hotel_id'=>'int',
'name'=>'string',
'description'=>'string',
'featured'=>'boolean',
'published'=>'boolean'
),
//these are the list of 'tasks' and accepted parameters, based on the ones above
//these are the actual parameter names which i said were the same as field names
//the ones in 'values' are usually values for inserting and updating
//the ones in 'conditions' are the ones used in the WHERE part of the query
'operations' =>array(
'add' => array(
'values' => array('name','description','featured'),
'conditions' => array()
),
'view' => array(
'values' => array(),
'conditions' => array('hotel_id')
),
'edit' => array(
'values' => array('name','description','featured'),
'conditions' => array('hotel_id')
),
'remove' => array(
'values' => array(),
'conditions' => array('hotel_id')
)
)
)
and so, from that config list:
if a parameters sent for a task is not complete, server returns an error.
if a parameter from the url is doubled, only the first parameter read is taken.
any other parameters not in the config are discarded
if that task is not allowed, it wont be listed for that table
if a task is not there, server returns an error
if a table is not there, server returns an error
I actually patterned this after seeing a component in joomla that uses this strategy. It reduces the model and controller to 4 dynamic functions which would be CRUD, leaving only the config file to be the only file editable later on (this was what I meant about dynamic code, I only add tables and tasks if further tables are needed) but I fear it may impose a security risk which I may have not known yet.
Any ideas for an alternative?
I have no problem with using the same (or very similar) names in the URL and the database — sure, you might be "exposing" implementation details, but if you're choosing radically different names in the URL and the DB, you're probably choosing bad names. I'm also a fan of consistent naming — communication with coders/testers/customers becomes much more difficult if everyone calls everything something slightly different.
What bugs me is that you're letting the user run arbitrary queries on your database. http://.../index.php?table=users&user_id=1, say? Or http://.../index.php?table=users&password=password (not that you should be storing passwords in plaintext)? Or http://.../index.php?table=users&age=11?
If the user connected to the DB has the same permissions as the user sitting in front of the web browser, it might make sense. Generally, that's not going to be the case, so you'll need some layer that knows what the user is and isn't allowed to see, and that layer is a lot easier to write correctly by whitelisting.
(If you've stuck enough logic into stored procedures, then it might work, but then your stored procedures will hard-code column names...)
When composing a SQL query with data from the input, it presents a security risk. But keep in mind that columns values are inserted to the fields by taking input from the user, analyzing it and composing a SQL query with it (except for prepared statements). So when done properly, you have nothing to worry about - simply restrict the user to those column & tables. Open source software's code/database is visible to all - and it doesn't harm the system so much as one would think.
Your aliasses could be a rot13() on the meta/name of your objects.
Although, if you escape the input accordingly when working with those names, I don't see any problem in exposing their names.