In my cake PHP application, I have a edit form where "email" field is readonly that means user can not update it.
NOw if I think according to security point of view, user can update the field by 'firebug' or some other browser plugins.
I am using $this->User->save($this->data) to save the updated data. By this function Email can be also be updated.
Do we have any way in cake php so that I can prevent this field to be update, like by passing here a argument or something like this?
You can simply remove the email field from $this->data:
unset($this->data['User']['email']);
$this->User->save($this->data);
You could do something like:
$dontUpdateField = array('email');
$this->Model->save(
$this->data,
true,
array_diff(array_keys($this->Model->schema()),$dontUpdateField)
);
If security is a concern, simply reject any data that has unexpected values. In cake you could do this, but it can be adapted for any framework/cms
/**
* Checks input array against array of expected values.
*
* Checks single dimension input array against array of expected values.
* For best results put this is in app_controller.
*
* #param array $data - 1 dimensional array of values received from untrusted source
* #param array $expected - list of expected fields
* #return boolean - true if all fields are expected, false if any field is unexpected.
*/
protected function _checkInput($data,$expected){
foreach(array_keys($data) as $key){
if (!in_array($key,$expected)){
return;
}
}
return true;
}
/**
* edit method.
*
* put this in <Model>_controller
* #param string $id
* #return void
* #todo create errors controller to handle incorrect requests
* #todo configure htaccess and Config/routes.php to redirect errors to errors controller
* #todo setup log functionality to record hack attempts
* #todo populate $expected with fields relevant to current model
*/
function edit($id=null){
$expected = ('expectedVal1', 'expectedVal2');
$this->Model->id = $id;
if (!$this->Model->exists()) {
throw new NotFoundException(__('Invalid model'));
}
if ($this->request->is('post')) {
if (!$this->_checkData($this->request->data['Model'], $expected)) {
//log the ip address and time
//redirect to somewhere safe
$this->redirect(array('controller'=>'errors','action'=>'view', 405);
}
if ($this->Model->save($this->request->data)) {
//do post save routines
//redirect as necessary
}
else {
$this->Session->setFlash(__('The model could not be saved. Please, try again.'));
}
}
$this->set('model',$this->Model->read($expected,$id));
}
You can use the security component and make the email hidden. While using this component, hidden fields cant be changed or cake will blackhole the form.
http://book.cakephp.org/1.3/en/view/1296/Security-Component
If your application is public it is strongly recommended that you use security, otherwise it is kinda trivial to inject data in your models by submitting extra fields on the form and when you do $this->Model->save($this->data)) the extra fields are saved, unless you do the extra work of validating every field of $this->data;
Related
Creating extra field 'images' resource forms usually throws 'column not found' type database level error.
But I need that type of extra field on the resource forms for some business logic under the hood when the create/update form is submitted.
I tried using removeNonCreationFields method on resource to remove that field column from saving to database but does not work and still throws error.
Please note that ->hideWhenCreating() or ->readonly() is not relevant as I need to interact on that field on create/delete forms.
Is there any other way to make such situation success with extra fields? Please help. Thanks.
My solution was:
app/Nova/Post.php
/**
* #param NovaRequest $request
* #param \Illuminate\Database\Eloquent\Model $model
* #param \Illuminate\Support\Collection $fields
* #return array|void
*/
protected static function fillFields(NovaRequest $request, $model, $fields)
{
$fillFields = parent::fillFields($request, $model, $fields);
// first element should be model object
$modelObject = $fillFields[0];
// remove all extra non-database attributes from the model
unset($modelObject->to_profile_gallery);
// I am not sure it will work if we unset on $model parameter and return it
// But you should try first doing so on $model parameter and return $model
return $fillFields;
}
Then you should use two functions, one for how to save in database and another for how to retrieve that specific data from database. Use these on the extra Field.
->fillUsing(function($request, $model, $attribute, $requestAttribute){
// during creation photos are handled by Nova Resource Observer
if($model->type !== post_type_photo()) return;
// run only for update request
FilepondHelper::handleMediaFillUsingCallback(PostMediaTag::photos, true, $request, $model, $attribute, $requestAttribute); // only update
})
->resolveUsing(function($value, $resource, $attribute) use($request){
return FilepondHelper::handleMediaResolveUsingCallback(PostMediaTag::photos, $value, $resource, $attribute, $request);
}),
Hoping this will solve your issue. Thanks
Hasnat approach worked perfectly for my case.
I wanted to have one special field when creating a resource only for internal logic, which should be ignored/discarded and not related in any way to a database field.
Thanks!
I am working on this laravel project where user can upload an avatar image. My users table does not have any column yet to store the file location. So i was testing this in phpunit following the TDD series in laracast.
After the file is uploaded successfully and moved to the desired location in the server, i called the update method on the authenticated user like below:
$user = auth()->user();
$user->update(['avatar_location' => 'avatars/avatar.jpg']);
Note that avatar_location is not yet there on the users table. I expected this to fail but it didn't. I tried to find out what was going on so i followed through to the update() method in the model class:
//file Illuminate/Database/Eloquent/Model.php
public function update(array $attributes = [], array $options = [])
{
//dd($attributes); //prints 'avatar_location"=>"avatars/avatar.jpg'
if (! $this->exists) {
//dd($attributes);
return false;
}
return $this->fill($attributes)->save($options);
}
till this point the dd($attribute) prints the value that i passed to the update() method.
So i followed into the fill() method that is being called with the attribute parameter. However when i die dumped the received parameter from inside the fill() method i am not seeing the key=>value pair that i passed. Instead it was showing the other attributes of the user:
/**
* Fill the model with an array of attributes.
*
* #param array $attributes
* #return $this
*
* #throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
{
//dd($attributes);
//does not print 'avatar_location"=>"avatars/avatar.jpg'
//rather, prints:
//array:4 [
// "name" => "Armand Mraz"
// "email" => "akautzer#example.net"
// "password" => "$2y$10$h7OG9/Toh31MsyFQc8lfg.wHeQC7maP4Bh37bea.DXU//IuRuXZi."
// "remember_token" => "X0udISwEEM"
]
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException($key);
}
}
return $this;
}
I spent a lot of time trying to figure out why?
can anyone please explain?
And why the update method is not failing even though i am trying to update a column that does not exist?
Thanks,Yeasir
When you're updating an object, Laravel is trying to match the keys of the array of data and the list of fillable fields. All pairs of key/valye missing from the fillable fields are not considered. It's the reason why it's not failing.
You have to update the fillable field list (property $fillable) in your user model.
Have a look at the documentation.
If you add avatar_location in your fillable fields list and the field doesn't exist, in this case, it will throw an exception.
I created an extension which allows the user to sign up via the frontend. I couldn't use working ones because the client requested special tasks.
This is the code which detects taken usernames.
public function createAction(\Vendor\Feregister\Domain\Model\FeUserX $newFeUserX)
{
$uname = $newFeUserX->getUsername();
$select_query = '*';
$from_table = 'fe_users';
$where_clause = 'username="'.$uname.'"';
$test = $GLOBALS['TYPO3_DB']->exec_SELECTquery($select_query, $from_table, $where_clause);
if ($GLOBALS['TYPO3_DB']->sql_num_rows($test)) {
$this->addFlashMessage('Username is already taken.', '', \TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR);
$this->redirect('new');
} else {
// do stuff when the username isn't taken yet
}
}
But unfortunately and obivously, when redirecting back to the new action, the fields are empty again.
Is there a way to pass the arguments back to the new action and fill the forms?
Yes, and extbase has a standardized way to do this. It works as follows:
If an action is called, its parameters are validated, except if validation is switched off in the doc comments. If validation fails, the previous action (the one whose view contained the submitted form) is called again, with the same parameters.
You can use this as follows:
/**
* #param \Vendor\Feregister\Domain\Model\FeUserX $newFeUserX
* #ignorevalidation $newFeUserX
*/
public function newAction(\Vendor\Feregister\Domain\Model\FeUserX $newFeUserX = null)
{
$this->view->assign('user', $newFeUserX);
// View renders form with name="newFeUserX" and object="{user}",
// action="create", fields use the property-attribute to fill
// in values and field names.
}
/**
* #param \Vendor\Feregister\Domain\Model\FeUserX $newFeUserX
* #validate $newFeUserX \Vendor\Feregister\Validator\UsernameDoesNotExistValidator
*/
public function createAction(\Vendor\Feregister\Domain\Model\FeUserX $newFeUserX)
{
// Do something with the user - you can be sure the username
// is not yet taken
}
The class \Vendor\Feregister\Validator\UsernameDoesNotExistValidator is a custom validator that implements the ValidatorInterface, or extends AbstractValidator. It should basically do the validation you are doing in your createAction (maybe using a repository instead of $GLOBALS['TYPO3_DB']). A validator returns errors in a standard way, making it easier to show nice error messages and localize them.
If the validation fails, extbase will try to forward to the action that rendered the form, in this case the new-action. In this case, it will work, because of the #ignorevalidation annotation on the new-action.
In addition, information about validation errors are available in the view, you can render them using the ViewHelper f:form.validationResults.
I have two classes with a bidirectional relationship: Player and Team Each player must have exactly one team and each team can have many players.
When I use the default form field (select) and I submit 0 (by manually editing the HTML) the form error shows correctly. However if I use a custom form field type and submit 0 there's no form error but an exception:
Catchable Fatal Error: Argument 1 passed to
...\Entity\Player::setTeam() must be an instance of ...\Entity\Team,
null given, called in
...\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php
on line 360 and defined in ...\Entity\Player.php line 58
How do I show a normal form error instead of this exception?
My classes are straighforward (only the relevant parts posted):
Team.php
class Team {
/**
* #ORM\OneToMany(targetEntity="...\Entity\Player", mappedBy="team")
*/
protected $players;
}
Player.php
class Player {
/**
* #var Team
*
* #ORM\ManyToOne(targetEntity="...\Entity\Team", inversedBy="players")
* #ORM\JoinColumn(name="team_id", referencedColumnName="id", nullable=false)
* #Assert\Valid
*/
protected $team;
/**
* Set team
*
* #param Team $team
* #return Player
*/
public function setTeam(Team $team) {
$this->team = $team;
return $this;
}
/**
* Get team
*
* #return Team
*/
public function getTeam() {
return $this->team;
}
}
The reverseTransform function of my DataTransformer looks like this:
public function reverseTransform($value)
{
if(!$value)
{
return $this->multiple ? array() : 0;
}
//... get entity
}
If you have created a custom form field with a custom data transformer, it is your responsibility to validate client datas. To get the generic message (This value is not valid), you need to throw a TransformationFailedException in your data transformer. Then, everything will be handled by the form component.
EDIT: By default majority of the data transformers in the Symfony core, converts a blank value to null. The responsibility of the data transformer is to convert client data to model data and eventually to detect major error like non-acceptable value for a choice list or missing data in case of datetime field, etc. It is not to validate the data which is the resposibility of the validator component. Then, it lets the validator component make the final validation which is often more complex than a simple data transformer. IMHO, you're currently mixing data transfomer and validation concern.
EDIT: Additionally, you need to allow in your domain object what the data transformer return itself (here, the null value).
I have a drop down list where users can select their timezone on a form. On submit, I'm looking for the best way to validate the timezone input field using Yii rules.
I'm able to get an array of timezones in PHP using the following:
DateTimeZone::listIdentifiers(DateTimeZone::ALL)
The values are like this (an indexed key with a timezone value):
Array([0]=>'Pacific/Midway',[1]=>'Pacific/Niue',...);
My select box looks like this:
<select name="timezone" id="timezone">
<option value="Pacific/Midway">Pacific/Midway (GMT-11:00)</option>
<option value="Pacific/Niue">Pacific/Niue (GMT-11:00)</option>
...
</select>
I tried the following:
public function rules() {
return array(
array('timezone','in','range'=>DateTimeZone::listIdentifiers(DateTimeZone::ALL)),
array(timezone','required'),
);
} // rules()
But it never validates, I think because the the range is a multidimensional array and in order for range to work it needs to be just an array of values like array('value1','value2'); Is that true?
Is there a better approach?
Best way is to create custom validator class, some example (untested, tune/fix to your needs):
class CountryValidator extends CValidator
{
/**
* Validates the attribute of the object.
* If there is any error, the error message is added to the object.
* #param CModel $object the object being validated
* #param string $attribute the attribute being validated
*/
protected function validateAttribute($object,$attribute)
{
$ids = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
if(!in_array($object->$attribute, $ids))
{
$this->addError($object,$attribute,'Wrong time zone selected!');
}
}
}
Then use it in your model:
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
return array(
array('timezone', 'alias.to.CountryValidator'),
);
}
This method has very important advantage: Like other validator it is reusable, and does not mess your model code.