Cannot test laravel livewire model hidden attribute with assertSet - laravel

I have a simple LIvewire component consisting of a form to create a new User, and it work, i can tell since i've been asked to write tests after the project already reached staging.
The Livewire component use a new User instance (non presisted, i.e. user->exists is false) to store inserted data before persisting it, and the user model hides the password attribute trough the protected $hidden array.
Now, i can sucesfully create new users trough the form page in my local environment, but when it comes to testing, it gives me error.
The test
Livewire::test(
FormComponent::class,
)
->set('user.name', 'user name')
->set('user.email', 'user#email.com')
->set('user.password', 'password')
->assertSet('user.name', 'user name')
->assertSet('user.email', 'user#email.com')
->assertSet('user.password', 'password');
The error
Failed asserting that null matches expected 'password'.
What i find out
Setting my compoment user instance trough the form page goes fine because livewire recognize it as a model, so do something like user->password = 'password', while when setting it from the test with set() it access its property with the access operator as it is an array, i.e.: user['password] = 'password'.
Commenting out the password entry in the $hidden array made the test pass.
This will explain the difference in the property setting.
Conclusion
Since the $hidden array is meant to hide model properties in its array/json representation it should not interfere with automated tests, moreover when using Livewire own methods, so to me this looks like a LIvewire Bug.
Help
Does anyone have aver encountered this bug?
Update#1
I've opened an issue on Livewire github page for this.

You have diagnosed this pretty well. When Livewire serializes the User model, password is not included because it is in the $hidden array. Even though the User model has not been persisted yet, serialization is the same.
I would guess that your input fields all use the defer modifier, which is why your fields are working in the browser; however, when you call set() in the test, it simulates a Livewire request, so the $hidden attributes are being wiped.
An alternate assertion for testing the password is my recommendation here. Try asserting that the created user can login with the selected password.
$this->assertTrue(
auth()->attempt(['email' => 'user#email.com', 'password' => 'password'])
);

Related

What is the security efficacy of 'fillable'?

I'm struggling to fully understand the security efficacy of fillable and wondering what to do with columns that can't have a default value and will never be provided by the user.
A mass assignment vulnerability occurs when a user passes an unexpected HTTP request field and that field changes a column in your database that you did not expect. For example, a malicious user might send an is_admin parameter through an HTTP request, which is then passed to your model's create method, allowing the user to escalate themselves to an administrator.
https://laravel.com/docs/8.x/eloquent#mass-assignment
The above snippet from the documentation contains a good example: is_admin. That value wouldn't be directly provided by the user but it still needs to be provided during create().
Another example might be slug. This would likely come from a user-provided title value. This can't have a default value and won't be provided by the user so it needs to be fillable.
As far as efficacy goes, doesn't it make sense for there to be a class of secured input that's identified as (1) not being provided by the user and (2) is fillable? It seems like a column loses its secure-by-default status if it's listed in fillable.
The fillable prevents batch assignment. Disables the use of arrays. Thus, it is protected from external attacks.
Example:
You can protect the is_admin column:
$fillable = ['name', 'password', 'email'];
Then, to be able to update or create the value, you must explicitly set the value in the model and save it, for example:
$user->is_admin = 1;
$user->save();

Login stops working after changing the input field name "email" to "login-email"

I am developing a Laravel 6.6.2 project and I came across this problem I can't seem to fix.
In the file login.blade.php view i'm trying to change name="email" in the input fields to name="login-email". But when I do this the login doesn't work anymore. So I think that Laravel uses the name email somewhere to validate the login. I can't find where Laravel looks for the name email instead of login-email and if this even is needed to change?
The reason I need to change this is because javascripts use the name value too. (Because I brought a template). I am still learning Laravel so don't be to harsh. Thanks in Advance.
Laravel has excellent documentation. This is always a good place to start.
# Authenticating
https://laravel.com/docs/6.x/authentication#included-authenticating
Username Customization
By default, Laravel uses the email field for authentication. If you would like to customize this, you may define a username method on your LoginController:
In your case, you would return login-email from the username method.
public function username()
{
return 'login-email';
}
Of course, you will also need to add or rename this field in the database if you haven't already.
Alternatively to renaming the field in the database, you could override the credentials method.
protected function credentials(Request $request)
{
return [
'email' => $request->{$this->username()},
'password' => $request->password,
];
}

Laravel - delete password value from request when form is not validated

I'm using FormRequest validation in my Laravel controller method. I'm validating (beside others) fields 'password' and 'password_confirmation'. The rules are:
$rules['password'] = 'required|string|min:8|required_with:password_confirmation';
$rules['password_confirmation'] = 'min:8|required_with:password|same:password';
(I'm not using 'confirmed', because the validation message is always only on the first field, not on the confirmation one)
When the password confirmation does not match thus the validation fails all data does get returned to the form, including the passwords. Is there some way to exclude them from returning only in case of failed validation - so that the user has to manually input them again? I presume it has to be done somewhere in the custom FormValidation class, probably overriding one of its methods - however, how would I go about it? Just delete it from the returning array?
in the password fields of your register.blade.php, remove the old('password') from the value attribute.

handle $errors and old input with 2 forms in a single page

I'm using laravel 5.2.
I'm trying to have 2 similar forms in one single page, one for registering, and one for logging in.
The problem is after some validation error, i can't recognize which one of the two was submitted to place some errors display and fill the correct form with the old input.
I had the idea of trying sending an hidden variable with the forms to recognize which one of the two was used, but when i get back to the forms page i can't retrieve the old('hidden_field').
I also tried to get the path of the referer page to check if i could recognize them from it, but it doesn't work.
Any different idea for a solution??
I. Deal with old input
- Make 1 attribute with different name in those 2 forms. So you can use old() normally
II. Deal with $errors. Use named error bag
- In your Controller
public function postRegister(Request $request){
$validator = Validator::make($request->all(), $array_rules);
if ($validator->fails()){
return redirect('url')->withErrors($validator, 'form_register');
}
}
public function postLogin(Request $request){
$validator = Validator::make($request->all(), $array_rules);
if ($validator->fails()){
return redirect('url')->withErrors($validator, 'form_login');
}
}
- In your view
//Access each form's errors normally
$errors->form_login->all()
$errors->form_register->all()
// Use old input normally
old('name_register');
old('name_login');
Hope this help you.
Thanks to a suggestion in the comments, i found out i had to add a validation rule to my hidden input field in order to pass it again to the view, and get the value with the old() method.
I could use it then to recognize which one of the two forms has been used.

Magento - Separate contact forms

I'm working on a website where the client needs to have multiple separate contact forms (one for contact, one for "request a quote", another couple for stuff like that).
I've already managed to create another contact form with additional fields, but it was the contact one, so the fields were only name, email, subject and message.
Now I've got the "skeleton" of the others, but my question is: is there a way to take advantage of the "Contact" backend to send emails? Or do I need to have another controller to manage them?
If so, can you show me some links or piece of code to start off with?
Thanks in advance.
To do what you want, a custom controller is necessary to pass the POST data.
If you examine \app\code\core\Mage\Contacts\controllers\IndexController.php on line ~62 you'll find postAction() which is called by indexAction() - the default action of the controller.
This is the method that is collecting the passed POST parameters and using the core/email_template model to send off the e-mail. I'd use this code as a reference for your controller.
Be sure you put it all in your own module as always with functionality additions.
To know what you can and cannot pass to the core/email_template model, take a look at \app\code\core\Mage\Core\Model\Email\Template.php. It's got loads of documentation in there for you.
Heck, it's even got example code!
// Loading of template
$emailTemplate = Mage::getModel('core/email_template')
->load(Mage::getStoreConfig('path_to_email_template_id_config'));
$variables = array(
'someObject' => Mage::getSingleton('some_model')
'someString' => 'Some string value'
);
$emailTemplate->send('some#domain.com', 'Name Of User', $variables);
In particular take a look at line ~371, where it passes the variables you set to the layout-specified e-mail template.
/**
* Send mail to recipient
*
* #param array|string $email E-mail(s)
* #param array|string|null $name receiver name(s)
* #param array $variables template variables
* #return boolean
**/
public function send($email, $name = null, array $variables = array())
Magento is doing the same thing that you'd do with any contact form on a plain old PHP form processor. It just delegates everything out to models like the one above so you don't have to do so much work re-inventing the wheel, as it were. Just a new controller to accept the parameters in the POST data.
Feel free to follow up and I'll update the answer to help you where I can!
Here's a concise list of things you'll need to make this happen:
A custom controller to accept the form(s) POST data and pass it to the core/email_template model.
A custom form in the front-end pointing to your controller URL (already done!)
A custom e-mail template for the core/email_template to use to display the data in the e-mail the recipient sees.

Resources