Backpack for Laravel - Permissionmanager: Can't create new user - php

I have the following problem. I am using Backpack for Laravel with the PermissionManager Extension. By default, you have to log in via your email address. However, you can change that to log in with a username. Works fine if I (for example) seed the database. But for some strange reason, I can not create new users via the PermissionManager PermissionManager Extension. If I try the following error message is the result:
SQLSTATE[HY000]: General error: 1364 Field 'name' doesn't have a
default value
(SQL: insert into users (updated_at, created_at)
values (2018-11-29 22:26:32, 2018-11-29 22:26:32))
Just for testing, I gave the name column a default value, but the error message is the same, except the error now is:
Field 'username' doesn't have a default value.
It just takes the next column in my database.
Of course, I've changed the CRUD-Requests, Form-Requests and so on, to store a username instead of an email. But it looks like the Request doesn't get my form values. It only tries to store the timestamps.
Strange thing: If I reverse all I did in the Controllers and so on and create an email column again, it will just work fine... did I overlook something?
How can I solve this error and store users with a username instead of an email?
Edit:
This is even odder... The data gets posted.
Edit 2:
After taking a really close look at the error messages I found the following:
As you can see, the array with my filled in data is still here.
However, in the next instance, the parameters array is completely empty as well as the $instance array.

Step 1: In order to using username you have to edit app/Http/Controllers/Auth/LoginController and add a new method called username
public function username(){
return 'username';
}
Step 2: In User model make sure you use AuthenticatesUsers trait and add username field in fillable array.
Step 3: Make sure in app/Http/Controllers/Auth/RegisterController you add the username validation and add username field in create method.
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'username' => $data['username'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
That's all. Hope it will solve your problem.

When this issue happened for me, I think I caused it by running php artisan vendor:publish instead of following the instructions and running php artisan vendor:publish --provider="Backpack\PermissionManager\PermissionManagerServiceProvider first.
My config/laravel-permission.php file didn't just have the permission and user models incorrect, but the entire file was spatie's version instead of Laravel Backpack's version. My guess is that spatie's vendor:publish register typically runs before Laravel Backpack PermissionManager's.

Never used this package before but I reviewed this package's files from github for solving your problem, i saw some thing in the UserCrudController.php file. The file provides default user CRUD operations. The create and update form items define via setup method in this controller. But i don't see any define for username field.
as I understand it, your user table has a username and in your model $fillable variable contains username and the user create/update form does not post username field to controller.
Can you try define username form element in to UserCrudController.php file for username, or remove username attribute from $fillable in your model.
I hope these solve your problem.

Related

CakePHP 4 - customising the query used by the Authentication component

On a CakePHP 4.3.5 website I'm using the Authentication and Authorization components mentioned in the documentation:
Authentication
Authorization
These supersede the old AuthComponent that was used in previous versions of CakePHP. This is mentioned in the docs:
Deprecated since version 4.0.0: The AuthComponent is deprecated as of 4.0.0 and will be replaced by the authorization and authentication plugins.
My question is related to the new Authentication component. I want to customise the query that I use when a user attempts to login.
I have 2 columns in my users table called email_verified and banned. By default in my application both of these fields are NULL in the database. So when a user creates an account both of these have NULL values. If/when a user verifies their email address, email_verified gets updated to a tinyint(1) with a value 1. The banned field remains NULL unless the user gets banned by an administrator, at which point it would also be updated to 1.
Thus I want my login query to find Users where email_verified => 1 and banned => NULL.
In the documentation - for what I assume is the "old" AuthComponent way of doing things - it has an example. The only difference in the example is that the query looks at a database column called active however I'm comfortable with how to change that to use the 2 columns I've mentioned above.
The problem is that this doesn't work and I assume it's because the new components work differently. There seems to be a lack of documentation about how to get it to work.
What I tried was as follows:
// src/Controller/AppController.php
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Authorization.Authorization', [
'authenticate' => [
'Form' => [
'finder' => 'auth'
]
// ...
}
I then added a method to my UsersTable.php model file as per the documentation, substituting the appropriate fields:
// src/Model/Table/UsersTable.php
public function findAuth(\Cake\ORM\Query $query, array $options)
{
$query
->select(['id', 'email', 'password', 'email_verified', 'banned'])
->where(['Users.email_verified' => 1, 'Users.banned' => null]);
return $query;
}
This doesn't work. Any user - with the correct username/password combination - can login, irrespective of the values of email_verified or banned. I even tried changing these to non-existent columns (e.g. Users.email_verified_foo_bar_baz) to see if it errored. It doesn't error and I don't think it's even using this function.
How can I get this to work with the new Authorization component in CakePHP 4?

CakePHP 4 - how to validate forms that need to save data to multiple tables

Apologies if this has been asked before. All of the examples I can find are old or apply to legacy versions of CakePHP, e.g. cakephp: saving to multiple models using one form is 7 years old.
I have an application in CakePHP 4.1.6. Two of the tables in the database are called tbl_users and tbl_orgs ("orgs" in this case means "Organisations").
When I add an Organisation I also want to create a User who is the main contact within the Organisation. This involves saving to both the tbl_orgs and tbl_users tables when the form is submitted.
The problem I'm experiencing is how to get the form working in a way where it will run the validation rules for both tbl_users and tbl_orgs when submitted.
This is how our application is currently structured:
There is a Controller method called add() in src/Controller/TblOrgsController.php. This was generated by bake and was initially used to insert a new Organisation into the tbl_orgs table. At this point it didn't do anything in terms of tbl_users however it worked in terms of saving a new Organisation and running the appropriate validation rules.
One validation rule is that every companyname record in tbl_orgs must be unique. If you try to insert more than 1 company with the name "My Company Limited" it would give the validation error "This company name already exists":
// src/Model/Table/TblOrgsTable.php
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add(
$rules->isUnique(['companyname']),
[
'errorField' => 'companyname',
'message' => 'This company name already exists',
]
);
return $rules;
}
Whilst the above applies to TblOrgs we also have an buildRules() in TblUsers which applies similar logic on an email field to make sure that all email addresses are unique per user.
In the add() Controller method we start by specifying a new empty entity for TblOrgs:
// src/Controller/TblOrgsController.php
public function add()
{
$org = $this->TblOrgs->newEmptyEntity();
// ...
$this->set(compact('org'));
}
When the form is created we pass $org:
// templates/TblOrgs/add.php
<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->end() ?>
When the TblOrgs fields are rendered by the browser we can inspect the HTML and see these are obeying the corresponding Model. This is clear because of things such as required="required" and maxlength="100" which correspond to the fact that field is not allowed to be empty and is a VARCHAR(100) field in the database:
<input type="text" name="companyname" required="required" id="companyname" maxlength="100">
It also works in terms of the rules specified in buildRules for TblOrgs. For example if I enter the same company name twice it shows the appropriate error in-line:
I then tried to introduce fields for TblUsers. I prefixed the form fields with dot notation, e.g. this is intended to correspond to tbl_users.email input field:
<?= $this->Form->control('TblUser.email') ?>
When inspecting the HTML it doesn't do the equivalent as for TblOrgs. For example things like maxlength or required are not present. It effectively isn't aware of TblUsers. I understand that $org in my Controller method is specifying a new entity for TblOrgs and not TblUsers. I reviewed the CakePHP documentation on Saving With Associations which says
The save() method is also able to create new records for associations
However, in the documentation the example it gives:
$firstComment = $articlesTable->Comments->newEmptyEntity();
// ...
$tag2 = $articlesTable->Tags->newEmptyEntity();
In this case Tags is a different Model to Comments but newEmtpyEntity() works for both. With this in mind I adapted my add() method to become:
$org = $this->TblOrgs->TblUsers->newEmptyEntity();
But this now gives an Entity for TblUsers. It seems you can have either one or the other, but not both.
The reason this doesn't work for my use-case is that I can either run my Validation Rules for TblOrgs (but not TblUsers) or vice-versa.
How do you set this up in a way where it will run the validation rules for both Models? It doesn't seem to be an unreasonable requirement that a form may need to save data to multiple tables and you'd want the validation rules for each of them to run. I get the impression from the documentation that it is possible, but it's unclear how.
For reference there is an appropriate relationship between the two tables:
// src/Model/Table/TblOrgsTable.php
public function initialize(array $config): void
{
$this->hasMany('TblUsers', [
'foreignKey' => 'o_id',
'joinType' => 'INNER',
]);
}
and
// src/Model/Table/TblUsersTable.php
public function initialize(array $config): void
{
$this->belongsTo('TblOrgs', [
'foreignKey' => 'o_id',
'joinType' => 'INNER',
]);
}
Okay, lots of confusion to clear up here. :-) My assumption here, based on what you've written, is that you're trying to use a single form to add a new organization, and the first user in it, and then maybe later you'll add more users to the org.
First, $this->TblOrgs->TblUsers is your users table object, so when you use
$org = $this->TblOrgs->TblUsers->newEmptyEntity();
what you're doing is creating a new user entity. The fact that you got to that table object through the orgs table, and that you're calling it $org doesn't change that. It doesn't somehow magically create a blank org entity with a blank user entity in it. But you don't need that entity structure here at all here, just the empty org entity. Go back to simply:
$org = $this->TblOrgs->newEmptyEntity();
Now, in your form, you'll want something like this:
<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->control('tbl_users.0.email') ?>
<?= $this->Form->end() ?>
The field is called tbl_users.0.email because:
The table name gets converted to lower case underscore format.
It's a hasMany relation from orgs to users, so it's expecting an array of users; we have to give a numeric index into that array, and 0 is a great place to start. If you were going to add a second user at the same time, the field for that would be tbl_users.1.email.
Note: A great way to figure out what format the form helper is expecting you to create your field names in is to read an existing set of records from the database (in this case, an org and its users), and then just dump that data, with something like debug($org);. You'll see that $org has a property called tbl_users, which is an array, and that will point straight to this structure I've described above.
With the fields set up like this, you should be able to patch the resulting data directly into your $org entity in your controller, and save that without any other work. The patch will created the entire structure, with a entity of class TblOrg, with a tbl_users property which is an array containing a single entity of class TblUser, and validation will have been done on both of them. (At least it should; you can use debug($org); as mentioned above to confirm it.) And when you save this entity, it will first save the TblOrg entity, then add that new ID into the TblUser entity before saving it, as well as checking the rules for both and making sure that nothing gets saved to the database if it can't all be saved. That all happens automatically for you with the single save call!
If your association was a hasOne or belongsTo relation (for example if you were adding a new user and also the org that they're in, instead of the other way around), you could dump a sample $user, and see that it has a property called tbl_org which is just a straight-up entity, not an array of entities, and note that tbl_org is now singular, because it's just one entity instead of a bunch. In this case, the field name to use would be tbl_org.companyname, no array index in there at all.

Laravel 8 user table column names

I am using laravel 8 with an existing user table. All is working as expected except the password reset link functionality. This is because my table has the email column name as "Email" instead of "email." Other applications use this table, so the column name cannot be changed. I can get the password reset link functionality working if I manually set the column name within the framework itself (example below).
File: /vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
public function retrieveByCredentials(array $credentials)
{
// framework code that retieves the user record for email address
if ($res) {
$res->email = $res->Email;
}
// rest of frame work code
}
This seems a little "hacky." Is there a better approach to this?
Laravel would benefit greatly from more customization regarding the user's table (custom user table name, column names, etc.).
Laravel has mutators and accessors. This does that you can change behavior of ->email access or assigning it. Add this snippet to your User.php model.
class User {
public function getEmailAttribute()
{
return $this->attributes['Email'];
}
}
You can read the docs about it. The naming convention for the function is get{PropertyName}Attribute, if you define your function like so, you can easily overwrite property logic in Laravel. Making it use the column Email.

Laravel Auth, custom database & table, {"error":{"type":"ErrorException","message":"Undefined index: password"

Creating a login route & function using Laravel's Auth class and my database. Not using Eloquent due to the large legacy database that must be used.
I know SHA1 shouldn't be used but the thousands of accounts in the database use it and I haven't migrated.
Route::post('login', function() {
$user = array(
'username_c' => Input::get('username'),
'password_c' => sha1(Input::get('password'))
);
if(Input::has('username') && Input::has('password')) {
if(Auth::attempt($user)) {
return json_encode(array('result' => true));
}
}
return json_encode(array('result' => false));
});
ONLY when I enter valid credentials, it spits back this error in the console:
{"error":{"type":"ErrorException","message":"Undefined index: password","file":"\/var\/www\/html\/vendor\/laravel\/framework\/src\/Illuminate\/Auth\/DatabaseUserProvider.php","line":135}}
What could be the problem here? I edited app/config/auth.php and changed the 'table' to my table, and the 'driver' to 'database'
From reading Laravel's docs., that's all I need to change. What's causing this?
If you look at the line that is being referenced, you see this:
$plain = $credentials['password'];
The authentication is expecting a 'password' field to exist in the credentials provided to Auth::attempt, and you're giving it 'password_c' instead, which is why you're getting that error.
The second line in that method is this:
return $this->hasher->check($plain, $user->getAuthPassword());
This is basically checking whether the plain password provided in the input matches the value that is stored in the database. This means that your input key name does not need to match the name of the database column. The name of the database column is determined by the $user->getAuthPassword() method call above. You will need to make the key that you pass to Auth::attempt to be 'password', though, instead of 'password_c'.
But this isn't just going to work, because as you've probably already noticed in the second line above that class is going to expect an actual instance of a User model, and by your own admission you don't have any Eloquent models.
My suggestion would be to go ahead and create the user eloquent model, because I believe you're going to have to. You don't have to turn all of your tables into models, but if you want authentication to work with Laravel's Auth facade then I don't think there's any way around it.
Update
This answer also has a good method for updating your current passwords to use the Laravel hash during authentication attempts with a fallback to using MD5 (the old hash function, in your case you could use sha1).
Update 2
You need to change your authentication logic to look something like this:
Route::post('login', function() {
if(Input::has('username') && Input::has('password')) {
$user = array(
'username' => Input::get('username'),
'password' => sha1(Input::get('password'))
);
if(Auth::attempt($user)) {
return json_encode(array('result' => true));
}
}
return json_encode(array('result' => false));
});
Ensure that your user model's getAuthPassword method returns the name of the password column and the getAuthIdentifier returns the name of the username column.

laravel how to specifiy the input columns

I have a form in html that has an input with name restaurantName, but my column in the database is name. I am not able to use this in the controller
Restaurant::create(Input::only('restaurantName'));
Because there is no column with restaurantName so my question is how to tell laravel that restaurantName is maped to name ?
many thanks
Edit
the html form has these fields:
restaurantName
website
username
password
mobileNumber
firstName
lastName
The database has two tables which are Admin and Restaurant, each restaurant has one admin and the admin is one for each restaurant, so it is one to one relationship
when I submit the form I do the following:
$input = Input::all();
$admin = Admin::create(Input::only('username', 'password', 'mobileNumber', 'firstName', 'lastName'));
$data = ['name' , Input::get('restaurantName'), 'website' => Input::get('website')];
$restaurant = new Restaurant($data);
$admin->restaurant()->save($restaurant);
the admin row is inserted to the database so the problem starts with the $data line code.
the exception is:
BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::save()
Firstly, you should never just blindly pass user input to models, and secondly, this particular problem has absolutely nothing to do with laravel.
Your problem can be solved with basic PHP. Simply, create your own array of input.
$data = [
'field' => Input::get('field'),
'name' => Input::get('restaurantName')
];
There seems to be a grave misconception that Laravel is its own independant system, it is not. It is literally a collection of PHP code, it doesn't do anything that can't be done with PHP and it doesn't prevent you from doing anything you can normally do with PHP.

Categories