Validate parameters in laravel - php

I have some doubts about validating parameters coming from a route. This is an example code, but I'm not sure if it's 100% correct as a best practice.
Regarding the various ids that are passed on the route, is it good practice to check that the id actually exists in the table? If so, should the message returned as an API response still be generic, or is it okay to specify that "nothing was found for parameter x"? However, this exposes which ids exist in the table to external people.
public function show(int $firstParam, int $secondParam, int $thirdParam): JsonResponse
{
$rulesForId = ['required', 'numeric'];
Validator::validate(
[ 'firstParam' => $firstParam, 'secondParam' => $secondParam, 'thirdParam' => $thirdParam ],
[ 'firstParam' => $rulesForId, 'secondParam' => $rulesForId, 'thirdParam' => $rulesForId]
);
try {
// Business logic
} catch (ModelNotFoundException $e) {
// Return error response
return Response::error($e->getMessage(), HttpResponse::HTTP_NOT_FOUND);
}
}

It all depends on what you are trying to achieve.
There's nothing wrong if you return 404 and let client know that the item with this ID does not exist, but again it depends.
Lets say you have bank app. If you have user A with a wallet (lets give it ID of 1). Then user B tries to GET /api/wallet/1 (this wallet belongs to user A) instead of returning 403(not allowed) you can still return 404, as for user B wallet with id 1 does not exist (also you dont let user B know that such wallet exists).
If you dont want anybody to know how much records you have in table(or to know approximate number) you can use UUID to your table.
Then request(example) might look like GET /api/wallet/66830110-adf4-11ed-afa1-0242ac120002. Nobody from the outside can find out how much records you have.
I think what you need is - first find the item with such id (if not found then 404), then check if client who requests this resource has permissions to read/write it. if one does not, then you can still return 404 as for this client requested resource does not exist.
Also be careful when you typehint input parameter with int as all incoming data is usually a string, this code might fail if one of your parameters wont be a number.

Related

Checking for empty values when saving into multiple tables with ZF2

I'm using ZF2 and mysql, but the question is platform-independent. I have a data transfer object Organization that gets hydrated from an html form. OrganizationMapper has a save method that (1) gets Organization as an argument and (2) fills a couple of database tables one after another.
Suppose the 1st table gets filled ok, but the 2nd doesn't because one of the properties of Organization isn't set (not null constraint on a column). The user gets an error, but the 1st table is already filled. If he attempts to submit the form again, but this time with all html fields filled, all the tables get filled ok, but the 1st has a previous unused row.
How could I avoid this situation?
I thought of checking for empty values with if's in the mapper's save method, but it doesn't seem elegant. I know about the InputFilter validations in ZF2, but these check the user input in the form, they don't check things when the php code communicates with the database.
Any help?
The best way is to validate all the data before you start writing it to the database.
I didn't use ZF2 and this solution is actually framework-dependent, so you need to check the ZF2 docs. For example, in Yii, you just define validation rules for every field of the model, so you can ensure that your Organization contains all the data before you start saving it to the database, probably something similar is possible in Zend.
Note, that validation doesn't mean just to check for empty values, you may need to verify different things, like: "email is correct email like xxx#yyy.com", "name is not empty", "name length is more than 3 chars", "name length is less than 1000 chars" and so on.
For Yii it roughly looks like this:
class Organization extends ActiveRecord {
...
// here we define the validation rules
public function rules() {
return [
// name is required
['name', 'required'],
// check min / max length
['name', 'string', 'min' => 3, 'max' => 12],
// check if email is valid
['email', 'email']
];
}
}
Now you can do $organization->validate() to make sure everything is correct (also when you do $organization->save() the rules will be checked before saving to the database).
And one more solution to protect from the inconsistent data is to use transactions. In the case you write to multiple tables, you anyway need them, even if you validated everything. Unexpected things happen, so it is better to protect your saving code like this (pseudo-code):
$transaction->start();
try {
$table1->writeSomeData();
$table2->writeMoreData();
$transaction->commit();
} (catch Exception $e) {
$transaction->rollback();
}
Again, check your framework documentation, it probably supports this in some way.

Cakephp Web Service issue on "add"

I am trying to setup a simple Restfull api using cakephp.
I followed the documentation from the Cakephp site.
But I am encountering the following issue.
I am using Postman plugin to test the Api calls.
I have a table called 'Categories' and in its controller have an action add().
public function add() {
if ($this->request->is('post')) {
$this->Category->create();
if ($this->Category->save($this->data)) {
$message = 'Saved';
}
else {
$message = 'Error';
}
$this->set(array(
'message' => $message,
'_serialize' => array('message')
));
}
}
and in db, I have Category table with schema (id (int 11, PK, A_I), name(varchar(40)), created (datetime), modified(datetime)).
So from postman I send POST requests to http://myProject/categories.json.
From my understanding when i send key:name and value: test, it must save into the database, which works fine. But I must get error when I replace the "key" with something other than name. i.e for exmaple key: name123 and value: test2, But this data too is getting saved in the db without any error except for the name field. i.e it is saving (id:some value, name:"", created:somevalue, modified:someValue)
I dont understand how. Any help will be greatly appreciated.
You will need to provide more info because what you say doesn't make sense. For example what do your posted data look like? Is there any kind of validation in the model? How do you expect a key/value pair to be stored in only one field (specifically name) in the DB?
What happens now is that you tell Cake to save the supplied data ($this->Category->save($this->data)) although you don't check (via cake's validation rules in the model or any other means) that it contains useful arguments and especially Category.name.
As computers will just do what you tell them to do and not what you imply or have in mind, it goes on and sends the calculated created/modified fields to the DB which in turn saves them with the autoincremented ID. Since name doesn't have a UNIQUE or NOT NULL field condition in the DB it is saved as NULL or empty string.

Yii's CCompareValidator doesn't work

Either I don't understand how CCompareValidator works in Yii (sic!) or it doesn't work for me at all.
I want to check, if an ID of row / record / user being updated isn't the same, as an ID of currently logged-in user. And prohibit update, if it is.
I used CCompareValidator at first:
array('id', 'compare', 'compareValue'=>Yii::app()->user->id, 'message'=>'Boom!')
It doesn't work -- it halts editing / update of every row / record / user, no matter, what an ID actually is.
So, I rewrote it to my own, custom validator. In my opinion, the code is the same as in case of built-in one:
array('id', 'compareId', 'compareValue'=>Yii::app()->user->id, 'message'=>'Boom!')
public function compareId($attribute = null, $params = null)
{
if($attribute === 'id')
{
if($this->id == $params['compareValue'])
{
$this->addError($params['message']);
}
}
}
It works like a charm -- allows update of any row / record / user, which ID is different than currently logged-in user's ID. Blocks update, showing defined message, in case compared IDs are equal.
What am I missing? Why original Yii's built in validator fails on such simple example, while my own works?
The validator works, as supposed, my logic have failed:
CCompareValidator, throws an error, when two compared values are not equal. On the other hand, if they're equal -- it passes validation without errors. That is supposed behavior.
I wanted an error, when values are equal (which means, that user is attempting to edit himself or herself) and pass as validated, when both values are different (logged user edits different one).
That's why I need to use 'operator'=>'!=' as configuration of validator. This is the answer.
BTW: All the glory of solving this problem goes to Keith at YiiFramework.com's forum.

CakePHP - Controller or No Controller?

I am currently building a web app which has two models, Donor and Donation Models respectively. It has multiple user roles. When the staff user first registers a donor, I want him to be redirected to another form which allows him to fill in the Donation details(the donor is registered once the first donation is successful).
Firs of all, should I create a donation controller, from which I would redirect the user using:
return $this->redirect(array('controller'=>'donations','action'=>'add'));
For the above to work, it requires me to save the newly registered donor's id in a session like so :
$this->Session->write('id', $this->Donor->id);
So the user is redirected to 'donations/add' in the url, and this works fine.. However I think this has some flaws. I was wandering whether I should create another action inside the Donor controller called 'add_donation', which will have its respective 'View'. The idea is to be able to form a url of the sort : 'donors/add_donation/4' (4 being the donor_id ! )
This URL follows this construct: 'controller/action/id'
If anyone could shed some light on best practices, or describe any caveats to my solution(the former, using session etc.) , please do help a brother out! Ill be deeply indebted to you! Thanks in advance!
After you saved the data you can do this in the DonorsController:
$this->redirect(array(
'controller' => 'donations',
'action' => 'add',
$this->Donor->getLastInsertId()
));
There is no need to return a redirect, it's useless because you get redirected. Notice that we pass the last inserted record id as get param in the redirect. The redirect method of the controller calls by default _stop() which calls exit().
CakePHP3: There is a discussion about changing that default behavior in 3.0. Looks like in CakePHP 3.0 the redirect() won't exit() by default any more.
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
I would not use the session here, specially not by saving it to a totally meaningless and generic value named "id". If at all I would use always meaningful names and namespaces, for example Donor.lastInsertId as session key.
It's not always clear where to put things if they're related but the rule of thumb goes that things should go into the domain they belong to, which is pretty clear in this case IMHO.
Edit:
Leaving this edit here just if someone else needs it - it does not comply with the usage scenario of the asker.
If you have the user logged in at this stage, modify the add function to check if the userId passed is the same as the one logged in:
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
if ($this->Auth->user('id') != $donorId) {
throw new InvalidArgumentException();
}
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
You can use also the same controller using more models with uses.
Or you can also to ask to another controller with Ajax and morover to get response with Json.

Passing parameters through form to Controller (CakePHP)

Okay, so I'm fairly new to CakePHP. This is the setup:
I've got a Model (Reservation), and a controller, (ReservationController).
I'm trying to provide a simple add() functionality.
The request url is: www.example.com/reservations/add/3
Where 3 is the ID of the event this reservation is for.
So far, so good. The problem is, the form is constructed like this:
<h2>Add reservation</h2>
<?php echo $form->create('Reservation');
echo $form->input('first_name');
echo $form->input('last_name');
echo $form->input('email');
echo $form->input('tickets_student'); echo $form->input('tickets_nstudent');
echo $form->end('Add');?>
When I hit the send button, the request URL becomes simply www.example.com/reservations/add/, and the event id is lost.
I've solved it now by grabbing the id in the controller, and make it available to the form:
// make it available for the form
$this->set('event_id', $eventid);
And then, in the form:
$form->create('Reservation',array('url' => array($event_id)));
It works, but it strikes me as a bit ugly. Isn't there an easier way to make sure the form POST action gets made to the current url, instead of the url without the id?
Nik's answer will fail if the website isn't in the server public_html root.
This answer is more solid:
<?php echo $form->create('Reservation',array('url'=>'/reservation/add/'.$data['id']));?>
Following the strictest convention for just a moment, reading a URL like /reservations/add/3 would be, well, confusing. You're calling on the ReservationsController to act on the Reservation model, but passing it an event ID. Calling /reservations/edit/3 is far less confusing, but just as wrong for your situation since the id value, "3", would be assumed to be a reservation identifier.
Essentially, you're making an ambiguous request at best. This is a simple form to create a reservation and that reservation has to be associated with an event. In a "traditional" scenario, the form would allow the user to select an event from some kind of list. After all, the foreign key, probably event_id in this case, is really just another property of a reservation. The list would have to be populated in the controller; this is usually done via a find( 'list' ) call.
If you already know the event that you want to create the reservation against (which you apparently do), then I'd probably select the analogous method of using a hidden field in the form. You still have to set the value in the controller just as you're doing, but the end result is a bit more Cake-y. The hidden field would be named data[Reservation][event_id] (again, I'm assuming the name of your foreign key field).
$form->create('Reservation',array('action' => 'add',$eventId);
and in the controller:
function add($eventId = null)
{
if($eventId == null)
{
// error state
throw new NotFoundException('Invalid id');
}
...
}
I do it all the time.
You can do following:
$form->create('Reservation',array('url' => $this->Html->url()));
this way all your variables from the url will be added in the form action :)
As Rob Wilkerson suggests, the issue is your URL route doesn't accurately describe the operation being performed. It becomes further confusing when you want to edit the reservation: /reservations/edit/6. Now the number in the URL means something different.
The URL convention I use for situations like these (adapted to your particular case) is /events/3/reservations/add. It takes a bit more up-front to configure your routes, but I've found it's superior for clarity down the road.
Sample routes.php:
Router::connect(
'/events/:event_id/reservations/:action',
array('controller'=>'reservations','action'=>'index'),
array(
'pass' => array('event_id'), // pass the event_id as a param to the action
'event_id' => '[0-9]+',
'actions'=>'add|index' // only reverse-routes with 'action'=>'add' or
// 'action'=>'index' will match this route
)
)
// Usage: array('controller'=>'reservations','action'=>'add','event_id'=>3)
Router::connect(
'/events/:event_id/reservations/:id/:action',
array('controller'=>'reservations'),
array(
'pass' => array('id'), // pass the reservation id as a param to the action
'id' => '[0-9]+',
'event_id' => '[0-9]+',
'actions'=>'edit|delete' // only reverse-routes with 'action'=>'edit' or
// 'action'=>'delete' will match this route
)
)
// Usage: array('controller'=>'reservations','action'=>'edit','event_id'=>3,'id'=>6)
In your FormHelper::create call, you'd have to specify most of the reverse-route you want to follow, but again, the up-front cost will pay dividends in the future. It's usually better to be explicit with Cake than to hope its automagic always works correctly, especially as the complexity of your application increases.

Categories