Laravel 5 - creating two tables during registration - authentication

In my database design, I have two tables, People & Auth. The Auth table holds authentication information and the person_id while the People table holds all other information (name, address, etc). There is a one-to-one relationship between the tables as seen in the models below.
The reason I have separated the data into two tables is because in my
application, I will have many people who do not have authentication
capabilities (customers to the user).
App/Auth.php
class Auth extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
public function person() {
$this->belongsTo('Person');
}
}
App/Person.php
class Person extends Model
{
public function auth() {
$this->hasOne('Auth');
}
}
In my AuthController::create() method, I am attempting to populate both models with the user supplied information like this:
protected function create(Request $request)
{
$person = \App\Person::create($request->all());
$auth = new \App\Auth;
$auth->fill($request->all());
$auth->person_id = $person->id;
$auth->save();
return $person;
}
In my application, I would like to authorize a user and pass a $user object as the authenticated person to subsequent routes. Am I doing this correctly? Is this the best way? There's cookies and bonus points if you can also explain how to retrieve the $user object after authentication...Auth table data is not needed in the $user object.
EDIT
I have changed my config/Auth.php file to reflect the changes as noted in the answers below (thx #user3702268). However, I have now found an error with my controller. In the AuthController::create() method, I am returning my App/Person object and this throws an ErrorException seeing as how App/Person does not implement the Authorizable trait. I do not want my App/Person object to be authorizable, but it is the object that I want returned as the authenticated $user in my views. How? Shall I simply override the postRegister method or is there a more Laravel way?
EDIT 2
I'm now returning the $auth object which uses the authorizable trait. In my views/controllers I'm trying to access the Person using Auth::user()->person but getting Class 'Person' not found errors

You should replace the App\User Class in config/auth.php line 31 the class that contains the username and password:
'model' => App\User::class,
to
'model' => App\Auth::class,
Be sure to encrypt the password before saving by using the bcrypt($request->get('password')) helper or Hash::make($request->get('password')). Then you can authenticate by calling:
Auth::attempt([$request->get('username'), $request->get('password')]);
You can retrieve the authenticated user using this:
Auth::user()

Related

Symfony 6 Api Platform Extension

I'm a beginner on Sf6 and i'm stuck on a problem with the doctrine extension. I try to recover some datas from an API and send them to a front-end Angular 13. My personnal project is an application for manage some garden equipments and i look for to recover datas according to the role of the user.
If the current user have ['ROLE_USER'] i want to fetch his owns datas but if the user have ['ROLE_ADMIN'] i want to fetch all the datas for this entity. I'm ever able to do it with my entity Garden but not for the equipments entity.
My relationnal logical data model:
And the code for CurrentUserExtension.php :
<?php
namespace App\Doctrine;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use App\Entity\Garden;
use App\Entity\Lawnmower;
use App\Entity\Lightning;
use App\Entity\Pool;
use App\Entity\Portal;
use App\Entity\Watering;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
/**
* This extension makes sure normal users can only access their own Datas
*/
final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
private $security;
public function __construct(Security $security) {
$this->security = $security;
}
public function applyToCollection(QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
string $operationName = null): void {
$this->addWhere($queryBuilder, $resourceClass);
}
public function applyToItem(QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
array $identifiers,
string $operationName = null,
array $context = []): void {
$this->addWhere($queryBuilder, $resourceClass);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void {
if ($this->security->isGranted('ROLE_ADMIN')
|| null === $user = $this->security->getUser()) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
switch ($resourceClass) {
case Garden::class:
$queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias))
->setParameter('current_user', $user);
break;
case Lawnmower::class:
case Lightning::class:
case Pool::class:
case Portal::class:
case Watering::class:
$gardenAlias = sprintf("%s_garden", $rootAlias);
$queryBuilder->innerJoin(sprintf('%s.garden', $rootAlias), $gardenAlias)
->andWhere(sprintf('%s.user = :current_user', $gardenAlias))
->setParameter('current_user', $user);
break;
}
}
}
It's my first post on Stackoverflow so feel free to say me if my post isn't formated as well. Some help will be appreciated.
Ps: As you could see in the final class CurrentUserExtension.php i'm using Api Platform.
According to the documentation of Api Platform (https://api-platform.com/docs/core/extensions/) i'm able to fetch gardens depending of the user role, the final class CurrentUserExtension work as expected. I'm looking for doing the same for the equipments entities (Watering, Lawnmower, Pool, Portal and Lightning). Notice the relation between my entities (one-to-many):
A User could have many gardens but a Garden could belong to a single User.
A Garden could have many waterings but a Watering could belong to a single Garden.
I just saw there is an error on my relationnal logical data model: the entities Lawnmower, Pool, Portal and Lightning doesn't have the property garden_user_id in their classe. But the entity Watering is ok, i have just a single foreign key garden_id.
I'm able to give you the SQL request for retrieve all the waterings for the user which have the id 2 (this request works fine):
SELECT w.id, w.garden_id, w.name, w.flow_sensor, w.pressure_sensor, w.status FROM watering AS w INNER JOIN garden AS g ON g.id = w.garden_id INNER JOIN user AS u ON u.id = g.user_id WHERE u.id = 2
I think i'm near to my goal but now i've the following error =>
"[Semantical Error] line 0, col 104 near 'o_garden INNER': Error: 'o_garden' is already defined."
Your problem can be solved with a "conception" change.
I would say I do not try to make a single api url with different behaviors based on the user.
To make this work, I advise you to do something like this :
Be careful; my answer is on Symfony 6.2, Php 8, and Api Platform 3
#[ApiResource(
operations: [
new GetCollection(),
new GetCollection(
uriTemplate: "/gardens/my-gardens"
security: "is_granted('ROLE_USER')"
controller: MyGardenController.php
)
],
security: "is_granted('ROLE_ADMIN')"
)]
class Garden {}
And inside
class MyGardenController extends AbstractController
{
public function __construct(
private Security $security,
private GardenRepository $gardenRepository,
)
{}
public function __invoke()
{
return $this->gardenRepository->findBy(['user' => $this->security->getUser());
}
}
So ! What does it do?
By default, the Garden entity is only accessible to ADMIN.
So by default, /api/gardens cant be accessed by a non admin user.
But, /api/gardens/my-gardens as a custom controller returns only a garden linked to the currently connected user.
Just call a different endpoint based on the user role on your front end.
But if you want to keep one endpoint, you could do this inside the custom controller :
public function __invoke()
{
if($this->isGranted('ROLE_ADMIN')){
return $this->gardenRepository->findAll();
}
return $this->gardenRepository->findBy(['user' => $this->security->getUser());
}

How to create an entity when another is updating

I have two entities, the first is called Registration and is intended to store the user data while a confirmation email is used to activate the account. The second one is called Account and is the actual entity storing the user data that was previously in Registration.
I want this second entity to be created/persisted in the DB only after the user confirms its registration, at that stage the Registration entity will turn its flag isActive to true and will transfer the required data to the be persisted Account.
How to do that using the API platform?
Well, I found a way, although I don't know if it is the best approach: using a custom state processor.
Say we have two entities: Registration and Account. Registration will store the attempt to create an account while Account will store relevant data about the account once it is created. The are not related in any 'declared' way but the second depends on the first.
The logic is here: "How to use a custom state processor to create an entity when another is updating"
#[ApiResource]
#[Patch(
//...
processor: ActivateAccount::class
)]
class Registration
{
//...
}
and here is the ActivateAccount class:
class ActivateAccount implements ProcessorInterface
{
private ProcessorInterface $decorated;
private EntityManagerInterface $manager;
public function __construct(
ProcessorInterface $decorated,
EntityManagerInterface $manager
) {
$this->decorated = $decorated;
$this->manager = $manager;
}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
) {
if (!$data->isIsActive()) // we only want to create a new account if current registration is inactive
{
$account = new Account();
$account->setEmail($data->getEmail()); // get the email from the registration data
$account->setPassword($data->getPassword()); // get password from registration data, already hashed
$this->manager->persist($account); // save the account to DB
$this->manager->flush();
$data->setIsActive(true); // update the registration to prevent the attempt to create another caacount with this data
}
$this->decorated->process(
$data,
$operation,
$uriVariables,
$context
); // finally process the registration, this really means save the updated entity
}
}
This custom processor needs to be wired in services configuration:
services:
App\State\ActivateAccount:
bind:
$decorated: '#api_platform.doctrine.orm.state.persist_processor'
And the job is done!

Cakephp 3.7.x How to retrieve user data? using Authentication component

I am using cakephp 3.7.2 with Authentication component
$user = $this->Authentication->getIdentity();
prints:
object(Authentication\Identity) {
'config' => [
'fieldMap' => [
'id' => 'id'
]
],
'data' => object(App\Model\Entity\User) {
'id' => (int) 1,
'email' => 'aa.aaa#gmail.com',
...
}
}
I have tried $user->data but it doesn't work.
How to print user data?
Authentication Component
So I have figured it out.
In User Entity class
Add use Authentication\IdentityInterface;
and then implement the IdentityInterface.
class User extends Entity implements IdentityInterface
{
blablabla...
yale yale yale ...
Now you can print:
$user = $this->Authentication->getIdentity();
debug($user->id);
As per Authentication component documentation
The identity object is returned by the service and made available in
the request. The object provides a method getIdentifier() that can be
called to get the id of the current log in identity.
You can use this accordingly as below to get the user data:
// Service
$identity = $authenticationService
->getIdentity()
->getIdentifier()
// Component
$identity = $this->Authentication
->getIdentity()
->getIdentifier();
// Request
$identity = $this->request
->getAttribute('identity')
->getIdentifier();
The identity object provides ArrayAccess but as well a get() method to
access data. It is strongly recommended to use the get() method over
array access because the get method is aware of the field mapping.
For eg. to access email and username from the identity you can use the below code.
$identity->get('email'); // to access email
$identity->get('username'); // to access username
Reference link: Authentication -> Docs -> Identity Object
Hope this will help.
I´m using AuthComponent in CakePHP 4.xxx.
I can get User data i.e. in a view with
$user = $this->getRequest()->getAttribute('identity');
I found the Information on: http://gotchahosting.com/blog/category/cakephp/4002624
Maybe it helps someone who is looking for information about this in CakePHP4

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

CakePHP Accessing Passed Form Data in table afterSave()

SOLVED! I created a $password property in my Entity class thinking I needed to. The password field is added to the Entity through the entire request. None of the code posted below had to be changed.
I am using friendsofcake/Crud to build a REST api. When I save my User models, I have an afterSave() event that does the password salting and hashing. The POST data sent to my UsersController:add() method includes a ['password'] parameter, but my UsersTable has ['hash','salt'] fields.
Basically, I need to know if and where the POST['password'] parameter is, and if it is available in the UsersTable afterSave() method. Otherwise, my system is hashing and salting empty password strings!
I am new to CakePHP and having trouble finding straightforward answers for 3.*.
Here is the afterSave() method in my UsersTable class.
/**
* Updates Meta information after being created.
*/
public function afterSave(Event $event, Entity $entity, ArrayObject $options)
{
if($entity->returnAfterSave) return;
// update the public id
if(empty($entity->public_id))
{
$hashids = new Hashids(static::HASH_SALT, static::HASH_LENGTH);
$entity->public_id = $hashids->encode($entity->id);
}
// generate a password salt
if(empty($entity->salt))
{
$entity->generateSalt();
}
// salt and hash the password
if(empty($entity->hash))
{
// NOTE: I figured since the form data
// is loaded into the entity, I created a $password property
// in my User Entity class. This assumption may be the problem??
// Update: It was a bad assumption. Removing said property solved issue.
$entity->hashPassword();
}
// save the changes
$entity->returnAfterSave = true;
$this->save($entity);
}
Also, I understand that this code will not properly save any new passwords, I will update the code for password changes after my creates are working properly.